将仓储层/基础设施改为 Ent + 原生 SQL 执行路径,并移除 AutoMigrate 与 GORM 依赖。 重构内容包括: - 仓储层改用 Ent/SQL(含 usage_log/account 等复杂查询),统一错误映射 - 基础设施与 setup 初始化切换为 Ent + SQL migrations - 集成测试与 fixtures 迁移到 Ent 事务模型 - 清理遗留 GORM 模型/依赖,补充迁移与文档说明 - 增加根目录 Makefile 便于前后端编译 测试: - go test -tags unit ./... - go test -tags integration ./...
91 lines
3.5 KiB
Go
91 lines
3.5 KiB
Go
//go:build integration
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/infrastructure"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMigrationsRunner_IsIdempotent_AndSchemaIsUpToDate(t *testing.T) {
|
|
tx := testTx(t)
|
|
|
|
// Re-apply migrations to verify idempotency (no errors, no duplicate rows).
|
|
require.NoError(t, infrastructure.ApplyMigrations(context.Background(), integrationDB))
|
|
|
|
// schema_migrations should have at least the current migration set.
|
|
var applied int
|
|
require.NoError(t, tx.QueryRowContext(context.Background(), "SELECT COUNT(*) FROM schema_migrations").Scan(&applied))
|
|
require.GreaterOrEqual(t, applied, 7, "expected schema_migrations to contain applied migrations")
|
|
|
|
// users: columns required by repository queries
|
|
requireColumn(t, tx, "users", "username", "character varying", 100, false)
|
|
requireColumn(t, tx, "users", "wechat", "character varying", 100, false)
|
|
requireColumn(t, tx, "users", "notes", "text", 0, false)
|
|
|
|
// accounts: schedulable and rate-limit fields
|
|
requireColumn(t, tx, "accounts", "schedulable", "boolean", 0, false)
|
|
requireColumn(t, tx, "accounts", "rate_limited_at", "timestamp with time zone", 0, true)
|
|
requireColumn(t, tx, "accounts", "rate_limit_reset_at", "timestamp with time zone", 0, true)
|
|
requireColumn(t, tx, "accounts", "overload_until", "timestamp with time zone", 0, true)
|
|
requireColumn(t, tx, "accounts", "session_window_status", "character varying", 20, true)
|
|
|
|
// api_keys: key length should be 128
|
|
requireColumn(t, tx, "api_keys", "key", "character varying", 128, false)
|
|
|
|
// redeem_codes: subscription fields
|
|
requireColumn(t, tx, "redeem_codes", "group_id", "bigint", 0, true)
|
|
requireColumn(t, tx, "redeem_codes", "validity_days", "integer", 0, false)
|
|
|
|
// usage_logs: billing_type used by filters/stats
|
|
requireColumn(t, tx, "usage_logs", "billing_type", "smallint", 0, false)
|
|
|
|
// settings table should exist
|
|
var settingsRegclass sql.NullString
|
|
require.NoError(t, tx.QueryRowContext(context.Background(), "SELECT to_regclass('public.settings')").Scan(&settingsRegclass))
|
|
require.True(t, settingsRegclass.Valid, "expected settings table to exist")
|
|
|
|
// user_allowed_groups table should exist
|
|
var uagRegclass sql.NullString
|
|
require.NoError(t, tx.QueryRowContext(context.Background(), "SELECT to_regclass('public.user_allowed_groups')").Scan(&uagRegclass))
|
|
require.True(t, uagRegclass.Valid, "expected user_allowed_groups table to exist")
|
|
}
|
|
|
|
func requireColumn(t *testing.T, tx *sql.Tx, table, column, dataType string, maxLen int, nullable bool) {
|
|
t.Helper()
|
|
|
|
var row struct {
|
|
DataType string
|
|
MaxLen sql.NullInt64
|
|
Nullable string
|
|
}
|
|
|
|
err := tx.QueryRowContext(context.Background(), `
|
|
SELECT
|
|
data_type,
|
|
character_maximum_length,
|
|
is_nullable
|
|
FROM information_schema.columns
|
|
WHERE table_schema = 'public'
|
|
AND table_name = $1
|
|
AND column_name = $2
|
|
`, table, column).Scan(&row.DataType, &row.MaxLen, &row.Nullable)
|
|
require.NoError(t, err, "query information_schema.columns for %s.%s", table, column)
|
|
require.Equal(t, dataType, row.DataType, "data_type mismatch for %s.%s", table, column)
|
|
|
|
if maxLen > 0 {
|
|
require.True(t, row.MaxLen.Valid, "expected maxLen for %s.%s", table, column)
|
|
require.Equal(t, int64(maxLen), row.MaxLen.Int64, "maxLen mismatch for %s.%s", table, column)
|
|
}
|
|
|
|
if nullable {
|
|
require.Equal(t, "YES", row.Nullable, "nullable mismatch for %s.%s", table, column)
|
|
} else {
|
|
require.Equal(t, "NO", row.Nullable, "nullable mismatch for %s.%s", table, column)
|
|
}
|
|
}
|