Release / publish (, ubuntu-22.04) (push) Failing after 14m39s
Release / publish (--target aarch64-apple-darwin, macos-latest) (push) Canceled after 0s
Release / publish (--target x86_64-apple-darwin, macos-15-intel) (push) Canceled after 0s
Release / publish (, windows-latest) (push) Canceled after 0s
* feat(models): Change::RebuildTable + role/privilege/readiness/maintenance structs (Task 1.1) * feat(introspection): role/privilege/tablespace/rebuild-readiness query builders (Task 1.2) * feat(lib): rebuild_table payload, table/role object kinds, v0.7.7 capabilities + types (Task 1.3) * feat(object_crud): table_ddl builder (create/edit-diff/options/drop) + build_ddl table arm (Task 2.1) * feat(object_crud): role_ddl builder (create/edit/drop/grant/revoke) + build_ddl role arm (Task 2.2) * feat(object_crud): rebuild_script assembler + fk/grant/sequence introspection queries (Task 2.3) * feat(commands): transactional RebuildTable arm + build_rebuild_script + get_table_columns commands (Task 2.4) * feat(commands): get_roles/privileges/readiness/tablespaces + run_maintenance (Task 2.5) * feat(commands): role/privilege/rebuild/maintenance typed wrappers (Task 3.1) * feat(store): roles slice + openFormTab table/role dedup (Task 3.2) * feat(ui): TableForm (create/edit-diff/rebuild) + cross-schema FK with ON DELETE/UPDATE (Task 4.1) * feat(ui): RoleForm + RoleDetail + RoleGrantsEditor (Task 4.2) * feat(ui): wire roles into object registry + explorer (Task 4.3) * feat(ui): Edit Table + maintenance menu + Create Table toolbar + rebuild badge (Task 4.4) * chore: bump version to 0.7.7 (Task 5.1) * docs: sync README/ROADMAP/AGENTS to v0.7.7 (Task 5.2) * test: v0.7.7 manual smoke checklist (Task 5.3) * chore: sync Cargo.lock to v0.7.7 * style(ui): match TableForm visual layout to shared object-form rows (remove stray padding) * feat(ui): table creator grid redesign + per-column FK slide-in panel with relationship controls * style(ui): table-creator columns grid — bordered cells like the data table view * feat(ui): table creator — type dropdown, PK/Nullable rules, drag-to-reorder columns (vertical), tidy add-column row * feat(ui): table creator — single-PK exclusivity, name on top, fixed column widths + contained h-scroll, no-wrap constraints * style(ui): table creator — constraints column sized to content (no clip), overscroll containment * style(ui): table creator — overscrollBehavior none on both scroll containers, add-column row py-2 * feat(ui): table creator — centered add-column button, schema dropdown auto-selected to current schema * style(ui): table creator — columns grid fills container width, scrolls only when too narrow * feat(ui): table creator — shorthand PG types in dropdown, Auto-Increment only for int-family * feat(ui): FK panel — animate in/out from the right, flush create-table-style rows * fix(ui): FK panel — smooth slide (willChange + eased), local column options from the form grid * feat(ui): FK composer (multi-column, type preview, gated sections) + composite PK + constraints cog in actions col + auto-named FKs * fix(ui): FK panel blocks unnamed table with hint; Foreign keys section lists staged + DB FKs * style(ui): FK panel — transparent full-width selects, title↔select borders, 2-col column picker * style(ui): FK panel — vertical divider between local/ref columns * style(ui): FK panel — section title padding py-2 * style(ui): FK panel — remove row margins + select/text padding in column picker * style(ui): FK panel — flush column picker (no section padding), border-r + py-1 cells * style(ui): FK panel — cell padding + row borders on column picker (user polish) * feat: FK inline into CREATE TABLE (single staged change in create mode); touch up class * style(ui): hide FK icon on PK columns; fix grid header/row column alignment * feat(ui): SQL preview in table creator uses read-only Monaco editor * feat(ui): Foreign keys section — relation rows with icon + Edit/Remove actions * style(ui): FK relation row — label, icon, table on one line * style(ui): FormRow selects transparent like FK panel; test matches FK relation label * style(ui): form-row focus outline amber-400/20 (subtle) * style(ui): SQL view — remove px-4 py-3 wrapper padding * feat(ui): Create Table button label; auto tree refresh after schema-modifying commits; table name on staged ddl card * fix(roles): cast rolconnlimit::int8 to fix panic deserializing int4 into i64 * fix(privileges): cast privilege_type::text to avoid domain-array panic; aclexplode for sequences/schemas (role_sequence_grants removed in PG15, schema_privileges never existed) * feat(ui): role privileges sections collapsible (collapsed by default) with 5-entry preview + fade + Show more * feat(ui): role privileges — collapsed state shows first 5 with fade; expand reveals all * chore(test): drop unused fireEvent import in RoleDetail test * style(ui): hide chevron when section has 5 or fewer entries * docs: README/ROADMAP/AGENTS — v0.7.7 shipped details (roles/grants, table editor, FK composer, privilege explorer); ROADMAP adds v0.7.8 Next up (quick wins) * docs(readme): collapse Recent changes and Key features into details blocks * docs(readme): move hero caption under image; align download + comparison table columns * docs(readme): collapse only body content — keep Recent Changes / Key Features headings visible * docs(readme): per-subsection collapsibles with meaningful summary labels
530 lines
16 KiB
TypeScript
530 lines
16 KiB
TypeScript
import { describe, it, expect, expectTypeOf } from "vitest";
|
|
import type {
|
|
Connection,
|
|
ConnectionInput,
|
|
ActiveView,
|
|
TableInfo,
|
|
ColumnInfo,
|
|
QueryResult,
|
|
ChangeItem,
|
|
ChangeItemType,
|
|
ChangeStatus,
|
|
DbViewerTab,
|
|
ConnectionTestResult,
|
|
SchemaGraph,
|
|
TableNode,
|
|
GraphColumn,
|
|
Relationship,
|
|
Settings,
|
|
IndexInfo,
|
|
ConstraintInfo,
|
|
RecentConnection,
|
|
RoleInfo,
|
|
PrivilegeEntry,
|
|
RebuildReadiness,
|
|
MaintenanceResult,
|
|
ObjectType,
|
|
} from "./types";
|
|
|
|
describe("ActiveView", () => {
|
|
it("includes db-viewer", () => {
|
|
const view: ActiveView = "db-viewer";
|
|
expect(view).toBe("db-viewer");
|
|
});
|
|
});
|
|
|
|
describe("Connection", () => {
|
|
it("accepts new SSH/SSL fields", () => {
|
|
const conn: Connection = {
|
|
id: "c1",
|
|
name: "Test",
|
|
db_type: "postgresql",
|
|
host: "localhost",
|
|
port: 5432,
|
|
username: "admin",
|
|
folder_id: null,
|
|
keychain_ref: null,
|
|
tag_ids: [],
|
|
favorite: false,
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
updated_at: "2024-01-01T00:00:00Z",
|
|
// new SSH/SSL fields
|
|
database: "mydb",
|
|
ssh_host: "bastion.example.com",
|
|
ssh_port: 22,
|
|
ssh_user: "tunnel",
|
|
ssh_auth_method: "key",
|
|
ssh_private_key_path: "/home/user/.ssh/id_rsa",
|
|
ssl_mode: "require",
|
|
ssl_ca_path: "/etc/ssl/certs/ca.pem",
|
|
ssl_cert_path: "/etc/ssl/certs/client.crt",
|
|
ssl_key_path: "/etc/ssl/certs/client.key",
|
|
};
|
|
expect(conn.ssh_host).toBe("bastion.example.com");
|
|
expect(conn.database).toBe("mydb");
|
|
expect(conn.ssl_mode).toBe("require");
|
|
});
|
|
|
|
it("does not include sensitive SSH/SSL fields (password/ssh_passphrase)", () => {
|
|
// TypeScript compile check: these should not be assignable
|
|
const conn: Connection = {
|
|
id: "c2",
|
|
name: "Minimal",
|
|
db_type: "postgresql",
|
|
host: "localhost",
|
|
port: null,
|
|
username: null,
|
|
folder_id: null,
|
|
keychain_ref: null,
|
|
tag_ids: [],
|
|
favorite: false,
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
updated_at: "2024-01-01T00:00:00Z",
|
|
};
|
|
// @ts-expect-error - password must NOT exist on Connection
|
|
conn.password;
|
|
// @ts-expect-error - ssh_passphrase must NOT exist on Connection
|
|
conn.ssh_passphrase;
|
|
expect(conn.host).toBe("localhost");
|
|
});
|
|
|
|
it("accepts SSH fields as optional (minimal connection)", () => {
|
|
const conn: Connection = {
|
|
id: "c3",
|
|
name: "Minimal",
|
|
db_type: "postgresql",
|
|
host: "localhost",
|
|
port: null,
|
|
username: null,
|
|
folder_id: null,
|
|
keychain_ref: null,
|
|
tag_ids: [],
|
|
favorite: false,
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
updated_at: "2024-01-01T00:00:00Z",
|
|
};
|
|
expect(conn.ssh_host).toBeUndefined();
|
|
expect(conn.database).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("ConnectionInput", () => {
|
|
it("accepts ssh_password", () => {
|
|
const input: ConnectionInput = {
|
|
name: "x", db_type: "postgresql", host: "h", port: 5432, ssh_password: "ssh-pw",
|
|
};
|
|
expect(input.ssh_password).toBe("ssh-pw");
|
|
});
|
|
|
|
it("accepts all SSH/SSL fields", () => {
|
|
const input: ConnectionInput = {
|
|
name: "Test",
|
|
db_type: "postgresql",
|
|
host: "localhost",
|
|
port: 5432,
|
|
ssh_host: "bastion.example.com",
|
|
ssh_port: 22,
|
|
ssh_user: "tunnel",
|
|
ssh_auth_method: "key",
|
|
ssh_private_key_path: "/home/user/.ssh/id_rsa",
|
|
ssh_passphrase: "s3cret",
|
|
ssl_mode: "verify-full",
|
|
ssl_ca_path: "/etc/ssl/certs/ca.pem",
|
|
ssl_cert_path: "/etc/ssl/certs/client.crt",
|
|
ssl_key_path: "/etc/ssl/certs/client.key",
|
|
password: "dbpass",
|
|
database: "mydb",
|
|
};
|
|
expect(input.ssh_auth_method).toBe("key");
|
|
expect(input.ssl_mode).toBe("verify-full");
|
|
expect(input.ssh_passphrase).toBe("s3cret");
|
|
expect(input.database).toBe("mydb");
|
|
});
|
|
|
|
it("accepts password auth method", () => {
|
|
const input: ConnectionInput = {
|
|
name: "PW SSH",
|
|
db_type: "postgresql",
|
|
host: "db.example.com",
|
|
port: 5432,
|
|
ssh_host: "gateway.example.com",
|
|
ssh_port: 2222,
|
|
ssh_user: "proxy",
|
|
ssh_auth_method: "password",
|
|
};
|
|
expect(input.ssh_auth_method).toBe("password");
|
|
});
|
|
|
|
it("allows all SSH/SSL fields to be omitted", () => {
|
|
const input: ConnectionInput = {
|
|
name: "Simple",
|
|
db_type: "sqlite",
|
|
host: "localhost",
|
|
port: null,
|
|
};
|
|
expect(input.ssh_host).toBeUndefined();
|
|
expect(input.ssl_mode).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("TableInfo", () => {
|
|
it("has the correct shape", () => {
|
|
const table: TableInfo = {
|
|
name: "users",
|
|
schema: "public",
|
|
table_type: "TABLE",
|
|
};
|
|
expect(table.name).toBe("users");
|
|
expect(table.schema).toBe("public");
|
|
expect(table.table_type).toBe("TABLE");
|
|
});
|
|
|
|
it("accepts view type", () => {
|
|
const table: TableInfo = { name: "v1", schema: "public", table_type: "VIEW" };
|
|
expect(table.table_type).toBe("VIEW");
|
|
});
|
|
});
|
|
|
|
describe("ColumnInfo", () => {
|
|
it("has the correct shape", () => {
|
|
const col: ColumnInfo = {
|
|
name: "id",
|
|
data_type: "integer",
|
|
is_nullable: false,
|
|
is_pk: true,
|
|
is_fk: false,
|
|
fk_ref: null,
|
|
default_value: null,
|
|
editable: true,
|
|
is_generated: false,
|
|
};
|
|
expect(col.name).toBe("id");
|
|
expect(col.is_pk).toBe(true);
|
|
});
|
|
|
|
it("supports foreign key references", () => {
|
|
const col: ColumnInfo = {
|
|
name: "user_id",
|
|
data_type: "integer",
|
|
is_nullable: true,
|
|
is_pk: false,
|
|
is_fk: true,
|
|
fk_ref: ["users", "id"],
|
|
default_value: null,
|
|
editable: true,
|
|
is_generated: false,
|
|
};
|
|
expect(col.fk_ref?.[0]).toBe("users");
|
|
});
|
|
});
|
|
|
|
describe("QueryResult", () => {
|
|
it("is well-typed with columns and rows", () => {
|
|
const result: QueryResult = {
|
|
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false},{name:"name",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false},{name:"email",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false}],
|
|
rows: [
|
|
[1, "Alice"],
|
|
[2, "Bob"],
|
|
],
|
|
total_rows: 2, page: 1, page_size: 50,
|
|
execution_time_ms: 12.5,
|
|
};
|
|
expect(result.columns.length).toBe(3);
|
|
expect(result.total_rows).toBe(2);
|
|
expect(result.execution_time_ms).toBe(12.5);
|
|
});
|
|
|
|
it("allows error state", () => {
|
|
const result: QueryResult = {
|
|
columns: [],
|
|
rows: [],
|
|
total_rows: 0, page: 1, page_size: 50,
|
|
error: "Syntax error near FROM",
|
|
};
|
|
expect(result.error).toBe("Syntax error near FROM");
|
|
});
|
|
|
|
it("can have null execution_time", () => {
|
|
const result: QueryResult = {
|
|
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false}],
|
|
rows: [],
|
|
total_rows: 0, page: 1, page_size: 50,
|
|
execution_time_ms: null,
|
|
};
|
|
expect(result.execution_time_ms).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("ChangeItem", () => {
|
|
it("has a discriminated type", () => {
|
|
const createItem: ChangeItem = {
|
|
type: "create_table",
|
|
sql: "CREATE TABLE users (id INT)",
|
|
status: "pending",
|
|
id: "ch1",
|
|
description: "Create users table",
|
|
};
|
|
expect(createItem.type).toBe("create_table");
|
|
expect(createItem.status).toBe("pending");
|
|
|
|
const dropItem: ChangeItem = {
|
|
type: "drop_table",
|
|
sql: "DROP TABLE users",
|
|
status: "applied",
|
|
id: "ch2",
|
|
};
|
|
expect(dropItem.type).toBe("drop_table");
|
|
expect(dropItem.status).toBe("applied");
|
|
});
|
|
|
|
it("accepts error status with error message", () => {
|
|
const item: ChangeItem = {
|
|
type: "alter_table",
|
|
sql: "ALTER TABLE users ADD COLUMN age INT",
|
|
status: "error",
|
|
error: "Column already exists",
|
|
id: "ch3",
|
|
};
|
|
expect(item.status).toBe("error");
|
|
expect(item.error).toBe("Column already exists");
|
|
});
|
|
|
|
it("accepts all ChangeItemType values", () => {
|
|
const types: ChangeItemType[] = [
|
|
"create_table",
|
|
"alter_table",
|
|
"drop_table",
|
|
"insert",
|
|
"update",
|
|
"delete",
|
|
"create_index",
|
|
"drop_index",
|
|
];
|
|
const item: ChangeItem = {
|
|
type: types[0],
|
|
sql: "test",
|
|
status: "pending",
|
|
id: "ch4",
|
|
};
|
|
expect(types).toContain(item.type);
|
|
});
|
|
});
|
|
|
|
describe("ChangeStatus", () => {
|
|
it("accepts all status values", () => {
|
|
const statuses: ChangeStatus[] = ["pending", "applied", "error"];
|
|
expect(statuses).toHaveLength(3);
|
|
});
|
|
});
|
|
|
|
describe("DbViewerTab", () => {
|
|
it("can be created with required fields", () => {
|
|
const tab: DbViewerTab = {
|
|
id: "tab1",
|
|
connection_id: "c1",
|
|
title: "Query 1",
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
updated_at: "2024-01-01T00:00:00Z",
|
|
};
|
|
expect(tab.title).toBe("Query 1");
|
|
expect(tab.query).toBeUndefined();
|
|
expect(tab.result).toBeUndefined();
|
|
expect(tab.changes).toBeUndefined();
|
|
});
|
|
|
|
it("can include query and result", () => {
|
|
const tab: DbViewerTab = {
|
|
id: "tab2",
|
|
connection_id: "c1",
|
|
title: "SELECT * FROM users",
|
|
query: "SELECT * FROM users",
|
|
result: {
|
|
columns: [{name:"id",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false},{name:"name",data_type:"text",is_pk:false,is_fk:false,is_nullable:false,default_value:null,fk_ref:null,editable:true,is_generated:false}],
|
|
rows: [],
|
|
total_rows: 0, page: 1, page_size: 50,
|
|
},
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
updated_at: "2024-01-01T00:00:00Z",
|
|
};
|
|
expect(tab.query).toBe("SELECT * FROM users");
|
|
expect(tab.result?.total_rows).toBe(0);
|
|
});
|
|
|
|
it("can include changes array", () => {
|
|
const tab: DbViewerTab = {
|
|
id: "tab3",
|
|
connection_id: "c1",
|
|
title: "Migration",
|
|
changes: [
|
|
{ type: "create_table", sql: "CREATE TABLE t (id INT)", status: "pending", id: "ch1" },
|
|
],
|
|
created_at: "2024-01-01T00:00:00Z",
|
|
updated_at: "2024-01-01T00:00:00Z",
|
|
};
|
|
expect(tab.changes).toHaveLength(1);
|
|
expect(tab.changes![0].type).toBe("create_table");
|
|
});
|
|
});
|
|
|
|
describe("ConnectionTestResult", () => {
|
|
it("can represent a successful test", () => {
|
|
const result: ConnectionTestResult = {
|
|
ok: true,
|
|
server_version: "16.2",
|
|
latency_ms: 5,
|
|
};
|
|
expect(result.ok).toBe(true);
|
|
expect(result.server_version).toBe("16.2");
|
|
});
|
|
|
|
it("can represent a failed test with error", () => {
|
|
const result: ConnectionTestResult = {
|
|
ok: false,
|
|
error: "Connection refused",
|
|
};
|
|
expect(result.ok).toBe(false);
|
|
expect(result.error).toBe("Connection refused");
|
|
});
|
|
});
|
|
|
|
describe("Schema graph types", () => {
|
|
it("GraphColumn has correct shape", () => {
|
|
const col: GraphColumn = {
|
|
name: "user_id",
|
|
data_type: "integer",
|
|
is_pk: false,
|
|
is_fk: true,
|
|
is_unique: false,
|
|
is_nullable: false,
|
|
fk_ref: ["public", "users", "id"],
|
|
};
|
|
expect(col.name).toBe("user_id");
|
|
expect(col.is_fk).toBe(true);
|
|
expect(col.fk_ref).toEqual(["public", "users", "id"]);
|
|
});
|
|
|
|
it("TableNode has correct shape", () => {
|
|
const node: TableNode = {
|
|
name: "orders",
|
|
schema: "public",
|
|
table_type: "TABLE",
|
|
columns: [
|
|
{ name: "id", data_type: "integer", is_pk: true, is_fk: false, is_unique: true, is_nullable: false, fk_ref: null },
|
|
{ name: "user_id", data_type: "integer", is_pk: false, is_fk: true, is_unique: false, is_nullable: false, fk_ref: ["public", "users", "id"] },
|
|
],
|
|
};
|
|
expect(node.name).toBe("orders");
|
|
expect(node.columns).toHaveLength(2);
|
|
});
|
|
|
|
it("Relationship has correct shape", () => {
|
|
const rel: Relationship = {
|
|
source_schema: "public",
|
|
source_table: "orders",
|
|
source_column: "user_id",
|
|
target_schema: "public",
|
|
target_table: "users",
|
|
target_column: "id",
|
|
cardinality: "1:N",
|
|
};
|
|
expect(rel.cardinality).toBe("1:N");
|
|
});
|
|
|
|
it("SchemaGraph has correct shape", () => {
|
|
const graph: SchemaGraph = {
|
|
tables: [
|
|
{ name: "users", schema: "public", table_type: "TABLE", columns: [] },
|
|
],
|
|
relationships: [],
|
|
};
|
|
expect(graph.tables).toHaveLength(1);
|
|
expect(graph.relationships).toHaveLength(0);
|
|
});
|
|
|
|
it("fk_ref can be null for non-FK columns", () => {
|
|
const col: GraphColumn = {
|
|
name: "name",
|
|
data_type: "text",
|
|
is_pk: false,
|
|
is_fk: false,
|
|
is_unique: false,
|
|
is_nullable: true,
|
|
fk_ref: null,
|
|
};
|
|
expect(col.fk_ref).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("Settings", () => {
|
|
it("includes the five editor option fields", () => {
|
|
const s: Settings = {
|
|
confirm_before_delete: true, default_folder_id: null, theme: "dark",
|
|
font_size: "medium", default_ports: {}, tag_order: null, table_refresh_rate: 0,
|
|
table_page_size: 50, shortcuts: {}, accent_color: "#2563EB",
|
|
editor_font_size: 13, editor_font_family: "Space Mono",
|
|
editor_word_wrap: "off", editor_minimap: false, editor_tab_size: 4,
|
|
};
|
|
expect(s.editor_font_size).toBe(13);
|
|
expect(s.editor_word_wrap).toBe("off");
|
|
});
|
|
});
|
|
|
|
describe("v0.5.0 types", () => {
|
|
it("ColumnInfo carries editability metadata", () => {
|
|
const c: ColumnInfo = {
|
|
name: "id", data_type: "integer", is_nullable: false, is_pk: true,
|
|
is_fk: false, fk_ref: null, default_value: null, editable: false, is_generated: false,
|
|
};
|
|
expectTypeOf(c.editable).toEqualTypeOf<boolean>();
|
|
expectTypeOf(c.is_generated).toEqualTypeOf<boolean>();
|
|
});
|
|
it("IndexInfo has the documented fields", () => {
|
|
const i: IndexInfo = {
|
|
name: "idx", schema: "public", table: "users", definition: "CREATE INDEX ...",
|
|
is_unique: true, method: "btree", columns: ["id"], size_bytes: 4096, tablespace: null,
|
|
};
|
|
expectTypeOf(i).toMatchTypeOf<IndexInfo>();
|
|
});
|
|
it("ConstraintInfo has the documented fields", () => {
|
|
const c: ConstraintInfo = {
|
|
name: "ck", schema: "public", table: "users", contype: "CHECK",
|
|
definition: "CHECK (x > 0)", deferrable: false, validated: true, columns: ["x"],
|
|
};
|
|
expectTypeOf(c).toMatchTypeOf<ConstraintInfo>();
|
|
});
|
|
it("RecentConnection pairs a connection id with an opened_at timestamp", () => {
|
|
const r: RecentConnection = { connection_id: "c1", opened_at: "2026-08-02T00:00:00Z" };
|
|
expectTypeOf(r.connection_id).toEqualTypeOf<string>();
|
|
});
|
|
it("Connection carries favorite", () => {
|
|
const c = { favorite: true } as Connection;
|
|
expectTypeOf(c.favorite).toEqualTypeOf<boolean>();
|
|
});
|
|
});
|
|
|
|
describe("v0.7.7 types", () => {
|
|
it("ObjectType includes roles", () => {
|
|
const t: ObjectType = "roles";
|
|
expect(t).toBe("roles");
|
|
});
|
|
it("ChangeItemType includes rebuild_table", () => {
|
|
const t: ChangeItemType = "rebuild_table";
|
|
expect(t).toBe("rebuild_table");
|
|
});
|
|
it("RoleInfo shape", () => {
|
|
const r: RoleInfo = {
|
|
name: "app", superuser: false, inherit: true, create_db: false, create_role: false,
|
|
can_login: true, replication: false, bypass_rls: false, connection_limit: -1,
|
|
valid_until: null, memberships: [],
|
|
};
|
|
expect(r.name).toBe("app");
|
|
});
|
|
it("PrivilegeEntry/RebuildReadiness/MaintenanceResult shapes", () => {
|
|
const pe: PrivilegeEntry = { object_class: "table", schema: "public", name: "users", privileges: ["SELECT"], grantable: false };
|
|
const rr: RebuildReadiness = { ok: true, reasons: [] };
|
|
const mr: MaintenanceResult = { duration_ms: 1, message: "ok" };
|
|
expect(pe.object_class).toBe("table");
|
|
expect(rr.ok).toBe(true);
|
|
expect(mr.duration_ms).toBe(1);
|
|
});
|
|
}); |