feat: harden loose-offline sync for user JWT, schema, and console ops
Enable Binding-scoped agent push/pull, empty-table schema ensure, SyncPage inspect/drop-table, default module import, and agent-bound publish docs from the 宇恒联调意见. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
94
platform/internal/dbsync/schema_test.go
Normal file
94
platform/internal/dbsync/schema_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package dbsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEnsureTableFromColumnsEmpty(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dsn := "file:" + filepath.ToSlash(filepath.Join(dir, "empty.db")) + "?_pragma=busy_timeout(5000)"
|
||||
db, err := Open(DriverSQLite, dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
ctx := context.Background()
|
||||
cols := []string{"id", "username", "password", "email"}
|
||||
if err := EnsureTableFromColumns(ctx, db, DriverSQLite, "accounts", "id", cols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var n int64
|
||||
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM accounts`).Scan(&n); err != nil || n != 0 {
|
||||
t.Fatalf("want empty table n=%d err=%v", n, err)
|
||||
}
|
||||
// idempotent
|
||||
if err := EnsureTableFromColumns(ctx, db, DriverSQLite, "accounts", "id", cols); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureSchemasOnRemoteCreatesEmpty(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dsn := "file:" + filepath.ToSlash(filepath.Join(dir, "remote.db")) + "?_pragma=busy_timeout(5000)"
|
||||
ch := &Channel{
|
||||
ID: "ch1",
|
||||
Remote: Endpoint{
|
||||
Driver: DriverSQLite,
|
||||
DSN: dsn,
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
defer InvalidateRemote(DriverSQLite, dsn)
|
||||
res, err := EnsureSchemasOnRemote(ctx, ch, SchemaEnsureRequest{
|
||||
Tables: []SchemaTableSpec{{
|
||||
Table: "accounts",
|
||||
PKColumn: "id",
|
||||
Columns: []string{"id", "username", "email"},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !res.OK || len(res.Results) != 1 || !res.Results[0].Created {
|
||||
t.Fatalf("unexpected: %+v", res)
|
||||
}
|
||||
desc, err := DescribeSchemasFromRemote(ctx, ch, SchemaDescribeRequest{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(desc.Tables) != 1 || desc.Tables[0].Name != "accounts" || desc.Tables[0].RowCount != 0 {
|
||||
t.Fatalf("describe: %+v", desc)
|
||||
}
|
||||
if len(desc.Tables[0].Columns) < 2 {
|
||||
t.Fatalf("columns: %v", desc.Tables[0].Columns)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullEmptyTableReturnsColumns(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dsn := "file:" + filepath.ToSlash(filepath.Join(dir, "pull.db")) + "?_pragma=busy_timeout(5000)"
|
||||
db, err := Open(DriverSQLite, dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := EnsureTableFromColumns(ctx, db, DriverSQLite, "accounts", "id", []string{"id", "username"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = db.Close()
|
||||
|
||||
ch := &Channel{ID: "ch1", Remote: Endpoint{Driver: DriverSQLite, DSN: dsn}}
|
||||
defer InvalidateRemote(DriverSQLite, dsn)
|
||||
res, err := PullFromRemote(ctx, ch, nil, PullRequest{Mode: PullModeBootstrap, Table: "accounts"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res.Items) != 0 {
|
||||
t.Fatalf("want empty items: %+v", res.Items)
|
||||
}
|
||||
if len(res.Columns) < 2 {
|
||||
t.Fatalf("want columns for empty table: %+v", res)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user