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 category recursive deletion ---'); const tempDir = mkdtempSync(join(tmpdir(), 'test-cat-del-rec-')); 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 { const googleAuthId = await store.google_auth.add('tester@example.com'); const company = await store.company.create({ name: 'Recursive Test Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId, }); await store.google_auth.setSelectedCompany(googleAuthId, company.id); const rootId = company.rootCategoryId; // 1. Refuse to delete root category, even with recursive: true console.log('1. Testing refusal to delete root category...'); await assert.rejects( () => store.category.delete({ googleAuthId, id: rootId, recursive: true }), /Cannot delete root category/ ); // 2. Build category tree: // Root // ├── OutsideCat (for preserved articles) // └── MainCat (to be recursively deleted) // ├── SubCat1 // │ └── SubSubCat1 // └── SubCat2 console.log('2. Building category tree...'); const outsideCat = await store.category.createUnderParent({ googleAuthId, parentId: rootId, name: 'Outside Category', }); const mainCat = await store.category.createUnderParent({ googleAuthId, parentId: rootId, name: 'Main Category', }); const subCat1 = await store.category.createUnderParent({ googleAuthId, parentId: mainCat.id, name: 'Sub Category 1', }); const subSubCat1 = await store.category.createUnderParent({ googleAuthId, parentId: subCat1.id, name: 'Sub Sub Category 1', }); const subCat2 = await store.category.createUnderParent({ googleAuthId, parentId: mainCat.id, name: 'Sub Category 2', }); // 3. Test non-recursive delete guards console.log('3. Testing non-recursive delete guards...'); await assert.rejects( () => store.category.delete({ googleAuthId, id: mainCat.id, recursive: false }), /Category has subcategories/ ); // 4. Create articles: // Article 1: Only in SubSubCat1 -> Should be deleted // Article 2: In SubCat1 AND OutsideCat -> Should survive, removed from SubCat1 // Article 3: Parent in SubCat2 with a variant child -> Both should be deleted // Article 4: Outside article in OutsideCat -> Untouched console.log('4. Creating articles in categories...'); const artOnlySub = await store.article.createUnderParent({ googleAuthId, parentId: subSubCat1.id, name: 'Only SubSubCat Article', }); const artMulti = await store.article.createUnderParent({ googleAuthId, parentId: subCat1.id, name: 'Multi Category Article', }); // Add outsideCat to artMulti await store.article.setCategoryIdsBySku(googleAuthId, artMulti.sku, [subCat1.id, outsideCat.id]); const attrName = await store.attributename.create(googleAuthId, { name: 'Size' }); const artParent = await store.article.createUnderParent({ googleAuthId, parentId: subCat2.id, name: 'Parent Article In Sub2', }); await store.article.toggleParent(googleAuthId, artParent.sku, { isParent: true, attributeNameId: attrName.id, }); const artChild = await store.article.addChild(googleAuthId, artParent.sku, { value: 'M', }); const artOutside = await store.article.createUnderParent({ googleAuthId, parentId: outsideCat.id, name: 'Purely Outside Article', }); // 5. Test POS terminal assignment unlinking on recursive delete console.log('5. Setting up POS terminal assignment...'); const posTerminal = await store.posterminal.upsert(googleAuthId, { name: 'POS Register 1', pairingpin: '1234', }); await store.posterminal.setOn(googleAuthId, posTerminal.id, subCat2.id); await store.posterminal.setOn(googleAuthId, posTerminal.id, outsideCat.id); const posCatsBefore = provider.db.prepare( 'SELECT category_id FROM posterminalcategory WHERE posterminal_id = ?' ).all(posTerminal.id).map(r => r.category_id); assert.ok(posCatsBefore.includes(subCat2.id)); assert.ok(posCatsBefore.includes(outsideCat.id)); // 6. Execute recursive deletion of mainCat console.log('6. Executing recursive category delete on mainCat...'); await store.category.delete({ googleAuthId, id: mainCat.id, recursive: true, }); // 7. Verify category tree state console.log('7. Verifying category tree...'); const catsRemaining = provider.db.prepare('SELECT id FROM category').all().map(r => r.id); assert.ok(catsRemaining.includes(rootId)); assert.ok(catsRemaining.includes(outsideCat.id)); assert.ok(!catsRemaining.includes(mainCat.id)); assert.ok(!catsRemaining.includes(subCat1.id)); assert.ok(!catsRemaining.includes(subSubCat1.id)); assert.ok(!catsRemaining.includes(subCat2.id)); // 8. Verify POS terminal assignment was unlinked for deleted category console.log('8. Verifying POS terminal categories...'); const posCatsAfter = provider.db.prepare( 'SELECT category_id FROM posterminalcategory WHERE posterminal_id = ?' ).all(posTerminal.id).map(r => r.category_id); assert.ok(!posCatsAfter.includes(subCat2.id)); assert.ok(posCatsAfter.includes(outsideCat.id)); // 9. Verify article states: console.log('9. Verifying article states...'); // artOnlySub: deleted await assert.rejects( () => store.article.getBySku(googleAuthId, artOnlySub.sku), /Article not found/ ); // tombstone for artOnlySub exists const t1 = provider.db.prepare('SELECT * FROM articletombstone WHERE sku = ?').all(artOnlySub.sku); assert.equal(t1.length, 1); // artParent and its child artChild: both deleted await assert.rejects( () => store.article.getBySku(googleAuthId, artParent.sku), /Article not found/ ); await assert.rejects( () => store.article.getBySku(googleAuthId, artChild.article.sku), /Article not found/ ); const tParent = provider.db.prepare('SELECT * FROM articletombstone WHERE sku = ?').all(artParent.sku); const tChild = provider.db.prepare('SELECT * FROM articletombstone WHERE sku = ?').all(artChild.article.sku); assert.equal(tParent.length, 1); assert.equal(tChild.length, 1); // artMulti: continues to exist! const artMultiDetail = await store.article.getBySku(googleAuthId, artMulti.sku); assert.equal(artMultiDetail.article.sku, artMulti.sku); // artMulti categoryarticle now ONLY contains outsideCat const artMultiCats = provider.db.prepare( 'SELECT category_id FROM categoryarticle WHERE article_id = ?' ).all(artMultiDetail.article.id).map(r => r.category_id); assert.deepEqual(artMultiCats, [outsideCat.id]); // artOutside: completely untouched const artOutsideDetail = await store.article.getBySku(googleAuthId, artOutside.sku); assert.equal(artOutsideDetail.article.sku, artOutside.sku); console.log('✅ ALL RECURSIVE CATEGORY DELETE TESTS PASSED'); } finally { try { rmSync(tempDir, { recursive: true, force: true }); } catch {} } } run().catch((err) => { console.error('Test failed:', err); process.exit(1); });