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)
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"image/color"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"crussell/db"
|
||||
@@ -34,24 +35,22 @@ import (
|
||||
"github.com/kovidgoyal/imaging"
|
||||
)
|
||||
|
||||
func setupTestDB(t *testing.T) func() {
|
||||
t.Helper()
|
||||
|
||||
pool := testdb.Pool(t)
|
||||
testdb.Migrate(t, pool)
|
||||
|
||||
// Truncate tables to ensure clean state
|
||||
testdb.TruncateTables(t, pool)
|
||||
|
||||
originalDB := db.DB
|
||||
db.DB = pool
|
||||
|
||||
jwt.Init()
|
||||
|
||||
return func() {
|
||||
db.DB = originalDB
|
||||
pool.Close()
|
||||
func TestMain(m *testing.M) {
|
||||
pool, err := testdb.NewPool("")
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
testdb.Migrate(&testing.T{}, pool)
|
||||
db.DB = pool
|
||||
jwt.Init()
|
||||
code := m.Run()
|
||||
pool.Close()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func resetTestData(t *testing.T) {
|
||||
t.Helper()
|
||||
testdb.TruncateTables(t, db.DB)
|
||||
}
|
||||
|
||||
func makeRequest(handler http.HandlerFunc, method, path string, body interface{}) *httptest.ResponseRecorder {
|
||||
@@ -95,8 +94,7 @@ func makeRequestWithContext(handler http.HandlerFunc, method, path string, body
|
||||
|
||||
// TestPortfolio_ListImages verifies that listing portfolio images returns all images in the database.
|
||||
func TestPortfolio_ListImages(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Insert test images
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
@@ -128,8 +126,7 @@ func TestPortfolio_ListImages(t *testing.T) {
|
||||
|
||||
// TestPortfolio_ListImages_WithTagFilter verifies that images can be filtered by tag using the 'tag' query parameter.
|
||||
func TestPortfolio_ListImages_WithTagFilter(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Insert test images
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
@@ -161,8 +158,7 @@ func TestPortfolio_ListImages_WithTagFilter(t *testing.T) {
|
||||
|
||||
// TestPortfolio_ListImages_Empty verifies that an empty database returns an empty images array (not an error).
|
||||
func TestPortfolio_ListImages_Empty(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
handler := http.HandlerFunc(ListImages)
|
||||
w := makeRequest(handler, "GET", "/api/portfolio/images", nil)
|
||||
@@ -187,8 +183,7 @@ func TestPortfolio_ListImages_Empty(t *testing.T) {
|
||||
|
||||
// TestPortfolio_ListTags verifies that listing tags returns all unique tags from portfolio images.
|
||||
func TestPortfolio_ListTags(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Insert test images with tag_names instead of directly into tags table
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
@@ -221,8 +216,7 @@ func TestPortfolio_ListTags(t *testing.T) {
|
||||
|
||||
// TestPortfolio_ListTags_WithQuery verifies that tags can be filtered by a query string.
|
||||
func TestPortfolio_ListTags_WithQuery(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Insert test images with tag_names instead of directly into tags table
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
@@ -255,8 +249,7 @@ func TestPortfolio_ListTags_WithQuery(t *testing.T) {
|
||||
|
||||
// TestPortfolio_ListTags_Empty verifies that an empty database returns an empty tags array.
|
||||
func TestPortfolio_ListTags_Empty(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
handler := http.HandlerFunc(ListTags)
|
||||
w := makeRequest(handler, "GET", "/api/portfolio/tags", nil)
|
||||
@@ -281,8 +274,7 @@ func TestPortfolio_ListTags_Empty(t *testing.T) {
|
||||
|
||||
// TestPortfolio_ListFilters verifies that filter categories are derived from tags (e.g., 'nature', 'color' from 'nature:forest').
|
||||
func TestPortfolio_ListFilters(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Insert test images with tags
|
||||
_, err := db.DB.Exec(context.Background(), `
|
||||
@@ -314,8 +306,7 @@ func TestPortfolio_ListFilters(t *testing.T) {
|
||||
|
||||
// TestPortfolio_ListFilters_Empty verifies that an empty database returns an empty filters array.
|
||||
func TestPortfolio_ListFilters_Empty(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
handler := http.HandlerFunc(ListFilters)
|
||||
w := makeRequest(handler, "GET", "/api/portfolio/filters", nil)
|
||||
@@ -340,8 +331,7 @@ func TestPortfolio_ListFilters_Empty(t *testing.T) {
|
||||
|
||||
// TestPortfolio_GetImage verifies that a single image can be retrieved by its timestamp ID.
|
||||
func TestPortfolio_GetImage(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Use timestamp-based image URL (matches upload pattern: portfolio/{timestamp}.jpg)
|
||||
timestamp := "1234567890123456789" // 19 digits = valid nanosecond timestamp
|
||||
@@ -384,8 +374,7 @@ func TestPortfolio_GetImage(t *testing.T) {
|
||||
|
||||
// TestPortfolio_GetImage_NotFound verifies that requesting a non-existent image returns 404.
|
||||
func TestPortfolio_GetImage_NotFound(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
handler := http.HandlerFunc(GetImage)
|
||||
w := makeRequest(handler, "GET", "/api/portfolio/images/nonexistent-id", nil)
|
||||
@@ -401,8 +390,7 @@ func TestPortfolio_GetImage_NotFound(t *testing.T) {
|
||||
|
||||
// TestPortfolio_Upload_Admin verifies that an admin user passes the authentication check for image upload.
|
||||
func TestPortfolio_Upload_Admin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Create a minimal S3 client mock by setting it to nil (handler will check and return error)
|
||||
// The handler requires S3 client, so we test the auth check first
|
||||
@@ -420,8 +408,7 @@ func TestPortfolio_Upload_Admin(t *testing.T) {
|
||||
|
||||
// TestPortfolio_Upload_NonAdmin verifies that non-admin users receive 403 Forbidden on image upload attempts.
|
||||
func TestPortfolio_Upload_NonAdmin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
handler := http.HandlerFunc(UploadImage)
|
||||
w := makeRequestWithContext(handler, "POST", "/api/portfolio/images", nil, "user-001", "verified_email")
|
||||
@@ -433,8 +420,7 @@ func TestPortfolio_Upload_NonAdmin(t *testing.T) {
|
||||
|
||||
// TestPortfolio_Upload_Unauthenticated verifies that unauthenticated requests receive 401 Unauthorized on image upload.
|
||||
func TestPortfolio_Upload_Unauthenticated(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
handler := http.HandlerFunc(UploadImage)
|
||||
w := makeRequest(handler, "POST", "/api/portfolio/images", nil)
|
||||
@@ -450,8 +436,7 @@ func TestPortfolio_Upload_Unauthenticated(t *testing.T) {
|
||||
|
||||
// TestPortfolio_Delete_Admin verifies that an admin user passes the authentication check for image deletion.
|
||||
func TestPortfolio_Delete_Admin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Insert test image
|
||||
var imageID string
|
||||
@@ -476,8 +461,7 @@ func TestPortfolio_Delete_Admin(t *testing.T) {
|
||||
|
||||
// TestPortfolio_Delete_NonAdmin verifies that non-admin users receive 403 Forbidden on image deletion attempts.
|
||||
func TestPortfolio_Delete_NonAdmin(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Insert test image
|
||||
var imageID string
|
||||
@@ -500,8 +484,7 @@ func TestPortfolio_Delete_NonAdmin(t *testing.T) {
|
||||
|
||||
// TestPortfolio_Delete_Unauthenticated verifies that unauthenticated requests receive 401 Unauthorized on image deletion.
|
||||
func TestPortfolio_Delete_Unauthenticated(t *testing.T) {
|
||||
cleanup := setupTestDB(t)
|
||||
defer cleanup()
|
||||
resetTestData(t)
|
||||
|
||||
// Insert test image
|
||||
var imageID string
|
||||
|
||||
Reference in New Issue
Block a user