Files
sub2api-cn-relay-manager/internal/testutil/sqlite_test.go
phamnazage-jpg 3838d78b00 test: H-01 补充 testutil 测试
- 添加 SQLiteTestDSN 函数测试(外键启用/禁用、特殊字符)
- 添加路径唯一性验证测试
- 添加 OpenSQLiteStore 功能测试(含并发测试)
- 添加 CloseSQLiteStore 测试
- 添加无效 DSN 错误处理测试
2026-06-02 06:49:07 +08:00

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)
}
}