@rawsql-ts/ztd-cli 0.14.4 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -11
- package/dist/commands/init.js +27 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/lint.d.ts +59 -0
- package/dist/commands/lint.js +338 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/sqlLintHelpers.d.ts +18 -0
- package/dist/utils/sqlLintHelpers.js +270 -0
- package/dist/utils/sqlLintHelpers.js.map +1 -0
- package/package.json +11 -4
- package/templates/AGENTS.md +95 -53
- package/templates/README.md +45 -67
- package/templates/dist/drivers/pg-testkit/src/driver/PgTestkitClient.d.ts +38 -0
- package/templates/dist/drivers/pg-testkit/src/driver/PgTestkitClient.js +117 -0
- package/templates/dist/drivers/pg-testkit/src/driver/PgTestkitClient.js.map +1 -0
- package/templates/dist/drivers/pg-testkit/src/driver/createPgTestkitPool.d.ts +4 -0
- package/templates/dist/drivers/pg-testkit/src/driver/createPgTestkitPool.js +71 -0
- package/templates/dist/drivers/pg-testkit/src/driver/createPgTestkitPool.js.map +1 -0
- package/templates/dist/drivers/pg-testkit/src/index.d.ts +5 -0
- package/templates/dist/drivers/pg-testkit/src/index.js +11 -0
- package/templates/dist/drivers/pg-testkit/src/index.js.map +1 -0
- package/templates/dist/drivers/pg-testkit/src/proxy/wrapPgClient.d.ts +3 -0
- package/templates/dist/drivers/pg-testkit/src/proxy/wrapPgClient.js +79 -0
- package/templates/dist/drivers/pg-testkit/src/proxy/wrapPgClient.js.map +1 -0
- package/templates/dist/drivers/pg-testkit/src/types.d.ts +69 -0
- package/templates/dist/drivers/pg-testkit/src/types.js +3 -0
- package/templates/dist/drivers/pg-testkit/src/types.js.map +1 -0
- package/templates/dist/drivers/pg-testkit/src/utils/fixtureState.d.ts +15 -0
- package/templates/dist/drivers/pg-testkit/src/utils/fixtureState.js +34 -0
- package/templates/dist/drivers/pg-testkit/src/utils/fixtureState.js.map +1 -0
- package/templates/dist/drivers/pg-testkit/src/utils/fixtureValidation.d.ts +12 -0
- package/templates/dist/drivers/pg-testkit/src/utils/fixtureValidation.js +53 -0
- package/templates/dist/drivers/pg-testkit/src/utils/fixtureValidation.js.map +1 -0
- package/templates/dist/mapper-core/src/index.d.ts +160 -0
- package/templates/dist/mapper-core/src/index.js +637 -0
- package/templates/dist/mapper-core/src/index.js.map +1 -0
- package/templates/dist/testkit-core/src/errors/index.d.ts +49 -0
- package/templates/dist/testkit-core/src/errors/index.js +111 -0
- package/templates/dist/testkit-core/src/errors/index.js.map +1 -0
- package/templates/dist/testkit-core/src/fixtures/ColumnAffinity.d.ts +5 -0
- package/templates/dist/testkit-core/src/fixtures/ColumnAffinity.js +29 -0
- package/templates/dist/testkit-core/src/fixtures/ColumnAffinity.js.map +1 -0
- package/templates/dist/testkit-core/src/fixtures/DdlFixtureLoader.d.ts +37 -0
- package/templates/dist/testkit-core/src/fixtures/DdlFixtureLoader.js +182 -0
- package/templates/dist/testkit-core/src/fixtures/DdlFixtureLoader.js.map +1 -0
- package/templates/dist/testkit-core/src/fixtures/FixtureProvider.d.ts +20 -0
- package/templates/dist/testkit-core/src/fixtures/FixtureProvider.js +121 -0
- package/templates/dist/testkit-core/src/fixtures/FixtureProvider.js.map +1 -0
- package/templates/dist/testkit-core/src/fixtures/FixtureStore.d.ts +51 -0
- package/templates/dist/testkit-core/src/fixtures/FixtureStore.js +199 -0
- package/templates/dist/testkit-core/src/fixtures/FixtureStore.js.map +1 -0
- package/templates/dist/testkit-core/src/fixtures/TableDefinitionSchemaRegistry.d.ts +10 -0
- package/templates/dist/testkit-core/src/fixtures/TableDefinitionSchemaRegistry.js +28 -0
- package/templates/dist/testkit-core/src/fixtures/TableDefinitionSchemaRegistry.js.map +1 -0
- package/templates/dist/testkit-core/src/fixtures/TableNameResolver.d.ts +18 -0
- package/templates/dist/testkit-core/src/fixtures/TableNameResolver.js +80 -0
- package/templates/dist/testkit-core/src/fixtures/TableNameResolver.js.map +1 -0
- package/templates/dist/testkit-core/src/fixtures/ddlLint.d.ts +59 -0
- package/templates/dist/testkit-core/src/fixtures/ddlLint.js +489 -0
- package/templates/dist/testkit-core/src/fixtures/ddlLint.js.map +1 -0
- package/templates/dist/testkit-core/src/fixtures/naming.d.ts +1 -0
- package/templates/dist/testkit-core/src/fixtures/naming.js +6 -0
- package/templates/dist/testkit-core/src/fixtures/naming.js.map +1 -0
- package/templates/dist/testkit-core/src/index.d.ts +17 -0
- package/templates/dist/testkit-core/src/index.js +47 -0
- package/templates/dist/testkit-core/src/index.js.map +1 -0
- package/templates/dist/testkit-core/src/logger/NoopLogger.d.ts +8 -0
- package/templates/dist/testkit-core/src/logger/NoopLogger.js +16 -0
- package/templates/dist/testkit-core/src/logger/NoopLogger.js.map +1 -0
- package/templates/dist/testkit-core/src/provider/TestkitProvider.d.ts +57 -0
- package/templates/dist/testkit-core/src/provider/TestkitProvider.js +149 -0
- package/templates/dist/testkit-core/src/provider/TestkitProvider.js.map +1 -0
- package/templates/dist/testkit-core/src/rewriter/ResultSelectRewriter.d.ts +43 -0
- package/templates/dist/testkit-core/src/rewriter/ResultSelectRewriter.js +473 -0
- package/templates/dist/testkit-core/src/rewriter/ResultSelectRewriter.js.map +1 -0
- package/templates/dist/testkit-core/src/rewriter/SelectAnalyzer.d.ts +9 -0
- package/templates/dist/testkit-core/src/rewriter/SelectAnalyzer.js +38 -0
- package/templates/dist/testkit-core/src/rewriter/SelectAnalyzer.js.map +1 -0
- package/templates/dist/testkit-core/src/rewriter/SelectFixtureRewriter.d.ts +42 -0
- package/templates/dist/testkit-core/src/rewriter/SelectFixtureRewriter.js +298 -0
- package/templates/dist/testkit-core/src/rewriter/SelectFixtureRewriter.js.map +1 -0
- package/templates/dist/testkit-core/src/sql/SqliteValuesBuilder.d.ts +12 -0
- package/templates/dist/testkit-core/src/sql/SqliteValuesBuilder.js +63 -0
- package/templates/dist/testkit-core/src/sql/SqliteValuesBuilder.js.map +1 -0
- package/templates/dist/testkit-core/src/types/index.d.ts +69 -0
- package/templates/dist/testkit-core/src/types/index.js +3 -0
- package/templates/dist/testkit-core/src/types/index.js.map +1 -0
- package/templates/dist/testkit-core/src/utils/queryHelpers.d.ts +28 -0
- package/templates/dist/testkit-core/src/utils/queryHelpers.js +81 -0
- package/templates/dist/testkit-core/src/utils/queryHelpers.js.map +1 -0
- package/templates/dist/writer-core/src/index.d.ts +34 -0
- package/templates/dist/writer-core/src/index.js +115 -0
- package/templates/dist/writer-core/src/index.js.map +1 -0
- package/templates/dist/ztd-cli/templates/src/db/sql-client.d.ts +20 -0
- package/templates/dist/ztd-cli/templates/src/db/sql-client.js +3 -0
- package/templates/dist/ztd-cli/templates/src/db/sql-client.js.map +1 -0
- package/templates/dist/ztd-cli/templates/src/repositories/user-accounts.d.ts +36 -0
- package/templates/dist/ztd-cli/templates/src/repositories/user-accounts.js +85 -0
- package/templates/dist/ztd-cli/templates/src/repositories/user-accounts.js.map +1 -0
- package/templates/dist/ztd-cli/templates/tests/generated/ztd-row-map.generated.d.ts +20 -0
- package/templates/dist/ztd-cli/templates/tests/generated/ztd-row-map.generated.js +33 -0
- package/templates/dist/ztd-cli/templates/tests/generated/ztd-row-map.generated.js.map +1 -0
- package/templates/dist/ztd-cli/templates/tests/support/global-setup.d.ts +10 -0
- package/templates/dist/ztd-cli/templates/tests/support/global-setup.js +29 -0
- package/templates/dist/ztd-cli/templates/tests/support/global-setup.js.map +1 -0
- package/templates/dist/ztd-cli/templates/tests/support/testkit-client.d.ts +66 -0
- package/templates/dist/ztd-cli/templates/tests/support/testkit-client.js +552 -0
- package/templates/dist/ztd-cli/templates/tests/support/testkit-client.js.map +1 -0
- package/templates/dist/ztd-cli/templates/tests/user-profiles.test.d.ts +1 -0
- package/templates/dist/ztd-cli/templates/tests/user-profiles.test.js +82 -0
- package/templates/dist/ztd-cli/templates/tests/user-profiles.test.js.map +1 -0
- package/templates/dist/ztd-cli/templates/tests/writer-constraints.test.d.ts +1 -0
- package/templates/dist/ztd-cli/templates/tests/writer-constraints.test.js +29 -0
- package/templates/dist/ztd-cli/templates/tests/writer-constraints.test.js.map +1 -0
- package/templates/dist/ztd-cli/templates/tests/ztd-layout.generated.d.ts +7 -0
- package/templates/dist/ztd-cli/templates/tests/ztd-layout.generated.js +10 -0
- package/templates/dist/ztd-cli/templates/tests/ztd-layout.generated.js.map +1 -0
- package/templates/src/db/sql-client.ts +1 -1
- package/templates/src/repositories/user-accounts.ts +179 -0
- package/templates/tests/AGENTS.md +59 -6
- package/templates/tests/support/global-setup.ts +1 -1
- package/templates/tests/support/testkit-client.ts +4 -4
- package/templates/tests/user-profiles.test.ts +161 -0
- package/templates/tests/writer-constraints.test.ts +32 -0
- package/templates/tests/ztd-layout.generated.ts +0 -2
- package/templates/tsconfig.json +1 -2
- package/templates/ztd/AGENTS.md +10 -85
- package/templates/ztd/README.md +10 -79
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-profiles.test.js","sourceRoot":"","sources":["../../../../tests/user-profiles.test.ts"],"names":[],"mappings":";;AAAA,mCAAgD;AAEhD,6EAI2C;AAC3C,6DAA+D;AAC/D,qEAAqE;AAErE,SAAS,iBAAiB;IACxB,OAAO;QACL;YACE,eAAe,EAAE,CAAC;YAClB,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,mBAAmB;YAC1B,YAAY,EAAE,cAAc;YAC5B,UAAU,EAAE,sBAAsB;YAClC,UAAU,EAAE,sBAAsB;SACnC;QACD;YACE,eAAe,EAAE,CAAC;YAClB,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,mBAAmB;YAC1B,YAAY,EAAE,eAAe;YAC7B,UAAU,EAAE,sBAAsB;YAClC,UAAU,EAAE,sBAAsB;SACnC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB;IACxB,OAAO;QACL;YACE,UAAU,EAAE,GAAG;YACf,eAAe,EAAE,CAAC;YAClB,GAAG,EAAE,oCAAoC;YACzC,OAAO,EAAE,qBAAqB;YAC9B,QAAQ,EAAE,IAAI;SACf;KACF,CAAC;AACJ,CAAC;AAED,SAAS,aAAa;IACpB,OAAO;QACL,IAAA,oCAAY,EACV,qBAAqB,EACrB,iBAAiB,EAAE,EACnB,oCAAY,CAAC,qBAAqB,CAAC,CACpC;QACD,IAAA,oCAAY,EACV,qBAAqB,EACrB,iBAAiB,EAAE,EACnB,oCAAY,CAAC,qBAAqB,CAAC,CACpC;KACF,CAAC;AACJ,CAAC;AAED,IAAA,iBAAQ,EAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,IAAA,aAAI,EAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC5D,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,IAAA,oCAAmB,EAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAA,gCAAgB,EAAC,MAAM,CAAC,CAAC;YAC9C,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB;oBACE,aAAa,EAAE,CAAC;oBAChB,QAAQ,EAAE,OAAO;oBACjB,KAAK,EAAE,mBAAmB;oBAC1B,WAAW,EAAE,cAAc;oBAC3B,SAAS,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;oBAC3C,SAAS,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;oBAC3C,OAAO,EAAE;wBACP,SAAS,EAAE,GAAG;wBACd,aAAa,EAAE,CAAC;wBAChB,GAAG,EAAE,oCAAoC;wBACzC,OAAO,EAAE,qBAAqB;wBAC9B,QAAQ,EAAE,IAAI;qBACf;iBACF;gBACD;oBACE,aAAa,EAAE,CAAC;oBAChB,QAAQ,EAAE,OAAO;oBACjB,KAAK,EAAE,mBAAmB;oBAC1B,WAAW,EAAE,eAAe;oBAC5B,SAAS,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;oBAC3C,SAAS,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;oBAC3C,OAAO,EAAE,SAAS;iBACnB;aACF,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const ztd_row_map_generated_1 = require("./generated/ztd-row-map.generated");
|
|
5
|
+
const user_accounts_1 = require("../src/repositories/user-accounts");
|
|
6
|
+
const userColumns = new Set(Object.keys(ztd_row_map_generated_1.tableSchemas['public.user_account'].columns));
|
|
7
|
+
(0, vitest_1.describe)('user_account writer columns', () => {
|
|
8
|
+
(0, vitest_1.test)('insert columns must exist on the canonical table', () => {
|
|
9
|
+
const { insertColumns } = user_accounts_1.userAccountWriterColumnSets;
|
|
10
|
+
const missing = insertColumns.filter((column) => !userColumns.has(column));
|
|
11
|
+
(0, vitest_1.expect)(missing).toEqual([]);
|
|
12
|
+
(0, vitest_1.expect)(insertColumns).toEqual(vitest_1.expect.arrayContaining(['username', 'email', 'display_name']));
|
|
13
|
+
});
|
|
14
|
+
(0, vitest_1.test)('update columns stay within allowed set and avoid immutable columns', () => {
|
|
15
|
+
const { updateColumns, immutableColumns } = user_accounts_1.userAccountWriterColumnSets;
|
|
16
|
+
const missing = updateColumns.filter((column) => !userColumns.has(column));
|
|
17
|
+
(0, vitest_1.expect)(missing).toEqual([]);
|
|
18
|
+
(0, vitest_1.expect)(updateColumns).not.toEqual(vitest_1.expect.arrayContaining([...immutableColumns]));
|
|
19
|
+
});
|
|
20
|
+
(0, vitest_1.test)('immutable columns reflect the DDL and are not targetted by updates', () => {
|
|
21
|
+
const { immutableColumns, updateColumns } = user_accounts_1.userAccountWriterColumnSets;
|
|
22
|
+
const missing = immutableColumns.filter((column) => !userColumns.has(column));
|
|
23
|
+
(0, vitest_1.expect)(missing).toEqual([]);
|
|
24
|
+
immutableColumns.forEach((column) => {
|
|
25
|
+
(0, vitest_1.expect)(updateColumns).not.toContain(column);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=writer-constraints.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer-constraints.test.js","sourceRoot":"","sources":["../../../../tests/writer-constraints.test.ts"],"names":[],"mappings":";;AAAA,mCAAgD;AAChD,6EAAiE;AACjE,qEAAgF;AAEhF,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,MAAM,CAAC,IAAI,CAAC,oCAAY,CAAC,qBAAqB,CAAC,CAAC,OAAO,CAAC,CACzD,CAAC;AAEF,IAAA,iBAAQ,EAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,IAAA,aAAI,EAAC,kDAAkD,EAAE,GAAG,EAAE;QAC5D,MAAM,EAAE,aAAa,EAAE,GAAG,2CAA2B,CAAC;QACtD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3E,IAAA,eAAM,EAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAA,eAAM,EAAC,aAAa,CAAC,CAAC,OAAO,CAC3B,eAAM,CAAC,eAAe,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAC9D,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAA,aAAI,EAAC,oEAAoE,EAAE,GAAG,EAAE;QAC9E,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,GAAG,2CAA2B,CAAC;QACxE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3E,IAAA,eAAM,EAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC5B,IAAA,eAAM,EAAC,aAAa,CAAC,CAAC,GAAG,CAAC,OAAO,CAC/B,eAAM,CAAC,eAAe,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAC9C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAA,aAAI,EAAC,oEAAoE,EAAE,GAAG,EAAE;QAC9E,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,GAAG,2CAA2B,CAAC;QACxE,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9E,IAAA,eAAM,EAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC5B,gBAAgB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;YAClC,IAAA,eAAM,EAAC,aAAa,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// GENERATED FILE. DO NOT EDIT.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.default = {
|
|
5
|
+
ztdRootDir: 'ztd',
|
|
6
|
+
ddlDir: 'ztd/ddl',
|
|
7
|
+
enumsDir: 'ztd/enums',
|
|
8
|
+
domainSpecsDir: 'ztd/domain-specs',
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=ztd-layout.generated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ztd-layout.generated.js","sourceRoot":"","sources":["../../../../tests/ztd-layout.generated.ts"],"names":[],"mappings":";AAAA,+BAA+B;;AAE/B,kBAAe;IACb,UAAU,EAAE,KAAK;IACjB,MAAM,EAAE,SAAS;IACjB,QAAQ,EAAE,WAAW;IACrB,cAAc,EAAE,kBAAkB;CACnC,CAAC"}
|
|
@@ -10,7 +10,7 @@ export type SqlQueryRows<T> = Promise<T[]>;
|
|
|
10
10
|
* Minimal SQL client interface required by the repository layer.
|
|
11
11
|
*
|
|
12
12
|
* - Production: adapt `pg` (or other drivers) to normalize results into `T[]`
|
|
13
|
-
* - Tests: compatible with `
|
|
13
|
+
* - Tests: compatible with the `testkit-postgres` pipeline exposed by `@rawsql-ts/adapter-node-pg` clients returned by `createTestkitClient()`
|
|
14
14
|
*
|
|
15
15
|
* Connection strategy note:
|
|
16
16
|
* - Prefer a shared client per worker process for performance.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { SqlClient } from '../db/sql-client';
|
|
2
|
+
import { createMapper, entity, toRowsExecutor } from '@rawsql-ts/mapper-core';
|
|
3
|
+
import { insert, Key, remove, update } from '@rawsql-ts/writer-core';
|
|
4
|
+
|
|
5
|
+
const userAccountTable = 'public.user_account';
|
|
6
|
+
|
|
7
|
+
type UserProfileRow = {
|
|
8
|
+
profileId: number;
|
|
9
|
+
userAccountId: number;
|
|
10
|
+
bio: string | null;
|
|
11
|
+
website: string | null;
|
|
12
|
+
verified: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* DTO that represents a user account with its optional profile information.
|
|
17
|
+
* @property {number} userAccountId The primary key for the user account.
|
|
18
|
+
* @property {string} username The canonical username.
|
|
19
|
+
* @property {string} email The account email address.
|
|
20
|
+
* @property {string} displayName The account display name.
|
|
21
|
+
* @property {Date} createdAt When the account was created.
|
|
22
|
+
* @property {Date} updatedAt When the account was last updated.
|
|
23
|
+
* @property {UserProfileRow} [profile] Optional profile payload joined from user_profile.
|
|
24
|
+
*/
|
|
25
|
+
export type UserAccountWithProfile = {
|
|
26
|
+
userAccountId: number;
|
|
27
|
+
username: string;
|
|
28
|
+
email: string;
|
|
29
|
+
displayName: string;
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
updatedAt: Date;
|
|
32
|
+
profile?: UserProfileRow;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Map the joined profile columns so we can hydrate nested objects later.
|
|
36
|
+
const profileMapping = entity<UserProfileRow>({
|
|
37
|
+
name: 'userProfile',
|
|
38
|
+
key: 'profileId',
|
|
39
|
+
columnMap: {
|
|
40
|
+
profileId: 'profile_id',
|
|
41
|
+
userAccountId: 'profile_user_account_id',
|
|
42
|
+
bio: 'bio',
|
|
43
|
+
website: 'website',
|
|
44
|
+
verified: 'verified',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const userAccountMapping = entity<UserAccountWithProfile>({
|
|
49
|
+
name: 'userAccount',
|
|
50
|
+
key: 'userAccountId',
|
|
51
|
+
columnMap: {
|
|
52
|
+
userAccountId: 'user_account_id',
|
|
53
|
+
username: 'username',
|
|
54
|
+
email: 'email',
|
|
55
|
+
displayName: 'display_name',
|
|
56
|
+
createdAt: 'created_at',
|
|
57
|
+
updatedAt: 'updated_at',
|
|
58
|
+
},
|
|
59
|
+
}).belongsTo('profile', profileMapping, 'userAccountId', { optional: true });
|
|
60
|
+
|
|
61
|
+
const userProfilesSql = `
|
|
62
|
+
SELECT
|
|
63
|
+
u.user_account_id,
|
|
64
|
+
u.username,
|
|
65
|
+
u.email,
|
|
66
|
+
u.display_name,
|
|
67
|
+
u.created_at,
|
|
68
|
+
u.updated_at,
|
|
69
|
+
p.profile_id,
|
|
70
|
+
p.user_account_id AS profile_user_account_id,
|
|
71
|
+
p.bio,
|
|
72
|
+
p.website,
|
|
73
|
+
p.verified
|
|
74
|
+
FROM public.user_account u
|
|
75
|
+
LEFT JOIN public.user_profile p ON p.user_account_id = u.user_account_id
|
|
76
|
+
ORDER BY u.user_account_id, p.profile_id;
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
// Build a mapper that can translate snake_case columns into camelCase DTOs.
|
|
80
|
+
const createMapperForClient = (client: SqlClient) =>
|
|
81
|
+
createMapper(
|
|
82
|
+
toRowsExecutor((sql, params: unknown[] = []) =>
|
|
83
|
+
client.query<Record<string, unknown>>(sql, params),
|
|
84
|
+
),
|
|
85
|
+
{
|
|
86
|
+
// The explicit column maps enumerate the nested entity columns while keyTransform handles generic snake_to_camel conversions.
|
|
87
|
+
keyTransform: 'snake_to_camel',
|
|
88
|
+
coerceDates: true,
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Queries all user accounts together with their associated profiles.
|
|
94
|
+
* @param {SqlClient} client Client proxy that executes the mapper SQL.
|
|
95
|
+
* @returns {Promise<UserAccountWithProfile[]>} The joined account-with-profile rows.
|
|
96
|
+
*/
|
|
97
|
+
export async function listUserProfiles(
|
|
98
|
+
client: SqlClient,
|
|
99
|
+
): Promise<UserAccountWithProfile[]> {
|
|
100
|
+
const mapper = createMapperForClient(client);
|
|
101
|
+
return mapper.query(userProfilesSql, [], userAccountMapping);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parameters required to insert a new user account.
|
|
106
|
+
* @property {string} username The requested username.
|
|
107
|
+
* @property {string} email The requested email address.
|
|
108
|
+
* @property {string} displayName The requested display name.
|
|
109
|
+
*/
|
|
110
|
+
export type NewUserAccount = {
|
|
111
|
+
username: string;
|
|
112
|
+
email: string;
|
|
113
|
+
displayName: string;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Payload describing the display name change for an existing account.
|
|
118
|
+
* @property {string} displayName The new display name to persist.
|
|
119
|
+
*/
|
|
120
|
+
export type DisplayNameUpdatePayload = {
|
|
121
|
+
displayName: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Builds an insert statement for the user_account writer.
|
|
126
|
+
* @param {NewUserAccount} input The normalized fields for the new account.
|
|
127
|
+
* @returns {ReturnType<typeof insert>} A well-formed insert statement for the user_account writer.
|
|
128
|
+
*/
|
|
129
|
+
export function buildInsertUserAccount(
|
|
130
|
+
input: NewUserAccount,
|
|
131
|
+
): ReturnType<typeof insert> {
|
|
132
|
+
return insert(userAccountTable, {
|
|
133
|
+
username: input.username,
|
|
134
|
+
email: input.email,
|
|
135
|
+
display_name: input.displayName,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Builds an update statement that refreshes the display name and timestamp.
|
|
141
|
+
* @param {Key} key The unique key identifying the row to update.
|
|
142
|
+
* @param {DisplayNameUpdatePayload} payload The new display name payload.
|
|
143
|
+
* @returns {ReturnType<typeof update>} A writer update statement that refreshes the display name and updated_at timestamp.
|
|
144
|
+
*/
|
|
145
|
+
export function buildUpdateDisplayName(
|
|
146
|
+
key: Key,
|
|
147
|
+
payload: DisplayNameUpdatePayload,
|
|
148
|
+
): ReturnType<typeof update> {
|
|
149
|
+
return update(
|
|
150
|
+
userAccountTable,
|
|
151
|
+
{
|
|
152
|
+
// Persist the new display name and bump the timestamp along with it.
|
|
153
|
+
display_name: payload.displayName,
|
|
154
|
+
updated_at: new Date(),
|
|
155
|
+
},
|
|
156
|
+
key,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Builds a delete statement for the specified user account key.
|
|
162
|
+
* @param {Key} key Identifies the row to remove.
|
|
163
|
+
* @returns {ReturnType<typeof remove>} A writer delete statement for the matching user account.
|
|
164
|
+
*/
|
|
165
|
+
export function buildRemoveUserAccount(key: Key): ReturnType<typeof remove> {
|
|
166
|
+
return remove(userAccountTable, key);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Column sets that writer tests use to ensure only approved columns are touched.
|
|
171
|
+
* @property {readonly string[]} insertColumns Columns allowed for new account inserts.
|
|
172
|
+
* @property {readonly string[]} updateColumns Columns permitted during updates.
|
|
173
|
+
* @property {readonly string[]} immutableColumns Columns that must remain unchanged.
|
|
174
|
+
*/
|
|
175
|
+
export const userAccountWriterColumnSets = {
|
|
176
|
+
insertColumns: ['username', 'email', 'display_name'] as const,
|
|
177
|
+
updateColumns: ['display_name', 'updated_at'] as const,
|
|
178
|
+
immutableColumns: ['user_account_id', 'created_at'] as const,
|
|
179
|
+
};
|
|
@@ -8,9 +8,21 @@ Tests are a shared workspace for humans and AI, but must respect ZTD-specific co
|
|
|
8
8
|
## Default execution mode (important)
|
|
9
9
|
|
|
10
10
|
- The default execution mode for tests is **ZTD**.
|
|
11
|
+
- ZTD tests run through the new `createTestkitProvider` helper, which keeps a single
|
|
12
|
+
shared backend connection per worker and wraps each scenario in a rollback
|
|
13
|
+
transaction by default.
|
|
11
14
|
- Do not switch to Traditional mode unless explicitly instructed by a human.
|
|
12
15
|
- Do not set `ZTD_EXECUTION_MODE=traditional` or pass `{ mode: "traditional" }` to `createTestkitClient()` without explicit instruction.
|
|
16
|
+
- When you need session-level changes (temporary tables, `SET` commands, etc.), opt
|
|
17
|
+
into per-test isolation via `provider.perTest()` or by passing `{ strategy:
|
|
18
|
+
'perTest' }` to `provider.withRepositoryFixture`. Shared mode otherwise keeps the
|
|
19
|
+
default behavior fast and deterministic.
|
|
13
20
|
- If instructions are ambiguous, assume ZTD mode and proceed.
|
|
21
|
+
- Tests MUST NOT mix ZTD mode and Traditional mode within the same test suite.
|
|
22
|
+
- Traditional mode is reserved for integration-style validation; it is not a convenience or performance workaround.
|
|
23
|
+
- Every repository/test change must be followed by `pnpm --filter <package> test` (substitute the real template package name).
|
|
24
|
+
- `tests/support/global-setup.ts` already uses `@testcontainers/postgresql`, so a disposable Postgres container spins up automatically whenever `DATABASE_URL` is absent.
|
|
25
|
+
- If the suite fails, resolve the failure and rerun until it succeeds before considering the change complete.
|
|
14
26
|
|
|
15
27
|
---
|
|
16
28
|
|
|
@@ -37,6 +49,8 @@ If a required type is missing:
|
|
|
37
49
|
- Export the correct type from `src/`.
|
|
38
50
|
|
|
39
51
|
Do not invent substitute models.
|
|
52
|
+
- Tests MUST treat `tests/generated/ztd-row-map.generated.ts` as the single source of truth for table-shaped rows.
|
|
53
|
+
- If a column exists in the database but not in the row map, the schema is considered outdated until regenerated.
|
|
40
54
|
|
|
41
55
|
---
|
|
42
56
|
|
|
@@ -58,6 +72,8 @@ Preferred patterns:
|
|
|
58
72
|
If behavior depends on transactions, isolation, or shared mutable state:
|
|
59
73
|
- That test does not belong in ZTD unit tests.
|
|
60
74
|
- Move it to an integration test and explicitly request Traditional mode.
|
|
75
|
+
- A single repository method call is the maximum scope of observation in ZTD tests.
|
|
76
|
+
- Tests that validate cross-call effects, workflows, or lifecycle transitions do not belong in ZTD tests.
|
|
61
77
|
|
|
62
78
|
---
|
|
63
79
|
|
|
@@ -67,23 +83,51 @@ If behavior depends on transactions, isolation, or shared mutable state:
|
|
|
67
83
|
- Keep fixtures minimal and intention-revealing.
|
|
68
84
|
- Do not add rows or columns unrelated to the test intent.
|
|
69
85
|
- Do not simulate application-side logic in fixtures.
|
|
86
|
+
- Fixtures must satisfy non-nullable columns and required constraints derived from DDL.
|
|
87
|
+
- Fixtures MUST NOT encode business rules, defaults, or derived values.
|
|
88
|
+
- Fixtures exist only to satisfy schema constraints and make SQL executable.
|
|
70
89
|
|
|
71
90
|
---
|
|
72
91
|
|
|
73
92
|
## Assertions (important)
|
|
74
93
|
|
|
75
94
|
- Assert only on relevant fields.
|
|
76
|
-
- Do not assert implicit ordering
|
|
95
|
+
- Do not assert implicit ordering unless the repository contract explicitly guarantees it
|
|
96
|
+
(e.g. the query includes a defined `ORDER BY`).
|
|
77
97
|
- Do not assert specific values of auto-generated IDs.
|
|
78
|
-
- Assert existence, type, or relative differences instead.
|
|
98
|
+
- Assert existence, type, cardinality, or relative differences instead.
|
|
79
99
|
|
|
80
100
|
---
|
|
81
101
|
|
|
82
|
-
## Repository boundaries
|
|
102
|
+
## Repository boundaries (important)
|
|
83
103
|
|
|
84
104
|
- Tests should verify observable behavior of repository methods.
|
|
85
105
|
- Do not duplicate SQL logic or business rules inside tests.
|
|
86
106
|
- Do not test internal helper functions or private implementation details.
|
|
107
|
+
- Tests must match the repository method contract exactly
|
|
108
|
+
(return type, nullability, and error behavior).
|
|
109
|
+
- Tests MUST NOT compensate for mapper or writer limitations by reimplementing logic in the test itself.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Test helper and resource lifecycle (important)
|
|
114
|
+
|
|
115
|
+
- Any test helper that creates a client, connection, or testkit instance
|
|
116
|
+
**must guarantee cleanup**.
|
|
117
|
+
- Always close resources using `try/finally` or a dedicated helper
|
|
118
|
+
(e.g. `withRepository`).
|
|
119
|
+
- Do not rely on test success paths to release resources.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Test file conventions (important)
|
|
124
|
+
|
|
125
|
+
- Do not assume Vitest globals are available.
|
|
126
|
+
- Explicitly import `describe`, `it`, and `expect` from `vitest`
|
|
127
|
+
unless the project explicitly documents global usage.
|
|
128
|
+
- Avoid implicit `any` in tests and helpers.
|
|
129
|
+
- Explicitly type fixtures and helper parameters
|
|
130
|
+
(e.g. `Parameters<typeof createTestkitClient>[0]`).
|
|
87
131
|
|
|
88
132
|
---
|
|
89
133
|
|
|
@@ -93,7 +137,9 @@ If behavior depends on transactions, isolation, or shared mutable state:
|
|
|
93
137
|
- However:
|
|
94
138
|
- Do not redefine models.
|
|
95
139
|
- Do not change schema assumptions.
|
|
96
|
-
|
|
140
|
+
- Do not edit `ztd/ddl`. `ztd/ddl` is the only authoritative `ztd` directory; do not create or assume additional `ztd` subdirectories without explicit instruction.
|
|
141
|
+
- The only authoritative source for tests is `ztd/ddl`.
|
|
142
|
+
- Tests may fail when `ztd/` definitions change; tests MUST be updated to reflect those changes, not the other way around.
|
|
97
143
|
|
|
98
144
|
---
|
|
99
145
|
|
|
@@ -105,10 +151,17 @@ If behavior depends on transactions, isolation, or shared mutable state:
|
|
|
105
151
|
|
|
106
152
|
---
|
|
107
153
|
|
|
154
|
+
## Writer metadata guardrail (template-specific)
|
|
155
|
+
|
|
156
|
+
- `tests/writer-constraints.test.ts` reads `userAccountWriterColumnSets` together with `tests/generated/ztd-row-map.generated.ts` to ensure every writer column actually exists on `public.user_account`.
|
|
157
|
+
- Run `npx ztd ztd-config` before executing the template tests so the generated row map reflects your current schema changes.
|
|
158
|
+
- When new columns appear in the writer helpers, adjust `userAccountWriterColumnSets`, rerun the row-map generator, and update the constraint test expectations.
|
|
159
|
+
- These tests exist to enforce column correctness at test time so writer helpers remain runtime-safe and schema-agnostic.
|
|
160
|
+
|
|
108
161
|
## Guiding principle
|
|
109
162
|
|
|
110
|
-
ZTD tests exist to validate **SQL semantics in isolation**.
|
|
111
|
-
They are not integration tests, migration tests, or transaction tests.
|
|
163
|
+
ZTD tests exist to validate **repository behavior derived from SQL semantics in isolation**.
|
|
164
|
+
They are not integration tests, migration tests, or transaction tests.
|
|
112
165
|
|
|
113
166
|
Prefer:
|
|
114
167
|
- Clear intent
|
|
@@ -3,7 +3,7 @@ import { PostgreSqlContainer } from '@testcontainers/postgresql';
|
|
|
3
3
|
/**
|
|
4
4
|
* Vitest global setup.
|
|
5
5
|
*
|
|
6
|
-
* ZTD tests are safe to run in parallel against a single Postgres instance because
|
|
6
|
+
* ZTD tests are safe to run in parallel against a single Postgres instance because testkit-postgres
|
|
7
7
|
* rewrites CRUD into fixture-backed SELECT queries (no physical tables are created/mutated).
|
|
8
8
|
*
|
|
9
9
|
* This setup starts exactly one disposable Postgres container when DATABASE_URL is not provided,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// ZTD testkit helper - AUTO GENERATED
|
|
2
|
-
// ztd-cli emits this file during project bootstrapping to wire pg
|
|
2
|
+
// ztd-cli emits this file during project bootstrapping to wire @rawsql-ts/adapter-node-pg adapters.
|
|
3
3
|
// Regenerate via npx ztd init (choose overwrite when prompted); avoid manual edits.
|
|
4
4
|
|
|
5
5
|
import { existsSync, promises as fsPromises } from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { Client, types } from 'pg';
|
|
8
8
|
import type { ClientConfig, QueryResultRow } from 'pg';
|
|
9
|
-
import { createPgTestkitClient } from '@rawsql-ts/pg
|
|
10
|
-
import type { PgQueryInput, PgQueryable } from '@rawsql-ts/pg
|
|
9
|
+
import { createPgTestkitClient } from '@rawsql-ts/adapter-node-pg';
|
|
10
|
+
import type { PgQueryInput, PgQueryable } from '@rawsql-ts/adapter-node-pg';
|
|
11
11
|
import type { TableFixture } from '@rawsql-ts/testkit-core';
|
|
12
12
|
|
|
13
13
|
const ddlDirectories = [path.resolve(__dirname, '../../ztd/ddl')];
|
|
@@ -172,7 +172,7 @@ async function getPgQueryable(): Promise<PgQueryable> {
|
|
|
172
172
|
|
|
173
173
|
const client = await getPgClient();
|
|
174
174
|
|
|
175
|
-
// Wrap the pg.Client to expose only the subset needed by pg
|
|
175
|
+
// Wrap the pg.Client to expose only the subset needed by @rawsql-ts/adapter-node-pg.
|
|
176
176
|
const wrappedQueryable: PgQueryable = {
|
|
177
177
|
query: <T extends QueryResultRow>(textOrConfig: PgQueryInput, values?: unknown[]) =>
|
|
178
178
|
client.query<T>(textOrConfig as never, values),
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, expect, test, afterAll } from 'vitest';
|
|
2
|
+
import type { TableFixture, TestkitProvider } from '@rawsql-ts/testkit-core';
|
|
3
|
+
import { createTestkitProvider } from '@rawsql-ts/testkit-core';
|
|
4
|
+
import { createPgTestkitClient } from '@rawsql-ts/pg-testkit';
|
|
5
|
+
import { Pool } from 'pg';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {
|
|
8
|
+
tableFixture,
|
|
9
|
+
tableSchemas,
|
|
10
|
+
TestRowMap,
|
|
11
|
+
} from './generated/ztd-row-map.generated';
|
|
12
|
+
import { listUserProfiles } from '../src/repositories/user-accounts';
|
|
13
|
+
|
|
14
|
+
const ddlDirectories = [path.resolve(__dirname, '../ztd/ddl')];
|
|
15
|
+
const skipReason = 'DATABASE_URL is not configured';
|
|
16
|
+
const configuredDatabaseUrl = process.env.DATABASE_URL?.trim();
|
|
17
|
+
const suiteTitle = configuredDatabaseUrl
|
|
18
|
+
? 'user profile mapper'
|
|
19
|
+
: `user profile mapper (skipped: ${skipReason})`;
|
|
20
|
+
const describeUserProfile = configuredDatabaseUrl
|
|
21
|
+
? describe
|
|
22
|
+
: (describe.skip as typeof describe);
|
|
23
|
+
let pool: Pool | undefined;
|
|
24
|
+
let providerPromise: Promise<TestkitProvider> | undefined;
|
|
25
|
+
|
|
26
|
+
// Lazily initialize the test provider so missing DATABASE_URL values do not trigger side effects.
|
|
27
|
+
function getProvider(): Promise<TestkitProvider> {
|
|
28
|
+
if (providerPromise) {
|
|
29
|
+
return providerPromise;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const databaseUrl = process.env.DATABASE_URL?.trim();
|
|
33
|
+
if (!databaseUrl) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
'Cannot initialize the repository testkit provider without DATABASE_URL.',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pool = new Pool({ connectionString: databaseUrl });
|
|
40
|
+
const activePool = pool;
|
|
41
|
+
providerPromise = createTestkitProvider({
|
|
42
|
+
connectionFactory: () => activePool.connect(),
|
|
43
|
+
resourceFactory: async (connection, fixtures) =>
|
|
44
|
+
createPgTestkitClient({
|
|
45
|
+
connectionFactory: () => connection,
|
|
46
|
+
tableRows: fixtures,
|
|
47
|
+
ddl: { directories: ddlDirectories },
|
|
48
|
+
}),
|
|
49
|
+
releaseResource: async (client) => {
|
|
50
|
+
await client.close();
|
|
51
|
+
},
|
|
52
|
+
disposeConnection: async (connection) => {
|
|
53
|
+
if (typeof connection.release === 'function') {
|
|
54
|
+
connection.release();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (typeof connection.end === 'function') {
|
|
58
|
+
await connection.end();
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return providerPromise;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
afterAll(async () => {
|
|
67
|
+
// Close resources only when initialization actually happened.
|
|
68
|
+
if (!providerPromise) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const provider = await providerPromise;
|
|
73
|
+
await provider.close();
|
|
74
|
+
if (pool) {
|
|
75
|
+
await pool.end();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function buildUserAccounts(): TestRowMap['public.user_account'][] {
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
user_account_id: 1,
|
|
83
|
+
username: 'alpha',
|
|
84
|
+
email: 'alpha@example.com',
|
|
85
|
+
display_name: 'Alpha Tester',
|
|
86
|
+
created_at: '2025-12-01T08:00:00Z',
|
|
87
|
+
updated_at: '2025-12-01T09:00:00Z',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
user_account_id: 2,
|
|
91
|
+
username: 'bravo',
|
|
92
|
+
email: 'bravo@example.com',
|
|
93
|
+
display_name: 'Bravo Builder',
|
|
94
|
+
created_at: '2025-12-02T10:00:00Z',
|
|
95
|
+
updated_at: '2025-12-02T11:00:00Z',
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildUserProfiles(): TestRowMap['public.user_profile'][] {
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
profile_id: 101,
|
|
104
|
+
user_account_id: 1,
|
|
105
|
+
bio: 'Lead engineer and mapper advocate.',
|
|
106
|
+
website: 'https://example.com',
|
|
107
|
+
verified: true,
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildFixtures(): TableFixture[] {
|
|
113
|
+
return [
|
|
114
|
+
tableFixture(
|
|
115
|
+
'public.user_account',
|
|
116
|
+
buildUserAccounts(),
|
|
117
|
+
tableSchemas['public.user_account'],
|
|
118
|
+
),
|
|
119
|
+
tableFixture(
|
|
120
|
+
'public.user_profile',
|
|
121
|
+
buildUserProfiles(),
|
|
122
|
+
tableSchemas['public.user_profile'],
|
|
123
|
+
),
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
describeUserProfile(suiteTitle, () => {
|
|
128
|
+
test('listUserProfiles hydrates optional profiles', async () => {
|
|
129
|
+
const fixtures = buildFixtures();
|
|
130
|
+
const provider = await getProvider();
|
|
131
|
+
await provider.withRepositoryFixture(fixtures, async (client) => {
|
|
132
|
+
const result = await listUserProfiles(client);
|
|
133
|
+
expect(result).toEqual([
|
|
134
|
+
{
|
|
135
|
+
userAccountId: 1,
|
|
136
|
+
username: 'alpha',
|
|
137
|
+
email: 'alpha@example.com',
|
|
138
|
+
displayName: 'Alpha Tester',
|
|
139
|
+
createdAt: new Date('2025-12-01T08:00:00Z'),
|
|
140
|
+
updatedAt: new Date('2025-12-01T09:00:00Z'),
|
|
141
|
+
profile: {
|
|
142
|
+
profileId: 101,
|
|
143
|
+
userAccountId: 1,
|
|
144
|
+
bio: 'Lead engineer and mapper advocate.',
|
|
145
|
+
website: 'https://example.com',
|
|
146
|
+
verified: true,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
userAccountId: 2,
|
|
151
|
+
username: 'bravo',
|
|
152
|
+
email: 'bravo@example.com',
|
|
153
|
+
displayName: 'Bravo Builder',
|
|
154
|
+
createdAt: new Date('2025-12-02T10:00:00Z'),
|
|
155
|
+
updatedAt: new Date('2025-12-02T11:00:00Z'),
|
|
156
|
+
profile: undefined,
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { tableSchemas } from './generated/ztd-row-map.generated';
|
|
3
|
+
import { userAccountWriterColumnSets } from '../src/repositories/user-accounts';
|
|
4
|
+
|
|
5
|
+
const userColumns = new Set(
|
|
6
|
+
Object.keys(tableSchemas['public.user_account'].columns),
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
describe('user_account writer columns', () => {
|
|
10
|
+
test('insert columns must exist on the canonical table', () => {
|
|
11
|
+
const { insertColumns } = userAccountWriterColumnSets;
|
|
12
|
+
const missing = insertColumns.filter((column) => !userColumns.has(column));
|
|
13
|
+
expect(missing, `Missing columns: ${missing.join(', ')}`).toEqual([]);
|
|
14
|
+
expect(insertColumns).toEqual(
|
|
15
|
+
expect.arrayContaining(['username', 'email', 'display_name']),
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('writer column sets align with the canonical table', () => {
|
|
20
|
+
const { updateColumns, immutableColumns } = userAccountWriterColumnSets;
|
|
21
|
+
const missingUpdates = updateColumns.filter((column) => !userColumns.has(column));
|
|
22
|
+
expect(missingUpdates, `Missing update columns: ${missingUpdates.join(', ')}`).toEqual([]);
|
|
23
|
+
const missingImmutables = immutableColumns.filter((column) => !userColumns.has(column));
|
|
24
|
+
expect(missingImmutables, `Missing immutable columns: ${missingImmutables.join(', ')}`).toEqual([]);
|
|
25
|
+
immutableColumns.forEach((column) => {
|
|
26
|
+
expect(
|
|
27
|
+
updateColumns,
|
|
28
|
+
`Immutable column "${column}" should never appear in updateColumns`,
|
|
29
|
+
).not.toContain(column);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
package/templates/tsconfig.json
CHANGED