@heroku/js-blanket 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/cjs/.tsbuildinfo +1 -0
- package/dist/cjs/adapters/logging/generic.js +23 -0
- package/dist/cjs/adapters/logging/generic.js.map +1 -0
- package/dist/cjs/adapters/logging/generic.test.js +432 -0
- package/dist/cjs/adapters/logging/generic.test.js.map +1 -0
- package/dist/cjs/core/patterns.js +17 -0
- package/dist/cjs/core/patterns.js.map +1 -0
- package/dist/cjs/core/presets.js +116 -0
- package/dist/cjs/core/presets.js.map +1 -0
- package/dist/cjs/core/scrubber.js +260 -0
- package/dist/cjs/core/scrubber.js.map +1 -0
- package/dist/cjs/core/scrubber.test.js +392 -0
- package/dist/cjs/core/scrubber.test.js.map +1 -0
- package/dist/cjs/core/types.js +3 -0
- package/dist/cjs/core/types.js.map +1 -0
- package/dist/cjs/core/types.test.js +326 -0
- package/dist/cjs/core/types.test.js.map +1 -0
- package/dist/cjs/index.js +16 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/index.test.js +31 -0
- package/dist/cjs/index.test.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/.tsbuildinfo +1 -0
- package/{src/adapters/logging/generic.ts → dist/esm/adapters/logging/generic.d.ts} +1 -4
- package/dist/esm/adapters/logging/generic.js +20 -0
- package/dist/esm/adapters/logging/generic.js.map +1 -0
- package/dist/esm/adapters/logging/generic.test.d.ts +7 -0
- package/dist/esm/adapters/logging/generic.test.js +430 -0
- package/dist/esm/adapters/logging/generic.test.js.map +1 -0
- package/dist/esm/core/patterns.d.ts +4 -0
- package/dist/esm/core/patterns.js +14 -0
- package/dist/esm/core/patterns.js.map +1 -0
- package/dist/esm/core/presets.d.ts +64 -0
- package/{src/core/presets.ts → dist/esm/core/presets.js} +46 -55
- package/dist/esm/core/presets.js.map +1 -0
- package/dist/esm/core/scrubber.d.ts +131 -0
- package/dist/esm/core/scrubber.js +256 -0
- package/dist/esm/core/scrubber.js.map +1 -0
- package/dist/esm/core/scrubber.test.d.ts +1 -0
- package/dist/esm/core/scrubber.test.js +390 -0
- package/dist/esm/core/scrubber.test.js.map +1 -0
- package/dist/esm/core/types.d.ts +169 -0
- package/dist/esm/core/types.js +2 -0
- package/dist/esm/core/types.js.map +1 -0
- package/dist/esm/core/types.test.d.ts +9 -0
- package/dist/esm/core/types.test.js +324 -0
- package/dist/esm/core/types.test.js.map +1 -0
- package/{src/index.ts → dist/esm/index.d.ts} +0 -3
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/index.test.d.ts +1 -0
- package/dist/esm/index.test.js +29 -0
- package/dist/esm/index.test.js.map +1 -0
- package/package.json +45 -47
- package/.c8rc.json +0 -11
- package/.editorconfig +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -41
- package/.github/copilot-instructions.md +0 -117
- package/.github/workflows/ci.yml +0 -25
- package/.husky/pre-commit +0 -1
- package/.lintstagedrc.json +0 -4
- package/.tool-versions +0 -1
- package/CODEOWNERS +0 -8
- package/CODE_OF_CONDUCT.md +0 -111
- package/CONTRIBUTING.md +0 -123
- package/SECURITY.md +0 -8
- package/docs/examples/logging-integration.md +0 -736
- package/eslint.config.mjs +0 -108
- package/prettier.config.mjs +0 -10
- package/scripts/test-setup.mjs +0 -24
- package/src/adapters/logging/generic.test.ts +0 -531
- package/src/core/patterns.ts +0 -22
- package/src/core/scrubber.test.ts +0 -465
- package/src/core/scrubber.ts +0 -284
- package/src/core/types.test.ts +0 -516
- package/src/core/types.ts +0 -176
- package/src/index.test.ts +0 -41
- package/tsconfig.cjs.json +0 -12
- package/tsconfig.esm.json +0 -12
- package/tsconfig.json +0 -32
- package/tsconfig.test.json +0 -9
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'mocha';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
import { Scrubber } from './scrubber.js';
|
|
4
|
-
|
|
5
|
-
describe('Scrubber', () => {
|
|
6
|
-
describe('Field-based scrubbing', () => {
|
|
7
|
-
it('scrubs sensitive fields at any depth', () => {
|
|
8
|
-
const scrubber = new Scrubber({
|
|
9
|
-
fields: ['access_token'],
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const input = {
|
|
13
|
-
user: {
|
|
14
|
-
profile: {
|
|
15
|
-
settings: {
|
|
16
|
-
auth: { access_token: 'secret123' },
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const { data } = scrubber.scrub(input);
|
|
23
|
-
expect(data.user.profile.settings.auth.access_token).to.equal(
|
|
24
|
-
'[SCRUBBED]'
|
|
25
|
-
);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('handles case-insensitive field matching', () => {
|
|
29
|
-
const scrubber = new Scrubber({
|
|
30
|
-
fields: ['password'],
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const input = {
|
|
34
|
-
Password: 'secret1',
|
|
35
|
-
PASSWORD: 'secret2',
|
|
36
|
-
password: 'secret3',
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const { data } = scrubber.scrub(input);
|
|
40
|
-
expect(data.Password).to.equal('[SCRUBBED]');
|
|
41
|
-
expect(data.PASSWORD).to.equal('[SCRUBBED]');
|
|
42
|
-
expect(data.password).to.equal('[SCRUBBED]');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('supports regex field patterns', () => {
|
|
46
|
-
const scrubber = new Scrubber({
|
|
47
|
-
fields: [/api[-_]?key/i], // Matches api_key, api-key, apikey (case insensitive)
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const input = {
|
|
51
|
-
user_api_key: 'secret',
|
|
52
|
-
API_KEY_V2: 'secret',
|
|
53
|
-
myApiKeyHere: 'secret',
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const { data } = scrubber.scrub(input);
|
|
57
|
-
expect(data.user_api_key).to.equal('[SCRUBBED]');
|
|
58
|
-
expect(data.API_KEY_V2).to.equal('[SCRUBBED]');
|
|
59
|
-
expect(data.myApiKeyHere).to.equal('[SCRUBBED]');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('Path-based scrubbing', () => {
|
|
64
|
-
it('scrubs specific paths only', () => {
|
|
65
|
-
const scrubber = new Scrubber({
|
|
66
|
-
paths: ['user.profile.email'],
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const input = {
|
|
70
|
-
user: {
|
|
71
|
-
profile: { email: 'bob@example.com', name: 'Bob' },
|
|
72
|
-
settings: { email: 'notifications@example.com' },
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const { data } = scrubber.scrub(input);
|
|
77
|
-
expect(data.user.profile.email).to.equal('[SCRUBBED]');
|
|
78
|
-
expect(data.user.settings.email).to.equal('notifications@example.com');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('scrubs array items by index', () => {
|
|
82
|
-
const scrubber = new Scrubber({
|
|
83
|
-
paths: ['users[0].password'],
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const input = {
|
|
87
|
-
users: [
|
|
88
|
-
{ name: 'bob', password: 'secret1' },
|
|
89
|
-
{ name: 'alice', password: 'secret2' },
|
|
90
|
-
],
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const { data } = scrubber.scrub(input);
|
|
94
|
-
expect(data.users?.[0]?.password).to.equal('[SCRUBBED]');
|
|
95
|
-
expect(data.users?.[1]?.password).to.equal('secret2');
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('Pattern-based scrubbing', () => {
|
|
100
|
-
it('scrubs SSN patterns in strings', () => {
|
|
101
|
-
const scrubber = new Scrubber({
|
|
102
|
-
patterns: [/\b\d{3}-\d{2}-\d{4}\b/g],
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const input = { message: 'User SSN is 123-45-6789' };
|
|
106
|
-
const { data } = scrubber.scrub(input);
|
|
107
|
-
expect(data.message).to.contain('[SCRUBBED]');
|
|
108
|
-
expect(data.message).not.to.contain('123-45-6789');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('scrubs email patterns in strings', () => {
|
|
112
|
-
const scrubber = new Scrubber({
|
|
113
|
-
patterns: [/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g],
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const input = { log: 'Auth failed for user@example.com' };
|
|
117
|
-
const { data } = scrubber.scrub(input);
|
|
118
|
-
expect(data.log).to.contain('[SCRUBBED]');
|
|
119
|
-
expect(data.log).not.to.contain('user@example.com');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('scrubs multiple patterns in same string', () => {
|
|
123
|
-
const scrubber = new Scrubber({
|
|
124
|
-
patterns: [
|
|
125
|
-
/\b\d{3}-\d{2}-\d{4}\b/g, // SSN
|
|
126
|
-
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Email
|
|
127
|
-
],
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const input = {
|
|
131
|
-
log: 'User bob@example.com has SSN 123-45-6789',
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const { data } = scrubber.scrub(input);
|
|
135
|
-
expect(data.log).not.to.contain('bob@example.com');
|
|
136
|
-
expect(data.log).not.to.contain('123-45-6789');
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('Array handling', () => {
|
|
141
|
-
it('scrubs fields across all array items', () => {
|
|
142
|
-
const scrubber = new Scrubber({
|
|
143
|
-
fields: ['password'],
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const users = [
|
|
147
|
-
{ name: 'bob', password: 'secret' },
|
|
148
|
-
{ name: 'alice', password: 'hidden' },
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
const { data } = scrubber.scrub(users);
|
|
152
|
-
expect(data[0]?.password).to.equal('[SCRUBBED]');
|
|
153
|
-
expect(data[1]?.password).to.equal('[SCRUBBED]');
|
|
154
|
-
expect(data[0]?.name).to.equal('bob');
|
|
155
|
-
expect(data[1]?.name).to.equal('alice');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('handles nested arrays', () => {
|
|
159
|
-
const scrubber = new Scrubber({
|
|
160
|
-
fields: ['api_key'],
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const input = {
|
|
164
|
-
teams: [
|
|
165
|
-
{
|
|
166
|
-
members: [
|
|
167
|
-
{ name: 'bob', api_key: 'secret1' },
|
|
168
|
-
{ name: 'alice', api_key: 'secret2' },
|
|
169
|
-
],
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const { data } = scrubber.scrub(input);
|
|
175
|
-
expect(data.teams?.[0]?.members?.[0]?.api_key).to.equal('[SCRUBBED]');
|
|
176
|
-
expect(data.teams?.[0]?.members?.[1]?.api_key).to.equal('[SCRUBBED]');
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('Circular reference handling', () => {
|
|
181
|
-
it('handles circular references without crashing', () => {
|
|
182
|
-
const scrubber = new Scrubber({ fields: [] });
|
|
183
|
-
const input: any = { name: 'test' };
|
|
184
|
-
input.self = input;
|
|
185
|
-
|
|
186
|
-
const { data } = scrubber.scrub(input);
|
|
187
|
-
expect(data.self).to.equal('[Circular Reference]');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('scrubs fields before detecting circular references', () => {
|
|
191
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
192
|
-
const input: any = { name: 'test', password: 'secret' };
|
|
193
|
-
input.self = input;
|
|
194
|
-
|
|
195
|
-
const { data } = scrubber.scrub(input);
|
|
196
|
-
expect(data.password).to.equal('[SCRUBBED]');
|
|
197
|
-
expect(data.self).to.equal('[Circular Reference]');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('handles nested circular references', () => {
|
|
201
|
-
const scrubber = new Scrubber({ fields: [] });
|
|
202
|
-
const input: any = { name: 'test', nested: { level: 1 } };
|
|
203
|
-
input.self = input;
|
|
204
|
-
input.nested.parent = input;
|
|
205
|
-
|
|
206
|
-
const { data } = scrubber.scrub(input);
|
|
207
|
-
expect(data.self).to.equal('[Circular Reference]');
|
|
208
|
-
expect(data.nested.parent).to.equal('[Circular Reference]');
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('Combined modes', () => {
|
|
213
|
-
it('applies field + path + pattern scrubbing together', () => {
|
|
214
|
-
const scrubber = new Scrubber({
|
|
215
|
-
fields: ['api_key'],
|
|
216
|
-
paths: ['user.email'],
|
|
217
|
-
patterns: [/\b\d{3}-\d{2}-\d{4}\b/g],
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const input = {
|
|
221
|
-
user: {
|
|
222
|
-
email: 'bob@example.com', // Path-based
|
|
223
|
-
api_key: 'secret-key-123', // Field-based
|
|
224
|
-
},
|
|
225
|
-
log: 'SSN: 123-45-6789', // Pattern-based
|
|
226
|
-
nested: {
|
|
227
|
-
service: {
|
|
228
|
-
api_key: 'another-secret', // Field-based (any depth)
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const { data, scrubbedPaths } = scrubber.scrub(input);
|
|
234
|
-
expect(data.user?.email).to.equal('[SCRUBBED]');
|
|
235
|
-
expect(data.user?.api_key).to.equal('[SCRUBBED]');
|
|
236
|
-
expect(data.log).not.to.contain('123-45-6789');
|
|
237
|
-
expect(data.nested?.service?.api_key).to.equal('[SCRUBBED]');
|
|
238
|
-
expect(scrubbedPaths.length).to.be.greaterThan(0);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe('Scrub result metadata', () => {
|
|
243
|
-
it('tracks scrubbed paths', () => {
|
|
244
|
-
const scrubber = new Scrubber({
|
|
245
|
-
fields: ['password'],
|
|
246
|
-
paths: ['user.email'],
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const input = {
|
|
250
|
-
user: { email: 'test@example.com', password: 'secret' },
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
const result = scrubber.scrub(input);
|
|
254
|
-
expect(result.scrubbed).to.be.true;
|
|
255
|
-
expect(result.scrubbedPaths).to.include.members([
|
|
256
|
-
'user.email',
|
|
257
|
-
'user.password',
|
|
258
|
-
]);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('reports scrubbed=false when nothing was scrubbed', () => {
|
|
262
|
-
const scrubber = new Scrubber({
|
|
263
|
-
fields: ['password'],
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const input = { name: 'Bob', age: 30 };
|
|
267
|
-
const result = scrubber.scrub(input);
|
|
268
|
-
expect(result.scrubbed).to.be.false;
|
|
269
|
-
expect(result.scrubbedPaths).to.have.length(0);
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
describe('Immutability', () => {
|
|
274
|
-
it('does not mutate original object', () => {
|
|
275
|
-
const scrubber = new Scrubber({
|
|
276
|
-
fields: ['password'],
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
const input = { user: { password: 'secret', name: 'Bob' } };
|
|
280
|
-
const original = JSON.stringify(input);
|
|
281
|
-
|
|
282
|
-
scrubber.scrub(input);
|
|
283
|
-
|
|
284
|
-
expect(JSON.stringify(input)).to.equal(original);
|
|
285
|
-
expect(input.user.password).to.equal('secret');
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
describe('Custom replacement text', () => {
|
|
290
|
-
it('uses custom replacement string', () => {
|
|
291
|
-
const scrubber = new Scrubber({
|
|
292
|
-
fields: ['password'],
|
|
293
|
-
replacement: '***REDACTED***',
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
const input = { password: 'secret' };
|
|
297
|
-
const { data } = scrubber.scrub(input);
|
|
298
|
-
expect(data.password).to.equal('***REDACTED***');
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe('Edge cases', () => {
|
|
303
|
-
it('handles null values', () => {
|
|
304
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
305
|
-
const input = { user: null };
|
|
306
|
-
const { data } = scrubber.scrub(input);
|
|
307
|
-
expect(data.user).to.be.null;
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('handles undefined values', () => {
|
|
311
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
312
|
-
const input = { user: undefined };
|
|
313
|
-
const { data } = scrubber.scrub(input);
|
|
314
|
-
expect(data.user).to.be.undefined;
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('handles empty objects', () => {
|
|
318
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
319
|
-
const input = {};
|
|
320
|
-
const { data } = scrubber.scrub(input);
|
|
321
|
-
expect(data).to.deep.equal({});
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it('handles empty arrays', () => {
|
|
325
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
326
|
-
const input: any[] = [];
|
|
327
|
-
const { data } = scrubber.scrub(input);
|
|
328
|
-
expect(data).to.deep.equal([]);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('handles primitive values', () => {
|
|
332
|
-
const scrubber = new Scrubber({ fields: ['password'] });
|
|
333
|
-
expect(scrubber.scrub('test').data).to.equal('test');
|
|
334
|
-
expect(scrubber.scrub(123).data).to.equal(123);
|
|
335
|
-
expect(scrubber.scrub(true).data).to.equal(true);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it('scrubs entire array element by index path', () => {
|
|
339
|
-
// Tests lines 76-78: scrubbing entire array element, not just a field
|
|
340
|
-
const scrubber = new Scrubber({
|
|
341
|
-
paths: ['users[0]', 'items[1]'], // Scrub specific array elements by full path
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
const input = {
|
|
345
|
-
users: [
|
|
346
|
-
{ name: 'bob', email: 'bob@example.com' }, // Should be scrubbed entirely
|
|
347
|
-
{ name: 'alice', email: 'alice@example.com' }, // Not scrubbed
|
|
348
|
-
],
|
|
349
|
-
items: [
|
|
350
|
-
{ id: 1, value: 'keep' }, // Not scrubbed
|
|
351
|
-
{ id: 2, value: 'scrub' }, // Should be scrubbed entirely
|
|
352
|
-
],
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
const { data } = scrubber.scrub(input);
|
|
356
|
-
expect(data.users?.[0]).to.equal('[SCRUBBED]');
|
|
357
|
-
expect(data.users?.[1]?.name).to.equal('alice'); // Not scrubbed
|
|
358
|
-
expect(data.items?.[0]?.value).to.equal('keep'); // Not scrubbed
|
|
359
|
-
expect(data.items?.[1]).to.equal('[SCRUBBED]'); // Entire element scrubbed
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it('scrubs array index across all arrays', () => {
|
|
363
|
-
// Tests that index-only paths (e.g., '0') scrub that index in ALL arrays
|
|
364
|
-
const scrubber = new Scrubber({
|
|
365
|
-
paths: ['1'], // Scrub index 1 of ANY array
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const input = {
|
|
369
|
-
users: [
|
|
370
|
-
{ name: 'bob' }, // Index 0 - not scrubbed
|
|
371
|
-
{ name: 'alice' }, // Index 1 - scrubbed
|
|
372
|
-
{ name: 'charlie' }, // Index 2 - not scrubbed
|
|
373
|
-
],
|
|
374
|
-
teams: [
|
|
375
|
-
{ id: 'team-a' }, // Index 0 - not scrubbed
|
|
376
|
-
{ id: 'team-b' }, // Index 1 - scrubbed
|
|
377
|
-
],
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
const { data } = scrubber.scrub(input);
|
|
381
|
-
expect(data.users?.[0]?.name).to.equal('bob');
|
|
382
|
-
expect(data.users?.[1]).to.equal('[SCRUBBED]'); // Scrubbed by index
|
|
383
|
-
expect(data.users?.[2]?.name).to.equal('charlie');
|
|
384
|
-
expect(data.teams?.[0]?.id).to.equal('team-a');
|
|
385
|
-
expect(data.teams?.[1]).to.equal('[SCRUBBED]'); // Scrubbed by index
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('handles deeply nested objects (10+ levels)', () => {
|
|
389
|
-
// Validates discovery doc requirement: "Scrubs nested objects 10+ levels deep"
|
|
390
|
-
const scrubber = new Scrubber({
|
|
391
|
-
fields: ['secret'],
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
// Build a 15-level deep object
|
|
395
|
-
const input: any = { level: 1 };
|
|
396
|
-
let current = input;
|
|
397
|
-
for (let i = 2; i <= 15; i++) {
|
|
398
|
-
current.nested = { level: i };
|
|
399
|
-
current = current.nested;
|
|
400
|
-
}
|
|
401
|
-
// Add secret at the deepest level
|
|
402
|
-
current.secret = 'deep-secret';
|
|
403
|
-
current.public = 'visible';
|
|
404
|
-
|
|
405
|
-
const { data } = scrubber.scrub(input);
|
|
406
|
-
|
|
407
|
-
// Navigate to the deepest level
|
|
408
|
-
let deepest: any = data;
|
|
409
|
-
for (let i = 1; i < 15; i++) {
|
|
410
|
-
expect(deepest.level).to.equal(i);
|
|
411
|
-
deepest = deepest.nested;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Verify scrubbing worked at 15 levels deep
|
|
415
|
-
expect(deepest.level).to.equal(15);
|
|
416
|
-
expect(deepest.secret).to.equal('[SCRUBBED]');
|
|
417
|
-
expect(deepest.public).to.equal('visible');
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('handles circular references in deep clone fallback (arrays)', () => {
|
|
421
|
-
// Tests lines 166-172: circular reference deep clone for arrays
|
|
422
|
-
const scrubber = new Scrubber({
|
|
423
|
-
fields: ['password'],
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
// Create an object with circular references that will trigger the fallback clone
|
|
427
|
-
const parent: any = { name: 'parent', password: 'secret' };
|
|
428
|
-
const child1: any = { name: 'child1', items: [] };
|
|
429
|
-
const child2 = { name: 'child2', password: 'hidden' };
|
|
430
|
-
|
|
431
|
-
// Create circular reference in an array
|
|
432
|
-
child1.items = [child2, parent]; // Array contains parent
|
|
433
|
-
parent.children = [child1]; // Parent contains array with circular ref
|
|
434
|
-
|
|
435
|
-
const { data } = scrubber.scrub(parent);
|
|
436
|
-
|
|
437
|
-
// Verify scrubbing happened
|
|
438
|
-
expect(data.password).to.equal('[SCRUBBED]');
|
|
439
|
-
expect(data.children?.[0]?.name).to.equal('child1');
|
|
440
|
-
|
|
441
|
-
// Verify circular reference was handled in the array
|
|
442
|
-
expect(data.children?.[0]?.items?.[1]).to.equal('[Circular Reference]');
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
it('handles arrays with circular self-reference', () => {
|
|
446
|
-
// Additional test for circular array deep cloning
|
|
447
|
-
const scrubber = new Scrubber({
|
|
448
|
-
fields: ['token'],
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
const obj: any = {
|
|
452
|
-
token: 'secret-token',
|
|
453
|
-
list: [{ name: 'item1' }],
|
|
454
|
-
};
|
|
455
|
-
// Array references the parent object
|
|
456
|
-
obj.list.push(obj);
|
|
457
|
-
|
|
458
|
-
const { data } = scrubber.scrub(obj);
|
|
459
|
-
|
|
460
|
-
expect(data.token).to.equal('[SCRUBBED]');
|
|
461
|
-
expect(data.list?.[0]?.name).to.equal('item1');
|
|
462
|
-
expect(data.list?.[1]).to.equal('[Circular Reference]');
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
});
|