package main import ( "path/filepath" "testing" "lchat/internal/bot" "lchat/internal/e2ee" ) // The zero-touch provisioning chain end-to-end against the real router: // mintAgentInvite (the Go port of agent/mint-invite.mjs, #856) mints an agent // invite via the admin API, and bot.EnsureSession (#806) redeems it and // persists a session — the exact pair add-bot and the activator co-spawn rely // on. If either side's HKDF walk drifted, the claim would fail here. func TestMintAgentInviteThenSelfProvision(t *testing.T) { ts, s := newTestServer(t) secret, err := mintAgentInvite(ts.URL, "admintok", "Eleven", "Your guide to the app.", "") if err != nil { t.Fatalf("mintAgentInvite: %v", err) } if secret == "" { t.Fatal("mintAgentInvite returned an empty secret") } // The server must hold only the derived id, never the secret itself. inv, err := e2ee.DeriveInvite(secret) if err != nil { t.Fatalf("derive: %v", err) } if _, found, err := s.db.getKeyInvite(inv.ID); err != nil || !found { t.Fatalf("minted invite not on the server under its derived id: found=%v err=%v", found, err) } path := filepath.Join(t.TempDir(), "eleven-session.json") sess, err := bot.EnsureSession(ts.URL, path, secret, "Eleven", t.Logf) if err != nil { t.Fatalf("EnsureSession: %v", err) } if sess == nil || sess.Token == "" || sess.UserPriv == "" { t.Fatalf("self-provisioning did not yield a full session: %+v", sess) } // The invite is one-time: a second fresh boot against the same secret must // fail loudly, not mint a second identity. if _, err := bot.EnsureSession(ts.URL, filepath.Join(t.TempDir(), "again.json"), secret, "Eleven", nil); err == nil { t.Fatal("re-redeeming a spent invite should fail") } } // The admin API refuses agent invites when bots are off — the mint surfaces // that as an error instead of writing a half-staged env. func TestMintAgentInviteRespectsBotsGate(t *testing.T) { ts, s := newTestServer(t) if err := s.db.setBotsEnabled(false); err != nil { t.Fatal(err) } if _, err := mintAgentInvite(ts.URL, "admintok", "Eleven", "", ""); err == nil { t.Fatal("expected the bots-off gate to reject the mint") } }