hub v0.14.0: passphrase-authed host enrollment (Day-0 option C)
New POST /api/v1/host-enroll (handleHostEnroll): X-Retrieval-Password authed,
body {customer_id} -> {host_id, api_key}. Mint-once-reuse (201 first, 200
reuse) so re-running the host-bootstrap never orphans a running agent's key;
auth checked before any mint. Backed by new Store.GetHostByCustomer
(ORDER BY updated_at DESC LIMIT 1, idx_hosts_customer).
GET /config/{id} and global-key POST /admin/hosts left untouched. Exact-match
route (path == "/host-enroll") to avoid the /hosts/ prefix collision.
Tests: host_enroll_test.go (mint/reuse/401-no-mint/404/400) + GetHostByCustomer
store test; companion red-proof verified always-mint fails the reuse assertion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtXesNa2LGbMmE4DNL6SE7
This commit is contained in:
@@ -45,6 +45,46 @@ func TestUpsertHost_AndLookup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHostByCustomer(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
|
||||
// none → nil, nil
|
||||
got, err := s.GetHostByCustomer("c1")
|
||||
if err != nil || got != nil {
|
||||
t.Fatalf("no host: got %+v / %v (want nil,nil)", got, err)
|
||||
}
|
||||
|
||||
// one → that host
|
||||
if err := s.UpsertHost(&Host{HostID: "c1-aaa111", CustomerID: "c1", APIKey: "k1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err = s.GetHostByCustomer("c1")
|
||||
if err != nil || got == nil || got.HostID != "c1-aaa111" || got.APIKey != "k1" {
|
||||
t.Fatalf("one host: got %+v / %v", got, err)
|
||||
}
|
||||
|
||||
// two for the same customer → most-recently-updated wins (never a duplicate on reuse).
|
||||
// updated_at is second-resolution, so set it explicitly to make the ordering deterministic.
|
||||
if err := s.UpsertHost(&Host{HostID: "c1-bbb222", CustomerID: "c1", APIKey: "k2"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.db.Exec(`UPDATE hosts SET updated_at='2026-01-01 00:00:00' WHERE host_id='c1-aaa111'`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.db.Exec(`UPDATE hosts SET updated_at='2026-06-26 00:00:00' WHERE host_id='c1-bbb222'`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err = s.GetHostByCustomer("c1")
|
||||
if err != nil || got == nil || got.HostID != "c1-bbb222" {
|
||||
t.Fatalf("two hosts: want most-recent c1-bbb222, got %+v / %v", got, err)
|
||||
}
|
||||
|
||||
// a different customer is unaffected
|
||||
if other, err := s.GetHostByCustomer("c2"); err != nil || other != nil {
|
||||
t.Fatalf("other customer: got %+v / %v (want nil,nil)", other, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveHostReport_BumpsRealityPreservesIntent(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
if err := s.UpsertHost(&Host{HostID: "h1", CustomerID: "c1", APIKey: "k1"}); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user