@sparkleideas/security 3.0.0-alpha.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/README.md +234 -0
- package/__tests__/acceptance/security-compliance.test.ts +674 -0
- package/__tests__/credential-generator.test.ts +310 -0
- package/__tests__/fixtures/configurations.ts +419 -0
- package/__tests__/fixtures/index.ts +21 -0
- package/__tests__/helpers/create-mock.ts +469 -0
- package/__tests__/helpers/index.ts +32 -0
- package/__tests__/input-validator.test.ts +381 -0
- package/__tests__/integration/security-flow.test.ts +606 -0
- package/__tests__/password-hasher.test.ts +239 -0
- package/__tests__/path-validator.test.ts +302 -0
- package/__tests__/safe-executor.test.ts +292 -0
- package/__tests__/token-generator.test.ts +371 -0
- package/__tests__/unit/credential-generator.test.ts +182 -0
- package/__tests__/unit/password-hasher.test.ts +359 -0
- package/__tests__/unit/path-validator.test.ts +509 -0
- package/__tests__/unit/safe-executor.test.ts +667 -0
- package/__tests__/unit/token-generator.test.ts +310 -0
- package/package.json +28 -0
- package/src/CVE-REMEDIATION.ts +251 -0
- package/src/application/index.ts +10 -0
- package/src/application/services/security-application-service.ts +193 -0
- package/src/credential-generator.ts +368 -0
- package/src/domain/entities/security-context.ts +173 -0
- package/src/domain/index.ts +17 -0
- package/src/domain/services/security-domain-service.ts +296 -0
- package/src/index.ts +271 -0
- package/src/input-validator.ts +466 -0
- package/src/password-hasher.ts +270 -0
- package/src/path-validator.ts +525 -0
- package/src/safe-executor.ts +525 -0
- package/src/token-generator.ts +463 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Factory Utilities for Security Module Testing
|
|
3
|
+
*
|
|
4
|
+
* Provides type-safe mock creation utilities for testing security components.
|
|
5
|
+
* Uses vitest's mocking capabilities with full TypeScript support.
|
|
6
|
+
*
|
|
7
|
+
* @module v3/security/__tests__/helpers/create-mock
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { vi, type MockInstance } from 'vitest';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Type representing a mocked interface where all methods are vi.fn() mocks
|
|
14
|
+
*/
|
|
15
|
+
export type MockedInterface<T> = {
|
|
16
|
+
[K in keyof T]: T[K] extends (...args: infer A) => infer R
|
|
17
|
+
? MockInstance<(...args: A) => R>
|
|
18
|
+
: T[K];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Type for a deeply mocked interface (including nested objects)
|
|
23
|
+
*/
|
|
24
|
+
export type DeepMockedInterface<T> = {
|
|
25
|
+
[K in keyof T]: T[K] extends (...args: infer A) => infer R
|
|
26
|
+
? MockInstance<(...args: A) => R>
|
|
27
|
+
: T[K] extends object
|
|
28
|
+
? DeepMockedInterface<T[K]>
|
|
29
|
+
: T[K];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a type-safe mock object for an interface.
|
|
34
|
+
* All methods are replaced with vi.fn() mocks.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* interface IService {
|
|
39
|
+
* getData(): Promise<string>;
|
|
40
|
+
* process(input: number): boolean;
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* const mock = createMock<IService>();
|
|
44
|
+
* mock.getData.mockResolvedValue('test');
|
|
45
|
+
* mock.process.mockReturnValue(true);
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @returns A proxy-based mock object
|
|
49
|
+
*/
|
|
50
|
+
export function createMock<T extends object>(): MockedInterface<T> {
|
|
51
|
+
const cache = new Map<string | symbol, MockInstance>();
|
|
52
|
+
|
|
53
|
+
return new Proxy({} as MockedInterface<T>, {
|
|
54
|
+
get(target, prop) {
|
|
55
|
+
if (!cache.has(prop)) {
|
|
56
|
+
cache.set(prop, vi.fn());
|
|
57
|
+
}
|
|
58
|
+
return cache.get(prop);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a partial mock with some real implementations.
|
|
65
|
+
*
|
|
66
|
+
* @param overrides - Partial implementation to use
|
|
67
|
+
* @returns A mock object with the provided overrides
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const mock = createPartialMock<IService>({
|
|
72
|
+
* getData: vi.fn().mockResolvedValue('real'),
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function createPartialMock<T extends object>(
|
|
77
|
+
overrides: Partial<MockedInterface<T>> = {}
|
|
78
|
+
): MockedInterface<T> {
|
|
79
|
+
const baseMock = createMock<T>();
|
|
80
|
+
return { ...baseMock, ...overrides };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Creates a spy on an existing object's methods.
|
|
85
|
+
*
|
|
86
|
+
* @param obj - The object to spy on
|
|
87
|
+
* @param methods - Array of method names to spy on
|
|
88
|
+
* @returns The object with spied methods
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const service = new RealService();
|
|
93
|
+
* const spied = createSpy(service, ['getData', 'process']);
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function createSpy<T extends object, K extends keyof T>(
|
|
97
|
+
obj: T,
|
|
98
|
+
methods: K[]
|
|
99
|
+
): T & { [P in K]: MockInstance } {
|
|
100
|
+
const spiedObj = { ...obj } as T & { [P in K]: MockInstance };
|
|
101
|
+
|
|
102
|
+
for (const method of methods) {
|
|
103
|
+
if (typeof obj[method] === 'function') {
|
|
104
|
+
(spiedObj as Record<K, MockInstance>)[method] = vi.fn(
|
|
105
|
+
(obj[method] as Function).bind(obj)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return spiedObj;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Mock factory for PasswordHasher
|
|
115
|
+
*/
|
|
116
|
+
export interface MockPasswordHasher {
|
|
117
|
+
hash: MockInstance<(password: string) => Promise<string>>;
|
|
118
|
+
verify: MockInstance<(password: string, hash: string) => Promise<boolean>>;
|
|
119
|
+
validate: MockInstance<(password: string) => { isValid: boolean; errors: string[] }>;
|
|
120
|
+
needsRehash: MockInstance<(hash: string) => boolean>;
|
|
121
|
+
getConfig: MockInstance<() => Record<string, unknown>>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createMockPasswordHasher(
|
|
125
|
+
overrides: Partial<MockPasswordHasher> = {}
|
|
126
|
+
): MockPasswordHasher {
|
|
127
|
+
return {
|
|
128
|
+
hash: vi.fn().mockResolvedValue('$2b$12$mockedHashValue'),
|
|
129
|
+
verify: vi.fn().mockResolvedValue(true),
|
|
130
|
+
validate: vi.fn().mockReturnValue({ isValid: true, errors: [] }),
|
|
131
|
+
needsRehash: vi.fn().mockReturnValue(false),
|
|
132
|
+
getConfig: vi.fn().mockReturnValue({ rounds: 12 }),
|
|
133
|
+
...overrides,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Mock factory for CredentialGenerator
|
|
139
|
+
*/
|
|
140
|
+
export interface MockCredentialGenerator {
|
|
141
|
+
generatePassword: MockInstance<(length?: number) => string>;
|
|
142
|
+
generateApiKey: MockInstance<(prefix?: string) => { key: string; prefix: string; keyId: string; createdAt: Date }>;
|
|
143
|
+
generateSecret: MockInstance<(length?: number) => string>;
|
|
144
|
+
generateEncryptionKey: MockInstance<() => string>;
|
|
145
|
+
generateInstallationCredentials: MockInstance<(expirationDays?: number) => {
|
|
146
|
+
adminPassword: string;
|
|
147
|
+
servicePassword: string;
|
|
148
|
+
jwtSecret: string;
|
|
149
|
+
sessionSecret: string;
|
|
150
|
+
encryptionKey: string;
|
|
151
|
+
generatedAt: Date;
|
|
152
|
+
expiresAt?: Date;
|
|
153
|
+
}>;
|
|
154
|
+
generateSessionToken: MockInstance<() => string>;
|
|
155
|
+
generateCsrfToken: MockInstance<() => string>;
|
|
156
|
+
generateNonce: MockInstance<() => string>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createMockCredentialGenerator(
|
|
160
|
+
overrides: Partial<MockCredentialGenerator> = {}
|
|
161
|
+
): MockCredentialGenerator {
|
|
162
|
+
const now = new Date();
|
|
163
|
+
return {
|
|
164
|
+
generatePassword: vi.fn().mockReturnValue('MockedSecureP@ssword123!'),
|
|
165
|
+
generateApiKey: vi.fn().mockReturnValue({
|
|
166
|
+
key: 'cf_mockedApiKey12345',
|
|
167
|
+
prefix: 'cf_',
|
|
168
|
+
keyId: '550e8400-e29b-41d4-a716-446655440000',
|
|
169
|
+
createdAt: now,
|
|
170
|
+
}),
|
|
171
|
+
generateSecret: vi.fn().mockReturnValue('0'.repeat(64)),
|
|
172
|
+
generateEncryptionKey: vi.fn().mockReturnValue('a'.repeat(64)),
|
|
173
|
+
generateInstallationCredentials: vi.fn().mockReturnValue({
|
|
174
|
+
adminPassword: 'MockedAdminP@ss123!',
|
|
175
|
+
servicePassword: 'MockedServiceP@ss123!',
|
|
176
|
+
jwtSecret: '0'.repeat(64),
|
|
177
|
+
sessionSecret: '1'.repeat(64),
|
|
178
|
+
encryptionKey: 'a'.repeat(64),
|
|
179
|
+
generatedAt: now,
|
|
180
|
+
}),
|
|
181
|
+
generateSessionToken: vi.fn().mockReturnValue('mockedSessionToken'),
|
|
182
|
+
generateCsrfToken: vi.fn().mockReturnValue('mockedCsrfToken'),
|
|
183
|
+
generateNonce: vi.fn().mockReturnValue('0'.repeat(32)),
|
|
184
|
+
...overrides,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Mock factory for PathValidator
|
|
190
|
+
*/
|
|
191
|
+
export interface MockPathValidator {
|
|
192
|
+
validate: MockInstance<(path: string) => Promise<{
|
|
193
|
+
isValid: boolean;
|
|
194
|
+
resolvedPath: string;
|
|
195
|
+
relativePath: string;
|
|
196
|
+
matchedPrefix: string;
|
|
197
|
+
errors: string[];
|
|
198
|
+
}>>;
|
|
199
|
+
validateSync: MockInstance<(path: string) => {
|
|
200
|
+
isValid: boolean;
|
|
201
|
+
resolvedPath: string;
|
|
202
|
+
relativePath: string;
|
|
203
|
+
matchedPrefix: string;
|
|
204
|
+
errors: string[];
|
|
205
|
+
}>;
|
|
206
|
+
validateOrThrow: MockInstance<(path: string) => Promise<string>>;
|
|
207
|
+
securePath: MockInstance<(prefix: string, ...segments: string[]) => Promise<string>>;
|
|
208
|
+
isWithinAllowed: MockInstance<(path: string) => boolean>;
|
|
209
|
+
getAllowedPrefixes: MockInstance<() => readonly string[]>;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function createMockPathValidator(
|
|
213
|
+
overrides: Partial<MockPathValidator> = {}
|
|
214
|
+
): MockPathValidator {
|
|
215
|
+
return {
|
|
216
|
+
validate: vi.fn().mockResolvedValue({
|
|
217
|
+
isValid: true,
|
|
218
|
+
resolvedPath: '/workspaces/project/src/file.ts',
|
|
219
|
+
relativePath: 'src/file.ts',
|
|
220
|
+
matchedPrefix: '/workspaces/project',
|
|
221
|
+
errors: [],
|
|
222
|
+
}),
|
|
223
|
+
validateSync: vi.fn().mockReturnValue({
|
|
224
|
+
isValid: true,
|
|
225
|
+
resolvedPath: '/workspaces/project/src/file.ts',
|
|
226
|
+
relativePath: 'src/file.ts',
|
|
227
|
+
matchedPrefix: '/workspaces/project',
|
|
228
|
+
errors: [],
|
|
229
|
+
}),
|
|
230
|
+
validateOrThrow: vi.fn().mockResolvedValue('/workspaces/project/src/file.ts'),
|
|
231
|
+
securePath: vi.fn().mockResolvedValue('/workspaces/project/src/file.ts'),
|
|
232
|
+
isWithinAllowed: vi.fn().mockReturnValue(true),
|
|
233
|
+
getAllowedPrefixes: vi.fn().mockReturnValue(['/workspaces/project']),
|
|
234
|
+
...overrides,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Mock factory for SafeExecutor
|
|
240
|
+
*/
|
|
241
|
+
export interface MockSafeExecutor {
|
|
242
|
+
execute: MockInstance<(command: string, args?: string[]) => Promise<{
|
|
243
|
+
stdout: string;
|
|
244
|
+
stderr: string;
|
|
245
|
+
exitCode: number;
|
|
246
|
+
command: string;
|
|
247
|
+
args: string[];
|
|
248
|
+
duration: number;
|
|
249
|
+
}>>;
|
|
250
|
+
executeStreaming: MockInstance<(command: string, args?: string[]) => {
|
|
251
|
+
process: unknown;
|
|
252
|
+
stdout: unknown;
|
|
253
|
+
stderr: unknown;
|
|
254
|
+
promise: Promise<unknown>;
|
|
255
|
+
}>;
|
|
256
|
+
sanitizeArgument: MockInstance<(arg: string) => string>;
|
|
257
|
+
isCommandAllowed: MockInstance<(command: string) => boolean>;
|
|
258
|
+
allowCommand: MockInstance<(command: string) => void>;
|
|
259
|
+
getAllowedCommands: MockInstance<() => readonly string[]>;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function createMockSafeExecutor(
|
|
263
|
+
overrides: Partial<MockSafeExecutor> = {}
|
|
264
|
+
): MockSafeExecutor {
|
|
265
|
+
return {
|
|
266
|
+
execute: vi.fn().mockResolvedValue({
|
|
267
|
+
stdout: 'success',
|
|
268
|
+
stderr: '',
|
|
269
|
+
exitCode: 0,
|
|
270
|
+
command: 'echo',
|
|
271
|
+
args: ['hello'],
|
|
272
|
+
duration: 10,
|
|
273
|
+
}),
|
|
274
|
+
executeStreaming: vi.fn().mockReturnValue({
|
|
275
|
+
process: {},
|
|
276
|
+
stdout: null,
|
|
277
|
+
stderr: null,
|
|
278
|
+
promise: Promise.resolve({
|
|
279
|
+
stdout: 'success',
|
|
280
|
+
stderr: '',
|
|
281
|
+
exitCode: 0,
|
|
282
|
+
command: 'echo',
|
|
283
|
+
args: ['hello'],
|
|
284
|
+
duration: 10,
|
|
285
|
+
}),
|
|
286
|
+
}),
|
|
287
|
+
sanitizeArgument: vi.fn().mockImplementation((arg: string) => arg.replace(/[;&|]/g, '')),
|
|
288
|
+
isCommandAllowed: vi.fn().mockReturnValue(true),
|
|
289
|
+
allowCommand: vi.fn(),
|
|
290
|
+
getAllowedCommands: vi.fn().mockReturnValue(['echo', 'git', 'npm', 'node']),
|
|
291
|
+
...overrides,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Mock factory for TokenGenerator
|
|
297
|
+
*/
|
|
298
|
+
export interface MockTokenGenerator {
|
|
299
|
+
generate: MockInstance<(length?: number) => string>;
|
|
300
|
+
generateWithExpiration: MockInstance<(expirationSeconds?: number, metadata?: Record<string, unknown>) => {
|
|
301
|
+
value: string;
|
|
302
|
+
createdAt: Date;
|
|
303
|
+
expiresAt: Date;
|
|
304
|
+
metadata?: Record<string, unknown>;
|
|
305
|
+
}>;
|
|
306
|
+
generateSessionToken: MockInstance<() => { value: string; createdAt: Date; expiresAt: Date }>;
|
|
307
|
+
generateCsrfToken: MockInstance<() => { value: string; createdAt: Date; expiresAt: Date }>;
|
|
308
|
+
generateApiToken: MockInstance<(prefix?: string) => { value: string; createdAt: Date; expiresAt: Date }>;
|
|
309
|
+
generateVerificationCode: MockInstance<(length?: number, expirationMinutes?: number, maxAttempts?: number) => {
|
|
310
|
+
code: string;
|
|
311
|
+
createdAt: Date;
|
|
312
|
+
expiresAt: Date;
|
|
313
|
+
attempts: number;
|
|
314
|
+
maxAttempts: number;
|
|
315
|
+
}>;
|
|
316
|
+
generateSignedToken: MockInstance<(payload: Record<string, unknown>, expirationSeconds?: number) => {
|
|
317
|
+
token: string;
|
|
318
|
+
signature: string;
|
|
319
|
+
combined: string;
|
|
320
|
+
createdAt: Date;
|
|
321
|
+
expiresAt: Date;
|
|
322
|
+
}>;
|
|
323
|
+
verifySignedToken: MockInstance<(combined: string) => Record<string, unknown> | null>;
|
|
324
|
+
generateTokenPair: MockInstance<() => {
|
|
325
|
+
accessToken: { value: string; createdAt: Date; expiresAt: Date };
|
|
326
|
+
refreshToken: { value: string; createdAt: Date; expiresAt: Date };
|
|
327
|
+
}>;
|
|
328
|
+
isExpired: MockInstance<(token: { expiresAt: Date }) => boolean>;
|
|
329
|
+
compare: MockInstance<(a: string, b: string) => boolean>;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function createMockTokenGenerator(
|
|
333
|
+
overrides: Partial<MockTokenGenerator> = {}
|
|
334
|
+
): MockTokenGenerator {
|
|
335
|
+
const now = new Date();
|
|
336
|
+
const expires = new Date(now.getTime() + 3600000);
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
generate: vi.fn().mockReturnValue('mockedToken12345'),
|
|
340
|
+
generateWithExpiration: vi.fn().mockReturnValue({
|
|
341
|
+
value: 'mockedTokenWithExpiration',
|
|
342
|
+
createdAt: now,
|
|
343
|
+
expiresAt: expires,
|
|
344
|
+
}),
|
|
345
|
+
generateSessionToken: vi.fn().mockReturnValue({
|
|
346
|
+
value: 'mockedSessionToken',
|
|
347
|
+
createdAt: now,
|
|
348
|
+
expiresAt: expires,
|
|
349
|
+
}),
|
|
350
|
+
generateCsrfToken: vi.fn().mockReturnValue({
|
|
351
|
+
value: 'mockedCsrfToken',
|
|
352
|
+
createdAt: now,
|
|
353
|
+
expiresAt: expires,
|
|
354
|
+
}),
|
|
355
|
+
generateApiToken: vi.fn().mockReturnValue({
|
|
356
|
+
value: 'cf_mockedApiToken',
|
|
357
|
+
createdAt: now,
|
|
358
|
+
expiresAt: expires,
|
|
359
|
+
}),
|
|
360
|
+
generateVerificationCode: vi.fn().mockReturnValue({
|
|
361
|
+
code: '123456',
|
|
362
|
+
createdAt: now,
|
|
363
|
+
expiresAt: expires,
|
|
364
|
+
attempts: 0,
|
|
365
|
+
maxAttempts: 3,
|
|
366
|
+
}),
|
|
367
|
+
generateSignedToken: vi.fn().mockReturnValue({
|
|
368
|
+
token: 'mockedToken',
|
|
369
|
+
signature: 'mockedSignature',
|
|
370
|
+
combined: 'mockedToken.mockedSignature',
|
|
371
|
+
createdAt: now,
|
|
372
|
+
expiresAt: expires,
|
|
373
|
+
}),
|
|
374
|
+
verifySignedToken: vi.fn().mockReturnValue({ userId: '123' }),
|
|
375
|
+
generateTokenPair: vi.fn().mockReturnValue({
|
|
376
|
+
accessToken: { value: 'accessToken', createdAt: now, expiresAt: expires },
|
|
377
|
+
refreshToken: { value: 'refreshToken', createdAt: now, expiresAt: new Date(now.getTime() + 604800000) },
|
|
378
|
+
}),
|
|
379
|
+
isExpired: vi.fn().mockReturnValue(false),
|
|
380
|
+
compare: vi.fn().mockImplementation((a: string, b: string) => a === b),
|
|
381
|
+
...overrides,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Mock factory for InputValidator
|
|
387
|
+
*/
|
|
388
|
+
export interface MockInputValidator {
|
|
389
|
+
validateEmail: MockInstance<(email: string) => string>;
|
|
390
|
+
validatePassword: MockInstance<(password: string) => string>;
|
|
391
|
+
validateIdentifier: MockInstance<(id: string) => string>;
|
|
392
|
+
validatePath: MockInstance<(path: string) => string>;
|
|
393
|
+
validateCommandArg: MockInstance<(arg: string) => string>;
|
|
394
|
+
validateLoginRequest: MockInstance<(data: unknown) => { email: string; password: string; mfaCode?: string }>;
|
|
395
|
+
validateCreateUser: MockInstance<(data: unknown) => { email: string; password: string; role: string }>;
|
|
396
|
+
validateTaskInput: MockInstance<(data: unknown) => { taskId: string; content: string; agentType: string }>;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function createMockInputValidator(
|
|
400
|
+
overrides: Partial<MockInputValidator> = {}
|
|
401
|
+
): MockInputValidator {
|
|
402
|
+
return {
|
|
403
|
+
validateEmail: vi.fn().mockImplementation((email: string) => email.toLowerCase()),
|
|
404
|
+
validatePassword: vi.fn().mockImplementation((password: string) => password),
|
|
405
|
+
validateIdentifier: vi.fn().mockImplementation((id: string) => id),
|
|
406
|
+
validatePath: vi.fn().mockImplementation((path: string) => path),
|
|
407
|
+
validateCommandArg: vi.fn().mockImplementation((arg: string) => arg),
|
|
408
|
+
validateLoginRequest: vi.fn().mockImplementation((data: unknown) => data as { email: string; password: string }),
|
|
409
|
+
validateCreateUser: vi.fn().mockImplementation((data: unknown) => data as { email: string; password: string; role: string }),
|
|
410
|
+
validateTaskInput: vi.fn().mockImplementation((data: unknown) => data as { taskId: string; content: string; agentType: string }),
|
|
411
|
+
...overrides,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Resets all mocks in a mock object
|
|
417
|
+
*/
|
|
418
|
+
export function resetMock<T extends object>(mock: MockedInterface<T>): void {
|
|
419
|
+
for (const key of Object.keys(mock)) {
|
|
420
|
+
const value = (mock as Record<string, unknown>)[key];
|
|
421
|
+
if (typeof value === 'function' && 'mockReset' in value) {
|
|
422
|
+
(value as MockInstance).mockReset();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Clears all mocks in a mock object (keeps implementation)
|
|
429
|
+
*/
|
|
430
|
+
export function clearMock<T extends object>(mock: MockedInterface<T>): void {
|
|
431
|
+
for (const key of Object.keys(mock)) {
|
|
432
|
+
const value = (mock as Record<string, unknown>)[key];
|
|
433
|
+
if (typeof value === 'function' && 'mockClear' in value) {
|
|
434
|
+
(value as MockInstance).mockClear();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Restores all mocks in a mock object
|
|
441
|
+
*/
|
|
442
|
+
export function restoreMock<T extends object>(mock: MockedInterface<T>): void {
|
|
443
|
+
for (const key of Object.keys(mock)) {
|
|
444
|
+
const value = (mock as Record<string, unknown>)[key];
|
|
445
|
+
if (typeof value === 'function' && 'mockRestore' in value) {
|
|
446
|
+
(value as MockInstance).mockRestore();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Helper to verify mock was called with specific arguments
|
|
453
|
+
*/
|
|
454
|
+
export function expectCalledWith<T extends MockInstance>(
|
|
455
|
+
mock: T,
|
|
456
|
+
...args: Parameters<T extends MockInstance<infer F> ? F : never>
|
|
457
|
+
): void {
|
|
458
|
+
expect(mock).toHaveBeenCalledWith(...args);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Helper to verify mock was called exactly N times
|
|
463
|
+
*/
|
|
464
|
+
export function expectCalledTimes<T extends MockInstance>(
|
|
465
|
+
mock: T,
|
|
466
|
+
times: number
|
|
467
|
+
): void {
|
|
468
|
+
expect(mock).toHaveBeenCalledTimes(times);
|
|
469
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Helpers Index
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all testing utilities for the security module.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/security/__tests__/helpers
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
createMock,
|
|
11
|
+
createPartialMock,
|
|
12
|
+
createSpy,
|
|
13
|
+
createMockPasswordHasher,
|
|
14
|
+
createMockCredentialGenerator,
|
|
15
|
+
createMockPathValidator,
|
|
16
|
+
createMockSafeExecutor,
|
|
17
|
+
createMockTokenGenerator,
|
|
18
|
+
createMockInputValidator,
|
|
19
|
+
resetMock,
|
|
20
|
+
clearMock,
|
|
21
|
+
restoreMock,
|
|
22
|
+
expectCalledWith,
|
|
23
|
+
expectCalledTimes,
|
|
24
|
+
type MockedInterface,
|
|
25
|
+
type DeepMockedInterface,
|
|
26
|
+
type MockPasswordHasher,
|
|
27
|
+
type MockCredentialGenerator,
|
|
28
|
+
type MockPathValidator,
|
|
29
|
+
type MockSafeExecutor,
|
|
30
|
+
type MockTokenGenerator,
|
|
31
|
+
type MockInputValidator,
|
|
32
|
+
} from './create-mock.js';
|