directus

Форк
0
/
redact-object.test.ts 
307 строк · 6.8 Кб
1
import { getRedactedString, REDACTED_TEXT } from '@directus/utils';
2
import { merge } from 'lodash-es';
3
import { describe, expect, test } from 'vitest';
4
import { getReplacer, redactObject } from './redact-object.js';
5

6
const input = {
7
	$trigger: {
8
		event: 'users.create',
9
		payload: {
10
			first_name: 'Example',
11
			last_name: 'User',
12
			email: 'user@example.com',
13
			password: 'secret',
14
		},
15
		key: 'eb641950-fffa-4388-8606-aede594ae487',
16
		collection: 'directus_users',
17
	},
18
	exec_fm27u: {
19
		$trigger: {
20
			event: 'users.create',
21
			payload: {
22
				first_name: 'Example',
23
				last_name: 'User',
24
				email: 'user@example.com',
25
				password: 'secret',
26
			},
27
			key: 'eb641950-fffa-4388-8606-aede594ae487',
28
			collection: 'directus_users',
29
		},
30
		$last: {
31
			event: 'users.create',
32
			payload: {
33
				first_name: 'Example',
34
				last_name: 'User',
35
				email: 'user@example.com',
36
				password: 'secret',
37
			},
38
			key: 'eb641950-fffa-4388-8606-aede594ae487',
39
			collection: 'directus_users',
40
		},
41
	},
42
};
43

44
test('should not mutate input', () => {
45
	const result = redactObject(input, { keys: [['$trigger']] }, getRedactedString);
46

47
	expect(result).not.toBe(input);
48
});
49

50
test('should support single level path', () => {
51
	const result = redactObject(input, { keys: [['$trigger']] }, getRedactedString);
52

53
	expect(result).toEqual(
54
		merge({}, input, {
55
			$trigger: REDACTED_TEXT,
56
		}),
57
	);
58
});
59

60
test('should support multi level path', () => {
61
	const result = redactObject(input, { keys: [['$trigger', 'payload', 'password']] }, getRedactedString);
62

63
	expect(result).toEqual(
64
		merge({}, input, {
65
			$trigger: {
66
				payload: { password: REDACTED_TEXT },
67
			},
68
		}),
69
	);
70
});
71

72
test('should support wildcard path', () => {
73
	const result = redactObject(input, { keys: [['*', 'payload']] }, getRedactedString);
74

75
	expect(result).toEqual(
76
		merge({}, input, {
77
			$trigger: {
78
				payload: REDACTED_TEXT,
79
			},
80
		}),
81
	);
82
});
83

84
test('should support deep path', () => {
85
	const result = redactObject(input, { keys: [['**', 'password']] }, getRedactedString);
86

87
	expect(result).toMatchObject(
88
		merge({}, input, {
89
			$trigger: {
90
				payload: {
91
					password: REDACTED_TEXT,
92
				},
93
			},
94
			exec_fm27u: {
95
				$trigger: {
96
					payload: {
97
						password: REDACTED_TEXT,
98
					},
99
				},
100
				$last: {
101
					payload: {
102
						password: REDACTED_TEXT,
103
					},
104
				},
105
			},
106
		}),
107
	);
108
});
109

110
test('should support multiple paths', () => {
111
	const result = redactObject(
112
		input,
113
		{
114
			keys: [
115
				['$trigger', 'key'],
116
				['*', 'payload', 'email'],
117
				['**', 'password'],
118
			],
119
		},
120
		getRedactedString,
121
	);
122

123
	expect(result).toEqual(
124
		merge({}, input, {
125
			$trigger: {
126
				key: REDACTED_TEXT,
127
				payload: {
128
					email: REDACTED_TEXT,
129
					password: REDACTED_TEXT,
130
				},
131
			},
132
			exec_fm27u: {
133
				$trigger: {
134
					payload: {
135
						password: REDACTED_TEXT,
136
					},
137
				},
138
				$last: {
139
					payload: {
140
						password: REDACTED_TEXT,
141
					},
142
				},
143
			},
144
		}),
145
	);
146
});
147

148
describe('getReplacer tests', () => {
149
	test('Returns parsed error object', () => {
150
		const errorMessage = 'Error Message';
151
		const errorCause = 'Error Cause';
152
		const replacer = getReplacer(getRedactedString);
153
		const result: any = replacer('', new Error(errorMessage, { cause: errorCause }));
154
		expect(result.name).toBe('Error');
155
		expect(result.message).toBe(errorMessage);
156
		expect(result.stack).toBeDefined();
157
		expect(result.cause).toBe(errorCause);
158
	});
159

160
	test('Returns other types as is', () => {
161
		const values = [
162
			undefined,
163
			null,
164
			0,
165
			1,
166
			true,
167
			false,
168
			'',
169
			'abc',
170
			[],
171
			['123'],
172
			{},
173
			{ abc: '123' },
174
			() => {
175
				// empty
176
			},
177
		];
178

179
		const replacer = getReplacer(getRedactedString);
180

181
		for (const value of values) {
182
			expect(replacer('', value)).toEqual(value);
183
		}
184
	});
185

186
	test('Correctly parses object with circular structure when used with JSON.stringify()', () => {
187
		const obj: Record<string, any> = {
188
			a: 'foo',
189
		};
190

191
		obj['b'] = obj;
192
		obj['c'] = { obj };
193
		obj['d'] = [obj];
194

195
		const expectedResult = {
196
			a: 'foo',
197
			b: '[Circular]',
198
			c: { obj: '[Circular]' },
199
			d: ['[Circular]'],
200
		};
201

202
		const result = JSON.parse(JSON.stringify(obj, getReplacer(getRedactedString)));
203

204
		expect(result).toStrictEqual(expectedResult);
205
	});
206

207
	test('Correctly parses object with repeatedly occurring same refs', () => {
208
		const ref = {};
209

210
		const obj: Record<string, any> = {
211
			a: ref,
212
			b: ref,
213
		};
214

215
		const expectedResult = {
216
			a: ref,
217
			b: ref,
218
		};
219

220
		const result = JSON.parse(JSON.stringify(obj, getReplacer(getRedactedString)));
221

222
		expect(result).toStrictEqual(expectedResult);
223
	});
224

225
	test('Correctly parses error object when used with JSON.stringify()', () => {
226
		const errorMessage = 'Error Message';
227
		const errorCause = 'Error Cause';
228

229
		const baseValue = {
230
			string: 'abc',
231
			num: 123,
232
			bool: true,
233
			null: null,
234
		};
235

236
		const objWithError = {
237
			...baseValue,
238
			error: new Error(errorMessage, { cause: errorCause }),
239
		};
240

241
		const expectedResult = {
242
			...baseValue,
243
			error: { name: 'Error', message: errorMessage, cause: errorCause },
244
		};
245

246
		const result = JSON.parse(JSON.stringify(objWithError, getReplacer(getRedactedString)));
247

248
		// Stack changes depending on env
249
		expect(result.error.stack).toBeDefined();
250
		delete result.error.stack;
251

252
		expect(result).toStrictEqual(expectedResult);
253
	});
254

255
	test('Correctly redacts values when used with JSON.stringify()', () => {
256
		const baseValue = {
257
			num: 123,
258
			bool: true,
259
			null: null,
260
			string_ignore: `No error Cause it's case sensitive~~`,
261
		};
262

263
		const objWithError = {
264
			...baseValue,
265
			string: `Replace cause case matches Errors~~`,
266
			nested: { another_str: 'just because of safety 123456' },
267
			nested_array: [{ str_a: 'cause surely' }, { str_b: 'not an Error' }, { str_ignore: 'nothing here' }],
268
			array: ['something', 'no Error', 'just because', 'all is good'],
269
			error: new Error('This is an Error message.', { cause: 'Here is an Error cause!' }),
270
		};
271

272
		const expectedResult = {
273
			...baseValue,
274
			string: `Replace ${getRedactedString('cause')} case matches ${getRedactedString('ERROR')}s~~`,
275
			nested: { another_str: `just be${getRedactedString('cause')} of safety 123456` },
276
			nested_array: [
277
				{ str_a: `${getRedactedString('cause')} surely` },
278
				{ str_b: `not an ${getRedactedString('ERROR')}` },
279
				{ str_ignore: 'nothing here' },
280
			],
281
			array: ['something', `no ${getRedactedString('ERROR')}`, `just be${getRedactedString('cause')}`, 'all is good'],
282
			error: {
283
				name: getRedactedString('ERROR'),
284
				message: `This is an ${getRedactedString('ERROR')} message.`,
285
				cause: `Here is an ${getRedactedString('ERROR')} ${getRedactedString('cause')}!`,
286
			},
287
		};
288

289
		const result = JSON.parse(
290
			JSON.stringify(
291
				objWithError,
292
				getReplacer(getRedactedString, {
293
					empty: '',
294
					ERROR: 'Error',
295
					cause: 'cause',
296
					number: 123456,
297
				}),
298
			),
299
		);
300

301
		// Stack changes depending on env
302
		expect(result.error.stack).toBeDefined();
303
		delete result.error.stack;
304

305
		expect(result).toStrictEqual(expectedResult);
306
	});
307
});
308

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.