181 lines
6.4 KiB
Go
181 lines
6.4 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")
|
|
loginHandler := auth.NewLoginHandler(token, createUseCase, useCases.NewAuthenticateUserUseCase(userRepo, hasher), 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")
|
|
|
|
if uc.IsRegistrationOpen() {
|
|
t.Fatalf("expected IsRegistrationOpen to return false")
|
|
}
|
|
|
|
_, err := uc.CreateUser(useCases.CreateUserCommand{
|
|
Username: "bob",
|
|
Password: "hunter2example12",
|
|
Email: "bob@example.com",
|
|
})
|
|
if !errors.Is(err, user.ErrRegistrationClosed) {
|
|
t.Fatalf("expected user.ErrRegistrationClosed, got %v", err)
|
|
}
|
|
})
|
|
}
|