package store import ( "errors" "path/filepath" "testing" ) // TestMigrationsFromEmpty 空库 → 最新版本;重复打开幂等(§13 迁移组)。 func TestMigrationsFromEmpty(t *testing.T) { dir := t.TempDir() dbPath := filepath.Join(dir, "pure-note.db") s1, err := Open(dbPath, false) if err != nil { t.Fatalf("首次打开失败: %v", err) } v1, err := s1.SchemaVersion() if err != nil { t.Fatal(err) } if v1 != MaxSchemaVersion { t.Fatalf("期望版本 %d 实际 %d", MaxSchemaVersion, v1) } s1.Close() // 重开:幂等,不再执行迁移 s2, err := Open(dbPath, false) if err != nil { t.Fatalf("二次打开失败: %v", err) } defer s2.Close() v2, err := s2.SchemaVersion() if err != nil { t.Fatal(err) } if v2 != v1 { t.Fatalf("迁移不幂等: %d → %d", v1, v2) } } // TestMigrationNewerRejected user_version 越界拒绝启动;--allow-newer 放行(§10.4)。 func TestMigrationNewerRejected(t *testing.T) { dir := t.TempDir() dbPath := filepath.Join(dir, "pure-note.db") s1, err := Open(dbPath, false) if err != nil { t.Fatal(err) } if _, err := s1.DB().Exec("PRAGMA user_version = 99"); err != nil { t.Fatal(err) } s1.Close() _, err = Open(dbPath, false) if !errors.Is(err, ErrSchemaNewer) { t.Fatalf("期望 ErrSchemaNewer,实际 %v", err) } s2, err := Open(dbPath, true) // --allow-newer if err != nil { t.Fatalf("allow-newer 应放行: %v", err) } defer s2.Close() } // TestForeignKeysCascade 外键级联生效(image_refs 依赖 foreign_keys=1)。 func TestForeignKeysCascade(t *testing.T) { s := openTestStore(t) nid, err := s.CreateNote(&Note{Slug: "casc", Title: "级联", Status: "public", Tags: []string{}, CreatedAt: 1, UpdatedAt: 1}) if err != nil { t.Fatal(err) } imgID, err := s.UpsertImage("aa", "image/png", 1, []byte{1}, 1) if err != nil { t.Fatal(err) } if err := s.RebuildImageRefs(nid, "![x](/api/images/"+itoa64(imgID)+")"); err != nil { t.Fatal(err) } if err := s.DeleteNoteForever(nid); err != nil { t.Fatal(err) } // 级联后图片变孤儿 orphans, err := s.ListOrphanImages() if err != nil { t.Fatal(err) } found := false for _, o := range orphans { if o.ID == imgID { found = true } } if !found { t.Fatal("物理删除笔记后其引用关系应被级联删除(图片转为孤儿)") } } func openTestStore(t *testing.T) *Store { t.Helper() s, err := Open(filepath.Join(t.TempDir(), "test.db"), false) if err != nil { t.Fatal(err) } t.Cleanup(func() { s.Close() }) return s } func itoa64(v int64) string { if v == 0 { return "0" } var b [20]byte i := len(b) for v > 0 { i-- b[i] = byte('0' + v%10) v /= 10 } return string(b[i:]) }