UNPKG

@nestjsvn/swagger-sse

Version:

OpenAPI documentation and interactive Swagger UI for NestJS Server-Sent Events endpoints

329 lines (328 loc) 15.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const ts = __importStar(require("typescript")); const ast_utils_1 = require("../../plugin/ast-utils"); describe('AST Utils', () => { describe('typeNameToEventName', () => { describe('kebab-case conversion', () => { it('should convert PascalCase to kebab-case', () => { expect((0, ast_utils_1.typeNameToEventName)('UserCreated', 'kebab-case')).toBe('user-created'); expect((0, ast_utils_1.typeNameToEventName)('MessageReceived', 'kebab-case')).toBe('message-received'); expect((0, ast_utils_1.typeNameToEventName)('OrderStatusUpdated', 'kebab-case')).toBe('order-status-updated'); }); it('should remove common suffixes', () => { expect((0, ast_utils_1.typeNameToEventName)('UserCreatedDto', 'kebab-case')).toBe('user-created'); expect((0, ast_utils_1.typeNameToEventName)('MessageReceivedEvent', 'kebab-case')).toBe('message-received'); expect((0, ast_utils_1.typeNameToEventName)('OrderData', 'kebab-case')).toBe('order'); expect((0, ast_utils_1.typeNameToEventName)('NotificationPayload', 'kebab-case')).toBe('notification'); }); it('should handle single words', () => { expect((0, ast_utils_1.typeNameToEventName)('User', 'kebab-case')).toBe('user'); expect((0, ast_utils_1.typeNameToEventName)('Message', 'kebab-case')).toBe('message'); }); it('should handle already lowercase', () => { expect((0, ast_utils_1.typeNameToEventName)('user', 'kebab-case')).toBe('user'); expect((0, ast_utils_1.typeNameToEventName)('message', 'kebab-case')).toBe('message'); }); }); describe('camelCase conversion', () => { it('should convert PascalCase to camelCase', () => { expect((0, ast_utils_1.typeNameToEventName)('UserCreated', 'camelCase')).toBe('userCreated'); expect((0, ast_utils_1.typeNameToEventName)('MessageReceived', 'camelCase')).toBe('messageReceived'); }); it('should remove suffixes and convert', () => { expect((0, ast_utils_1.typeNameToEventName)('UserCreatedDto', 'camelCase')).toBe('userCreated'); expect((0, ast_utils_1.typeNameToEventName)('MessageReceivedEvent', 'camelCase')).toBe('messageReceived'); }); }); describe('PascalCase conversion', () => { it('should keep PascalCase but remove suffixes', () => { expect((0, ast_utils_1.typeNameToEventName)('UserCreatedDto', 'PascalCase')).toBe('UserCreated'); expect((0, ast_utils_1.typeNameToEventName)('MessageReceivedEvent', 'PascalCase')).toBe('MessageReceived'); }); it('should keep original if no suffix', () => { expect((0, ast_utils_1.typeNameToEventName)('UserCreated', 'PascalCase')).toBe('UserCreated'); expect((0, ast_utils_1.typeNameToEventName)('MessageReceived', 'PascalCase')).toBe('MessageReceived'); }); }); describe('default behavior', () => { it('should default to kebab-case', () => { expect((0, ast_utils_1.typeNameToEventName)('UserCreatedDto')).toBe('user-created'); expect((0, ast_utils_1.typeNameToEventName)('MessageReceivedEvent')).toBe('message-received'); }); }); }); describe('isDecoratorNamed', () => { function createDecoratorFromCode(code) { const sourceFile = ts.createSourceFile('test.ts', code, ts.ScriptTarget.Latest, true); let decorator = null; function visit(node) { if (ts.isDecorator(node)) { decorator = node; return; } ts.forEachChild(node, visit); } visit(sourceFile); return decorator; } it('should identify simple decorator names', () => { const decorator = createDecoratorFromCode('@Test class MyClass {}'); if (decorator) { expect((0, ast_utils_1.isDecoratorNamed)(decorator, 'Test')).toBe(true); expect((0, ast_utils_1.isDecoratorNamed)(decorator, 'Other')).toBe(false); } }); it('should identify decorator with call expression', () => { const decorator = createDecoratorFromCode('@Test() class MyClass {}'); if (decorator) { expect((0, ast_utils_1.isDecoratorNamed)(decorator, 'Test')).toBe(true); expect((0, ast_utils_1.isDecoratorNamed)(decorator, 'Other')).toBe(false); } }); it('should identify decorator with parameters', () => { const decorator = createDecoratorFromCode('@Test("param") class MyClass {}'); if (decorator) { expect((0, ast_utils_1.isDecoratorNamed)(decorator, 'Test')).toBe(true); expect((0, ast_utils_1.isDecoratorNamed)(decorator, 'Other')).toBe(false); } }); }); describe('hasSseDecorator', () => { function createMethodFromCode(code) { const sourceFile = ts.createSourceFile('test.ts', `class TestClass { ${code} }`, ts.ScriptTarget.Latest, true); let method = null; function visit(node) { if (ts.isMethodDeclaration(node)) { method = node; return; } ts.forEachChild(node, visit); } visit(sourceFile); return method; } it('should detect @Sse decorator', () => { const method = createMethodFromCode('@Sse() method() {}'); if (method) { const result = (0, ast_utils_1.hasSseDecorator)(method); expect(typeof result).toBe('boolean'); } }); it('should detect @Sse decorator with parameters', () => { const method = createMethodFromCode('@Sse("stream") method() {}'); if (method) { const result = (0, ast_utils_1.hasSseDecorator)(method); expect(typeof result).toBe('boolean'); } }); it('should not detect other decorators', () => { const method = createMethodFromCode('@Get() method() {}'); if (method) { const result = (0, ast_utils_1.hasSseDecorator)(method); expect(result).toBe(false); } }); it('should handle methods without decorators', () => { const method = createMethodFromCode('method() {}'); if (method) { const result = (0, ast_utils_1.hasSseDecorator)(method); expect(result).toBe(false); } }); }); describe('hasApiSseDecorator', () => { function createMethodFromCode(code) { const sourceFile = ts.createSourceFile('test.ts', `class TestClass { ${code} }`, ts.ScriptTarget.Latest, true); let method = null; function visit(node) { if (ts.isMethodDeclaration(node)) { method = node; return; } ts.forEachChild(node, visit); } visit(sourceFile); return method; } it('should detect @ApiSse decorator', () => { const method = createMethodFromCode('@ApiSse({}) method() {}'); if (method) { const result = (0, ast_utils_1.hasApiSseDecorator)(method); expect(typeof result).toBe('boolean'); } }); it('should not detect other decorators', () => { const method = createMethodFromCode('@Sse() method() {}'); if (method) { const result = (0, ast_utils_1.hasApiSseDecorator)(method); expect(result).toBe(false); } }); }); describe('extractEventTypes', () => { function createMethodFromCode(code) { const sourceFile = ts.createSourceFile('test.ts', `class TestClass { ${code} }`, ts.ScriptTarget.Latest, true); let method = null; function visit(node) { if (ts.isMethodDeclaration(node)) { method = node; return; } ts.forEachChild(node, visit); } visit(sourceFile); return method; } it('should return empty array for methods without return type', () => { const method = createMethodFromCode('method() {}'); if (method) { const result = (0, ast_utils_1.extractEventTypes)(method); expect(result).toEqual([]); } }); it('should return empty array for non-Observable return types', () => { const method = createMethodFromCode('method(): string {}'); if (method) { const result = (0, ast_utils_1.extractEventTypes)(method); expect(result).toEqual([]); } }); it('should handle Observable return type', () => { const method = createMethodFromCode('method(): Observable<MessageEvent<UserDto>> {}'); if (method) { const result = (0, ast_utils_1.extractEventTypes)(method); expect(Array.isArray(result)).toBe(true); } }); }); describe('analyzeObservableType', () => { function createTypeReferenceFromCode(code) { const sourceFile = ts.createSourceFile('test.ts', `let x: ${code};`, ts.ScriptTarget.Latest, true); let typeRef = null; function visit(node) { if (ts.isTypeReferenceNode(node)) { typeRef = node; return; } ts.forEachChild(node, visit); } visit(sourceFile); return typeRef; } it('should return empty array for non-Observable types', () => { const typeRef = createTypeReferenceFromCode('Promise<string>'); if (typeRef) { const result = (0, ast_utils_1.analyzeObservableType)(typeRef); expect(result).toEqual([]); } }); it('should return empty array for Observable without type arguments', () => { const typeRef = createTypeReferenceFromCode('Observable'); if (typeRef) { const result = (0, ast_utils_1.analyzeObservableType)(typeRef); expect(result).toEqual([]); } }); it('should handle Observable<MessageEvent<T>>', () => { const typeRef = createTypeReferenceFromCode('Observable<MessageEvent<UserDto>>'); if (typeRef) { const result = (0, ast_utils_1.analyzeObservableType)(typeRef); expect(Array.isArray(result)).toBe(true); } }); }); describe('addApiSseDecorator', () => { function createMethodFromCode(code) { const sourceFile = ts.createSourceFile('test.ts', `class TestClass { ${code} }`, ts.ScriptTarget.Latest, true); let method = null; function visit(node) { if (ts.isMethodDeclaration(node)) { method = node; return; } ts.forEachChild(node, visit); } visit(sourceFile); return method; } it('should return a method declaration', () => { const method = createMethodFromCode('method() {}'); if (method) { const context = { factory: ts.factory, }; const result = (0, ast_utils_1.addApiSseDecorator)(method, context, ['UserDto']); expect(ts.isMethodDeclaration(result)).toBe(true); } }); it('should handle empty event types', () => { const method = createMethodFromCode('method() {}'); if (method) { const context = { factory: ts.factory, }; const result = (0, ast_utils_1.addApiSseDecorator)(method, context, []); expect(ts.isMethodDeclaration(result)).toBe(true); } }); it('should handle multiple event types', () => { const method = createMethodFromCode('method() {}'); if (method) { const context = { factory: ts.factory, }; const result = (0, ast_utils_1.addApiSseDecorator)(method, context, ['UserDto', 'MessageDto']); expect(ts.isMethodDeclaration(result)).toBe(true); } }); it('should handle plugin options', () => { const method = createMethodFromCode('method() {}'); if (method) { const context = { factory: ts.factory, }; const options = { eventNamingConvention: 'camelCase', introspectComments: true, autoGenerateOperationId: true, }; const result = (0, ast_utils_1.addApiSseDecorator)(method, context, ['UserDto'], options); expect(ts.isMethodDeclaration(result)).toBe(true); } }); }); });