pgsql-test 2.14.10 → 2.14.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -631,6 +631,44 @@ const { conn, db, teardown } = await getConnections({
631
631
  });
632
632
  ```
633
633
 
634
+ ## Snapshot Utilities
635
+
636
+ The `pgsql-test/utils` module provides utilities for sanitizing database query results for snapshot testing. These helpers replace dynamic values (IDs, UUIDs, dates, hashes) with stable placeholders, making snapshots deterministic.
637
+
638
+ ```ts
639
+ import { snapshot } from 'pgsql-test/utils';
640
+
641
+ const result = await db.any('SELECT * FROM users');
642
+ expect(snapshot(result)).toMatchSnapshot();
643
+ ```
644
+
645
+ ### Available Functions
646
+
647
+ | Function | Description |
648
+ |----------|-------------|
649
+ | `snapshot(obj)` | Recursively prunes all dynamic values from an object or array |
650
+ | `prune(obj)` | Applies all prune functions to a single object |
651
+ | `pruneDates(obj)` | Replaces `Date` objects and date strings (fields ending in `_at` or `At`) with `[DATE]` |
652
+ | `pruneIds(obj)` | Replaces `id` and `*_id` fields with `[ID]` |
653
+ | `pruneIdArrays(obj)` | Replaces `*_ids` array fields with `[UUIDs-N]` |
654
+ | `pruneUUIDs(obj)` | Replaces UUID strings in `uuid` and `queue_name` fields with `[UUID]` |
655
+ | `pruneHashes(obj)` | Replaces `*_hash` fields starting with `$` with `[hash]` |
656
+
657
+ ### Example
658
+
659
+ ```ts
660
+ import { snapshot, pruneIds, pruneDates } from 'pgsql-test/utils';
661
+
662
+ // Full sanitization
663
+ const users = await db.any('SELECT * FROM users');
664
+ expect(snapshot(users)).toMatchSnapshot();
665
+
666
+ // Selective sanitization
667
+ const row = await db.one('SELECT id, name, created_at FROM users WHERE id = $1', [1]);
668
+ const sanitized = pruneDates(pruneIds(row));
669
+ // { id: '[ID]', name: 'Alice', created_at: '[DATE]' }
670
+ ```
671
+
634
672
  ---
635
673
 
636
674
  ## Education and Tutorials
package/esm/index.js CHANGED
@@ -4,3 +4,4 @@ export * from './manager';
4
4
  export * from './roles';
5
5
  export * from './seed';
6
6
  export * from './test-client';
7
+ export { snapshot } from './utils';
package/esm/utils.js ADDED
@@ -0,0 +1,57 @@
1
+ const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2
+ const idReplacement = (v) => (!v ? v : '[ID]');
3
+ function mapValues(obj, fn) {
4
+ return Object.entries(obj).reduce((acc, [key, value]) => {
5
+ acc[key] = fn(value, key);
6
+ return acc;
7
+ }, {});
8
+ }
9
+ export const pruneDates = (row) => mapValues(row, (v, k) => {
10
+ if (!v) {
11
+ return v;
12
+ }
13
+ if (v instanceof Date) {
14
+ return '[DATE]';
15
+ }
16
+ else if (typeof v === 'string' &&
17
+ /(_at|At)$/.test(k) &&
18
+ /^20[0-9]{2}-[0-9]{2}-[0-9]{2}/.test(v)) {
19
+ return '[DATE]';
20
+ }
21
+ return v;
22
+ });
23
+ export const pruneIds = (row) => mapValues(row, (v, k) => (k === 'id' || (typeof k === 'string' && k.endsWith('_id'))) &&
24
+ (typeof v === 'string' || typeof v === 'number')
25
+ ? idReplacement(v)
26
+ : v);
27
+ export const pruneIdArrays = (row) => mapValues(row, (v, k) => typeof k === 'string' && k.endsWith('_ids') && Array.isArray(v)
28
+ ? `[UUIDs-${v.length}]`
29
+ : v);
30
+ export const pruneUUIDs = (row) => mapValues(row, (v, k) => {
31
+ if (typeof v !== 'string') {
32
+ return v;
33
+ }
34
+ if (['uuid', 'queue_name'].includes(k) && uuidRegexp.test(v)) {
35
+ return '[UUID]';
36
+ }
37
+ if (k === 'gravatar' && /^[0-9a-f]{32}$/i.test(v)) {
38
+ return '[gUUID]';
39
+ }
40
+ return v;
41
+ });
42
+ export const pruneHashes = (row) => mapValues(row, (v, k) => typeof k === 'string' &&
43
+ k.endsWith('_hash') &&
44
+ typeof v === 'string' &&
45
+ v.startsWith('$')
46
+ ? '[hash]'
47
+ : v);
48
+ export const prune = (obj) => pruneHashes(pruneUUIDs(pruneIds(pruneIdArrays(pruneDates(obj)))));
49
+ export const snapshot = (obj) => {
50
+ if (Array.isArray(obj)) {
51
+ return obj.map(snapshot);
52
+ }
53
+ else if (obj && typeof obj === 'object') {
54
+ return mapValues(prune(obj), snapshot);
55
+ }
56
+ return obj;
57
+ };
package/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from './manager';
4
4
  export * from './roles';
5
5
  export * from './seed';
6
6
  export * from './test-client';
7
+ export { snapshot } from './utils';
package/index.js CHANGED
@@ -14,9 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.snapshot = void 0;
17
18
  __exportStar(require("./admin"), exports);
18
19
  __exportStar(require("./connect"), exports);
19
20
  __exportStar(require("./manager"), exports);
20
21
  __exportStar(require("./roles"), exports);
21
22
  __exportStar(require("./seed"), exports);
22
23
  __exportStar(require("./test-client"), exports);
24
+ var utils_1 = require("./utils");
25
+ Object.defineProperty(exports, "snapshot", { enumerable: true, get: function () { return utils_1.snapshot; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgsql-test",
3
- "version": "2.14.10",
3
+ "version": "2.14.12",
4
4
  "author": "Dan Lynch <pyramation@gmail.com>",
5
5
  "description": "pgsql-test offers isolated, role-aware, and rollback-friendly PostgreSQL environments for integration tests — giving developers realistic test coverage without external state pollution",
6
6
  "main": "index.js",
@@ -57,19 +57,19 @@
57
57
  "devDependencies": {
58
58
  "@types/pg": "^8.15.2",
59
59
  "@types/pg-copy-streams": "^1.2.5",
60
- "makage": "^0.1.6"
60
+ "makage": "^0.1.8"
61
61
  },
62
62
  "dependencies": {
63
- "@launchql/core": "^2.17.4",
64
- "@launchql/env": "^2.5.5",
65
- "@launchql/logger": "^1.1.11",
66
- "@launchql/server-utils": "^2.6.7",
67
- "@launchql/types": "^2.8.5",
63
+ "@launchql/core": "^2.17.6",
64
+ "@launchql/env": "^2.5.6",
65
+ "@launchql/logger": "^1.1.12",
66
+ "@launchql/server-utils": "^2.6.8",
67
+ "@launchql/types": "^2.8.6",
68
68
  "csv-parse": "^6.1.0",
69
69
  "pg": "^8.16.0",
70
- "pg-cache": "^1.4.7",
70
+ "pg-cache": "^1.4.8",
71
71
  "pg-copy-streams": "^7.0.0",
72
- "pg-env": "^1.1.6"
72
+ "pg-env": "^1.1.7"
73
73
  },
74
- "gitHead": "e85e82c301d859ff7e2317bf07345156af9ff8d1"
74
+ "gitHead": "3812f24a480b2035b3413ec7fecfe492f294e590"
75
75
  }
package/utils.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ type AnyObject = Record<string, any>;
2
+ export declare const pruneDates: (row: AnyObject) => AnyObject;
3
+ export declare const pruneIds: (row: AnyObject) => AnyObject;
4
+ export declare const pruneIdArrays: (row: AnyObject) => AnyObject;
5
+ export declare const pruneUUIDs: (row: AnyObject) => AnyObject;
6
+ export declare const pruneHashes: (row: AnyObject) => AnyObject;
7
+ export declare const prune: (obj: AnyObject) => AnyObject;
8
+ export declare const snapshot: (obj: unknown) => unknown;
9
+ export {};
package/utils.js ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.snapshot = exports.prune = exports.pruneHashes = exports.pruneUUIDs = exports.pruneIdArrays = exports.pruneIds = exports.pruneDates = void 0;
4
+ const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
+ const idReplacement = (v) => (!v ? v : '[ID]');
6
+ function mapValues(obj, fn) {
7
+ return Object.entries(obj).reduce((acc, [key, value]) => {
8
+ acc[key] = fn(value, key);
9
+ return acc;
10
+ }, {});
11
+ }
12
+ const pruneDates = (row) => mapValues(row, (v, k) => {
13
+ if (!v) {
14
+ return v;
15
+ }
16
+ if (v instanceof Date) {
17
+ return '[DATE]';
18
+ }
19
+ else if (typeof v === 'string' &&
20
+ /(_at|At)$/.test(k) &&
21
+ /^20[0-9]{2}-[0-9]{2}-[0-9]{2}/.test(v)) {
22
+ return '[DATE]';
23
+ }
24
+ return v;
25
+ });
26
+ exports.pruneDates = pruneDates;
27
+ const pruneIds = (row) => mapValues(row, (v, k) => (k === 'id' || (typeof k === 'string' && k.endsWith('_id'))) &&
28
+ (typeof v === 'string' || typeof v === 'number')
29
+ ? idReplacement(v)
30
+ : v);
31
+ exports.pruneIds = pruneIds;
32
+ const pruneIdArrays = (row) => mapValues(row, (v, k) => typeof k === 'string' && k.endsWith('_ids') && Array.isArray(v)
33
+ ? `[UUIDs-${v.length}]`
34
+ : v);
35
+ exports.pruneIdArrays = pruneIdArrays;
36
+ const pruneUUIDs = (row) => mapValues(row, (v, k) => {
37
+ if (typeof v !== 'string') {
38
+ return v;
39
+ }
40
+ if (['uuid', 'queue_name'].includes(k) && uuidRegexp.test(v)) {
41
+ return '[UUID]';
42
+ }
43
+ if (k === 'gravatar' && /^[0-9a-f]{32}$/i.test(v)) {
44
+ return '[gUUID]';
45
+ }
46
+ return v;
47
+ });
48
+ exports.pruneUUIDs = pruneUUIDs;
49
+ const pruneHashes = (row) => mapValues(row, (v, k) => typeof k === 'string' &&
50
+ k.endsWith('_hash') &&
51
+ typeof v === 'string' &&
52
+ v.startsWith('$')
53
+ ? '[hash]'
54
+ : v);
55
+ exports.pruneHashes = pruneHashes;
56
+ const prune = (obj) => (0, exports.pruneHashes)((0, exports.pruneUUIDs)((0, exports.pruneIds)((0, exports.pruneIdArrays)((0, exports.pruneDates)(obj)))));
57
+ exports.prune = prune;
58
+ const snapshot = (obj) => {
59
+ if (Array.isArray(obj)) {
60
+ return obj.map(exports.snapshot);
61
+ }
62
+ else if (obj && typeof obj === 'object') {
63
+ return mapValues((0, exports.prune)(obj), exports.snapshot);
64
+ }
65
+ return obj;
66
+ };
67
+ exports.snapshot = snapshot;