Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 41 additions & 16 deletions packages/wormhole/src/plugins/presets/apifox.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { ApiPlugin } from '@/type'
import { isEmpty } from 'lodash'

export type ScopeType = 'ALL' | 'SELECTED_ENDPOINTS' | 'SELECTED_TAGS' | 'SELECTED_FOLDERS'

export interface APIFoxBody {
scope?: {
type?: 'ALL' | 'SELECTED_TAGS'
type?: ScopeType
selectedEndpointIds?: number[]
selectedTags?: string[]
selectedFolderIds?: number[]
excludedByTags?: string[]
}
options?: {
Expand All @@ -13,36 +16,49 @@ export interface APIFoxBody {
}
oasVersion?: '2.0' | '3.0' | '3.1'
exportFormat?: 'JSON' | 'YAML'
environmentIds?: string[]
environmentIds?: number[]
branchId?: number
moduleId?: number
}

export interface ApifoxOptions
extends Pick<APIFoxBody, 'oasVersion' | 'exportFormat'>,
extends Pick<APIFoxBody, 'oasVersion' | 'exportFormat' | 'environmentIds' | 'branchId' | 'moduleId'>,
Pick<
NonNullable<APIFoxBody['options']>,
'includeApifoxExtensionProperties' | 'addFoldersToTags'
'includeApifoxExtensionProperties' | 'addFoldersToTags'
> {
projectId: string
apifoxToken: string
locale?: string
apifoxVersion?: string
scopeType?: ScopeType
selectedEndpointIds?: number[]
selectedTags?: string[]
selectedFolderIds?: number[]
excludedByTags?: string[]
}

export function apifox({
projectId,
locale = 'zh-CN',
apifoxVersion = '2024-03-28',
selectedTags,
scopeType = 'ALL',
selectedEndpointIds = [],
selectedTags = [],
selectedFolderIds = [],
excludedByTags = [],
apifoxToken,
oasVersion = '3.0',
exportFormat = 'JSON',
includeApifoxExtensionProperties = false,
addFoldersToTags = false,
environmentIds,
branchId,
moduleId,
}: ApifoxOptions): ApiPlugin {
const body: APIFoxBody = {
scope: {
type: scopeType,
excludedByTags,
},
options: {
Expand All @@ -51,18 +67,27 @@ export function apifox({
},
oasVersion,
exportFormat,
environmentIds,
branchId,
moduleId,
}
if (!body.scope) {
body.scope = {}
}
const tags = !isEmpty(selectedTags) ? selectedTags : '*'
if (tags === '*') {
body.scope.type = 'ALL'
}
else {
body.scope.type = 'SELECTED_TAGS'
body.scope.selectedTags = tags

// 根据不同的 scope 类型设置相应的参数
switch (scopeType) {
case 'ALL':
// 导出全部不需要额外参数
break
case 'SELECTED_ENDPOINTS':
body.scope!.selectedEndpointIds = selectedEndpointIds
break
case 'SELECTED_TAGS':
body.scope!.selectedTags = selectedTags
break
case 'SELECTED_FOLDERS':
body.scope!.selectedFolderIds = selectedFolderIds
break
}

return {
name: 'apifox',
config(config) {
Expand Down
146 changes: 144 additions & 2 deletions packages/wormhole/test/plugins/apifox.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ApifoxOptions } from '@/plugins/presets/apifox'
import type { GeneratorConfig } from '@/type'
import { describe, expect, it } from 'vitest'
import { apifox } from '@/plugins/presets/apifox'
Expand All @@ -13,7 +14,7 @@ describe('apifox preset plugin - config', () => {
plugins: [],
}

it('should set ALL scope when selectedTags not provided and construct request', async () => {
it('should set ALL scope by default and construct request', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
Expand Down Expand Up @@ -47,10 +48,11 @@ describe('apifox preset plugin - config', () => {
})
})

it('should set SELECTED_TAGS scope when selectedTags provided', async () => {
it('should set SELECTED_TAGS scope when scopeType is SELECTED_TAGS', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'SELECTED_TAGS',
selectedTags: ['user', 'order'],
})

Expand All @@ -71,6 +73,7 @@ describe('apifox preset plugin - config', () => {
oasVersion: '3.1',
exportFormat: 'YAML',
excludedByTags: ['internal'],
scopeType: 'ALL',
})

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
Expand Down Expand Up @@ -114,6 +117,145 @@ describe('apifox preset plugin - config', () => {
expect(plugin.name).toBe('apifox')
})

// Tests for enhanced functionality
it('should set SELECTED_ENDPOINTS scope when scopeType is SELECTED_ENDPOINTS', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'SELECTED_ENDPOINTS',
selectedEndpointIds: [123, 456],
})

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
const data = next.fetchOptions?.data as Record<string, any>
expect(data.scope?.type).toBe('SELECTED_ENDPOINTS')
expect(data.scope?.selectedEndpointIds).toEqual([123, 456])
})

it('should set SELECTED_FOLDERS scope when scopeType is SELECTED_FOLDERS', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'SELECTED_FOLDERS',
selectedFolderIds: [789, 101112],
})

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
const data = next.fetchOptions?.data as Record<string, any>
expect(data.scope?.type).toBe('SELECTED_FOLDERS')
expect(data.scope?.selectedFolderIds).toEqual([789, 101112])
})

it('should handle new optional parameters correctly', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'ALL',
environmentIds: [1, 2],
branchId: 100,
moduleId: 200,
})

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
const data = next.fetchOptions?.data as Record<string, any>

expect(data.environmentIds).toEqual([1, 2])
expect(data.branchId).toBe(100)
expect(data.moduleId).toBe(200)
})

it('should handle excludedByTags parameter correctly', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'ALL',
excludedByTags: ['excludeTag1', 'excludeTag2'],
})

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
const data = next.fetchOptions?.data as Record<string, any>
expect(data.scope.excludedByTags).toEqual(['excludeTag1', 'excludeTag2'])
})

it('should not set selectedTags when it is undefined even in SELECTED_TAGS mode', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'SELECTED_TAGS',
// 注意这里没有提供 selectedTags
})

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
// 在新版插件中,selectedTags 有默认值 [],并且在 scopeType 为 SELECTED_TAGS 时始终会被设置
const data = next.fetchOptions?.data as Record<string, any>
expect(data.scope.selectedTags).toEqual([])
})

it('should set empty array when selectedTags is explicitly set to undefined', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'SELECTED_TAGS',
selectedTags: undefined,
} as ApifoxOptions)

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
// 即使显式设置为 undefined,TypeScript 默认参数也会将其转换为空数组
const data = next.fetchOptions?.data as Record<string, any>
expect(data.scope.selectedTags).toEqual([])
})

it('should set empty array when selectedTags is explicitly set to empty array', async () => {
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'SELECTED_TAGS',
selectedTags: [],
})

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
const data = next.fetchOptions?.data as Record<string, any>
expect(data.scope.selectedTags).toEqual([])
})

it('should correctly infer scopeType when not explicitly provided', async () => {
// 新版插件中不再根据 selectedTags 自动推断 scopeType,而是默认使用 ALL
const plugin1 = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
selectedTags: ['tag1'],
})

const next1 = (await plugin1.config!(baseInputConfig)) ?? baseInputConfig
// 新版插件默认 scopeType 是 ALL,不会根据 selectedTags 自动推断
const data1 = next1.fetchOptions?.data as Record<string, any>
expect(data1.scope.type).toBe('ALL')

// 当没有提供 selectedTags 也没有指定 scopeType 时,应该默认为 ALL
const plugin2 = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
})
const next2 = (await plugin2.config!(baseInputConfig)) ?? baseInputConfig
const data2 = next2.fetchOptions?.data as Record<string, any>
expect(data2.scope.type).toBe('ALL')
})

it('should behave like old version when scopeType is not specified but selectedTags is provided (backward compatibility)', async () => {
// 为了向后兼容,如果需要根据 selectedTags 推断 scopeType,需要显式设置
const plugin = apifox({
projectId: 'proj-123',
apifoxToken: 'token-abc',
scopeType: 'SELECTED_TAGS',
selectedTags: ['user', 'order'],
})

const next = (await plugin.config!(baseInputConfig)) ?? baseInputConfig
const data = next.fetchOptions?.data as Record<string, any>
expect(data.scope?.type).toBe('SELECTED_TAGS')
expect(data.scope?.selectedTags).toEqual(['user', 'order'])
})

// Integration: actually use apifox via generateWithPlugin and MSW
it('should generate using Apifox export endpoint via MSW', async () => {
const { apiDefinitionsFile } = await generateWithPlugin(
Expand Down
16 changes: 12 additions & 4 deletions packages/wormhole/typings/plugins.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,10 +227,13 @@ export type ApiDescriptor = Omit<OperationObject, "requestBody" | "parameters" |
* });
*/
export declare function createPlugin<T extends any[]>(plugin: (...args: T) => ApiPlugin): (...args: T) => ApiPlugin;
export type ScopeType = "ALL" | "SELECTED_ENDPOINTS" | "SELECTED_TAGS" | "SELECTED_FOLDERS";
export interface APIFoxBody {
scope?: {
type?: "ALL" | "SELECTED_TAGS";
type?: ScopeType;
selectedEndpointIds?: number[];
selectedTags?: string[];
selectedFolderIds?: number[];
excludedByTags?: string[];
};
options?: {
Expand All @@ -239,17 +242,22 @@ export interface APIFoxBody {
};
oasVersion?: "2.0" | "3.0" | "3.1";
exportFormat?: "JSON" | "YAML";
environmentIds?: string[];
environmentIds?: number[];
branchId?: number;
moduleId?: number;
}
export interface ApifoxOptions extends Pick<APIFoxBody, "oasVersion" | "exportFormat">, Pick<NonNullable<APIFoxBody["options"]>, "includeApifoxExtensionProperties" | "addFoldersToTags"> {
export interface ApifoxOptions extends Pick<APIFoxBody, "oasVersion" | "exportFormat" | "environmentIds" | "branchId" | "moduleId">, Pick<NonNullable<APIFoxBody["options"]>, "includeApifoxExtensionProperties" | "addFoldersToTags"> {
projectId: string;
apifoxToken: string;
locale?: string;
apifoxVersion?: string;
scopeType?: ScopeType;
selectedEndpointIds?: number[];
selectedTags?: string[];
selectedFolderIds?: number[];
excludedByTags?: string[];
}
export declare function apifox({ projectId, locale, apifoxVersion, selectedTags, excludedByTags, apifoxToken, oasVersion, exportFormat, includeApifoxExtensionProperties, addFoldersToTags, }: ApifoxOptions): ApiPlugin;
export declare function apifox({ projectId, locale, apifoxVersion, scopeType, selectedEndpointIds, selectedTags, selectedFolderIds, excludedByTags, apifoxToken, oasVersion, exportFormat, includeApifoxExtensionProperties, addFoldersToTags, environmentIds, branchId, moduleId, }: ApifoxOptions): ApiPlugin;
/**
* Filter configuration interface
*/
Expand Down