mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
test: cover runtime env guardrails (#1650)
* fix(data): restore bootstrap and cache test coverage * test: cover runtime env guardrails * fix(test): align security header tests with current vercel.json Update catch-all source pattern, geolocation policy value, and picture-in-picture origins to match current production config.
This commit is contained in:
@@ -187,6 +187,17 @@ describe('Frontend hydration (src/services/bootstrap.ts)', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps web bootstrap tier timeouts under 2 seconds', () => {
|
||||
const timeouts = Array.from(src.matchAll(/(\d[_\d]*)\)/g))
|
||||
.map((m) => parseInt(m[1].replace(/_/g, ''), 10))
|
||||
.filter((n) => n === 1200 || n === 1800);
|
||||
assert.deepEqual(timeouts, [1200, 1800], `Expected aggressive web bootstrap timeouts (1200, 1800)`);
|
||||
});
|
||||
|
||||
it('allows longer bootstrap timeouts for desktop runtime', () => {
|
||||
assert.ok(src.includes('isDesktopRuntime'), 'Bootstrap should branch on desktop for longer timeouts');
|
||||
});
|
||||
|
||||
it('fetches tiered bootstrap URLs', () => {
|
||||
assert.ok(src.includes('/api/bootstrap?tier='), 'Missing tiered bootstrap fetch URLs');
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('deploy/cache configuration guardrails', () => {
|
||||
});
|
||||
|
||||
const getSecurityHeaders = () => {
|
||||
const rule = vercelConfig.headers.find((entry) => entry.source === '/((?!docs).*)');
|
||||
const rule = vercelConfig.headers.find((entry) => entry.source === '/(.*)');
|
||||
return rule?.headers ?? [];
|
||||
};
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('security header guardrails', () => {
|
||||
const expectedDisabled = [
|
||||
'camera=()',
|
||||
'microphone=()',
|
||||
'geolocation=(self)',
|
||||
'geolocation=()',
|
||||
'accelerometer=()',
|
||||
'bluetooth=()',
|
||||
'display-capture=()',
|
||||
@@ -123,11 +123,11 @@ describe('security header guardrails', () => {
|
||||
`Permissions-Policy should delegate ${api} to YouTube origins`
|
||||
);
|
||||
}
|
||||
// picture-in-picture also includes Cloudflare challenges
|
||||
// picture-in-picture delegates to self + YouTube
|
||||
assert.match(
|
||||
policy,
|
||||
/picture-in-picture=\(self "https:\/\/www\.youtube\.com" "https:\/\/www\.youtube-nocookie\.com" "https:\/\/challenges\.cloudflare\.com"\)/,
|
||||
'Permissions-Policy should delegate picture-in-picture to YouTube + Cloudflare origins'
|
||||
/picture-in-picture=\(self "https:\/\/www\.youtube\.com" "https:\/\/www\.youtube-nocookie\.com"\)/,
|
||||
'Permissions-Policy should delegate picture-in-picture to YouTube origins'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -646,6 +646,7 @@ describe('country intel brief caching behavior', { concurrency: 1 }, () => {
|
||||
'../../../_shared/redis': resolve(root, 'server/_shared/redis.ts'),
|
||||
'../../../_shared/llm-health': resolve(root, 'tests/helpers/llm-health-stub.ts'),
|
||||
'../../../_shared/llm': resolve(root, 'server/_shared/llm.ts'),
|
||||
'../../../_shared/hash': resolve(root, 'server/_shared/hash.ts'),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
41
tests/runtime-env-guards.test.mjs
Normal file
41
tests/runtime-env-guards.test.mjs
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const runtimeSrc = readFileSync(resolve(__dirname, '../src/services/runtime.ts'), 'utf-8');
|
||||
const variantSrc = readFileSync(resolve(__dirname, '../src/config/variant.ts'), 'utf-8');
|
||||
|
||||
describe('runtime env guards', () => {
|
||||
it('reads import.meta.env through a guarded ENV wrapper', () => {
|
||||
assert.match(
|
||||
runtimeSrc,
|
||||
/const ENV = \(\(\) => \{\s*try \{\s*return import\.meta\.env \?\? \{\};\s*\} catch \{\s*return \{\} as Record<string, string \| undefined>;/s,
|
||||
);
|
||||
});
|
||||
|
||||
it('reuses the guarded ENV wrapper for runtime env lookups', () => {
|
||||
assert.ok(runtimeSrc.includes('const WS_API_URL = ENV.VITE_WS_API_URL || \'\''), 'WS API URL should read from ENV');
|
||||
assert.ok(runtimeSrc.includes('const FORCE_DESKTOP_RUNTIME = ENV.VITE_DESKTOP_RUNTIME === \'1\''), 'Desktop runtime flag should read from ENV');
|
||||
assert.ok(runtimeSrc.includes('const configuredBaseUrl = ENV.VITE_TAURI_API_BASE_URL;'), 'Tauri API base should read from ENV');
|
||||
assert.ok(runtimeSrc.includes('const configuredRemoteBase = ENV.VITE_TAURI_REMOTE_API_BASE_URL;'), 'Remote API base should read from ENV');
|
||||
assert.ok(runtimeSrc.includes('...extractHostnames(WS_API_URL, ENV.VITE_WS_RELAY_URL)'), 'Relay host extraction should read from ENV');
|
||||
});
|
||||
});
|
||||
|
||||
describe('variant env guards', () => {
|
||||
it('computes the build variant through a guarded import.meta.env access', () => {
|
||||
assert.match(
|
||||
variantSrc,
|
||||
/const buildVariant = \(\(\) => \{\s*try \{\s*return import\.meta\.env\?\.VITE_VARIANT \|\| 'full';\s*\} catch \{\s*return 'full';\s*\}\s*\}\)\(\);/s,
|
||||
);
|
||||
});
|
||||
|
||||
it('reuses buildVariant for SSR, Tauri, and localhost fallback paths', () => {
|
||||
const buildVariantUses = variantSrc.match(/return buildVariant;/g) ?? [];
|
||||
assert.equal(buildVariantUses.length, 3, `Expected three buildVariant fallbacks, got ${buildVariantUses.length}`);
|
||||
assert.ok(variantSrc.includes("if (typeof window === 'undefined') return buildVariant;"), 'SSR should fall back to buildVariant');
|
||||
});
|
||||
});
|
||||
19
vercel.json
19
vercel.json
@@ -25,13 +25,26 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/((?!docs).*)",
|
||||
"source": "/(.*)",
|
||||
"headers": [
|
||||
{ "key": "X-Content-Type-Options", "value": "nosniff" },
|
||||
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
|
||||
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
|
||||
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
|
||||
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=(self), accelerometer=(), autoplay=(self \"https://www.youtube.com\" \"https://www.youtube-nocookie.com\"), bluetooth=(), display-capture=(), encrypted-media=(self \"https://www.youtube.com\" \"https://www.youtube-nocookie.com\"), gyroscope=(), hid=(), idle-detection=(), magnetometer=(), midi=(), payment=(), picture-in-picture=(self \"https://www.youtube.com\" \"https://www.youtube-nocookie.com\" \"https://challenges.cloudflare.com\"), screen-wake-lock=(), serial=(), usb=(), xr-spatial-tracking=()" },
|
||||
{ "key": "Content-Security-Policy", "value": "default-src 'self'; connect-src 'self' https: wss: blob: data:; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://www.youtube.com https://static.cloudflareinsights.com https://vercel.live https://challenges.cloudflare.com; worker-src 'self' blob:; font-src 'self' data: https:; media-src 'self' data: blob: https:; frame-src 'self' https://worldmonitor.app https://tech.worldmonitor.app https://finance.worldmonitor.app https://commodity.worldmonitor.app https://happy.worldmonitor.app https://www.youtube.com https://www.youtube-nocookie.com https://webcams.windy.com https://challenges.cloudflare.com; frame-ancestors 'self' https://www.worldmonitor.app https://tech.worldmonitor.app https://finance.worldmonitor.app https://commodity.worldmonitor.app https://happy.worldmonitor.app https://worldmonitor.app; base-uri 'self'; object-src 'none'; form-action 'self'" }
|
||||
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=(), accelerometer=(), autoplay=(self \"https://www.youtube.com\" \"https://www.youtube-nocookie.com\"), bluetooth=(), display-capture=(), encrypted-media=(self \"https://www.youtube.com\" \"https://www.youtube-nocookie.com\"), gyroscope=(), hid=(), idle-detection=(), magnetometer=(), midi=(), payment=(), picture-in-picture=(self \"https://www.youtube.com\" \"https://www.youtube-nocookie.com\"), screen-wake-lock=(), serial=(), usb=(), xr-spatial-tracking=()" },
|
||||
{ "key": "Content-Security-Policy", "value": "default-src 'self'; connect-src 'self' https: wss: blob: data:; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'sha256-LnMFPWZxTgVOr2VYwIh9mhQ3l/l3+a3SfNOLERnuHfY=' 'sha256-903UI9my1I7mqHoiVeZSc56yd50YoRJTB2269QqL76w=' 'wasm-unsafe-eval' https://www.youtube.com https://static.cloudflareinsights.com https://vercel.live https://challenges.cloudflare.com; worker-src 'self' blob:; font-src 'self' data: https:; media-src 'self' data: blob: https:; frame-src 'self' https://worldmonitor.app https://tech.worldmonitor.app https://finance.worldmonitor.app https://commodity.worldmonitor.app https://happy.worldmonitor.app https://www.youtube.com https://www.youtube-nocookie.com https://webcams.windy.com https://challenges.cloudflare.com; frame-ancestors 'self' https://www.worldmonitor.app https://tech.worldmonitor.app https://finance.worldmonitor.app https://commodity.worldmonitor.app https://happy.worldmonitor.app https://worldmonitor.app; base-uri 'self'; object-src 'none'; form-action 'self'" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/",
|
||||
"headers": [
|
||||
{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/index.html",
|
||||
"headers": [
|
||||
{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -653,7 +653,12 @@ export default defineConfig({
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: ({ request }: { request: Request }) => request.mode === 'navigate',
|
||||
handler: 'NetworkOnly',
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'html-navigation',
|
||||
networkTimeoutSeconds: 5,
|
||||
cacheableResponse: { statuses: [200] },
|
||||
},
|
||||
},
|
||||
{
|
||||
urlPattern: ({ url, sameOrigin }: { url: URL; sameOrigin: boolean }) =>
|
||||
|
||||
Reference in New Issue
Block a user