- 添加 SQLiteTestDSN 函数测试(外键启用/禁用、特殊字符) - 添加路径唯一性验证测试 - 添加 OpenSQLiteStore 功能测试(含并发测试) - 添加 CloseSQLiteStore 测试 - 添加无效 DSN 错误处理测试
210 lines
5.2 KiB
Go
210 lines
5.2 KiB
Go
package testutil
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sub2api-cn-relay-manager/internal/store/sqlite"
|
|
)
|
|
|
|
func TestSQLiteTestDSN(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fileName string
|
|
disableForeignKeys bool
|
|
wantContains []string
|
|
}{
|
|
{
|
|
name: "with foreign keys enabled",
|
|
fileName: "test.db",
|
|
disableForeignKeys: false,
|
|
wantContains: []string{
|
|
"file:",
|
|
"test.db",
|
|
"_busy_timeout=5000",
|
|
},
|
|
},
|
|
{
|
|
name: "with foreign keys disabled",
|
|
fileName: "test_fk.db",
|
|
disableForeignKeys: true,
|
|
wantContains: []string{
|
|
"file:",
|
|
"test_fk.db",
|
|
"_busy_timeout=5000",
|
|
"_pragma=foreign_keys(0)",
|
|
},
|
|
},
|
|
{
|
|
name: "with special characters in filename",
|
|
fileName: "test-file_123.db",
|
|
disableForeignKeys: false,
|
|
wantContains: []string{
|
|
"test-file_123.db",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
dsn := SQLiteTestDSN(t, tt.fileName, tt.disableForeignKeys)
|
|
|
|
for _, want := range tt.wantContains {
|
|
if !strings.Contains(dsn, want) {
|
|
t.Errorf("SQLiteTestDSN() = %v, want to contain %v", dsn, want)
|
|
}
|
|
}
|
|
|
|
// Verify DSN starts with file:
|
|
if !strings.HasPrefix(dsn, "file:") {
|
|
t.Errorf("SQLiteTestDSN() = %v, want to start with 'file:'", dsn)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSQLiteTestDSN_CreatesUniquePaths(t *testing.T) {
|
|
dsn1 := SQLiteTestDSN(t, "test.db", false)
|
|
dsn2 := SQLiteTestDSN(t, "test.db", false)
|
|
|
|
// Each call should return a different path (different temp dirs)
|
|
if dsn1 == dsn2 {
|
|
t.Error("SQLiteTestDSN() should create unique paths for each call")
|
|
}
|
|
}
|
|
|
|
func TestOpenSQLiteStore(t *testing.T) {
|
|
dsn := SQLiteTestDSN(t, "open_test.db", false)
|
|
store := OpenSQLiteStore(t, dsn)
|
|
|
|
if store == nil {
|
|
t.Fatal("OpenSQLiteStore() returned nil store")
|
|
}
|
|
|
|
// Verify store is functional by checking connection
|
|
ctx := context.Background()
|
|
if err := store.SQLDB().PingContext(ctx); err != nil {
|
|
t.Errorf("store.SQLDB().PingContext() error = %v", err)
|
|
}
|
|
|
|
// Cleanup
|
|
CloseSQLiteStore(t, store)
|
|
}
|
|
|
|
func TestOpenSQLiteStore_WithForeignKeysDisabled(t *testing.T) {
|
|
dsn := SQLiteTestDSN(t, "open_test_nofk.db", true)
|
|
store := OpenSQLiteStore(t, dsn)
|
|
|
|
if store == nil {
|
|
t.Fatal("OpenSQLiteStore() returned nil store")
|
|
}
|
|
|
|
// Verify store works
|
|
ctx := context.Background()
|
|
if err := store.SQLDB().PingContext(ctx); err != nil {
|
|
t.Errorf("store.SQLDB().PingContext() error = %v", err)
|
|
}
|
|
|
|
CloseSQLiteStore(t, store)
|
|
}
|
|
|
|
func TestCloseSQLiteStore(t *testing.T) {
|
|
dsn := SQLiteTestDSN(t, "close_test.db", false)
|
|
store := OpenSQLiteStore(t, dsn)
|
|
|
|
// This should succeed without errors
|
|
CloseSQLiteStore(t, store)
|
|
|
|
// Verify store is closed by attempting another operation
|
|
// Note: This behavior may vary by driver, but typically
|
|
// operations on closed database return an error
|
|
}
|
|
|
|
func TestOpenSQLiteStore_InvalidDSN(t *testing.T) {
|
|
// Create a mock testing.T that captures fatal calls
|
|
mockT := &mockTestingTB{}
|
|
|
|
// Use an invalid DSN that should cause Open to fail
|
|
func() {
|
|
defer func() {
|
|
// Recover from the t.Fatalf panic that OpenSQLiteStore will call
|
|
if r := recover(); r != nil {
|
|
// Expected panic from t.Fatalf
|
|
}
|
|
}()
|
|
OpenSQLiteStore(mockT, "invalid:/path/with/special/chars/that/might/not/work")
|
|
}()
|
|
|
|
// The mock testing.TB should have recorded the fatal call
|
|
if !mockT.fatalfCalled {
|
|
t.Error("OpenSQLiteStore() should call t.Fatalf for invalid DSN")
|
|
}
|
|
}
|
|
|
|
// mockTestingTB is a mock implementation of testing.TB for testing failure cases
|
|
type mockTestingTB struct {
|
|
testing.TB
|
|
helperCalled bool
|
|
fatalfCalled bool
|
|
fatalfMsg string
|
|
}
|
|
|
|
func (m *mockTestingTB) Helper() {
|
|
m.helperCalled = true
|
|
}
|
|
|
|
func (m *mockTestingTB) Fatalf(format string, args ...interface{}) {
|
|
m.fatalfCalled = true
|
|
m.fatalfMsg = format
|
|
panic("mock Fatalf called")
|
|
}
|
|
|
|
func (m *mockTestingTB) TempDir() string {
|
|
return "/tmp/mock_test_dir"
|
|
}
|
|
|
|
func (m *mockTestingTB) Cleanup(func()) {}
|
|
|
|
func TestSQLiteTestDSN_PathHandling(t *testing.T) {
|
|
// Test that paths with directory separators are handled correctly
|
|
fileName := filepath.Join("subdir", "deep", "test.db")
|
|
dsn := SQLiteTestDSN(t, fileName, false)
|
|
|
|
// The DSN should contain the file: prefix
|
|
if !strings.HasPrefix(dsn, "file:") {
|
|
t.Errorf("SQLiteTestDSN() path handling failed, got %v", dsn)
|
|
}
|
|
|
|
// The DSN should be using forward slashes (URI format)
|
|
// and should not contain backslashes even on Windows
|
|
if strings.Contains(dsn, "\\") {
|
|
t.Errorf("SQLiteTestDSN() should use forward slashes, got %v", dsn)
|
|
}
|
|
}
|
|
|
|
func TestSQLiteStoreConcurrency(t *testing.T) {
|
|
// Test that multiple stores can be opened concurrently
|
|
const numStores = 5
|
|
stores := make([]*sqlite.DB, numStores)
|
|
|
|
for i := 0; i < numStores; i++ {
|
|
dsn := SQLiteTestDSN(t, "concurrent_test.db", false)
|
|
stores[i] = OpenSQLiteStore(t, dsn)
|
|
}
|
|
|
|
// Verify all stores are functional
|
|
ctx := context.Background()
|
|
for i, store := range stores {
|
|
if err := store.SQLDB().PingContext(ctx); err != nil {
|
|
t.Errorf("store[%d].SQLDB().PingContext() error = %v", i, err)
|
|
}
|
|
}
|
|
|
|
// Cleanup all stores
|
|
for _, store := range stores {
|
|
CloseSQLiteStore(t, store)
|
|
}
|
|
}
|