diff --git a/docs/quality/evidence/qa-browser-acceptance-runtime.json b/docs/quality/evidence/qa-browser-acceptance-runtime.json index 4529490..162c387 100644 --- a/docs/quality/evidence/qa-browser-acceptance-runtime.json +++ b/docs/quality/evidence/qa-browser-acceptance-runtime.json @@ -27,9 +27,13 @@ ], "dailyBulletinHeading": true, "analyticsTargetLabel": true, + "accountScopedAnalyticsTarget": true, + "primaryAnalyticsTargetRestored": true, "icsImported": true, "icsStorageScoped": true, "icsPersistsAfterTabChange": true, + "accountScopedIcs": true, + "primaryIcsRestored": true, "readyPreparationStatus": 200, "readyOutputButtons": 4, "readyOutputButtonsEnabled": true, diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index e80c0e9..1ba75dc 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -2272,3 +2272,9 @@ Doğrulama: yeni regresyonla otomatik test sayısı 295/295’e yükseldi; üret İlan metni ayrıştırıcısı artık EKAP ve kurum ilanlarında kullanılan “İhale tarihi ve saati”, “Son Teklif Tarihi” ve “İhale Tarihi/Saati” başlıklarını aynı tarih-saat alanına dönüştürüyor. Ayrıştırma yalnız tarih ve saat birlikte bulunduğunda çalışıyor; teslim süresi gibi yalnız tarih içeren maddeler son teklif tarihi olarak yanlış eşleştirilmiyor. Doğrulama: üç yaygın başlık varyantı için regresyonlar eklendi; 296 otomatik test ve üretim derlemesi başarılı. Resmî beş ihale dosyasıyla uçtan uca doğrulama ve canlı deployment hâlâ açık. + +### 2026-09-06 · D01 iki hesaplı tarayıcı kabulü + +Yerel browser QA fikstürüne ikinci geçerli test hesabı eklendi. Birinci hesaba ait analitik ciro hedefi ve ICS etkinliği ikinci hesapta görünmüyor; hesap birinciye döndüğünde her iki kayıt da geri yükleniyor. Böylece istemci tarafındaki hesap kapsamlı anahtarların yalnız statik kod ve birim testte değil, gerçek oturum değişimi ve yeniden yükleme akışında da çalıştığı doğrulanıyor. + +Doğrulama: `npm run qa:browser` başarılı; `accountScopedAnalyticsTarget`, `primaryAnalyticsTargetRestored`, `accountScopedIcs` ve `primaryIcsRestored` kanıtları [`qa-browser-acceptance-runtime.json`](evidence/qa-browser-acceptance-runtime.json) içinde. Kapsam sentetik yerel QA’dır; gerçek iki firma hesabı, production verisi ve offline kabulü hâlâ açık. diff --git a/tests/browser-acceptance.mjs b/tests/browser-acceptance.mjs index 43283a9..f988b4e 100644 --- a/tests/browser-acceptance.mjs +++ b/tests/browser-acceptance.mjs @@ -39,6 +39,24 @@ try { await page.getByLabel('Ciro hedefi (TL)', { exact: true }).waitFor({ state: 'attached', timeout: 10_000 }); evidence.checks.analyticsTargetLabel = await page.getByLabel('Ciro hedefi (TL)', { exact: true }).count() === 1; if (!evidence.checks.analyticsTargetLabel) throw new Error('Analitik ciro hedefi alanı erişilebilir ad taşımıyor.'); + const primaryTarget = page.getByLabel('Ciro hedefi (TL)', { exact: true }); + await primaryTarget.fill('1250'); + await page.waitForFunction(() => localStorage.getItem('ihaleist_ciro_hedefi:browser-qa%40example.test') === '1250'); + await page.goto(`${url.replace(/\/qa-signin$/, '')}/qa-switch-account?account=other`, { waitUntil: 'networkidle' }); + await page.getByRole('button', { name: 'Pazar', exact: true }).click(); + await page.getByRole('button', { name: 'Analitik Dashboard', exact: true }).click(); + await page.getByLabel('Ciro hedefi (TL)', { exact: true }).waitFor({ state: 'attached', timeout: 10_000 }); + evidence.checks.accountScopedAnalyticsTarget = await page.getByLabel('Ciro hedefi (TL)', { exact: true }).inputValue() === ''; + if (!evidence.checks.accountScopedAnalyticsTarget) throw new Error('İkinci hesap ilk hesabın ciro hedefini devraldı.'); + await page.getByLabel('Ciro hedefi (TL)', { exact: true }).fill('980'); + await page.waitForFunction(() => localStorage.getItem('ihaleist_ciro_hedefi:browser-qa-other%40example.test') === '980'); + await page.goto(`${url.replace(/\/qa-signin$/, '')}/qa-switch-account?account=primary`, { waitUntil: 'networkidle' }); + await page.getByRole('button', { name: 'Pazar', exact: true }).click(); + await page.getByRole('button', { name: 'Analitik Dashboard', exact: true }).click(); + await page.getByRole('button', { name: 'Ciro hedefini düzenle', exact: true }).click(); + await page.getByLabel('Ciro hedefi (TL)', { exact: true }).waitFor({ state: 'attached', timeout: 10_000 }); + evidence.checks.primaryAnalyticsTargetRestored = await page.getByLabel('Ciro hedefi (TL)', { exact: true }).inputValue() === '1250'; + if (!evidence.checks.primaryAnalyticsTargetRestored) throw new Error('İlk hesabın ciro hedefi hesap değişiminden sonra geri yüklenmedi.'); await page.getByRole('button', { name: 'Keşfet', exact: true }).click(); await page.getByRole('button', { name: 'İhale Takvimi' }).click(); await page.waitForLoadState('networkidle'); @@ -55,6 +73,20 @@ try { await page.getByRole('button', { name: 'İhale Takvimi' }).click(); evidence.checks.icsPersistsAfterTabChange = await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).count() === 1; if (!evidence.checks.icsPersistsAfterTabChange) throw new Error('ICS kaydı sekme değişiminden sonra korunmadı.'); + await page.goto(`${url.replace(/\/qa-signin$/, '')}/qa-switch-account?account=other`, { waitUntil: 'networkidle' }); + await page.getByRole('button', { name: 'Keşfet' }).click(); + await page.getByRole('button', { name: 'İhale Takvimi' }).click(); + evidence.checks.accountScopedIcs = await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).count() === 0; + if (!evidence.checks.accountScopedIcs) throw new Error('İkinci hesap ilk hesabın ICS etkinliğini devraldı.'); + await page.goto(`${url.replace(/\/qa-signin$/, '')}/qa-switch-account?account=primary`, { waitUntil: 'networkidle' }); + await page.getByRole('button', { name: 'Keşfet' }).click(); + await page.getByRole('button', { name: 'İhale Takvimi' }).click(); + await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).waitFor({ state: 'attached', timeout: 10_000 }); + evidence.checks.primaryIcsRestored = await page.getByText('1 harici etkinlik içe aktarıldı.', { exact: true }).count() === 1; + if (!evidence.checks.primaryIcsRestored) { + const storage = await page.evaluate(() => Object.fromEntries(Object.keys(sessionStorage).map(key => [key, sessionStorage.getItem(key)]))); + throw new Error(`İlk hesabın ICS etkinliği hesap değişiminden sonra geri yüklenmedi: ${JSON.stringify(storage)}`); + } } finally { await rm(icsDir, { recursive: true, force: true }); } const readyPage = await context.newPage(); await readyPage.setViewportSize({ width: 1440, height: 1000 }); diff --git a/tests/browser-qa.mjs b/tests/browser-qa.mjs index 03a5451..c46c653 100644 --- a/tests/browser-qa.mjs +++ b/tests/browser-qa.mjs @@ -15,6 +15,8 @@ process.env.AUTH_SECRET=randomBytes(32).toString('hex'); const {signToken}=await import('../server/auth.mjs'); const user={email:'browser-qa@example.test',name:'Laboratuvar QA',isAdmin:false}; const token=signToken(user); +const otherUser={email:'browser-qa-other@example.test',name:'İkinci Laboratuvar QA',isAdmin:false}; +const otherToken=signToken(otherUser); const keyUser={email:'keys-browser-qa@example.test',name:'API QA',isAdmin:true}; const keyToken=signToken(keyUser); let apiKeyListFailure=false; @@ -170,6 +172,14 @@ const ui=await createServer({server:{host:'127.0.0.1',port:0,proxy:{'/api':{targ if(req.url?.startsWith('/qa-brief-failure?')&&req.method==='POST'){briefFailure=new URL(req.url,'http://localhost').searchParams.get('mode');res.end('Özet test durumu: '+briefFailure);return;} if(req.url?.startsWith('/api/me/brief')&&((briefFailure==='read'&&req.method==='GET')||(briefFailure==='write'&&req.method==='POST'))){res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Haftalık özet deposuna erişilemiyor.'}));return;} if(req.url==='/qa-api-controls') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`
Yalnız geçici test deposunu etkiler.
`);return;} + if(req.url?.startsWith('/qa-switch-account?')) { + const account=new URL(req.url,'http://localhost').searchParams.get('account'); + const selected=account==='other'?otherUser:user; + const selectedToken=account==='other'?otherToken:token; + res.setHeader('Content-Type','text/html; charset=utf-8'); + res.end(`QA hesap değişimi
`); + return; + } if(req.method==='POST' && req.url?.startsWith('/qa-api-failure?')) {apiKeyListFailure=new URL(req.url,'http://localhost').searchParams.get('mode')==='on';res.end('ok');return;} if(apiKeyListFailure && req.method==='GET' && req.url==='/api/me/apikeys') {res.writeHead(503,{'Content-Type':'application/json'});res.end(JSON.stringify({success:false,error:'QA: Anahtar deposuna erişilemiyor.'}));return;} if(req.url==='/qa-signin') {res.setHeader('Content-Type','text/html; charset=utf-8');res.end(`Sentetik ihale verileri ve geçici kullanıcı deposu kullanılır.
`);return;}