Files
90CNServer/server/internal/modules/dnf/repository/mysql/schema_test.go
T
hanfuit 645b64fe21 DNF90 服务端与启动器完整源码(含 GM 工具功能增强)
服务端(Go):
- GM 塞背包/带属性装备发放:在线角色推送 op14 增量物品行,背包实时刷新,无需回角色选择界面
- GM 改金币/改等级:去掉在线拒绝,落库后推送 op14 钱包/op37 等级状态,在线实时刷新
- 等级端点统一切换到新版 GMSetCharacterLevel(自带在线刷新)
- 无限/锁死疲劳模式 + 燃烧疲劳:每房间消耗 2 点、打怪与通关经验双倍
- 燃烧疲劳开关时给所有在线角色推送系统提示(op12 mode 0)
- 深渊史诗掉率:GM 全局倍率 + 城镇增益药剂窗口倍率,真实接入掉落结算

启动器(Python/JS):
- 标题栏自绘、窗口 1204x717、禁止拖拽改大小、仅最小化/关闭
- GM 工具:疲劳与燃烧、深渊史诗掉率、拍卖行上架(单一定价对齐)
- 拍卖行上架前端对齐服务端单一价格语义,修复 400 报错
2026-11-04 14:31:55 +08:00

499 lines
19 KiB
Go

// 本文件验证 DNF 仓储 schema 生成、落地产物和 legacy USERINFO 表白名单。
package mysql
import (
"context"
"database/sql"
"errors"
"longheng.io/server/internal/modules/dnf/repository"
"os"
"path/filepath"
"strings"
"testing"
mysql "github.com/go-sql-driver/mysql"
)
type fakeSchemaExec struct {
statements []string
failAt int
failErr error
}
func (f *fakeSchemaExec) ExecContext(_ context.Context, query string, _ ...any) (sql.Result, error) {
f.statements = append(f.statements, query)
if f.failAt > 0 && len(f.statements) == f.failAt {
if f.failErr != nil {
return nil, f.failErr
}
return nil, errFakeSchemaExec
}
return nil, nil
}
var errFakeSchemaExec = errors.New("fake schema exec")
func TestMySQLSchemaCreatesDatabaseAndTables(t *testing.T) {
schema, err := MySQLSchema(SchemaOptions{
CreateDatabases: true,
TablePrefix: "dnf",
DatabasePlan: repository.DatabasePlan{
WriteDatabases: []string{"dnf_s1_w1"},
},
})
if err != nil {
t.Fatalf("MySQLSchema() error = %v", err)
}
if len(schema) != 47 {
t.Fatalf("schema statement count = %d, want 47", len(schema))
}
joined := strings.Join(schema, "\n")
for _, want := range []string{
"CREATE DATABASE IF NOT EXISTS `dnf_s1_w1`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_accounts`",
"honor_exp BIGINT UNSIGNED NOT NULL DEFAULT 0",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_characters`",
"`pc_room_id` BIGINT NOT NULL DEFAULT 65537",
"`fatigue` SMALLINT UNSIGNED NOT NULL DEFAULT 156",
"`fatigue_limit` SMALLINT UNSIGNED NOT NULL DEFAULT 156",
"`tutorial_completed` TINYINT UNSIGNED NOT NULL DEFAULT 0",
"`tutorial_reward_progress_38` TINYINT UNSIGNED NOT NULL DEFAULT 0",
"`story_digest_last_level` INT UNSIGNED NOT NULL DEFAULT 0",
"`story_digest_migration_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0",
"`town_id` SMALLINT UNSIGNED NOT NULL DEFAULT 38",
"`area_id` SMALLINT UNSIGNED NOT NULL DEFAULT 1",
"`pos_x` SMALLINT NOT NULL DEFAULT 450",
"`pos_y` SMALLINT NOT NULL DEFAULT 234",
"`stat_strength` INT NOT NULL DEFAULT 0",
"`stat_independent_attack` INT NOT NULL DEFAULT 0",
"`roster_card_flag` TINYINT UNSIGNED NOT NULL DEFAULT 0",
"`create_option_byte_63` TINYINT UNSIGNED NOT NULL DEFAULT 0",
"UNIQUE KEY uk_dnf_characters_account_slot_active (account_id, slot, delete_flag)",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_character_rosters`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_equipments`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_equipment_entries`",
"total_sp INT NOT NULL DEFAULT 0",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_skill_layouts`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_dungeon_permissions`",
"clear_state TINYINT UNSIGNED NOT NULL DEFAULT 0",
"KEY idx_dnf_dungeon_permissions_character_sort (character_id, sort_order)",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_hell_party_permissions`",
"KEY idx_dnf_hell_party_permissions_character_quest (character_id, quest_id)",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_pet_entries`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_pets`",
"equipped_key VARCHAR(128) NOT NULL DEFAULT ''",
"town_display TINYINT UNSIGNED NOT NULL DEFAULT 0",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_quests`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_quest_states`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_skill_states`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_skill_tree_points`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_skill_tree_states`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_skill_commands`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_mailboxes`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_mails`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_packet_templates`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_epic_piece_balances`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_united_friend_relations`",
"COLLATE utf8mb4_bin",
} {
if !strings.Contains(joined, want) {
t.Fatalf("schema missing %q:\n%s", want, joined)
}
}
if strings.Contains(joined, "_json JSON") {
t.Fatalf("schema still contains repository JSON columns:\n%s", joined)
}
}
func TestMySQLSchemaIncludesCSharpLegacyTables(t *testing.T) {
wantLegacyTables := embeddedCSharpTableCount(t) + 2
schema, err := MySQLSchema(SchemaOptions{
IncludeCSharpLegacySchema: true,
TablePrefix: "dnf",
DatabasePlan: repository.DatabasePlan{
WriteDatabases: []string{"dnf_s1_w1"},
},
})
if err != nil {
t.Fatalf("MySQLSchema() error = %v", err)
}
joined := strings.Join(schema, "\n")
if got := countSQLPrefix(schema, "CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_legacy_"); got != wantLegacyTables {
t.Fatalf("legacy table count = %d, want %d", got, wantLegacyTables)
}
for _, want := range []string{
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_legacy_accounts`",
"`extra46` BIGINT NOT NULL DEFAULT 0",
"`extra47` BIGINT NOT NULL DEFAULT 0",
"`extra51` BIGINT NOT NULL DEFAULT 0",
"`active_status_resistance_17` BIGINT NOT NULL DEFAULT 0",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_legacy_character_userinfo80_slots`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_legacy_character_userinfo90_control`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_legacy_character_userinfob6_values`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_legacy_character_userinfo22e_state`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_legacy_character_userinfo29f_rows`",
"CREATE TABLE IF NOT EXISTS `dnf_s1_w1`.`dnf_legacy_account_settings`",
"INSERT IGNORE INTO `dnf_s1_w1`.`dnf_legacy_accounts`",
} {
if !strings.Contains(joined, want) {
t.Fatalf("legacy schema missing %q", want)
}
}
}
func TestCSharpLegacySchemaRemovesInvalidLargeDefaults(t *testing.T) {
schema, err := CSharpLegacyMySQLSchema("dnf_s1_w1", "dnf")
if err != nil {
t.Fatalf("CSharpLegacyMySQLSchema() error = %v", err)
}
joined := strings.Join(schema, "\n")
if !strings.Contains(joined, "`payload` LONGBLOB NOT NULL") {
t.Fatalf("legacy schema should keep payload as required LONGBLOB:\n%s", joined)
}
for _, bad := range []string{
"LONGBLOB NOT NULL DEFAULT",
"BLOB NOT NULL DEFAULT",
"LONGTEXT NOT NULL DEFAULT",
"TEXT NOT NULL DEFAULT",
"JSON NOT NULL DEFAULT",
} {
if strings.Contains(joined, bad) {
t.Fatalf("legacy schema contains MySQL-invalid large column default %q", bad)
}
}
}
func TestRepositorySQLFileContainsLegacySchema(t *testing.T) {
wantLegacyTables := embeddedCSharpTableCount(t) + 2
path := filepath.Join("testdata", "repository.mysql.sql")
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read repository.mysql.sql: %v", err)
}
sql := string(data)
if got := strings.Count(sql, "CREATE TABLE IF NOT EXISTS `dnf_s9999_w01`.`dnf_legacy_"); got != wantLegacyTables {
t.Fatalf("repository.mysql.sql legacy table count = %d, want %d", got, wantLegacyTables)
}
for _, want := range []string{
"dnf_character_rosters",
"dnf_equipments",
"dnf_equipment_entries",
"dnf_pets",
"dnf_pet_entries",
"dnf_quests",
"equipped_key VARCHAR(128) NOT NULL DEFAULT ''",
"town_display TINYINT UNSIGNED NOT NULL DEFAULT 0",
"dnf_quest_states",
"dnf_skill_states",
"dnf_skill_tree_points",
"dnf_skill_tree_states",
"dnf_skill_commands",
"dnf_skill_layouts",
"dnf_mailboxes",
"dnf_mails",
"dnf_epic_piece_balances",
"dnf_dungeon_permissions",
"dnf_hell_party_permissions",
"honor_exp BIGINT UNSIGNED NOT NULL DEFAULT 0",
"`pc_room_id` BIGINT NOT NULL DEFAULT 65537",
"`fatigue` SMALLINT UNSIGNED NOT NULL DEFAULT 156",
"`fatigue_limit` SMALLINT UNSIGNED NOT NULL DEFAULT 156",
"`fatigue_battery` SMALLINT UNSIGNED NOT NULL DEFAULT 0",
"`tutorial_completed` TINYINT UNSIGNED NOT NULL DEFAULT 0",
"`story_digest_last_level` INT UNSIGNED NOT NULL DEFAULT 0",
"`story_digest_migration_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0",
"`stat_strength` INT NOT NULL DEFAULT 0",
"`stat_independent_attack` INT NOT NULL DEFAULT 0",
"`roster_card_flag` TINYINT UNSIGNED NOT NULL DEFAULT 0",
"`create_option_byte_63` TINYINT UNSIGNED NOT NULL DEFAULT 0",
"UNIQUE KEY uk_dnf_characters_account_slot_active (account_id, slot, delete_flag)",
"dnf_legacy_accounts",
"dnf_legacy_character_items",
"`extra46` BIGINT NOT NULL DEFAULT 0",
"`extra47` BIGINT NOT NULL DEFAULT 0",
"`extra51` BIGINT NOT NULL DEFAULT 0",
"`active_status_resistance_17` BIGINT NOT NULL DEFAULT 0",
"dnf_legacy_character_userinfo80_slots",
"dnf_legacy_character_userinfo90_control",
"dnf_legacy_character_userinfob6_values",
"dnf_legacy_character_userinfo22e_state",
"dnf_legacy_character_userinfo29f_rows",
"dnf_legacy_account_settings",
} {
if !strings.Contains(sql, want) {
t.Fatalf("repository.mysql.sql missing %q", want)
}
}
if strings.Contains(sql, "_json JSON") {
t.Fatal("repository.mysql.sql still contains repository JSON columns")
}
if strings.Contains(sql, "LONGBLOB NOT NULL DEFAULT") {
t.Fatal("repository.mysql.sql contains MySQL-invalid LONGBLOB default")
}
}
func TestLegacyUserInfoQueryAllowsOnlyUserInfoAndSubtypeTables(t *testing.T) {
for _, table := range []string{
"legacy_character_userinfo23_scalar_values",
"legacy_character_userinfob6_values",
"legacy_character_subtype0_fields",
"legacy_character_subtype1_fields",
"legacy_character_achievement_chunks",
"legacy_character_achievement_complete",
} {
if err := validateLegacyUserInfoQuery(table, []string{"character_id"}, nil); err != nil {
t.Fatalf("validateLegacyUserInfoQuery(%q) error = %v", table, err)
}
}
for _, table := range []string{
"legacy_character_items",
"legacy_character_subtype2_fields",
"legacy_character_userinfo23_scalar_values;DROP",
} {
if err := validateLegacyUserInfoQuery(table, []string{"character_id"}, nil); err == nil {
t.Fatalf("validateLegacyUserInfoQuery(%q) succeeded, want rejected", table)
}
}
}
func TestEnsureMySQLSchemaRequiresExplicitAutoCreate(t *testing.T) {
exec := &fakeSchemaExec{}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if err != nil {
t.Fatalf("EnsureMySQLSchema() error = %v", err)
}
if len(exec.statements) != 0 {
t.Fatalf("schema should not run when AutoCreate=false: %v", exec.statements)
}
}
func TestEnsureMySQLSchemaExecutesWhenEnabled(t *testing.T) {
exec := &fakeSchemaExec{}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if err != nil {
t.Fatalf("EnsureMySQLSchema() error = %v", err)
}
if len(exec.statements) != 63 {
t.Fatalf("executed statements = %d, want 63", len(exec.statements))
}
wantMigrations := []string{
"ALTER TABLE `dnf_s1_w1`.`dnf_accounts` ADD COLUMN `honor_exp` BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER `state`",
"ALTER TABLE `dnf_s1_w1`.`dnf_accounts` ADD COLUMN `represent_account_name` VARCHAR(64) NULL AFTER `honor_exp`",
"ALTER TABLE `dnf_s1_w1`.`dnf_accounts` ADD UNIQUE KEY `uk_dnf_accounts_represent_account_name` (`represent_account_name`)",
"ALTER TABLE `dnf_s1_w1`.`dnf_characters` ADD COLUMN `tutorial_completed` TINYINT UNSIGNED NOT NULL DEFAULT 0 AFTER `user_state`",
"ALTER TABLE `dnf_s1_w1`.`dnf_characters` ADD COLUMN `tutorial_reward_progress_38` TINYINT UNSIGNED NOT NULL DEFAULT 0 AFTER `tutorial_completed`",
"ALTER TABLE `dnf_s1_w1`.`dnf_characters` ADD COLUMN `story_digest_last_level` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `tutorial_reward_progress_38`",
"ALTER TABLE `dnf_s1_w1`.`dnf_characters` ADD COLUMN `story_digest_migration_version` SMALLINT UNSIGNED NOT NULL DEFAULT 0 AFTER `story_digest_last_level`",
"ALTER TABLE `dnf_s1_w1`.`dnf_characters` ADD COLUMN `cera` BIGINT NOT NULL DEFAULT 0 AFTER `gold`",
"ALTER TABLE `dnf_s1_w1`.`dnf_characters` ADD COLUMN `fatigue_battery` SMALLINT UNSIGNED NOT NULL DEFAULT 0 AFTER `fatigue_limit`",
"ALTER TABLE `dnf_s1_w1`.`dnf_characters` ADD COLUMN `premium_crystal_selection` TINYINT NOT NULL DEFAULT -1 AFTER `aura_flag`",
}
// 迁移块固定收尾;块内偏移随迁移数量增长,这里跟随最新的迁移条数。
migrationBlock := 16
for index, want := range wantMigrations {
if got := exec.statements[len(exec.statements)-migrationBlock+index]; got != want {
t.Fatalf("migration %d = %q, want %q", index, got, want)
}
}
wantLast := "ALTER TABLE `dnf_s1_w1`.`dnf_united_friend_relations` ADD COLUMN `friend_since` DATETIME(6) NULL AFTER `updated_at`"
if got := exec.statements[len(exec.statements)-1]; got != wantLast {
t.Fatalf("last migration = %q, want %q", got, wantLast)
}
if migrationBlock != len(wantMigrations)+6 {
t.Fatalf("migrationBlock = %d, want %d (10 条已断言 + 5 条技能列 + 1 条好友时间)", migrationBlock, len(wantMigrations)+6)
}
}
func TestEnsureMySQLSchemaIgnoresExistingHonorExpColumn(t *testing.T) {
exec := &fakeSchemaExec{
failAt: 49,
failErr: &mysql.MySQLError{Number: 1060, Message: "Duplicate column name 'honor_exp'"},
}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if err != nil {
t.Fatalf("EnsureMySQLSchema() duplicate honor migration error = %v", err)
}
if len(exec.statements) != 63 {
t.Fatalf("executed statements = %d, want 63", len(exec.statements))
}
}
func TestEnsureMySQLSchemaIgnoresExistingRepresentNameIndex(t *testing.T) {
exec := &fakeSchemaExec{
failAt: 51,
failErr: &mysql.MySQLError{Number: 1061, Message: "Duplicate key name 'uk_dnf_accounts_represent_account_name'"},
}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if err != nil {
t.Fatalf("EnsureMySQLSchema() duplicate represent-name index error = %v", err)
}
if len(exec.statements) != 63 {
t.Fatalf("executed statements = %d, want 63", len(exec.statements))
}
}
func TestEnsureMySQLSchemaIgnoresExistingTutorialFlagColumn(t *testing.T) {
exec := &fakeSchemaExec{
failAt: 52,
failErr: &mysql.MySQLError{Number: 1060, Message: "Duplicate column name 'tutorial_completed'"},
}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if err != nil {
t.Fatalf("EnsureMySQLSchema() duplicate migration error = %v", err)
}
if len(exec.statements) != 63 {
t.Fatalf("executed statements = %d, want 63", len(exec.statements))
}
}
func TestEnsureMySQLSchemaIgnoresExistingTutorialRewardFlagColumn(t *testing.T) {
exec := &fakeSchemaExec{
failAt: 53,
failErr: &mysql.MySQLError{Number: 1060, Message: "Duplicate column name 'tutorial_reward_progress_38'"},
}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if err != nil {
t.Fatalf("EnsureMySQLSchema() duplicate reward migration error = %v", err)
}
if len(exec.statements) != 63 {
t.Fatalf("executed statements = %d, want 63", len(exec.statements))
}
}
func TestEnsureMySQLSchemaIgnoresExistingStoryDigestColumns(t *testing.T) {
for _, test := range []struct {
name string
failAt int
column string
}{
{name: "last level", failAt: 54, column: "story_digest_last_level"},
{name: "migration version", failAt: 55, column: "story_digest_migration_version"},
} {
t.Run(test.name, func(t *testing.T) {
exec := &fakeSchemaExec{
failAt: test.failAt,
failErr: &mysql.MySQLError{Number: 1060, Message: "Duplicate column name '" + test.column + "'"},
}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if err != nil {
t.Fatalf("EnsureMySQLSchema() duplicate %s error = %v", test.column, err)
}
if len(exec.statements) != 63 {
t.Fatalf("executed statements = %d, want 63", len(exec.statements))
}
})
}
}
func TestEnsureMySQLSchemaIgnoresExistingCeraColumn(t *testing.T) {
exec := &fakeSchemaExec{
failAt: 56,
failErr: &mysql.MySQLError{Number: 1060, Message: "Duplicate column name 'cera'"},
}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if err != nil {
t.Fatalf("EnsureMySQLSchema() duplicate cera migration error = %v", err)
}
if len(exec.statements) != 63 {
t.Fatalf("executed statements = %d, want 63", len(exec.statements))
}
}
func TestEnsureMySQLSchemaStopsOnTutorialFlagMigrationError(t *testing.T) {
exec := &fakeSchemaExec{failAt: 50}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if !errors.Is(err, errFakeSchemaExec) {
t.Fatalf("EnsureMySQLSchema() migration error = %v, want fake error", err)
}
if len(exec.statements) != 50 {
t.Fatalf("executed statements = %d, want 50", len(exec.statements))
}
}
func TestEnsureMySQLSchemaStopsOnExecError(t *testing.T) {
exec := &fakeSchemaExec{failAt: 2}
err := EnsureMySQLSchema(context.Background(), exec, SchemaOptions{
AutoCreate: true,
CreateDatabases: true,
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if !errors.Is(err, errFakeSchemaExec) {
t.Fatalf("EnsureMySQLSchema() error = %v, want fake error", err)
}
if len(exec.statements) != 2 {
t.Fatalf("executed statements = %d, want 2", len(exec.statements))
}
}
func TestMySQLSchemaRejectsBadTablePrefix(t *testing.T) {
_, err := MySQLSchema(SchemaOptions{
TablePrefix: "dnf;drop",
DatabasePlan: repository.DatabasePlan{WriteDatabases: []string{"dnf_s1_w1"}},
})
if !errors.Is(err, ErrSchemaConfigInvalid) {
t.Fatalf("MySQLSchema() error = %v, want ErrSchemaConfigInvalid", err)
}
}
func embeddedCSharpTableCount(t *testing.T) int {
t.Helper()
raw, err := csharpSchemaFS.ReadFile(csharpSchemaFile)
if err != nil {
t.Fatalf("read embedded C# schema: %v", err)
}
count := 0
for _, statement := range splitSQL(stripSQLComments(string(raw))) {
if strings.HasPrefix(strings.ToUpper(strings.TrimSpace(statement)), "CREATE TABLE IF NOT EXISTS ") {
count++
}
}
if count == 0 {
t.Fatal("embedded C# schema has no CREATE TABLE statements")
}
return count
}
func countSQLPrefix(statements []string, prefix string) int {
count := 0
for _, statement := range statements {
if strings.HasPrefix(statement, prefix) {
count++
}
}
return count
}