package web import ( "net/http/httptest" "net/url" "strings" "testing" "gitea.dooplex.hu/admin/felhom-hub/internal/store" ) // R-509 (operator decision A, 2026-09-15) — the connect e-mail goes out by itself whenever a customer // is left WAITING FOR A BOX. BIGNIGHT: tester-1 had its e-mail added after creation and its previous // host deleted; the box's console promised a mail; the mailbox held 0 messages ten minutes later. func updateEmail(t *testing.T, s *Server, id, email string) { t.Helper() form := url.Values{"customer_name": {id}, "domain": {id + ".hu"}, "email": {email}} req := httptest.NewRequest("POST", "/configs/"+id, strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rr := httptest.NewRecorder() s.handleConfigUpdate(rr, req, id) if rr.Code != 303 { t.Fatalf("config update = %d (%s)", rr.Code, rr.Body.String()) } } // RED-PROOF (run 2026-09-15, recorded in REPORT.md): with the autoMintSelfBindIfWaiting call removed // from handleConfigUpdate, no link went out and this failed at "e-mail set on a customer with no box // sent no link". func TestSelfBind_EmailSetOnWaitingCustomerSendsLink(t *testing.T) { s, st := newTestServer(t) m := &stubMailer{} s.SetSelfBindMailer(m) seedForMint(t, st, "tester", "") updateEmail(t, s, "tester", "tester1@felhom.example") if m.link == "" { t.Fatal("e-mail set on a customer with no box sent no link") } ev, _ := st.GetLatestEventByType("tester", selfBindSentEvent) if ev == nil || !strings.Contains(ev.DetailsJSON, "no box") { t.Fatalf("the send was not recorded with its occasion: %+v", ev) } // The same address saved again → no second mail. m.link = "" updateEmail(t, s, "tester", "tester1@felhom.example") if m.link != "" { t.Fatal("an unchanged e-mail re-sent the link on every save") } } func TestSelfBind_CustomerWithBoxGetsNoLink(t *testing.T) { s, st := newTestServer(t) m := &stubMailer{} s.SetSelfBindMailer(m) seedForMint(t, st, "hasbox", "") if err := st.UpsertHost(&store.Host{HostID: "hasbox-01", CustomerID: "hasbox", APIKey: "h"}); err != nil { t.Fatal(err) } updateEmail(t, s, "hasbox", "owner@hasbox.example") if m.link != "" { t.Fatal("a customer that has a box was mailed a pairing link") } } func TestSelfBind_HostDeleteSendsLink(t *testing.T) { s, st := newTestServer(t) m := &stubMailer{} s.SetSelfBindMailer(m) seedForMint(t, st, "rebuilt", "owner@rebuilt.example") if err := st.UpsertHost(&store.Host{HostID: "rebuilt-01", CustomerID: "rebuilt", APIKey: "h"}); err != nil { t.Fatal(err) } form := url.Values{"confirm_host_id": {"rebuilt-01"}} req := httptest.NewRequest("POST", "/hosts/rebuilt-01/delete", strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") rr := httptest.NewRecorder() s.handleHostDelete(rr, req, "rebuilt-01") if rr.Code != 303 { t.Fatalf("host delete = %d (%s)", rr.Code, rr.Body.String()) } if m.link == "" { t.Fatal("host delete kept the customer waiting for a box and sent no link") } if ev, _ := st.GetLatestEventByType("rebuilt", selfBindSentEvent); ev == nil || !strings.Contains(ev.DetailsJSON, "host delete") { t.Fatalf("host-delete send not recorded: %+v", ev) } }