178 lines
6.9 KiB
TypeScript
178 lines
6.9 KiB
TypeScript
// @ts-ignore Node strip-types runtime requires explicit .ts extension in ESM imports.
|
|
import {
|
|
downloadDocumentContentMarkdown,
|
|
downloadDocumentFile,
|
|
getCurrentAuthSession,
|
|
getDocumentPreviewBlob,
|
|
getDocumentThumbnailBlob,
|
|
getRuntimeApiToken,
|
|
loginWithPassword,
|
|
logoutCurrentSession,
|
|
setRuntimeApiToken,
|
|
updateDocumentMetadata,
|
|
} from './api.ts';
|
|
|
|
/**
|
|
* Throws when a test condition is false.
|
|
*/
|
|
function assert(condition: boolean, message: string): void {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies that async functions reject with an expected message fragment.
|
|
*/
|
|
async function assertRejects(action: () => Promise<unknown>, expectedMessage: string): Promise<void> {
|
|
try {
|
|
await action();
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
assert(message.includes(expectedMessage), `Expected error containing "${expectedMessage}" but received "${message}"`);
|
|
return;
|
|
}
|
|
throw new Error(`Expected rejection containing "${expectedMessage}"`);
|
|
}
|
|
|
|
/**
|
|
* Converts fetch inputs into a URL string for assertions.
|
|
*/
|
|
function toRequestUrl(input: RequestInfo | URL): string {
|
|
if (typeof input === 'string') {
|
|
return input;
|
|
}
|
|
if (input instanceof URL) {
|
|
return input.toString();
|
|
}
|
|
return input.url;
|
|
}
|
|
|
|
/**
|
|
* Runs API helper tests for authenticated media and auth session workflows.
|
|
*/
|
|
async function runApiTests(): Promise<void> {
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
try {
|
|
setRuntimeApiToken(null);
|
|
|
|
const requestUrls: string[] = [];
|
|
const requestAuthHeaders: Array<string | null> = [];
|
|
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
requestUrls.push(toRequestUrl(input));
|
|
requestAuthHeaders.push(new Headers(init?.headers).get('Authorization'));
|
|
return new Response('preview-bytes', { status: 200 });
|
|
}) as typeof fetch;
|
|
|
|
const thumbnail = await getDocumentThumbnailBlob('doc-1');
|
|
const preview = await getDocumentPreviewBlob('doc-1');
|
|
|
|
assert(await thumbnail.text() === 'preview-bytes', 'Thumbnail blob bytes mismatch');
|
|
assert(await preview.text() === 'preview-bytes', 'Preview blob bytes mismatch');
|
|
assert(
|
|
requestUrls[0] === 'http://localhost:8000/api/v1/documents/doc-1/thumbnail',
|
|
`Unexpected thumbnail URL ${requestUrls[0]}`,
|
|
);
|
|
assert(
|
|
requestUrls[1] === 'http://localhost:8000/api/v1/documents/doc-1/preview',
|
|
`Unexpected preview URL ${requestUrls[1]}`,
|
|
);
|
|
assert(requestAuthHeaders[0] === null, `Expected no auth header for thumbnail request, got "${requestAuthHeaders[0]}"`);
|
|
assert(requestAuthHeaders[1] === null, `Expected no auth header for preview request, got "${requestAuthHeaders[1]}"`);
|
|
|
|
setRuntimeApiToken('session-user-token');
|
|
assert(getRuntimeApiToken() === 'session-user-token', 'Expected runtime token readback to match active token');
|
|
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
const authHeader = new Headers(init?.headers).get('Authorization');
|
|
assert(authHeader === 'Bearer session-user-token', `Expected session token auth header, got "${authHeader}"`);
|
|
return new Response('preview-bytes', { status: 200 });
|
|
}) as typeof fetch;
|
|
await getDocumentPreviewBlob('doc-session-auth');
|
|
|
|
let mergedContentType: string | null = null;
|
|
let mergedAuthorization: string | null = null;
|
|
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
const headers = new Headers(init?.headers);
|
|
mergedContentType = headers.get('Content-Type');
|
|
mergedAuthorization = headers.get('Authorization');
|
|
return new Response('{}', { status: 200 });
|
|
}) as typeof fetch;
|
|
await updateDocumentMetadata('doc-headers', { original_filename: 'renamed.pdf' });
|
|
assert(mergedContentType === 'application/json', `Expected JSON content type to be preserved, got "${mergedContentType}"`);
|
|
assert(mergedAuthorization === 'Bearer session-user-token', `Expected auth header, got "${mergedAuthorization}"`);
|
|
|
|
globalThis.fetch = (async (): Promise<Response> => {
|
|
return new Response(
|
|
JSON.stringify({
|
|
access_token: 'issued-session-token',
|
|
token_type: 'bearer',
|
|
expires_at: '2026-03-01T10:30:00Z',
|
|
user: {
|
|
id: '3a42f5e0-b1ad-4f68-b2f4-3fa8c2fb31c9',
|
|
username: 'admin',
|
|
role: 'admin',
|
|
},
|
|
}),
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
);
|
|
}) as typeof fetch;
|
|
const loginPayload = await loginWithPassword('admin', 'password');
|
|
assert(loginPayload.access_token === 'issued-session-token', 'Unexpected issued session token in login payload');
|
|
assert(loginPayload.user.username === 'admin', 'Unexpected login user payload');
|
|
|
|
globalThis.fetch = (async (): Promise<Response> => {
|
|
return new Response(
|
|
JSON.stringify({
|
|
expires_at: '2026-03-01T10:30:00Z',
|
|
user: {
|
|
id: '3a42f5e0-b1ad-4f68-b2f4-3fa8c2fb31c9',
|
|
username: 'admin',
|
|
role: 'admin',
|
|
},
|
|
}),
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
);
|
|
}) as typeof fetch;
|
|
const sessionPayload = await getCurrentAuthSession();
|
|
assert(sessionPayload.user.role === 'admin', 'Expected admin role from auth session payload');
|
|
|
|
globalThis.fetch = (async (): Promise<Response> => {
|
|
return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
}) as typeof fetch;
|
|
await logoutCurrentSession();
|
|
|
|
globalThis.fetch = (async (): Promise<Response> => {
|
|
return new Response('file-bytes', {
|
|
status: 200,
|
|
headers: {
|
|
'content-disposition': 'attachment; filename="invoice.pdf"',
|
|
},
|
|
});
|
|
}) as typeof fetch;
|
|
|
|
const fileResult = await downloadDocumentFile('doc-2');
|
|
assert(fileResult.filename === 'invoice.pdf', `Unexpected download filename ${fileResult.filename}`);
|
|
assert((await fileResult.blob.text()) === 'file-bytes', 'Original download bytes mismatch');
|
|
|
|
globalThis.fetch = (async (): Promise<Response> => {
|
|
return new Response('# markdown', { status: 200 });
|
|
}) as typeof fetch;
|
|
|
|
const markdownResult = await downloadDocumentContentMarkdown('doc-3');
|
|
assert(markdownResult.filename === 'document-content.md', `Unexpected markdown filename ${markdownResult.filename}`);
|
|
assert((await markdownResult.blob.text()) === '# markdown', 'Markdown bytes mismatch');
|
|
|
|
globalThis.fetch = (async (): Promise<Response> => {
|
|
return new Response('forbidden', { status: 401 });
|
|
}) as typeof fetch;
|
|
|
|
await assertRejects(async () => downloadDocumentContentMarkdown('doc-4'), 'Failed to download document markdown');
|
|
} finally {
|
|
setRuntimeApiToken(null);
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
}
|
|
|
|
await runApiTests();
|