From cc3c3ad73d9032ef781aaf114fe41a4cb0584b03 Mon Sep 17 00:00:00 2001 From: ht Date: Sun, 6 Sep 2026 19:23:59 +0300 Subject: [PATCH] validate live API response contracts --- docs/quality/evidence/qa-live-runtime.json | 18 +++++++++++++++--- docs/quality/uygulama-durumu.md | 6 ++++++ tests/live-runtime-probe.mjs | 19 +++++++++++++------ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/docs/quality/evidence/qa-live-runtime.json b/docs/quality/evidence/qa-live-runtime.json index 7713cb4..971ba9a 100644 --- a/docs/quality/evidence/qa-live-runtime.json +++ b/docs/quality/evidence/qa-live-runtime.json @@ -1,10 +1,11 @@ { "base": "https://ihaleist.com", "readOnly": true, - "checkedAt": "2026-09-06T16:13:35.286Z", + "checkedAt": "2026-09-06T16:23:52.207Z", "checks": { "home": { "status": 200, + "contentType": "text/html", "headers": { "x-content-type-options": null, "referrer-policy": null, @@ -13,19 +14,30 @@ }, "health": { "status": 200, + "contentType": "application/json; charset=utf-8", "headers": { "x-content-type-options": null, "referrer-policy": null, "permissions-policy": null - } + }, + "json": { + "ok": true + }, + "apiSuccess": true }, "ready": { "status": 404, + "contentType": "application/json; charset=utf-8", "headers": { "x-content-type-options": null, "referrer-policy": null, "permissions-policy": null - } + }, + "json": { + "success": false, + "error": "not found" + }, + "apiSuccess": false } }, "pass": false, diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index 68d8ea9..4ea351e 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -2254,3 +2254,9 @@ Doğrulama: 294 otomatik test, üretim derlemesi ve `git diff --check` başarıl Browser QA Playwright context’i `America/New_York` saat diliminde çalışacak şekilde ayarlandı; buna rağmen İstanbul tarih/saat akışları, günlük bülten, ICS, çıktı ve mobil viewport kontrolleri başarılı. Böylece önceki zaman standardizasyonu yalnızca Türkiye yerel cihazında değil, farklı cihaz saat diliminde de sınanıyor. Doğrulama: browserTimezone `America/New_York`, mobil viewport 390 px ve yatay taşma yok; tüm kabul kontrolleri başarılı. + +### 2026-09-06 · Canlı probe JSON sözleşmesi + +Canlı drift probe’u artık API yanıt gövdesini de doğruluyor: `/api/health` için `ok:true`, `/api/ready` için `success:true` ve JSON içerik türü aranıyor. Böylece HTTP 200 dönen SPA/ara hata sayfaları sağlıklı servis kabul edilmiyor. + +Güncel canlı sonuçta health `ok:true` olsa da readiness `404` ve güvenlik başlıkları eksik olduğu için probe başarısız kaldı; kanıt `qa-live-runtime.json` içinde. diff --git a/tests/live-runtime-probe.mjs b/tests/live-runtime-probe.mjs index c5bad05..2fd52b7 100644 --- a/tests/live-runtime-probe.mjs +++ b/tests/live-runtime-probe.mjs @@ -8,15 +8,22 @@ const timeoutMs = 15_000; const requiredHeaders = ['x-content-type-options', 'referrer-policy', 'permissions-policy']; const checks = {}; -async function probe(path) { +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' }); - return { + const result = { status: response.status, + contentType: response.headers.get('content-type'), headers: Object.fromEntries(requiredHeaders.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 { @@ -25,15 +32,15 @@ async function probe(path) { } checks.home = await probe('/'); -checks.health = await probe('/api/health'); -checks.ready = await probe('/api/ready'); -const missingHeaders = requiredHeaders.filter(name => checks.home?.headers?.[name] == null || checks.health?.headers?.[name] == null); +checks.health = await probe('/api/health', 'ok'); +checks.ready = await probe('/api/ready', 'success'); +const missingHeaders = requiredHeaders.filter(name => [checks.home, checks.health, checks.ready].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.ready?.status === 200 && missingHeaders.length === 0, + pass: checks.home?.status === 200 && checks.health?.status === 200 && checks.health.apiSuccess === true && checks.ready?.status === 200 && checks.ready.apiSuccess === true && missingHeaders.length === 0, missingHeaders, };