added tests to config and database

This commit is contained in:
2025-08-30 08:59:45 +02:00
parent ae8c1e468d
commit 0151c9a4f8
5 changed files with 1638 additions and 18 deletions
+405
View File
@@ -0,0 +1,405 @@
package database
import (
"bamort/config"
"database/sql/driver"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// setupTestEnvironment sets up test environment variables
func setupTestEnvironment(t *testing.T) {
// Save original values
origEnv := os.Getenv("ENVIRONMENT")
origDevTesting := os.Getenv("DEV_TESTING")
origDatabaseType := os.Getenv("DATABASE_TYPE")
origDatabaseURL := os.Getenv("DATABASE_URL")
// Cleanup function to restore original values
t.Cleanup(func() {
if origEnv != "" {
os.Setenv("ENVIRONMENT", origEnv)
} else {
os.Unsetenv("ENVIRONMENT")
}
if origDevTesting != "" {
os.Setenv("DEV_TESTING", origDevTesting)
} else {
os.Unsetenv("DEV_TESTING")
}
if origDatabaseType != "" {
os.Setenv("DATABASE_TYPE", origDatabaseType)
} else {
os.Unsetenv("DATABASE_TYPE")
}
if origDatabaseURL != "" {
os.Setenv("DATABASE_URL", origDatabaseURL)
} else {
os.Unsetenv("DATABASE_URL")
}
// Reset global DB variable
DB = nil
// Reload configuration
config.LoadConfig()
})
}
func TestGetBackendDir(t *testing.T) {
// Test that getBackendDir returns a valid path
backendDir := getBackendDir()
// Should be an absolute path
assert.True(t, filepath.IsAbs(backendDir), "getBackendDir should return an absolute path")
// Should end with "backend"
assert.True(t, strings.HasSuffix(backendDir, "backend"), "getBackendDir should return path ending with 'backend'")
// The directory should exist
info, err := os.Stat(backendDir)
assert.NoError(t, err, "Backend directory should exist")
assert.True(t, info.IsDir(), "Backend path should be a directory")
// Should contain expected subdirectories
expectedDirs := []string{"database", "models", "config"}
for _, expectedDir := range expectedDirs {
dirPath := filepath.Join(backendDir, expectedDir)
info, err := os.Stat(dirPath)
assert.NoError(t, err, "Expected directory %s should exist", expectedDir)
if err == nil {
assert.True(t, info.IsDir(), "%s should be a directory", expectedDir)
}
}
}
func TestPreparedTestDBPath(t *testing.T) {
// Test that PreparedTestDB contains the correct path
assert.True(t, strings.Contains(PreparedTestDB, "testdata"), "PreparedTestDB should contain testdata directory")
assert.True(t, strings.HasSuffix(PreparedTestDB, "prepared_test_data.db"), "PreparedTestDB should end with prepared_test_data.db")
assert.True(t, filepath.IsAbs(PreparedTestDB), "PreparedTestDB should be an absolute path")
}
func TestTestDataDirPath(t *testing.T) {
// Test that TestDataDir contains the correct path
assert.True(t, strings.Contains(TestDataDir, "maintenance"), "TestDataDir should contain maintenance directory")
assert.True(t, strings.Contains(TestDataDir, "testdata"), "TestDataDir should contain testdata directory")
assert.True(t, filepath.IsAbs(TestDataDir), "TestDataDir should be an absolute path")
}
func TestConnectDatabase_TestEnvironment(t *testing.T) {
setupTestEnvironment(t)
// Set environment to test
os.Setenv("ENVIRONMENT", "test")
config.LoadConfig()
// Reset DB to ensure fresh connection
DB = nil
// ConnectDatabase should use test database when environment is "test"
db := ConnectDatabase()
assert.NotNil(t, db, "ConnectDatabase should return a valid database connection")
assert.Equal(t, db, DB, "ConnectDatabase should set global DB variable")
}
func TestConnectDatabase_DevTestingYes(t *testing.T) {
setupTestEnvironment(t)
// Set dev testing to yes
os.Setenv("ENVIRONMENT", "production")
os.Setenv("DEV_TESTING", "yes")
config.LoadConfig()
// Reset DB to ensure fresh connection
DB = nil
// ConnectDatabase should use test database when DEV_TESTING=yes
db := ConnectDatabase()
assert.NotNil(t, db, "ConnectDatabase should return a valid database connection")
assert.Equal(t, db, DB, "ConnectDatabase should set global DB variable")
}
func TestConnectDatabaseOrig_SQLite(t *testing.T) {
setupTestEnvironment(t)
// Create a temporary SQLite file
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "test.db")
// Set up configuration for SQLite
os.Setenv("DATABASE_TYPE", "sqlite")
os.Setenv("DATABASE_URL", dbPath)
config.LoadConfig()
// Reset DB to ensure fresh connection
DB = nil
// Test ConnectDatabaseOrig with SQLite
db := ConnectDatabaseOrig()
assert.NotNil(t, db, "ConnectDatabaseOrig should return a valid SQLite connection")
assert.Equal(t, db, DB, "ConnectDatabaseOrig should set global DB variable")
// Verify we can perform basic operations
sqlDB, err := db.DB()
assert.NoError(t, err, "Should be able to get underlying sql.DB")
err = sqlDB.Ping()
assert.NoError(t, err, "Should be able to ping the database")
}
func TestConnectDatabaseOrig_DefaultMySQL(t *testing.T) {
setupTestEnvironment(t)
// Set up configuration with empty DATABASE_URL to trigger default MySQL
os.Setenv("DATABASE_TYPE", "mysql")
os.Setenv("DATABASE_URL", "")
config.LoadConfig()
// Reset DB to ensure fresh connection
DB = nil
// Note: This test will fail to connect since we don't have a real MySQL server
// But we can test that it attempts to use the default configuration
defer func() {
if r := recover(); r != nil {
// Expected to panic since we don't have a real MySQL server
assert.Contains(t, r.(string), "Failed to connect to database", "Should panic with connection error")
}
}()
ConnectDatabaseOrig()
// If we reach here, the connection surprisingly succeeded
// This could happen in a test environment with MySQL available
if DB != nil {
assert.NotNil(t, DB, "If connection succeeds, DB should be set")
}
}
func TestConnectDatabaseOrig_UnsupportedDatabaseType(t *testing.T) {
setupTestEnvironment(t)
// Set up configuration with unsupported database type
os.Setenv("DATABASE_TYPE", "postgresql")
os.Setenv("DATABASE_URL", "postgres://test:test@localhost:5432/test")
config.LoadConfig()
// Reset DB to ensure fresh connection
DB = nil
// Should fall back to MySQL for unsupported database types
defer func() {
if r := recover(); r != nil {
// Expected to panic since we don't have a real MySQL server
assert.Contains(t, r.(string), "Failed to connect to database", "Should panic with connection error")
}
}()
ConnectDatabaseOrig()
}
func TestGetDB(t *testing.T) {
setupTestEnvironment(t)
// Reset DB to ensure fresh connection
DB = nil
// First call should initialize DB
db1 := GetDB()
assert.NotNil(t, db1, "GetDB should return a valid database connection")
assert.Equal(t, db1, DB, "GetDB should set global DB variable")
// Second call should return the same instance
db2 := GetDB()
assert.Equal(t, db1, db2, "GetDB should return the same instance on subsequent calls")
}
func TestGetDB_AlreadyInitialized(t *testing.T) {
setupTestEnvironment(t)
// Set up a mock database connection
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "test.db")
mockDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
require.NoError(t, err, "Should be able to create mock database")
// Set DB to the mock instance
DB = mockDB
// GetDB should return the existing instance
db := GetDB()
assert.Equal(t, mockDB, db, "GetDB should return the existing DB instance")
}
func TestStringArray_Value(t *testing.T) {
tests := []struct {
name string
input StringArray
expected string
}{
{
name: "Empty array",
input: StringArray{},
expected: "[]",
},
{
name: "Single element",
input: StringArray{"test"},
expected: `["test"]`,
},
{
name: "Multiple elements",
input: StringArray{"one", "two", "three"},
expected: `["one","two","three"]`,
},
{
name: "Array with special characters",
input: StringArray{"test\"quote", "test\nnewline", "test\\backslash"},
expected: `["test\"quote","test\nnewline","test\\backslash"]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value, err := tt.input.Value()
assert.NoError(t, err, "Value() should not return an error")
// Convert the returned driver.Value to string
bytes, ok := value.([]byte)
require.True(t, ok, "Value() should return []byte")
assert.JSONEq(t, tt.expected, string(bytes), "Value() should return correct JSON")
})
}
}
func TestStringArray_Scan(t *testing.T) {
tests := []struct {
name string
input interface{}
expected StringArray
hasError bool
}{
{
name: "Nil input",
input: nil,
expected: StringArray{},
hasError: false,
},
{
name: "Empty JSON array",
input: []byte("[]"),
expected: StringArray{},
hasError: false,
},
{
name: "Single element JSON array",
input: []byte(`["test"]`),
expected: StringArray{"test"},
hasError: false,
},
{
name: "Multiple elements JSON array",
input: []byte(`["one","two","three"]`),
expected: StringArray{"one", "two", "three"},
hasError: false,
},
{
name: "JSON array with special characters",
input: []byte(`["test\"quote","test\nnewline","test\\backslash"]`),
expected: StringArray{"test\"quote", "test\nnewline", "test\\backslash"},
hasError: false,
},
{
name: "Invalid input type",
input: "not a byte slice",
expected: StringArray{},
hasError: true,
},
{
name: "Invalid JSON",
input: []byte(`invalid json`),
expected: StringArray{},
hasError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var sa StringArray
err := sa.Scan(tt.input)
if tt.hasError {
assert.Error(t, err, "Scan() should return an error for invalid input")
} else {
assert.NoError(t, err, "Scan() should not return an error for valid input")
assert.Equal(t, tt.expected, sa, "Scan() should set correct values")
}
})
}
}
func TestStringArray_ValueScanRoundTrip(t *testing.T) {
// Test that Value() and Scan() work together correctly
original := StringArray{"test1", "test2", "test3"}
// Convert to driver.Value
value, err := original.Value()
assert.NoError(t, err, "Value() should not error")
// Scan back to StringArray
var result StringArray
err = result.Scan(value)
assert.NoError(t, err, "Scan() should not error")
// Should be equal to original
assert.Equal(t, original, result, "Value/Scan round trip should preserve data")
}
func TestStringArray_DatabaseCompatibility(t *testing.T) {
// Test that StringArray implements the required database interfaces
var sa StringArray
// Should implement driver.Valuer
_, ok := interface{}(sa).(driver.Valuer)
assert.True(t, ok, "StringArray should implement driver.Valuer interface")
// Should have Scan method for sql.Scanner interface
assert.True(t, true, "StringArray has Scan method for sql.Scanner interface")
}
// Benchmark tests for performance-critical functions
func BenchmarkGetBackendDir(b *testing.B) {
for i := 0; i < b.N; i++ {
getBackendDir()
}
}
func BenchmarkStringArray_Value(b *testing.B) {
sa := StringArray{"test1", "test2", "test3", "test4", "test5"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = sa.Value()
}
}
func BenchmarkStringArray_Scan(b *testing.B) {
data := []byte(`["test1","test2","test3","test4","test5"]`)
b.ResetTimer()
for i := 0; i < b.N; i++ {
var sa StringArray
_ = sa.Scan(data)
}
}
+303
View File
@@ -0,0 +1,303 @@
package database
import (
"errors"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// Test model for migration testing
type TestModel struct {
ID uint `gorm:"primarykey"`
Name string `gorm:"size:100;not null"`
Age int `gorm:"default:0"`
}
// Another test model for migration testing
type AnotherTestModel struct {
ID uint `gorm:"primarykey"`
Description string `gorm:"size:200"`
Active bool `gorm:"default:true"`
}
// setupTestDB creates a test database for migration testing
func setupTestDB(t *testing.T) *gorm.DB {
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "test_migrate.db")
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
require.NoError(t, err, "Should be able to create test database")
return db
}
// MockDB creates a mock database that can be configured to return errors
type MockDB struct {
*gorm.DB
shouldError bool
errorMsg string
}
func (m *MockDB) AutoMigrate(dst ...interface{}) error {
if m.shouldError {
return errors.New(m.errorMsg)
}
return m.DB.AutoMigrate(dst...)
}
func TestMigrateStructure_WithProvidedDB(t *testing.T) {
// Create a test database
testDB := setupTestDB(t)
// Test migration with provided database
err := MigrateStructure(testDB)
assert.NoError(t, err, "MigrateStructure should succeed with valid provided database")
// Verify that the database connection is working
sqlDB, err := testDB.DB()
assert.NoError(t, err, "Should be able to get underlying sql.DB")
err = sqlDB.Ping()
assert.NoError(t, err, "Should be able to ping the database")
}
func TestMigrateStructure_WithGlobalDB(t *testing.T) {
// Save original global DB
originalDB := DB
defer func() {
DB = originalDB
}()
// Set up global test database
DB = setupTestDB(t)
// Test migration without providing database (should use global DB)
err := MigrateStructure()
assert.NoError(t, err, "MigrateStructure should succeed with global database")
// Verify that the global database connection is working
sqlDB, err := DB.DB()
assert.NoError(t, err, "Should be able to get underlying sql.DB from global DB")
err = sqlDB.Ping()
assert.NoError(t, err, "Should be able to ping the global database")
}
func TestMigrateStructure_WithNilProvidedDB(t *testing.T) {
// Save original global DB
originalDB := DB
defer func() {
DB = originalDB
}()
// Set up global test database
DB = setupTestDB(t)
// Test migration with nil provided database (should fall back to global DB)
err := MigrateStructure(nil)
assert.NoError(t, err, "MigrateStructure should succeed when nil is provided and global DB exists")
}
func TestMigrateStructure_WithMultipleDBParams(t *testing.T) {
// Create two test databases
testDB1 := setupTestDB(t)
testDB2 := setupTestDB(t)
// Test that it uses the first provided database
err := MigrateStructure(testDB1, testDB2)
assert.NoError(t, err, "MigrateStructure should succeed with multiple DB parameters")
// The function should have used testDB1 (the first parameter)
// We can't directly verify this, but the test should pass if the first DB is valid
}
func TestMigrateStructure_WithActualModels(t *testing.T) {
// Create a test database
testDB := setupTestDB(t)
// Register models for migration
err := testDB.AutoMigrate(&TestModel{}, &AnotherTestModel{})
require.NoError(t, err, "Should be able to register test models")
// Test migration (AutoMigrate is called without models, which is valid in GORM)
err = MigrateStructure(testDB)
assert.NoError(t, err, "MigrateStructure should succeed with registered models")
// Verify tables exist by checking if we can create records
testRecord := TestModel{Name: "Test", Age: 25}
err = testDB.Create(&testRecord).Error
assert.NoError(t, err, "Should be able to create test record after migration")
anotherRecord := AnotherTestModel{Description: "Test Description", Active: true}
err = testDB.Create(&anotherRecord).Error
assert.NoError(t, err, "Should be able to create another test record after migration")
}
func TestMigrateStructure_ErrorHandling(t *testing.T) {
// Save original global DB
originalDB := DB
defer func() {
DB = originalDB
}()
// Set global DB to nil to force an error scenario
DB = nil
// Test migration without providing database when global DB is nil
// This should cause a panic when trying to call AutoMigrate on nil
defer func() {
if r := recover(); r != nil {
// Expected to panic when trying to call methods on nil database
assert.NotNil(t, r, "Should panic when DB is nil")
}
}()
// This should panic because targetDB will be nil and we can't call AutoMigrate on nil
MigrateStructure()
// If we reach here without panic, the test should fail
t.Fatal("Expected panic when calling MigrateStructure with nil DB")
}
func TestMigrateStructure_EmptyMigration(t *testing.T) {
// Test that migration works even when there are no models to migrate
testDB := setupTestDB(t)
// Call AutoMigrate with no models (empty migration)
err := testDB.AutoMigrate()
assert.NoError(t, err, "AutoMigrate should succeed with no models")
// Test our MigrateStructure function
err = MigrateStructure(testDB)
assert.NoError(t, err, "MigrateStructure should succeed with empty migration")
}
func TestMigrateStructure_DatabaseTypes(t *testing.T) {
// Test with SQLite (in-memory)
db1, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err, "Should be able to create in-memory SQLite database")
err = MigrateStructure(db1)
assert.NoError(t, err, "MigrateStructure should work with in-memory SQLite")
// Test with SQLite (file-based)
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "file_test.db")
db2, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
require.NoError(t, err, "Should be able to create file-based SQLite database")
err = MigrateStructure(db2)
assert.NoError(t, err, "MigrateStructure should work with file-based SQLite")
}
func TestMigrateStructure_ParameterVariations(t *testing.T) {
tests := []struct {
name string
setupFunc func(t *testing.T) []*gorm.DB
expectError bool
description string
}{
{
name: "No parameters",
setupFunc: func(t *testing.T) []*gorm.DB {
// Set up global DB
originalDB := DB
t.Cleanup(func() { DB = originalDB })
DB = setupTestDB(t)
return []*gorm.DB{}
},
expectError: false,
description: "Should use global DB when no parameters provided",
},
{
name: "One valid parameter",
setupFunc: func(t *testing.T) []*gorm.DB {
return []*gorm.DB{setupTestDB(t)}
},
expectError: false,
description: "Should use provided DB when one parameter given",
},
{
name: "Multiple parameters",
setupFunc: func(t *testing.T) []*gorm.DB {
return []*gorm.DB{setupTestDB(t), setupTestDB(t)}
},
expectError: false,
description: "Should use first DB when multiple parameters given",
},
{
name: "Nil first parameter with global DB",
setupFunc: func(t *testing.T) []*gorm.DB {
originalDB := DB
t.Cleanup(func() { DB = originalDB })
DB = setupTestDB(t)
return []*gorm.DB{nil}
},
expectError: false,
description: "Should fall back to global DB when first parameter is nil",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dbs := tt.setupFunc(t)
err := MigrateStructure(dbs...)
if tt.expectError {
assert.Error(t, err, tt.description)
} else {
assert.NoError(t, err, tt.description)
}
})
}
}
// Benchmark tests for performance
func BenchmarkMigrateStructure(b *testing.B) {
// Set up test database
tempDir := b.TempDir()
dbPath := filepath.Join(tempDir, "benchmark.db")
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
b.Fatalf("Failed to create test database: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := MigrateStructure(testDB)
if err != nil {
b.Fatalf("MigrateStructure failed: %v", err)
}
}
}
func BenchmarkMigrateStructure_GlobalDB(b *testing.B) {
// Save original global DB
originalDB := DB
defer func() {
DB = originalDB
}()
// Set up global test database
tempDir := b.TempDir()
dbPath := filepath.Join(tempDir, "benchmark_global.db")
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
b.Fatalf("Failed to create test database: %v", err)
}
DB = testDB
b.ResetTimer()
for i := 0; i < b.N; i++ {
err := MigrateStructure()
if err != nil {
b.Fatalf("MigrateStructure failed: %v", err)
}
}
}
+391
View File
@@ -0,0 +1,391 @@
package database
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
// setupHandlerTestEnvironment sets up test environment for handler tests
func setupHandlerTestEnvironment(t *testing.T) {
// Save original values
origEnv := os.Getenv("ENVIRONMENT")
origDevTesting := os.Getenv("DEV_TESTING")
origDatabaseType := os.Getenv("DATABASE_TYPE")
origDatabaseURL := os.Getenv("DATABASE_URL")
originalDB := DB
// Set test environment
os.Setenv("ENVIRONMENT", "test")
os.Setenv("DEV_TESTING", "yes")
// Cleanup function to restore original values
t.Cleanup(func() {
if origEnv != "" {
os.Setenv("ENVIRONMENT", origEnv)
} else {
os.Unsetenv("ENVIRONMENT")
}
if origDevTesting != "" {
os.Setenv("DEV_TESTING", origDevTesting)
} else {
os.Unsetenv("DEV_TESTING")
}
if origDatabaseType != "" {
os.Setenv("DATABASE_TYPE", origDatabaseType)
} else {
os.Unsetenv("DATABASE_TYPE")
}
if origDatabaseURL != "" {
os.Setenv("DATABASE_URL", origDatabaseURL)
} else {
os.Unsetenv("DATABASE_URL")
}
// Reset global DB variable
DB = originalDB
})
}
func TestSetupCheck(t *testing.T) {
setupHandlerTestEnvironment(t)
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Create a Gin router and register the handler
router := gin.New()
router.GET("/setup-check", SetupCheck)
// Create a test HTTP request
req, err := http.NewRequest("GET", "/setup-check", nil)
assert.NoError(t, err, "Should be able to create HTTP request")
// Create a response recorder
recorder := httptest.NewRecorder()
// Perform the request
router.ServeHTTP(recorder, req)
// Verify that the function executed without panicking
// Since SetupCheck doesn't return any response, we just check that it didn't crash
assert.True(t, true, "SetupCheck should execute without panicking")
// Verify that DB is now initialized (ConnectDatabase was called)
assert.NotNil(t, DB, "SetupCheck should initialize the database connection")
}
func TestSetupCheck_DatabaseInitialization(t *testing.T) {
setupHandlerTestEnvironment(t)
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Reset DB to ensure fresh test
DB = nil
// Create a Gin context manually
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Request, _ = http.NewRequest("GET", "/setup-check", nil)
// Call SetupCheck directly
SetupCheck(c)
// Verify that the database connection was established
assert.NotNil(t, DB, "SetupCheck should establish database connection")
// Verify we can perform basic database operations
if DB != nil {
sqlDB, err := DB.DB()
assert.NoError(t, err, "Should be able to get underlying sql.DB")
if sqlDB != nil {
err = sqlDB.Ping()
assert.NoError(t, err, "Should be able to ping the database")
}
}
}
func TestSetupCheck_MultipleInvocations(t *testing.T) {
setupHandlerTestEnvironment(t)
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Reset DB to ensure fresh test
DB = nil
// Create Gin contexts for multiple requests
recorder1 := httptest.NewRecorder()
c1, _ := gin.CreateTestContext(recorder1)
c1.Request, _ = http.NewRequest("GET", "/setup-check", nil)
recorder2 := httptest.NewRecorder()
c2, _ := gin.CreateTestContext(recorder2)
c2.Request, _ = http.NewRequest("GET", "/setup-check", nil)
// Call SetupCheck multiple times
SetupCheck(c1)
firstDB := DB
assert.NotNil(t, firstDB, "First call should establish database connection")
SetupCheck(c2)
secondDB := DB
assert.NotNil(t, secondDB, "Second call should maintain database connection")
// The key thing is that both calls succeed and establish a database connection
// The actual instance may vary depending on the implementation, but both should be valid
if firstDB != nil && secondDB != nil {
// Verify both connections are working
sqlDB1, err1 := firstDB.DB()
sqlDB2, err2 := secondDB.DB()
assert.NoError(t, err1, "First database connection should be valid")
assert.NoError(t, err2, "Second database connection should be valid")
if sqlDB1 != nil {
assert.NoError(t, sqlDB1.Ping(), "First database should be pingable")
}
if sqlDB2 != nil {
assert.NoError(t, sqlDB2.Ping(), "Second database should be pingable")
}
}
}
func TestSetupCheck_WithDifferentHTTPMethods(t *testing.T) {
setupHandlerTestEnvironment(t)
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Test with different HTTP methods
methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH"}
for _, method := range methods {
t.Run("Method_"+method, func(t *testing.T) {
// Reset DB for each test
DB = nil
// Create a Gin context with the specific HTTP method
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Request, _ = http.NewRequest(method, "/setup-check", nil)
// Call SetupCheck
SetupCheck(c)
// Verify that it works regardless of HTTP method
assert.NotNil(t, DB, "SetupCheck should work with %s method", method)
})
}
}
func TestSetupCheck_WithRequestHeaders(t *testing.T) {
setupHandlerTestEnvironment(t)
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Reset DB to ensure fresh test
DB = nil
// Create a request with various headers
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
req, _ := http.NewRequest("GET", "/setup-check", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer test-token")
req.Header.Set("User-Agent", "test-client/1.0")
c.Request = req
// Call SetupCheck
SetupCheck(c)
// Verify that it works with headers present
assert.NotNil(t, DB, "SetupCheck should work with request headers")
// Verify that the headers are still accessible in the context
assert.Equal(t, "application/json", c.GetHeader("Content-Type"))
assert.Equal(t, "Bearer test-token", c.GetHeader("Authorization"))
assert.Equal(t, "test-client/1.0", c.GetHeader("User-Agent"))
}
func TestSetupCheck_WithQueryParameters(t *testing.T) {
setupHandlerTestEnvironment(t)
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Reset DB to ensure fresh test
DB = nil
// Create a request with query parameters
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
req, _ := http.NewRequest("GET", "/setup-check?param1=value1&param2=value2", nil)
c.Request = req
// Call SetupCheck
SetupCheck(c)
// Verify that it works with query parameters
assert.NotNil(t, DB, "SetupCheck should work with query parameters")
// Verify that query parameters are still accessible
assert.Equal(t, "value1", c.Query("param1"))
assert.Equal(t, "value2", c.Query("param2"))
}
func TestSetupCheck_FullHTTPFlow(t *testing.T) {
setupHandlerTestEnvironment(t)
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Create a complete Gin router with middleware
router := gin.New()
// Add some middleware to test that SetupCheck works in a middleware chain
router.Use(gin.Logger())
router.Use(gin.Recovery())
// Register the handler
router.GET("/api/setup-check", SetupCheck)
// Create a test server
server := httptest.NewServer(router)
defer server.Close()
// Make a real HTTP request to the test server
resp, err := http.Get(server.URL + "/api/setup-check")
assert.NoError(t, err, "Should be able to make HTTP request")
defer resp.Body.Close()
// Since SetupCheck doesn't write any response, we just verify the request succeeded
// The status code should be 200 (OK) even though no response was written
assert.Equal(t, http.StatusOK, resp.StatusCode, "Request should succeed")
// Verify that DB was initialized
assert.NotNil(t, DB, "SetupCheck should initialize database in full HTTP flow")
}
func TestSetupCheck_ErrorScenarios(t *testing.T) {
// Note: Since SetupCheck just calls ConnectDatabase() and doesn't handle errors,
// we test that it doesn't panic even in error scenarios
// Save original environment
originalDB := DB
defer func() {
DB = originalDB
}()
// Set Gin to test mode
gin.SetMode(gin.TestMode)
t.Run("WithInvalidDatabaseConfig", func(t *testing.T) {
// Set invalid database configuration
os.Setenv("DATABASE_TYPE", "invalid")
os.Setenv("DATABASE_URL", "invalid://connection/string")
defer func() {
os.Unsetenv("DATABASE_TYPE")
os.Unsetenv("DATABASE_URL")
}()
// Reset DB
DB = nil
// Create Gin context
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Request, _ = http.NewRequest("GET", "/setup-check", nil)
// This might panic depending on the ConnectDatabase implementation
// We'll handle the panic gracefully
defer func() {
if r := recover(); r != nil {
// If it panics, that's expected behavior with invalid config
t.Logf("SetupCheck panicked with invalid config (expected): %v", r)
}
}()
// Call SetupCheck - might panic with invalid config
SetupCheck(c)
})
}
func TestSetupCheck_ContextIntegrity(t *testing.T) {
setupHandlerTestEnvironment(t)
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Create a Gin context with some values set
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Request, _ = http.NewRequest("GET", "/setup-check", nil)
// Set some values in the context before calling SetupCheck
c.Set("test_key", "test_value")
c.Set("user_id", 12345)
// Call SetupCheck
SetupCheck(c)
// Verify that context values are preserved
value, exists := c.Get("test_key")
assert.True(t, exists, "Context should preserve existing values")
assert.Equal(t, "test_value", value, "Context values should remain unchanged")
userID, exists := c.Get("user_id")
assert.True(t, exists, "Context should preserve existing values")
assert.Equal(t, 12345, userID, "Context values should remain unchanged")
// Verify that the database was still initialized
assert.NotNil(t, DB, "SetupCheck should initialize database without affecting context")
}
// Benchmark test for performance
func BenchmarkSetupCheck(b *testing.B) {
// Setup test environment
os.Setenv("ENVIRONMENT", "test")
defer os.Unsetenv("ENVIRONMENT")
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Create a reusable context
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Request, _ = http.NewRequest("GET", "/setup-check", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
SetupCheck(c)
}
}
func BenchmarkSetupCheck_WithHTTPOverhead(b *testing.B) {
// Setup test environment
os.Setenv("ENVIRONMENT", "test")
defer os.Unsetenv("ENVIRONMENT")
// Set Gin to test mode
gin.SetMode(gin.TestMode)
// Create a router
router := gin.New()
router.GET("/setup-check", SetupCheck)
b.ResetTimer()
for i := 0; i < b.N; i++ {
recorder := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/setup-check", nil)
router.ServeHTTP(recorder, req)
}
}
+24 -18
View File
@@ -114,26 +114,32 @@ func ResetTestDB() {
if isTestDb {
logger.Debug("ResetTestDB: Verwende Test-Datenbank, führe Cleanup durch")
sqlDB, err := DB.DB()
if err == nil {
logger.Debug("ResetTestDB: Schließe Datenbankverbindung")
sqlDB.Close()
DB = nil
logger.Debug("ResetTestDB: DB auf nil gesetzt")
if testdbTempDir != "" {
logger.Debug("ResetTestDB: Lösche temporäres Verzeichnis: %s", testdbTempDir)
err = os.RemoveAll(testdbTempDir)
if err != nil {
logger.Error("ResetTestDB: Fehler beim Löschen des temporären Verzeichnisses: %s", err.Error())
} else {
logger.Info("ResetTestDB: Temporäres Verzeichnis erfolgreich gelöscht")
}
testdbTempDir = ""
// Check if DB is not nil before trying to use it
if DB != nil {
sqlDB, err := DB.DB()
if err == nil {
logger.Debug("ResetTestDB: Schließe Datenbankverbindung")
sqlDB.Close()
} else {
logger.Error("ResetTestDB: Fehler beim Abrufen der SQL-Datenbank: %s", err.Error())
}
} else {
logger.Error("ResetTestDB: Fehler beim Abrufen der SQL-Datenbank: %s", err.Error())
logger.Debug("ResetTestDB: DB ist bereits nil, überspringe Verbindungsschließung")
}
// Always set DB to nil and clean up temp directory
DB = nil
logger.Debug("ResetTestDB: DB auf nil gesetzt")
if testdbTempDir != "" {
logger.Debug("ResetTestDB: Lösche temporäres Verzeichnis: %s", testdbTempDir)
err := os.RemoveAll(testdbTempDir)
if err != nil {
logger.Error("ResetTestDB: Fehler beim Löschen des temporären Verzeichnisses: %s", err.Error())
} else {
logger.Info("ResetTestDB: Temporäres Verzeichnis erfolgreich gelöscht")
}
testdbTempDir = ""
}
} else {
logger.Debug("ResetTestDB: Verwende Live-Datenbank, überspringe Cleanup")
+515
View File
@@ -0,0 +1,515 @@
package database
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// setupTestHelperEnvironment sets up the test environment for testhelper tests
func setupTestHelperEnvironment(t *testing.T) {
// Save original state
originalDB := DB
originalIsTestDb := isTestDb
originalTestdbTempDir := testdbTempDir
// Cleanup function
t.Cleanup(func() {
// Restore original state
DB = originalDB
isTestDb = originalIsTestDb
testdbTempDir = originalTestdbTempDir
})
}
func TestCopyFile(t *testing.T) {
// Create a temporary directory for testing
tempDir := t.TempDir()
// Create a source file with test content
sourceContent := "This is test content for file copying\nLine 2\nLine 3"
sourceFile := filepath.Join(tempDir, "source.txt")
err := os.WriteFile(sourceFile, []byte(sourceContent), 0644)
require.NoError(t, err, "Should be able to create source file")
// Define destination file path
destFile := filepath.Join(tempDir, "destination.txt")
// Test successful file copy
err = copyFile(sourceFile, destFile)
assert.NoError(t, err, "copyFile should succeed with valid source and destination")
// Verify destination file exists and has correct content
destContent, err := os.ReadFile(destFile)
assert.NoError(t, err, "Should be able to read destination file")
assert.Equal(t, sourceContent, string(destContent), "Destination file should have same content as source")
// Verify file info
sourceInfo, err := os.Stat(sourceFile)
require.NoError(t, err, "Should be able to stat source file")
destInfo, err := os.Stat(destFile)
require.NoError(t, err, "Should be able to stat destination file")
assert.Equal(t, sourceInfo.Size(), destInfo.Size(), "Files should have same size")
}
func TestCopyFile_NonExistentSource(t *testing.T) {
tempDir := t.TempDir()
sourceFile := filepath.Join(tempDir, "nonexistent.txt")
destFile := filepath.Join(tempDir, "destination.txt")
// Test copying from non-existent source
err := copyFile(sourceFile, destFile)
assert.Error(t, err, "copyFile should fail with non-existent source file")
assert.True(t, os.IsNotExist(err), "Error should be file not found error")
// Verify destination file was not created
_, err = os.Stat(destFile)
assert.True(t, os.IsNotExist(err), "Destination file should not exist")
}
func TestCopyFile_InvalidDestination(t *testing.T) {
tempDir := t.TempDir()
// Create a source file
sourceFile := filepath.Join(tempDir, "source.txt")
err := os.WriteFile(sourceFile, []byte("test content"), 0644)
require.NoError(t, err, "Should be able to create source file")
// Try to copy to an invalid destination (non-existent directory)
destFile := filepath.Join(tempDir, "nonexistent_dir", "destination.txt")
err = copyFile(sourceFile, destFile)
assert.Error(t, err, "copyFile should fail with invalid destination path")
}
func TestCopyFile_EmptyFile(t *testing.T) {
tempDir := t.TempDir()
// Create an empty source file
sourceFile := filepath.Join(tempDir, "empty.txt")
err := os.WriteFile(sourceFile, []byte(""), 0644)
require.NoError(t, err, "Should be able to create empty source file")
destFile := filepath.Join(tempDir, "empty_dest.txt")
// Test copying empty file
err = copyFile(sourceFile, destFile)
assert.NoError(t, err, "copyFile should succeed with empty file")
// Verify destination file exists and is empty
destContent, err := os.ReadFile(destFile)
assert.NoError(t, err, "Should be able to read empty destination file")
assert.Empty(t, destContent, "Destination file should be empty")
}
func TestCopyFile_LargeFile(t *testing.T) {
tempDir := t.TempDir()
// Create a larger file (1MB)
sourceFile := filepath.Join(tempDir, "large.txt")
largeContent := strings.Repeat("This is a test line.\n", 50000) // ~1MB
err := os.WriteFile(sourceFile, []byte(largeContent), 0644)
require.NoError(t, err, "Should be able to create large source file")
destFile := filepath.Join(tempDir, "large_dest.txt")
// Test copying large file
err = copyFile(sourceFile, destFile)
assert.NoError(t, err, "copyFile should succeed with large file")
// Verify file sizes match
sourceInfo, err := os.Stat(sourceFile)
require.NoError(t, err, "Should be able to stat source file")
destInfo, err := os.Stat(destFile)
require.NoError(t, err, "Should be able to stat destination file")
assert.Equal(t, sourceInfo.Size(), destInfo.Size(), "Large files should have same size")
}
func TestSetupTestDB_WithTestDatabase(t *testing.T) {
setupTestHelperEnvironment(t)
// Reset global state
DB = nil
isTestDb = false
// Create a mock prepared test database
tempDir := t.TempDir()
mockPreparedDB := filepath.Join(tempDir, "prepared_test.db")
// Create a simple SQLite database file
db, err := gorm.Open(sqlite.Open(mockPreparedDB), &gorm.Config{})
require.NoError(t, err, "Should be able to create mock prepared database")
sqlDB, err := db.DB()
require.NoError(t, err, "Should be able to get underlying sql.DB")
sqlDB.Close()
// Temporarily override PreparedTestDB path
originalPreparedTestDB := PreparedTestDB
PreparedTestDB = mockPreparedDB
defer func() {
PreparedTestDB = originalPreparedTestDB
}()
// Test SetupTestDB with test database (default behavior)
SetupTestDB()
// Verify database was set up
assert.NotNil(t, DB, "SetupTestDB should set global DB")
assert.True(t, isTestDb, "Should be using test database")
// Verify we can use the database
sqlDB, err = DB.DB()
assert.NoError(t, err, "Should be able to get underlying sql.DB")
if sqlDB != nil {
err = sqlDB.Ping()
assert.NoError(t, err, "Should be able to ping test database")
}
}
func TestSetupTestDB_WithLiveDatabase(t *testing.T) {
setupTestHelperEnvironment(t)
// Reset global state
DB = nil
isTestDb = false
testdbTempDir = ""
// Test SetupTestDB with live database
SetupTestDB(false)
// Verify database was set up
assert.NotNil(t, DB, "SetupTestDB should set global DB")
assert.False(t, isTestDb, "Should be using live database")
// Verify we can use the database (this will connect to the actual configured database)
sqlDB, err := DB.DB()
if err == nil && sqlDB != nil {
err = sqlDB.Ping()
assert.NoError(t, err, "Should be able to ping live database")
}
}
func TestSetupTestDB_AlreadyInitialized(t *testing.T) {
setupTestHelperEnvironment(t)
// Create a mock database connection
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "existing.db")
existingDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
require.NoError(t, err, "Should be able to create existing database")
// Set global DB to existing connection
DB = existingDB
isTestDb = true
// Call SetupTestDB - should not change the existing DB
SetupTestDB()
// Verify DB remains the same
assert.Equal(t, existingDB, DB, "SetupTestDB should not change existing DB")
}
func TestSetupTestDB_MissingPreparedDatabase(t *testing.T) {
setupTestHelperEnvironment(t)
// Reset global state
DB = nil
isTestDb = false
// Set PreparedTestDB to non-existent file
originalPreparedTestDB := PreparedTestDB
PreparedTestDB = "/nonexistent/path/to/database.db"
defer func() {
PreparedTestDB = originalPreparedTestDB
}()
// Test SetupTestDB with missing prepared database - should panic
defer func() {
if r := recover(); r != nil {
assert.Contains(t, r.(string), "failed to copy prepared test database",
"Should panic with appropriate error message")
}
}()
SetupTestDB(true)
// If we reach here, the test should fail
t.Fatal("Expected panic when prepared test database is missing")
}
func TestSetupTestDB_ParameterVariations(t *testing.T) {
setupTestHelperEnvironment(t)
tests := []struct {
name string
params []bool
expectedVal bool
description string
}{
{
name: "No parameters",
params: []bool{},
expectedVal: true,
description: "Should default to test database",
},
{
name: "Explicit true",
params: []bool{true},
expectedVal: true,
description: "Should use test database when explicitly set to true",
},
{
name: "Explicit false",
params: []bool{false},
expectedVal: false,
description: "Should use live database when explicitly set to false",
},
{
name: "Multiple parameters (first true)",
params: []bool{true, false},
expectedVal: true,
description: "Should use first parameter when multiple provided",
},
{
name: "Multiple parameters (first false)",
params: []bool{false, true},
expectedVal: false,
description: "Should use first parameter when multiple provided",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset state for each test
DB = nil
isTestDb = false
if tt.expectedVal {
// For test database, we need a mock prepared database
tempDir := t.TempDir()
mockPreparedDB := filepath.Join(tempDir, "prepared_test.db")
db, err := gorm.Open(sqlite.Open(mockPreparedDB), &gorm.Config{})
require.NoError(t, err, "Should be able to create mock prepared database")
sqlDB, err := db.DB()
require.NoError(t, err, "Should be able to get underlying sql.DB")
sqlDB.Close()
originalPreparedTestDB := PreparedTestDB
PreparedTestDB = mockPreparedDB
defer func() {
PreparedTestDB = originalPreparedTestDB
}()
}
SetupTestDB(tt.params...)
assert.Equal(t, tt.expectedVal, isTestDb, tt.description)
assert.NotNil(t, DB, "Database should be initialized")
})
}
}
func TestResetTestDB_WithTestDatabase(t *testing.T) {
setupTestHelperEnvironment(t)
// Set up a test database first
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "test_reset.db")
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
require.NoError(t, err, "Should be able to create test database")
// Set global state as if SetupTestDB was called
DB = testDB
isTestDb = true
// Call ResetTestDB
ResetTestDB()
// Verify cleanup
assert.Nil(t, DB, "DB should be reset to nil")
// Note: We can't easily test directory cleanup since the package-level
// testdbTempDir variable isn't being set properly in the current implementation
// But we can verify that the function completes without error
}
func TestResetTestDB_WithLiveDatabase(t *testing.T) {
setupTestHelperEnvironment(t)
// Set up as if using live database
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "live_db.db")
liveDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
require.NoError(t, err, "Should be able to create live database")
DB = liveDB
isTestDb = false
// Call ResetTestDB
ResetTestDB()
// For live database, cleanup should be skipped
// DB and other state should remain unchanged for live database
// Note: The actual behavior might vary, but the function should not crash
assert.True(t, true, "ResetTestDB should not crash with live database")
}
func TestResetTestDB_WithNilDB(t *testing.T) {
setupTestHelperEnvironment(t)
// Set state with nil DB
DB = nil
isTestDb = true
// Call ResetTestDB - should not crash
defer func() {
if r := recover(); r != nil {
t.Fatalf("ResetTestDB should not panic with nil DB: %v", r)
}
}()
ResetTestDB()
// Should complete without error
assert.Nil(t, DB, "DB should remain nil")
}
func TestResetTestDB_ErrorHandling(t *testing.T) {
setupTestHelperEnvironment(t)
// Create a test database and close it immediately to simulate error conditions
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "error_test.db")
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
require.NoError(t, err, "Should be able to create test database")
// Close the underlying connection to simulate error
sqlDB, err := testDB.DB()
require.NoError(t, err, "Should be able to get underlying sql.DB")
sqlDB.Close()
// Set global state
DB = testDB
isTestDb = true
// Call ResetTestDB - should handle errors gracefully
defer func() {
if r := recover(); r != nil {
t.Fatalf("ResetTestDB should not panic on errors: %v", r)
}
}()
ResetTestDB()
// Function should complete even with errors
assert.True(t, true, "ResetTestDB should handle errors gracefully")
}
func TestSetupTestDB_ResetTestDB_Cycle(t *testing.T) {
setupTestHelperEnvironment(t)
// Create a mock prepared test database
tempDir := t.TempDir()
mockPreparedDB := filepath.Join(tempDir, "prepared_cycle_test.db")
db, err := gorm.Open(sqlite.Open(mockPreparedDB), &gorm.Config{})
require.NoError(t, err, "Should be able to create mock prepared database")
sqlDB, err := db.DB()
require.NoError(t, err, "Should be able to get underlying sql.DB")
sqlDB.Close()
originalPreparedTestDB := PreparedTestDB
PreparedTestDB = mockPreparedDB
defer func() {
PreparedTestDB = originalPreparedTestDB
}()
// Test complete setup and reset cycle
for i := 0; i < 3; i++ {
t.Run(fmt.Sprintf("Cycle_%d", i+1), func(t *testing.T) {
// Reset state
DB = nil
isTestDb = false
// Setup
SetupTestDB()
assert.NotNil(t, DB, "SetupTestDB should initialize DB")
assert.True(t, isTestDb, "Should be using test database")
// Verify database is working
sqlDB, err := DB.DB()
assert.NoError(t, err, "Should be able to get underlying sql.DB")
if sqlDB != nil {
err = sqlDB.Ping()
assert.NoError(t, err, "Should be able to ping the database")
}
// Reset
ResetTestDB()
assert.Nil(t, DB, "ResetTestDB should reset DB to nil")
})
}
}
// Benchmark tests
func BenchmarkCopyFile(b *testing.B) {
// Create test files
tempDir := b.TempDir()
sourceFile := filepath.Join(tempDir, "source.txt")
content := strings.Repeat("benchmark test content\n", 1000)
err := os.WriteFile(sourceFile, []byte(content), 0644)
if err != nil {
b.Fatalf("Failed to create source file: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
destFile := filepath.Join(tempDir, fmt.Sprintf("dest_%d.txt", i))
err := copyFile(sourceFile, destFile)
if err != nil {
b.Fatalf("copyFile failed: %v", err)
}
}
}
func BenchmarkSetupTestDB(b *testing.B) {
// Create a mock prepared test database
tempDir := b.TempDir()
mockPreparedDB := filepath.Join(tempDir, "benchmark_prepared.db")
db, err := gorm.Open(sqlite.Open(mockPreparedDB), &gorm.Config{})
if err != nil {
b.Fatalf("Failed to create mock prepared database: %v", err)
}
sqlDB, err := db.DB()
if err != nil {
b.Fatalf("Failed to get underlying sql.DB: %v", err)
}
sqlDB.Close()
originalPreparedTestDB := PreparedTestDB
PreparedTestDB = mockPreparedDB
defer func() {
PreparedTestDB = originalPreparedTestDB
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Reset state for each iteration
DB = nil
isTestDb = false
SetupTestDB()
// Clean up
ResetTestDB()
}
}