From 120657758c7eb42b3b9f08069b235aaf57472900 Mon Sep 17 00:00:00 2001 From: ht Date: Sun, 6 Sep 2026 14:55:05 +0300 Subject: [PATCH] fix: reject unconfirmed Telegram tests and invalid update responses --- docs/quality/uygulama-durumu.md | 7 +++++++ server/index.js | 6 ++++-- server/telegram.mjs | 2 +- src/components/tools/TelegramBaglanti.jsx | 2 +- src/services/account.js | 6 +++++- tests/account-request.test.mjs | 13 +++++++++++++ tests/http-state.test.mjs | 15 ++++++++++++++- tests/telegram-link.test.mjs | 12 ++++++++++++ 8 files changed, 57 insertions(+), 6 deletions(-) diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index e485222..e01a768 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -1131,3 +1131,10 @@ Taslak türü seçicisine erişilebilir ad eklendi. Üretim derlemesi geçti. Ö - Tarayıcı testinde bağlantı kaldırıldıktan sonra tüketilmiş kodun ekranda kaldığı ve Doğrula düğmesinin açık olduğu bulundu. Bağlama/kaldırma tamamlandığında kod artık temizleniyor. Kod yenileme başlarken eski kod kaldırılıyor. Sunucunun geçersiz/süresi dolmuş kod hatası ayrı `TELEGRAM_CHALLENGE_INVALID` koduyla taşınıyor; istemci bu durumda eski kodu temizliyor. - Dokuz tarayıcı kontrolü geçti: koddan önce doğrulama kapalı, sunucu kodu görünür, süre açıklaması, farklı yeni kod, önceki mesajın reddi, başarılı sentetik bağlama, kaldırmada eski kodun temizlenmesi, doğrulamanın kapanması ve kaldırma onayı. Kanıt `rapor/qa-telegram-link-flow.json`. Test sekmeleri ve bu çalışmanın geçici sunucusu kapatıldı. - 266 test ve build başarılı; son hata-kodu doğrulaması için üç Telegram birim testi ayrıca geçti. Süre dolması sunucu testinde doğrulandı; süre hatası sonrası kod temizleme kolu bu tarayıcı koşusunda ayrıca tetiklenmedi. Gerçek sağlayıcıda bağlama/gönderim yapılmadı. Genel planın diğer kabul maddeleri açık. + +### 2026-09-06 — Telegram sağlayıcı hatası ile başarılı test ayrımı + +- Kayıtlı sohbet varken sunucu bot tokenı kaldırılmışsa eski test endpointi sendTelegram'ın false dönüşünü yok sayıp success:true veriyordu. Artık yapılandırılmamış hizmet 503, kimliği olmayan kabul hata döndürür. İstek gerçek sohbet/mesaj kabul kimliğini ister; istemci de dolu kimlik olmadan başarı göstermez ve otomatik tekrar yapmaz. UI 'sağlayıcı kabul etti, sohbetinizde kontrol edin' der. +- getUpdates doğrulaması HTTP başarısı, boolean ok:true ve gerçek sonuç dizisi gerektiriyor. Eksik/yanlış tipte sonuçlar artık boş liste gibi 'kod bulunamadı' sayılmıyor; sağlayıcı yanıt hatası olarak bildiriliyor. +- 269 test ve build başarılı. Yeni testler kaydedilmiş bağlantı + yapılandırılmamış hizmetin HTTP 503 vermesi, kimliksiz test kabulünün istemcide reddi/tekrarlanmaması ve bozuk güncelleme yanıtlarını kapsıyor. HTTP test sunucusunda Telegram tokenı açıkça boş bırakılıyor; yalnız geçici veri deposu kullanılıyor. Gerçek Telegram mesajı gönderilmedi. +- Test gönderimi yanıtında id alanı zorunlu hale geldi; ön yüz/sunucu birlikte güncellenmeli. Test mesajı düğmesi için kalıcı istek tekrar engeli ve gerçek sağlayıcı uçtan uca kabulü henüz doğrulanmadı. Genel plan açık. diff --git a/server/index.js b/server/index.js index 69746d9..6e1a216 100644 --- a/server/index.js +++ b/server/index.js @@ -911,8 +911,10 @@ const server = http.createServer(async (req, res) => { const body = await readBody(req); if (body.islem === 'test') { if (!tg?.chatId) return json(res, 400, { success: false, error: 'Telegram bağlı değil' }); - await sendTelegram(tg.chatId, '✅ İhaleist bağlantı testi — bildirimler bu sohbete gelecek.'); - return json(res, 200, { success: true }); + if(!telegramConfigured())return json(res,503,{success:false,error:'Telegram hizmeti etkin değil.'}); + const receipt=await sendTelegram(tg.chatId,'✅ İhaleist bağlantı testi.',{parseMode:null,returnReceipt:true}); + if(!receipt?.id)throw new Error('Telegram gönderim kabulü doğrulanamadı.'); + return json(res,200,{success:true,id:receipt.id}); } const linker=createTelegramLinker(store); if(body.islem==='kod'){ diff --git a/server/telegram.mjs b/server/telegram.mjs index 526bd77..2443224 100644 --- a/server/telegram.mjs +++ b/server/telegram.mjs @@ -57,7 +57,7 @@ export async function kodIleBagla(kod, {issuedAt,expiresAt,now=Date.now()} = {}) if(!/^[a-f0-9]{48}$/.test(String(kod))||!Number.isFinite(issuedAt)||!Number.isFinite(expiresAt)||now>=expiresAt)return null; const r = await fetch(`${API()}/getUpdates?limit=100`, { signal: AbortSignal.timeout(15000) }); const d = await r.json().catch(() => ({})); - if (!d.ok) throw new Error(d.description || 'getUpdates başarısız'); + if (!r.ok || d.ok!==true || !Array.isArray(d.result)) throw new Error(d.description || 'Telegram güncelleme yanıtı doğrulanamadı. Yeniden deneyin.'); const aranan = String(kod); for (const u of [...(d.result || [])].reverse()) { const m = u.message; diff --git a/src/components/tools/TelegramBaglanti.jsx b/src/components/tools/TelegramBaglanti.jsx index 72ed16b..a331d7d 100644 --- a/src/components/tools/TelegramBaglanti.jsx +++ b/src/components/tools/TelegramBaglanti.jsx @@ -71,7 +71,7 @@ const TelegramPanel = ({isAdmin}) => { ) : d.bagli ? (
- diff --git a/src/services/account.js b/src/services/account.js index d31ed16..2d7e274 100644 --- a/src/services/account.js +++ b/src/services/account.js @@ -113,7 +113,11 @@ export const updateDocumentMeta = (id, { sonKullanma, kategori } = {}) => export const telegramDurum = () => iste('/api/me/telegram'); export const telegramKodOlustur = () => iste('/api/me/telegram', {method:'POST',body:JSON.stringify({islem:'kod'})}); export const telegramBagla = (kod) => iste('/api/me/telegram', { method: 'POST', body: JSON.stringify({ kod }) }); -export const telegramTest = () => iste('/api/me/telegram', { method: 'POST', body: JSON.stringify({ islem: 'test' }) }); +export const telegramTest = async () => { + const data=await iste('/api/me/telegram',{method:'POST',body:JSON.stringify({islem:'test'})}); + if(typeof data.id!=='string'||!data.id.trim())throw new Error('Telegram gönderim kabulü doğrulanamadı. Tekrar göndermeden önce sohbetinizi kontrol edin.'); + return data; +}; export const telegramSil = () => iste('/api/me/telegram', { method: 'DELETE' }); // --- Takip bildirimleri (zeyilname / sonuç) --- diff --git a/tests/account-request.test.mjs b/tests/account-request.test.mjs index 23c7021..e54f882 100644 --- a/tests/account-request.test.mjs +++ b/tests/account-request.test.mjs @@ -36,3 +36,16 @@ test('late private account responses and explicit cancellation do not reach the await assert.rejects(accountRequest('/state',{signal:controller.signal}),{name:'AbortError'}); } finally {globalThis.fetch=original;if(storage)Object.defineProperty(globalThis,'localStorage',storage);else delete globalThis.localStorage;} }); + +test('Telegram test does not report success without a provider receipt or retry an ambiguous response',async()=>{ + const {telegramTest}=await import('../src/services/account.js'); + const original=globalThis.fetch;let calls=0; + try{ + globalThis.fetch=async()=>{calls++;return Response.json({success:true});}; + await assert.rejects(telegramTest(),/kabulü doğrulanamadı/);assert.equal(calls,1); + globalThis.fetch=async()=>Response.json({success:true,id:'123:42'}); + assert.equal((await telegramTest()).id,'123:42'); + globalThis.fetch=async()=>Response.json({success:false,error:'Telegram hizmeti etkin değil.'},{status:503}); + await assert.rejects(telegramTest(),/etkin değil/); + }finally{globalThis.fetch=original;} +}); diff --git a/tests/http-state.test.mjs b/tests/http-state.test.mjs index e30180f..0b919eb 100644 --- a/tests/http-state.test.mjs +++ b/tests/http-state.test.mjs @@ -7,6 +7,7 @@ import {join} from 'node:path'; import {randomBytes} from 'node:crypto'; import {createServer} from 'node:net'; const dir=mkdtempSync(join(tmpdir(),'ihaleist-http-')); +process.env.IHALEIST_USER_DATA_DIR=dir; process.env.AUTH_SECRET=randomBytes(32).toString('hex'); const {signToken}=await import('../server/auth.mjs'); const token=signToken({email:'qa@example.test',name:'QA'}); @@ -19,7 +20,7 @@ const request=async(path,method='GET',body,auth=token)=>{ before(async()=>{ const socket=createServer();await new Promise(r=>socket.listen(0,'127.0.0.1',r));const port=socket.address().port;await new Promise(r=>socket.close(r)); base='http://127.0.0.1:'+port; - child=spawn(process.execPath,['server/index.js'],{cwd:new URL('..',import.meta.url),env:{...process.env,NODE_ENV:'test',PORT:String(port),IHALEIST_USER_DATA_DIR:dir,IHALEIST_SHARED_DATA_DIR:join(dir,'shared'),UYUM_GOMME:'0'},stdio:'ignore'}); + child=spawn(process.execPath,['server/index.js'],{cwd:new URL('..',import.meta.url),env:{...process.env,NODE_ENV:'test',TELEGRAM_BOT_TOKEN:'',PORT:String(port),IHALEIST_USER_DATA_DIR:dir,IHALEIST_SHARED_DATA_DIR:join(dir,'shared'),UYUM_GOMME:'0'},stdio:'ignore'}); for(let i=0;i<100;i++){try{await fetch(base+'/api/auth/google/config');return;}catch{await new Promise(r=>setTimeout(r,50));}} throw new Error('QA API did not start'); }); @@ -294,3 +295,15 @@ test('Telegram cannot be linked through generic data writes or legacy client cod assert.equal((await request('/api/me/telegram','PATCH',{})).status,405); assert.equal((await request('/api/me/telegram','POST',{islem:'kod'},null)).status,401); }); + +test('saved Telegram link does not imply an unconfigured test message was sent',async()=>{ + const {createTelegramLinker}=await import('../server/telegram-link.mjs'); + // Parent and HTTP child use the same disposable database; no real provider credentials. + const tgStore=await import('../server/user-store.mjs'); + const linker=createTelegramLinker(tgStore),email='telegram-http@example.test'; + const challenge=linker.issue(email);await linker.link(email,challenge.kod,async()=>({chatId:'123',ad:'Synthetic'})); + const session=signToken({email,name:'Telegram Test'}); + const result=await request('/api/me/telegram','POST',{islem:'test'},session); + assert.equal(result.status,503);assert.equal(result.data.success,false); + assert.equal(result.data.id,undefined); +}); diff --git a/tests/telegram-link.test.mjs b/tests/telegram-link.test.mjs index a349e91..e6aa358 100644 --- a/tests/telegram-link.test.mjs +++ b/tests/telegram-link.test.mjs @@ -54,3 +54,15 @@ test('matching accepts only a fresh exact code from a private non-bot sender',as updates=[{message:{...good,text:'/start '+code}}];assert.equal((await kodIleBagla(code,window)).ad,'QA'); } finally {globalThis.fetch=oldFetch;if(oldToken===undefined)delete process.env.TELEGRAM_BOT_TOKEN;else process.env.TELEGRAM_BOT_TOKEN=oldToken;} }); + +test('provider errors and malformed updates are not treated as code-not-found results',async()=>{ + const oldFetch=globalThis.fetch,oldToken=process.env.TELEGRAM_BOT_TOKEN; + process.env.TELEGRAM_BOT_TOKEN='synthetic-token'; + const window={issuedAt:1000000,expiresAt:1600000,now:1100000}; + try { + for(const [ok,body] of [[false,{ok:true,result:[]}],[true,{ok:true}],[true,{ok:true,result:{}}],[true,{ok:'true',result:[]}]]){ + globalThis.fetch=async()=>({ok,json:async()=>body}); + await assert.rejects(kodIleBagla('a'.repeat(48),window),/yanıtı doğrulanamadı/); + } + }finally{globalThis.fetch=oldFetch;if(oldToken===undefined)delete process.env.TELEGRAM_BOT_TOKEN;else process.env.TELEGRAM_BOT_TOKEN=oldToken;} +});