package meta_test import ( "context" "database/sql" "os" "testing" "time" "aijianzhan/platform/internal/blueprint" "aijianzhan/platform/internal/meta" _ "github.com/lib/pq" ) func testDSN(t *testing.T) string { t.Helper() dsn := os.Getenv("PLATFORM_TEST_DSN") if dsn == "" { dsn = "postgres://platform:platform@127.0.0.1:5432/platform?sslmode=disable" } return dsn } func TestPostgresMetaPersist(t *testing.T) { db, err := sql.Open("postgres", testDSN(t)) if err != nil { t.Skip(err) } if err := db.Ping(); err != nil { t.Skip(err) } defer db.Close() ctx := context.Background() if err := meta.EnsureSchema(ctx, db); err != nil { t.Fatal(err) } store := meta.NewPostgresStore(db) slug := "meta_persist_demo" tenantID := int64(99001) _, _ = db.ExecContext(ctx, `DELETE FROM platform_meta.tenant_apps WHERE tenant_id=$1 AND slug=$2`, tenantID, slug) n := false bp := &blueprint.Blueprint{ Version: "1.0", Meta: blueprint.Meta{Name: "持久化测试", Slug: slug, Locale: "zh-CN"}, Storage: blueprint.Storage{Mode: "schema_per_app", Engine: "postgres", SchemaName: "app_t99001_meta_persist_demo"}, Entities: []blueprint.Entity{{ Name: "item", Table: "item", Label: "Item", PrimaryKey: "id", Fields: []blueprint.Field{ {Name: "id", Type: "bigint", Label: "ID", Nullable: &n}, {Name: "title", Type: "string", Label: "标题", Nullable: &n, MaxLength: 64}, }, }}, Apis: blueprint.Apis{ BasePath: "/api/v1/apps/" + slug, Resources: []blueprint.APIResource{{ Entity: "item", Path: "/items", Operations: []string{"list", "get", "create"}, }}, }, Pages: []blueprint.Page{{ ID: "list", Title: "列表", Route: "/items", Type: "list", Entity: "item", }}, Security: blueprint.Security{ Visibility: "private", Roles: []blueprint.Role{{ Name: "管理员", Permissions: []string{"读取模块", "写入模块", "发布模块", "新增数据", "查询数据"}, }}, RowPolicies: []blueprint.RowPolicy{{Entity: "item", Rule: "tenant_isolated"}}, }, } rec := &meta.AppRecord{ AppID: "11111111-1111-1111-1111-111111111111", TenantID: tenantID, Slug: slug, Name: bp.Meta.Name, SchemaName: bp.Storage.SchemaName, Engine: "postgres", Status: meta.StatusPublished, Blueprint: bp, DDL: []string{"SELECT 1"}, Endpoints: []string{"GET /api/v1/apps/" + slug + "/items"}, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } if err := store.Save(ctx, rec); err != nil { t.Fatal(err) } got, err := store.GetBySlug(ctx, tenantID, slug) if err != nil { t.Fatal(err) } if got.Status != meta.StatusPublished || got.Blueprint == nil || got.Blueprint.Meta.Name != "持久化测试" { t.Fatalf("unexpected record: %+v", got) } ref, err := store.ResolveResource(ctx, tenantID, slug, "items") if err != nil { t.Fatal(err) } if ref.Entity.Name != "item" { t.Fatalf("entity=%s", ref.Entity.Name) } // 二次 Save 应保留 app_id rec.Name = "持久化测试-更新" rec.AppID = "22222222-2222-2222-2222-222222222222" if err := store.Save(ctx, rec); err != nil { t.Fatal(err) } got2, err := store.GetBySlug(ctx, tenantID, slug) if err != nil { t.Fatal(err) } if got2.AppID != "11111111-1111-1111-1111-111111111111" { t.Fatalf("app_id should be preserved, got %s", got2.AppID) } if got2.Name != "持久化测试-更新" { t.Fatalf("name not updated: %s", got2.Name) } }