diff --git a/frontend-vue/src/shared/api/java-modules.ts b/frontend-vue/src/shared/api/java-modules.ts index 0ddc47b8..2d04f015 100644 --- a/frontend-vue/src/shared/api/java-modules.ts +++ b/frontend-vue/src/shared/api/java-modules.ts @@ -11,6 +11,7 @@ import { getTaskProgressCacheTtlMs, getTaskProgressTimeoutMs } from "../task-pro import { createTaskProgressRequestCache } from "../task-progress-request-cache.ts"; export * from "./types/modules/dedupe.ts"; +export * from "./types/modules/split.ts"; const JAVA_API_PREFIX = "/newApi/api"; @@ -145,44 +146,6 @@ export async function uploadTempFileToJava( } -export interface SplitArchiveEntry { - filename: string; - rowCount?: number; -} - -export interface SplitResultItem { - resultId?: number; - sourceFilename: string; - outputFilename?: string; - success: boolean; - error?: string; - rowCount?: number; - downloadUrl?: string; - entryCount?: number; - entries?: SplitArchiveEntry[]; -} - -export interface SplitRunVo { - total: number; - successCount: number; - failedCount: number; - items: SplitResultItem[]; -} - -export interface SplitHistoryVo { - items: SplitResultItem[]; -} - -export interface SplitRunRequest { - files: UploadedFileRef[]; - selectedColumns: string[]; - splitMode: "rows_per_file" | "parts"; - rowsPerFile?: number; - parts?: number; - archiveName?: string; - user_id: number; -} - export interface ConvertResultItem { resultId?: number; sourceFilename: string; @@ -390,40 +353,6 @@ export function getExcelInfo(fileKey: string) { } -export function runSplit( - request: Omit | SplitRunRequest, -) { - return unwrapJavaResponse( - post, SplitRunRequest>( - `${JAVA_API_PREFIX}/split/run`, - { - ...request, - user_id: getCurrentUserId(), - }, - ), - ); -} - -export function getSplitHistory() { - return unwrapJavaResponse( - get>(`${JAVA_API_PREFIX}/split/history`, { - params: { user_id: getCurrentUserId() }, - }), - ); -} - -export function deleteSplitHistory(resultId: number) { - return unwrapJavaResponse( - del>(`${JAVA_API_PREFIX}/split/history/${resultId}`, { - params: { user_id: getCurrentUserId() }, - }), - ); -} - -export function getSplitResultDownloadUrl(resultId: number) { - return getJavaDownloadUrl(`/split/results/${resultId}/download`); -} - export function getConvertTemplates() { return unwrapJavaResponse( get>( diff --git a/frontend-vue/src/shared/api/types/modules/split.ts b/frontend-vue/src/shared/api/types/modules/split.ts new file mode 100644 index 00000000..ff754b5a --- /dev/null +++ b/frontend-vue/src/shared/api/types/modules/split.ts @@ -0,0 +1,86 @@ +import { get, post, del, type JavaApiResponse, unwrapJavaResponse } from '../../http.ts' +import { buildJavaUrl } from '../../url.ts' +import { getCurrentUserId } from '../../user.ts' +import { getJavaDownloadUrl } from '../../download-url.ts' +import type { UploadedFileRef } from '../upload.ts' +import { API_ENDPOINTS } from '../../endpoints.ts' + +export interface SplitArchiveEntry { + filename: string; + rowCount?: number; +} + +export interface SplitResultItem { + resultId?: number; + sourceFilename: string; + outputFilename?: string; + success: boolean; + error?: string; + rowCount?: number; + downloadUrl?: string; + entryCount?: number; + entries?: SplitArchiveEntry[]; +} + +export interface SplitRunVo { + total: number; + successCount: number; + failedCount: number; + items: SplitResultItem[]; +} + +export interface SplitHistoryVo { + items: SplitResultItem[]; +} + +export interface SplitRunRequest { + files: UploadedFileRef[]; + selectedColumns: string[]; + splitMode: "rows_per_file" | "parts"; + rowsPerFile?: number; + parts?: number; + archiveName?: string; + user_id: number; +} + +export function runSplit( + request: Omit | SplitRunRequest, +) { + return unwrapJavaResponse( + post, SplitRunRequest>( + buildJavaUrl(API_ENDPOINTS.split.run), + { + ...request, + user_id: getCurrentUserId(), + }, + ), + ); +} + +export function getSplitHistory() { + return unwrapJavaResponse( + get>( + buildJavaUrl(API_ENDPOINTS.split.history), + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function deleteSplitHistory(resultId: number) { + return unwrapJavaResponse( + del>( + buildJavaUrl(API_ENDPOINTS.split.historyDelete.replace('{resultId}', String(resultId))), + { + params: { user_id: getCurrentUserId() }, + }, + ), + ); +} + +export function getSplitResultDownloadUrl(resultId: number) { + return getJavaDownloadUrl( + API_ENDPOINTS.split.resultDownload.replace('{resultId}', String(resultId)), + ); +} diff --git a/frontend-vue/tests/modules-split.test.ts b/frontend-vue/tests/modules-split.test.ts new file mode 100644 index 00000000..29acef6b --- /dev/null +++ b/frontend-vue/tests/modules-split.test.ts @@ -0,0 +1,122 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { http } from '../src/shared/api/http.ts' +import { + runSplit, + getSplitHistory, + deleteSplitHistory, + getSplitResultDownloadUrl, + type SplitRunRequest, + type SplitRunVo, +} from '../src/shared/api/types/modules/split.ts' + +function setupWindow() { + ;(globalThis as Record).window = { + localStorage: { getItem: () => '42' }, + location: { origin: 'http://localhost' }, + } +} + +function mockRequest( + t: Parameters[1] extends (t: infer T) => unknown ? T : never, + impl: (config: { url?: string; method?: string; params?: Record; data?: unknown }) => Promise, +) { + t.mock.method(http, 'request', impl as never) +} + +const okResponse = (data: unknown) => Promise.resolve({ data: { success: true, message: 'ok', data } }) + +const splitRequest: Omit = { + files: [{ fileKey: 'k1' }], + selectedColumns: ['a'], + splitMode: 'rows_per_file', + rowsPerFile: 500, + archiveName: '拆分结果', +} + +test('test_split_run_url', async (t) => { + setupWindow() + let captured: { url?: string } = {} + mockRequest(t, (config) => { + captured = config + return okResponse({ total: 1, successCount: 1, failedCount: 0, items: [] }) + }) + await runSplit(splitRequest) + assert.equal(captured.url, '/newApi/api/split/run') +}) + +test('test_split_run_method_payload', async (t) => { + setupWindow() + let captured: { method?: string; data?: unknown } = {} + mockRequest(t, (config) => { + captured = config + return okResponse({ total: 0, successCount: 0, failedCount: 0, items: [] }) + }) + await runSplit(splitRequest) + assert.equal(captured.method, 'POST') + assert.deepEqual(captured.data, { ...splitRequest, user_id: 42 }) +}) + +test('test_split_history', async (t) => { + setupWindow() + let captured: { url?: string; method?: string; params?: Record } = {} + mockRequest(t, (config) => { + captured = config + return okResponse({ items: [] }) + }) + await getSplitHistory() + assert.equal(captured.url, '/newApi/api/split/history') + assert.equal(captured.method, 'GET') + assert.deepEqual(captured.params, { user_id: 42 }) +}) + +test('test_split_delete_history', async (t) => { + setupWindow() + let captured: { url?: string; method?: string } = {} + mockRequest(t, (config) => { + captured = config + return okResponse(null) + }) + await deleteSplitHistory(7) + assert.equal(captured.url, '/newApi/api/split/history/7') + assert.equal(captured.method, 'DELETE') +}) + +test('test_split_download_url', async (t) => { + setupWindow() + const url = getSplitResultDownloadUrl(7) + assert.equal(url, 'http://localhost/newApi/api/split/results/7/download?user_id=42') +}) + +test('test_split_signature_unchanged', async (t) => { + setupWindow() + assert.equal(runSplit.length, 1) + assert.equal(getSplitHistory.length, 0) + assert.equal(deleteSplitHistory.length, 1) + assert.equal(getSplitResultDownloadUrl.length, 1) + const request: SplitRunRequest = { ...splitRequest, user_id: 1 } + assert.equal(request.splitMode, 'rows_per_file') + const entry = { filename: 'a.xlsx', rowCount: 10 } + assert.equal(entry.rowCount, 10) +}) + +test('test_split_export_compat', async (t) => { + setupWindow() + const fromJavaModules = await import('../src/shared/api/java-modules.ts') + const fromSplit = await import('../src/shared/api/types/modules/split.ts') + assert.equal(fromJavaModules.runSplit, fromSplit.runSplit) + assert.equal(fromJavaModules.getSplitHistory, fromSplit.getSplitHistory) + assert.equal(fromJavaModules.deleteSplitHistory, fromSplit.deleteSplitHistory) + assert.equal(fromJavaModules.getSplitResultDownloadUrl, fromSplit.getSplitResultDownloadUrl) +}) + +test('test_split_unwrap_and_error', async (t) => { + setupWindow() + mockRequest(t, () => okResponse({ total: 1, successCount: 0, failedCount: 1, items: [{ sourceFilename: 'a', success: false, error: '解析失败' }] })) + const result = await runSplit(splitRequest) + assert.equal(result.failedCount, 1) + assert.equal(result.items[0].error, '解析失败') + + mockRequest(t, () => Promise.resolve({ data: { success: false, message: '服务不可用' } })) + await assert.rejects(runSplit(splitRequest), /服务不可用/, 'success=false 应回传异常') +})