import assert from 'node:assert/strict' import fs from 'node:fs/promises' import http from 'node:http' import os from 'node:os' import path from 'node:path' import test from 'node:test' import { generateApiFiles } from '../src/core/generate.js' const createTempDir = async () => { return fs.mkdtemp(path.join(os.tmpdir(), 'yx-generate-api-')) } const writeJson = async (filePath, value) => { await fs.writeFile(filePath, JSON.stringify(value, null, 2), 'utf8') } const readFile = async (filePath) => { return fs.readFile(filePath, 'utf8') } test('full generation cleans stale auto-generated files and keeps manual files', async () => { const tempDir = await createTempDir() try { const initialSwaggerPath = path.join(tempDir, 'swagger-initial.json') const updatedSwaggerPath = path.join(tempDir, 'swagger-updated.json') const outputDir = path.join(tempDir, 'generated') await writeJson(initialSwaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetThing': { get: { tags: ['Alpha'], summary: 'Get alpha thing', responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Beta/GetList': { get: { tags: ['Beta'], summary: 'Get beta list', responses: { 200: { description: 'OK' }, }, }, }, }, }) await writeJson(updatedSwaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetThing': { get: { tags: ['Alpha'], summary: 'Get alpha thing', responses: { 200: { description: 'OK' }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: initialSwaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) await fs.writeFile(path.join(outputDir, 'manual-helper.js'), 'export const keepMe = true\n', 'utf8') await generateApiFiles({ projectRoot: tempDir, swaggerUrl: updatedSwaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) await assert.doesNotReject(fs.access(path.join(outputDir, 'alpha.js'))) await assert.rejects(fs.access(path.join(outputDir, 'beta.js'))) await assert.doesNotReject(fs.access(path.join(outputDir, 'manual-helper.js'))) const sharedFile = await readFile(path.join(outputDir, 'shared.js')) assert.doesNotMatch(sharedFile, /from 'qs'/) assert.match(sharedFile, /appendQueryEntries/) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('partial generation keeps other generated modules even when cleanOutput is enabled', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetThing': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Beta/GetList': { get: { tags: ['Beta'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: ['Alpha'], cleanOutput: true, }) await assert.doesNotReject(fs.access(path.join(outputDir, 'beta.js'))) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generated index includes namespace exports and flattened re-exports', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetThing': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Beta/GetList': { get: { tags: ['Beta'], responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Beta/GetDetail': { get: { tags: ['Beta'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) const indexContent = await readFile(path.join(outputDir, 'index.js')) assert.match(indexContent, /export \* as alphaApi from ["']\.\/alpha["']/) assert.match(indexContent, /export \* as betaApi from ["']\.\/beta["']/) assert.match(indexContent, /export \* from ["']\.\/alpha["']/) assert.match(indexContent, /export \* from ["']\.\/beta["']/) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generated module file includes module description from swagger tags', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', tags: [ { name: 'Ranking', description: '排行榜', }, ], paths: { '/api/v1/Ranking/GetList': { get: { responses: { 200: { description: 'OK' }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) const rankingContent = await readFile(path.join(outputDir, 'ranking.js')) assert.match(rankingContent, /\/\/ Module: Ranking/) assert.match(rankingContent, /\/\/ Module description: 排行榜/) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generated module file omits module description when swagger tags have no description', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', tags: [ { name: 'Ranking', }, ], paths: { '/api/v1/Ranking/GetList': { get: { tags: ['Ranking'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) const rankingContent = await readFile(path.join(outputDir, 'ranking.js')) assert.match(rankingContent, /\/\/ Module: Ranking/) assert.doesNotMatch(rankingContent, /\/\/ Module description:/) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generation can skip generated index.js when generateIndexFile is false', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await fs.mkdir(outputDir, { recursive: true }) await fs.writeFile( path.join(outputDir, 'index.js'), `// Auto-generated. Do not edit manually. // Swagger: old export * from './old' `, 'utf8', ) await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetThing': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, generateIndexFile: false, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) await assert.doesNotReject(fs.access(path.join(outputDir, 'alpha.js'))) await assert.rejects(fs.access(path.join(outputDir, 'index.js'))) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generation allows duplicate module export names when generateIndexFile is false', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetList': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Beta/GetList': { get: { tags: ['Beta'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await assert.doesNotReject( generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, generateIndexFile: false, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }), ) const alphaContent = await readFile(path.join(outputDir, 'alpha.js')) const betaContent = await readFile(path.join(outputDir, 'beta.js')) assert.match(alphaContent, /const getListApi =/) assert.match(betaContent, /const getListApi =/) await assert.rejects(fs.access(path.join(outputDir, 'index.js'))) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generation can rename duplicate exports by appending module names', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetList': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Beta/GetList': { get: { tags: ['Beta'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, resolveDuplicateExports: async ({ conflicts }) => { assert.equal(conflicts.length, 1) assert.equal(conflicts[0].exportName, 'getListApi') return { action: 'rename' } }, }) const alphaContent = await readFile(path.join(outputDir, 'alpha.js')) const betaContent = await readFile(path.join(outputDir, 'beta.js')) const indexContent = await readFile(path.join(outputDir, 'index.js')) assert.match(alphaContent, /const getListAlphaApi =/) assert.match(betaContent, /const getListBetaApi =/) assert.match(indexContent, /export \* from ["']\.\/alpha["']/) assert.match(indexContent, /export \* from ["']\.\/beta["']/) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generation automatically avoids collisions with module namespace export names', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/Values': { get: { tags: ['Values'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) const valuesContent = await readFile(path.join(outputDir, 'values.js')) const indexContent = await readFile(path.join(outputDir, 'index.js')) assert.match(valuesContent, /const getValuesApi =/) assert.match(indexContent, /export \* as valuesApi from ["']\.\/values["']/) assert.match(indexContent, /export \* from ["']\.\/values["']/) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('file download responses generate blob request config', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/File/Export': { get: { tags: ['File'], parameters: [ { name: 'id', in: 'query', schema: { type: 'integer', }, }, ], responses: { 200: { description: 'OK', content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary', }, }, }, }, }, }, }, '/api/v1/File/CreateExport': { post: { tags: ['File'], requestBody: { content: { 'application/json': { schema: { type: 'object', properties: { name: { type: 'string', }, }, }, }, }, }, responses: { 200: { description: 'OK', content: { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': {}, }, }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) const fileContent = await readFile(path.join(outputDir, 'file.js')) assert.match(fileContent, /@returns\s+\{Promise\}/) assert.match( fileContent, /const exportApi = \(params = \{\}\) =>\s+request\.get\(buildUrl\(`\/api\/v1\/File\/Export`, params\), \{\s*responseType: ["']blob["'],?\s*\}\)/s, ) assert.match( fileContent, /const createExportApi = \(data\) =>\s+request\.post\(`\/api\/v1\/File\/CreateExport`, data, \{\s*responseType: ["']blob["'],?\s*\}\)/s, ) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('untyped export endpoints infer blob request config from operation metadata', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Report/GetUserPointRecordFile': { get: { tags: ['Report'], summary: '导出积分排行榜', responses: { 200: { description: 'OK', }, }, }, }, '/api/v1/Report/GetExportJob': { get: { tags: ['Report'], summary: '获取导出任务状态', responses: { 200: { description: 'OK', content: { 'application/json': { schema: { type: 'object', properties: { status: { type: 'string', }, }, }, }, }, }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) const reportContent = await readFile(path.join(outputDir, 'report.js')) assert.match( reportContent, /const getUserPointRecordFileApi = \(\) =>\s+request\.get\(`\/api\/v1\/Report\/GetUserPointRecordFile`, \{\s*responseType: ["']blob["'],?\s*\}\)/s, ) assert.match( reportContent, /const getExportJobApi = \(\) =>\s+request\.get\(`\/api\/v1\/Report\/GetExportJob`\)/s, ) assert.doesNotMatch( reportContent, /request\.get\(`\/api\/v1\/Report\/GetExportJob`, \{\s*responseType: ["']blob["']/s, ) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('response jsdoc expands nested array object fields', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetRanking': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK', content: { 'application/json': { schema: { $ref: '#/components/schemas/NoteSelectRankingResultPageResponse', }, }, }, }, }, }, }, }, components: { schemas: { NoteSelectRankingResultPageResponse: { type: 'object', properties: { total: { type: 'integer', }, items: { type: 'array', items: { $ref: '#/components/schemas/NoteSelectRankingResultItem', }, }, }, }, NoteSelectRankingResultItem: { type: 'object', properties: { noteId: { type: 'integer', }, author: { $ref: '#/components/schemas/NoteAuthor', }, }, }, NoteAuthor: { type: 'object', properties: { nickName: { type: 'string', }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) const alphaContent = await readFile(path.join(outputDir, 'alpha.js')) assert.match( alphaContent, /@returns\s+\{Promise<\{\s*total\?: number,\s*items\?: Array<\{\s*noteId\?: number,\s*author\?: \{\s*nickName\?: string\s*\}\s*\}>\s*\}>\}/s, ) assert.match(alphaContent, /Response schema: NoteSelectRankingResultPageResponse/) assert.match(alphaContent, /\*\s*\n \* Response fields:/) assert.match(alphaContent, /- items\?: \{Array\}/) assert.match(alphaContent, / - items\[\]\.noteId\?: \{number\}/) assert.match(alphaContent, / - items\[\]\.author\?: \{Object\}/) assert.match(alphaContent, / - items\[\]\.author\.nickName\?: \{string\}/) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('request body jsdoc expands nested array object fields', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/Create': { post: { tags: ['Alpha'], requestBody: { content: { 'application/json': { schema: { $ref: '#/components/schemas/CreatePayload', }, }, }, }, responses: { 200: { description: 'OK' }, }, }, }, }, components: { schemas: { CreatePayload: { type: 'object', properties: { title: { type: 'string', }, items: { type: 'array', items: { $ref: '#/components/schemas/CreateItem', }, }, }, }, CreateItem: { type: 'object', properties: { id: { type: 'integer', }, author: { $ref: '#/components/schemas/Author', }, }, }, Author: { type: 'object', properties: { nickName: { type: 'string', }, }, }, }, }, }) await generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }) const alphaContent = await readFile(path.join(outputDir, 'alpha.js')) assert.match( alphaContent, /@param\s+\{\{\s*title\?: string,\s*items\?: Array<\{\s*id\?: number,\s*author\?: \{\s*nickName\?: string\s*\}\s*\}>\s*\}\}\s+data Request body/s, ) assert.match(alphaContent, /Request body schema: CreatePayload/) assert.match(alphaContent, /\*\s*\n \* Request body fields:/) assert.match(alphaContent, /- data\.title\?: \{string\}/) assert.match(alphaContent, /- data\.items\?: \{Array\}/) assert.match(alphaContent, / - data\.items\[\]\.id\?: \{number\}/) assert.match(alphaContent, / - data\.items\[\]\.author\?: \{Object\}/) assert.match(alphaContent, / - data\.items\[\]\.author\.nickName\?: \{string\}/) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('response jsdoc handles circular schemas without recursion errors', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Tree/GetNode': { get: { tags: ['Tree'], responses: { 200: { description: 'OK', content: { 'application/json': { schema: { $ref: '#/components/schemas/TreeNode', }, }, }, }, }, }, }, }, components: { schemas: { TreeNode: { type: 'object', properties: { id: { type: 'integer', }, children: { type: 'array', items: { $ref: '#/components/schemas/TreeNode', }, }, }, }, }, }, }) await assert.doesNotReject( generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }), ) const treeContent = await readFile(path.join(outputDir, 'tree.js')) assert.match(treeContent, /@returns\s+\{Promise<\{\s*id\?: number,\s*children\?: Array\s*\}>\}/s) assert.match(treeContent, /- children\?: \{Array\}/) assert.doesNotMatch(treeContent, /children\[\]\./) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('request body jsdoc handles circular schemas without recursion errors', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Tree/CreateNode': { post: { tags: ['Tree'], requestBody: { content: { 'application/json': { schema: { $ref: '#/components/schemas/TreeNode', }, }, }, }, responses: { 200: { description: 'OK' }, }, }, }, }, components: { schemas: { TreeNode: { type: 'object', properties: { id: { type: 'integer', }, children: { type: 'array', items: { $ref: '#/components/schemas/TreeNode', }, }, }, }, }, }, }) await assert.doesNotReject( generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }), ) const treeContent = await readFile(path.join(outputDir, 'tree.js')) assert.match( treeContent, /@param\s+\{\{\s*id\?: number,\s*children\?: Array\s*\}\}\s+data Request body/s, ) assert.match(treeContent, /Request body schema: TreeNode/) assert.match(treeContent, /- data\.children\?: \{Array\}/) assert.doesNotMatch(treeContent, /data\.children\[\]\./) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('partial generation fails when retained generated files still collide with namespace exports', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') const outputDir = path.join(tempDir, 'generated') await fs.mkdir(outputDir, { recursive: true }) await fs.writeFile( path.join(outputDir, 'values.js'), `// Auto-generated. Do not edit manually. // Module: Values const valuesApi = () => null export { valuesApi } `, 'utf8', ) await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetThing': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await assert.rejects( generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir, requestImport: '../request', paramStyle: 'object', modules: ['Alpha'], cleanOutput: true, }), /namespace export/, ) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generation fails when two modules expose the same API function name', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetList': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Beta/GetList': { get: { tags: ['Beta'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await assert.rejects( generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir: path.join(tempDir, 'generated'), requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }), /\/api\/v1\/Alpha\/GetList/, ) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('generation fails if duplicate exports still conflict after rename', async () => { const tempDir = await createTempDir() try { const swaggerPath = path.join(tempDir, 'swagger.json') await writeJson(swaggerPath, { openapi: '3.0.0', paths: { '/api/v1/Alpha/GetList': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Alpha/GetListAlpha': { get: { tags: ['Alpha'], responses: { 200: { description: 'OK' }, }, }, }, '/api/v1/Beta/GetList': { get: { tags: ['Beta'], responses: { 200: { description: 'OK' }, }, }, }, }, }) await assert.rejects( generateApiFiles({ projectRoot: tempDir, swaggerUrl: swaggerPath, swaggerTimeoutMs: 1000, outputDir: path.join(tempDir, 'generated'), requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, resolveDuplicateExports: async () => { return { action: 'rename' } }, }), /Automatic rename still leaves duplicate exports/, ) } finally { await fs.rm(tempDir, { recursive: true, force: true }) } }) test('swagger timeout surfaces a clear error message', async () => { const tempDir = await createTempDir() const server = http.createServer((_, response) => { setTimeout(() => { response.writeHead(200, { 'Content-Type': 'application/json' }) response.end(JSON.stringify({ openapi: '3.0.0', paths: {} })) }, 100) }) try { await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) const { port } = server.address() await assert.rejects( generateApiFiles({ projectRoot: tempDir, swaggerUrl: `http://127.0.0.1:${port}/swagger.json`, swaggerTimeoutMs: 10, outputDir: path.join(tempDir, 'generated'), requestImport: '../request', paramStyle: 'object', modules: [], cleanOutput: true, }), /timed out/, ) } finally { server.close() await fs.rm(tempDir, { recursive: true, force: true }) } })