Files
RenewCMS/test/registration_status_endpoint_http_test.go
T
2026-10-06 22:25:23 +02:00

181 lines
6.5 KiB
Go

package test
import (
"RenewCMS/api/handlers/auth"
validator "RenewCMS/api/pkg/validator"
"RenewCMS/internal/domain/user"
"RenewCMS/internal/infrastructure/useCases"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5"
)
func newRegistrationStatusRouter(userRepo *mockUserRepo, mailRepo *mockMailRepo) (*chi.Mux, *useCases.CreateUserUseCase) {
token := jwtauth.New("HS256", []byte("test-secret"), nil)
hasher := &stubHasher{}
createUseCase := useCases.NewCreateUserUseCase(userRepo, hasher, mailRepo, "https://cms.example.com", discardRecorder)
loginHandler := auth.NewLoginHandler(token, createUseCase, useCases.NewAuthenticateUserUseCase(userRepo, hasher, discardRecorder), true)
r := chi.NewRouter()
r.Route("/v1/auth", func(r chi.Router) {
r.Post("/register", loginHandler.Register)
r.Get("/registration", loginHandler.RegistrationStatus)
})
return r, createUseCase
}
func registrationStatusRequest(router *chi.Mux, cookie *http.Cookie) *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodGet, "/v1/auth/registration", nil)
if cookie != nil {
req.AddCookie(cookie)
}
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
return rec
}
func TestRegistrationStatusEndpointHttp(t *testing.T) {
validator.InitValidator()
t.Run("Registration is reported open while no verified account exists", func(t *testing.T) {
userRepo := newMockUserRepo()
mailRepo := newMockMailRepo()
router, _ := newRegistrationStatusRouter(userRepo, mailRepo)
rec := registrationStatusRequest(router, nil)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String())
}
if rec.Header().Get("Cache-Control") != "no-store" {
t.Errorf("expected Cache-Control no-store, got %q", rec.Header().Get("Cache-Control"))
}
if rec.Body.String() != `{"open":true}` {
t.Errorf("expected body {\"open\":true}, got %q", rec.Body.String())
}
})
t.Run("Registration is reported open while every existing account is unverified", func(t *testing.T) {
userRepo := newMockUserRepo()
userRepo.users[1] = user.User{ID: 1, Username: "alice", Email: "alice@example.com", IsVerified: false}
userRepo.nextID = 1
mailRepo := newMockMailRepo()
router, _ := newRegistrationStatusRouter(userRepo, mailRepo)
rec := registrationStatusRequest(router, nil)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String())
}
if rec.Body.String() != `{"open":true}` {
t.Errorf("expected body {\"open\":true}, got %q", rec.Body.String())
}
})
t.Run("Registration is reported closed once a verified account exists", func(t *testing.T) {
userRepo := newMockUserRepo()
userRepo.users[1] = user.User{ID: 1, Username: "alice", Email: "alice@example.com", IsVerified: true}
userRepo.nextID = 1
mailRepo := newMockMailRepo()
router, _ := newRegistrationStatusRouter(userRepo, mailRepo)
rec := registrationStatusRequest(router, nil)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String())
}
if rec.Header().Get("Cache-Control") != "no-store" {
t.Errorf("expected Cache-Control no-store, got %q", rec.Header().Get("Cache-Control"))
}
if rec.Body.String() != `{"open":false}` {
t.Errorf("expected body {\"open\":false}, got %q", rec.Body.String())
}
})
t.Run("An expired, forged, or otherwise invalid jwt cookie makes no difference to the response", func(t *testing.T) {
userRepo := newMockUserRepo()
userRepo.users[1] = user.User{ID: 1, Username: "alice", Email: "alice@example.com", IsVerified: true}
userRepo.nextID = 1
mailRepo := newMockMailRepo()
router, _ := newRegistrationStatusRouter(userRepo, mailRepo)
invalidCookie := &http.Cookie{Name: "jwt", Value: "not-a-valid-jwt-token"}
rec := registrationStatusRequest(router, invalidCookie)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String())
}
if rec.Body.String() != `{"open":false}` {
t.Errorf("expected body {\"open\":false}, got %q", rec.Body.String())
}
if rec.Header().Get("Set-Cookie") != "" {
t.Errorf("expected no cookie to be set or cleared, got %q", rec.Header().Get("Set-Cookie"))
}
})
t.Run("The endpoint reveals nothing beyond the open flag", func(t *testing.T) {
userRepo := newMockUserRepo()
userRepo.users[1] = user.User{ID: 1, Username: "alice", Email: "alice@example.com", IsVerified: true}
userRepo.users[2] = user.User{ID: 2, Username: "bob", Email: "bob@example.com", IsVerified: true}
userRepo.users[3] = user.User{ID: 3, Username: "carol", Email: "carol@example.com", IsVerified: false}
userRepo.nextID = 3
mailRepo := newMockMailRepo()
router, _ := newRegistrationStatusRouter(userRepo, mailRepo)
rec := registrationStatusRequest(router, nil)
body := rec.Body.String()
if body != `{"open":false}` {
t.Fatalf("expected body {\"open\":false} with only the open field, got %q", body)
}
forbidden := []string{"alice", "bob", "carol", "@example.com", "is_verified", "count"}
for _, term := range forbidden {
if strings.Contains(body, term) {
t.Errorf("expected the body to not reveal %q, got %q", term, body)
}
}
})
t.Run("The route is never rate-limited, unlike login and password reset", func(t *testing.T) {
userRepo := newMockUserRepo()
mailRepo := newMockMailRepo()
router, _ := newRegistrationStatusRouter(userRepo, mailRepo)
for i := 0; i < 20; i++ {
rec := registrationStatusRequest(router, nil)
if rec.Code != http.StatusOK {
t.Fatalf("request %d: expected status 200, got %d", i+1, rec.Code)
}
if rec.Code == http.StatusTooManyRequests {
t.Fatalf("request %d: unexpectedly rate-limited", i+1)
}
}
})
t.Run("The same rule CreateUser enforces is what answers this endpoint", func(t *testing.T) {
userRepo := newMockUserRepo()
userRepo.users[1] = user.User{ID: 1, Username: "alice", Email: "alice@example.com", IsVerified: true}
userRepo.nextID = 1
mailRepo := newMockMailRepo()
uc := useCases.NewCreateUserUseCase(userRepo, &stubHasher{}, mailRepo, "https://cms.example.com", discardRecorder)
if uc.IsRegistrationOpen() {
t.Fatalf("expected IsRegistrationOpen to return false")
}
_, err := uc.CreateUser(testCtx, useCases.CreateUserCommand{
Username: "bob",
Password: "hunter2example12",
Email: "bob@example.com",
})
if !errors.Is(err, user.ErrRegistrationClosed) {
t.Fatalf("expected user.ErrRegistrationClosed, got %v", err)
}
})
}