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:
2026-05-10 17:27:51 +01:00
parent 83c62ffb97
commit e73c96b653
26 changed files with 554 additions and 920 deletions
+32 -49
View File
@@ -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