package memory import ( "context" "os" "testing" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) // TestBaseModelID 验证雪花 ID 规约:NewID 非空且不重复;BeforeCreate 补 ID、已有则保留。 func TestBaseModelID(t *testing.T) { a, b := NewID(), NewID() if a == "" || b == "" { t.Fatal("NewID 不应为空") } if a == b { t.Errorf("两次 NewID 应不同: %s", a) } var m BaseModel if err := m.BeforeCreate(nil); err != nil { t.Fatal(err) } if m.ID == "" { t.Error("BeforeCreate 应补 ID") } m.ID = "fixed" _ = m.BeforeCreate(nil) if m.ID != "fixed" { t.Error("BeforeCreate 不应覆盖已有 ID") } } // TestProfileStore_Integration 跑真实 Postgres(设 MEMORY_TEST_DSN 才执行): // 验证迁移后表含雪花规约字段、Upsert (user_id,key) 冲突覆盖而非新增、Get 渲染。 func TestProfileStore_Integration(t *testing.T) { dsn := os.Getenv("MEMORY_TEST_DSN") if dsn == "" { t.Skip("设 MEMORY_TEST_DSN 启用 Postgres 集成测试") } raw, err := gorm.Open(postgres.New(postgres.Config{DSN: dsn}), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) if err != nil { t.Fatalf("连接 Postgres: %v", err) } _ = raw.Migrator().DropTable("sundynix_user_profile") // 干净起点 s := Open(dsn) // 触发迁移 + AutoMigrate if s.db == nil { t.Fatal("Store 不应降级") } ctx := context.Background() // 新规约字段齐全。 for _, col := range []string{"id", "created_at", "updated_at", "deleted_at"} { if !raw.Migrator().HasColumn(&Profile{}, col) { t.Errorf("表应含规约字段 %s", col) } } // Upsert 两次同 (user_id,key) → 覆盖,不新增;id 稳定。 if err := s.Upsert(ctx, "u1", "城市", "北京"); err != nil { t.Fatal(err) } var first Profile raw.Where("user_id = ? AND key = ?", "u1", "城市").First(&first) if first.ID == "" || first.CreatedAt.IsZero() { t.Error("行应有雪花 id 与创建时间") } if err := s.Upsert(ctx, "u1", "城市", "上海"); err != nil { t.Fatal(err) } var cnt int64 raw.Model(&Profile{}).Where("user_id = ?", "u1").Count(&cnt) if cnt != 1 { t.Errorf("同键 upsert 应覆盖,期望 1 行,得 %d", cnt) } var after Profile raw.Where("user_id = ? AND key = ?", "u1", "城市").First(&after) if after.Value != "上海" || after.ID != first.ID { t.Errorf("应覆盖 value 且保留 id: value=%s id=%s/%s", after.Value, after.ID, first.ID) } // Get 渲染多行(按 key 排序)。 _ = s.Upsert(ctx, "u1", "爱好", "围棋") got, _ := s.Get(ctx, "u1") if got == "" || got != "- 城市:上海\n- 爱好:围棋" { t.Errorf("Get 渲染不符: %q", got) } } // TestProfileStore_LegacyMigration 验证旧复合主键表(无 id)→ 雪花规约表的迁移保留数据。 func TestProfileStore_LegacyMigration(t *testing.T) { dsn := os.Getenv("MEMORY_TEST_DSN") if dsn == "" { t.Skip("设 MEMORY_TEST_DSN 启用 Postgres 集成测试") } raw, err := gorm.Open(postgres.New(postgres.Config{DSN: dsn}), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) if err != nil { t.Fatalf("连接 Postgres: %v", err) } // 造一个旧 schema 表(复合主键、无 id/时间戳)并塞一条偏好。 raw.Migrator().DropTable("sundynix_user_profile") if err := raw.Exec(`CREATE TABLE sundynix_user_profile (user_id varchar(64), "key" varchar(64), value text, PRIMARY KEY(user_id, "key"))`).Error; err != nil { t.Fatalf("建旧表: %v", err) } raw.Exec(`INSERT INTO sundynix_user_profile (user_id, "key", value) VALUES ('legacy', '语言', 'Go')`) s := Open(dsn) // 应触发 migrateLegacyProfile:备份→重建→回灌 if s.db == nil { t.Fatal("Store 不应降级") } if !raw.Migrator().HasColumn(&Profile{}, "id") { t.Error("迁移后应有 id 列") } var p Profile if err := raw.Where("user_id = ? AND key = ?", "legacy", "语言").First(&p).Error; err != nil { t.Fatalf("迁移后旧数据应保留: %v", err) } if p.Value != "Go" || p.ID == "" { t.Errorf("旧偏好应保留且补新 id: value=%s id=%s", p.Value, p.ID) } }