package web // v0.67.0 (R-36 sub-item) — the self-bind link is minted automatically at customer creation and at // RESET completion, so the box's console banner („nyisd meg az e-mailben kapott link") is already // true when the customer first reads it, instead of true-once-the-operator-remembers. // // The load-bearing property is that auto-minting NEVER fails the operation it rides on: a customer // create that provisioned Cloudflare, offsite and PBS must not 500 because a courtesy email // bounced. import ( "testing" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) func seedForMint(t *testing.T, st *store.Store, id, email string) { t.Helper() if err := st.SaveCustomerConfig(&store.CustomerConfig{ CustomerID: id, CustomerName: id, Domain: id + ".hu", RetrievalPassword: "pw", APIKey: "k", Status: "active", Email: email, }); err != nil { t.Fatal(err) } } func liveTokenCount(t *testing.T, st *store.Store, customerID string) int { t.Helper() n, err := st.CountSelfBindTokens(customerID) if err != nil { t.Fatalf("count self-bind tokens: %v", err) } return n } func TestAutoMintSelfBindLink(t *testing.T) { t.Run("mints and sends when an email is registered", func(t *testing.T) { s, st := newTestServer(t) m := &stubMailer{} s.SetSelfBindMailer(m) seedForMint(t, st, "acme", "ops@acme.hu") s.autoMintSelfBindLink("acme", "ops@acme.hu", "customer creation") if m.link == "" { t.Fatal("no self-bind link was sent") } if liveTokenCount(t, st, "acme") != 1 { t.Error("expected exactly one live capability token after auto-mint") } }) t.Run("no registered email → nothing minted (F1 honoured)", func(t *testing.T) { s, st := newTestServer(t) m := &stubMailer{} s.SetSelfBindMailer(m) seedForMint(t, st, "acme", "") s.autoMintSelfBindLink("acme", "", "customer creation") if m.link != "" { t.Error("a link was sent for a customer with no registered address") } if liveTokenCount(t, st, "acme") != 0 { t.Error("a token was minted that nobody could ever receive — F1 says mint nothing") } }) t.Run("send failure invalidates the token (F2 honoured)", func(t *testing.T) { s, st := newTestServer(t) s.SetSelfBindMailer(&stubMailer{fail: true}) seedForMint(t, st, "acme", "ops@acme.hu") s.autoMintSelfBindLink("acme", "ops@acme.hu", "customer creation") if liveTokenCount(t, st, "acme") != 0 { t.Error("send failed but a live capability token was left behind — F2 says delete it") } }) t.Run("RESET does not leave a pre-RESET link live", func(t *testing.T) { // PurgeCustomerResetDBState does NOT clear selfbind_tokens, so a link minted before a reset // would otherwise survive it. Auto-mint must guarantee that afterwards the only live token // is one it just issued — or none. s, st := newTestServer(t) s.SetSelfBindMailer(&stubMailer{}) seedForMint(t, st, "acme", "ops@acme.hu") // A link exists from before the reset. s.autoMintSelfBindLink("acme", "ops@acme.hu", "customer creation") if liveTokenCount(t, st, "acme") != 1 { t.Fatal("setup: expected a pre-reset token") } // Now the customer loses their address and a RESET happens: the skip path must still clear it. s.autoMintSelfBindLink("acme", "", "RESET completion") if liveTokenCount(t, st, "acme") != 0 { t.Error("a capability link minted BEFORE the reset is still live after it") } }) t.Run("no mailer configured → no token, no panic", func(t *testing.T) { s, st := newTestServer(t) // SetSelfBindMailer never called seedForMint(t, st, "acme", "ops@acme.hu") s.autoMintSelfBindLink("acme", "ops@acme.hu", "customer creation") if liveTokenCount(t, st, "acme") != 0 { t.Error("a token was minted with no mailer to deliver it") } }) }