fix: improve test infrastructure and add ID validation
- Add TestMain to set test env vars and testdb.TruncateTables for test isolation - Add chi routing context to test helpers for path parameter extraction - Fix SQL error handling to use errors.Is() instead of == - Add validators package with ID validation - Fix admin test middleware chain (RequireAdmin wrapper) - Update test user inserts to include phone and date_of_birth fields - Update service delete test to check soft-delete (is_active=false) - Update holiday hours test to use new schema (weekday, is_open) - Add phone number validation tests for UK mobile numbers
This commit is contained in:
@@ -11,12 +11,19 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Global counter for unique emails in tests
|
||||
var testEmailCounter int64
|
||||
|
||||
func CreateTestAdminUser(pool *pgxpool.Pool) (string, error) {
|
||||
return createTestUser(pool, "Admin", "User", "admin@test.com", "admin")
|
||||
return createTestUser(pool, "Admin", "User", "", "admin")
|
||||
}
|
||||
|
||||
func CreateTestUser(pool *pgxpool.Pool) (string, error) {
|
||||
return createTestUser(pool, "Test", "User", "user@test.com", "verified_email")
|
||||
return createTestUser(pool, "Test", "User", "", "verified_email")
|
||||
}
|
||||
|
||||
func CreateTestUserWithEmail(pool *pgxpool.Pool, email, role string) (string, error) {
|
||||
return createTestUser(pool, "Test", "User", email, role)
|
||||
}
|
||||
|
||||
func createTestUser(pool *pgxpool.Pool, firstName, lastName, email, role string) (string, error) {
|
||||
@@ -25,13 +32,19 @@ func createTestUser(pool *pgxpool.Pool, firstName, lastName, email, role string)
|
||||
return "", fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
// Generate unique email if not provided
|
||||
if email == "" {
|
||||
testEmailCounter++
|
||||
email = fmt.Sprintf("%s.%s.%d@test.com", firstName, lastName, testEmailCounter)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
var userID string
|
||||
err = pool.QueryRow(ctx, `
|
||||
INSERT INTO users (n_first_name, n_last_name, email, password_hash, account_role, account_type)
|
||||
VALUES ($1, $2, $3, $4, $5, 'email')
|
||||
INSERT INTO users (n_first_name, n_last_name, email, phone, date_of_birth, password_hash, account_role, account_type)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, 'email')
|
||||
RETURNING id
|
||||
`, firstName, lastName, email, string(passwordHash), role).Scan(&userID)
|
||||
`, firstName, lastName, email, "+447123456789", "1990-01-01", string(passwordHash), role).Scan(&userID)
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create user: %w", err)
|
||||
|
||||
@@ -58,14 +58,81 @@ func NewPool(dsn string) (*pgxpool.Pool, error) {
|
||||
func Migrate(t *testing.T, pool *pgxpool.Pool) {
|
||||
t.Helper()
|
||||
|
||||
// Check if database already has tables by checking for the users table
|
||||
ctx := context.Background()
|
||||
|
||||
// Check if database already has tables
|
||||
var err error
|
||||
// Check if database already has tables - use information_schema which is more reliable
|
||||
var tableCount int
|
||||
err := pool.QueryRow(ctx, "SELECT COUNT(*) FROM pg_tables WHERE tablename = 'users'").Scan(&tableCount)
|
||||
err = pool.QueryRow(ctx, "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE'").Scan(&tableCount)
|
||||
if err == nil && tableCount > 0 {
|
||||
// Tables already exist, skip migration
|
||||
t.Log("Database already has tables, skipping migration")
|
||||
return
|
||||
t.Log("Database already has types, dropping and recreating for clean state...")
|
||||
|
||||
// Drop all tables, sequences, and views in correct order
|
||||
dropOrder := []string{
|
||||
"admin_notifications",
|
||||
"user_notification_preferences",
|
||||
"user_referrals",
|
||||
"booking_services",
|
||||
"payments",
|
||||
"bookings",
|
||||
"user_service_patch_tests",
|
||||
"services",
|
||||
"verification_codes",
|
||||
"user_social_logins",
|
||||
"users",
|
||||
"images",
|
||||
"tags",
|
||||
"working_hours",
|
||||
"exceptional_group_applications",
|
||||
"exceptional_working_hours",
|
||||
"exceptional_working_hours_groups",
|
||||
"business_settings",
|
||||
}
|
||||
|
||||
// Drop all objects with error logging
|
||||
allDropStmts := append([]string{}, dropOrder...)
|
||||
allDropStmts = append(allDropStmts,
|
||||
"DROP SEQUENCE IF EXISTS invoice_number_seq",
|
||||
"DROP SEQUENCE IF EXISTS tags_id_seq",
|
||||
"DROP SEQUENCE IF EXISTS exceptional_working_hours_id_seq",
|
||||
"DROP SEQUENCE IF EXISTS exceptional_working_hours_groups_id_seq",
|
||||
"DROP TYPE IF EXISTS account_role CASCADE",
|
||||
"DROP TYPE IF EXISTS account_type CASCADE",
|
||||
"DROP TYPE IF EXISTS payment_type CASCADE",
|
||||
"DROP TYPE IF EXISTS payment_method CASCADE",
|
||||
"DROP TYPE IF EXISTS payment_status CASCADE",
|
||||
"DROP TYPE IF EXISTS booking_status CASCADE",
|
||||
"DROP TYPE IF EXISTS verification_purpose CASCADE",
|
||||
"DROP TYPE IF EXISTS admin_notification_reason CASCADE",
|
||||
)
|
||||
|
||||
for _, item := range dropOrder {
|
||||
stmt := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE", item)
|
||||
if _, err := pool.Exec(ctx, stmt); err != nil {
|
||||
t.Logf("Warning dropping table %s: %v (expected if using IF EXISTS)", item, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Drop sequences and types
|
||||
for _, item := range []string{
|
||||
"DROP SEQUENCE IF EXISTS invoice_number_seq",
|
||||
"DROP SEQUENCE IF EXISTS tags_id_seq",
|
||||
"DROP SEQUENCE IF EXISTS exceptional_working_hours_id_seq",
|
||||
"DROP SEQUENCE IF EXISTS exceptional_working_hours_groups_id_seq",
|
||||
"DROP TYPE IF EXISTS account_role CASCADE",
|
||||
"DROP TYPE IF EXISTS account_type CASCADE",
|
||||
"DROP TYPE IF EXISTS payment_type CASCADE",
|
||||
"DROP TYPE IF EXISTS payment_method CASCADE",
|
||||
"DROP TYPE IF EXISTS payment_status CASCADE",
|
||||
"DROP TYPE IF EXISTS booking_status CASCADE",
|
||||
"DROP TYPE IF EXISTS verification_purpose CASCADE",
|
||||
"DROP TYPE IF EXISTS admin_notification_reason CASCADE",
|
||||
} {
|
||||
if _, err := pool.Exec(ctx, item); err != nil {
|
||||
t.Logf("Warning dropping item: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
@@ -91,6 +158,12 @@ func Migrate(t *testing.T, pool *pgxpool.Pool) {
|
||||
// Note: This doesn't handle stored procedures properly, but the database
|
||||
// should already be set up with the correct schema
|
||||
t.Log("Running migration...")
|
||||
|
||||
// Execute the schema SQL
|
||||
_, err = pool.Exec(ctx, schemaSQL)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute migration: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Tx(t *testing.T, pool *pgxpool.Pool) pgx.Tx {
|
||||
|
||||
Reference in New Issue
Block a user