@zenstackhq/testtools 3.0.0-beta.1 → 3.0.0-beta.10

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/dist/index.cjs CHANGED
@@ -31,18 +31,38 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var src_exports = {};
33
33
  __export(src_exports, {
34
+ createPolicyTestClient: () => createPolicyTestClient,
35
+ createTestClient: () => createTestClient,
34
36
  createTestProject: () => createTestProject,
35
37
  generateTsSchema: () => generateTsSchema,
36
38
  generateTsSchemaFromFile: () => generateTsSchemaFromFile,
37
- generateTsSchemaInPlace: () => generateTsSchemaInPlace
39
+ generateTsSchemaInPlace: () => generateTsSchemaInPlace,
40
+ getTestDbProvider: () => getTestDbProvider,
41
+ loadSchema: () => loadSchema,
42
+ loadSchemaWithError: () => loadSchemaWithError,
43
+ testLogger: () => testLogger
38
44
  });
39
45
  module.exports = __toCommonJS(src_exports);
40
46
 
47
+ // src/client.ts
48
+ var import_common_helpers2 = require("@zenstackhq/common-helpers");
49
+ var import_plugin_policy = require("@zenstackhq/plugin-policy");
50
+ var import_runtime4 = require("@zenstackhq/runtime");
51
+ var import_sdk2 = require("@zenstackhq/sdk");
52
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
53
+ var import_kysely = require("kysely");
54
+ var import_node_child_process2 = require("child_process");
55
+ var import_node_crypto2 = require("crypto");
56
+ var import_node_fs3 = __toESM(require("fs"), 1);
57
+ var import_node_path3 = __toESM(require("path"), 1);
58
+ var import_pg = require("pg");
59
+ var import_vitest2 = require("vitest");
60
+
41
61
  // src/project.ts
42
62
  var import_node_fs = __toESM(require("fs"), 1);
43
63
  var import_node_path = __toESM(require("path"), 1);
44
64
  var import_tmp = __toESM(require("tmp"), 1);
45
- function createTestProject() {
65
+ function createTestProject(zmodelContent) {
46
66
  const { name: workDir } = import_tmp.default.dirSync({
47
67
  unsafeCleanup: true
48
68
  });
@@ -82,18 +102,35 @@ function createTestProject() {
82
102
  "**/*.ts"
83
103
  ]
84
104
  }, null, 4));
105
+ if (zmodelContent) {
106
+ import_node_fs.default.writeFileSync(import_node_path.default.join(workDir, "schema.zmodel"), zmodelContent);
107
+ }
85
108
  return workDir;
86
109
  }
87
110
  __name(createTestProject, "createTestProject");
88
111
 
89
112
  // src/schema.ts
90
- var import_language = require("@zenstackhq/language");
113
+ var import_common_helpers = require("@zenstackhq/common-helpers");
91
114
  var import_sdk = require("@zenstackhq/sdk");
92
- var import_glob = require("glob");
93
115
  var import_node_child_process = require("child_process");
116
+ var import_node_crypto = __toESM(require("crypto"), 1);
94
117
  var import_node_fs2 = __toESM(require("fs"), 1);
118
+ var import_node_os = __toESM(require("os"), 1);
95
119
  var import_node_path2 = __toESM(require("path"), 1);
96
120
  var import_ts_pattern = require("ts-pattern");
121
+ var import_vitest = require("vitest");
122
+
123
+ // src/utils.ts
124
+ var import_language = require("@zenstackhq/language");
125
+ function loadDocumentWithPlugins(filePath) {
126
+ const pluginModelFiles = [
127
+ require.resolve("@zenstackhq/plugin-policy/plugin.zmodel")
128
+ ];
129
+ return (0, import_language.loadDocument)(filePath, pluginModelFiles);
130
+ }
131
+ __name(loadDocumentWithPlugins, "loadDocumentWithPlugins");
132
+
133
+ // src/schema.ts
97
134
  function makePrelude(provider, dbUrl) {
98
135
  return (0, import_ts_pattern.match)(provider).with("sqlite", () => {
99
136
  return `
@@ -119,8 +156,7 @@ async function generateTsSchema(schemaText, provider = "sqlite", dbUrl, extraSou
119
156
  import_node_fs2.default.writeFileSync(zmodelPath, `${noPrelude ? "" : makePrelude(provider, dbUrl)}
120
157
 
121
158
  ${schemaText}`);
122
- const pluginModelFiles = import_glob.glob.sync(import_node_path2.default.resolve(__dirname, "../../runtime/src/plugins/**/plugin.zmodel"));
123
- const result = await (0, import_language.loadDocument)(zmodelPath, pluginModelFiles);
159
+ const result = await loadDocumentWithPlugins(zmodelPath);
124
160
  if (!result.success) {
125
161
  throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`);
126
162
  }
@@ -128,14 +164,17 @@ ${schemaText}`);
128
164
  await generator.generate(result.model, workDir);
129
165
  if (extraSourceFiles) {
130
166
  for (const [fileName, content] of Object.entries(extraSourceFiles)) {
131
- const filePath = import_node_path2.default.resolve(workDir, `${fileName}.ts`);
167
+ const filePath = import_node_path2.default.resolve(workDir, !fileName.endsWith(".ts") ? `${fileName}.ts` : fileName);
132
168
  import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(filePath), {
133
169
  recursive: true
134
170
  });
135
171
  import_node_fs2.default.writeFileSync(filePath, content);
136
172
  }
137
173
  }
138
- return compileAndLoad(workDir);
174
+ return {
175
+ ...await compileAndLoad(workDir),
176
+ model: result.model
177
+ };
139
178
  }
140
179
  __name(generateTsSchema, "generateTsSchema");
141
180
  async function compileAndLoad(workDir) {
@@ -157,8 +196,7 @@ function generateTsSchemaFromFile(filePath) {
157
196
  __name(generateTsSchemaFromFile, "generateTsSchemaFromFile");
158
197
  async function generateTsSchemaInPlace(schemaPath) {
159
198
  const workDir = import_node_path2.default.dirname(schemaPath);
160
- const pluginModelFiles = import_glob.glob.sync(import_node_path2.default.resolve(__dirname, "../../runtime/src/plugins/**/plugin.zmodel"));
161
- const result = await (0, import_language.loadDocument)(schemaPath, pluginModelFiles);
199
+ const result = await loadDocumentWithPlugins(schemaPath);
162
200
  if (!result.success) {
163
201
  throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);
164
202
  }
@@ -167,11 +205,343 @@ async function generateTsSchemaInPlace(schemaPath) {
167
205
  return compileAndLoad(workDir);
168
206
  }
169
207
  __name(generateTsSchemaInPlace, "generateTsSchemaInPlace");
208
+ async function loadSchema(schema, additionalSchemas) {
209
+ if (!schema.includes("datasource ")) {
210
+ schema = `${makePrelude("sqlite")}
211
+
212
+ ${schema}`;
213
+ }
214
+ const tempDir = import_node_fs2.default.mkdtempSync(import_node_path2.default.join(import_node_os.default.tmpdir(), "zenstack-schema"));
215
+ const tempFile = import_node_path2.default.join(tempDir, `schema.zmodel`);
216
+ import_node_fs2.default.writeFileSync(tempFile, schema);
217
+ if (additionalSchemas) {
218
+ for (const [fileName, content] of Object.entries(additionalSchemas)) {
219
+ let name = fileName;
220
+ if (!name.endsWith(".zmodel")) {
221
+ name += ".zmodel";
222
+ }
223
+ const filePath = import_node_path2.default.join(tempDir, name);
224
+ import_node_fs2.default.writeFileSync(filePath, content);
225
+ }
226
+ }
227
+ const r = await loadDocumentWithPlugins(tempFile);
228
+ (0, import_vitest.expect)(r).toSatisfy((r2) => r2.success, `Failed to load schema: ${r.errors?.map((e) => e.toString()).join(", ")}`);
229
+ (0, import_common_helpers.invariant)(r.success);
230
+ return r.model;
231
+ }
232
+ __name(loadSchema, "loadSchema");
233
+ async function loadSchemaWithError(schema, error) {
234
+ if (!schema.includes("datasource ")) {
235
+ schema = `${makePrelude("sqlite")}
236
+
237
+ ${schema}`;
238
+ }
239
+ const tempFile = import_node_path2.default.join(import_node_os.default.tmpdir(), `zenstack-schema-${import_node_crypto.default.randomUUID()}.zmodel`);
240
+ import_node_fs2.default.writeFileSync(tempFile, schema);
241
+ const r = await loadDocumentWithPlugins(tempFile);
242
+ (0, import_vitest.expect)(r.success).toBe(false);
243
+ (0, import_common_helpers.invariant)(!r.success);
244
+ if (typeof error === "string") {
245
+ (0, import_vitest.expect)(r).toSatisfy((r2) => r2.errors.some((e) => e.toString().toLowerCase().includes(error.toLowerCase())), `Expected error message to include "${error}" but got: ${r.errors.map((e) => e.toString()).join(", ")}`);
246
+ } else {
247
+ (0, import_vitest.expect)(r).toSatisfy((r2) => r2.errors.some((e) => error.test(e)), `Expected error message to match "${error}" but got: ${r.errors.map((e) => e.toString()).join(", ")}`);
248
+ }
249
+ }
250
+ __name(loadSchemaWithError, "loadSchemaWithError");
251
+
252
+ // src/client.ts
253
+ function getTestDbProvider() {
254
+ const val = process.env["TEST_DB_PROVIDER"] ?? "sqlite";
255
+ if (![
256
+ "sqlite",
257
+ "postgresql"
258
+ ].includes(val)) {
259
+ throw new Error(`Invalid TEST_DB_PROVIDER value: ${val}`);
260
+ }
261
+ return val;
262
+ }
263
+ __name(getTestDbProvider, "getTestDbProvider");
264
+ var TEST_PG_CONFIG = {
265
+ host: process.env["TEST_PG_HOST"] ?? "localhost",
266
+ port: process.env["TEST_PG_PORT"] ? parseInt(process.env["TEST_PG_PORT"]) : 5432,
267
+ user: process.env["TEST_PG_USER"] ?? "postgres",
268
+ password: process.env["TEST_PG_PASSWORD"] ?? "postgres"
269
+ };
270
+ async function createTestClient(schema, options, schemaFile) {
271
+ let workDir = options?.workDir;
272
+ let _schema;
273
+ const provider = options?.provider ?? getTestDbProvider() ?? "sqlite";
274
+ const dbName = options?.dbName ?? getTestDbName(provider);
275
+ const dbUrl = provider === "sqlite" ? `file:${dbName}` : `postgres://${TEST_PG_CONFIG.user}:${TEST_PG_CONFIG.password}@${TEST_PG_CONFIG.host}:${TEST_PG_CONFIG.port}/${dbName}`;
276
+ let model;
277
+ if (typeof schema === "string") {
278
+ const generated = await generateTsSchema(schema, provider, dbUrl, options?.extraSourceFiles);
279
+ workDir = generated.workDir;
280
+ model = generated.model;
281
+ _schema = {
282
+ ...generated.schema,
283
+ provider: {
284
+ type: provider
285
+ }
286
+ };
287
+ } else {
288
+ _schema = {
289
+ ...schema,
290
+ provider: {
291
+ type: provider
292
+ }
293
+ };
294
+ workDir ??= createTestProject();
295
+ if (schemaFile) {
296
+ let schemaContent = import_node_fs3.default.readFileSync(schemaFile, "utf-8");
297
+ if (dbUrl) {
298
+ schemaContent = schemaContent.replace(/datasource\s+db\s*{[^}]*}/m, `datasource db {
299
+ provider = '${provider}'
300
+ url = '${dbUrl}'
301
+ }`);
302
+ }
303
+ import_node_fs3.default.writeFileSync(import_node_path3.default.join(workDir, "schema.zmodel"), schemaContent);
304
+ }
305
+ }
306
+ (0, import_common_helpers2.invariant)(workDir);
307
+ if (options?.debug) {
308
+ console.log(`Work directory: ${workDir}`);
309
+ }
310
+ const { plugins, ...rest } = options ?? {};
311
+ const _options = {
312
+ ...rest
313
+ };
314
+ if (options?.usePrismaPush) {
315
+ (0, import_common_helpers2.invariant)(typeof schema === "string" || schemaFile, "a schema file must be provided when using prisma db push");
316
+ if (!model) {
317
+ const r = await loadDocumentWithPlugins(import_node_path3.default.join(workDir, "schema.zmodel"));
318
+ if (!r.success) {
319
+ throw new Error(r.errors.join("\n"));
320
+ }
321
+ model = r.model;
322
+ }
323
+ const prismaSchema = new import_sdk2.PrismaSchemaGenerator(model);
324
+ const prismaSchemaText = await prismaSchema.generate();
325
+ import_node_fs3.default.writeFileSync(import_node_path3.default.resolve(workDir, "schema.prisma"), prismaSchemaText);
326
+ (0, import_node_child_process2.execSync)("npx prisma db push --schema ./schema.prisma --skip-generate --force-reset", {
327
+ cwd: workDir,
328
+ stdio: "ignore"
329
+ });
330
+ } else {
331
+ if (provider === "postgresql") {
332
+ (0, import_common_helpers2.invariant)(dbName, "dbName is required");
333
+ const pgClient = new import_pg.Client(TEST_PG_CONFIG);
334
+ await pgClient.connect();
335
+ await pgClient.query(`DROP DATABASE IF EXISTS "${dbName}"`);
336
+ await pgClient.query(`CREATE DATABASE "${dbName}"`);
337
+ await pgClient.end();
338
+ }
339
+ }
340
+ if (provider === "postgresql") {
341
+ _options.dialect = new import_kysely.PostgresDialect({
342
+ pool: new import_pg.Pool({
343
+ ...TEST_PG_CONFIG,
344
+ database: dbName
345
+ })
346
+ });
347
+ } else {
348
+ _options.dialect = new import_kysely.SqliteDialect({
349
+ database: new import_better_sqlite3.default(import_node_path3.default.join(workDir, dbName))
350
+ });
351
+ }
352
+ let client = new import_runtime4.ZenStackClient(_schema, _options);
353
+ if (!options?.usePrismaPush) {
354
+ await client.$pushSchema();
355
+ }
356
+ if (plugins) {
357
+ for (const plugin of plugins) {
358
+ client = client.$use(plugin);
359
+ }
360
+ }
361
+ return client;
362
+ }
363
+ __name(createTestClient, "createTestClient");
364
+ async function createPolicyTestClient(schema, options) {
365
+ return createTestClient(schema, {
366
+ ...options,
367
+ plugins: [
368
+ ...options?.plugins ?? [],
369
+ new import_plugin_policy.PolicyPlugin()
370
+ ]
371
+ });
372
+ }
373
+ __name(createPolicyTestClient, "createPolicyTestClient");
374
+ function testLogger(e) {
375
+ console.log(e.query.sql, e.query.parameters);
376
+ }
377
+ __name(testLogger, "testLogger");
378
+ function getTestDbName(provider) {
379
+ if (provider === "sqlite") {
380
+ return "./test.db";
381
+ }
382
+ const testName = import_vitest2.expect.getState().currentTestName;
383
+ const testPath = import_vitest2.expect.getState().testPath ?? "";
384
+ (0, import_common_helpers2.invariant)(testName);
385
+ const digest = (0, import_node_crypto2.createHash)("md5").update(testName + testPath).digest("hex");
386
+ return "test_" + testName.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").substring(0, 30) + digest.slice(0, 6);
387
+ }
388
+ __name(getTestDbName, "getTestDbName");
389
+
390
+ // src/vitest-ext.ts
391
+ var import_runtime6 = require("@zenstackhq/runtime");
392
+ var import_vitest3 = require("vitest");
393
+ function isPromise(value) {
394
+ return typeof value.then === "function" && typeof value.catch === "function";
395
+ }
396
+ __name(isPromise, "isPromise");
397
+ function expectError(err, errorType) {
398
+ if (err instanceof errorType) {
399
+ return {
400
+ message: /* @__PURE__ */ __name(() => "", "message"),
401
+ pass: true
402
+ };
403
+ } else {
404
+ return {
405
+ message: /* @__PURE__ */ __name(() => `expected ${errorType}, got ${err}`, "message"),
406
+ pass: false
407
+ };
408
+ }
409
+ }
410
+ __name(expectError, "expectError");
411
+ function expectErrorMessages(expectedMessages, message) {
412
+ for (const m of expectedMessages) {
413
+ if (!message.includes(m)) {
414
+ return {
415
+ message: /* @__PURE__ */ __name(() => `expected message not found in error: ${m}, got message: ${message}`, "message"),
416
+ pass: false
417
+ };
418
+ }
419
+ }
420
+ return void 0;
421
+ }
422
+ __name(expectErrorMessages, "expectErrorMessages");
423
+ import_vitest3.expect.extend({
424
+ async toResolveTruthy(received) {
425
+ if (!isPromise(received)) {
426
+ return {
427
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
428
+ pass: false
429
+ };
430
+ }
431
+ const r = await received;
432
+ return {
433
+ pass: !!r,
434
+ message: /* @__PURE__ */ __name(() => `Expected promise to resolve to a truthy value, but got ${r}`, "message")
435
+ };
436
+ },
437
+ async toResolveFalsy(received) {
438
+ if (!isPromise(received)) {
439
+ return {
440
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
441
+ pass: false
442
+ };
443
+ }
444
+ const r = await received;
445
+ return {
446
+ pass: !r,
447
+ message: /* @__PURE__ */ __name(() => `Expected promise to resolve to a falsy value, but got ${r}`, "message")
448
+ };
449
+ },
450
+ async toResolveNull(received) {
451
+ if (!isPromise(received)) {
452
+ return {
453
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
454
+ pass: false
455
+ };
456
+ }
457
+ const r = await received;
458
+ return {
459
+ pass: r === null,
460
+ message: /* @__PURE__ */ __name(() => `Expected promise to resolve to a null value, but got ${r}`, "message")
461
+ };
462
+ },
463
+ async toResolveWithLength(received, length) {
464
+ const r = await received;
465
+ return {
466
+ pass: Array.isArray(r) && r.length === length,
467
+ message: /* @__PURE__ */ __name(() => `Expected promise to resolve with an array with length ${length}, but got ${r}`, "message")
468
+ };
469
+ },
470
+ async toBeRejectedNotFound(received) {
471
+ if (!isPromise(received)) {
472
+ return {
473
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
474
+ pass: false
475
+ };
476
+ }
477
+ try {
478
+ await received;
479
+ } catch (err) {
480
+ return expectError(err, import_runtime6.NotFoundError);
481
+ }
482
+ return {
483
+ message: /* @__PURE__ */ __name(() => `expected NotFoundError, got no error`, "message"),
484
+ pass: false
485
+ };
486
+ },
487
+ async toBeRejectedByPolicy(received, expectedMessages) {
488
+ if (!isPromise(received)) {
489
+ return {
490
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
491
+ pass: false
492
+ };
493
+ }
494
+ try {
495
+ await received;
496
+ } catch (err) {
497
+ if (expectedMessages && err instanceof import_runtime6.RejectedByPolicyError) {
498
+ const r = expectErrorMessages(expectedMessages, err.message || "");
499
+ if (r) {
500
+ return r;
501
+ }
502
+ }
503
+ return expectError(err, import_runtime6.RejectedByPolicyError);
504
+ }
505
+ return {
506
+ message: /* @__PURE__ */ __name(() => `expected PolicyError, got no error`, "message"),
507
+ pass: false
508
+ };
509
+ },
510
+ async toBeRejectedByValidation(received, expectedMessages) {
511
+ if (!isPromise(received)) {
512
+ return {
513
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
514
+ pass: false
515
+ };
516
+ }
517
+ try {
518
+ await received;
519
+ } catch (err) {
520
+ if (expectedMessages && err instanceof import_runtime6.InputValidationError) {
521
+ const r = expectErrorMessages(expectedMessages, err.message || "");
522
+ if (r) {
523
+ return r;
524
+ }
525
+ }
526
+ return expectError(err, import_runtime6.InputValidationError);
527
+ }
528
+ return {
529
+ message: /* @__PURE__ */ __name(() => `expected InputValidationError, got no error`, "message"),
530
+ pass: false
531
+ };
532
+ }
533
+ });
170
534
  // Annotate the CommonJS export names for ESM import in node:
171
535
  0 && (module.exports = {
536
+ createPolicyTestClient,
537
+ createTestClient,
172
538
  createTestProject,
173
539
  generateTsSchema,
174
540
  generateTsSchemaFromFile,
175
- generateTsSchemaInPlace
541
+ generateTsSchemaInPlace,
542
+ getTestDbProvider,
543
+ loadSchema,
544
+ loadSchemaWithError,
545
+ testLogger
176
546
  });
177
547
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/project.ts","../src/schema.ts"],"sourcesContent":["export * from './project';\nexport * from './schema';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport tmp from 'tmp';\n\nexport function createTestProject() {\n const { name: workDir } = tmp.dirSync({ unsafeCleanup: true });\n\n fs.mkdirSync(path.join(workDir, 'node_modules'));\n\n // symlink all entries from \"node_modules\"\n const nodeModules = fs.readdirSync(path.join(__dirname, '../node_modules'));\n for (const entry of nodeModules) {\n if (entry.startsWith('@zenstackhq')) {\n continue;\n }\n fs.symlinkSync(\n path.join(__dirname, '../node_modules', entry),\n path.join(workDir, 'node_modules', entry),\n 'dir',\n );\n }\n\n // in addition, symlink zenstack packages\n const zenstackPackages = ['language', 'sdk', 'runtime', 'cli'];\n fs.mkdirSync(path.join(workDir, 'node_modules/@zenstackhq'));\n for (const pkg of zenstackPackages) {\n fs.symlinkSync(\n path.join(__dirname, `../../${pkg}`),\n path.join(workDir, `node_modules/@zenstackhq/${pkg}`),\n 'dir',\n );\n }\n\n fs.writeFileSync(\n path.join(workDir, 'package.json'),\n JSON.stringify(\n {\n name: 'test',\n version: '1.0.0',\n type: 'module',\n },\n null,\n 4,\n ),\n );\n\n fs.writeFileSync(\n path.join(workDir, 'tsconfig.json'),\n JSON.stringify(\n {\n compilerOptions: {\n module: 'ESNext',\n target: 'ESNext',\n moduleResolution: 'Bundler',\n esModuleInterop: true,\n skipLibCheck: true,\n strict: true,\n },\n include: ['**/*.ts'],\n },\n null,\n 4,\n ),\n );\n\n return workDir;\n}\n","import { loadDocument } from '@zenstackhq/language';\nimport { TsSchemaGenerator } from '@zenstackhq/sdk';\nimport type { SchemaDef } from '@zenstackhq/sdk/schema';\nimport { glob } from 'glob';\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { match } from 'ts-pattern';\nimport { createTestProject } from './project';\n\nfunction makePrelude(provider: 'sqlite' | 'postgresql', dbUrl?: string) {\n return match(provider)\n .with('sqlite', () => {\n return `\ndatasource db {\n provider = 'sqlite'\n url = '${dbUrl ?? 'file:./test.db'}'\n}\n`;\n })\n .with('postgresql', () => {\n return `\ndatasource db {\n provider = 'postgresql'\n url = '${dbUrl ?? 'postgres://postgres:postgres@localhost:5432/db'}'\n}\n`;\n })\n .exhaustive();\n}\n\nexport async function generateTsSchema(\n schemaText: string,\n provider: 'sqlite' | 'postgresql' = 'sqlite',\n dbUrl?: string,\n extraSourceFiles?: Record<string, string>,\n) {\n const workDir = createTestProject();\n\n const zmodelPath = path.join(workDir, 'schema.zmodel');\n const noPrelude = schemaText.includes('datasource ');\n fs.writeFileSync(zmodelPath, `${noPrelude ? '' : makePrelude(provider, dbUrl)}\\n\\n${schemaText}`);\n\n const pluginModelFiles = glob.sync(path.resolve(__dirname, '../../runtime/src/plugins/**/plugin.zmodel'));\n const result = await loadDocument(zmodelPath, pluginModelFiles);\n if (!result.success) {\n throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`);\n }\n\n const generator = new TsSchemaGenerator();\n await generator.generate(result.model, workDir);\n\n if (extraSourceFiles) {\n for (const [fileName, content] of Object.entries(extraSourceFiles)) {\n const filePath = path.resolve(workDir, `${fileName}.ts`);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, content);\n }\n }\n\n // compile the generated TS schema\n return compileAndLoad(workDir);\n}\n\nasync function compileAndLoad(workDir: string) {\n execSync('npx tsc', {\n cwd: workDir,\n stdio: 'inherit',\n });\n\n // load the schema module\n const module = await import(path.join(workDir, 'schema.js'));\n return { workDir, schema: module.schema as SchemaDef };\n}\n\nexport function generateTsSchemaFromFile(filePath: string) {\n const schemaText = fs.readFileSync(filePath, 'utf8');\n return generateTsSchema(schemaText);\n}\n\nexport async function generateTsSchemaInPlace(schemaPath: string) {\n const workDir = path.dirname(schemaPath);\n const pluginModelFiles = glob.sync(path.resolve(__dirname, '../../runtime/src/plugins/**/plugin.zmodel'));\n const result = await loadDocument(schemaPath, pluginModelFiles);\n if (!result.success) {\n throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);\n }\n const generator = new TsSchemaGenerator();\n await generator.generate(result.model, workDir);\n return compileAndLoad(workDir);\n}\n"],"mappings":"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","names":["createTestProject","name","workDir","tmp","dirSync","unsafeCleanup","fs","mkdirSync","path","join","nodeModules","readdirSync","__dirname","entry","startsWith","symlinkSync","zenstackPackages","pkg","writeFileSync","JSON","stringify","version","type","compilerOptions","module","target","moduleResolution","esModuleInterop","skipLibCheck","strict","include","import_node_fs","import_node_path","makePrelude","provider","dbUrl","match","with","exhaustive","generateTsSchema","schemaText","extraSourceFiles","workDir","createTestProject","zmodelPath","path","join","noPrelude","includes","fs","writeFileSync","pluginModelFiles","glob","sync","resolve","__dirname","result","loadDocument","success","Error","errors","generator","TsSchemaGenerator","generate","model","fileName","content","Object","entries","filePath","mkdirSync","dirname","recursive","compileAndLoad","execSync","cwd","stdio","module","schema","generateTsSchemaFromFile","readFileSync","generateTsSchemaInPlace","schemaPath"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/project.ts","../src/schema.ts","../src/utils.ts","../src/vitest-ext.ts"],"sourcesContent":["export * from './client';\nexport * from './project';\nexport * from './schema';\nexport * from './vitest-ext';\n","import { invariant } from '@zenstackhq/common-helpers';\nimport type { Model } from '@zenstackhq/language/ast';\nimport { PolicyPlugin } from '@zenstackhq/plugin-policy';\nimport { ZenStackClient, type ClientContract, type ClientOptions } from '@zenstackhq/runtime';\nimport type { SchemaDef } from '@zenstackhq/runtime/schema';\nimport { PrismaSchemaGenerator } from '@zenstackhq/sdk';\nimport SQLite from 'better-sqlite3';\nimport { PostgresDialect, SqliteDialect, type LogEvent } from 'kysely';\nimport { execSync } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { Client as PGClient, Pool } from 'pg';\nimport { expect } from 'vitest';\nimport { createTestProject } from './project';\nimport { generateTsSchema } from './schema';\nimport { loadDocumentWithPlugins } from './utils';\n\nexport function getTestDbProvider() {\n const val = process.env['TEST_DB_PROVIDER'] ?? 'sqlite';\n if (!['sqlite', 'postgresql'].includes(val!)) {\n throw new Error(`Invalid TEST_DB_PROVIDER value: ${val}`);\n }\n return val as 'sqlite' | 'postgresql';\n}\n\nconst TEST_PG_CONFIG = {\n host: process.env['TEST_PG_HOST'] ?? 'localhost',\n port: process.env['TEST_PG_PORT'] ? parseInt(process.env['TEST_PG_PORT']) : 5432,\n user: process.env['TEST_PG_USER'] ?? 'postgres',\n password: process.env['TEST_PG_PASSWORD'] ?? 'postgres',\n};\n\nexport type CreateTestClientOptions<Schema extends SchemaDef> = Omit<ClientOptions<Schema>, 'dialect'> & {\n provider?: 'sqlite' | 'postgresql';\n dbName?: string;\n usePrismaPush?: boolean;\n extraSourceFiles?: Record<string, string>;\n workDir?: string;\n debug?: boolean;\n};\n\nexport async function createTestClient<Schema extends SchemaDef>(\n schema: Schema,\n options?: CreateTestClientOptions<Schema>,\n schemaFile?: string,\n): Promise<ClientContract<Schema>>;\nexport async function createTestClient<Schema extends SchemaDef>(\n schema: string,\n options?: CreateTestClientOptions<Schema>,\n): Promise<any>;\nexport async function createTestClient<Schema extends SchemaDef>(\n schema: Schema | string,\n options?: CreateTestClientOptions<Schema>,\n schemaFile?: string,\n): Promise<any> {\n let workDir = options?.workDir;\n let _schema: Schema;\n const provider = options?.provider ?? getTestDbProvider() ?? 'sqlite';\n\n const dbName = options?.dbName ?? getTestDbName(provider);\n\n const dbUrl =\n provider === 'sqlite'\n ? `file:${dbName}`\n : `postgres://${TEST_PG_CONFIG.user}:${TEST_PG_CONFIG.password}@${TEST_PG_CONFIG.host}:${TEST_PG_CONFIG.port}/${dbName}`;\n\n let model: Model | undefined;\n\n if (typeof schema === 'string') {\n const generated = await generateTsSchema(schema, provider, dbUrl, options?.extraSourceFiles);\n workDir = generated.workDir;\n model = generated.model;\n // replace schema's provider\n _schema = {\n ...generated.schema,\n provider: {\n type: provider,\n },\n } as Schema;\n } else {\n // replace schema's provider\n _schema = {\n ...schema,\n provider: {\n type: provider,\n },\n };\n workDir ??= createTestProject();\n if (schemaFile) {\n let schemaContent = fs.readFileSync(schemaFile, 'utf-8');\n if (dbUrl) {\n // replace `datasource db { }` section\n schemaContent = schemaContent.replace(\n /datasource\\s+db\\s*{[^}]*}/m,\n `datasource db {\n provider = '${provider}'\n url = '${dbUrl}'\n}`,\n );\n }\n fs.writeFileSync(path.join(workDir, 'schema.zmodel'), schemaContent);\n }\n }\n\n invariant(workDir);\n if (options?.debug) {\n console.log(`Work directory: ${workDir}`);\n }\n\n const { plugins, ...rest } = options ?? {};\n const _options: ClientOptions<Schema> = {\n ...rest,\n } as ClientOptions<Schema>;\n\n if (options?.usePrismaPush) {\n invariant(typeof schema === 'string' || schemaFile, 'a schema file must be provided when using prisma db push');\n if (!model) {\n const r = await loadDocumentWithPlugins(path.join(workDir, 'schema.zmodel'));\n if (!r.success) {\n throw new Error(r.errors.join('\\n'));\n }\n model = r.model;\n }\n const prismaSchema = new PrismaSchemaGenerator(model);\n const prismaSchemaText = await prismaSchema.generate();\n fs.writeFileSync(path.resolve(workDir!, 'schema.prisma'), prismaSchemaText);\n execSync('npx prisma db push --schema ./schema.prisma --skip-generate --force-reset', {\n cwd: workDir,\n stdio: 'ignore',\n });\n } else {\n if (provider === 'postgresql') {\n invariant(dbName, 'dbName is required');\n const pgClient = new PGClient(TEST_PG_CONFIG);\n await pgClient.connect();\n await pgClient.query(`DROP DATABASE IF EXISTS \"${dbName}\"`);\n await pgClient.query(`CREATE DATABASE \"${dbName}\"`);\n await pgClient.end();\n }\n }\n\n if (provider === 'postgresql') {\n _options.dialect = new PostgresDialect({\n pool: new Pool({\n ...TEST_PG_CONFIG,\n database: dbName,\n }),\n });\n } else {\n _options.dialect = new SqliteDialect({\n database: new SQLite(path.join(workDir!, dbName)),\n });\n }\n\n let client = new ZenStackClient(_schema, _options);\n\n if (!options?.usePrismaPush) {\n await client.$pushSchema();\n }\n\n if (plugins) {\n for (const plugin of plugins) {\n client = client.$use(plugin);\n }\n }\n\n return client;\n}\n\nexport async function createPolicyTestClient<Schema extends SchemaDef>(\n schema: Schema,\n options?: CreateTestClientOptions<Schema>,\n): Promise<ClientContract<Schema>>;\nexport async function createPolicyTestClient<Schema extends SchemaDef>(\n schema: string,\n options?: CreateTestClientOptions<Schema>,\n): Promise<any>;\nexport async function createPolicyTestClient<Schema extends SchemaDef>(\n schema: Schema | string,\n options?: CreateTestClientOptions<Schema>,\n): Promise<any> {\n return createTestClient(\n schema as any,\n {\n ...options,\n plugins: [...(options?.plugins ?? []), new PolicyPlugin()],\n } as any,\n );\n}\n\nexport function testLogger(e: LogEvent) {\n console.log(e.query.sql, e.query.parameters);\n}\n\nfunction getTestDbName(provider: string) {\n if (provider === 'sqlite') {\n return './test.db';\n }\n const testName = expect.getState().currentTestName;\n const testPath = expect.getState().testPath ?? '';\n invariant(testName);\n // digest test name\n const digest = createHash('md5')\n .update(testName + testPath)\n .digest('hex');\n // compute a database name based on test name\n return (\n 'test_' +\n testName\n .toLowerCase()\n .replace(/[^a-z0-9_]/g, '_')\n .replace(/_+/g, '_')\n .substring(0, 30) +\n digest.slice(0, 6)\n );\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport tmp from 'tmp';\n\nexport function createTestProject(zmodelContent?: string) {\n const { name: workDir } = tmp.dirSync({ unsafeCleanup: true });\n\n fs.mkdirSync(path.join(workDir, 'node_modules'));\n\n // symlink all entries from \"node_modules\"\n const nodeModules = fs.readdirSync(path.join(__dirname, '../node_modules'));\n for (const entry of nodeModules) {\n if (entry.startsWith('@zenstackhq')) {\n continue;\n }\n fs.symlinkSync(\n path.join(__dirname, '../node_modules', entry),\n path.join(workDir, 'node_modules', entry),\n 'dir',\n );\n }\n\n // in addition, symlink zenstack packages\n const zenstackPackages = ['language', 'sdk', 'runtime', 'cli'];\n fs.mkdirSync(path.join(workDir, 'node_modules/@zenstackhq'));\n for (const pkg of zenstackPackages) {\n fs.symlinkSync(\n path.join(__dirname, `../../${pkg}`),\n path.join(workDir, `node_modules/@zenstackhq/${pkg}`),\n 'dir',\n );\n }\n\n fs.writeFileSync(\n path.join(workDir, 'package.json'),\n JSON.stringify(\n {\n name: 'test',\n version: '1.0.0',\n type: 'module',\n },\n null,\n 4,\n ),\n );\n\n fs.writeFileSync(\n path.join(workDir, 'tsconfig.json'),\n JSON.stringify(\n {\n compilerOptions: {\n module: 'ESNext',\n target: 'ESNext',\n moduleResolution: 'Bundler',\n esModuleInterop: true,\n skipLibCheck: true,\n strict: true,\n },\n include: ['**/*.ts'],\n },\n null,\n 4,\n ),\n );\n\n if (zmodelContent) {\n fs.writeFileSync(path.join(workDir, 'schema.zmodel'), zmodelContent);\n }\n\n return workDir;\n}\n","import { invariant } from '@zenstackhq/common-helpers';\nimport { TsSchemaGenerator } from '@zenstackhq/sdk';\nimport type { SchemaDef } from '@zenstackhq/sdk/schema';\nimport { execSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { match } from 'ts-pattern';\nimport { expect } from 'vitest';\nimport { createTestProject } from './project';\nimport { loadDocumentWithPlugins } from './utils';\n\nfunction makePrelude(provider: 'sqlite' | 'postgresql', dbUrl?: string) {\n return match(provider)\n .with('sqlite', () => {\n return `\ndatasource db {\n provider = 'sqlite'\n url = '${dbUrl ?? 'file:./test.db'}'\n}\n`;\n })\n .with('postgresql', () => {\n return `\ndatasource db {\n provider = 'postgresql'\n url = '${dbUrl ?? 'postgres://postgres:postgres@localhost:5432/db'}'\n}\n`;\n })\n .exhaustive();\n}\n\nexport async function generateTsSchema(\n schemaText: string,\n provider: 'sqlite' | 'postgresql' = 'sqlite',\n dbUrl?: string,\n extraSourceFiles?: Record<string, string>,\n) {\n const workDir = createTestProject();\n\n const zmodelPath = path.join(workDir, 'schema.zmodel');\n const noPrelude = schemaText.includes('datasource ');\n fs.writeFileSync(zmodelPath, `${noPrelude ? '' : makePrelude(provider, dbUrl)}\\n\\n${schemaText}`);\n\n const result = await loadDocumentWithPlugins(zmodelPath);\n if (!result.success) {\n throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`);\n }\n\n const generator = new TsSchemaGenerator();\n await generator.generate(result.model, workDir);\n\n if (extraSourceFiles) {\n for (const [fileName, content] of Object.entries(extraSourceFiles)) {\n const filePath = path.resolve(workDir, !fileName.endsWith('.ts') ? `${fileName}.ts` : fileName);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, content);\n }\n }\n\n // compile the generated TS schema\n return { ...(await compileAndLoad(workDir)), model: result.model };\n}\n\nasync function compileAndLoad(workDir: string) {\n execSync('npx tsc', {\n cwd: workDir,\n stdio: 'inherit',\n });\n\n // load the schema module\n const module = await import(path.join(workDir, 'schema.js'));\n return { workDir, schema: module.schema as SchemaDef };\n}\n\nexport function generateTsSchemaFromFile(filePath: string) {\n const schemaText = fs.readFileSync(filePath, 'utf8');\n return generateTsSchema(schemaText);\n}\n\nexport async function generateTsSchemaInPlace(schemaPath: string) {\n const workDir = path.dirname(schemaPath);\n const result = await loadDocumentWithPlugins(schemaPath);\n if (!result.success) {\n throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);\n }\n const generator = new TsSchemaGenerator();\n await generator.generate(result.model, workDir);\n return compileAndLoad(workDir);\n}\n\nexport async function loadSchema(schema: string, additionalSchemas?: Record<string, string>) {\n if (!schema.includes('datasource ')) {\n schema = `${makePrelude('sqlite')}\\n\\n${schema}`;\n }\n\n // create a temp folder\n const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zenstack-schema'));\n\n // create a temp file\n const tempFile = path.join(tempDir, `schema.zmodel`);\n fs.writeFileSync(tempFile, schema);\n\n if (additionalSchemas) {\n for (const [fileName, content] of Object.entries(additionalSchemas)) {\n let name = fileName;\n if (!name.endsWith('.zmodel')) {\n name += '.zmodel';\n }\n const filePath = path.join(tempDir, name);\n fs.writeFileSync(filePath, content);\n }\n }\n\n const r = await loadDocumentWithPlugins(tempFile);\n expect(r).toSatisfy(\n (r) => r.success,\n `Failed to load schema: ${(r as any).errors?.map((e: any) => e.toString()).join(', ')}`,\n );\n invariant(r.success);\n return r.model;\n}\n\nexport async function loadSchemaWithError(schema: string, error: string | RegExp) {\n if (!schema.includes('datasource ')) {\n schema = `${makePrelude('sqlite')}\\n\\n${schema}`;\n }\n\n // create a temp file\n const tempFile = path.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`);\n fs.writeFileSync(tempFile, schema);\n const r = await loadDocumentWithPlugins(tempFile);\n expect(r.success).toBe(false);\n invariant(!r.success);\n if (typeof error === 'string') {\n expect(r).toSatisfy(\n (r) => r.errors.some((e: any) => e.toString().toLowerCase().includes(error.toLowerCase())),\n `Expected error message to include \"${error}\" but got: ${r.errors.map((e: any) => e.toString()).join(', ')}`,\n );\n } else {\n expect(r).toSatisfy(\n (r) => r.errors.some((e: any) => error.test(e)),\n `Expected error message to match \"${error}\" but got: ${r.errors.map((e: any) => e.toString()).join(', ')}`,\n );\n }\n}\n","import { loadDocument } from '@zenstackhq/language';\n\nexport function loadDocumentWithPlugins(filePath: string) {\n const pluginModelFiles = [require.resolve('@zenstackhq/plugin-policy/plugin.zmodel')];\n return loadDocument(filePath, pluginModelFiles);\n}\n","import { InputValidationError, NotFoundError, RejectedByPolicyError } from '@zenstackhq/runtime';\nimport { expect } from 'vitest';\n\nfunction isPromise(value: any) {\n return typeof value.then === 'function' && typeof value.catch === 'function';\n}\n\nfunction expectError(err: any, errorType: any) {\n if (err instanceof errorType) {\n return {\n message: () => '',\n pass: true,\n };\n } else {\n return {\n message: () => `expected ${errorType}, got ${err}`,\n pass: false,\n };\n }\n}\n\nfunction expectErrorMessages(expectedMessages: string[], message: string) {\n for (const m of expectedMessages) {\n if (!message.includes(m)) {\n return {\n message: () => `expected message not found in error: ${m}, got message: ${message}`,\n pass: false,\n };\n }\n }\n return undefined;\n}\n\nexpect.extend({\n async toResolveTruthy(received: Promise<unknown>) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n const r = await received;\n return {\n pass: !!r,\n message: () => `Expected promise to resolve to a truthy value, but got ${r}`,\n };\n },\n\n async toResolveFalsy(received: Promise<unknown>) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n const r = await received;\n return {\n pass: !r,\n message: () => `Expected promise to resolve to a falsy value, but got ${r}`,\n };\n },\n\n async toResolveNull(received: Promise<unknown>) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n const r = await received;\n return {\n pass: r === null,\n message: () => `Expected promise to resolve to a null value, but got ${r}`,\n };\n },\n\n async toResolveWithLength(received: Promise<unknown>, length: number) {\n const r = await received;\n return {\n pass: Array.isArray(r) && r.length === length,\n message: () => `Expected promise to resolve with an array with length ${length}, but got ${r}`,\n };\n },\n\n async toBeRejectedNotFound(received: Promise<unknown>) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n try {\n await received;\n } catch (err) {\n return expectError(err, NotFoundError);\n }\n return {\n message: () => `expected NotFoundError, got no error`,\n pass: false,\n };\n },\n\n async toBeRejectedByPolicy(received: Promise<unknown>, expectedMessages?: string[]) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n try {\n await received;\n } catch (err) {\n if (expectedMessages && err instanceof RejectedByPolicyError) {\n const r = expectErrorMessages(expectedMessages, err.message || '');\n if (r) {\n return r;\n }\n }\n return expectError(err, RejectedByPolicyError);\n }\n return {\n message: () => `expected PolicyError, got no error`,\n pass: false,\n };\n },\n\n async toBeRejectedByValidation(received: Promise<unknown>, expectedMessages?: string[]) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n try {\n await received;\n } catch (err) {\n if (expectedMessages && err instanceof InputValidationError) {\n const r = expectErrorMessages(expectedMessages, err.message || '');\n if (r) {\n return r;\n }\n }\n return expectError(err, InputValidationError);\n }\n return {\n message: () => `expected InputValidationError, got no error`,\n pass: false,\n };\n },\n});\n"],"mappings":"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","names":["import_common_helpers","import_runtime","import_sdk","import_node_child_process","import_node_crypto","import_node_fs","import_node_path","import_vitest","createTestProject","zmodelContent","name","workDir","tmp","dirSync","unsafeCleanup","fs","mkdirSync","path","join","nodeModules","readdirSync","__dirname","entry","startsWith","symlinkSync","zenstackPackages","pkg","writeFileSync","JSON","stringify","version","type","compilerOptions","module","target","moduleResolution","esModuleInterop","skipLibCheck","strict","include","import_node_fs","import_node_path","loadDocumentWithPlugins","filePath","pluginModelFiles","require","loadDocument","makePrelude","provider","dbUrl","match","with","exhaustive","generateTsSchema","schemaText","extraSourceFiles","workDir","createTestProject","zmodelPath","path","join","noPrelude","includes","fs","writeFileSync","result","loadDocumentWithPlugins","success","Error","errors","generator","TsSchemaGenerator","generate","model","fileName","content","Object","entries","filePath","resolve","endsWith","mkdirSync","dirname","recursive","compileAndLoad","execSync","cwd","stdio","module","schema","generateTsSchemaFromFile","readFileSync","generateTsSchemaInPlace","schemaPath","loadSchema","additionalSchemas","tempDir","mkdtempSync","os","tmpdir","tempFile","name","r","expect","toSatisfy","map","e","toString","invariant","loadSchemaWithError","error","crypto","randomUUID","toBe","some","toLowerCase","test","getTestDbProvider","val","process","env","includes","Error","TEST_PG_CONFIG","host","port","parseInt","user","password","createTestClient","schema","options","schemaFile","workDir","_schema","provider","dbName","getTestDbName","dbUrl","model","generated","generateTsSchema","extraSourceFiles","type","createTestProject","schemaContent","fs","readFileSync","replace","writeFileSync","path","join","invariant","debug","console","log","plugins","rest","_options","usePrismaPush","r","loadDocumentWithPlugins","success","errors","prismaSchema","PrismaSchemaGenerator","prismaSchemaText","generate","resolve","execSync","cwd","stdio","pgClient","PGClient","connect","query","end","dialect","PostgresDialect","pool","Pool","database","SqliteDialect","SQLite","client","ZenStackClient","$pushSchema","plugin","$use","createPolicyTestClient","PolicyPlugin","testLogger","e","sql","parameters","testName","expect","getState","currentTestName","testPath","digest","createHash","update","toLowerCase","substring","slice","import_runtime","import_vitest","isPromise","value","then","catch","expectError","err","errorType","message","pass","expectErrorMessages","expectedMessages","m","includes","undefined","expect","extend","toResolveTruthy","received","r","toResolveFalsy","toResolveNull","toResolveWithLength","length","Array","isArray","toBeRejectedNotFound","NotFoundError","toBeRejectedByPolicy","RejectedByPolicyError","toBeRejectedByValidation","InputValidationError"]}
package/dist/index.d.cts CHANGED
@@ -1,18 +1,41 @@
1
- import { SchemaDef } from '@zenstackhq/sdk/schema';
1
+ import { ClientOptions, ClientContract } from '@zenstackhq/runtime';
2
+ import { SchemaDef } from '@zenstackhq/runtime/schema';
3
+ import { LogEvent } from 'kysely';
4
+ import * as _zenstackhq_language_ast from '@zenstackhq/language/ast';
5
+ import { SchemaDef as SchemaDef$1 } from '@zenstackhq/sdk/schema';
2
6
 
3
- declare function createTestProject(): string;
7
+ declare function getTestDbProvider(): "sqlite" | "postgresql";
8
+ type CreateTestClientOptions<Schema extends SchemaDef> = Omit<ClientOptions<Schema>, 'dialect'> & {
9
+ provider?: 'sqlite' | 'postgresql';
10
+ dbName?: string;
11
+ usePrismaPush?: boolean;
12
+ extraSourceFiles?: Record<string, string>;
13
+ workDir?: string;
14
+ debug?: boolean;
15
+ };
16
+ declare function createTestClient<Schema extends SchemaDef>(schema: Schema, options?: CreateTestClientOptions<Schema>, schemaFile?: string): Promise<ClientContract<Schema>>;
17
+ declare function createTestClient<Schema extends SchemaDef>(schema: string, options?: CreateTestClientOptions<Schema>): Promise<any>;
18
+ declare function createPolicyTestClient<Schema extends SchemaDef>(schema: Schema, options?: CreateTestClientOptions<Schema>): Promise<ClientContract<Schema>>;
19
+ declare function createPolicyTestClient<Schema extends SchemaDef>(schema: string, options?: CreateTestClientOptions<Schema>): Promise<any>;
20
+ declare function testLogger(e: LogEvent): void;
21
+
22
+ declare function createTestProject(zmodelContent?: string): string;
4
23
 
5
24
  declare function generateTsSchema(schemaText: string, provider?: 'sqlite' | 'postgresql', dbUrl?: string, extraSourceFiles?: Record<string, string>): Promise<{
25
+ model: _zenstackhq_language_ast.Model;
6
26
  workDir: string;
7
- schema: SchemaDef;
27
+ schema: SchemaDef$1;
8
28
  }>;
9
29
  declare function generateTsSchemaFromFile(filePath: string): Promise<{
30
+ model: _zenstackhq_language_ast.Model;
10
31
  workDir: string;
11
- schema: SchemaDef;
32
+ schema: SchemaDef$1;
12
33
  }>;
13
34
  declare function generateTsSchemaInPlace(schemaPath: string): Promise<{
14
35
  workDir: string;
15
- schema: SchemaDef;
36
+ schema: SchemaDef$1;
16
37
  }>;
38
+ declare function loadSchema(schema: string, additionalSchemas?: Record<string, string>): Promise<_zenstackhq_language_ast.Model>;
39
+ declare function loadSchemaWithError(schema: string, error: string | RegExp): Promise<void>;
17
40
 
18
- export { createTestProject, generateTsSchema, generateTsSchemaFromFile, generateTsSchemaInPlace };
41
+ export { type CreateTestClientOptions, createPolicyTestClient, createTestClient, createTestProject, generateTsSchema, generateTsSchemaFromFile, generateTsSchemaInPlace, getTestDbProvider, loadSchema, loadSchemaWithError, testLogger };
package/dist/index.d.ts CHANGED
@@ -1,18 +1,41 @@
1
- import { SchemaDef } from '@zenstackhq/sdk/schema';
1
+ import { ClientOptions, ClientContract } from '@zenstackhq/runtime';
2
+ import { SchemaDef } from '@zenstackhq/runtime/schema';
3
+ import { LogEvent } from 'kysely';
4
+ import * as _zenstackhq_language_ast from '@zenstackhq/language/ast';
5
+ import { SchemaDef as SchemaDef$1 } from '@zenstackhq/sdk/schema';
2
6
 
3
- declare function createTestProject(): string;
7
+ declare function getTestDbProvider(): "sqlite" | "postgresql";
8
+ type CreateTestClientOptions<Schema extends SchemaDef> = Omit<ClientOptions<Schema>, 'dialect'> & {
9
+ provider?: 'sqlite' | 'postgresql';
10
+ dbName?: string;
11
+ usePrismaPush?: boolean;
12
+ extraSourceFiles?: Record<string, string>;
13
+ workDir?: string;
14
+ debug?: boolean;
15
+ };
16
+ declare function createTestClient<Schema extends SchemaDef>(schema: Schema, options?: CreateTestClientOptions<Schema>, schemaFile?: string): Promise<ClientContract<Schema>>;
17
+ declare function createTestClient<Schema extends SchemaDef>(schema: string, options?: CreateTestClientOptions<Schema>): Promise<any>;
18
+ declare function createPolicyTestClient<Schema extends SchemaDef>(schema: Schema, options?: CreateTestClientOptions<Schema>): Promise<ClientContract<Schema>>;
19
+ declare function createPolicyTestClient<Schema extends SchemaDef>(schema: string, options?: CreateTestClientOptions<Schema>): Promise<any>;
20
+ declare function testLogger(e: LogEvent): void;
21
+
22
+ declare function createTestProject(zmodelContent?: string): string;
4
23
 
5
24
  declare function generateTsSchema(schemaText: string, provider?: 'sqlite' | 'postgresql', dbUrl?: string, extraSourceFiles?: Record<string, string>): Promise<{
25
+ model: _zenstackhq_language_ast.Model;
6
26
  workDir: string;
7
- schema: SchemaDef;
27
+ schema: SchemaDef$1;
8
28
  }>;
9
29
  declare function generateTsSchemaFromFile(filePath: string): Promise<{
30
+ model: _zenstackhq_language_ast.Model;
10
31
  workDir: string;
11
- schema: SchemaDef;
32
+ schema: SchemaDef$1;
12
33
  }>;
13
34
  declare function generateTsSchemaInPlace(schemaPath: string): Promise<{
14
35
  workDir: string;
15
- schema: SchemaDef;
36
+ schema: SchemaDef$1;
16
37
  }>;
38
+ declare function loadSchema(schema: string, additionalSchemas?: Record<string, string>): Promise<_zenstackhq_language_ast.Model>;
39
+ declare function loadSchemaWithError(schema: string, error: string | RegExp): Promise<void>;
17
40
 
18
- export { createTestProject, generateTsSchema, generateTsSchemaFromFile, generateTsSchemaInPlace };
41
+ export { type CreateTestClientOptions, createPolicyTestClient, createTestClient, createTestProject, generateTsSchema, generateTsSchemaFromFile, generateTsSchemaInPlace, getTestDbProvider, loadSchema, loadSchemaWithError, testLogger };
package/dist/index.js CHANGED
@@ -1,11 +1,31 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ // src/client.ts
11
+ import { invariant as invariant2 } from "@zenstackhq/common-helpers";
12
+ import { PolicyPlugin } from "@zenstackhq/plugin-policy";
13
+ import { ZenStackClient } from "@zenstackhq/runtime";
14
+ import { PrismaSchemaGenerator } from "@zenstackhq/sdk";
15
+ import SQLite from "better-sqlite3";
16
+ import { PostgresDialect, SqliteDialect } from "kysely";
17
+ import { execSync as execSync2 } from "child_process";
18
+ import { createHash } from "crypto";
19
+ import fs3 from "fs";
20
+ import path3 from "path";
21
+ import { Client as PGClient, Pool } from "pg";
22
+ import { expect as expect2 } from "vitest";
3
23
 
4
24
  // src/project.ts
5
25
  import fs from "fs";
6
26
  import path from "path";
7
27
  import tmp from "tmp";
8
- function createTestProject() {
28
+ function createTestProject(zmodelContent) {
9
29
  const { name: workDir } = tmp.dirSync({
10
30
  unsafeCleanup: true
11
31
  });
@@ -45,18 +65,35 @@ function createTestProject() {
45
65
  "**/*.ts"
46
66
  ]
47
67
  }, null, 4));
68
+ if (zmodelContent) {
69
+ fs.writeFileSync(path.join(workDir, "schema.zmodel"), zmodelContent);
70
+ }
48
71
  return workDir;
49
72
  }
50
73
  __name(createTestProject, "createTestProject");
51
74
 
52
75
  // src/schema.ts
53
- import { loadDocument } from "@zenstackhq/language";
76
+ import { invariant } from "@zenstackhq/common-helpers";
54
77
  import { TsSchemaGenerator } from "@zenstackhq/sdk";
55
- import { glob } from "glob";
56
78
  import { execSync } from "child_process";
79
+ import crypto from "crypto";
57
80
  import fs2 from "fs";
81
+ import os from "os";
58
82
  import path2 from "path";
59
83
  import { match } from "ts-pattern";
84
+ import { expect } from "vitest";
85
+
86
+ // src/utils.ts
87
+ import { loadDocument } from "@zenstackhq/language";
88
+ function loadDocumentWithPlugins(filePath) {
89
+ const pluginModelFiles = [
90
+ __require.resolve("@zenstackhq/plugin-policy/plugin.zmodel")
91
+ ];
92
+ return loadDocument(filePath, pluginModelFiles);
93
+ }
94
+ __name(loadDocumentWithPlugins, "loadDocumentWithPlugins");
95
+
96
+ // src/schema.ts
60
97
  function makePrelude(provider, dbUrl) {
61
98
  return match(provider).with("sqlite", () => {
62
99
  return `
@@ -82,8 +119,7 @@ async function generateTsSchema(schemaText, provider = "sqlite", dbUrl, extraSou
82
119
  fs2.writeFileSync(zmodelPath, `${noPrelude ? "" : makePrelude(provider, dbUrl)}
83
120
 
84
121
  ${schemaText}`);
85
- const pluginModelFiles = glob.sync(path2.resolve(__dirname, "../../runtime/src/plugins/**/plugin.zmodel"));
86
- const result = await loadDocument(zmodelPath, pluginModelFiles);
122
+ const result = await loadDocumentWithPlugins(zmodelPath);
87
123
  if (!result.success) {
88
124
  throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`);
89
125
  }
@@ -91,14 +127,17 @@ ${schemaText}`);
91
127
  await generator.generate(result.model, workDir);
92
128
  if (extraSourceFiles) {
93
129
  for (const [fileName, content] of Object.entries(extraSourceFiles)) {
94
- const filePath = path2.resolve(workDir, `${fileName}.ts`);
130
+ const filePath = path2.resolve(workDir, !fileName.endsWith(".ts") ? `${fileName}.ts` : fileName);
95
131
  fs2.mkdirSync(path2.dirname(filePath), {
96
132
  recursive: true
97
133
  });
98
134
  fs2.writeFileSync(filePath, content);
99
135
  }
100
136
  }
101
- return compileAndLoad(workDir);
137
+ return {
138
+ ...await compileAndLoad(workDir),
139
+ model: result.model
140
+ };
102
141
  }
103
142
  __name(generateTsSchema, "generateTsSchema");
104
143
  async function compileAndLoad(workDir) {
@@ -120,8 +159,7 @@ function generateTsSchemaFromFile(filePath) {
120
159
  __name(generateTsSchemaFromFile, "generateTsSchemaFromFile");
121
160
  async function generateTsSchemaInPlace(schemaPath) {
122
161
  const workDir = path2.dirname(schemaPath);
123
- const pluginModelFiles = glob.sync(path2.resolve(__dirname, "../../runtime/src/plugins/**/plugin.zmodel"));
124
- const result = await loadDocument(schemaPath, pluginModelFiles);
162
+ const result = await loadDocumentWithPlugins(schemaPath);
125
163
  if (!result.success) {
126
164
  throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);
127
165
  }
@@ -130,10 +168,342 @@ async function generateTsSchemaInPlace(schemaPath) {
130
168
  return compileAndLoad(workDir);
131
169
  }
132
170
  __name(generateTsSchemaInPlace, "generateTsSchemaInPlace");
171
+ async function loadSchema(schema, additionalSchemas) {
172
+ if (!schema.includes("datasource ")) {
173
+ schema = `${makePrelude("sqlite")}
174
+
175
+ ${schema}`;
176
+ }
177
+ const tempDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "zenstack-schema"));
178
+ const tempFile = path2.join(tempDir, `schema.zmodel`);
179
+ fs2.writeFileSync(tempFile, schema);
180
+ if (additionalSchemas) {
181
+ for (const [fileName, content] of Object.entries(additionalSchemas)) {
182
+ let name = fileName;
183
+ if (!name.endsWith(".zmodel")) {
184
+ name += ".zmodel";
185
+ }
186
+ const filePath = path2.join(tempDir, name);
187
+ fs2.writeFileSync(filePath, content);
188
+ }
189
+ }
190
+ const r = await loadDocumentWithPlugins(tempFile);
191
+ expect(r).toSatisfy((r2) => r2.success, `Failed to load schema: ${r.errors?.map((e) => e.toString()).join(", ")}`);
192
+ invariant(r.success);
193
+ return r.model;
194
+ }
195
+ __name(loadSchema, "loadSchema");
196
+ async function loadSchemaWithError(schema, error) {
197
+ if (!schema.includes("datasource ")) {
198
+ schema = `${makePrelude("sqlite")}
199
+
200
+ ${schema}`;
201
+ }
202
+ const tempFile = path2.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`);
203
+ fs2.writeFileSync(tempFile, schema);
204
+ const r = await loadDocumentWithPlugins(tempFile);
205
+ expect(r.success).toBe(false);
206
+ invariant(!r.success);
207
+ if (typeof error === "string") {
208
+ expect(r).toSatisfy((r2) => r2.errors.some((e) => e.toString().toLowerCase().includes(error.toLowerCase())), `Expected error message to include "${error}" but got: ${r.errors.map((e) => e.toString()).join(", ")}`);
209
+ } else {
210
+ expect(r).toSatisfy((r2) => r2.errors.some((e) => error.test(e)), `Expected error message to match "${error}" but got: ${r.errors.map((e) => e.toString()).join(", ")}`);
211
+ }
212
+ }
213
+ __name(loadSchemaWithError, "loadSchemaWithError");
214
+
215
+ // src/client.ts
216
+ function getTestDbProvider() {
217
+ const val = process.env["TEST_DB_PROVIDER"] ?? "sqlite";
218
+ if (![
219
+ "sqlite",
220
+ "postgresql"
221
+ ].includes(val)) {
222
+ throw new Error(`Invalid TEST_DB_PROVIDER value: ${val}`);
223
+ }
224
+ return val;
225
+ }
226
+ __name(getTestDbProvider, "getTestDbProvider");
227
+ var TEST_PG_CONFIG = {
228
+ host: process.env["TEST_PG_HOST"] ?? "localhost",
229
+ port: process.env["TEST_PG_PORT"] ? parseInt(process.env["TEST_PG_PORT"]) : 5432,
230
+ user: process.env["TEST_PG_USER"] ?? "postgres",
231
+ password: process.env["TEST_PG_PASSWORD"] ?? "postgres"
232
+ };
233
+ async function createTestClient(schema, options, schemaFile) {
234
+ let workDir = options?.workDir;
235
+ let _schema;
236
+ const provider = options?.provider ?? getTestDbProvider() ?? "sqlite";
237
+ const dbName = options?.dbName ?? getTestDbName(provider);
238
+ const dbUrl = provider === "sqlite" ? `file:${dbName}` : `postgres://${TEST_PG_CONFIG.user}:${TEST_PG_CONFIG.password}@${TEST_PG_CONFIG.host}:${TEST_PG_CONFIG.port}/${dbName}`;
239
+ let model;
240
+ if (typeof schema === "string") {
241
+ const generated = await generateTsSchema(schema, provider, dbUrl, options?.extraSourceFiles);
242
+ workDir = generated.workDir;
243
+ model = generated.model;
244
+ _schema = {
245
+ ...generated.schema,
246
+ provider: {
247
+ type: provider
248
+ }
249
+ };
250
+ } else {
251
+ _schema = {
252
+ ...schema,
253
+ provider: {
254
+ type: provider
255
+ }
256
+ };
257
+ workDir ??= createTestProject();
258
+ if (schemaFile) {
259
+ let schemaContent = fs3.readFileSync(schemaFile, "utf-8");
260
+ if (dbUrl) {
261
+ schemaContent = schemaContent.replace(/datasource\s+db\s*{[^}]*}/m, `datasource db {
262
+ provider = '${provider}'
263
+ url = '${dbUrl}'
264
+ }`);
265
+ }
266
+ fs3.writeFileSync(path3.join(workDir, "schema.zmodel"), schemaContent);
267
+ }
268
+ }
269
+ invariant2(workDir);
270
+ if (options?.debug) {
271
+ console.log(`Work directory: ${workDir}`);
272
+ }
273
+ const { plugins, ...rest } = options ?? {};
274
+ const _options = {
275
+ ...rest
276
+ };
277
+ if (options?.usePrismaPush) {
278
+ invariant2(typeof schema === "string" || schemaFile, "a schema file must be provided when using prisma db push");
279
+ if (!model) {
280
+ const r = await loadDocumentWithPlugins(path3.join(workDir, "schema.zmodel"));
281
+ if (!r.success) {
282
+ throw new Error(r.errors.join("\n"));
283
+ }
284
+ model = r.model;
285
+ }
286
+ const prismaSchema = new PrismaSchemaGenerator(model);
287
+ const prismaSchemaText = await prismaSchema.generate();
288
+ fs3.writeFileSync(path3.resolve(workDir, "schema.prisma"), prismaSchemaText);
289
+ execSync2("npx prisma db push --schema ./schema.prisma --skip-generate --force-reset", {
290
+ cwd: workDir,
291
+ stdio: "ignore"
292
+ });
293
+ } else {
294
+ if (provider === "postgresql") {
295
+ invariant2(dbName, "dbName is required");
296
+ const pgClient = new PGClient(TEST_PG_CONFIG);
297
+ await pgClient.connect();
298
+ await pgClient.query(`DROP DATABASE IF EXISTS "${dbName}"`);
299
+ await pgClient.query(`CREATE DATABASE "${dbName}"`);
300
+ await pgClient.end();
301
+ }
302
+ }
303
+ if (provider === "postgresql") {
304
+ _options.dialect = new PostgresDialect({
305
+ pool: new Pool({
306
+ ...TEST_PG_CONFIG,
307
+ database: dbName
308
+ })
309
+ });
310
+ } else {
311
+ _options.dialect = new SqliteDialect({
312
+ database: new SQLite(path3.join(workDir, dbName))
313
+ });
314
+ }
315
+ let client = new ZenStackClient(_schema, _options);
316
+ if (!options?.usePrismaPush) {
317
+ await client.$pushSchema();
318
+ }
319
+ if (plugins) {
320
+ for (const plugin of plugins) {
321
+ client = client.$use(plugin);
322
+ }
323
+ }
324
+ return client;
325
+ }
326
+ __name(createTestClient, "createTestClient");
327
+ async function createPolicyTestClient(schema, options) {
328
+ return createTestClient(schema, {
329
+ ...options,
330
+ plugins: [
331
+ ...options?.plugins ?? [],
332
+ new PolicyPlugin()
333
+ ]
334
+ });
335
+ }
336
+ __name(createPolicyTestClient, "createPolicyTestClient");
337
+ function testLogger(e) {
338
+ console.log(e.query.sql, e.query.parameters);
339
+ }
340
+ __name(testLogger, "testLogger");
341
+ function getTestDbName(provider) {
342
+ if (provider === "sqlite") {
343
+ return "./test.db";
344
+ }
345
+ const testName = expect2.getState().currentTestName;
346
+ const testPath = expect2.getState().testPath ?? "";
347
+ invariant2(testName);
348
+ const digest = createHash("md5").update(testName + testPath).digest("hex");
349
+ return "test_" + testName.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").substring(0, 30) + digest.slice(0, 6);
350
+ }
351
+ __name(getTestDbName, "getTestDbName");
352
+
353
+ // src/vitest-ext.ts
354
+ import { InputValidationError, NotFoundError, RejectedByPolicyError } from "@zenstackhq/runtime";
355
+ import { expect as expect3 } from "vitest";
356
+ function isPromise(value) {
357
+ return typeof value.then === "function" && typeof value.catch === "function";
358
+ }
359
+ __name(isPromise, "isPromise");
360
+ function expectError(err, errorType) {
361
+ if (err instanceof errorType) {
362
+ return {
363
+ message: /* @__PURE__ */ __name(() => "", "message"),
364
+ pass: true
365
+ };
366
+ } else {
367
+ return {
368
+ message: /* @__PURE__ */ __name(() => `expected ${errorType}, got ${err}`, "message"),
369
+ pass: false
370
+ };
371
+ }
372
+ }
373
+ __name(expectError, "expectError");
374
+ function expectErrorMessages(expectedMessages, message) {
375
+ for (const m of expectedMessages) {
376
+ if (!message.includes(m)) {
377
+ return {
378
+ message: /* @__PURE__ */ __name(() => `expected message not found in error: ${m}, got message: ${message}`, "message"),
379
+ pass: false
380
+ };
381
+ }
382
+ }
383
+ return void 0;
384
+ }
385
+ __name(expectErrorMessages, "expectErrorMessages");
386
+ expect3.extend({
387
+ async toResolveTruthy(received) {
388
+ if (!isPromise(received)) {
389
+ return {
390
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
391
+ pass: false
392
+ };
393
+ }
394
+ const r = await received;
395
+ return {
396
+ pass: !!r,
397
+ message: /* @__PURE__ */ __name(() => `Expected promise to resolve to a truthy value, but got ${r}`, "message")
398
+ };
399
+ },
400
+ async toResolveFalsy(received) {
401
+ if (!isPromise(received)) {
402
+ return {
403
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
404
+ pass: false
405
+ };
406
+ }
407
+ const r = await received;
408
+ return {
409
+ pass: !r,
410
+ message: /* @__PURE__ */ __name(() => `Expected promise to resolve to a falsy value, but got ${r}`, "message")
411
+ };
412
+ },
413
+ async toResolveNull(received) {
414
+ if (!isPromise(received)) {
415
+ return {
416
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
417
+ pass: false
418
+ };
419
+ }
420
+ const r = await received;
421
+ return {
422
+ pass: r === null,
423
+ message: /* @__PURE__ */ __name(() => `Expected promise to resolve to a null value, but got ${r}`, "message")
424
+ };
425
+ },
426
+ async toResolveWithLength(received, length) {
427
+ const r = await received;
428
+ return {
429
+ pass: Array.isArray(r) && r.length === length,
430
+ message: /* @__PURE__ */ __name(() => `Expected promise to resolve with an array with length ${length}, but got ${r}`, "message")
431
+ };
432
+ },
433
+ async toBeRejectedNotFound(received) {
434
+ if (!isPromise(received)) {
435
+ return {
436
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
437
+ pass: false
438
+ };
439
+ }
440
+ try {
441
+ await received;
442
+ } catch (err) {
443
+ return expectError(err, NotFoundError);
444
+ }
445
+ return {
446
+ message: /* @__PURE__ */ __name(() => `expected NotFoundError, got no error`, "message"),
447
+ pass: false
448
+ };
449
+ },
450
+ async toBeRejectedByPolicy(received, expectedMessages) {
451
+ if (!isPromise(received)) {
452
+ return {
453
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
454
+ pass: false
455
+ };
456
+ }
457
+ try {
458
+ await received;
459
+ } catch (err) {
460
+ if (expectedMessages && err instanceof RejectedByPolicyError) {
461
+ const r = expectErrorMessages(expectedMessages, err.message || "");
462
+ if (r) {
463
+ return r;
464
+ }
465
+ }
466
+ return expectError(err, RejectedByPolicyError);
467
+ }
468
+ return {
469
+ message: /* @__PURE__ */ __name(() => `expected PolicyError, got no error`, "message"),
470
+ pass: false
471
+ };
472
+ },
473
+ async toBeRejectedByValidation(received, expectedMessages) {
474
+ if (!isPromise(received)) {
475
+ return {
476
+ message: /* @__PURE__ */ __name(() => "a promise is expected", "message"),
477
+ pass: false
478
+ };
479
+ }
480
+ try {
481
+ await received;
482
+ } catch (err) {
483
+ if (expectedMessages && err instanceof InputValidationError) {
484
+ const r = expectErrorMessages(expectedMessages, err.message || "");
485
+ if (r) {
486
+ return r;
487
+ }
488
+ }
489
+ return expectError(err, InputValidationError);
490
+ }
491
+ return {
492
+ message: /* @__PURE__ */ __name(() => `expected InputValidationError, got no error`, "message"),
493
+ pass: false
494
+ };
495
+ }
496
+ });
133
497
  export {
498
+ createPolicyTestClient,
499
+ createTestClient,
134
500
  createTestProject,
135
501
  generateTsSchema,
136
502
  generateTsSchemaFromFile,
137
- generateTsSchemaInPlace
503
+ generateTsSchemaInPlace,
504
+ getTestDbProvider,
505
+ loadSchema,
506
+ loadSchemaWithError,
507
+ testLogger
138
508
  };
139
509
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/project.ts","../src/schema.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport tmp from 'tmp';\n\nexport function createTestProject() {\n const { name: workDir } = tmp.dirSync({ unsafeCleanup: true });\n\n fs.mkdirSync(path.join(workDir, 'node_modules'));\n\n // symlink all entries from \"node_modules\"\n const nodeModules = fs.readdirSync(path.join(__dirname, '../node_modules'));\n for (const entry of nodeModules) {\n if (entry.startsWith('@zenstackhq')) {\n continue;\n }\n fs.symlinkSync(\n path.join(__dirname, '../node_modules', entry),\n path.join(workDir, 'node_modules', entry),\n 'dir',\n );\n }\n\n // in addition, symlink zenstack packages\n const zenstackPackages = ['language', 'sdk', 'runtime', 'cli'];\n fs.mkdirSync(path.join(workDir, 'node_modules/@zenstackhq'));\n for (const pkg of zenstackPackages) {\n fs.symlinkSync(\n path.join(__dirname, `../../${pkg}`),\n path.join(workDir, `node_modules/@zenstackhq/${pkg}`),\n 'dir',\n );\n }\n\n fs.writeFileSync(\n path.join(workDir, 'package.json'),\n JSON.stringify(\n {\n name: 'test',\n version: '1.0.0',\n type: 'module',\n },\n null,\n 4,\n ),\n );\n\n fs.writeFileSync(\n path.join(workDir, 'tsconfig.json'),\n JSON.stringify(\n {\n compilerOptions: {\n module: 'ESNext',\n target: 'ESNext',\n moduleResolution: 'Bundler',\n esModuleInterop: true,\n skipLibCheck: true,\n strict: true,\n },\n include: ['**/*.ts'],\n },\n null,\n 4,\n ),\n );\n\n return workDir;\n}\n","import { loadDocument } from '@zenstackhq/language';\nimport { TsSchemaGenerator } from '@zenstackhq/sdk';\nimport type { SchemaDef } from '@zenstackhq/sdk/schema';\nimport { glob } from 'glob';\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { match } from 'ts-pattern';\nimport { createTestProject } from './project';\n\nfunction makePrelude(provider: 'sqlite' | 'postgresql', dbUrl?: string) {\n return match(provider)\n .with('sqlite', () => {\n return `\ndatasource db {\n provider = 'sqlite'\n url = '${dbUrl ?? 'file:./test.db'}'\n}\n`;\n })\n .with('postgresql', () => {\n return `\ndatasource db {\n provider = 'postgresql'\n url = '${dbUrl ?? 'postgres://postgres:postgres@localhost:5432/db'}'\n}\n`;\n })\n .exhaustive();\n}\n\nexport async function generateTsSchema(\n schemaText: string,\n provider: 'sqlite' | 'postgresql' = 'sqlite',\n dbUrl?: string,\n extraSourceFiles?: Record<string, string>,\n) {\n const workDir = createTestProject();\n\n const zmodelPath = path.join(workDir, 'schema.zmodel');\n const noPrelude = schemaText.includes('datasource ');\n fs.writeFileSync(zmodelPath, `${noPrelude ? '' : makePrelude(provider, dbUrl)}\\n\\n${schemaText}`);\n\n const pluginModelFiles = glob.sync(path.resolve(__dirname, '../../runtime/src/plugins/**/plugin.zmodel'));\n const result = await loadDocument(zmodelPath, pluginModelFiles);\n if (!result.success) {\n throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`);\n }\n\n const generator = new TsSchemaGenerator();\n await generator.generate(result.model, workDir);\n\n if (extraSourceFiles) {\n for (const [fileName, content] of Object.entries(extraSourceFiles)) {\n const filePath = path.resolve(workDir, `${fileName}.ts`);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, content);\n }\n }\n\n // compile the generated TS schema\n return compileAndLoad(workDir);\n}\n\nasync function compileAndLoad(workDir: string) {\n execSync('npx tsc', {\n cwd: workDir,\n stdio: 'inherit',\n });\n\n // load the schema module\n const module = await import(path.join(workDir, 'schema.js'));\n return { workDir, schema: module.schema as SchemaDef };\n}\n\nexport function generateTsSchemaFromFile(filePath: string) {\n const schemaText = fs.readFileSync(filePath, 'utf8');\n return generateTsSchema(schemaText);\n}\n\nexport async function generateTsSchemaInPlace(schemaPath: string) {\n const workDir = path.dirname(schemaPath);\n const pluginModelFiles = glob.sync(path.resolve(__dirname, '../../runtime/src/plugins/**/plugin.zmodel'));\n const result = await loadDocument(schemaPath, pluginModelFiles);\n if (!result.success) {\n throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);\n }\n const generator = new TsSchemaGenerator();\n await generator.generate(result.model, workDir);\n return compileAndLoad(workDir);\n}\n"],"mappings":"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","names":["fs","path","tmp","createTestProject","name","workDir","tmp","dirSync","unsafeCleanup","fs","mkdirSync","path","join","nodeModules","readdirSync","__dirname","entry","startsWith","symlinkSync","zenstackPackages","pkg","writeFileSync","JSON","stringify","version","type","compilerOptions","module","target","moduleResolution","esModuleInterop","skipLibCheck","strict","include","loadDocument","TsSchemaGenerator","glob","execSync","fs","path","match","makePrelude","provider","dbUrl","match","with","exhaustive","generateTsSchema","schemaText","extraSourceFiles","workDir","createTestProject","zmodelPath","path","join","noPrelude","includes","fs","writeFileSync","pluginModelFiles","glob","sync","resolve","__dirname","result","loadDocument","success","Error","errors","generator","TsSchemaGenerator","generate","model","fileName","content","Object","entries","filePath","mkdirSync","dirname","recursive","compileAndLoad","execSync","cwd","stdio","module","schema","generateTsSchemaFromFile","readFileSync","generateTsSchemaInPlace","schemaPath"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/project.ts","../src/schema.ts","../src/utils.ts","../src/vitest-ext.ts"],"sourcesContent":["import { invariant } from '@zenstackhq/common-helpers';\nimport type { Model } from '@zenstackhq/language/ast';\nimport { PolicyPlugin } from '@zenstackhq/plugin-policy';\nimport { ZenStackClient, type ClientContract, type ClientOptions } from '@zenstackhq/runtime';\nimport type { SchemaDef } from '@zenstackhq/runtime/schema';\nimport { PrismaSchemaGenerator } from '@zenstackhq/sdk';\nimport SQLite from 'better-sqlite3';\nimport { PostgresDialect, SqliteDialect, type LogEvent } from 'kysely';\nimport { execSync } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { Client as PGClient, Pool } from 'pg';\nimport { expect } from 'vitest';\nimport { createTestProject } from './project';\nimport { generateTsSchema } from './schema';\nimport { loadDocumentWithPlugins } from './utils';\n\nexport function getTestDbProvider() {\n const val = process.env['TEST_DB_PROVIDER'] ?? 'sqlite';\n if (!['sqlite', 'postgresql'].includes(val!)) {\n throw new Error(`Invalid TEST_DB_PROVIDER value: ${val}`);\n }\n return val as 'sqlite' | 'postgresql';\n}\n\nconst TEST_PG_CONFIG = {\n host: process.env['TEST_PG_HOST'] ?? 'localhost',\n port: process.env['TEST_PG_PORT'] ? parseInt(process.env['TEST_PG_PORT']) : 5432,\n user: process.env['TEST_PG_USER'] ?? 'postgres',\n password: process.env['TEST_PG_PASSWORD'] ?? 'postgres',\n};\n\nexport type CreateTestClientOptions<Schema extends SchemaDef> = Omit<ClientOptions<Schema>, 'dialect'> & {\n provider?: 'sqlite' | 'postgresql';\n dbName?: string;\n usePrismaPush?: boolean;\n extraSourceFiles?: Record<string, string>;\n workDir?: string;\n debug?: boolean;\n};\n\nexport async function createTestClient<Schema extends SchemaDef>(\n schema: Schema,\n options?: CreateTestClientOptions<Schema>,\n schemaFile?: string,\n): Promise<ClientContract<Schema>>;\nexport async function createTestClient<Schema extends SchemaDef>(\n schema: string,\n options?: CreateTestClientOptions<Schema>,\n): Promise<any>;\nexport async function createTestClient<Schema extends SchemaDef>(\n schema: Schema | string,\n options?: CreateTestClientOptions<Schema>,\n schemaFile?: string,\n): Promise<any> {\n let workDir = options?.workDir;\n let _schema: Schema;\n const provider = options?.provider ?? getTestDbProvider() ?? 'sqlite';\n\n const dbName = options?.dbName ?? getTestDbName(provider);\n\n const dbUrl =\n provider === 'sqlite'\n ? `file:${dbName}`\n : `postgres://${TEST_PG_CONFIG.user}:${TEST_PG_CONFIG.password}@${TEST_PG_CONFIG.host}:${TEST_PG_CONFIG.port}/${dbName}`;\n\n let model: Model | undefined;\n\n if (typeof schema === 'string') {\n const generated = await generateTsSchema(schema, provider, dbUrl, options?.extraSourceFiles);\n workDir = generated.workDir;\n model = generated.model;\n // replace schema's provider\n _schema = {\n ...generated.schema,\n provider: {\n type: provider,\n },\n } as Schema;\n } else {\n // replace schema's provider\n _schema = {\n ...schema,\n provider: {\n type: provider,\n },\n };\n workDir ??= createTestProject();\n if (schemaFile) {\n let schemaContent = fs.readFileSync(schemaFile, 'utf-8');\n if (dbUrl) {\n // replace `datasource db { }` section\n schemaContent = schemaContent.replace(\n /datasource\\s+db\\s*{[^}]*}/m,\n `datasource db {\n provider = '${provider}'\n url = '${dbUrl}'\n}`,\n );\n }\n fs.writeFileSync(path.join(workDir, 'schema.zmodel'), schemaContent);\n }\n }\n\n invariant(workDir);\n if (options?.debug) {\n console.log(`Work directory: ${workDir}`);\n }\n\n const { plugins, ...rest } = options ?? {};\n const _options: ClientOptions<Schema> = {\n ...rest,\n } as ClientOptions<Schema>;\n\n if (options?.usePrismaPush) {\n invariant(typeof schema === 'string' || schemaFile, 'a schema file must be provided when using prisma db push');\n if (!model) {\n const r = await loadDocumentWithPlugins(path.join(workDir, 'schema.zmodel'));\n if (!r.success) {\n throw new Error(r.errors.join('\\n'));\n }\n model = r.model;\n }\n const prismaSchema = new PrismaSchemaGenerator(model);\n const prismaSchemaText = await prismaSchema.generate();\n fs.writeFileSync(path.resolve(workDir!, 'schema.prisma'), prismaSchemaText);\n execSync('npx prisma db push --schema ./schema.prisma --skip-generate --force-reset', {\n cwd: workDir,\n stdio: 'ignore',\n });\n } else {\n if (provider === 'postgresql') {\n invariant(dbName, 'dbName is required');\n const pgClient = new PGClient(TEST_PG_CONFIG);\n await pgClient.connect();\n await pgClient.query(`DROP DATABASE IF EXISTS \"${dbName}\"`);\n await pgClient.query(`CREATE DATABASE \"${dbName}\"`);\n await pgClient.end();\n }\n }\n\n if (provider === 'postgresql') {\n _options.dialect = new PostgresDialect({\n pool: new Pool({\n ...TEST_PG_CONFIG,\n database: dbName,\n }),\n });\n } else {\n _options.dialect = new SqliteDialect({\n database: new SQLite(path.join(workDir!, dbName)),\n });\n }\n\n let client = new ZenStackClient(_schema, _options);\n\n if (!options?.usePrismaPush) {\n await client.$pushSchema();\n }\n\n if (plugins) {\n for (const plugin of plugins) {\n client = client.$use(plugin);\n }\n }\n\n return client;\n}\n\nexport async function createPolicyTestClient<Schema extends SchemaDef>(\n schema: Schema,\n options?: CreateTestClientOptions<Schema>,\n): Promise<ClientContract<Schema>>;\nexport async function createPolicyTestClient<Schema extends SchemaDef>(\n schema: string,\n options?: CreateTestClientOptions<Schema>,\n): Promise<any>;\nexport async function createPolicyTestClient<Schema extends SchemaDef>(\n schema: Schema | string,\n options?: CreateTestClientOptions<Schema>,\n): Promise<any> {\n return createTestClient(\n schema as any,\n {\n ...options,\n plugins: [...(options?.plugins ?? []), new PolicyPlugin()],\n } as any,\n );\n}\n\nexport function testLogger(e: LogEvent) {\n console.log(e.query.sql, e.query.parameters);\n}\n\nfunction getTestDbName(provider: string) {\n if (provider === 'sqlite') {\n return './test.db';\n }\n const testName = expect.getState().currentTestName;\n const testPath = expect.getState().testPath ?? '';\n invariant(testName);\n // digest test name\n const digest = createHash('md5')\n .update(testName + testPath)\n .digest('hex');\n // compute a database name based on test name\n return (\n 'test_' +\n testName\n .toLowerCase()\n .replace(/[^a-z0-9_]/g, '_')\n .replace(/_+/g, '_')\n .substring(0, 30) +\n digest.slice(0, 6)\n );\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport tmp from 'tmp';\n\nexport function createTestProject(zmodelContent?: string) {\n const { name: workDir } = tmp.dirSync({ unsafeCleanup: true });\n\n fs.mkdirSync(path.join(workDir, 'node_modules'));\n\n // symlink all entries from \"node_modules\"\n const nodeModules = fs.readdirSync(path.join(__dirname, '../node_modules'));\n for (const entry of nodeModules) {\n if (entry.startsWith('@zenstackhq')) {\n continue;\n }\n fs.symlinkSync(\n path.join(__dirname, '../node_modules', entry),\n path.join(workDir, 'node_modules', entry),\n 'dir',\n );\n }\n\n // in addition, symlink zenstack packages\n const zenstackPackages = ['language', 'sdk', 'runtime', 'cli'];\n fs.mkdirSync(path.join(workDir, 'node_modules/@zenstackhq'));\n for (const pkg of zenstackPackages) {\n fs.symlinkSync(\n path.join(__dirname, `../../${pkg}`),\n path.join(workDir, `node_modules/@zenstackhq/${pkg}`),\n 'dir',\n );\n }\n\n fs.writeFileSync(\n path.join(workDir, 'package.json'),\n JSON.stringify(\n {\n name: 'test',\n version: '1.0.0',\n type: 'module',\n },\n null,\n 4,\n ),\n );\n\n fs.writeFileSync(\n path.join(workDir, 'tsconfig.json'),\n JSON.stringify(\n {\n compilerOptions: {\n module: 'ESNext',\n target: 'ESNext',\n moduleResolution: 'Bundler',\n esModuleInterop: true,\n skipLibCheck: true,\n strict: true,\n },\n include: ['**/*.ts'],\n },\n null,\n 4,\n ),\n );\n\n if (zmodelContent) {\n fs.writeFileSync(path.join(workDir, 'schema.zmodel'), zmodelContent);\n }\n\n return workDir;\n}\n","import { invariant } from '@zenstackhq/common-helpers';\nimport { TsSchemaGenerator } from '@zenstackhq/sdk';\nimport type { SchemaDef } from '@zenstackhq/sdk/schema';\nimport { execSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { match } from 'ts-pattern';\nimport { expect } from 'vitest';\nimport { createTestProject } from './project';\nimport { loadDocumentWithPlugins } from './utils';\n\nfunction makePrelude(provider: 'sqlite' | 'postgresql', dbUrl?: string) {\n return match(provider)\n .with('sqlite', () => {\n return `\ndatasource db {\n provider = 'sqlite'\n url = '${dbUrl ?? 'file:./test.db'}'\n}\n`;\n })\n .with('postgresql', () => {\n return `\ndatasource db {\n provider = 'postgresql'\n url = '${dbUrl ?? 'postgres://postgres:postgres@localhost:5432/db'}'\n}\n`;\n })\n .exhaustive();\n}\n\nexport async function generateTsSchema(\n schemaText: string,\n provider: 'sqlite' | 'postgresql' = 'sqlite',\n dbUrl?: string,\n extraSourceFiles?: Record<string, string>,\n) {\n const workDir = createTestProject();\n\n const zmodelPath = path.join(workDir, 'schema.zmodel');\n const noPrelude = schemaText.includes('datasource ');\n fs.writeFileSync(zmodelPath, `${noPrelude ? '' : makePrelude(provider, dbUrl)}\\n\\n${schemaText}`);\n\n const result = await loadDocumentWithPlugins(zmodelPath);\n if (!result.success) {\n throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`);\n }\n\n const generator = new TsSchemaGenerator();\n await generator.generate(result.model, workDir);\n\n if (extraSourceFiles) {\n for (const [fileName, content] of Object.entries(extraSourceFiles)) {\n const filePath = path.resolve(workDir, !fileName.endsWith('.ts') ? `${fileName}.ts` : fileName);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n fs.writeFileSync(filePath, content);\n }\n }\n\n // compile the generated TS schema\n return { ...(await compileAndLoad(workDir)), model: result.model };\n}\n\nasync function compileAndLoad(workDir: string) {\n execSync('npx tsc', {\n cwd: workDir,\n stdio: 'inherit',\n });\n\n // load the schema module\n const module = await import(path.join(workDir, 'schema.js'));\n return { workDir, schema: module.schema as SchemaDef };\n}\n\nexport function generateTsSchemaFromFile(filePath: string) {\n const schemaText = fs.readFileSync(filePath, 'utf8');\n return generateTsSchema(schemaText);\n}\n\nexport async function generateTsSchemaInPlace(schemaPath: string) {\n const workDir = path.dirname(schemaPath);\n const result = await loadDocumentWithPlugins(schemaPath);\n if (!result.success) {\n throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);\n }\n const generator = new TsSchemaGenerator();\n await generator.generate(result.model, workDir);\n return compileAndLoad(workDir);\n}\n\nexport async function loadSchema(schema: string, additionalSchemas?: Record<string, string>) {\n if (!schema.includes('datasource ')) {\n schema = `${makePrelude('sqlite')}\\n\\n${schema}`;\n }\n\n // create a temp folder\n const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zenstack-schema'));\n\n // create a temp file\n const tempFile = path.join(tempDir, `schema.zmodel`);\n fs.writeFileSync(tempFile, schema);\n\n if (additionalSchemas) {\n for (const [fileName, content] of Object.entries(additionalSchemas)) {\n let name = fileName;\n if (!name.endsWith('.zmodel')) {\n name += '.zmodel';\n }\n const filePath = path.join(tempDir, name);\n fs.writeFileSync(filePath, content);\n }\n }\n\n const r = await loadDocumentWithPlugins(tempFile);\n expect(r).toSatisfy(\n (r) => r.success,\n `Failed to load schema: ${(r as any).errors?.map((e: any) => e.toString()).join(', ')}`,\n );\n invariant(r.success);\n return r.model;\n}\n\nexport async function loadSchemaWithError(schema: string, error: string | RegExp) {\n if (!schema.includes('datasource ')) {\n schema = `${makePrelude('sqlite')}\\n\\n${schema}`;\n }\n\n // create a temp file\n const tempFile = path.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`);\n fs.writeFileSync(tempFile, schema);\n const r = await loadDocumentWithPlugins(tempFile);\n expect(r.success).toBe(false);\n invariant(!r.success);\n if (typeof error === 'string') {\n expect(r).toSatisfy(\n (r) => r.errors.some((e: any) => e.toString().toLowerCase().includes(error.toLowerCase())),\n `Expected error message to include \"${error}\" but got: ${r.errors.map((e: any) => e.toString()).join(', ')}`,\n );\n } else {\n expect(r).toSatisfy(\n (r) => r.errors.some((e: any) => error.test(e)),\n `Expected error message to match \"${error}\" but got: ${r.errors.map((e: any) => e.toString()).join(', ')}`,\n );\n }\n}\n","import { loadDocument } from '@zenstackhq/language';\n\nexport function loadDocumentWithPlugins(filePath: string) {\n const pluginModelFiles = [require.resolve('@zenstackhq/plugin-policy/plugin.zmodel')];\n return loadDocument(filePath, pluginModelFiles);\n}\n","import { InputValidationError, NotFoundError, RejectedByPolicyError } from '@zenstackhq/runtime';\nimport { expect } from 'vitest';\n\nfunction isPromise(value: any) {\n return typeof value.then === 'function' && typeof value.catch === 'function';\n}\n\nfunction expectError(err: any, errorType: any) {\n if (err instanceof errorType) {\n return {\n message: () => '',\n pass: true,\n };\n } else {\n return {\n message: () => `expected ${errorType}, got ${err}`,\n pass: false,\n };\n }\n}\n\nfunction expectErrorMessages(expectedMessages: string[], message: string) {\n for (const m of expectedMessages) {\n if (!message.includes(m)) {\n return {\n message: () => `expected message not found in error: ${m}, got message: ${message}`,\n pass: false,\n };\n }\n }\n return undefined;\n}\n\nexpect.extend({\n async toResolveTruthy(received: Promise<unknown>) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n const r = await received;\n return {\n pass: !!r,\n message: () => `Expected promise to resolve to a truthy value, but got ${r}`,\n };\n },\n\n async toResolveFalsy(received: Promise<unknown>) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n const r = await received;\n return {\n pass: !r,\n message: () => `Expected promise to resolve to a falsy value, but got ${r}`,\n };\n },\n\n async toResolveNull(received: Promise<unknown>) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n const r = await received;\n return {\n pass: r === null,\n message: () => `Expected promise to resolve to a null value, but got ${r}`,\n };\n },\n\n async toResolveWithLength(received: Promise<unknown>, length: number) {\n const r = await received;\n return {\n pass: Array.isArray(r) && r.length === length,\n message: () => `Expected promise to resolve with an array with length ${length}, but got ${r}`,\n };\n },\n\n async toBeRejectedNotFound(received: Promise<unknown>) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n try {\n await received;\n } catch (err) {\n return expectError(err, NotFoundError);\n }\n return {\n message: () => `expected NotFoundError, got no error`,\n pass: false,\n };\n },\n\n async toBeRejectedByPolicy(received: Promise<unknown>, expectedMessages?: string[]) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n try {\n await received;\n } catch (err) {\n if (expectedMessages && err instanceof RejectedByPolicyError) {\n const r = expectErrorMessages(expectedMessages, err.message || '');\n if (r) {\n return r;\n }\n }\n return expectError(err, RejectedByPolicyError);\n }\n return {\n message: () => `expected PolicyError, got no error`,\n pass: false,\n };\n },\n\n async toBeRejectedByValidation(received: Promise<unknown>, expectedMessages?: string[]) {\n if (!isPromise(received)) {\n return { message: () => 'a promise is expected', pass: false };\n }\n try {\n await received;\n } catch (err) {\n if (expectedMessages && err instanceof InputValidationError) {\n const r = expectErrorMessages(expectedMessages, err.message || '');\n if (r) {\n return r;\n }\n }\n return expectError(err, InputValidationError);\n }\n return {\n message: () => `expected InputValidationError, got no error`,\n pass: false,\n };\n },\n});\n"],"mappings":"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","names":["invariant","PolicyPlugin","ZenStackClient","PrismaSchemaGenerator","SQLite","PostgresDialect","SqliteDialect","execSync","createHash","fs","path","Client","PGClient","Pool","expect","fs","path","tmp","createTestProject","zmodelContent","name","workDir","tmp","dirSync","unsafeCleanup","fs","mkdirSync","path","join","nodeModules","readdirSync","__dirname","entry","startsWith","symlinkSync","zenstackPackages","pkg","writeFileSync","JSON","stringify","version","type","compilerOptions","module","target","moduleResolution","esModuleInterop","skipLibCheck","strict","include","invariant","TsSchemaGenerator","execSync","crypto","fs","os","path","match","expect","loadDocument","loadDocumentWithPlugins","filePath","pluginModelFiles","require","resolve","loadDocument","makePrelude","provider","dbUrl","match","with","exhaustive","generateTsSchema","schemaText","extraSourceFiles","workDir","createTestProject","zmodelPath","path","join","noPrelude","includes","fs","writeFileSync","result","loadDocumentWithPlugins","success","Error","errors","generator","TsSchemaGenerator","generate","model","fileName","content","Object","entries","filePath","resolve","endsWith","mkdirSync","dirname","recursive","compileAndLoad","execSync","cwd","stdio","module","schema","generateTsSchemaFromFile","readFileSync","generateTsSchemaInPlace","schemaPath","loadSchema","additionalSchemas","tempDir","mkdtempSync","os","tmpdir","tempFile","name","r","expect","toSatisfy","map","e","toString","invariant","loadSchemaWithError","error","crypto","randomUUID","toBe","some","toLowerCase","test","getTestDbProvider","val","process","env","includes","Error","TEST_PG_CONFIG","host","port","parseInt","user","password","createTestClient","schema","options","schemaFile","workDir","_schema","provider","dbName","getTestDbName","dbUrl","model","generated","generateTsSchema","extraSourceFiles","type","createTestProject","schemaContent","fs","readFileSync","replace","writeFileSync","path","join","invariant","debug","console","log","plugins","rest","_options","usePrismaPush","r","loadDocumentWithPlugins","success","errors","prismaSchema","PrismaSchemaGenerator","prismaSchemaText","generate","resolve","execSync","cwd","stdio","pgClient","PGClient","connect","query","end","dialect","PostgresDialect","pool","Pool","database","SqliteDialect","SQLite","client","ZenStackClient","$pushSchema","plugin","$use","createPolicyTestClient","PolicyPlugin","testLogger","e","sql","parameters","testName","expect","getState","currentTestName","testPath","digest","createHash","update","toLowerCase","substring","slice","InputValidationError","NotFoundError","RejectedByPolicyError","expect","isPromise","value","then","catch","expectError","err","errorType","message","pass","expectErrorMessages","expectedMessages","m","includes","undefined","expect","extend","toResolveTruthy","received","r","toResolveFalsy","toResolveNull","toResolveWithLength","length","Array","isArray","toBeRejectedNotFound","NotFoundError","toBeRejectedByPolicy","RejectedByPolicyError","toBeRejectedByValidation","InputValidationError"]}
@@ -0,0 +1,16 @@
1
+ import 'vitest';
2
+
3
+ interface CustomMatchers<R = unknown> {
4
+ toResolveTruthy: () => Promise<R>;
5
+ toResolveFalsy: () => Promise<R>;
6
+ toResolveNull: () => Promise<R>;
7
+ toResolveWithLength: (length: number) => Promise<R>;
8
+ toBeRejectedNotFound: () => Promise<R>;
9
+ toBeRejectedByPolicy: (expectedMessages?: string[]) => Promise<R>;
10
+ toBeRejectedByValidation: (expectedMessages?: string[]) => Promise<R>;
11
+ }
12
+
13
+ declare module 'vitest' {
14
+ interface Assertion<T = any> extends CustomMatchers<T> {}
15
+ interface AsymmetricMatchersContaining extends CustomMatchers {}
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenstackhq/testtools",
3
- "version": "3.0.0-beta.1",
3
+ "version": "3.0.0-beta.10",
4
4
  "description": "ZenStack Test Tools",
5
5
  "type": "module",
6
6
  "keywords": [],
@@ -19,26 +19,35 @@
19
19
  "types": "./dist/index.d.cts",
20
20
  "default": "./dist/index.cjs"
21
21
  }
22
+ },
23
+ "./types": {
24
+ "types": "./dist/types.d.ts"
22
25
  }
23
26
  },
24
27
  "dependencies": {
25
28
  "glob": "^11.0.2",
29
+ "kysely": "^0.27.6",
30
+ "prisma": "^6.10.0",
26
31
  "tmp": "^0.2.3",
27
32
  "ts-pattern": "^5.7.1",
28
- "prisma": "^6.10.0",
29
- "typescript": "^5.8.0",
30
- "@zenstackhq/language": "3.0.0-beta.1",
31
- "@zenstackhq/sdk": "3.0.0-beta.1"
33
+ "@zenstackhq/common-helpers": "3.0.0-beta.10",
34
+ "@zenstackhq/language": "3.0.0-beta.10",
35
+ "@zenstackhq/runtime": "3.0.0-beta.10",
36
+ "@zenstackhq/sdk": "3.0.0-beta.10",
37
+ "@zenstackhq/plugin-policy": "3.0.0-beta.10"
32
38
  },
33
39
  "peerDependencies": {
34
40
  "better-sqlite3": "^12.2.0",
35
41
  "pg": "^8.13.1"
36
42
  },
37
43
  "devDependencies": {
44
+ "@types/better-sqlite3": "^7.6.13",
38
45
  "@types/node": "^20.17.24",
39
46
  "@types/tmp": "^0.2.6",
40
- "@zenstackhq/typescript-config": "3.0.0-beta.1",
41
- "@zenstackhq/eslint-config": "3.0.0-beta.1"
47
+ "@types/pg": "^8.11.11",
48
+ "typescript": "^5.8.0",
49
+ "@zenstackhq/eslint-config": "3.0.0-beta.10",
50
+ "@zenstackhq/typescript-config": "3.0.0-beta.10"
42
51
  },
43
52
  "scripts": {
44
53
  "build": "tsc --noEmit && tsup-node",