Files
Crussell/backend/testutils/helpers.go
T
popertots e73c96b653 refactor: optimize test DB setup — TestMain per package, truncate-only between tests
- Add TestMain to all 10 test packages (schema DROP+CREATE runs once per package)
- Convert per-test setupTestDB to resetTestData (TRUNCATE only, ~60% faster)
- Add 3 missing tables to TruncateTables (booking_edit_requests, exceptional_group_applications, business_settings)
- Remove dead truncateDiscountTables helper
- Consolidate discount_test.go into package bookings (was external test package)
- Update testutils.SetupTestDB to truncate-only
- Fix unused imports across user, bookings, and handlers packages
- Verify: 286 passing, 2 skipped, 0 failures with -count=2 (no state leakage)
2026-05-10 17:27:51 +01:00

175 lines
6.1 KiB
Go

//go:build test
// +build test
package testutils
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"crussell/db"
"crussell/mw"
"crussell/testutils/jwt"
"crussell/testutils/testdb"
"github.com/jackc/pgx/v5/pgxpool"
)
// SetupTestDB resets test data by truncating tables
// Assumes db.DB is already set by TestMain
func SetupTestDB(t *testing.T) func() {
t.Helper()
testdb.TruncateTables(t, db.DB)
return func() {}
}
// MakeRequest makes an HTTP request to a handler with optional JWT token
// token can be user token or admin token. Pass empty string for no auth.
func MakeRequest(handler http.Handler, method, path string, body interface{}, token string) *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)
}
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
return w
}
// MakeRequestWithContext makes an HTTP request with context values (for auth middleware testing)
// Use this when you need to test handlers that rely on context values set by middleware
func MakeRequestWithContext(handler http.Handler, method, path string, body interface{}, ctx context.Context) *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)
}
req = req.WithContext(ctx)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
return w
}
// MakeUserRequest makes a request as an authenticated user
// Generates a valid user token and includes it in the Authorization header
func MakeUserRequest(handler http.Handler, method, path string, body interface{}, userID string) *httptest.ResponseRecorder {
token := jwt.GenerateUserToken(userID)
return MakeRequest(handler, method, path, body, token)
}
// MakeAdminRequest makes a request as an authenticated admin
// Generates a valid admin token and includes it in the Authorization header
func MakeAdminRequest(handler http.Handler, method, path string, body interface{}, adminID string) *httptest.ResponseRecorder {
token := jwt.GenerateTestToken(adminID, "admin")
return MakeRequest(handler, method, path, body, token)
}
// MakeContextRequest makes a request with user context set
// Useful for testing handlers that check context before validating token
func MakeContextRequest(handler http.Handler, method, path string, body interface{}, userID string) *httptest.ResponseRecorder {
ctx := context.WithValue(context.Background(), mw.UserIDKey, userID)
return MakeRequestWithContext(handler, method, path, body, ctx)
}
// ParseResponseBody unmarshals the response body into dest
func ParseResponseBody(w *httptest.ResponseRecorder, dest interface{}) error {
return json.Unmarshal(w.Body.Bytes(), dest)
}
// AssertStatusCode checks that the response has the expected HTTP status code
func AssertStatusCode(t *testing.T, w *httptest.ResponseRecorder, expectedCode int) {
t.Helper()
if w.Code != expectedCode {
t.Errorf("expected status %d, got %d. body: %s", expectedCode, w.Code, w.Body.String())
}
}
// AssertStatusCodeWithMessage checks status code and logs full response on mismatch
func AssertStatusCodeWithMessage(t *testing.T, w *httptest.ResponseRecorder, expectedCode int, message string) {
t.Helper()
if w.Code != expectedCode {
t.Errorf("%s: expected status %d, got %d.\nResponse: %s", message, expectedCode, w.Code, w.Body.String())
}
}
// AssertJSONResponse checks that the response is valid JSON and unmarshals it
func AssertJSONResponse(t *testing.T, w *httptest.ResponseRecorder, dest interface{}) {
t.Helper()
if err := ParseResponseBody(w, dest); err != nil {
t.Errorf("failed to parse JSON response: %v\nBody: %s", err, w.Body.String())
}
}
// AssertResponseContains checks that the response body contains a substring
func AssertResponseContains(t *testing.T, w *httptest.ResponseRecorder, substring string) {
t.Helper()
if !bytes.Contains(w.Body.Bytes(), []byte(substring)) {
t.Errorf("expected response to contain '%s', but got:\n%s", substring, w.Body.String())
}
}
// AssertResponseNotContains checks that the response body does NOT contain a substring
func AssertResponseNotContains(t *testing.T, w *httptest.ResponseRecorder, substring string) {
t.Helper()
if bytes.Contains(w.Body.Bytes(), []byte(substring)) {
t.Errorf("expected response to NOT contain '%s', but got:\n%s", substring, w.Body.String())
}
}
// TestDBSnapshot creates a snapshot of the test database for transaction rollback
// Returns the pool and a cleanup function
func TestDBSnapshot(t *testing.T) (*pgxpool.Pool, func()) {
t.Helper()
pool := testdb.Pool(t)
testdb.Migrate(t, pool)
return pool, func() { pool.Close() }
}
// GetBodyAsString returns the response body as a string
func GetBodyAsString(w *httptest.ResponseRecorder) string {
return w.Body.String()
}
// GetBodyAsJSON unmarshals and returns the response body
// Returns error if JSON is invalid
func GetBodyAsJSON(w *httptest.ResponseRecorder) (map[string]interface{}, error) {
var result map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &result)
return result, err
}
// MakeRequestNoAuth makes an HTTP request without authentication (for testing unauthenticated endpoints)
func MakeRequestNoAuth(handler http.Handler, method, path string, body interface{}) *httptest.ResponseRecorder {
return MakeRequest(handler, method, path, body, "")
}
// AssertErrorStatusCode checks status code and validates error is in response body
func AssertErrorStatusCode(t *testing.T, w *httptest.ResponseRecorder, expectedCode int, expectedErrorSubstring string) {
t.Helper()
AssertStatusCode(t, w, expectedCode)
AssertResponseContains(t, w, expectedErrorSubstring)
}
// GetResponseStatus returns just the status code for convenient assertions
func GetResponseStatus(w *httptest.ResponseRecorder) int {
return w.Code
}