Files
thu-maic--openmaic/tests/document/extract-document-route.test.ts
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

199 lines
5.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { NextRequest } from 'next/server';
const mocks = vi.hoisted(() => ({
isServerConfiguredProvider: vi.fn(() => false),
resolvePDFApiKey: vi.fn((_providerId: string, clientKey?: string) => clientKey || ''),
resolvePDFBaseUrl: vi.fn((_providerId: string, clientBaseUrl?: string) => clientBaseUrl),
parseWithMinerUCloud: vi.fn(),
}));
vi.mock('@/lib/logger', () => ({
createLogger: () => ({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
}),
}));
vi.mock('@/lib/server/provider-config', () => ({
isServerConfiguredProvider: mocks.isServerConfiguredProvider,
resolvePDFApiKey: mocks.resolvePDFApiKey,
resolvePDFBaseUrl: mocks.resolvePDFBaseUrl,
}));
vi.mock('@/lib/pdf/mineru-cloud', () => ({
parseWithMinerUCloud: mocks.parseWithMinerUCloud,
}));
async function postExtractDocument(input: {
file: File;
providerId?: string;
apiKey?: string;
baseUrl?: string;
}) {
const { POST } = await import('@/app/api/extract-document/route');
const formData = new FormData();
formData.append('file', input.file);
if (input.providerId) formData.append('providerId', input.providerId);
if (input.apiKey) formData.append('apiKey', input.apiKey);
if (input.baseUrl) formData.append('baseUrl', input.baseUrl);
const request = new Request('http://localhost/api/extract-document', {
method: 'POST',
body: formData,
});
return POST(request as unknown as NextRequest);
}
describe('POST /api/extract-document', () => {
beforeEach(() => {
vi.resetModules();
vi.unstubAllEnvs();
mocks.isServerConfiguredProvider.mockReturnValue(false);
mocks.resolvePDFApiKey.mockImplementation(
(_providerId: string, clientKey?: string) => clientKey || '',
);
mocks.resolvePDFBaseUrl.mockImplementation(
(_providerId: string, clientBaseUrl?: string) => clientBaseUrl,
);
mocks.parseWithMinerUCloud.mockReset();
mocks.parseWithMinerUCloud.mockResolvedValue({
text: 'cloud parsed text',
images: [],
metadata: {
pageCount: 1,
parser: 'mineru-cloud',
},
});
delete process.env.PDF_MINERU_BASE_URL;
delete process.env.PDF_MINERU_API_KEY;
});
it('returns 400 for unsupported course material MIME types', async () => {
const res = await postExtractDocument({
file: new File(['x,y'], 'sheet.csv', { type: 'text/csv' }),
});
const json = await res.json();
expect(res.status).toBe(400);
expect(json).toMatchObject({
success: false,
errorCode: 'INVALID_REQUEST',
});
});
it('returns 400 for an unknown requested provider', async () => {
const res = await postExtractDocument({
file: new File(['hello'], 'notes.txt', { type: 'text/plain' }),
providerId: 'missing-provider',
});
const json = await res.json();
expect(res.status).toBe(400);
expect(json).toMatchObject({
success: false,
errorCode: 'INVALID_REQUEST',
error: 'Unknown document extractor provider: missing-provider',
});
});
it('treats an incompatible preferred provider as a hint and falls back by MIME type', async () => {
const res = await postExtractDocument({
file: new File(['hello'], 'notes.txt', { type: 'text/plain' }),
providerId: 'unpdf',
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json).toMatchObject({
success: true,
data: {
text: 'hello',
metadata: {
mimeType: 'text/plain',
parser: 'plain-text',
},
},
});
});
it('returns actionable 422 diagnostics when DOCX requires unconfigured MinerU', async () => {
const res = await postExtractDocument({
file: new File(['not really docx'], 'lesson.docx', {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
}),
});
const json = await res.json();
expect(res.status).toBe(422);
expect(json).toMatchObject({
success: false,
errorCode: 'INVALID_REQUEST',
});
expect(json.error).toContain('DOCX extraction requires a configured MinerU document extractor');
expect(json.error).toContain('self-hosted MinerU base URL or a MinerU Cloud API key');
});
it('allows MinerU Cloud PDF extraction with an API key and no base URL', async () => {
const res = await postExtractDocument({
file: new File(['%PDF-1.4'], 'lesson.pdf', { type: 'application/pdf' }),
providerId: 'mineru-cloud',
apiKey: 'cloud-key',
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json).toMatchObject({
success: true,
data: {
text: 'cloud parsed text',
metadata: {
parser: 'mineru-cloud',
},
},
});
expect(mocks.parseWithMinerUCloud).toHaveBeenCalledWith(
expect.objectContaining({
providerId: 'mineru-cloud',
apiKey: 'cloud-key',
baseUrl: undefined,
}),
expect.any(Buffer),
'lesson.pdf',
);
});
it('falls back to MinerU Cloud for DOCX when self-hosted MinerU is unavailable and a cloud key is provided', async () => {
const res = await postExtractDocument({
file: new File(['not really docx'], 'lesson.docx', {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
}),
apiKey: 'cloud-key',
});
const json = await res.json();
expect(res.status).toBe(200);
expect(json).toMatchObject({
success: true,
data: {
text: 'cloud parsed text',
metadata: {
parser: 'mineru-cloud',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
},
},
});
expect(mocks.parseWithMinerUCloud).toHaveBeenCalledWith(
expect.objectContaining({
providerId: 'mineru-cloud',
apiKey: 'cloud-key',
baseUrl: undefined,
}),
expect.any(Buffer),
'lesson.docx',
);
});
});