196 lines
8.0 KiB
TypeScript
196 lines
8.0 KiB
TypeScript
// @ts-expect-error Node strip-types runtime requires explicit .ts extension in ESM imports.
|
|
import { API_TOKEN_RUNTIME_GLOBAL_KEY, downloadDocumentContentMarkdown, downloadDocumentFile, getDocumentPreviewBlob, getDocumentThumbnailBlob, setApiTokenResolver, 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;
|
|
}
|
|
|
|
/**
|
|
* Creates a minimal session storage implementation for Node-based tests.
|
|
*/
|
|
function createMemorySessionStorage(): Storage {
|
|
const values = new Map<string, string>();
|
|
return {
|
|
get length(): number {
|
|
return values.size;
|
|
},
|
|
clear(): void {
|
|
values.clear();
|
|
},
|
|
getItem(key: string): string | null {
|
|
return values.has(key) ? values.get(key) ?? null : null;
|
|
},
|
|
key(index: number): string | null {
|
|
return Array.from(values.keys())[index] ?? null;
|
|
},
|
|
removeItem(key: string): void {
|
|
values.delete(key);
|
|
},
|
|
setItem(key: string, value: string): void {
|
|
values.set(key, String(value));
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Runs API helper tests for authenticated media and download flows.
|
|
*/
|
|
async function runApiTests(): Promise<void> {
|
|
const originalFetch = globalThis.fetch;
|
|
const runtimeGlobalSource = globalThis as typeof globalThis & Record<string, unknown>;
|
|
const originalRuntimeGlobalToken = runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY];
|
|
const sessionStorageDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'sessionStorage');
|
|
|
|
try {
|
|
Object.defineProperty(globalThis, 'sessionStorage', {
|
|
configurable: true,
|
|
writable: true,
|
|
value: createMemorySessionStorage(),
|
|
});
|
|
setApiTokenResolver(null);
|
|
setRuntimeApiToken(null);
|
|
delete runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY];
|
|
|
|
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] === '/api/v1/documents/doc-1/thumbnail',
|
|
`Unexpected thumbnail URL ${requestUrls[0]}`,
|
|
);
|
|
assert(
|
|
requestUrls[1] === '/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');
|
|
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');
|
|
|
|
setRuntimeApiToken('session-user-token');
|
|
runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY] = 'runtime-global-token';
|
|
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
const authHeader = new Headers(init?.headers).get('Authorization');
|
|
assert(authHeader === 'Bearer runtime-global-token', `Expected global runtime token auth header, got "${authHeader}"`);
|
|
return new Response('preview-bytes', { status: 200 });
|
|
}) as typeof fetch;
|
|
await getDocumentPreviewBlob('doc-global-auth');
|
|
|
|
setApiTokenResolver(() => 'resolver-token');
|
|
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 resolver-token', `Expected resolver token auth header, got "${mergedAuthorization}"`);
|
|
|
|
setApiTokenResolver(() => ' ');
|
|
globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
const authHeader = new Headers(init?.headers).get('Authorization');
|
|
assert(authHeader === 'Bearer runtime-global-token', `Expected fallback runtime global token auth header, got "${authHeader}"`);
|
|
return new Response('preview-bytes', { status: 200 });
|
|
}) as typeof fetch;
|
|
await getDocumentPreviewBlob('doc-resolver-fallback');
|
|
|
|
setApiTokenResolver(null);
|
|
setRuntimeApiToken(null);
|
|
delete runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY];
|
|
|
|
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 {
|
|
setApiTokenResolver(null);
|
|
setRuntimeApiToken(null);
|
|
if (originalRuntimeGlobalToken === undefined) {
|
|
delete runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY];
|
|
} else {
|
|
runtimeGlobalSource[API_TOKEN_RUNTIME_GLOBAL_KEY] = originalRuntimeGlobalToken;
|
|
}
|
|
if (sessionStorageDescriptor) {
|
|
Object.defineProperty(globalThis, 'sessionStorage', sessionStorageDescriptor);
|
|
} else {
|
|
delete (globalThis as { sessionStorage?: Storage }).sessionStorage;
|
|
}
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
}
|
|
|
|
await runApiTests();
|