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 Variant Flow: Next-Free SKU, Parentsort Reclaim, and Duplicate Protection ---'); const tempDir = mkdtempSync(join(tmpdir(), 'test-variants-')); 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('variant-tester@example.com'); const company = await store.company.create({ name: 'Variant Corp', iso639: 'de', taxrate: 19, reducedtaxrate: 7, googleAuthId, }); await store.google_auth.setSelectedCompany(googleAuthId, company.id); // Create parent article const parent = await store.article.createUnderParent({ googleAuthId, parentId: company.rootCategoryId, name: 'T-Shirt', }); const attrName = await store.attributename.create(googleAuthId, { name: 'Farbe' }); await store.article.toggleParent(googleAuthId, parent.sku, { isParent: true, attributeNameId: attrName.id, }); console.log('1. Add initial children (Rot, Blau, Grün)...'); const child1 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Rot' }); const child2 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Blau' }); const child3 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Grün' }); assert.equal(child1.article.sku, `${parent.sku}-1`); assert.equal(child2.article.sku, `${parent.sku}-2`); assert.equal(child3.article.sku, `${parent.sku}-3`); let list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 3); assert.deepEqual(list.map((c) => c.parentsort), [0, 1, 2]); console.log('2. Testing Duplicate Variant Protection (Defect 3)...'); await assert.rejects( async () => { await store.article.addChild(googleAuthId, parent.sku, { value: 'Rot' }); }, (err) => { assert.match(err.message, /Variant with this attribute value already exists/); return true; }, 'Expected duplicate variant attribute value to be rejected' ); console.log('3. Unlink middle child (child2 / SKU-2) -> leaves orphan holding SKU-2 (Bug A & B)...'); await store.article.removeChild(googleAuthId, parent.sku, child2.article.sku, { deleteArticle: false, }); // Check that remaining children's parentsort was recompacted list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 2); assert.deepEqual(list.map((c) => c.sku), [child1.article.sku, child3.article.sku]); assert.deepEqual(list.map((c) => c.parentsort), [0, 1]); // Check orphan still exists in DB const orphan = await store.article.getBySku(googleAuthId, child2.article.sku); assert.equal(orphan.article.parentArticleId, null); assert.equal(orphan.article.sku, `${parent.sku}-2`); console.log('4. Add new child (Gelb) -> SKU must NOT collide with orphan (Bug A: next free index)...'); const child4 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Gelb' }); // Since SKU-1, SKU-2 (orphan), SKU-3 exist, next free index is 4 assert.equal(child4.article.sku, `${parent.sku}-4`); list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 3); assert.deepEqual(list.map((c) => c.parentsort), [0, 1, 2]); console.log('5. Testing gaps in parentsort (Evidence: parent with child at parentsort: 1)...'); // Unlink child3 and child4 so only child1 remains await store.article.removeChild(googleAuthId, parent.sku, child3.article.sku, { deleteArticle: true }); await store.article.removeChild(googleAuthId, parent.sku, child4.article.sku, { deleteArticle: true }); // Manually force child1's parentsort: 1 to simulate pre-existing gap state (like parent 94) await provider.query( `UPDATE article SET parentsort = 1 WHERE sku = @sku`, { sku: child1.article.sku } ); list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 1); assert.equal(list[0].parentsort, 1); // exactly like parent 94 // Now adding a variant MUST NOT hit UNIQUE constraint failed (409) console.log('Adding variant to parent with child at parentsort: 1...'); const child5 = await store.article.addChild(googleAuthId, parent.sku, { value: 'Schwarz' }); assert.ok(child5); list = await store.article.listChildren(googleAuthId, parent.sku); assert.equal(list.length, 2); // Parentsort should have picked 0 (next free non-negative integer) assert.deepEqual(list.map((c) => c.parentsort), [0, 1]); console.log('6. Testing rejecting sku when creating via addChild...'); await assert.rejects( async () => { await store.article.addChild(googleAuthId, parent.sku, { sku: 'SOME-CUSTOM-SKU', value: 'Lila', }); }, (err) => { assert.match(err.message, /sku is not allowed when creating a child article/); return true; }, 'Expected sku on addChild to be rejected' ); console.log('7. Testing attaching existing orphan article (SKU-2) via attachChild...'); const reconnected = await store.article.attachChild(googleAuthId, parent.sku, { sku: child2.article.sku, value: 'Blau', }); assert.equal(reconnected.article.sku, child2.article.sku); assert.equal(reconnected.article.parentArticleId, parent.id); console.log('8. Testing idempotent attachChild on same parent...'); const idempotentResult = await store.article.attachChild(googleAuthId, parent.sku, { sku: child2.article.sku, value: 'Blau', }); assert.equal(idempotentResult.article.sku, child2.article.sku); console.log('✅ ALL VARIANT FLOW TESTS PASSED'); } finally { try { rmSync(tempDir, { recursive: true, force: true }); } catch {} } } run().catch((err) => { console.error('Test failed:', err); process.exit(1); });