package dbsync import ( "context" "path/filepath" "testing" "time" ) func TestCheckpointSaveRestoreRoundTrip(t *testing.T) { dir := t.TempDir() store, err := NewFileStore(dir) if err != nil { t.Fatal(err) } remoteDSN := "file:" + filepath.ToSlash(filepath.Join(dir, "online.db")) + "?_pragma=busy_timeout(5000)" ch := &Channel{ ID: "ch-cp-1", TenantID: 1, Name: "test", Remote: Endpoint{ Driver: DriverSQLite, DSN: remoteDSN, }, PKColumns: map[string]string{"demo": "id"}, } ctx := context.Background() db, err := Open(DriverSQLite, remoteDSN) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = db.Close() InvalidateRemote(DriverSQLite, remoteDSN) }) if _, err := db.ExecContext(ctx, `CREATE TABLE demo (id TEXT PRIMARY KEY, name TEXT)`); err != nil { t.Fatal(err) } if _, err := db.ExecContext(ctx, `INSERT INTO demo(id,name) VALUES('a','one'),('b','two')`); err != nil { t.Fatal(err) } if err := EnsureMeta(ctx, db, DriverSQLite); err != nil { t.Fatal(err) } if err := SaveCheckpoint(ctx, store, ch, "test"); err != nil { t.Fatal(err) } meta, err := LoadCheckpointMeta(store, ch.ID) if err != nil || meta.Latest == nil || meta.Latest.RowCount != 2 { t.Fatalf("meta=%+v err=%v", meta, err) } // mutate then restore if _, err := db.ExecContext(ctx, `DELETE FROM demo WHERE id='a'`); err != nil { t.Fatal(err) } if _, err := db.ExecContext(ctx, `INSERT INTO demo(id,name) VALUES('c','three')`); err != nil { t.Fatal(err) } res, err := RestoreCheckpoint(ctx, store, ch, "latest") if err != nil { t.Fatal(err) } if !res.OK || res.Upserted < 2 { t.Fatalf("restore=%+v", res) } var n int if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM demo`).Scan(&n); err != nil || n != 2 { t.Fatalf("count=%d err=%v", n, err) } var name string if err := db.QueryRowContext(ctx, `SELECT name FROM demo WHERE id='a'`).Scan(&name); err != nil || name != "one" { t.Fatalf("row a name=%q err=%v", name, err) } // second save rotates time.Sleep(10 * time.Millisecond) if _, err := db.ExecContext(ctx, `UPDATE demo SET name='one2' WHERE id='a'`); err != nil { t.Fatal(err) } if err := SaveCheckpoint(ctx, store, ch, "test2"); err != nil { t.Fatal(err) } meta2, _ := LoadCheckpointMeta(store, ch.ID) if meta2.Previous == nil || meta2.Previous.RowCount != 2 { t.Fatalf("expected previous after rotate: %+v", meta2) } }