package console import ( "context" "testing" "time" "github.com/carlosframework/platform/internal/mail" ) func mailRows(t *testing.T, s *Store, table string) int { t.Helper() var n int if err := s.db.QueryRow(`SELECT COUNT(*) FROM ` + table).Scan(&n); err != nil { t.Fatal(err) } return n } // Gated, every mail-table writer goes through withMailAppSQL: with no // account row and no bound mutation runner there is no permit to be had, so // each refuses and nothing lands. None of them reaches a raw statement. func TestMailSQLWritersRefuseWithoutPermit(t *testing.T) { s := openGatedStore(t) ctx := gateCtx(t) now := time.Date(2026, 10, 7, 12, 0, 0, 0, time.UTC) ev := mail.Event{Type: mail.EventBounce, MessageID: "m1", At: now} if err := s.RecordMailEvent(ctx, "bab", "titogo", mail.SenderAppID, "2026-10-07", mail.EventSend); err == nil { t.Fatal("RecordMailEvent wrote without a permit") } if err := s.SetMailEventHook(ctx, "bab", "titogo", "/mail/events", "a@b", now); err == nil { t.Fatal("SetMailEventHook wrote without a permit") } if _, err := s.DeleteMailEventHook(ctx, "bab", "titogo", now); err == nil { t.Fatal("DeleteMailEventHook wrote without a permit") } if err := s.EnqueueMailEventDelivery(ctx, "bab", "titogo", "n1", []byte(`{}`), now); err == nil { t.Fatal("EnqueueMailEventDelivery wrote without a permit") } if err := s.abandonMailEventDeliveries(ctx, "bab", "titogo", "x", now); err == nil { t.Fatal("abandonMailEventDeliveries wrote without a permit") } if err := s.recordMailEventAttempt(ctx, mailEventDelivery{ID: 1, Account: "bab", App: "titogo"}, 200, "", now); err == nil { t.Fatal("recordMailEventAttempt wrote without a permit") } h := mailEventHookRow{Account: "bab", App: "titogo", MailEventHook: MailEventHook{FailingSince: now.Add(-time.Hour)}} if err := s.clearMailEventFailing(ctx, h); err == nil { t.Fatal("clearMailEventFailing wrote without a permit") } if _, err := s.noteMailEventTurn(ctx, h, true, "", now); err == nil { t.Fatal("noteMailEventTurn wrote without a permit") } _ = s.RecordMailMessageEvent(ctx, "bab", "titogo", "n1", ev, true, now) for _, table := range mailAppTables { if n := mailRows(t, s, table); n != 0 { t.Fatalf("%s holds %d rows written without a permit", table, n) } } // An invalid name is refused before any lookup. if err := s.withMailAppSQL(ctx, "BAB", "titogo", "sql.test", func(*Store) error { return nil }); err == nil { t.Fatal("invalid sqid accepted") } if err := s.withMailAppSQL(ctx, "bab", "", "sql.test", func(*Store) error { return nil }); err == nil { t.Fatal("empty app accepted") } } // Flag-off (no gate) the writers behave exactly as before. func TestMailSQLWritersUnchangedFlagOff(t *testing.T) { s := openTestStore(t) ctx := context.Background() now := time.Date(2026, 10, 7, 12, 0, 0, 0, time.UTC) if err := s.SetMailEventHook(ctx, "bab", "titogo", "/mail/events", "a@b", now); err != nil { t.Fatal(err) } h, err := s.MailEventHookFor(ctx, "bab", "titogo") if err != nil || h == nil || h.Path != "/mail/events" { t.Fatalf("hook: %+v %v", h, err) } if err := s.RecordMailEvent(ctx, "bab", "titogo", "", "2026-10-07", mail.EventSend); err != nil { t.Fatal(err) } c, err := s.MailCounts(ctx, "bab", "titogo", "", "2026-10-07") if err != nil || c.Sends != 1 { t.Fatalf("counts: %+v %v", c, err) } if err := s.EnqueueMailEventDelivery(ctx, "bab", "titogo", "n1", []byte(`{}`), now); err != nil { t.Fatal(err) } rows, err := s.dueMailEventDeliveriesFor(ctx, "bab", "titogo", now, 5) if err != nil || len(rows) != 1 { t.Fatalf("due: %v %v", rows, err) } // An attempt names its row's own app: a row id with another app's // names updates nothing. forged := rows[0] forged.App = "other" if err := s.recordMailEventAttempt(ctx, forged, 200, "", now); err != nil { t.Fatal(err) } if st, err := s.MailEventPushStatusFor(ctx, "bab", "titogo", now.Add(-time.Hour)); err != nil || st.Delivered != 0 || st.Pending != 1 { t.Fatalf("forged attempt landed: %+v %v", st, err) } if err := s.recordMailEventAttempt(ctx, rows[0], 200, "", now); err != nil { t.Fatal(err) } if st, err := s.MailEventPushStatusFor(ctx, "bab", "titogo", now.Add(-time.Hour)); err != nil || st.Delivered != 1 { t.Fatalf("attempt: %+v %v", st, err) } was, err := s.DeleteMailEventHook(ctx, "bab", "titogo", now) if err != nil || !was { t.Fatalf("delete: %v %v", was, err) } }