mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 07:28:37 +08:00
test: 统一前端测试夹具与发布工具链 (#704)
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { HttpResponse } from 'msw'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { apiFailureJson, apiJson } from '../support/msw/response'
|
||||
|
||||
describe('API response fixtures', () => {
|
||||
it('keeps raw MSW responses unchanged for HTTP errors and plugin protocols', async () => {
|
||||
const response = HttpResponse.json([{ id: 1 }])
|
||||
|
||||
await expect(response.json()).resolves.toEqual([{ id: 1 }])
|
||||
})
|
||||
|
||||
it('builds the complete main-program success envelope explicitly', async () => {
|
||||
const response = apiJson({ id: 1 })
|
||||
|
||||
await expect(response.json()).resolves.toEqual({ data: { id: 1 }, message: '', success: true })
|
||||
})
|
||||
|
||||
it('builds the complete main-program business-failure envelope explicitly', async () => {
|
||||
const response = apiFailureJson('rejected', { id: 1 })
|
||||
|
||||
await expect(response.json()).resolves.toEqual({ data: { id: 1 }, message: 'rejected', success: false })
|
||||
})
|
||||
})
|
||||
@@ -6,6 +6,7 @@ const workflowPath = resolve(process.cwd(), '.github/workflows/test.yml')
|
||||
const releaseWorkflowPath = resolve(process.cwd(), '.github/workflows/build.yml')
|
||||
const testingGuidePath = resolve(process.cwd(), 'docs/testing.md')
|
||||
const codeQualityGuidePath = resolve(process.cwd(), 'docs/code-quality.md')
|
||||
const prettierIgnorePath = resolve(process.cwd(), '.prettierignore')
|
||||
|
||||
describe('前端测试 workflow', () => {
|
||||
it('在 PR 与 v3 push 上运行,并将变更文件格式检查限制为 PR', () => {
|
||||
@@ -59,9 +60,25 @@ describe('前端测试 workflow', () => {
|
||||
const workflow = readFileSync(releaseWorkflowPath, 'utf8')
|
||||
|
||||
expect(workflow).toContain('name: Build Moviepilot-Frontend v3')
|
||||
expect(workflow).toContain('permissions:\n contents: write')
|
||||
expect(workflow).toContain('push:\n branches:\n - v3')
|
||||
expect(workflow).toContain(" - 'package.json'")
|
||||
expect(workflow).toContain('timeout-minutes: 30')
|
||||
expect(workflow).toContain('uses: actions/checkout@v7')
|
||||
expect(workflow).toContain('uses: actions/setup-node@v7')
|
||||
expect(workflow).toContain("node-version: '24'")
|
||||
expect(workflow).toContain('cache-dependency-path: yarn.lock')
|
||||
expect(workflow).toContain('corepack install --global yarn@1.22.22')
|
||||
expect(workflow).toContain('run: yarn --frozen-lockfile')
|
||||
expect(workflow).toContain('echo "frontend_version=v$frontend_version"')
|
||||
expect(workflow).not.toContain('actions/checkout@v3')
|
||||
expect(workflow).not.toContain('actions/setup-node@v3')
|
||||
expect(workflow).not.toContain(' - v2')
|
||||
})
|
||||
|
||||
it('全仓格式检查排除仓内 linked worktree', () => {
|
||||
const prettierIgnore = readFileSync(prettierIgnorePath, 'utf8')
|
||||
|
||||
expect(prettierIgnore).toContain('/.worktrees/')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
import { abortAllRequests } from '@/utils/requestOptimizer'
|
||||
import { cleanup } from '@testing-library/vue'
|
||||
import { HttpResponse, type HttpResponseInit, type JsonBodyType } from 'msw'
|
||||
import { afterAll, afterEach, beforeAll, vi } from 'vitest'
|
||||
import { createDataApiMock as buildDataApiMock } from './support/apiMock'
|
||||
import { server } from './support/msw/server'
|
||||
@@ -13,61 +12,6 @@ declare global {
|
||||
|
||||
globalThis.createDataApiMock = buildDataApiMock
|
||||
|
||||
const originalJsonResponse = HttpResponse.json.bind(HttpResponse)
|
||||
|
||||
/** 判断测试夹具是否已经表达了业务响应状态。 */
|
||||
function isLegacyApiEnvelope(body: unknown): body is Record<string, unknown> & { success: boolean } {
|
||||
return (
|
||||
body !== null &&
|
||||
typeof body === 'object' &&
|
||||
!Array.isArray(body) &&
|
||||
typeof (body as { success?: unknown }).success === 'boolean'
|
||||
)
|
||||
}
|
||||
|
||||
/** 识别 MSW 夹具中历史遗留的 Axios `{ data }` 响应壳。 */
|
||||
function isLegacyDataWrapper(body: unknown): body is { data: unknown } {
|
||||
if (!body || typeof body !== 'object' || Array.isArray(body)) return false
|
||||
return Object.keys(body).length === 1 && 'data' in body
|
||||
}
|
||||
|
||||
/**
|
||||
* 将业务测试中的裸成功数据适配为当前后端统一 envelope。
|
||||
*
|
||||
* 低层客户端协议测试使用 Axios adapter,不经过这里;HTTP 错误保持原载荷,
|
||||
* 便于继续覆盖 detail、Blob 和非标准错误响应的归一化行为。
|
||||
*/
|
||||
function installApiEnvelopeFixtureAdapter() {
|
||||
Object.defineProperty(HttpResponse, 'json', {
|
||||
configurable: true,
|
||||
value: <BodyType extends JsonBodyType>(body?: BodyType | null, init: HttpResponseInit = {}) => {
|
||||
const status = init.status ?? 200
|
||||
if (status >= 400) return originalJsonResponse(body, init)
|
||||
|
||||
if (isLegacyApiEnvelope(body)) {
|
||||
const envelope = body as Record<string, unknown> & { success: boolean }
|
||||
return originalJsonResponse(
|
||||
{
|
||||
...envelope,
|
||||
message: typeof envelope.message === 'string' ? envelope.message : '',
|
||||
data: Object.hasOwn(envelope, 'data') ? envelope.data : null,
|
||||
},
|
||||
init,
|
||||
)
|
||||
}
|
||||
|
||||
if (isLegacyDataWrapper(body)) {
|
||||
return originalJsonResponse({ success: true, message: '', data: body.data }, init)
|
||||
}
|
||||
|
||||
return originalJsonResponse({ success: true, message: '', data: body ?? null }, init)
|
||||
},
|
||||
writable: true,
|
||||
})
|
||||
}
|
||||
|
||||
installApiEnvelopeFixtureAdapter()
|
||||
|
||||
class ResizeObserverStub implements ResizeObserver {
|
||||
disconnect() {}
|
||||
observe() {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { DiscoverSource } from '@/api/types'
|
||||
import { HttpResponse, http, type JsonBodyType } from 'msw'
|
||||
import { apiJson } from '../response'
|
||||
|
||||
const API_BASE_URL = 'http://localhost/api/v1/'
|
||||
|
||||
@@ -21,7 +22,8 @@ export function discoverSourcesHandler(
|
||||
) {
|
||||
return http.get(discoverApiUrls.sources, async () => {
|
||||
await onRequest()
|
||||
return HttpResponse.json(sources as unknown as JsonBodyType, { status })
|
||||
if (status >= 400) return HttpResponse.json(sources as unknown as JsonBodyType, { status })
|
||||
return apiJson(sources, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,7 +34,8 @@ export function discoverOrderConfigHandler(
|
||||
) {
|
||||
return http.get(discoverApiUrls.orderConfig, async () => {
|
||||
await onRequest()
|
||||
return HttpResponse.json({ data: { value: order }, success: status < 400 }, { status })
|
||||
if (status >= 400) return HttpResponse.json({ detail: 'failed' }, { status })
|
||||
return apiJson({ value: order }, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,6 +46,7 @@ export function saveDiscoverOrderHandler(
|
||||
return http.post(discoverApiUrls.orderConfig, async ({ request }) => {
|
||||
const order = (await request.json()) as DiscoverTabConfigItem[]
|
||||
await onSave(order)
|
||||
return HttpResponse.json({ success: status < 400 }, { status })
|
||||
if (status >= 400) return HttpResponse.json({ detail: 'failed' }, { status })
|
||||
return apiJson(null, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { DownloadHistory, DownloadingInfo } from '@/api/types'
|
||||
import { HttpResponse, http, type JsonBodyType } from 'msw'
|
||||
import { apiFailureJson, apiJson } from '../response'
|
||||
|
||||
const API_BASE_URL = 'http://localhost/api/v1/'
|
||||
|
||||
@@ -15,8 +16,15 @@ export const downloadApiUrls = {
|
||||
history: new URL('history/download', API_BASE_URL).href,
|
||||
}
|
||||
|
||||
function jsonResponse(body: JsonBodyType, status: number) {
|
||||
return HttpResponse.json(body, { status })
|
||||
function dataResponse(body: JsonBodyType, status: number) {
|
||||
if (status >= 400) return HttpResponse.json(body, { status })
|
||||
return apiJson(body, { status })
|
||||
}
|
||||
|
||||
function mutationResponse(response: DownloadMutationResponse, status: number) {
|
||||
if (status >= 400) return HttpResponse.json(response, { status })
|
||||
if (!response.success) return apiFailureJson(response.message ?? '', null, { status })
|
||||
return apiJson(null, { status })
|
||||
}
|
||||
|
||||
/** 拦截下载任务快照查询,并保留下载器查询参数供断言。 */
|
||||
@@ -29,7 +37,7 @@ export function downloadingListHandler(
|
||||
const url = new URL(request.url)
|
||||
await onRequest(url)
|
||||
const body = typeof response === 'function' ? await response(url) : response
|
||||
return jsonResponse(body as unknown as JsonBodyType, status)
|
||||
return dataResponse(body as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,7 +52,7 @@ export function downloadActionHandler(
|
||||
return http.get(downloadApiUrls.action(operation, hash), async ({ request }) => {
|
||||
const url = new URL(request.url)
|
||||
await onRequest(url)
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -58,7 +66,7 @@ export function deleteDownloadHandler(
|
||||
return http.delete(downloadApiUrls.delete(hash), async ({ request }) => {
|
||||
const url = new URL(request.url)
|
||||
await onRequest(url)
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,7 +80,7 @@ export function downloadHistoryHandler(
|
||||
const url = new URL(request.url)
|
||||
await onRequest(url)
|
||||
const body = typeof response === 'function' ? await response(url) : response
|
||||
return jsonResponse(body as unknown as JsonBodyType, status)
|
||||
return dataResponse(body as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -84,6 +92,6 @@ export function deleteDownloadHistoryHandler(
|
||||
) {
|
||||
return http.delete(downloadApiUrls.history, async ({ request }) => {
|
||||
await onRequest((await request.json()) as DownloadHistory)
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { MediaInfo, MediaSeason, NotExistMediaInfo, TmdbEpisode } from '@/api/types'
|
||||
import { HttpResponse, http, type JsonBodyType } from 'msw'
|
||||
import { apiFailureJson, apiJson } from '../response'
|
||||
|
||||
const API_BASE_URL = 'http://localhost/api/v1/'
|
||||
|
||||
@@ -14,6 +15,11 @@ export const mediaApiUrls = {
|
||||
seasons: new URL('media/seasons', API_BASE_URL).href,
|
||||
}
|
||||
|
||||
function dataResponse(body: JsonBodyType, status: number) {
|
||||
if (status >= 400) return HttpResponse.json(body, { status })
|
||||
return apiJson(body, { status })
|
||||
}
|
||||
|
||||
export function mediaExistsHandler(
|
||||
response: { data?: Record<string, unknown>; message?: string; success: boolean },
|
||||
status = 200,
|
||||
@@ -21,7 +27,8 @@ export function mediaExistsHandler(
|
||||
) {
|
||||
return http.get(mediaApiUrls.exists, async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return HttpResponse.json({ message: '', ...response } as JsonBodyType, { status })
|
||||
if (!response.success) return apiFailureJson(response.message ?? '', response.data ?? null, { status })
|
||||
return apiJson(response.data ?? null, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,7 +40,7 @@ export function mediaDetailsHandler(
|
||||
) {
|
||||
return http.get(mediaApiUrls.details(String(mediaId)), ({ request }) => {
|
||||
onRequest(new URL(request.url))
|
||||
return HttpResponse.json(response as unknown as JsonBodyType, { status })
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,7 +51,7 @@ export function mediaRemoteExistsHandler(
|
||||
) {
|
||||
return http.post(mediaApiUrls.existsRemote, async ({ request }) => {
|
||||
await onRequest((await request.json()) as Record<string, unknown>)
|
||||
return HttpResponse.json(response as JsonBodyType, { status })
|
||||
return dataResponse(response as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,7 +63,8 @@ export function mediaPlayHandler(
|
||||
) {
|
||||
return http.get(mediaApiUrls.play(itemId), () => {
|
||||
onRequest()
|
||||
return HttpResponse.json(response as JsonBodyType, { status })
|
||||
if (!response.success) return apiFailureJson(response.message ?? '', response.data ?? null, { status })
|
||||
return apiJson(response.data ?? null, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,7 +77,7 @@ export function tmdbSeasonEpisodesHandler(
|
||||
) {
|
||||
return http.get(new URL(`tmdb/${tmdbId}/${season}`, API_BASE_URL).href, ({ request }) => {
|
||||
onRequest(new URL(request.url))
|
||||
return HttpResponse.json(response as unknown as JsonBodyType, { status })
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,7 +88,7 @@ export function mediaSeasonsHandler(
|
||||
) {
|
||||
return http.get(mediaApiUrls.seasons, async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return HttpResponse.json(response as unknown as JsonBodyType, { status })
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,7 +100,7 @@ export function mediaEpisodeGroupsHandler(
|
||||
) {
|
||||
return http.get(mediaApiUrls.episodeGroups(tmdbId), async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return HttpResponse.json(response as JsonBodyType, { status })
|
||||
return dataResponse(response as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,7 +112,7 @@ export function mediaGroupSeasonsHandler(
|
||||
) {
|
||||
return http.get(mediaApiUrls.groupSeasons(episodeGroup), async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return HttpResponse.json(response as unknown as JsonBodyType, { status })
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,6 +124,6 @@ export function mediaNotExistsHandler(
|
||||
return http.post(mediaApiUrls.notExists, async ({ request }) => {
|
||||
const payload = (await request.json()) as Record<string, unknown>
|
||||
await onRequest(payload, new URL(request.url))
|
||||
return HttpResponse.json(response as unknown as JsonBodyType, { status })
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { RecommendSource } from '@/api/types'
|
||||
import { HttpResponse, http, type JsonBodyType } from 'msw'
|
||||
import { apiJson } from '../response'
|
||||
|
||||
const API_BASE_URL = 'http://localhost/api/v1/'
|
||||
|
||||
@@ -9,36 +10,28 @@ export const recommendApiUrls = {
|
||||
sources: new URL('recommend/source', API_BASE_URL).href,
|
||||
}
|
||||
|
||||
export function recommendSourcesHandler(
|
||||
sources: RecommendSource[],
|
||||
status = 200,
|
||||
onRequest: () => void = () => {},
|
||||
) {
|
||||
export function recommendSourcesHandler(sources: RecommendSource[], status = 200, onRequest: () => void = () => {}) {
|
||||
return http.get(recommendApiUrls.sources, () => {
|
||||
onRequest()
|
||||
return HttpResponse.json(sources, { status })
|
||||
if (status >= 400) return HttpResponse.json(sources, { status })
|
||||
return apiJson(sources, { status })
|
||||
})
|
||||
}
|
||||
|
||||
export function recommendConfigHandler(
|
||||
config: JsonBodyType,
|
||||
status = 200,
|
||||
onRequest: () => void = () => {},
|
||||
) {
|
||||
export function recommendConfigHandler(config: JsonBodyType, status = 200, onRequest: () => void = () => {}) {
|
||||
return http.get(recommendApiUrls.config, () => {
|
||||
onRequest()
|
||||
return HttpResponse.json({ data: { value: config } }, { status })
|
||||
if (status >= 400) return HttpResponse.json({ detail: 'failed' }, { status })
|
||||
return apiJson({ value: config }, { status })
|
||||
})
|
||||
}
|
||||
|
||||
export function saveRecommendConfigHandler(
|
||||
onSave: (config: Record<string, boolean>) => void = () => {},
|
||||
status = 200,
|
||||
) {
|
||||
export function saveRecommendConfigHandler(onSave: (config: Record<string, boolean>) => void = () => {}, status = 200) {
|
||||
return http.post(recommendApiUrls.config, async ({ request }) => {
|
||||
const config = (await request.json()) as Record<string, boolean>
|
||||
onSave(config)
|
||||
return HttpResponse.json({ success: status < 400 }, { status })
|
||||
if (status >= 400) return HttpResponse.json({ detail: 'failed' }, { status })
|
||||
return apiJson(null, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,6 +43,7 @@ export function recommendMediaHandler(
|
||||
) {
|
||||
return http.get(recommendApiUrls.media(sourcePath), () => {
|
||||
onRequest()
|
||||
return HttpResponse.json(response as JsonBodyType, { status })
|
||||
if (status >= 400) return HttpResponse.json(response as JsonBodyType, { status })
|
||||
return apiJson(response, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
TransferDirectoryConf,
|
||||
} from '@/api/types'
|
||||
import { HttpResponse, http, type JsonBodyType, type RequestHandler } from 'msw'
|
||||
import { apiFailureJson, apiJson } from '../response'
|
||||
|
||||
const API_BASE_URL = 'http://localhost/api/v1/'
|
||||
|
||||
@@ -65,8 +66,15 @@ export const subscribeApiUrls = {
|
||||
update: new URL('subscribe/', API_BASE_URL).href,
|
||||
}
|
||||
|
||||
function jsonResponse(body: JsonBodyType, status: number) {
|
||||
return HttpResponse.json(body, { status })
|
||||
function dataResponse(body: JsonBodyType, status: number) {
|
||||
if (status >= 400) return HttpResponse.json(body, { status })
|
||||
return apiJson(body, { status })
|
||||
}
|
||||
|
||||
function mutationResponse(response: SubscribeMutationResponse, status: number) {
|
||||
if (status >= 400) return HttpResponse.json(response, { status })
|
||||
if (!response.success) return apiFailureJson(response.message ?? '', response.data ?? null, { status })
|
||||
return apiJson(response.data ?? null, { status })
|
||||
}
|
||||
|
||||
export function subscribeListHandler(
|
||||
@@ -76,7 +84,7 @@ export function subscribeListHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.list, ({ request }) => {
|
||||
onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return dataResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -87,7 +95,7 @@ export function popularSubscribesHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.popular, async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response as unknown as JsonBodyType, status)
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,7 +106,7 @@ export function subscribeSharesHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.shares, async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response as unknown as JsonBodyType, status)
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -110,7 +118,7 @@ export function shareSubscribeHandler(
|
||||
return http.post(subscribeApiUrls.share, async ({ request }) => {
|
||||
const payload = (await request.json()) as SubscribeShare
|
||||
await onShare(payload)
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -122,7 +130,7 @@ export function forkSubscribeHandler(
|
||||
return http.post(subscribeApiUrls.fork, async ({ request }) => {
|
||||
const payload = (await request.json()) as SubscribeShare
|
||||
await onFork(payload)
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -133,7 +141,8 @@ export function followSubscribersSettingHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.followSubscribers, async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse({ data: { value: users }, success: status < 400 }, status)
|
||||
if (status >= 400) return HttpResponse.json({ detail: 'failed' }, { status })
|
||||
return apiJson({ value: users }, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -144,7 +153,7 @@ export function followSubscriberHandler(
|
||||
) {
|
||||
return http.post(subscribeApiUrls.follow, async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -155,7 +164,7 @@ export function unfollowSubscriberHandler(
|
||||
) {
|
||||
return http.delete(subscribeApiUrls.follow, async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -167,7 +176,7 @@ export function deleteSubscribeShareHandler(
|
||||
) {
|
||||
return http.delete(subscribeApiUrls.shareById(id), async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,7 +187,7 @@ export function subscribeShareStatisticsHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.shareStatistics, async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response as unknown as JsonBodyType, status)
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -190,7 +199,7 @@ export function subscribeFilesHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.filesById(id), async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return dataResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -202,7 +211,7 @@ export function subscribeHistoryHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.historyByType(type), async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response as unknown as JsonBodyType, status)
|
||||
return dataResponse(response as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -214,7 +223,7 @@ export function deleteSubscribeHistoryHandler(
|
||||
) {
|
||||
return http.delete(subscribeApiUrls.historyById(id), async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -226,7 +235,8 @@ export function subscribeOrderConfigHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.orderConfig(type), ({ request }) => {
|
||||
onRequest(new URL(request.url))
|
||||
return jsonResponse({ data: { value }, success: status < 400 }, status)
|
||||
if (status >= 400) return HttpResponse.json({ detail: 'failed' }, { status })
|
||||
return apiJson({ value }, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -239,7 +249,7 @@ export function saveSubscribeOrderConfigHandler(
|
||||
return http.post(subscribeApiUrls.orderConfig(type), async ({ request }) => {
|
||||
const payload = (await request.json()) as { id: number }[]
|
||||
await onSave(payload, new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -251,7 +261,7 @@ export function updateSubscribeStatusHandler(
|
||||
) {
|
||||
return http.put(subscribeApiUrls.statusById(id), async ({ request }) => {
|
||||
await onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -263,7 +273,7 @@ export function searchSubscribeByIdHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.searchById(id), ({ request }) => {
|
||||
onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -275,7 +285,7 @@ export function resetSubscribeByIdHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.resetById(id), ({ request }) => {
|
||||
onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -287,7 +297,7 @@ export function createSubscribeHandler(
|
||||
return http.post(subscribeApiUrls.create, async ({ request }) => {
|
||||
const payload = (await request.json()) as Record<string, unknown>
|
||||
onCreate(payload)
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -299,7 +309,7 @@ export function updateSubscribeHandler(
|
||||
return http.put(subscribeApiUrls.update, async ({ request }) => {
|
||||
const payload = (await request.json()) as Record<string, unknown>
|
||||
onUpdate(payload)
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -311,7 +321,7 @@ export function querySubscribeByMediaHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.queryByMedia(mediaId), ({ request }) => {
|
||||
onRequest(new URL(request.url))
|
||||
return jsonResponse(subscribe as JsonBodyType, status)
|
||||
return dataResponse(subscribe as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -323,7 +333,7 @@ export function deleteSubscribeByMediaHandler(
|
||||
) {
|
||||
return http.delete(subscribeApiUrls.deleteByMedia(mediaId), ({ request }) => {
|
||||
onRequest(new URL(request.url))
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -335,7 +345,7 @@ export function subscribeDetailsHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.details(id), () => {
|
||||
onRequest()
|
||||
return jsonResponse(subscribe as unknown as JsonBodyType, status)
|
||||
return dataResponse(subscribe as unknown as JsonBodyType, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -347,7 +357,7 @@ export function deleteSubscribeByIdHandler(
|
||||
) {
|
||||
return http.delete(subscribeApiUrls.deleteById(id), () => {
|
||||
onRequest()
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -359,7 +369,8 @@ export function defaultSubscribeConfigHandler(
|
||||
) {
|
||||
return http.get(subscribeApiUrls.defaultConfig(type), () => {
|
||||
onRequest()
|
||||
return jsonResponse({ data: { value: config }, success: status < 400 }, status)
|
||||
if (status >= 400) return HttpResponse.json({ detail: 'failed' }, { status })
|
||||
return apiJson({ value: config }, { status })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -372,7 +383,7 @@ export function saveDefaultSubscribeConfigHandler(
|
||||
return http.post(subscribeApiUrls.defaultConfig(type, true), async ({ request }) => {
|
||||
const payload = (await request.json()) as Record<string, unknown>
|
||||
onSave(payload)
|
||||
return jsonResponse(response, status)
|
||||
return mutationResponse(response, status)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -409,23 +420,23 @@ export function subscribeDialogOptionHandlers(options: SubscribeDialogOptions =
|
||||
return [
|
||||
http.get(subscribeApiUrls.sites, () => {
|
||||
onSites()
|
||||
return jsonResponse(sites as unknown as JsonBodyType, 200)
|
||||
return dataResponse(sites as unknown as JsonBodyType, 200)
|
||||
}),
|
||||
http.get(subscribeApiUrls.downloaders, () => {
|
||||
onDownloaders()
|
||||
return jsonResponse(downloaders as unknown as JsonBodyType, 200)
|
||||
return dataResponse(downloaders as unknown as JsonBodyType, 200)
|
||||
}),
|
||||
http.get(subscribeApiUrls.directories, () => {
|
||||
onDirectories()
|
||||
return jsonResponse({ data: { value: directories }, success: true }, 200)
|
||||
return apiJson({ value: directories })
|
||||
}),
|
||||
http.get(subscribeApiUrls.filterRuleGroups, () => {
|
||||
onFilterRuleGroups()
|
||||
return jsonResponse({ data: { value: filterRuleGroups }, success: true }, 200)
|
||||
return apiJson({ value: filterRuleGroups })
|
||||
}),
|
||||
http.get(subscribeApiUrls.episodeGroups(tmdbId), () => {
|
||||
onEpisodeGroups()
|
||||
return jsonResponse(episodeGroups as unknown as JsonBodyType, 200)
|
||||
return dataResponse(episodeGroups as unknown as JsonBodyType, 200)
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ApiResponse } from '@/api/types'
|
||||
import { HttpResponse, type HttpResponseInit, type JsonBodyType } from 'msw'
|
||||
|
||||
/** 构造主程序普通 API 的固定三段式响应。 */
|
||||
export function apiEnvelope<T>(data: T | null, message = ''): ApiResponse<T> {
|
||||
return { data, message, success: true }
|
||||
}
|
||||
|
||||
/** 返回主程序普通 API 的成功响应;调用点必须显式选择该协议。 */
|
||||
export function apiJson<T>(data: T | null, init: HttpResponseInit = {}) {
|
||||
return HttpResponse.json(apiEnvelope(data) as unknown as JsonBodyType, init)
|
||||
}
|
||||
|
||||
/** 返回主程序普通 API 的业务失败响应。 */
|
||||
export function apiFailureJson<T = null>(message: string, data: T | null = null, init: HttpResponseInit = {}) {
|
||||
const body: ApiResponse<T> = { data, message, success: false }
|
||||
return HttpResponse.json(body as unknown as JsonBodyType, init)
|
||||
}
|
||||
Reference in New Issue
Block a user