Merge remote-tracking branch 'upstream/main' into main (保留常驻通知与顺延迁移版本)
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const CurrentSchemaVersion int64 = 18
|
||||
const CurrentSchemaVersion int64 = 21
|
||||
|
||||
const baselineSchemaChecksum = "sha256:open-ai-canvas-schema-v1-20260830"
|
||||
const schemaMigrationAppliedAtIndexChecksum = "sha256:schema-migrations-applied-at-index-v2-20260830"
|
||||
@@ -67,13 +67,22 @@ var schemaMigrations = []migration{
|
||||
{version: 15, name: "agent_profiles", checksum: "sha256:agent-profiles-v15-20260914", apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.AgentProfile{})
|
||||
}},
|
||||
{version: 16, name: "banner_announcements", checksum: "sha256:banner-announcements-v16-20260917", apply: func(tx *gorm.DB) error {
|
||||
{version: 16, name: "agent_lessons", checksum: "sha256:agent-lessons-v16-20260917", apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.AgentLesson{})
|
||||
}},
|
||||
{version: 17, name: "agent_lessons_owner_index", checksum: "sha256:agent-lessons-owner-index-v17-20260917", apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.AgentLesson{})
|
||||
}},
|
||||
{version: 18, name: "agent_memory_settings", checksum: "sha256:agent-memory-settings-v18-20260917", apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.AgentMemorySetting{})
|
||||
}},
|
||||
{version: 19, name: "banner_announcements", checksum: "sha256:banner-announcements-v19-20260917", apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.BannerAnnouncement{})
|
||||
}},
|
||||
{version: 17, name: "banner_announcement_title_runs", checksum: "sha256:banner-announcement-title-runs-v17-20260917", apply: func(tx *gorm.DB) error {
|
||||
{version: 20, name: "banner_announcement_title_runs", checksum: "sha256:banner-announcement-title-runs-v20-20260917", apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.BannerAnnouncement{})
|
||||
}},
|
||||
{version: 18, name: "banner_announcement_notice_type", checksum: "sha256:banner-announcement-notice-type-v18-20260917", apply: func(tx *gorm.DB) error {
|
||||
{version: 21, name: "banner_announcement_notice_type", checksum: "sha256:banner-announcement-notice-type-v21-20260917", apply: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.BannerAnnouncement{})
|
||||
}},
|
||||
}
|
||||
|
||||
@@ -35,6 +35,24 @@ func TestMigrateSchemaRecordsAndValidatesVersion(t *testing.T) {
|
||||
if !db.Migrator().HasTable(&model.AgentProfile{}) || !db.Migrator().HasIndex(&model.AgentProfile{}, "idx_agent_profiles_scope") {
|
||||
t.Fatal("schema migration v15 did not create scoped Agent profiles")
|
||||
}
|
||||
if !db.Migrator().HasTable(&model.AgentLesson{}) || !db.Migrator().HasIndex(&model.AgentLesson{}, "idx_agent_lessons_status") {
|
||||
t.Fatal("schema migration v16 did not create Agent lessons")
|
||||
}
|
||||
if !db.Migrator().HasIndex(&model.AgentLesson{}, "idx_agent_lessons_author_status") {
|
||||
t.Fatal("schema migration v17 did not create owner status index")
|
||||
}
|
||||
if !db.Migrator().HasTable(&model.AgentMemorySetting{}) {
|
||||
t.Fatal("schema migration v18 did not create agent memory settings")
|
||||
}
|
||||
if !db.Migrator().HasTable(&model.BannerAnnouncement{}) {
|
||||
t.Fatal("schema migration v19 did not create banner announcements")
|
||||
}
|
||||
if !db.Migrator().HasColumn(&model.BannerAnnouncement{}, "title_runs") {
|
||||
t.Fatal("schema migration v20 did not create banner announcements title_runs")
|
||||
}
|
||||
if !db.Migrator().HasColumn(&model.BannerAnnouncement{}, "notice_type") {
|
||||
t.Fatal("schema migration v21 did not create banner announcements notice_type")
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatalf("migration should be idempotent: %v", err)
|
||||
}
|
||||
@@ -66,19 +84,15 @@ func TestMigrateSchemaV15UpgradesExistingDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSchemaV16UpgradesExistingDatabaseWithBannerAnnouncements(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-banner-announcements-v16?mode=memory&cache=shared"})
|
||||
func TestMigrateSchemaV16UpgradesExistingDatabase(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-agent-lessons-v16?mode=memory&cache=shared"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !db.Migrator().HasTable(&model.BannerAnnouncement{}) {
|
||||
t.Fatal("v16 migration did not create banner_announcements table")
|
||||
}
|
||||
// 模拟旧库升级:删表 + 删除 v16 记录,重跑迁移应能重建。
|
||||
if err := db.Migrator().DropTable(&model.BannerAnnouncement{}); err != nil {
|
||||
if err := db.Migrator().DropTable(&model.AgentLesson{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("version = ?", 16).Delete(&schemaMigration{}).Error; err != nil {
|
||||
@@ -87,8 +101,8 @@ func TestMigrateSchemaV16UpgradesExistingDatabaseWithBannerAnnouncements(t *test
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatalf("upgrade from v15: %v", err)
|
||||
}
|
||||
if !db.Migrator().HasTable(&model.BannerAnnouncement{}) {
|
||||
t.Fatal("v16 upgrade did not reinstall banner_announcements table")
|
||||
if !db.Migrator().HasTable(&model.AgentLesson{}) || !db.Migrator().HasIndex(&model.AgentLesson{}, "idx_agent_lessons_status") {
|
||||
t.Fatal("v16 upgrade did not install Agent lesson table and status index")
|
||||
}
|
||||
status, err := ReadSchemaStatus(db)
|
||||
if err != nil || !status.Ready || status.Current != CurrentSchemaVersion {
|
||||
@@ -96,23 +110,15 @@ func TestMigrateSchemaV16UpgradesExistingDatabaseWithBannerAnnouncements(t *test
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSchemaV17AddsBannerAnnouncementTitleRuns(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-banner-title-runs-v17?mode=memory&cache=shared"})
|
||||
func TestMigrateSchemaV17UpgradesExistingDatabase(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-agent-lessons-v17?mode=memory&cache=shared"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !db.Migrator().HasColumn(&model.BannerAnnouncement{}, "title_runs") {
|
||||
t.Fatal("v17 migration did not add banner_announcements.title_runs")
|
||||
}
|
||||
// 模拟 v16 旧库升级:先写入一条通知,删掉新列并移除 v17 记录,重跑迁移应补回列且保留原数据。
|
||||
legacy := &model.BannerAnnouncement{ID: "legacy-banner", Title: "旧库通知", Status: "active"}
|
||||
if err := db.Create(legacy).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Migrator().DropColumn(&model.BannerAnnouncement{}, "title_runs"); err != nil {
|
||||
if err := db.Migrator().DropIndex(&model.AgentLesson{}, "idx_agent_lessons_author_status"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("version = ?", 17).Delete(&schemaMigration{}).Error; err != nil {
|
||||
@@ -121,8 +127,97 @@ func TestMigrateSchemaV17AddsBannerAnnouncementTitleRuns(t *testing.T) {
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatalf("upgrade from v16: %v", err)
|
||||
}
|
||||
if !db.Migrator().HasIndex(&model.AgentLesson{}, "idx_agent_lessons_author_status") {
|
||||
t.Fatal("v17 upgrade did not install owner status index")
|
||||
}
|
||||
status, err := ReadSchemaStatus(db)
|
||||
if err != nil || !status.Ready || status.Current != CurrentSchemaVersion {
|
||||
t.Fatalf("unexpected upgraded schema status: %+v, %v", status, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSchemaV18UpgradesExistingDatabase(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-agent-memory-settings-v18?mode=memory&cache=shared"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Migrator().DropTable(&model.AgentMemorySetting{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("version = ?", 18).Delete(&schemaMigration{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatalf("upgrade from v17: %v", err)
|
||||
}
|
||||
if !db.Migrator().HasTable(&model.AgentMemorySetting{}) {
|
||||
t.Fatal("v18 upgrade did not install agent memory settings")
|
||||
}
|
||||
status, err := ReadSchemaStatus(db)
|
||||
if err != nil || !status.Ready || status.Current != CurrentSchemaVersion {
|
||||
t.Fatalf("unexpected upgraded schema status: %+v, %v", status, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSchemaV19UpgradesExistingDatabaseWithBannerAnnouncements(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-banner-announcements-v19?mode=memory&cache=shared"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !db.Migrator().HasTable(&model.BannerAnnouncement{}) {
|
||||
t.Fatal("v19 migration did not create banner_announcements table")
|
||||
}
|
||||
// 模拟旧库升级:删表 + 删除 v19 记录,重跑迁移应能重建。
|
||||
if err := db.Migrator().DropTable(&model.BannerAnnouncement{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("version = ?", 19).Delete(&schemaMigration{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatalf("upgrade from v18: %v", err)
|
||||
}
|
||||
if !db.Migrator().HasTable(&model.BannerAnnouncement{}) {
|
||||
t.Fatal("v19 upgrade did not reinstall banner_announcements table")
|
||||
}
|
||||
status, err := ReadSchemaStatus(db)
|
||||
if err != nil || !status.Ready || status.Current != CurrentSchemaVersion {
|
||||
t.Fatalf("unexpected upgraded schema status: %+v, %v", status, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSchemaV20AddsBannerAnnouncementTitleRuns(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-banner-title-runs-v20?mode=memory&cache=shared"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !db.Migrator().HasColumn(&model.BannerAnnouncement{}, "title_runs") {
|
||||
t.Fatal("v17 upgrade did not restore banner_announcements.title_runs")
|
||||
t.Fatal("v20 migration did not add banner_announcements.title_runs")
|
||||
}
|
||||
legacy := &model.BannerAnnouncement{ID: "legacy-banner", Title: "旧库通知", Status: "active"}
|
||||
if err := db.Create(legacy).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Migrator().DropColumn(&model.BannerAnnouncement{}, "title_runs"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("version = ?", 20).Delete(&schemaMigration{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatalf("upgrade from v19: %v", err)
|
||||
}
|
||||
if !db.Migrator().HasColumn(&model.BannerAnnouncement{}, "title_runs") {
|
||||
t.Fatal("v20 upgrade did not restore banner_announcements.title_runs")
|
||||
}
|
||||
var stored model.BannerAnnouncement
|
||||
if err := db.First(&stored, "id = ?", "legacy-banner").Error; err != nil {
|
||||
@@ -137,8 +232,8 @@ func TestMigrateSchemaV17AddsBannerAnnouncementTitleRuns(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateSchemaV18AddsBannerAnnouncementNoticeType(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-banner-notice-type-v18?mode=memory&cache=shared"})
|
||||
func TestMigrateSchemaV21AddsBannerAnnouncementNoticeType(t *testing.T) {
|
||||
db, err := Open(Config{Driver: "sqlite", DSN: "file:migration-banner-notice-type-v21?mode=memory&cache=shared"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -146,30 +241,28 @@ func TestMigrateSchemaV18AddsBannerAnnouncementNoticeType(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !db.Migrator().HasColumn(&model.BannerAnnouncement{}, "notice_type") {
|
||||
t.Fatal("v18 migration did not add banner_announcements.notice_type")
|
||||
t.Fatal("v21 migration did not add banner_announcements.notice_type")
|
||||
}
|
||||
// 模拟 v17 旧库升级:先写入一条通知,删掉新列并移除 v18 记录,重跑迁移应补回列且保留原数据。
|
||||
legacy := &model.BannerAnnouncement{ID: "legacy-banner-v17", Title: "旧库通知", TitleRunsJSON: `[{"text":"旧库通知"}]`, Status: "active"}
|
||||
legacy := &model.BannerAnnouncement{ID: "legacy-banner-v20", Title: "旧库通知", TitleRunsJSON: `[{"text":"旧库通知"}]`, Status: "active"}
|
||||
if err := db.Create(legacy).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Migrator().DropColumn(&model.BannerAnnouncement{}, "notice_type"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Where("version = ?", 18).Delete(&schemaMigration{}).Error; err != nil {
|
||||
if err := db.Where("version = ?", 21).Delete(&schemaMigration{}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := MigrateSchema(db); err != nil {
|
||||
t.Fatalf("upgrade from v17: %v", err)
|
||||
t.Fatalf("upgrade from v20: %v", err)
|
||||
}
|
||||
if !db.Migrator().HasColumn(&model.BannerAnnouncement{}, "notice_type") {
|
||||
t.Fatal("v18 upgrade did not restore banner_announcements.notice_type")
|
||||
t.Fatal("v21 upgrade did not restore banner_announcements.notice_type")
|
||||
}
|
||||
var stored model.BannerAnnouncement
|
||||
if err := db.First(&stored, "id = ?", "legacy-banner-v17").Error; err != nil {
|
||||
if err := db.First(&stored, "id = ?", "legacy-banner-v20").Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// 新增列在旧行上为空,前端按默认类型与默认图标回落,因此只校验旧数据未丢失。
|
||||
if stored.Title != "旧库通知" || stored.TitleRunsJSON != `[{"text":"旧库通知"}]` {
|
||||
t.Fatalf("legacy banner lost during upgrade: %+v", stored)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ func Models() []any {
|
||||
&model.CloudAgentExecution{},
|
||||
&model.CloudAgentCanvasMutation{},
|
||||
&model.AgentProfile{},
|
||||
&model.AgentLesson{},
|
||||
&model.AgentMemorySetting{},
|
||||
&model.User{},
|
||||
&model.AuthSession{},
|
||||
&model.UserIdentity{},
|
||||
|
||||
Reference in New Issue
Block a user