313 lines
24 KiB
JavaScript
313 lines
24 KiB
JavaScript
import {test,before,after} from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import {spawn} from 'node:child_process';
|
||
import {mkdtempSync,rmSync} from 'node:fs';
|
||
import {tmpdir} from 'node:os';
|
||
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'});
|
||
const other=signToken({email:'other@example.test',name:'Other'});
|
||
let child,base;
|
||
const request=async(path,method='GET',body,auth=token)=>{
|
||
const r=await fetch(base+path,{method,headers:{'Content-Type':'application/json',...(auth?{Authorization:'Bearer '+auth}:{})},body:body?JSON.stringify(body):undefined});
|
||
return {status:r.status,data:await r.json(),headers:r.headers};
|
||
};
|
||
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',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');
|
||
});
|
||
after(async()=>{if(child){child.kill();await new Promise(r=>child.once('exit',r));}rmSync(dir,{recursive:true,force:true});});
|
||
test('HTTP preparation requires auth, persists draft and rejects stale revisions',async()=>{
|
||
const unauthorized=await request('/api/me/tender-state','GET',null,null);
|
||
assert.equal(unauthorized.status,401);
|
||
assert.equal(unauthorized.headers.get('x-content-type-options'),'nosniff');
|
||
assert.equal(unauthorized.headers.get('referrer-policy'),'no-referrer');
|
||
const draft={firma:{firmaUnvan:'QA Laboratuvar'},priceSchema:1,pricesBySource:{kit1:'12.50'},gecerlilikGun:'120',checks:{products:{kit1:{product:'Test kit',status:'suitable',source:'Teknik 3.2'}},documents:{doc1:{status:'verified',evidence:'Test v1'}}},manualDocuments:[{document:'Yetki',note:'7.1',mandatory:true}],analysisSource:{itemsAndSpecs:[{name:'Kit',quantity:'2 adet'}],documentChecklist:[]}};
|
||
const data={key:'2026/1582991',patch:{takipte:true,gorev:{kontrolListesi:[]},bidDraft:draft,checklist:[{key:'collect-documents',sourceSnapshot:'source-v1',done:true}]},snapshot:{id:'ilan-qa',ikn:'2026/1582991'},version:0};
|
||
assert.equal((await request('/api/me/tender-state','PUT',data)).status,200);
|
||
const read=await request('/api/me/tender-state');assert.deepEqual(read.data.records[data.key].data.bidDraft,draft);
|
||
assert.deepEqual(read.data.records[data.key].data.checklist,data.patch.checklist);
|
||
assert.equal((await request('/api/me/tender-state','PUT',data)).status,409);
|
||
assert.deepEqual((await request('/api/me/tender-state','GET',null,other)).data.records,{});
|
||
});
|
||
test('HTTP documents and API keys expose empty successful state, not endless load errors',async()=>{
|
||
const docs=await request('/api/me/documents');assert.equal(docs.status,200);assert.deepEqual(docs.data.belgeler,[]);
|
||
const keys=await request('/api/me/apikeys');assert.equal(keys.status,200);assert.deepEqual(keys.data.anahtarlar,[]);
|
||
const data=await request('/api/me/data');assert.equal(data.status,200);assert.deepEqual(data.data.veri,{});
|
||
});
|
||
|
||
test('HTTP file upload, owned download, metadata update and deletion operate on actual bytes',async()=>{
|
||
const bytes=Buffer.from('QA belge — Türkçe içerik, yalnız izole test.');
|
||
for (const invalid of ['!!!!','a','a===','AB==',[],{}]) {
|
||
assert.equal((await request('/api/me/documents','POST',{ad:'broken.txt',icerikBase64:invalid})).status,400);
|
||
}
|
||
const uploaded=await request('/api/me/documents','POST',{ad:'qa.txt',mime:'text/plain',icerikBase64:bytes.toString('base64'),kategori:'Firma',sonKullanma:'2026-12-31'});
|
||
assert.equal(uploaded.status,200);
|
||
const id=uploaded.data.id;
|
||
const list=await request('/api/me/documents');const file=list.data.belgeler.find(f=>f.id===id);
|
||
assert.equal(file.erisilebilir,true);assert.equal(file.boyut,bytes.length);assert.equal(file.dosya,undefined);
|
||
const downloaded=await fetch(base+'/api/me/documents/download?id='+id,{headers:{Authorization:'Bearer '+token}});
|
||
assert.equal(downloaded.status,200);assert.deepEqual(Buffer.from(await downloaded.arrayBuffer()),bytes);
|
||
assert.equal((await request('/api/me/documents/download?id='+id,'GET',null,other)).status,404);
|
||
assert.equal((await request('/api/me/documents/meta','PATCH',{id,sonKullanma:'2027-01-01'})).status,200);
|
||
const changed=await request('/api/me/documents');assert.equal(changed.data.belgeler.find(f=>f.id===id).son_kullanma,'2027-01-01');
|
||
for (const invalid of ['2027-02-29','2026-02-31','2026-12-31suffix',42]) {
|
||
assert.equal((await request('/api/me/documents/meta','PATCH',{id,sonKullanma:invalid})).status,400);
|
||
assert.equal((await request('/api/me/documents','POST',{ad:'invalid.txt',icerikBase64:bytes.toString('base64'),sonKullanma:invalid})).status,400);
|
||
}
|
||
const categoryOnly=await request('/api/me/documents/meta','PATCH',{id,kategori:'Yeterlik'});
|
||
assert.equal(categoryOnly.data.belgeler.find(f=>f.id===id).son_kullanma,'2027-01-01');
|
||
assert.equal((await request('/api/me/documents/meta','PATCH',{id,sonKullanma:null},other)).status,404);
|
||
const cleared=await request('/api/me/documents/meta','PATCH',{id,sonKullanma:null});
|
||
assert.equal(cleared.data.belgeler.find(f=>f.id===id).son_kullanma,null);
|
||
assert.equal(cleared.data.belgeler.find(f=>f.id===id).kategori,'Yeterlik');
|
||
assert.equal((await request('/api/me/documents')).data.belgeler.length,1);
|
||
assert.equal((await request('/api/me/documents?id='+id,'DELETE')).status,200);
|
||
assert.equal((await request('/api/me/documents/download?id='+id)).status,404);
|
||
});
|
||
|
||
test('shared item extraction requires an owned tender document and matching tender',async()=>{
|
||
const body={ad:'scope.txt',icerikBase64:Buffer.from('Yalnız test dokümanı, cetvel yok.').toString('base64'),ikn:'2026/123456'};
|
||
const company=await request('/api/me/documents','POST',body);
|
||
assert.equal(company.status,200);
|
||
assert.equal(company.data.processing.status,'not_applicable');
|
||
assert.equal(company.data.belgeler.find(d=>d.id===company.data.id).tur,'firma');
|
||
const extract=id=>({documentId:id,ikn:body.ikn,metin:'Cetvel yok.'});
|
||
assert.equal((await request('/api/kalem/cikar','POST',extract(company.data.id))).status,403);
|
||
await request('/api/me/documents/meta','PATCH',{id:company.data.id,kategori:'İhale Dokümanı'});
|
||
assert.equal((await request('/api/kalem/cikar','POST',extract(company.data.id))).status,403);
|
||
const tender=await request('/api/me/documents','POST',{...body,documentType:'ihale'});
|
||
assert.equal(tender.status,200);
|
||
assert.equal(tender.data.processing.status,'awaiting_text');
|
||
const processing=async()=> (await request('/api/me/documents')).data.belgeler.find(d=>d.id===tender.data.id).isleme;
|
||
assert.equal((await request('/api/kalem/cikar','POST',extract(tender.data.id),other)).status,403);
|
||
assert.equal((await request('/api/kalem/cikar','POST',{...extract(tender.data.id),ikn:'2026/999999'})).status,403);
|
||
assert.equal((await processing()).status,'awaiting_text');
|
||
const prior=await processing();
|
||
const readError={id:tender.data.id,expectedProcessing:prior};
|
||
assert.equal((await request('/api/me/documents/read-error','POST',readError,null)).status,401);
|
||
assert.equal((await request('/api/me/documents/read-error','POST',readError,other)).status,404);
|
||
assert.equal((await request('/api/me/documents/read-error','POST',{id:company.data.id,expectedProcessing:null})).status,404);
|
||
assert.equal((await request('/api/me/documents/read-error','POST',readError)).status,200);
|
||
assert.equal((await processing()).status,'error');
|
||
assert.equal((await request('/api/me/documents/read-error','POST',readError)).status,409);
|
||
|
||
assert.equal((await request('/api/kalem/cikar','POST',{...extract(tender.data.id),metin:{invalid:true}})).status,400);
|
||
assert.equal((await processing()).status,'error');
|
||
assert.equal((await request('/api/kalem/cikar','POST',extract(tender.data.id))).status,200);
|
||
assert.equal((await processing()).status,'no_items');
|
||
assert.equal((await processing()).count,0);
|
||
assert.ok((await processing()).updatedAt);
|
||
assert.equal((await request('/api/me/documents')).data.belgeler.filter(d=>d.id===tender.data.id).length,1);
|
||
const retained=await fetch(base+'/api/me/documents/download?id='+tender.data.id,{headers:{Authorization:'Bearer '+token}});
|
||
assert.equal(retained.status,200);
|
||
assert.equal(await retained.text(),Buffer.from(body.icerikBase64,'base64').toString());
|
||
const reviewUrl='/api/me/documents/text-review?id='+tender.data.id;
|
||
assert.equal((await request(reviewUrl,'GET',null,other)).status,404);
|
||
const initialReview=await request(reviewUrl);
|
||
assert.equal(initialReview.data.review,null);
|
||
const correction={id:tender.data.id,fileHash:initialReview.data.fileHash,expectedRevision:0,
|
||
correctedText:'Sentetik belge metni tüm sayfalarla karşılaştırıldı. Bu gerçek ihale incelemesi değildir.',
|
||
note:'Sayfa 1 üzerinde sentetik test karşılaştırması yapıldı.',allPagesCompared:true};
|
||
assert.equal((await request('/api/me/documents/text-review','POST',{...correction,allPagesCompared:false})).status,400);
|
||
assert.equal((await request('/api/me/documents/text-review','POST',correction,other)).status,404);
|
||
const savedReview=await request('/api/me/documents/text-review','POST',correction);
|
||
assert.equal(savedReview.status,200);assert.equal(savedReview.data.review.revision,1);
|
||
const verification={ikn:body.ikn,review:{documentId:tender.data.id,revision:1,fileHash:savedReview.data.fileHash,textHash:savedReview.data.review.text_hash}};
|
||
assert.equal((await request('/api/me/documents/text-review/validate','POST',verification)).status,200);
|
||
assert.equal((await request('/api/me/documents/text-review/validate','POST',verification,other)).status,404);
|
||
assert.equal((await request('/api/me/documents/text-review/validate','POST',{...verification,review:{...verification.review,textHash:'wrong'}})).status,409);
|
||
assert.equal((await request('/api/me/documents/text-review/validate','POST',{...verification,ikn:'2026/999'})).status,409);
|
||
|
||
assert.equal((await request(reviewUrl)).data.review.corrected_text,correction.correctedText);
|
||
assert.equal((await request('/api/me/documents/text-review','POST',correction)).status,409);
|
||
assert.equal((await request('/api/me/documents/text-review','POST',{...correction,expectedRevision:1,fileHash:'old-hash'})).status,409);
|
||
assert.equal((await request('/api/me/documents/text-review','POST',{...correction,expectedRevision:1,correctedText:correction.correctedText+' İkinci inceleme.'})).status,200);
|
||
assert.equal((await request('/api/me/documents/text-review/validate','POST',verification)).status,409);
|
||
|
||
|
||
assert.equal((await request('/api/kalem/cikar','POST',{ikn:body.ikn,metin:'test'})).status,403);
|
||
await request('/api/me/documents?id='+company.data.id,'DELETE');
|
||
await request('/api/me/documents?id='+tender.data.id,'DELETE');
|
||
});
|
||
|
||
test('HTTP API key creation, private listing, MCP use and revocation form a complete lifecycle', async () => {
|
||
const admin=signToken({email:'keys@example.test',name:'Key QA',isAdmin:true});
|
||
const created=await request('/api/me/apikeys','POST',{ad:'QA integration'},admin);
|
||
assert.equal(created.status,200);
|
||
const {id,anahtar}=created.data.anahtar;
|
||
const listed=await request('/api/me/apikeys','GET',null,admin);
|
||
assert.equal(listed.data.anahtarlar.length,1);
|
||
assert.equal(listed.data.anahtarlar[0].anahtar,undefined);
|
||
assert.equal(listed.data.anahtarlar[0].hash,undefined);
|
||
assert.equal((await request('/api/me/apikeys?id='+id,'DELETE',null,other)).status,404);
|
||
const rpc={jsonrpc:'2.0',id:1,method:'initialize',params:{}};
|
||
assert.equal((await request('/mcp','POST',rpc,anahtar)).status,200);
|
||
const used=await request('/api/me/apikeys','GET',null,admin);
|
||
assert.ok(used.data.anahtarlar[0].son_kullanim);
|
||
const revoked=await request('/api/me/apikeys?id='+id,'DELETE',null,admin);
|
||
assert.equal(revoked.status,200);
|
||
assert.equal(revoked.data.limit,listed.data.limit);
|
||
assert.deepEqual(revoked.data.anahtarlar,[]);
|
||
assert.equal((await request('/mcp','POST',rpc,anahtar)).status,401);
|
||
});
|
||
|
||
test('concurrent API key requests never exceed quota and malformed input creates nothing',async()=>{
|
||
const auth=signToken({email:'quota-http@example.test',name:'Quota QA',isAdmin:true});
|
||
const pending=Array.from({length:16},(_,i)=>request('/api/me/apikeys','POST',{ad:`QA ${i}`},auth));
|
||
const results=await Promise.all(pending);
|
||
assert.equal(results.filter(r=>r.status===200).length,10);
|
||
assert.equal(results.filter(r=>r.status===402&&r.data.kod==='KOTA_ASILDI').length,6);
|
||
const keys=await request('/api/me/apikeys','GET',null,auth);
|
||
assert.equal(keys.data.anahtarlar.length,10);
|
||
await request('/api/me/apikeys?id='+keys.data.anahtarlar[0].id,'DELETE',null,auth);
|
||
const invalid=await fetch(base+'/api/me/apikeys',{method:'POST',headers:{Authorization:'Bearer '+auth,'Content-Type':'application/json'},body:'{broken'});
|
||
assert.equal(invalid.status,400);
|
||
assert.equal((await request('/api/me/apikeys','POST',{ad:{}},auth)).status,400);
|
||
assert.equal((await request('/api/me/apikeys','GET',null,auth)).data.anahtarlar.length,9);
|
||
assert.equal((await request('/api/me/apikeys','POST',{ad:'replacement'},auth)).status,200);
|
||
assert.equal((await request('/api/me/apikeys','PUT',{},auth)).status,405);
|
||
});
|
||
|
||
test('source history requires authentication and distinguishes empty results from invalid pagination',async()=>{
|
||
assert.equal((await request('/api/tenders/source-history?ikn=2026/123','GET',null,null)).status,401);
|
||
const response=await request('/api/tenders/source-history?ikn=2026/123');
|
||
assert.equal(response.status,200);assert.deepEqual(response.data.records,[]);assert.equal(response.data.nextCursor,null);
|
||
assert.equal((await request('/api/tenders/source-history?ikn=2026/123&before=bad')).status,400);
|
||
assert.equal((await request('/api/tenders/source-history?ikn=2026/123','POST',{})).status,405);
|
||
});
|
||
|
||
test('durable AI job endpoints enforce account isolation without starting provider calls',async()=>{
|
||
assert.equal((await request('/api/me/ai-jobs','GET',null,null)).status,401);
|
||
const list=await request('/api/me/ai-jobs?ikn=2026/123');assert.equal(list.status,200);assert.deepEqual(list.data.jobs,[]);
|
||
assert.equal((await request('/api/me/ai-jobs?id=missing')).status,404);
|
||
assert.equal((await request('/api/me/ai-jobs?id=missing','DELETE',null,other)).status,404);
|
||
assert.equal((await request('/api/me/ai-jobs','PUT',{})).status,405);
|
||
});
|
||
|
||
test('outbox review endpoint requires administrator and rejects unsupported or invalid changes',async()=>{
|
||
assert.equal((await request('/api/admin/notify/outbox','GET',null,null)).status,401);
|
||
assert.equal((await request('/api/admin/notify/outbox')).status,403);
|
||
assert.equal((await request('/api/admin/notify/outbox','POST',{id:'x',action:'skip',note:'Bu kullanıcı yönetici değildir.'})).status,403);
|
||
const admin=signToken({email:'outbox-admin@example.test',name:'Admin',isAdmin:true});
|
||
assert.equal((await request('/api/admin/notify/outbox','GET',null,admin)).status,200);
|
||
assert.equal((await request('/api/admin/notify/outbox','DELETE',null,admin)).status,405);
|
||
assert.equal((await request('/api/admin/notify/outbox','POST',{id:'x',action:'skip',note:'short'},admin)).status,400);
|
||
assert.equal((await request('/api/admin/notify/outbox','POST',{id:'missing',action:'skip',note:'Sağlayıcı kaydı kontrol edildi ve kapatılıyor.'},admin)).status,409);
|
||
});
|
||
|
||
test('profile and saved-filter writes require the loaded revision and reject concurrent overwrites',async()=>{
|
||
for(const anahtar of ['firma_profil','saved_filters']){
|
||
const deger=anahtar==='firma_profil'?{company:'QA başlangıç'}:[{id:'qa',name:'Başlangıç'}];
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger})).status,428);
|
||
const initial=await request('/api/me/data');const revision=initial.data.revisions[anahtar] ?? null;
|
||
const first=await request('/api/me/data','PUT',{anahtar,deger,revision});assert.equal(first.status,200);assert.match(first.data.revision,/^[a-f0-9]{64}$/);
|
||
const attempts=await Promise.all(Array.from({length:8},(_,i)=>request('/api/me/data','PUT',{anahtar,deger:anahtar==='firma_profil'?{company:'QA '+i}:[{id:'qa'+i}],revision:first.data.revision})));
|
||
assert.equal(attempts.filter(x=>x.status===200).length,1);
|
||
assert.equal(attempts.filter(x=>x.status===409&&x.data.kod==='VERSION_CONFLICT').length,7);
|
||
const after=await request('/api/me/data');assert.equal(after.data.revisions[anahtar],attempts.find(x=>x.status===200).data.revision);
|
||
const stale=await request('/api/me/data','PUT',{anahtar,deger,revision:first.data.revision});assert.equal(stale.status,409);
|
||
assert.deepEqual((await request('/api/me/data')).data.veri[anahtar],after.data.veri[anahtar]);
|
||
// A revision read under another owner cannot grant write access to this record.
|
||
const foreign=await request('/api/me/data','PUT',{anahtar,deger,revision:after.data.revisions[anahtar]},other);assert.equal(foreign.status,409);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger,revision:null},other)).status,200);
|
||
}
|
||
});
|
||
test('team membership rejects stale and over-quota writes and permits removing last member',async()=>{
|
||
const anahtar='ekip',member={id:'qa-team',name:'QA ekip üyesi',role:'Uzman',email:''};
|
||
const initial=await request('/api/me/data');
|
||
const revision=initial.data.revisions.ekip ?? null;
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:{},revision})).status,400);
|
||
const saved=await request('/api/me/data','PUT',{anahtar,deger:[member],revision});assert.equal(saved.status,200);
|
||
const stale=await request('/api/me/data','PUT',{anahtar,deger:[],revision});assert.equal(stale.status,409);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:[member,{...member,id:'qa-2'}],revision:saved.data.revision})).status,402);
|
||
assert.deepEqual((await request('/api/me/data')).data.veri.ekip,[member]);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:[],revision:saved.data.revision})).status,200);
|
||
assert.deepEqual((await request('/api/me/data')).data.veri.ekip,[]);
|
||
});
|
||
test('favorite institutions require a version and retain current values after a stale update',async()=>{
|
||
const anahtar='favori_kurumlar';
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:['QA Kurum']})).status,428);
|
||
const first=await request('/api/me/data','PUT',{anahtar,deger:['QA Kurum'],revision:null});assert.equal(first.status,200);
|
||
const updated=await request('/api/me/data','PUT',{anahtar,deger:['QA Kurum','QA Diğer'],revision:first.data.revision});assert.equal(updated.status,200);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:[],revision:first.data.revision})).status,409);
|
||
for(const bad of [{},[null],[' '],['QA Kurum',' QA Kurum ']])assert.equal((await request('/api/me/data','PUT',{anahtar,deger:bad,revision:updated.data.revision})).status,400);
|
||
assert.deepEqual((await request('/api/me/data')).data.veri[anahtar],['QA Kurum','QA Diğer']);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:[],revision:updated.data.revision})).status,200);
|
||
});
|
||
test('competitor watchlist keeps the saved roster when a stale or invalid write is rejected',async()=>{
|
||
const anahtar='rakip_izleme',item={id:'qa-radar',name:'QA Medikal',sector:'Sağlık'};
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:[item]})).status,428);
|
||
const first=await request('/api/me/data','PUT',{anahtar,deger:[item],revision:null});assert.equal(first.status,200);
|
||
for(const deger of [{},[{}],[{...item,sector:{}}],[item,{...item,id:'other',name:' qa medikal '}]])
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger,revision:first.data.revision})).status,400);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:[],revision:null})).status,409);
|
||
assert.deepEqual((await request('/api/me/data')).data.veri[anahtar],[item]);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:[],revision:first.data.revision})).status,200);
|
||
});
|
||
test('notification preferences reject stale writes and malformed quiet times',async()=>{
|
||
const anahtar='reminder',deger={pushEnabled:true,quietEnabled:true,quietStart:'22:00',quietEnd:'08:00'};
|
||
const first=await request('/api/me/data','PUT',{anahtar,deger,revision:null});assert.equal(first.status,200);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:{...deger,quietStart:'25:00'},revision:first.data.revision})).status,400);
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar,deger:{...deger,pushEnabled:false},revision:null})).status,409);
|
||
assert.deepEqual((await request('/api/me/data')).data.veri.reminder,deger);
|
||
});
|
||
test('weekly brief preference loads without feed preview and changes only with a current revision',async()=>{
|
||
const first=await request('/api/me/brief');assert.equal(first.status,200);assert.equal(first.data.kapali,false);assert.equal(first.data.revision,null);assert.equal(first.data.metin,undefined);
|
||
assert.equal((await request('/api/me/brief','POST',{})).status,400);
|
||
const saved=await request('/api/me/brief','POST',{kapali:true,revision:null});assert.equal(saved.status,200);assert.equal(saved.data.kapali,true);
|
||
assert.equal((await request('/api/me/brief','POST',{kapali:false,revision:null})).status,409);
|
||
assert.equal((await request('/api/me/brief')).data.kapali,true);
|
||
assert.equal((await request('/api/me/brief','GET',null,other)).data.kapali,false);
|
||
assert.equal((await request('/api/me/brief','POST',{kapali:false,revision:saved.data.revision})).status,200);
|
||
});
|
||
|
||
test('weekly brief review is admin-only and persists evidence without sending messages',async()=>{
|
||
const route='/api/admin/brief/outbox';
|
||
assert.equal((await request(route,'GET',null,null)).status,401);
|
||
assert.equal((await request(route)).status,403);
|
||
assert.equal((await request(route,'POST',{id:'x',action:'skip',note:'Yetkisiz işlem kontrolü.'})).status,403);
|
||
const admin=signToken({email:'weekly-admin@example.test',name:'Admin',isAdmin:true});
|
||
const {createBriefOutbox}=await import('../server/brief-outbox.mjs');
|
||
const box=createBriefOutbox(dir),batch=box.prepare('2026-09-07','weekly-test@example.test',[{channel:'email',to:'weekly-test@example.test'}]);
|
||
box.hold(box.claim(batch.id,0),'Test sonucu belirsiz');
|
||
const list=await request(route,'GET',null,admin);
|
||
assert.equal(list.status,200);assert.equal(list.data.deliveries.length,1);
|
||
const id=list.data.deliveries[0].id;
|
||
assert.equal((await request(route,'DELETE',null,admin)).status,405);
|
||
const input={id,action:'confirmed_sent',providerId:'test-provider-receipt',confirmed:true,note:'İzole test sağlayıcı kaydı doğrulandı.'};
|
||
assert.equal((await request(route,'POST',{...input,confirmed:false},admin)).status,400);
|
||
assert.equal((await request(route,'POST',input,admin)).status,200);
|
||
assert.equal((await request(route,'POST',input,admin)).status,409);
|
||
const saved=(await request(route,'GET',null,admin)).data.deliveries[0];
|
||
assert.equal(saved.review.actor,'weekly-admin@example.test');assert.equal(saved.providerId,input.providerId);
|
||
assert.equal(saved.state,'sent_manual');
|
||
});
|
||
|
||
test('Telegram cannot be linked through generic data writes or legacy client codes',async()=>{
|
||
assert.equal((await request('/api/me/data','PUT',{anahtar:'telegram',deger:{chatId:'123'}})).status,403);
|
||
assert.equal((await request('/api/me/telegram','POST',{kod:'123456'})).status,400);
|
||
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);
|
||
});
|