Skip to content

Commit 516bc45

Browse files
Michael-ObeleCopilot
andcommitted
[patch] feat: add tests for TypeScript schema loading and route discovery patterns
Co-authored-by: Copilot <copilot@github.com>
1 parent 605edc9 commit 516bc45

3 files changed

Lines changed: 127 additions & 1 deletion

File tree

src/lib/base-spec.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,48 @@ describe('base-spec', () => {
105105
});
106106
});
107107

108+
it('should load and merge schemas from a TypeScript baseSchemasPath', () => {
109+
const libDir = join(tempDir, 'src', 'lib');
110+
mkdirSync(libDir, { recursive: true });
111+
112+
writeFileSync(
113+
join(libDir, 'schemas.ts'),
114+
`
115+
import type { SchemaObject } from 'openapi-types';
116+
117+
/**
118+
* @swagger
119+
* components:
120+
* schemas:
121+
* Session:
122+
* type: object
123+
* properties:
124+
* token:
125+
* type: string
126+
*/
127+
export const SessionSchema: SchemaObject = {};
128+
`,
129+
'utf-8'
130+
);
131+
132+
const spec = createBaseSpec({
133+
rootDir: tempDir,
134+
baseSchemasPath: 'src/lib/schemas.ts',
135+
info: {
136+
title: 'Test API',
137+
version: '1.0.0'
138+
}
139+
});
140+
141+
expect(spec.components?.schemas).toHaveProperty('Session');
142+
expect(spec.components?.schemas?.Session).toMatchObject({
143+
type: 'object',
144+
properties: {
145+
token: { type: 'string' }
146+
}
147+
});
148+
});
149+
108150
it('should merge security schemes from baseSchemasPath', () => {
109151
// Create schemas file with security schemes
110152
const libDir = join(tempDir, 'src', 'lib');

src/lib/base-spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import swaggerJsdoc from 'swagger-jsdoc';
22
import type { OpenAPIV3 } from 'openapi-types';
33
import { resolve } from 'path';
4-
import { readFileSync } from 'fs';
4+
import { writeFileSync, mkdirSync, readFileSync, rmSync } from 'fs';
5+
import { dirname, join } from 'path';
56
import yaml from 'js-yaml';
7+
import ts from 'typescript';
8+
import { tmpdir } from 'os';
69
import { createLogger } from './logger.js';
710

811
export interface BaseSpecOptions {
@@ -52,6 +55,19 @@ export function createBaseSpec(options: BaseSpecOptions): OpenAPIV3.Document {
5255

5356
const logger = createLogger(silent);
5457

58+
const stripTypeScript = (code: string): string => {
59+
const result = ts.transpileModule(code, {
60+
compilerOptions: {
61+
target: ts.ScriptTarget.ESNext,
62+
module: ts.ModuleKind.ESNext,
63+
removeComments: false,
64+
isolatedModules: true
65+
}
66+
});
67+
68+
return result.outputText;
69+
};
70+
5571
// Start with minimal base spec
5672
const baseSpec: OpenAPIV3.Document = {
5773
openapi: '3.0.0',
@@ -66,10 +82,24 @@ export function createBaseSpec(options: BaseSpecOptions): OpenAPIV3.Document {
6682

6783
// If baseSchemasPath is provided, parse it for shared schemas
6884
if (baseSchemasPath) {
85+
const tempDir = join(
86+
tmpdir(),
87+
`openapi-base-spec-${Date.now()}-${Math.random().toString(36).slice(2)}`
88+
);
89+
mkdirSync(tempDir, { recursive: true });
90+
6991
try {
7092
const schemaFilePath = resolve(rootDir, baseSchemasPath);
7193
const apis = [schemaFilePath];
7294

95+
if (schemaFilePath.endsWith('.ts')) {
96+
const sourceCode = readFileSync(schemaFilePath, 'utf-8');
97+
const tempSchemaPath = join(tempDir, 'base-schemas.js');
98+
mkdirSync(dirname(tempSchemaPath), { recursive: true });
99+
writeFileSync(tempSchemaPath, stripTypeScript(sourceCode), 'utf-8');
100+
apis[0] = tempSchemaPath;
101+
}
102+
73103
const schemaSpec = swaggerJsdoc({
74104
definition: {
75105
openapi: '3.0.0',
@@ -125,6 +155,8 @@ export function createBaseSpec(options: BaseSpecOptions): OpenAPIV3.Document {
125155
`[openapi] Failed to parse base schemas from ${baseSchemasPath}:`,
126156
error instanceof Error ? error.message : error
127157
);
158+
} finally {
159+
rmSync(tempDir, { recursive: true, force: true });
128160
}
129161
}
130162

src/lib/generator.spec.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,58 @@ export const GET: RequestHandler = async () => {
150150
expect(spec.paths?.['/api/upload/status/{uploadId}']?.get?.summary).toBe('Get upload status');
151151
});
152152

153+
it('should honor include and exclude patterns when discovering routes', () => {
154+
const includedDir = join(tempDir, 'src', 'routes', 'api', 'included');
155+
const excludedDir = join(tempDir, 'src', 'routes', 'api', 'excluded');
156+
mkdirSync(includedDir, { recursive: true });
157+
mkdirSync(excludedDir, { recursive: true });
158+
159+
writeFileSync(
160+
join(includedDir, '+server.js'),
161+
`
162+
/**
163+
* @swagger
164+
* /api/included:
165+
* get:
166+
* summary: Included route
167+
*/
168+
export async function GET() {
169+
return new Response('included');
170+
}
171+
`,
172+
'utf-8'
173+
);
174+
175+
writeFileSync(
176+
join(excludedDir, '+server.js'),
177+
`
178+
/**
179+
* @swagger
180+
* /api/excluded:
181+
* get:
182+
* summary: Excluded route
183+
*/
184+
export async function GET() {
185+
return new Response('excluded');
186+
}
187+
`,
188+
'utf-8'
189+
);
190+
191+
const spec = generateSpec({
192+
rootDir: tempDir,
193+
include: ['src/routes/api/**/+server.js'],
194+
exclude: ['**/excluded/**'],
195+
info: {
196+
title: 'Test API',
197+
version: '1.0.0'
198+
}
199+
});
200+
201+
expect(Object.keys(spec.paths || {})).toContain('/api/included');
202+
expect(Object.keys(spec.paths || {})).not.toContain('/api/excluded');
203+
});
204+
153205
it('should merge shared schemas from baseSchemasPath', () => {
154206
// Create shared schemas file
155207
const libDir = join(tempDir, 'src', 'lib');

0 commit comments

Comments
 (0)