2026-08-11 19:07:05 +08:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func TestStoreProjectAndSnapshot(t *testing.T) {
|
|
|
|
|
|
s, e := OpenStore(filepath.Join(t.TempDir(), "test.db"))
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
p, e := s.SaveProject(0, ProjectInput{Name: "demo", Path: dir})
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, e = s.SaveProject(0, ProjectInput{Name: "duplicate", Path: dir}); e == nil {
|
|
|
|
|
|
t.Fatal("duplicate path accepted")
|
|
|
|
|
|
}
|
|
|
|
|
|
e = s.ReplaceScan(p.ID, []LanguageStat{{Name: "Go", Files: 1, Code: 2, Comments: 1, Blanks: 1}}, []FileEntry{{Path: "main.go", Name: "main.go", Extension: ".go", Size: 20}})
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
got, e := s.GetProject(p.ID)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got.Stats.TotalLines != 4 || got.Stats.FileCount != 1 {
|
|
|
|
|
|
t.Fatalf("unexpected stats: %#v", got.Stats)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-14 07:51:46 +08:00
|
|
|
|
func TestAISummaryRoundTrip(t *testing.T) {
|
|
|
|
|
|
s, e := OpenStore(filepath.Join(t.TempDir(), "test.db"))
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
p, e := s.SaveProject(0, ProjectInput{Name: "demo", Path: t.TempDir()})
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e = s.SaveAISummary(p.ID, "project", "spark", "第一版介绍"); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 覆盖更新(同项目同模块只保留最新一份)
|
|
|
|
|
|
if e = s.SaveAISummary(p.ID, "project", "deepseek", "第二版介绍"); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e = s.SaveAISummary(p.ID, "git", "spark", "Git 介绍"); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
got, e := s.GetAISummaries(p.ID)
|
|
|
|
|
|
if e != nil || len(got) != 2 {
|
|
|
|
|
|
t.Fatalf("expected 2 summaries, got %d (%v)", len(got), e)
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, x := range got {
|
|
|
|
|
|
switch x.Kind {
|
|
|
|
|
|
case "project":
|
|
|
|
|
|
if x.Content != "第二版介绍" || x.Provider != "deepseek" {
|
|
|
|
|
|
t.Fatalf("project summary mismatch: %#v", x)
|
|
|
|
|
|
}
|
|
|
|
|
|
case "git":
|
|
|
|
|
|
if x.Content != "Git 介绍" || x.Provider != "spark" {
|
|
|
|
|
|
t.Fatalf("git summary mismatch: %#v", x)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestAISummaryCleanup(t *testing.T) {
|
|
|
|
|
|
if got := stripFence("```markdown\n介绍正文\n- 列表\n```"); got != "介绍正文\n- 列表" {
|
|
|
|
|
|
t.Fatalf("stripFence: %q", got)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := stripFence("普通文本"); got != "普通文本" {
|
|
|
|
|
|
t.Fatalf("stripFence should keep plain text: %q", got)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := stripFence("```\n# 标题\n正文没闭合"); got != "# 标题\n正文没闭合" {
|
|
|
|
|
|
t.Fatalf("stripFence should drop unclosed opening fence: %q", got)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := dedentCommon(" 第一行\n 第二行"); got != "第一行\n第二行" {
|
|
|
|
|
|
t.Fatalf("dedentCommon: %q", got)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := dedentCommon("第一行\n 嵌套保留"); got != "第一行\n 嵌套保留" {
|
|
|
|
|
|
t.Fatalf("dedentCommon should keep relative indent: %q", got)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 19:07:05 +08:00
|
|
|
|
func TestNormalizePath(t *testing.T) {
|
|
|
|
|
|
if _, e := normalizePath(filepath.Join(t.TempDir(), "missing")); e == nil {
|
|
|
|
|
|
t.Fatal("missing directory accepted")
|
|
|
|
|
|
}
|
|
|
|
|
|
d := t.TempDir()
|
|
|
|
|
|
p, e := normalizePath(d)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, e = os.Stat(p); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestDuplicateProjectReturnsStableCode(t *testing.T) {
|
|
|
|
|
|
s, e := OpenStore(filepath.Join(t.TempDir(), "test.db"))
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
if _, e = s.SaveProject(0, ProjectInput{Name: "one", Path: dir}); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, e = s.SaveProject(0, ProjectInput{Name: "two", Path: dir}); e == nil || !strings.HasPrefix(e.Error(), "PROJECT_PATH_DUPLICATE") {
|
|
|
|
|
|
t.Fatalf("unexpected error: %v", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestProjectGroupsFilterProjectsAndDashboard(t *testing.T) {
|
|
|
|
|
|
s, e := OpenStore(filepath.Join(t.TempDir(), "groups.db"))
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
g, e := s.SaveProjectGroup(0, "Backend")
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
dir1 := t.TempDir()
|
|
|
|
|
|
dir2 := t.TempDir()
|
|
|
|
|
|
p1, e := s.SaveProject(0, ProjectInput{Name: "api", Path: dir1, GroupID: g.ID})
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, e = s.SaveProject(0, ProjectInput{Name: "web", Path: dir2}); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e = s.ReplaceScan(p1.ID, []LanguageStat{{Name: "Go", Files: 2, Code: 10, Comments: 1, Blanks: 1}}, nil); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
projects, e := s.ListProjects(g.ID)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(projects) != 1 || projects[0].GroupID != g.ID || projects[0].GroupName != "Backend" {
|
|
|
|
|
|
t.Fatalf("unexpected grouped projects: %#v", projects)
|
|
|
|
|
|
}
|
|
|
|
|
|
d, e := s.Dashboard(g.ID)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if d.Projects != 1 || d.TotalLines != 12 {
|
|
|
|
|
|
t.Fatalf("unexpected dashboard: %#v", d)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestDeleteProjectGroupMovesProjectsToDefault(t *testing.T) {
|
|
|
|
|
|
s, e := OpenStore(filepath.Join(t.TempDir(), "groups.db"))
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
g, e := s.SaveProjectGroup(0, "Temp")
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
p, e := s.SaveProject(0, ProjectInput{Name: "demo", Path: t.TempDir(), GroupID: g.ID})
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e = s.DeleteProjectGroup(g.ID); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
got, e := s.GetProject(p.ID)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got.GroupID != 1 {
|
|
|
|
|
|
t.Fatalf("groupId=%d want 1", got.GroupID)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestSettingsReturnsActiveDatabasePath(t *testing.T) {
|
|
|
|
|
|
dbPath := filepath.Join(t.TempDir(), "active.db")
|
|
|
|
|
|
s, e := OpenStore(dbPath)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
settings, e := s.Settings()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if settings.DatabasePath != dbPath {
|
|
|
|
|
|
t.Fatalf("databasePath=%q want %q", settings.DatabasePath, dbPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestGlassOpacityPersistsAndValidates(t *testing.T) {
|
|
|
|
|
|
s, e := OpenStore(filepath.Join(t.TempDir(), "settings.db"))
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
if e = s.SaveSettings(AppSettings{Theme: "dark", Locale: "zh-CN", GitScope: "current", AutoRefresh: true, GlassOpacity: 30}); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
got, e := s.Settings()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got.GlassOpacity != 30 {
|
|
|
|
|
|
t.Fatalf("glassOpacity=%d", got.GlassOpacity)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e = s.SaveSettings(AppSettings{GlassOpacity: 99}); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
got, e = s.Settings()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got.GlassOpacity != 55 {
|
|
|
|
|
|
t.Fatalf("invalid opacity did not reset: %d", got.GlassOpacity)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func TestLoadingStylePersistsAndValidates(t *testing.T) {
|
|
|
|
|
|
s, e := OpenStore(filepath.Join(t.TempDir(), "settings.db"))
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
if e = s.SaveSettings(AppSettings{Theme: "dark", Locale: "zh-CN", GitScope: "current", AutoRefresh: true, GlassOpacity: 55, LoadingStyle: "fullscreen-grid"}); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
got, e := s.Settings()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got.LoadingStyle != "fullscreen-grid" {
|
|
|
|
|
|
t.Fatalf("loadingStyle=%q", got.LoadingStyle)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e = s.SaveSettings(AppSettings{GlassOpacity: 55, LoadingStyle: "floating"}); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
got, e = s.Settings()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got.LoadingStyle != "fullscreen-orbit" {
|
|
|
|
|
|
t.Fatalf("invalid loadingStyle did not reset: %q", got.LoadingStyle)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-14 07:51:46 +08:00
|
|
|
|
func TestMigrateSkippedWhenVersionCurrent(t *testing.T) {
|
|
|
|
|
|
db := filepath.Join(t.TempDir(), "versioned.db")
|
|
|
|
|
|
s, e := OpenStore(db)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 删除一张迁移会创建的表:若重开时迁移被跳过,该表应保持缺失。
|
|
|
|
|
|
if _, e = s.db.Exec(`DROP TABLE git_hotspots`); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
s.db.Close()
|
|
|
|
|
|
s, e = OpenStore(db)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
var n int
|
|
|
|
|
|
_ = s.db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='git_hotspots'`).Scan(&n)
|
|
|
|
|
|
if n != 0 {
|
|
|
|
|
|
t.Fatal("migration ran again despite current schema version")
|
|
|
|
|
|
}
|
|
|
|
|
|
// 回退版本号后重开,迁移应重新执行并补回该表。
|
|
|
|
|
|
if _, e = s.db.Exec(`DELETE FROM schema_migrations`); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
s.db.Close()
|
|
|
|
|
|
s, e = OpenStore(db)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
_ = s.db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='git_hotspots'`).Scan(&n)
|
|
|
|
|
|
if n != 1 {
|
|
|
|
|
|
t.Fatal("migration did not run after version reset")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-11 19:07:05 +08:00
|
|
|
|
func TestLegacyLoadingStyleDefaultMigratesOnce(t *testing.T) {
|
|
|
|
|
|
db := filepath.Join(t.TempDir(), "settings.db")
|
|
|
|
|
|
s, e := OpenStore(db)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, e = s.db.Exec(`UPDATE settings SET value='bar' WHERE key='loadingStyle'`); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
2026-08-14 07:51:46 +08:00
|
|
|
|
// 模拟旧版本数据库:回退已记录的结构版本,重开时才会执行一次迁移转换。
|
|
|
|
|
|
if _, e = s.db.Exec(`DELETE FROM schema_migrations`); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
2026-08-11 19:07:05 +08:00
|
|
|
|
s.db.Close()
|
|
|
|
|
|
s, e = OpenStore(db)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
got, e := s.Settings()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got.LoadingStyle != "fullscreen-orbit" {
|
|
|
|
|
|
t.Fatalf("legacy loadingStyle=%q", got.LoadingStyle)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e = s.SaveSettings(AppSettings{GlassOpacity: 55, LoadingStyle: "bar"}); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
s.db.Close()
|
|
|
|
|
|
s, e = OpenStore(db)
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
got, e = s.Settings()
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if got.LoadingStyle != "bar" {
|
|
|
|
|
|
t.Fatalf("explicit loadingStyle=%q", got.LoadingStyle)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-14 07:51:46 +08:00
|
|
|
|
|
|
|
|
|
|
func TestMultiNoteCRUD(t *testing.T) {
|
|
|
|
|
|
s, e := OpenStore(filepath.Join(t.TempDir(), "notes.db"))
|
|
|
|
|
|
if e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
defer s.db.Close()
|
|
|
|
|
|
a, e := s.SaveNoteByID(0, "第一条")
|
|
|
|
|
|
if e != nil || a.ID == 0 || a.UUID == "" {
|
|
|
|
|
|
t.Fatalf("create note: %#v %v", a, e)
|
|
|
|
|
|
}
|
|
|
|
|
|
b, e := s.SaveNoteByID(0, "第二条")
|
|
|
|
|
|
if e != nil || b.ID == a.ID {
|
|
|
|
|
|
t.Fatalf("second note: %#v %v", b, e)
|
|
|
|
|
|
}
|
|
|
|
|
|
notes, e := s.ListNotes(0)
|
|
|
|
|
|
if e != nil || len(notes) != 2 {
|
|
|
|
|
|
t.Fatalf("list notes: %d %v", len(notes), e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if notes[0].ID != b.ID {
|
|
|
|
|
|
t.Fatalf("latest first, got %#v", notes[0])
|
|
|
|
|
|
}
|
|
|
|
|
|
// GetNote 返回最近更新的一条;SaveNote 兼容入口更新它
|
|
|
|
|
|
if n, _ := s.GetNote(); n.ID != b.ID {
|
|
|
|
|
|
t.Fatalf("GetNote should return latest, got %d", n.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
if a2, e := s.SaveNoteByID(a.ID, "第一条改"); e != nil || a2.Content != "第一条改" {
|
|
|
|
|
|
t.Fatalf("update note: %#v %v", a2, e)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 更新过的 a 变为最近一条
|
|
|
|
|
|
if n, _ := s.GetNote(); n.ID != a.ID {
|
|
|
|
|
|
t.Fatalf("GetNote should follow update, got %d", n.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, e := s.SaveNoteByID(99999, "missing"); e == nil || e.Error() != "NOTE_NOT_FOUND" {
|
|
|
|
|
|
t.Fatalf("expect NOTE_NOT_FOUND, got %v", e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if e := s.DeleteNote(b.ID); e != nil {
|
|
|
|
|
|
t.Fatal(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
if notes, _ = s.ListNotes(0); len(notes) != 1 || notes[0].ID != a.ID {
|
|
|
|
|
|
t.Fatalf("after delete: %#v", notes)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 软删行仍在表中且 dirty(供同步推送)
|
|
|
|
|
|
var deleted, dirty int
|
|
|
|
|
|
_ = s.db.QueryRow(`SELECT deleted,dirty FROM notes WHERE id=?`, b.ID).Scan(&deleted, &dirty)
|
|
|
|
|
|
if deleted != 1 || dirty != 1 {
|
|
|
|
|
|
t.Fatalf("soft delete flags: deleted=%d dirty=%d", deleted, dirty)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|