yx_generate_api_js/test/generate.test.js

1211 lines
32 KiB
JavaScript

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<Blob>\}/)
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<Object>\}/)
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<Object>\}/)
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<Object>\s*\}>\}/s)
assert.match(treeContent, /- children\?: \{Array<Object>\}/)
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<Object>\s*\}\}\s+data Request body/s,
)
assert.match(treeContent, /Request body schema: TreeNode/)
assert.match(treeContent, /- data\.children\?: \{Array<Object>\}/)
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 })
}
})