package main import ( "encoding/hex" "os" "path/filepath" "testing" cs "fn-registry/functions/cybersecurity" "github.com/enmanuel/unibus/pkg/client" "github.com/enmanuel/unibus/pkg/frame" "github.com/enmanuel/unibus/pkg/membership" ) // openTestStore opens a fresh SQLite membership store in a temp dir. func openTestStore(t *testing.T) membership.Store { t.Helper() store, err := membership.Open(filepath.Join(t.TempDir(), "unibus.db")) if err != nil { t.Fatalf("open store: %v", err) } t.Cleanup(func() { store.Close() }) return store } // TestProvisionBotGolden is the happy path: provisioning a bot registers it in the // allowlist with the right handle and role, AND writes a 0600 credentials file // that LoadIdentity reconstructs into the same identity — so a worker/clientcheck // binary pointed at the file connects as exactly this user with no extra step. func TestProvisionBotGolden(t *testing.T) { store := openTestStore(t) out := filepath.Join(t.TempDir(), "notifier.id") signPubHex, endpoint, err := provisionBot(store, "notifier", membership.RoleMember, out) if err != nil { t.Fatalf("provisionBot: %v", err) } // Registered in the allowlist with the right handle/role/status. u, err := store.GetUser(signPubHex) if err != nil { t.Fatalf("get provisioned user: %v", err) } if u.Handle != "notifier" || u.Role != membership.RoleMember || u.Status != membership.StatusActive { t.Fatalf("provisioned user row wrong: %+v", u) } // And it shows up in user list (the `user list` surface). users, err := store.ListUsers() if err != nil { t.Fatalf("list users: %v", err) } found := false for _, x := range users { if x.SignPub == signPubHex { found = true } } if !found { t.Fatalf("provisioned bot missing from user list: %+v", users) } // Credentials file exists, is 0600, and round-trips through LoadIdentity to the // same signing key + endpoint (no-friction contract). info, err := os.Stat(out) if err != nil { t.Fatalf("stat out file: %v", err) } if perm := info.Mode().Perm(); perm != 0o600 { t.Fatalf("out file perms = %o, want 600", perm) } id, err := client.LoadIdentity(out) if err != nil { t.Fatalf("LoadIdentity(out): %v", err) } if got := hex.EncodeToString(id.SignPub); got != signPubHex { t.Fatalf("loaded sign_pub %q != provisioned %q", got, signPubHex) } if got := frame.EndpointID(id.SignPub); got != endpoint { t.Fatalf("loaded endpoint %q != reported %q", got, endpoint) } } // TestProvisionBotDefaultRole: an empty role defaults to member. func TestProvisionBotDefaultRole(t *testing.T) { store := openTestStore(t) out := filepath.Join(t.TempDir(), "bot.id") signPubHex, _, err := provisionBot(store, "defrole", "", out) if err != nil { t.Fatalf("provisionBot: %v", err) } u, err := store.GetUser(signPubHex) if err != nil { t.Fatalf("get user: %v", err) } if u.Role != membership.RoleMember { t.Fatalf("empty role should default to member, got %q", u.Role) } } // TestProvisionBotSignPubAlreadyRegistered is the error path: provisioning an // identity whose signing key is already in the allowlist fails with a clear error // (not a panic) AND does not write a credentials file (no half-provisioned bot). func TestProvisionBotSignPubAlreadyRegistered(t *testing.T) { store := openTestStore(t) // Pre-register a key, then try to provision a bot with that SAME identity. id, err := cs.GenerateIdentity() if err != nil { t.Fatalf("generate identity: %v", err) } signPubHex := hex.EncodeToString(id.SignPub) if err := store.AddUser(signPubHex, "preexisting", membership.RoleMember); err != nil { t.Fatalf("pre-register: %v", err) } out := filepath.Join(t.TempDir(), "dup.id") _, _, err = provisionBotWithIdentity(store, id, "dupbot", membership.RoleMember, out) if err == nil { t.Fatalf("provisioning an already-registered key should error") } if _, statErr := os.Stat(out); !os.IsNotExist(statErr) { t.Fatalf("credentials file must NOT be written on a duplicate-key failure (stat err = %v)", statErr) } } // TestProvisionBotOutExists is the other error path: an existing --out file is // refused BEFORE the store is mutated, so the run leaves no orphan user behind. func TestProvisionBotOutExists(t *testing.T) { store := openTestStore(t) out := filepath.Join(t.TempDir(), "taken.id") if err := os.WriteFile(out, []byte("preexisting credentials"), 0o600); err != nil { t.Fatalf("seed out file: %v", err) } _, _, err := provisionBot(store, "clobber", membership.RoleMember, out) if err == nil { t.Fatalf("provisioning over an existing out file should error") } // The store must be untouched: no user was registered. users, err := store.ListUsers() if err != nil { t.Fatalf("list users: %v", err) } if len(users) != 0 { t.Fatalf("no user should be registered when out exists, got %+v", users) } }