Fix test setup and middleware chain - Handler tests now passing
- Fix TestRequireRoleMiddleware by chaining RequireAuth before RequireRole (role context requirement) - Remove unused 'strings' import from testdb.go - Create crussell_test database in Docker setup - Tests now properly initialize authentication context for role-based tests Result: handlers test suite passes (13/13 tests) Remaining failures in admin/auth/bookings/portfolio/scheduling/services/user packages need further investigation (environment setup, database constraints, endpoint initialization)
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
//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 initializes a test database and returns a cleanup function
|
||||
// Replaces the global db.DB with a test pool
|
||||
func SetupTestDB(t *testing.T) func() {
|
||||
t.Helper()
|
||||
|
||||
pool := testdb.Pool(t)
|
||||
testdb.Migrate(t, pool)
|
||||
|
||||
// Replace global db.DB with test pool
|
||||
originalDB := db.DB
|
||||
db.DB = pool
|
||||
|
||||
// Initialize JWT for tests
|
||||
jwt.Init()
|
||||
|
||||
return func() {
|
||||
db.DB = originalDB
|
||||
pool.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user