import { readFileSync, readdirSync } from 'node:fs'; import { join, resolve } from 'node:path'; import * as ts from 'typescript'; const sourceRoot = resolve(__dirname); const validObjectId = '507f1f77bcf86cd799439011'; type Constructor = new (...args: any[]) => any; type Operation = { dependency: number; method: string; args: unknown[] }; function collectAdapterFiles(directory: string): string[] { return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const path = join(directory, entry.name); if (entry.isDirectory()) { return collectAdapterFiles(path); } return entry.name.endsWith('.controller.ts') || entry.name.endsWith('.repository.ts') ? [path] : []; }); } function publicMethodParameters(file: string, className: string): Map { const source = ts.createSourceFile( file, readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS, ); const methods = new Map(); source.forEachChild((node) => { if (!ts.isClassDeclaration(node) || node.name?.text !== className) return; for (const member of node.members) { if (!ts.isMethodDeclaration(member) || !member.name) continue; const isPrivate = member.modifiers?.some( (modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword, ); if (isPrivate) continue; methods.set( member.name.getText(source).replace(/^['"]|['"]$/g, ''), member.parameters.map((parameter) => parameter.name.getText(source)), ); } }); return methods; } function createTransportValue(): any { const target = function transportValue() { return value; }; Object.assign(target, { status: 'ready', success: true, checks: {}, items: [], data: [], user: { sub: validObjectId, userId: validObjectId, id: validObjectId, email: 'member@example.com', permissions: [], }, }); const value: any = new Proxy(target, { apply: () => value, get: (object, property) => { if (property === 'then') return undefined; if (property === Symbol.iterator) return function* emptyIterator() {}; if (property === Symbol.toPrimitive) return () => validObjectId; if (property === 'toString' || property === 'valueOf') return () => validObjectId; if (property === 'map' || property === 'filter' || property === 'slice') return () => []; if (Reflect.has(object, property)) return Reflect.get(object, property); return value; }, }); return value; } function representativePayload() { return { id: validObjectId, _id: validObjectId, sub: validObjectId, userId: validObjectId, authorId: validObjectId, ownerId: validObjectId, ownerAdminId: validObjectId, postId: validObjectId, commentId: validObjectId, targetId: validObjectId, conversationId: validObjectId, participantId: validObjectId, recipientId: validObjectId, parentCommentId: validObjectId, email: 'member@example.com', username: 'member', name: 'Member', content: 'contract probe', title: 'Contract probe', reason: 'contract probe', type: 'post', targetType: 'post', resourceType: 'post', status: 'active', action: 'activate', isActive: true, isDisabled: false, targetIds: [validObjectId], participantIds: [validObjectId], userIds: [validObjectId], deviceId: 'device-contract', fcmToken: 'token-contract', imageUrls: [], tags: [], page: 1, limit: 2, }; } function argumentFor(parameterName: string): unknown { const name = parameterName.toLowerCase(); const upload = { originalname: 'image.jpg', mimetype: 'image/jpeg', size: 3, buffer: Buffer.from([0xff, 0xd8, 0xff]), }; if (name === 'req' || name === 'request' || name.endsWith('request')) { return { user: representativePayload(), headers: { 'x-request-id': 'contract-request' }, method: 'GET', originalUrl: '/contract', }; } if (name === 'res' || name.includes('response')) { const response: any = new Proxy( {}, { get: (target, property) => { if (!Reflect.has(target, property)) { Reflect.set(target, property, jest.fn(() => response)); } return Reflect.get(target, property); }, }, ); return response; } if (name === 'user' || name.includes('currentuser')) return representativePayload(); if (name.includes('files')) { return Object.assign([upload], { imageFiles: [upload], shopImageFiles: [upload], avatarFile: [upload], audioFile: [upload], videoFile: [upload], }); } if (name.includes('file')) return upload; if (/(^|_)(ids|idlist)$/.test(name) || name.endsWith('ids')) return [validObjectId]; if (name.endsWith('id') || name === 'jti') return validObjectId; if (name.includes('limit')) return 2; if (name.includes('skip') || name.includes('offset')) return 0; if (name.includes('page')) return 1; if (name.includes('direction')) return -1; if (name.includes('session')) return undefined; if (name.includes('email')) return 'member@example.com'; if (name.endsWith('names') || name === 'usernames') return ['member']; if (name.includes('text') || name.includes('content') || name.includes('title') || name.includes('reason')) { return 'contract probe'; } if (name === 'q' || name.includes('search')) return 'contract'; if (name.includes('sort')) return { createdAt: -1 }; if (name.includes('filter')) return {}; return representativePayload(); } function createControllerDependency(index: number, operations: Operation[]) { const methods = new Map(); return new Proxy( {}, { get: (_target, property) => { if (property === 'then') return undefined; if (!methods.has(property)) { methods.set( property, jest.fn((...args: unknown[]) => { operations.push({ dependency: index, method: String(property), args }); return createTransportValue(); }), ); } return methods.get(property); }, }, ); } function createRepositoryModel(index: number, operations: Operation[]) { const document: any = { id: validObjectId, _id: validObjectId, participantIds: [validObjectId], read: false, modifiedCount: 1, deletedCount: 1, populate: jest.fn(async () => document), save: jest.fn(async (...args: unknown[]): Promise => { operations.push({ dependency: index, method: 'document.save', args }); return document; }), }; const createQuery = () => { const queryResult: any[] = []; Object.assign(queryResult, document); const query: any = new Proxy( {}, { get: (_target, property) => { if (property === 'then') return undefined; if (property === 'exec') { return jest.fn(async () => { operations.push({ dependency: index, method: 'query.exec', args: [] }); return queryResult; }); } return jest.fn((...args: unknown[]) => { operations.push({ dependency: index, method: `query.${String(property)}`, args }); return query; }); }, }, ); return query; }; const methods = new Map(); const modelTarget = function modelConstructor() { return document; }; return new Proxy(modelTarget, { construct: (_target, args) => { operations.push({ dependency: index, method: 'new', args }); return document; }, get: (_target, property) => { if (property === 'collection') return { name: `collection_${index}` }; if (!methods.has(property)) { methods.set( property, jest.fn((...args: unknown[]) => { operations.push({ dependency: index, method: String(property), args }); return property === 'create' ? Promise.resolve(Array.isArray(args[0]) ? [document] : document) : createQuery(); }), ); } return methods.get(property); }, }); } describe('controller and repository contracts', () => { const files = collectAdapterFiles(sourceRoot); it.each(files.filter((file) => file.endsWith('.controller.ts')))( '%s delegates every public handler or enforces its documented local rejection', async (file) => { const moduleExports = require(file) as Record; const controllers = Object.values(moduleExports).filter( (exported): exported is Constructor => typeof exported === 'function' && exported.name.endsWith('Controller'), ); expect(controllers).not.toHaveLength(0); for (const Controller of controllers) { const operations: Operation[] = []; const dependencies = Array.from({ length: Controller.length }, (_, index) => createControllerDependency(index, operations), ); const instance = Reflect.construct(Controller, dependencies); const methods = publicMethodParameters(file, Controller.name); expect(methods.size).toBeGreaterThan(0); for (const [method, parameters] of methods) { const key = `${Controller.name}.${method}`; const args = parameters.map(argumentFor); const before = operations.length; if (key === 'ChatController.messagesMissingConversationId') { await expect(Promise.resolve().then(() => instance[method](...args))).rejects.toThrow( 'conversationId is required', ); expect(operations).toHaveLength(before); continue; } if (key === 'AuthController.googleAuth') { await expect(Promise.resolve(instance[method](...args))).resolves.toBeUndefined(); expect(operations).toHaveLength(before); continue; } try { await instance[method](...args); } catch (error) { const detail = error instanceof Error ? error.message : String(error); throw new Error(`${key} failed its delegation contract: ${detail}`); } const delegated = operations.slice(before); if (!delegated.length) { throw new Error(`${key} did not delegate to an injected dependency`); } expect(delegated[0]).toEqual( expect.objectContaining({ dependency: expect.any(Number), method: expect.any(String) }), ); } } }, ); it.each(files.filter((file) => file.endsWith('.repository.ts')))( '%s routes every public operation through its persistence model', async (file) => { const moduleExports = require(file) as Record; const repositories = Object.values(moduleExports).filter( (exported): exported is Constructor => typeof exported === 'function' && exported.name.endsWith('Repository'), ); expect(repositories).not.toHaveLength(0); for (const Repository of repositories) { const operations: Operation[] = []; const dependencies = Array.from({ length: Repository.length }, (_, index) => createRepositoryModel(index, operations), ); const instance = Reflect.construct(Repository, dependencies); const methods = publicMethodParameters(file, Repository.name); expect(methods.size).toBeGreaterThan(0); for (const [method, parameters] of methods) { const before = operations.length; const args = parameters.map(argumentFor); if (`${Repository.name}.${method}` === 'FollowsRepository.isDuplicateKeyError') { expect(instance[method]({ code: 11000 })).toBe(true); expect(instance[method]({ code: 1 })).toBe(false); expect(operations).toHaveLength(before); continue; } try { await instance[method](...args); } catch (error) { const detail = error instanceof Error ? error.message : String(error); throw new Error(`${Repository.name}.${method} failed its persistence contract: ${detail}`); } const persistenceCalls = operations.slice(before); if (!persistenceCalls.length) { throw new Error(`${Repository.name}.${method} did not reach an injected persistence model`); } expect(persistenceCalls.some((operation) => operation.method !== 'toString')).toBe(true); } } }, ); });