fix: persist tender preparation and correct calendar export

This commit is contained in:
2026-09-06 08:30:32 +03:00
parent 909ebae7c2
commit c91631d99f
19 changed files with 569 additions and 182 deletions
+25
View File
@@ -0,0 +1,25 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createTenderICS, tenderDeadline } from '../src/services/tenderCalendar.js';
const t = {ikn:'2026/1582991',baslik:'Mikroskop',kurum:'Giresun',tarih:'10.09.2026',saat:'',aciklama:'Tarih ve Saati : 10.09.2026 - 10:00'};
test('Giresun date is September 10 at 10:00 Turkey, not hard-coded August', () => {
assert.equal(tenderDeadline(t).toISOString(),'2026-09-10T07:00:00.000Z');
assert.match(createTenderICS(t), /DTSTART:20260910T070000Z/);
});
test('all five tender deadlines and year rollover', () => {
for (const [date,time,expected] of [
['09.09.2026','10:00','2026-09-09T07:00'],['11.09.2026','10:00','2026-09-11T07:00'],
['22.09.2026','10:30','2026-09-22T07:30'],['22.09.2026','11:00','2026-09-22T08:00'],
['01.01.2027','01:00','2026-12-31T22:00'],['29.02.2028','10:00','2028-02-29T07:00']])
assert.ok(tenderDeadline({...t,tarih:date,saat:time}).toISOString().startsWith(expected));
});
test('missing time, impossible date, invalid time are rejected', () => {
for (const patch of [{saat:'',aciklama:''},{tarih:'31.02.2026',saat:'10:00'},{saat:'25:00'}])
assert.throws(() => tenderDeadline({...t,...patch}));
});
test('ICS escapes text, folds UTF-8 lines, keeps stable UID', () => {
const ics = createTenderICS({...t,baslik:'Ş'.repeat(100)+'\nEND:VEVENT;,'});
assert.ok(ics.split('\r\n').every(l => Buffer.byteLength(l) <= 75));
assert.equal(ics.split('UID:')[1].split('\r\n')[0],'2026%2F1582991@ihaleist.com');
assert.ok(ics.includes('\\nEND:VEVENT\\;\\,'));
});
+57
View File
@@ -0,0 +1,57 @@
import { test, after } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
const dir = mkdtempSync(join(tmpdir(), 'ihaleist-store-'));
process.env.IHALEIST_USER_DATA_DIR = dir;
const legacy = new DatabaseSync(join(dir, 'users.sqlite'));
legacy.exec(`CREATE TABLE belgeler (id TEXT PRIMARY KEY,email TEXT NOT NULL,ad TEXT NOT NULL,kategori TEXT,boyut INTEGER NOT NULL,mime TEXT,dosya TEXT NOT NULL,olusturma TEXT);
INSERT INTO belgeler VALUES ('old','a@example.test','Eski belge','Yasal',5,'text/plain','old.txt','2026-09-01');`);
legacy.close();
const store = await import('../server/user-store.mjs');
after(() => rmSync(dir, { recursive: true, force: true }));
test('old volume migrates before indexing, preserves files and migration is repeatable', () => {
for (let i = 0; i < 2; i++) {
const db = store.openDb();
assert.equal(db.prepare('SELECT COUNT(*) n FROM schema_migrations').get().n, 1);
assert.ok(db.prepare('PRAGMA table_info(belgeler)').all().some(c => c.name === 'ikn'));
db.close();
}
assert.equal(store.listDocuments('a@example.test')[0].ad, 'Eski belge');
assert.deepEqual(store.listDocuments('b@example.test'), []);
});
test('five preparation records persist across DB close/reopen and stay account scoped', () => {
const records = Object.fromEntries(['1573235','1582991','1523851','1499061','1534665'].map(id => ['2026/'+id, { takipte: true, gorev: { kontrolListesi: [{ad:'ÜTS',tamam:false}] }, kanbanStatus:'teklif_hazirlaniyor' }]));
assert.equal(store.setData('a@example.test', 'gorevler', records), true);
assert.deepEqual(store.getAllData('a@example.test').gorevler, records);
assert.equal(store.getData('b@example.test', 'gorevler'), null);
});
test('document and API key ownership enforced', () => {
const id = store.addDocument('a@example.test', {ad:'Şartname',boyut:10,dosya:'spec.pdf',ikn:'2026/1582991'});
assert.equal(store.listDocuments('a@example.test', {ikn:'2026/1582991'}).length, 1);
assert.equal(store.getDocument('b@example.test', id), null);
const key = store.createApiKey('a@example.test', 'Test');
assert.equal(store.verifyApiKey(key.anahtar).email, 'a@example.test');
assert.equal(store.revokeApiKey('b@example.test', key.id), false);
store.revokeApiKey('a@example.test', key.id);
assert.equal(store.verifyApiKey(key.anahtar), null);
});
test('database write failure propagates instead of pretending success', () => {
const db = store.openDb();
db.exec(`CREATE TRIGGER reject_write BEFORE INSERT ON kullanici_verisi BEGIN SELECT RAISE(FAIL,'test write failure'); END;`);
db.close();
assert.throws(() => store.setData('a@example.test','takip',[]), /test write failure/);
});
test('per-tender versions prevent stale updates; five tasks persist', () => {
for (const id of ['1573235','1582991','1523851','1499061','1534665']) {
const key = '2026/'+id;
const saved = store.saveTenderState('c@example.test', key, {takipte:true,gorev:{kontrolListesi:[]}}, {id:key,ikn:key}, 0);
assert.equal(saved.version,1);
assert.throws(() => store.saveTenderState('c@example.test', key, {takipte:false}, {}, 0), e => e.kod === 'VERSION_CONFLICT');
assert.equal(store.saveTenderState('c@example.test', key, {notes:[{text:'test'}]}, {}, 1).version,2);
}
assert.equal(Object.keys(store.getTenderStates('c@example.test')).length,5);
assert.deepEqual(store.getTenderStates('d@example.test'),{});
});