Files
dashlibstorage/scripts/test-category-delete-recursive.js
2026-10-08 16:11:44 +02:00

215 lines
7.7 KiB
JavaScript

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);
});