@nestjsvn/swagger-sse
Version:
OpenAPI documentation and interactive Swagger UI for NestJS Server-Sent Events endpoints
329 lines (328 loc) • 15.1 kB
JavaScript
"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);
}
});
});
});