package httpapi import ( "bytes" "context" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/descrybe/descrybe-v2/apps/api/internal/auth" "github.com/descrybe/descrybe-v2/apps/api/internal/mail" "github.com/google/uuid" ) type recordingMailer struct { enabled bool sent []mail.Message err error } func (m *recordingMailer) Enabled() bool { return m.enabled } func (m *recordingMailer) Send(msg mail.Message) error { if m.err != nil { return m.err } m.sent = append(m.sent, msg) return nil } func TestHandleAdminSendSetPasswordEmailsUnauthorized(t *testing.T) { t.Parallel() s := &Server{Mail: &recordingMailer{enabled: true}, Auth: &auth.Service{}} req := httptest.NewRequest(http.MethodPost, "/api/admin/emails/set-password", bytes.NewBufferString("{}")) rec := httptest.NewRecorder() s.handleAdminSendSetPasswordEmails(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("status=%d want 401", rec.Code) } } func TestHandleAdminSendSetPasswordEmailsMailerRequired(t *testing.T) { t.Parallel() adminID := uuid.New() s := &Server{Auth: &auth.Service{}} ctx := context.WithValue(context.Background(), ctxUserID, adminID) req := httptest.NewRequest(http.MethodPost, "/api/admin/emails/set-password", bytes.NewBufferString("{}")) req = req.WithContext(ctx) rec := httptest.NewRecorder() s.handleAdminSendSetPasswordEmails(rec, req) if rec.Code != http.StatusServiceUnavailable { t.Fatalf("status=%d want 503", rec.Code) } } func TestHandleAdminSendSetPasswordEmailsRateLimited(t *testing.T) { t.Parallel() adminID := uuid.New() s := &Server{ Mail: &recordingMailer{enabled: true}, Auth: &auth.Service{}, } s.ensureAdminSetPasswordLimiters() s.adminSetPasswordReqRL = newSlidingWindowLimiter(1, time.Minute) reqKey := "admin-set-password:" + adminID.String() if !s.adminSetPasswordReqRL.allow(reqKey) { t.Fatal("setup: expected first allow") } ctx := context.WithValue(context.Background(), ctxUserID, adminID) req := httptest.NewRequest(http.MethodPost, "/api/admin/emails/set-password", bytes.NewBufferString("{}")) req = req.WithContext(ctx) rec := httptest.NewRecorder() s.handleAdminSendSetPasswordEmails(rec, req) if rec.Code != http.StatusTooManyRequests { t.Fatalf("status=%d want 429 body=%s", rec.Code, rec.Body.String()) } if rec.Header().Get("Retry-After") == "" { t.Fatal("expected Retry-After header") } raw := rec.Body.String() if strings.Contains(raw, "@") { t.Fatalf("rate-limit response must not include email addresses: %s", raw) } } func TestHandleAdminSendSetPasswordEmailsSendRateLimitedSingleUser(t *testing.T) { t.Parallel() adminID := uuid.New() targetID := uuid.New() s := &Server{ Mail: &recordingMailer{enabled: true}, Auth: &auth.Service{}, } s.ensureAdminSetPasswordLimiters() s.adminSetPasswordReqRL = newSlidingWindowLimiter(10, time.Minute) s.adminSetPasswordSendRL = newSlidingWindowLimiter(1, time.Minute) sendKey := "admin-set-password-send:" + adminID.String() if !s.adminSetPasswordSendRL.allow(sendKey) { t.Fatal("setup: expected first send allow") } body := `{"user_id":"` + targetID.String() + `"}` ctx := context.WithValue(context.Background(), ctxUserID, adminID) req := httptest.NewRequest(http.MethodPost, "/api/admin/emails/set-password", bytes.NewBufferString(body)) req = req.WithContext(ctx) rec := httptest.NewRecorder() s.handleAdminSendSetPasswordEmails(rec, req) if rec.Code != http.StatusTooManyRequests { t.Fatalf("status=%d want 429 body=%s", rec.Code, rec.Body.String()) } if rec.Header().Get("Retry-After") == "" { t.Fatal("expected Retry-After header") } }