@claude-flow/security 3.0.0-alpha.1 → 3.0.0-alpha.7

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.
Files changed (96) hide show
  1. package/README.md +234 -234
  2. package/__tests__/acceptance/security-compliance.test.ts +674 -674
  3. package/__tests__/credential-generator.test.ts +310 -310
  4. package/__tests__/fixtures/configurations.ts +425 -419
  5. package/__tests__/fixtures/index.ts +21 -21
  6. package/__tests__/helpers/create-mock.ts +469 -469
  7. package/__tests__/helpers/index.ts +32 -32
  8. package/__tests__/input-validator.test.ts +382 -381
  9. package/__tests__/integration/security-flow.test.ts +606 -606
  10. package/__tests__/password-hasher.test.ts +239 -239
  11. package/__tests__/path-validator.test.ts +302 -302
  12. package/__tests__/safe-executor.test.ts +292 -292
  13. package/__tests__/token-generator.test.ts +371 -371
  14. package/__tests__/unit/credential-generator.test.ts +182 -182
  15. package/__tests__/unit/password-hasher.test.ts +359 -359
  16. package/__tests__/unit/path-validator.test.ts +509 -509
  17. package/__tests__/unit/safe-executor.test.ts +667 -667
  18. package/__tests__/unit/token-generator.test.ts +310 -310
  19. package/dist/credential-generator.js +9 -9
  20. package/dist/domain/services/security-domain-service.js +1 -1
  21. package/dist/domain/services/security-domain-service.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/password-hasher.d.ts.map +1 -1
  26. package/dist/password-hasher.js +8 -1
  27. package/dist/password-hasher.js.map +1 -1
  28. package/dist/safe-executor.d.ts +7 -0
  29. package/dist/safe-executor.d.ts.map +1 -1
  30. package/dist/safe-executor.js +21 -1
  31. package/dist/safe-executor.js.map +1 -1
  32. package/package.json +27 -28
  33. package/src/CVE-REMEDIATION.ts +251 -251
  34. package/src/application/index.ts +10 -10
  35. package/src/application/services/security-application-service.ts +193 -193
  36. package/src/credential-generator.ts +368 -368
  37. package/src/domain/entities/security-context.ts +173 -173
  38. package/src/domain/index.ts +17 -17
  39. package/src/domain/services/security-domain-service.ts +296 -296
  40. package/src/index.ts +271 -271
  41. package/src/input-validator.ts +466 -466
  42. package/src/password-hasher.ts +277 -270
  43. package/src/path-validator.ts +525 -525
  44. package/src/safe-executor.ts +546 -525
  45. package/src/token-generator.ts +463 -463
  46. package/tmp.json +0 -0
  47. package/tsconfig.json +9 -9
  48. package/tsconfig.tsbuildinfo +1 -1
  49. package/vitest.config.ts +20 -0
  50. package/.agentic-flow/intelligence.json +0 -16
  51. package/__tests__/coverage/base.css +0 -224
  52. package/__tests__/coverage/block-navigation.js +0 -87
  53. package/__tests__/coverage/coverage-final.json +0 -11
  54. package/__tests__/coverage/favicon.png +0 -0
  55. package/__tests__/coverage/index.html +0 -161
  56. package/__tests__/coverage/lcov-report/base.css +0 -224
  57. package/__tests__/coverage/lcov-report/block-navigation.js +0 -87
  58. package/__tests__/coverage/lcov-report/favicon.png +0 -0
  59. package/__tests__/coverage/lcov-report/index.html +0 -161
  60. package/__tests__/coverage/lcov-report/prettify.css +0 -1
  61. package/__tests__/coverage/lcov-report/prettify.js +0 -2
  62. package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  63. package/__tests__/coverage/lcov-report/sorter.js +0 -210
  64. package/__tests__/coverage/lcov-report/src/CVE-REMEDIATION.ts.html +0 -838
  65. package/__tests__/coverage/lcov-report/src/application/services/index.html +0 -116
  66. package/__tests__/coverage/lcov-report/src/application/services/security-application-service.ts.html +0 -664
  67. package/__tests__/coverage/lcov-report/src/credential-generator.ts.html +0 -1189
  68. package/__tests__/coverage/lcov-report/src/domain/entities/index.html +0 -116
  69. package/__tests__/coverage/lcov-report/src/domain/entities/security-context.ts.html +0 -604
  70. package/__tests__/coverage/lcov-report/src/domain/services/index.html +0 -116
  71. package/__tests__/coverage/lcov-report/src/domain/services/security-domain-service.ts.html +0 -973
  72. package/__tests__/coverage/lcov-report/src/index.html +0 -206
  73. package/__tests__/coverage/lcov-report/src/input-validator.ts.html +0 -1483
  74. package/__tests__/coverage/lcov-report/src/password-hasher.ts.html +0 -895
  75. package/__tests__/coverage/lcov-report/src/path-validator.ts.html +0 -1660
  76. package/__tests__/coverage/lcov-report/src/safe-executor.ts.html +0 -1660
  77. package/__tests__/coverage/lcov-report/src/token-generator.ts.html +0 -1474
  78. package/__tests__/coverage/lcov.info +0 -3650
  79. package/__tests__/coverage/prettify.css +0 -1
  80. package/__tests__/coverage/prettify.js +0 -2
  81. package/__tests__/coverage/sort-arrow-sprite.png +0 -0
  82. package/__tests__/coverage/sorter.js +0 -210
  83. package/__tests__/coverage/src/CVE-REMEDIATION.ts.html +0 -838
  84. package/__tests__/coverage/src/application/services/index.html +0 -116
  85. package/__tests__/coverage/src/application/services/security-application-service.ts.html +0 -664
  86. package/__tests__/coverage/src/credential-generator.ts.html +0 -1189
  87. package/__tests__/coverage/src/domain/entities/index.html +0 -116
  88. package/__tests__/coverage/src/domain/entities/security-context.ts.html +0 -604
  89. package/__tests__/coverage/src/domain/services/index.html +0 -116
  90. package/__tests__/coverage/src/domain/services/security-domain-service.ts.html +0 -973
  91. package/__tests__/coverage/src/index.html +0 -206
  92. package/__tests__/coverage/src/input-validator.ts.html +0 -1483
  93. package/__tests__/coverage/src/password-hasher.ts.html +0 -895
  94. package/__tests__/coverage/src/path-validator.ts.html +0 -1660
  95. package/__tests__/coverage/src/safe-executor.ts.html +0 -1660
  96. package/__tests__/coverage/src/token-generator.ts.html +0 -1474
@@ -1,469 +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
- }
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
+ }