63 lines
3.0 KiB
JavaScript
63 lines
3.0 KiB
JavaScript
// Read-only production drift probe. No authentication, writes, payments or
|
||
// provider calls are performed. It intentionally fails when a required
|
||
// deployment contract is missing.
|
||
import { mkdir, writeFile } from 'node:fs/promises';
|
||
|
||
const base = String(process.argv[2] || process.env.IHALEIST_LIVE_URL || 'https://ihaleist.com').replace(/\/$/, '');
|
||
const evidencePath = process.env.IHALEIST_LIVE_EVIDENCE_PATH || 'docs/quality/evidence/qa-live-runtime.json';
|
||
const timeoutMs = 15_000;
|
||
const requiredHeaders = [
|
||
'x-content-type-options', 'referrer-policy', 'permissions-policy',
|
||
'x-frame-options', 'content-security-policy',
|
||
...(base.startsWith('https://') ? ['strict-transport-security'] : []),
|
||
];
|
||
const observedHeaders = [...new Set([...requiredHeaders, 'strict-transport-security'])];
|
||
const checks = {};
|
||
|
||
async function probe(path, successField = null) {
|
||
const controller = new AbortController();
|
||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||
try {
|
||
const response = await fetch(base + path, { signal: controller.signal, redirect: 'manual' });
|
||
const result = {
|
||
status: response.status,
|
||
contentType: response.headers.get('content-type'),
|
||
headers: Object.fromEntries(observedHeaders.map(name => [name, response.headers.get(name)])),
|
||
};
|
||
if (successField) {
|
||
const raw = await response.text();
|
||
try { result.json = JSON.parse(raw); } catch { result.jsonError = 'JSON yanıtı ayrıştırılamadı.'; }
|
||
result.apiSuccess = result.json?.[successField] === true;
|
||
}
|
||
return result;
|
||
} catch (error) {
|
||
return { error: error.name === 'AbortError' ? `timeout after ${timeoutMs}ms` : error.message };
|
||
} finally {
|
||
clearTimeout(timer);
|
||
}
|
||
}
|
||
|
||
checks.home = await probe('/');
|
||
checks.health = await probe('/api/health', 'ok');
|
||
// /api/health and /api/ready use the same { ok: true } probe contract. The
|
||
// readiness payload also includes a separate `ready` diagnostic field.
|
||
checks.ready = await probe('/api/ready', 'ok');
|
||
// The EKAP document fallback depends on the bundled browser being available;
|
||
// check its public capability endpoint without opening a document or mutating
|
||
// any account data.
|
||
checks.documents = await probe('/api/documents/config', 'tarayiciVar');
|
||
const missingHeaders = requiredHeaders.filter(name => [checks.home, checks.health, checks.ready, checks.documents].some(check => check?.headers?.[name] == null));
|
||
const report = {
|
||
base,
|
||
readOnly: true,
|
||
checkedAt: new Date().toISOString(),
|
||
checks,
|
||
pass: checks.home?.status === 200 && checks.health?.status === 200 && checks.health.apiSuccess === true && checks.ready?.status === 200 && checks.ready.apiSuccess === true && checks.documents?.status === 200 && checks.documents.apiSuccess === true && missingHeaders.length === 0,
|
||
missingHeaders,
|
||
};
|
||
|
||
await mkdir('docs/quality/evidence', { recursive: true });
|
||
await writeFile(evidencePath, JSON.stringify(report, null, 2) + '\n');
|
||
console.log(JSON.stringify(report));
|
||
if (!report.pass) process.exitCode = 1;
|