Files
ihaleist/tests/http-state.test.mjs
T

91 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.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()};
};
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,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()=>{
assert.equal((await request('/api/me/tender-state','GET',null,null)).status,401);
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.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((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 request('/api/kalem/cikar','POST',extract(tender.data.id))).status,200);
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');
});