feat: 新增 MySQL 专项测试能力
- 新增 backend/tests/mysql/ 目录,包含 Docker Compose 配置和测试文件 - 新增 Makefile 命令: test-mysql, test-mysql-up, test-mysql-down, test-mysql-quick - 使用 build tag 控制测试启用,默认不运行 - 测试覆盖: 迁移正确性、外键约束、UNIQUE 约束、并发写入 - 发现 statsRepo.Record 存在并发 bug(检查-然后-操作竞态条件)
This commit is contained in:
130
backend/tests/mysql/constraint_test.go
Normal file
130
backend/tests/mysql/constraint_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
//go:build mysql
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"nex/backend/internal/config"
|
||||
)
|
||||
|
||||
func TestConstraint_ForeignKeyEnforced(t *testing.T) {
|
||||
db := SetupMySQLTestDB(t)
|
||||
|
||||
model := config.Model{
|
||||
ID: "test-model-id",
|
||||
ProviderID: "non-existent-provider",
|
||||
ModelName: "gpt-4",
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
err := db.Create(&model).Error
|
||||
assert.Error(t, err, "创建 model 时 provider_id 不存在应失败")
|
||||
assert.Contains(t, err.Error(), "foreign key constraint", "错误应为外键约束错误")
|
||||
}
|
||||
|
||||
func TestConstraint_CascadeDelete(t *testing.T) {
|
||||
db := SetupMySQLTestDB(t)
|
||||
|
||||
provider := config.Provider{
|
||||
ID: "test-provider-cascade",
|
||||
Name: "Test Provider",
|
||||
APIKey: "test-key",
|
||||
BaseURL: "https://test.com",
|
||||
Enabled: true,
|
||||
}
|
||||
err := db.Create(&provider).Error
|
||||
require.NoError(t, err, "创建 provider 应成功")
|
||||
|
||||
model := config.Model{
|
||||
ID: "test-model-cascade",
|
||||
ProviderID: provider.ID,
|
||||
ModelName: "gpt-4",
|
||||
Enabled: true,
|
||||
}
|
||||
err = db.Create(&model).Error
|
||||
require.NoError(t, err, "创建 model 应成功")
|
||||
|
||||
err = db.Delete(&provider).Error
|
||||
require.NoError(t, err, "删除 provider 应成功")
|
||||
|
||||
var count int64
|
||||
err = db.Model(&config.Model{}).Where("provider_id = ?", provider.ID).Count(&count).Error
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), count, "删除 provider 后其 models 应被级联删除")
|
||||
}
|
||||
|
||||
func TestConstraint_UniqueProviderModel(t *testing.T) {
|
||||
db := SetupMySQLTestDB(t)
|
||||
|
||||
provider := config.Provider{
|
||||
ID: "test-provider-unique",
|
||||
Name: "Test Provider",
|
||||
APIKey: "test-key",
|
||||
BaseURL: "https://test.com",
|
||||
Enabled: true,
|
||||
}
|
||||
err := db.Create(&provider).Error
|
||||
require.NoError(t, err, "创建 provider 应成功")
|
||||
|
||||
model1 := config.Model{
|
||||
ID: "test-model-unique-1",
|
||||
ProviderID: provider.ID,
|
||||
ModelName: "gpt-4",
|
||||
Enabled: true,
|
||||
}
|
||||
err = db.Create(&model1).Error
|
||||
require.NoError(t, err, "创建第一个 model 应成功")
|
||||
|
||||
model2 := config.Model{
|
||||
ID: "test-model-unique-2",
|
||||
ProviderID: provider.ID,
|
||||
ModelName: "gpt-4",
|
||||
Enabled: true,
|
||||
}
|
||||
err = db.Create(&model2).Error
|
||||
assert.Error(t, err, "创建相同 (provider_id, model_name) 的 model 应失败")
|
||||
assert.True(t, errors.Is(err, gorm.ErrDuplicatedKey) ||
|
||||
(err != nil && (err.Error() == "Error 1062" || containsDuplicateError(err.Error()))),
|
||||
"错误应为唯一约束错误")
|
||||
}
|
||||
|
||||
func TestConstraint_UniqueUsageStats(t *testing.T) {
|
||||
db := SetupMySQLTestDB(t)
|
||||
|
||||
today := time.Now().Format("2006-01-02")
|
||||
todayTime, _ := time.Parse("2006-01-02", today)
|
||||
|
||||
providerID := "test-provider-unique-stats"
|
||||
|
||||
stats1 := config.UsageStats{
|
||||
ProviderID: providerID,
|
||||
ModelName: "gpt-4",
|
||||
RequestCount: 10,
|
||||
Date: todayTime,
|
||||
}
|
||||
err := db.Create(&stats1).Error
|
||||
require.NoError(t, err, "创建第一个 usage_stats 应成功")
|
||||
|
||||
stats2 := config.UsageStats{
|
||||
ProviderID: providerID,
|
||||
ModelName: "gpt-4",
|
||||
RequestCount: 20,
|
||||
Date: todayTime,
|
||||
}
|
||||
err = db.Create(&stats2).Error
|
||||
assert.Error(t, err, "创建相同 (provider_id, model_name, date) 的 usage_stats 应失败")
|
||||
assert.True(t, errors.Is(err, gorm.ErrDuplicatedKey) ||
|
||||
(err != nil && (err.Error() == "Error 1062" || containsDuplicateError(err.Error()))),
|
||||
"错误应为唯一约束错误")
|
||||
}
|
||||
|
||||
func containsDuplicateError(errStr string) bool {
|
||||
return len(errStr) > 0 && (errStr[0:8] == "Error 10" || errStr[0:5] == "Dupli")
|
||||
}
|
||||
Reference in New Issue
Block a user