Files
openmaic/OpenMAIC/tests/bundle/client-upload.test.ts
2026-08-16 14:58:47 +08:00

103 lines
3.4 KiB
TypeScript

import { describe, expect, test, vi } from 'vitest';
import { uploadCoursewareZip } from '@/lib/bundle/client-upload';
function jsonResponse(status: number, body: unknown): Response {
return new Response(JSON.stringify(body), {
status,
headers: { 'Content-Type': 'application/json' },
});
}
describe('uploadCoursewareZip', () => {
test('sends multipart fields + bearer token and returns the record', async () => {
const fetchImpl = vi.fn(async (_input: unknown, _init?: RequestInit) => {
return jsonResponse(201, {
success: true,
record: {
coursewareId: 'cw-1',
version: 3,
title: '示例课件',
status: 'published',
publishedAt: '2026-08-15T00:00:00.000Z',
contentHash: 'ab'.repeat(32),
sceneCount: 3,
quizSceneCount: 1,
bundleUrl: 'http://localhost/api/coursewares/cw-1/bundles/3/download',
},
});
});
const result = await uploadCoursewareZip({
zip: new Blob([new Uint8Array([1, 2, 3])], { type: 'application/zip' }),
coursewareId: 'cw-1',
token: 'secret',
fetchImpl,
});
expect(result.ok).toBe(true);
expect(result.record?.version).toBe(3);
expect(fetchImpl).toHaveBeenCalledTimes(1);
const [endpoint, init] = fetchImpl.mock.calls[0] as unknown as [string, RequestInit];
expect(endpoint).toBe('/api/coursewares');
expect((init.headers as Record<string, string>).authorization).toBe('Bearer secret');
const form = init.body as FormData;
expect(form.get('coursewareId')).toBe('cw-1');
expect(form.get('status')).toBeNull(); // default published → omitted
expect((form.get('zip') as File).name).toBe('bundle.zip');
});
test('includes explicit version and status', async () => {
const fetchImpl = vi.fn(async () => jsonResponse(201, { success: true }));
await uploadCoursewareZip({
zip: new Blob(['x']),
coursewareId: 'cw-2',
token: 't',
version: 7,
status: 'unpublished',
endpoint: 'https://registry.example.com/api/coursewares',
fetchImpl,
});
const [endpoint, init] = fetchImpl.mock.calls[0] as unknown as [string, RequestInit];
expect(endpoint).toBe('https://registry.example.com/api/coursewares');
const form = init.body as FormData;
expect(form.get('version')).toBe('7');
expect(form.get('status')).toBe('unpublished');
});
test('surfaces server error body details', async () => {
const fetchImpl = vi.fn(async () =>
jsonResponse(400, {
success: false,
errorCode: 'INVALID_REQUEST',
error: 'Courseware publish failed',
details: 'Bundle content hash mismatch',
}),
);
const result = await uploadCoursewareZip({
zip: new Blob(['x']),
coursewareId: 'cw-1',
token: 't',
fetchImpl,
});
expect(result.ok).toBe(false);
expect(result.status).toBe(400);
expect(result.errorCode).toBe('INVALID_REQUEST');
expect(result.details).toBe('Bundle content hash mismatch');
});
test('reports network failure as status 0', async () => {
const fetchImpl = vi.fn(async () => {
throw new Error('boom');
});
const result = await uploadCoursewareZip({
zip: new Blob(['x']),
coursewareId: 'cw-1',
token: 't',
fetchImpl,
});
expect(result.ok).toBe(false);
expect(result.status).toBe(0);
expect(result.error).toContain('Network error');
});
});