package registry import ( "database/sql" "errors" "path/filepath" "regexp" "strings" "sync" "testing" ) // Distinct, realistic-shape (64 lowercase hex) token hashes for tests — // AddFleetBox/SetFleetBoxToken now reject anything else. var ( tokenHashA = strings.Repeat("a", 64) tokenHashB = strings.Repeat("b", 64) tokenHashC = strings.Repeat("c", 64) ) // TestFleetRoundTrips pins UpsertFleet/GetFleet/ListFleets against the exact // shape later tasks depend on (task-1-brief.md's Fleet struct). func TestFleetRoundTrips(t *testing.T) { reg := openTestRegistry(t) want := Fleet{Name: "acme", Account: "amadan", Bucket: "acme-data", CreatedAt: 1700000000} if err := reg.UpsertFleet(want); err != nil { t.Fatal(err) } got, err := reg.GetFleet("acme") if err != nil { t.Fatal(err) } if got == nil { t.Fatal("GetFleet returned nil for an upserted fleet") } if *got != want { t.Fatalf("round trip mismatch:\n got %+v\n want %+v", *got, want) } list, err := reg.ListFleets() if err != nil { t.Fatal(err) } if len(list) != 1 || list[0] != want { t.Fatalf("ListFleets = %+v, want [%+v]", list, want) } } func TestGetFleetMissingIsNilNil(t *testing.T) { reg := openTestRegistry(t) got, err := reg.GetFleet("nope") if err != nil || got != nil { t.Fatalf("GetFleet(missing) = %v, %v; want nil, nil", got, err) } } // TestCreateFleetInsertOnly: the ordinary sequential case — first create // wins, a second with the same name is refused without touching the // existing row. func TestCreateFleetInsertOnly(t *testing.T) { reg := openTestRegistry(t) created, err := reg.CreateFleet(Fleet{Name: "acme", Account: "amadan", Bucket: "acme-data"}) if err != nil || !created { t.Fatalf("first CreateFleet: created=%v err=%v, want true, nil", created, err) } created, err = reg.CreateFleet(Fleet{Name: "acme", Account: "intruder", Bucket: "stolen"}) if err != nil || created { t.Fatalf("second CreateFleet (same name): created=%v err=%v, want false, nil", created, err) } got, err := reg.GetFleet("acme") if err != nil || got == nil || got.Account != "amadan" || got.Bucket != "acme-data" { t.Fatalf("GetFleet after a refused create = %+v, %v, want the FIRST caller's row untouched", got, err) } } // TestCreateFleetRaceInsertOnly is the property the console's // handleAPIFleetNew now depends on entirely (no more read-then-write): two // callers racing the SAME never-before-seen name — from two DIFFERENT // accounts — must never both win, and the row that survives must belong to // whichever one did. This is what makes UpsertFleet's ON CONFLICT DO UPDATE // (which CreateFleet deliberately does not use) unreachable from a genuine // race here. func TestCreateFleetRaceInsertOnly(t *testing.T) { reg := openTestRegistry(t) var wg sync.WaitGroup created := make([]bool, 2) errs := make([]error, 2) accounts := []string{"aaaa", "bbbb"} wg.Add(2) for i := 0; i < 2; i++ { go func(i int) { defer wg.Done() created[i], errs[i] = reg.CreateFleet(Fleet{Name: "acme", Account: accounts[i], Bucket: accounts[i] + "-data"}) }(i) } wg.Wait() for i, err := range errs { if err != nil { t.Fatalf("CreateFleet[%d]: %v", i, err) } } if created[0] == created[1] { t.Fatalf("created = %v, %v — want exactly one true", created[0], created[1]) } winner := accounts[0] if created[1] { winner = accounts[1] } got, err := reg.GetFleet("acme") if err != nil || got == nil { t.Fatalf("GetFleet after the race: %+v, %v", got, err) } if got.Account != winner { t.Fatalf("surviving row account = %q, want the winner's %q", got.Account, winner) } } // TestFleetBoxRoundTrips pins AddFleetBox/ListFleetBoxes against the exact // FleetBox shape later tasks depend on. func TestFleetBoxRoundTrips(t *testing.T) { reg := openTestRegistry(t) if err := reg.UpsertFleet(Fleet{Name: "acme", Account: "amadan", Bucket: "acme-data"}); err != nil { t.Fatal(err) } want := FleetBox{Fleet: "acme", Label: "pi-1", TokenHash: tokenHashA, Status: "active", AddedAt: 1700000001} if err := reg.AddFleetBox(want); err != nil { t.Fatal(err) } boxes, err := reg.ListFleetBoxes("acme") if err != nil { t.Fatal(err) } if len(boxes) != 1 || boxes[0] != want { t.Fatalf("ListFleetBoxes = %+v, want [%+v]", boxes, want) } } // TestAddFleetBoxRejectsUnknownFleet: no FK, so this is the only rail // against an orphan box — one authenticating a channel for a fleet with no // account/bucket record at all. func TestAddFleetBoxRejectsUnknownFleet(t *testing.T) { reg := openTestRegistry(t) if err := reg.AddFleetBox(FleetBox{Fleet: "ghost", Label: "pi-1", TokenHash: tokenHashA, Status: "active"}); err == nil { t.Fatal("AddFleetBox against a never-upserted fleet: got nil error, want one") } } // TestAddFleetBoxRejectsDuplicateActive is the brief's core invariant: two // boxes cannot hold the same (fleet, label) channel identity while both are // active. func TestAddFleetBoxRejectsDuplicateActive(t *testing.T) { reg := openTestRegistry(t) if err := reg.UpsertFleet(Fleet{Name: "acme", Account: "amadan", Bucket: "acme-data"}); err != nil { t.Fatal(err) } box := FleetBox{Fleet: "acme", Label: "pi-1", TokenHash: tokenHashA, Status: "active"} if err := reg.AddFleetBox(box); err != nil { t.Fatal(err) } err := reg.AddFleetBox(box) if err == nil { t.Fatal("AddFleetBox on a duplicate active (fleet,label): got nil error, want one") } // Distinguishable from any other failure via errors.Is, not string // matching — the property console/fleets.go's handler now relies on to // tell an ordinary conflict apart from a genuine infrastructure error. if !errors.Is(err, ErrFleetBoxActive) { t.Fatalf("AddFleetBox on a duplicate active: err = %v, want it to wrap ErrFleetBoxActive", err) } } // TestAddFleetBoxReactivatesADetachedLabel: the brief only forbids a // duplicate ACTIVE box, implying a detached label can be re-added — the path // a re-provisioned box takes. func TestAddFleetBoxReactivatesADetachedLabel(t *testing.T) { reg := openTestRegistry(t) if err := reg.UpsertFleet(Fleet{Name: "acme", Account: "amadan", Bucket: "acme-data"}); err != nil { t.Fatal(err) } box := FleetBox{Fleet: "acme", Label: "pi-1", TokenHash: tokenHashA, Status: "active"} if err := reg.AddFleetBox(box); err != nil { t.Fatal(err) } if err := reg.SetFleetBoxStatus("acme", "pi-1", "detached"); err != nil { t.Fatal(err) } reAdd := FleetBox{Fleet: "acme", Label: "pi-1", TokenHash: tokenHashB, Status: "active"} if err := reg.AddFleetBox(reAdd); err != nil { t.Fatalf("AddFleetBox over a detached label: %v", err) } boxes, err := reg.ListFleetBoxes("acme") if err != nil { t.Fatal(err) } if len(boxes) != 1 || boxes[0].TokenHash != tokenHashB || boxes[0].Status != "active" { t.Fatalf("re-add over a detached label = %+v, want reactivated with the new token", boxes) } } // TestFleetChannelAuthOmitsDetachedBoxes pins the exact contract later tasks // rely on: channel name "/