Skip to content

Commit 2822a34

Browse files
feat(pgsql-test): add IdHash support for snapshot utilities
1 parent 4138d46 commit 2822a34

2 files changed

Lines changed: 97 additions & 10 deletions

File tree

packages/pgsql-test/__tests__/utils.test.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
pruneIds,
66
pruneIdArrays,
77
pruneUUIDs,
8-
pruneHashes
8+
pruneHashes,
9+
IdHash
910
} from '../src/utils';
1011

1112
describe('snapshot utilities', () => {
@@ -46,6 +47,7 @@ describe('snapshot utilities', () => {
4647
const row: Record<string, unknown> = { id: null, user_id: undefined };
4748
expect(pruneIds(row)).toEqual({ id: null, user_id: undefined });
4849
});
50+
4951
});
5052

5153
describe('pruneIdArrays', () => {
@@ -113,6 +115,7 @@ describe('snapshot utilities', () => {
113115
name: 'Alice'
114116
});
115117
});
118+
116119
});
117120

118121
describe('snapshot', () => {
@@ -177,5 +180,72 @@ describe('snapshot utilities', () => {
177180
expect(snapshot(null)).toBe(null);
178181
expect(snapshot(undefined)).toBe(undefined);
179182
});
183+
184+
});
185+
186+
describe('IdHash support', () => {
187+
it('pruneIds uses IdHash to map IDs to numbered placeholders', () => {
188+
const idHash: IdHash = { 'uuid-1': 1, 'uuid-2': 2 };
189+
const row = { id: 'uuid-1', user_id: 'uuid-2', org_id: 'unknown', name: 'Alice' };
190+
expect(pruneIds(row, idHash)).toEqual({
191+
id: '[ID-1]',
192+
user_id: '[ID-2]',
193+
org_id: '[ID]',
194+
name: 'Alice'
195+
});
196+
});
197+
198+
it('prune applies IdHash mapping when provided', () => {
199+
const idHash: IdHash = { '1': 1, '2': 2 };
200+
const row = { id: 1, user_id: 2, name: 'Alice' };
201+
expect(prune(row, idHash)).toEqual({
202+
id: '[ID-1]',
203+
user_id: '[ID-2]',
204+
name: 'Alice'
205+
});
206+
});
207+
208+
it('snapshot uses IdHash to preserve ID relationships', () => {
209+
const idHash: IdHash = {
210+
'user-uuid-1': 1,
211+
'user-uuid-2': 2,
212+
'post-uuid-1': 3
213+
};
214+
const data = [
215+
{ id: 'user-uuid-1', name: 'Alice', post_id: 'post-uuid-1' },
216+
{ id: 'user-uuid-2', name: 'Bob', post_id: 'post-uuid-1' }
217+
];
218+
expect(snapshot(data, idHash)).toEqual([
219+
{ id: '[ID-1]', name: 'Alice', post_id: '[ID-3]' },
220+
{ id: '[ID-2]', name: 'Bob', post_id: '[ID-3]' }
221+
]);
222+
});
223+
224+
it('snapshot uses IdHash with nested objects', () => {
225+
const idHash: IdHash = { 'org-1': 1, 'user-1': 2 };
226+
const data = {
227+
org: { id: 'org-1', name: 'Acme' },
228+
user: { id: 'user-1', org_id: 'org-1' }
229+
};
230+
expect(snapshot(data, idHash)).toEqual({
231+
org: { id: '[ID-1]', name: 'Acme' },
232+
user: { id: '[ID-2]', org_id: '[ID-1]' }
233+
});
234+
});
235+
236+
it('snapshot uses IdHash with string labels', () => {
237+
const idHash: IdHash = {
238+
'uuid-user-1': 'user1',
239+
'uuid-user-2': 'user2',
240+
'uuid-group-1': 'group1',
241+
'uuid-group-2': 'group2'
242+
};
243+
const data = [
244+
{ actor_id: 'uuid-user-2', entity_id: 'uuid-group-2', is_admin: true }
245+
];
246+
expect(snapshot(data, idHash)).toEqual([
247+
{ actor_id: '[ID-user2]', entity_id: '[ID-group2]', is_admin: true }
248+
]);
249+
});
180250
});
181251
});

packages/pgsql-test/src/utils.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
22

3-
const idReplacement = (v: unknown): string | unknown => (!v ? v : '[ID]');
3+
// ID hash map for tracking ID relationships in snapshots
4+
// Values can be numbers (e.g., 1 -> [ID-1]) or strings (e.g., 'user2' -> [ID-user2])
5+
export type IdHash = Record<string, number | string>;
6+
7+
const idReplacement = (v: unknown, idHash?: IdHash): string | unknown => {
8+
if (!v) return v;
9+
if (!idHash) return '[ID]';
10+
const key = String(v);
11+
return idHash[key] !== undefined ? `[ID-${idHash[key]}]` : '[ID]';
12+
};
413

514
// Generic object type for any key-value mapping
615
type AnyObject = Record<string, any>;
@@ -32,11 +41,11 @@ export const pruneDates = (row: AnyObject): AnyObject =>
3241
return v;
3342
});
3443

35-
export const pruneIds = (row: AnyObject): AnyObject =>
44+
export const pruneIds = (row: AnyObject, idHash?: IdHash): AnyObject =>
3645
mapValues(row, (v, k) =>
3746
(k === 'id' || (typeof k === 'string' && k.endsWith('_id'))) &&
3847
(typeof v === 'string' || typeof v === 'number')
39-
? idReplacement(v)
48+
? idReplacement(v, idHash)
4049
: v
4150
);
4251

@@ -95,27 +104,35 @@ export const composePruners = (...pruners: Pruner[]): Pruner =>
95104
(row: AnyObject): AnyObject =>
96105
pruners.reduce((acc, pruner) => pruner(acc), row);
97106

98-
// Default pruners used by prune/snapshot
107+
// Pruner with optional IdHash support
108+
type PrunerWithIdHash = (row: AnyObject, idHash?: IdHash) => AnyObject;
109+
110+
// Default pruners used by prune/snapshot (without IdHash)
99111
export const defaultPruners: Pruner[] = [
100112
pruneTokens,
101113
prunePeoplestamps,
102114
pruneDates,
103115
pruneIdArrays,
104-
pruneIds,
105116
pruneUUIDs,
106117
pruneHashes
107118
];
108119

109-
export const prune = composePruners(...defaultPruners);
120+
// Compose pruners and apply pruneIds with IdHash support
121+
export const prune = (row: AnyObject, idHash?: IdHash): AnyObject => {
122+
const pruned = composePruners(...defaultPruners)(row);
123+
return pruneIds(pruned, idHash);
124+
};
110125

111126
// Factory to create a snapshot function with custom pruners
112127
export const createSnapshot = (pruners: Pruner[]) => {
113128
const pruneFn = composePruners(...pruners);
114-
const snap = (obj: unknown): unknown => {
129+
const snap = (obj: unknown, idHash?: IdHash): unknown => {
115130
if (Array.isArray(obj)) {
116-
return obj.map(snap);
131+
return obj.map((el) => snap(el, idHash));
117132
} else if (obj && typeof obj === 'object') {
118-
return mapValues(pruneFn(obj as AnyObject), snap);
133+
const pruned = pruneFn(obj as AnyObject);
134+
const prunedWithIds = pruneIds(pruned, idHash);
135+
return mapValues(prunedWithIds, (v) => snap(v, idHash));
119136
}
120137
return obj;
121138
};

0 commit comments

Comments
 (0)