refactor(backend): replace resetTestData with SetupTestDB and add new tests
Migrate all test files from resetTestData(t) to testutils.SetupTestDB(t) for isolated per-package test databases. - Add new feature tests: name history assertions, referral discount preview, time blockers, email validation, GDPR export, loyalty manual redemption - Update existing tests to use batch queries and SetupTestDB - Remove test_helpers.go resetTestData infrastructure - Add comprehensive user profile tests (442 new lines) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -22,11 +22,9 @@ package auth
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -35,55 +33,20 @@ import (
|
||||
"crussell/db"
|
||||
"crussell/internal/dav"
|
||||
"crussell/mw"
|
||||
"crussell/testutils"
|
||||
"crussell/testutils/fixtures"
|
||||
"crussell/testutils/jwt"
|
||||
"crussell/testutils/testdb"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := testdb.NewPool("")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create test pool: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
testdb.Migrate(&testing.T{}, pool)
|
||||
db.DB = pool
|
||||
jwt.Init()
|
||||
dav.Service = &dav.BaseService{}
|
||||
code := m.Run()
|
||||
pool.Close()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func resetTestData(t *testing.T) {
|
||||
t.Helper()
|
||||
testdb.TruncateTables(t, db.DB)
|
||||
testutils.SetupTestDB(t)
|
||||
dav.Service = &dav.BaseService{}
|
||||
}
|
||||
|
||||
// helper function to make JSON request
|
||||
func makeRequest(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder {
|
||||
var req *http.Request
|
||||
if body != nil {
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
req = httptest.NewRequest(method, path, bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req = httptest.NewRequest(method, path, nil)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
// Helper to parse response body
|
||||
func parseResponseBody(w *httptest.ResponseRecorder, dest interface{}) error {
|
||||
return json.Unmarshal(w.Body.Bytes(), dest)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Register Handler Tests
|
||||
// =============================================================================
|
||||
@@ -107,7 +70,7 @@ func TestRegister_Success(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -165,7 +128,7 @@ func TestRegister_InvalidInput_MissingFields(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := makeRequest(handler, "POST", "/api/register", tt.body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", tt.body)
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
@@ -190,7 +153,7 @@ func TestRegister_InvalidInput_InvalidEmail(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -214,7 +177,7 @@ func TestRegister_InvalidInput_InvalidPhone(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -256,7 +219,7 @@ func TestRegister_ValidUKPhoneNumbers(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected status 201 for %s, got %d. body: %s", tc.phone, w.Code, w.Body.String())
|
||||
@@ -298,7 +261,7 @@ func TestRegister_InvalidPhoneNumbers(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400 for invalid phone %s, got %d. body: %s", tc.phone, w.Code, w.Body.String())
|
||||
@@ -327,7 +290,7 @@ func TestRegister_InvalidInput_Under16(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -361,7 +324,7 @@ func TestRegister_DuplicateEmail(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusConflict {
|
||||
t.Errorf("expected status 409, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -397,7 +360,7 @@ func TestLogin_Success(t *testing.T) {
|
||||
Password: "testpassword123",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -406,7 +369,7 @@ func TestLogin_Success(t *testing.T) {
|
||||
var resp struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
if err := testutils.ParseResponseBody(w, &resp); err != nil {
|
||||
t.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
@@ -434,7 +397,7 @@ func TestLogin_InvalidCredentials_WrongPassword(t *testing.T) {
|
||||
Password: "wrongpassword",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("expected status 401, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -453,7 +416,7 @@ func TestLogin_InvalidCredentials_NonExistentEmail(t *testing.T) {
|
||||
Password: "password123",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("expected status 401, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -500,7 +463,7 @@ func TestRefreshToken_Success(t *testing.T) {
|
||||
var resp struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
if err := testutils.ParseResponseBody(w, &resp); err != nil {
|
||||
t.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
@@ -551,14 +514,14 @@ func TestVerifyGenerate_ValidEmail(t *testing.T) {
|
||||
Email: "user@test.com",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/verify/generate", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/verify/generate", body)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp VerificationResponse
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
if err := testutils.ParseResponseBody(w, &resp); err != nil {
|
||||
t.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
@@ -591,14 +554,14 @@ func TestVerifyGenerate_NonExistentEmail(t *testing.T) {
|
||||
Email: "nonexistent@test.com",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/verify/generate", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/verify/generate", body)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp VerificationResponse
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
if err := testutils.ParseResponseBody(w, &resp); err != nil {
|
||||
t.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
@@ -642,14 +605,14 @@ func TestVerifyCheck_ValidCode(t *testing.T) {
|
||||
Code: code,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/verify/check", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/verify/check", body)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp VerificationResponse
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
if err := testutils.ParseResponseBody(w, &resp); err != nil {
|
||||
t.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
@@ -677,7 +640,7 @@ func TestVerifyCheck_InvalidCode(t *testing.T) {
|
||||
Code: "nonexistent-code-12345",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/verify/check", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/verify/check", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -713,7 +676,7 @@ func TestVerifyCheck_ExpiredCode(t *testing.T) {
|
||||
Code: code,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/verify/check", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/verify/check", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -761,7 +724,7 @@ func TestRegister_NameTooLong(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -786,7 +749,7 @@ func TestRegister_InvalidNameCharacters(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -822,13 +785,13 @@ func TestVerifyCheck_AlreadyUsed(t *testing.T) {
|
||||
body := VerifyCodeRequest{
|
||||
Code: code,
|
||||
}
|
||||
w := makeRequest(handler, "POST", "/api/verify/check", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/verify/check", body)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("first verification: expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
// Second verification with same code should return 403 (already used)
|
||||
w = makeRequest(handler, "POST", "/api/verify/check", body)
|
||||
w = testutils.MakeRequestNoAuth(handler, "POST", "/api/verify/check", body)
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("second verification: expected status 403, got %d", w.Code)
|
||||
}
|
||||
@@ -875,7 +838,7 @@ func TestVerifyCheck_RoleChangeToVerified(t *testing.T) {
|
||||
body := VerifyCodeRequest{
|
||||
Code: code,
|
||||
}
|
||||
w := makeRequest(handler, "POST", "/api/verify/check", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/verify/check", body)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
@@ -953,7 +916,7 @@ func TestRegister_PasswordLength_Minimum(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if tt.expectError {
|
||||
if w.Code == http.StatusCreated {
|
||||
@@ -985,7 +948,7 @@ func TestRegister_EmptyPassword(t *testing.T) {
|
||||
AgreedToPolicy: true,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
// Empty password fails because it's a required field
|
||||
if w.Code != http.StatusBadRequest {
|
||||
@@ -1027,7 +990,7 @@ func TestRegister_WithValidReferralCode(t *testing.T) {
|
||||
ReferralCode: knownCode,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -1072,7 +1035,7 @@ func TestRegister_WithInvalidReferralCode(t *testing.T) {
|
||||
ReferralCode: "nonexistent1234", // 12 chars but doesn't exist
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -1109,7 +1072,7 @@ func TestRegister_WithInvalidReferralCodeFormat(t *testing.T) {
|
||||
ReferralCode: tt.code,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400 for %s (%s), got %d. body: %s", tt.name, tt.desc, w.Code, w.Body.String())
|
||||
@@ -1163,7 +1126,7 @@ func TestRegister_ReferralCodeCaseInsensitive(t *testing.T) {
|
||||
ReferralCode: tt.inputCode,
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/register", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/register", body)
|
||||
|
||||
if w.Code != tt.wantStatus {
|
||||
t.Errorf("%s: expected status %d, got %d. body: %s", tt.name, tt.wantStatus, w.Code, w.Body.String())
|
||||
@@ -1232,7 +1195,7 @@ func TestLogoutHandler_Success(t *testing.T) {
|
||||
var resp struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
if err := testutils.ParseResponseBody(w, &resp); err != nil {
|
||||
t.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
if !resp.Success {
|
||||
@@ -1417,7 +1380,7 @@ func TestLoginResponse_IncludesJTI(t *testing.T) {
|
||||
Password: "testpassword123",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -1427,7 +1390,7 @@ func TestLoginResponse_IncludesJTI(t *testing.T) {
|
||||
Token string `json:"token"`
|
||||
JTI string `json:"jti"`
|
||||
}
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
if err := testutils.ParseResponseBody(w, &resp); err != nil {
|
||||
t.Errorf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
@@ -1479,7 +1442,7 @@ func TestLoginInProgress_Cap(t *testing.T) {
|
||||
Password: "testpassword123",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
|
||||
if w.Code != http.StatusTooManyRequests {
|
||||
t.Errorf("expected status 429, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -1563,7 +1526,7 @@ func TestLogin_AccountLockout_After5Failures(t *testing.T) {
|
||||
Email: "user@test.com",
|
||||
Password: "wrongpassword",
|
||||
}
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("attempt %d: expected 401, got %d", i+1, w.Code)
|
||||
}
|
||||
@@ -1573,7 +1536,7 @@ func TestLogin_AccountLockout_After5Failures(t *testing.T) {
|
||||
Email: "user@test.com",
|
||||
Password: "wrongpassword",
|
||||
}
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
if w.Code != http.StatusTooManyRequests {
|
||||
t.Errorf("expected 429 after 5 failures, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
@@ -1623,7 +1586,7 @@ func TestLogin_AccountLockout_ResetsOnSuccess(t *testing.T) {
|
||||
Password: "testpassword123",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
@@ -1790,7 +1753,7 @@ func TestLogin_ResponseIncludesRefreshToken(t *testing.T) {
|
||||
Password: "testpassword123",
|
||||
}
|
||||
|
||||
w := makeRequest(handler, "POST", "/api/login", body)
|
||||
w := testutils.MakeRequestNoAuth(handler, "POST", "/api/login", body)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
|
||||
@@ -1801,7 +1764,7 @@ func TestLogin_ResponseIncludesRefreshToken(t *testing.T) {
|
||||
JTI string `json:"jti"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
}
|
||||
if err := parseResponseBody(w, &resp); err != nil {
|
||||
if err := testutils.ParseResponseBody(w, &resp); err != nil {
|
||||
t.Fatalf("failed to parse response: %v", err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user