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 articletombstone table and deletion flow ---'); const tempDir = mkdtempSync(join(tmpdir(), 'test-tombstone-')); 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 table exists and has expected columns console.log('1. Checking articletombstone schema...'); const tableInfo = provider.db.pragma('table_info("articletombstone")'); const colNames = tableInfo.map((c) => c.name); console.log('articletombstone columns:', colNames); assert.deepEqual(colNames, [ 'id', 'company_id', 'article_id', 'sku', 'name', 'taxrate', 'net', 'deleted_at', ]); // Setup company first console.log('2. Setting up base data...'); const googleAuthId = await store.google_auth.add('tombstone-tester@example.com'); const company = await store.company.create({ name: 'Tombstone Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId, }); await store.google_auth.setSelectedCompany(googleAuthId, company.id); // 3. Test CHECK constraint (article_id IS NOT NULL OR sku IS NOT NULL) console.log('3. Testing CHECK constraint...'); // Setting both article_id and sku to NULL should fail assert.throws(() => { provider.db.prepare(` INSERT INTO articletombstone (company_id, article_id, sku, name) VALUES (?, NULL, NULL, 'Test') `).run(company.id); }, /CHECK constraint failed/); // article_id present, sku null -> should succeed const r1 = provider.db.prepare(` INSERT INTO articletombstone (company_id, article_id, sku, name) VALUES (?, 999, NULL, 'Only ID') `).run(company.id); assert.ok(r1.lastInsertRowid); // sku present, article_id null -> should succeed const r2 = provider.db.prepare(` INSERT INTO articletombstone (company_id, article_id, sku, name) VALUES (?, NULL, 'SKU-ONLY', 'Only SKU') `).run(company.id); assert.ok(r2.lastInsertRowid); // Clean up manual test entries provider.db.prepare('DELETE FROM articletombstone').run(); // 4. Test article deletion flow console.log('4. Creating test article...'); const article = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Coffee Mug', }); // Set prices for the article await store.article.setPricesBySku(googleAuthId, article.sku, [ { price: 12.50, tax: 19, minmultiplier: 1 }, ]); // Verify article exists const detailBefore = await store.article.getBySku(googleAuthId, article.sku); assert.equal(detailBefore.article.name, 'Coffee Mug'); assert.equal(detailBefore.article.prices[0].net, 12.50); assert.equal(detailBefore.article.prices[0].taxRate, 19); // Delete the article console.log('4. Deleting article and checking tombstone creation...'); await store.article.delete(googleAuthId, article.sku); // Verify article is deleted await assert.rejects( () => store.article.getBySku(googleAuthId, article.sku), /Article not found/ ); // Verify tombstone record const tombstones = provider.db.prepare('SELECT * FROM articletombstone WHERE sku = ?').all(article.sku); assert.equal(tombstones.length, 1); const tomb = tombstones[0]; console.log('Created tombstone:', tomb); assert.equal(tomb.company_id, company.id); assert.equal(tomb.article_id, article.id); assert.equal(tomb.sku, article.sku); assert.equal(tomb.name, 'Coffee Mug'); assert.equal(tomb.net, 12.50); assert.equal(tomb.taxrate, 19); assert.ok(tomb.deleted_at); // 5. Test cascading deletion with parent and variants console.log('5. Testing parent with variant child deletion...'); const parent = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'Pants', }); const attrName = await store.attributename.create(googleAuthId, { name: 'Größe' }); await store.article.toggleParent(googleAuthId, parent.sku, { isParent: true, attributeNameId: attrName.id, }); const child = await store.article.addChild(googleAuthId, parent.sku, { value: 'M' }); await store.article.setPricesBySku(googleAuthId, child.article.sku, [ { price: 49.99, tax: 19, minmultiplier: 1 }, ]); // Delete child via removeChild with deleteArticle console.log('Deleting child via removeChild({ deleteArticle: true })...'); await store.article.removeChild(googleAuthId, parent.sku, child.article.sku, { deleteArticle: true }); // Add another child and delete parent with cascade: true const child2 = await store.article.addChild(googleAuthId, parent.sku, { value: 'L' }); console.log('Deleting parent with cascade: true...'); await store.article.delete(googleAuthId, parent.sku, { cascade: true }); const allTombstones = provider.db.prepare('SELECT * FROM articletombstone ORDER BY id ASC').all(); assert.equal(allTombstones.length, 4); // 1st tombstone: Coffee Mug assert.equal(allTombstones[0].name, 'Coffee Mug'); assert.equal(allTombstones[0].sku, '1'); assert.equal(allTombstones[0].net, 12.5); assert.equal(allTombstones[0].taxrate, 19); // 2nd tombstone: Pants - M (deleted via removeChild) assert.equal(allTombstones[1].name, 'Pants - M'); assert.equal(allTombstones[1].sku, child.article.sku); assert.equal(allTombstones[1].net, 49.99); assert.equal(allTombstones[1].taxrate, 19); // 3rd tombstone: Pants - L (child deleted in cascade) assert.equal(allTombstones[2].name, 'Pants - L'); assert.equal(allTombstones[2].sku, child2.article.sku); // 4th tombstone: Pants (parent deleted in cascade) assert.equal(allTombstones[3].name, 'Pants'); assert.equal(allTombstones[3].sku, parent.sku); // 6. Test posterminal maxknowntombstoneid column and updates console.log('6. Testing posterminal maxknowntombstoneid column...'); const posterminalCols = provider.db.pragma('table_info("posterminal")').map((c) => c.name); assert.ok(posterminalCols.includes('maxknowntombstoneid'), 'posterminal should have maxknowntombstoneid column'); const terminal = await store.posterminal.upsert(googleAuthId, { name: 'POS Terminal 1', pairingpin: '1234', }); assert.equal(terminal.maxknowntombstoneid, null); // Pair terminal await store.posterminal.pair(terminal.id, { token: 'tok-123', clientserial: 'SN-001', pairedAt: new Date().toISOString(), }); const foundByToken = await store.posterminal.findByToken('tok-123'); assert.equal(foundByToken.id, terminal.id); assert.equal(foundByToken.maxknowntombstoneid, null); // Update maxknowntombstoneid await store.posterminal.setMaxKnownTombstoneId(terminal.id, 4); const foundAfterUpdate = await store.posterminal.findByToken('tok-123'); assert.equal(foundAfterUpdate.maxknowntombstoneid, 4); console.log('✅ ALL TOMBSTONE TESTS PASSED'); } finally { try { rmSync(tempDir, { recursive: true, force: true }); } catch {} } } run().catch((err) => { console.error('Test failed:', err); process.exit(1); });