103 lines
3.4 KiB
TypeScript
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');
|
|
});
|
|
});
|