import assert from 'node:assert/strict'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import LibDashStore from '../src/index.js'; import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js'; async function run() { console.log('--- Testing posterminalobituary table and deletion flow ---'); const tempDir = mkdtempSync(join(tmpdir(), 'test-obituary-')); const dbFile = join(tempDir, 'test.db'); const storageDir = join(tempDir, 'storage'); const provider = new BetterSqlite3Provider({ filename: dbFile, storageDir, }); await provider.initialize(); const store = await new LibDashStore(provider); try { // 1. Verify posterminalobituary table exists and has expected columns console.log('1. Checking posterminalobituary schema...'); const tableInfo = provider.db.pragma('table_info("posterminalobituary")'); const colNames = tableInfo.map((c) => c.name); console.log('posterminalobituary columns:', colNames); assert.deepEqual(colNames, [ 'id', 'posterminal_id', 'entity_type', 'entity_id', 'created_at', ]); // Setup company first console.log('2. Setting up base data...'); const googleAuthId = await store.google_auth.add('obituary-tester@example.com'); const company = await store.company.create({ name: 'Obituary Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId, }); await store.google_auth.setSelectedCompany(googleAuthId, company.id); // Setup active paired POS terminal const terminal = await store.posterminal.upsert(googleAuthId, { name: 'POS Terminal 1', pairingpin: '1234', }); await store.posterminal.pair(terminal.id, { token: 'pos-token-123', clientserial: 'SN-001', pairedAt: new Date().toISOString(), }); // 3. Test article deletion flow console.log('3. Testing article deletion obituary...'); const article = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Coffee Mug', }); await store.article.delete(googleAuthId, article.sku); // 4. Test category deletion flow console.log('4. Testing category deletion obituary...'); const cat = await store.category.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Snacks', }); await store.category.delete({ googleAuthId, id: cat.id }); // 5. Test customer deletion flow console.log('5. Testing customer deletion obituary...'); const customer = await store.entity.create(googleAuthId, { name: 'Mustermann', firstName: 'Max', customergroupId: company.posCustomerGroupId, }); await store.entity.delete(googleAuthId, customer.id); // 6. Test customer group deletion flow console.log('6. Testing customer group deletion obituary...'); const cg = await store.customergroup.create(googleAuthId, { name: 'VIP Guests', }); await store.customergroup.delete(googleAuthId, cg.id); // 7. Verify all obituaries exist in posterminalobituary console.log('7. Verifying obituaries in database...'); const allObituaries = provider.db .prepare('SELECT * FROM posterminalobituary WHERE posterminal_id = ? ORDER BY id ASC') .all(terminal.id); console.log('Created obituaries:', allObituaries); assert.equal(allObituaries.length, 4); assert.equal(allObituaries[0].entity_type, 'article'); assert.equal(allObituaries[0].entity_id, article.id); assert.equal(allObituaries[1].entity_type, 'category'); assert.equal(allObituaries[1].entity_id, cat.id); assert.equal(allObituaries[2].entity_type, 'customer'); assert.equal(allObituaries[2].entity_id, customer.id); assert.equal(allObituaries[3].entity_type, 'customergroup'); assert.equal(allObituaries[3].entity_id, cg.id); // 8. Test getDeletedEntityCount & getDeletedEntityList APIs console.log('8. Testing posterminal.getDeletedEntityCount & getDeletedEntityList...'); const initialCount = await store.posterminal.getDeletedEntityCount({ posterminalId: terminal.id, cursor: 0, }); assert.equal(initialCount, 4); const list = await store.posterminal.getDeletedEntityList({ posterminalId: terminal.id, cursor: 0, limit: 10, }); assert.equal(list.length, 4); assert.equal(list[0].entityType, 'article'); assert.equal(list[0].entityId, article.id); assert.equal(list[1].entityType, 'category'); assert.equal(list[2].entityType, 'customer'); assert.equal(list[3].entityType, 'customergroup'); // Test cursor pagination const cursor = allObituaries[1].id; const countAfterCursor = await store.posterminal.getDeletedEntityCount({ posterminalId: terminal.id, cursor, }); assert.equal(countAfterCursor, 2); const listAfterCursor = await store.posterminal.getDeletedEntityList({ posterminalId: terminal.id, cursor, limit: 10, }); assert.equal(listAfterCursor.length, 2); assert.equal(listAfterCursor[0].id, allObituaries[2].id); assert.equal(listAfterCursor[1].id, allObituaries[3].id); // 9. Test maxknownobituaryid console.log('9. Testing maxknownobituaryid...'); await store.posterminal.setMaxKnownObituaryId(terminal.id, allObituaries[3].id); const updatedTerminal = await store.posterminal.findByToken('pos-token-123'); assert.equal(updatedTerminal.maxknownobituaryid, allObituaries[3].id); // 10. Test setOff category & orphaned article obituary generation console.log('10. Testing setOff with orphaned and non-orphaned articles...'); // Create Cat A and Cat B const catA = await store.category.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Category A', }); const catB = await store.category.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Category B', }); // Art A only in Cat A const artA = await store.article.createUnderParent({ googleAuthId, parentId: catA.id, name: 'Article A (only in Cat A)', }); // Art AB in both Cat A and Cat B const artAB = await store.article.createUnderParent({ googleAuthId, parentId: catA.id, name: 'Article AB (in Cat A and B)', }); await store.article.setCategoryIdsBySku(googleAuthId, artAB.sku, [catA.id, catB.id]); // Turn both categories on for the terminal await store.posterminal.setOn(googleAuthId, terminal.id, catA.id); await store.posterminal.setOn(googleAuthId, terminal.id, catB.id); // Clear previous obituaries to test cleanly provider.db.prepare('DELETE FROM posterminalobituary WHERE posterminal_id = ?').run(terminal.id); // Turn off Cat A: // - Cat A should get an obituary // - Art A should get an obituary (orphaned on this terminal) // - Art AB should NOT get an obituary (still has active Cat B on this terminal) await store.posterminal.setOff(googleAuthId, terminal.id, catA.id); const obituariesAfterSetOff = provider.db .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ? ORDER BY id ASC') .all(terminal.id); console.log('Obituaries after setOff(Cat A):', obituariesAfterSetOff); assert.equal(obituariesAfterSetOff.length, 2); assert.deepEqual(obituariesAfterSetOff, [ { entity_type: 'category', entity_id: catA.id }, { entity_type: 'article', entity_id: artA.id }, ]); // 11. Test setOn category clears pending obituaries for that category and its articles console.log('11. Testing setOn clears pending category and article obituaries...'); await store.posterminal.setOn(googleAuthId, terminal.id, catA.id); const obituariesAfterSetOn = provider.db .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ?') .all(terminal.id); console.log('Obituaries after setOn(Cat A):', obituariesAfterSetOn); assert.equal(obituariesAfterSetOn.length, 0); // 12. Test setOffAll generates obituaries for all active categories and all their articles console.log('12. Testing setOffAll obituaries...'); await store.posterminal.setOffAll(googleAuthId, terminal.id); const obituariesAfterSetOffAll = provider.db .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ? ORDER BY id ASC') .all(terminal.id); console.log('Obituaries after setOffAll:', obituariesAfterSetOffAll); // Cat A, Cat B, Art A, Art AB const catObituaries = obituariesAfterSetOffAll.filter((t) => t.entity_type === 'category').map((t) => t.entity_id); const artObituaries = obituariesAfterSetOffAll.filter((t) => t.entity_type === 'article').map((t) => t.entity_id); assert(catObituaries.includes(catA.id)); assert(catObituaries.includes(catB.id)); assert(artObituaries.includes(artA.id)); assert(artObituaries.includes(artAB.id)); // 13. Test setOnAll clears category and article obituaries console.log('13. Testing setOnAll clears all category & article obituaries...'); await store.posterminal.setOnAll(googleAuthId, terminal.id); const obituariesAfterSetOnAll = provider.db .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ?') .all(terminal.id); console.log('Obituaries after setOnAll:', obituariesAfterSetOnAll); assert.equal(obituariesAfterSetOnAll.length, 0); // 14. Test setCategoryIdsBySku generates obituary when removed from POS-enabled category console.log('14. Testing setCategoryIdsBySku obituary generation and clearing...'); // Setup terminal: only has Cat A enabled await store.posterminal.setOffAll(googleAuthId, terminal.id); // Clear obituaries from setOffAll to start clean provider.db.prepare('DELETE FROM posterminalobituary WHERE posterminal_id = ?').run(terminal.id); await store.posterminal.setOn(googleAuthId, terminal.id, catA.id); // Create article in Cat A const artMoving = await store.article.createUnderParent({ googleAuthId, parentId: catA.id, name: 'Moving Article', }); // Should have 0 obituaries on terminal let obits = provider.db .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ?') .all(terminal.id); assert.equal(obits.length, 0); // Move article from Cat A to Cat B (which is not enabled on terminal) console.log('Moving article to Cat B (not on terminal)...'); await store.article.setCategoryIdsBySku(googleAuthId, artMoving.sku, [catB.id]); obits = provider.db .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ?') .all(terminal.id); console.log('Obituaries after moving to Cat B:', obits); assert.equal(obits.length, 1); assert.deepEqual(obits[0], { entity_type: 'article', entity_id: artMoving.id }); // Move article back to Cat A (enabled on terminal) console.log('Moving article back to Cat A (on terminal)...'); await store.article.setCategoryIdsBySku(googleAuthId, artMoving.sku, [catA.id]); obits = provider.db .prepare('SELECT entity_type, entity_id FROM posterminalobituary WHERE posterminal_id = ?') .all(terminal.id); console.log('Obituaries after moving back to Cat A:', obits); assert.equal(obits.length, 0); console.log('✅ ALL POSTERMINALOBITUARY TESTS PASSED'); } finally { try { rmSync(tempDir, { recursive: true, force: true }); } catch {} } } run().catch((err) => { console.error('Test failed:', err); process.exit(1); });