package console
import (
"net/http"
"net/url"
"reflect"
"strings"
"testing"
"time"
"github.com/carlosframework/platform/internal/registry"
"github.com/carlosframework/platform/internal/status"
)
func TestInstanceGroupingPaginatesWithoutLoss(t *testing.T) {
rows := []InstanceRow{
{Host: "a.example.com", Channel: "stable", HealthWord: "Running"},
{Host: "b.example.com", Channel: "edge", HealthWord: "Asleep"},
{Host: "c.example.com", Channel: "stable", HealthWord: "Running"},
{Host: "d.example.com", Channel: "edge", HealthWord: "Running"},
}
q := instanceQuery{Group: true}
var got []string
for i := 0; i < 4; i++ {
p := pageInstanceRows(rows, q, 1)
for _, r := range p.Rows {
got = append(got, r.Host)
}
if p.NextCursor == "" {
break
}
q.Cursor = p.NextCursor
u, err := url.Parse(p.NextURL)
if err != nil {
t.Fatal(err)
}
if u.Query().Get("group") != "channel" {
t.Fatal(p.NextURL)
}
}
want := []string{"b.example.com", "d.example.com", "a.example.com", "c.example.com"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("got %v want %v", got, want)
}
p := pageInstanceRows(rows, instanceQuery{Cursor: q.Cursor}, 50)
if len(p.Rows) != 4 {
t.Fatal("changing grouping must reset a cursor from the other order")
}
}
func TestInstanceRowsExcludePlatformAliases(t *testing.T) {
routes := []RouteView{
{Host: "app.example", Account: "ours", Kind: "instance"},
{Host: "static.example", Account: "ours", Kind: "static"},
{Host: "alias.example", Account: "ours", Kind: "static", Platform: true},
{Host: "foreign.example", Account: "theirs", Kind: "instance"},
}
owned := ownedInstanceRoutes(routes, "ours")
if len(owned) != 2 || owned[0].Host != "app.example" || owned[1].Host != "static.example" {
t.Fatalf("owned instance routes = %+v", owned)
}
rows := assembleInstanceRows(&AppView{Routes: routes}, &Account{Sqid: "ours"}, nil)
if len(rows) != 2 || rows[0].Host != "app.example" || rows[1].Host != "static.example" {
t.Fatalf("instance rows = %+v", rows)
}
}
func TestInstanceChannelFilterAndLinks(t *testing.T) {
rows := []InstanceRow{{Host: "a.example.com", Channel: "canary/a+b", HealthWord: "Running"}, {Host: "b.example.com", Channel: "edge", HealthWord: "Asleep"}}
q := instanceQuery{Channel: "canary/a+b", Group: true}
p := pageInstanceRows(rows, q, 50)
if p.Total != 1 || p.Rows[0].Host != "a.example.com" {
t.Fatalf("%+v", p)
}
for _, l := range p.Links {
u, e := url.Parse(l.URL)
if e != nil {
t.Fatal(e)
}
if u.Query().Get("channel") != q.Channel || u.Query().Get("group") != "channel" {
t.Fatal(l.URL)
}
}
q.Channel = "missing"
if p := pageInstanceRows(rows, q, 50); p.Total != 0 {
t.Fatalf("unmatched channel widened: %+v", p)
}
}
func TestInstanceGroupsKeepTargetSeparate(t *testing.T) {
rows := []InstanceRow{{Host: "a.example.com", Channel: "edge", Version: "old"}, {Host: "b.example.com", Channel: "edge", Version: "new"}}
groups := groupInstanceRows(rows[:1], []ChannelView{{Name: "edge", Version: "new"}}, rows)
if len(groups) != 1 || groups[0].Total != 2 || groups[0].TargetVersion != "new" || groups[0].Rows[0].Version != "old" {
t.Fatalf("%+v", groups)
}
}
func TestInstanceGroupCountsRespectActiveFilters(t *testing.T) {
rows := []InstanceRow{
{Host: "a.example.com", Channel: "edge", HealthWord: "Running"},
{Host: "b.example.com", Channel: "edge", HealthWord: "Asleep"},
{Host: "c.example.com", Channel: "stable", HealthWord: "Running"},
}
p := pageInstanceRows(rows, instanceQuery{Group: true, Channel: "edge", Health: "Running"}, 1)
groups := groupInstanceRows(p.Rows, nil, p.Matching)
if len(groups) != 1 || groups[0].Channel != "edge" || groups[0].Total != 1 || len(groups[0].Rows) != 1 {
t.Fatalf("groups = %+v; page = %+v", groups, p)
}
if p.Counts["Running"] != 2 || p.Counts["Asleep"] != 1 {
t.Fatalf("global health counts changed with filters: %+v", p.Counts)
}
}
func TestOwnedInstanceRoutesDoesNotPassOtherAccountsToSelfReport(t *testing.T) {
routes := []RouteView{{Host: "mine.example.com", Account: "bab"}, {Host: "theirs.example.com", Account: "bac"}}
got := ownedInstanceRoutes(routes, "bab")
if len(got) != 1 || got[0].Host != "mine.example.com" {
t.Fatalf("%+v", got)
}
}
func TestInstanceDetailOwnsRouteAndAliases(t *testing.T) {
fx := newConsoleFixture(t)
c := signInPaul(t, fx)
reg, err := registry.Open(fx.regPath)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { reg.Close() })
if err := reg.Upsert(registry.Route{Host: "foreign.example.com", App: "kass", Account: "bac", Kind: "instance", Version: "foreign-version"}); err != nil {
t.Fatal(err)
}
if err := reg.UpsertAlias(registry.RouteAlias{Alias: "alias.example.com", Host: "kass.carlosapp.dev", Account: fx.sqid, App: "kass"}); err != nil {
t.Fatal(err)
}
if err := reg.UpsertAlias(registry.RouteAlias{Alias: "foreign-alias.example.com", Host: "kass.carlosapp.dev", Account: "bac", App: "kass"}); err != nil {
t.Fatal(err)
}
status, _, body := get(t, c, fx.u("/apps/kass/instances/kass.carlosapp.dev?channel=stable&group=channel"))
if status != http.StatusOK {
t.Fatalf("%d: %s", status, body)
}
for _, want := range []string{"kass.carlosapp.dev", "alias.example.com", `/instances?channel=stable&group=channel`, `href="https://kass.carlosapp.dev"`} {
if !strings.Contains(body, want) {
t.Errorf("missing %q", want)
}
}
for _, bad := range []string{"foreign-version", "foreign-alias.example.com", "stamp its unit"} {
if strings.Contains(body, bad) {
t.Errorf("leaked %q", bad)
}
}
for _, host := range []string{"foreign.example.com", "otherapp.carlosapp.dev", "missing.example.com"} {
status, _, body := get(t, c, fx.u("/apps/kass/instances/"+host))
if status != http.StatusNotFound || body != "404 page not found\n" {
t.Fatalf("%s = %d %q", host, status, body)
}
}
}
func TestInstanceDetailRejectsPlatformAlias(t *testing.T) {
fx := newConsoleFixture(t)
c := signInPaul(t, fx)
fx.addRoute(t, registry.Route{Host: "platform-alias.example.com", App: "kass", Account: fx.sqid, Kind: "static", Platform: true})
status, _, body := get(t, c, fx.u("/apps/kass/instances/platform-alias.example.com"))
if status != http.StatusNotFound || body != "404 page not found\n" {
t.Fatalf("platform alias detail = %d %q", status, body)
}
}
func TestInstanceListLinksToDetailAndKeepsHealthShort(t *testing.T) {
fx := newConsoleFixture(t)
c := signInPaul(t, fx)
status, _, body := get(t, c, fx.u("/apps/kass/instances?group=channel&channel=stable"))
if status != http.StatusOK {
t.Fatalf("%d %s", status, body)
}
if !strings.Contains(body, `/instances/kass.carlosapp.dev?channel=stable&group=channel`) {
t.Fatal("missing filtered detail link")
}
if !strings.Contains(body, `href="https://kass.carlosapp.dev"`) {
t.Fatal("missing separate external link")
}
if strings.Contains(body, "stamp its unit") {
t.Fatal("health cell still carries infrastructure instructions")
}
if !strings.Contains(body, `class="instance-group"`) {
t.Fatal("missing grouped table")
}
}
func TestInstanceListKeepsUnknownSelectedChannelVisible(t *testing.T) {
fx := newConsoleFixture(t)
c := signInPaul(t, fx)
status, _, body := get(t, c, fx.u("/apps/kass/instances?channel=retired"))
if status != http.StatusOK {
t.Fatalf("%d %s", status, body)
}
if !strings.Contains(body, ``) {
t.Fatalf("selected unknown channel is invisible: %s", body)
}
if strings.Contains(body, `