ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 3m17s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 3m12s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m42s
ci / ts / ts (packages + web) (push) Failing after 4m37s
ci / module boundaries / boundaries (push) Successful in 5s
ci / desktop / rust (desktop proxy) (push) Successful in 6m12s
69 lines
3.7 KiB
TypeScript
69 lines
3.7 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { createHash } from 'node:crypto';
|
|
import { expect, test } from '@playwright/test';
|
|
|
|
const nginxHeaders = readFileSync(new URL('../../../deploy/nginx/security-headers.conf', import.meta.url), 'utf8');
|
|
const desktopConfig = JSON.parse(readFileSync(new URL('../../desktop/src-tauri/tauri.conf.json', import.meta.url), 'utf8'));
|
|
const policies: Record<string, string> = {
|
|
web: nginxHeaders.match(/add_header Content-Security-Policy "([^"]+)" always;/)![1],
|
|
desktop: desktopConfig.app.security.csp,
|
|
};
|
|
|
|
for (const [platform, policy] of Object.entries(policies)) {
|
|
test(`upload hash worker runs under ${platform} CSP`, async ({ page }) => {
|
|
// Apply the deployed policy to both the document and worker responses.
|
|
await page.route('**/*', async (route) => {
|
|
if (new URL(route.request().url()).pathname === '/csp-upload-test') {
|
|
await route.fulfill({ contentType: 'text/html', body: '<!doctype html><title>Upload CSP</title>', headers: { 'Content-Security-Policy': policy } });
|
|
return;
|
|
}
|
|
if (new URL(route.request().url()).pathname === '/csp-eval-test.js') {
|
|
await route.fulfill({ contentType: 'application/javascript', body: `
|
|
try { new Function('return 1')(); document.documentElement.dataset.evalBlocked = 'false'; }
|
|
catch { document.documentElement.dataset.evalBlocked = 'true'; }
|
|
WebAssembly.compile(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])).then(
|
|
() => { document.documentElement.dataset.wasmBlocked = 'false'; },
|
|
() => { document.documentElement.dataset.wasmBlocked = 'true'; },
|
|
);
|
|
` });
|
|
return;
|
|
}
|
|
const response = await route.fetch();
|
|
await route.fulfill({ response, headers: { ...response.headers(), 'Content-Security-Policy': policy } });
|
|
});
|
|
await page.goto('/csp-upload-test');
|
|
const result = await page.evaluate(async () => {
|
|
const worker = new Worker('/src/features/uploads/hash.worker.ts', { type: 'module' });
|
|
try {
|
|
const results = [];
|
|
// Reuse one worker for empty, known-vector and multi-chunk binary files.
|
|
const binary = Uint8Array.from({ length: (9 << 20) + 17 }, (_, i) => i % 251);
|
|
for (const [id, bytes] of [['empty', new Uint8Array()], ['abc', new TextEncoder().encode('abc')], ['binary', binary]] as const) {
|
|
results.push(await new Promise((resolve, reject) => {
|
|
const progress: number[] = [];
|
|
worker.onerror = (event) => reject(new Error(event.message));
|
|
worker.onmessage = ({ data }) => {
|
|
if (data.type === 'progress') { progress.push(data.done); return; }
|
|
resolve({ ...data, progress });
|
|
};
|
|
worker.postMessage({ id, file: new File([bytes], `${id}.flac`) });
|
|
}));
|
|
}
|
|
return results;
|
|
} finally {
|
|
worker.terminate();
|
|
}
|
|
});
|
|
const binary = Uint8Array.from({ length: (9 << 20) + 17 }, (_, i) => i % 251);
|
|
expect(result).toEqual([
|
|
{ id: 'empty', type: 'done', sha256: createHash('sha256').update('').digest('hex'), progress: [] },
|
|
{ id: 'abc', type: 'done', sha256: createHash('sha256').update('abc').digest('hex'), progress: [] },
|
|
{ id: 'binary', type: 'done', sha256: createHash('sha256').update(binary).digest('hex'), progress: expect.arrayContaining([expect.any(Number)]) },
|
|
]);
|
|
// A page script observes CSP; Playwright's evaluate bypasses the eval restriction.
|
|
await page.addScriptTag({ url: '/csp-eval-test.js' });
|
|
await expect(page.locator('html')).toHaveAttribute('data-eval-blocked', 'true');
|
|
await expect(page.locator('html')).toHaveAttribute('data-wasm-blocked', 'true');
|
|
});
|
|
}
|