This commit is contained in:
seb
2026-10-04 21:22:05 +02:00
parent 0268583ae3
commit 4718cb5726
48 changed files with 758 additions and 758 deletions

View File

@@ -52,7 +52,7 @@ export async function fillDatabase(store, options = {}) {
secondaryCompany: null,
categories: [],
articles: [],
fatherArticles: [],
parentArticles: [],
childArticles: [],
customergroups: [],
entities: [],
@@ -326,7 +326,7 @@ export async function fillDatabase(store, options = {}) {
const numArticles = counts.articles;
for (let i = 1; i <= numArticles; i++) {
const targetCat = ctx.categories[i % ctx.categories.length];
const isFather = i % 4 === 1;
const isParent = i % 4 === 1;
const art = await store.article.createUnderParent({
googleAuthId: ctx.googleAuthId,
parentId: targetCat.id,
@@ -365,12 +365,12 @@ export async function fillDatabase(store, options = {}) {
ctx.categories[(i + 1) % ctx.categories.length].id,
]);
// V/K Father-Child hierarchy
if (isFather) {
ctx.fatherArticles.push(art);
} else if (ctx.fatherArticles.length > 0) {
const parentFather = ctx.fatherArticles[ctx.fatherArticles.length - 1];
await store.article.setFatherBySku(ctx.googleAuthId, sku, parentFather.sku);
// V/K Parent-Child hierarchy
if (isParent) {
ctx.parentArticles.push(art);
} else if (ctx.parentArticles.length > 0) {
const parentParent = ctx.parentArticles[ctx.parentArticles.length - 1];
await store.article.setParentBySku(ctx.googleAuthId, sku, parentParent.sku);
ctx.childArticles.push(art);
}
@@ -426,7 +426,7 @@ export async function fillDatabase(store, options = {}) {
brand: 'BenchBrand Pro',
net: '45,50',
tax: 19,
fatherSku: `IMP-${Date.now()}-1`,
parentSku: `IMP-${Date.now()}-1`,
},
];
await store.article.importRows({

View File

@@ -6,7 +6,7 @@ import LibDashStore from '../src/index.js';
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
async function run() {
console.log('--- Testing Father History & AttributeValue Shapes ---');
console.log('--- Testing Parent History & AttributeValue Shapes ---');
const tempDir = mkdtempSync(join(tmpdir(), 'test-hist-attr-'));
const dbFile = join(tempDir, 'test.db');
@@ -31,11 +31,11 @@ async function run() {
});
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
console.log('1. Testing setFatherBySku history logging...');
const father = await store.article.createUnderParent({
console.log('1. Testing setParentBySku history logging...');
const parent = await store.article.createUnderParent({
googleAuthId,
parentId: company.rootCategoryId,
name: 'Father Shirt',
name: 'Parent Shirt',
});
const child = await store.article.createUnderParent({
googleAuthId,
@@ -44,19 +44,19 @@ async function run() {
});
// Check history before linking
let fatherHist = await store.article.getHistoryBySku(googleAuthId, father.sku);
assert.equal(fatherHist.length, 1);
assert.match(fatherHist[0].text, /angelegt/);
let parentHist = await store.article.getHistoryBySku(googleAuthId, parent.sku);
assert.equal(parentHist.length, 1);
assert.match(parentHist[0].text, /angelegt/);
// Link child to father
await store.article.setFatherBySku(googleAuthId, child.sku, father.sku);
// Link child to parent
await store.article.setParentBySku(googleAuthId, child.sku, parent.sku);
// Father history must now contain the conversion entry and the child entry!
fatherHist = await store.article.getHistoryBySku(googleAuthId, father.sku);
const hasConversion = fatherHist.some((h) => h.text.includes('Artikel zu Vaterartikel konvertiert'));
const hasVariantAdded = fatherHist.some((h) => h.text.includes('hinzugefügt'));
assert.ok(hasConversion, `Father history missing conversion row: ${JSON.stringify(fatherHist)}`);
assert.ok(hasVariantAdded, `Father history missing variant added row: ${JSON.stringify(fatherHist)}`);
// Parent history must now contain the conversion entry and the child entry!
parentHist = await store.article.getHistoryBySku(googleAuthId, parent.sku);
const hasConversion = parentHist.some((h) => h.text.includes('Artikel zu Vaterartikel konvertiert'));
const hasVariantAdded = parentHist.some((h) => h.text.includes('hinzugefügt'));
assert.ok(hasConversion, `Parent history missing conversion row: ${JSON.stringify(parentHist)}`);
assert.ok(hasVariantAdded, `Parent history missing variant added row: ${JSON.stringify(parentHist)}`);
// Child history must contain the assignment entry
const childHist = await store.article.getHistoryBySku(googleAuthId, child.sku);
@@ -67,15 +67,15 @@ async function run() {
const importRes = await store.article.importRows({
googleAuthId,
rows: [
{ sku: 'IMP-FATHER', name: 'Imported Father', parentId: company.rootCategoryId },
{ sku: 'IMP-CHILD', name: 'Imported Child', parentId: company.rootCategoryId, fatherSku: 'IMP-FATHER' },
{ sku: 'IMP-PARENT', name: 'Imported Parent', parentId: company.rootCategoryId },
{ sku: 'IMP-CHILD', name: 'Imported Child', parentId: company.rootCategoryId, parentSku: 'IMP-PARENT' },
],
});
assert.equal(importRes.errors.length, 0);
const impFatherHist = await store.article.getHistoryBySku(googleAuthId, 'IMP-FATHER');
const impHasConversion = impFatherHist.some((h) => h.text.includes('Artikel zu Vaterartikel konvertiert'));
assert.ok(impHasConversion, `Imported father missing conversion row: ${JSON.stringify(impFatherHist)}`);
const impParentHist = await store.article.getHistoryBySku(googleAuthId, 'IMP-PARENT');
const impHasConversion = impParentHist.some((h) => h.text.includes('Artikel zu Vaterartikel konvertiert'));
assert.ok(impHasConversion, `Imported parent missing conversion row: ${JSON.stringify(impParentHist)}`);
console.log('3. Testing flexible attributevalue request shapes in setByArticleSku...');
const attrName = await store.attributename.create(googleAuthId, { name: 'Farbe' });

View File

@@ -119,31 +119,31 @@ async function run() {
assert.equal(tomb.taxrate, 19);
assert.ok(tomb.deleted_at);
// 5. Test cascading deletion with father and variants
console.log('5. Testing father with variant child deletion...');
const father = await store.article.createUnderParent({
// 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.toggleFather(googleAuthId, father.sku, {
isFather: true,
await store.article.toggleParent(googleAuthId, parent.sku, {
isParent: true,
attributeNameId: attrName.id,
});
const child = await store.article.addChild(googleAuthId, father.sku, { value: 'M' });
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, father.sku, child.article.sku, { deleteArticle: true });
await store.article.removeChild(googleAuthId, parent.sku, child.article.sku, { deleteArticle: true });
// Add another child and delete father with cascade: true
const child2 = await store.article.addChild(googleAuthId, father.sku, { value: 'L' });
console.log('Deleting father with cascade: true...');
await store.article.delete(googleAuthId, father.sku, { cascade: 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);
@@ -164,9 +164,9 @@ async function run() {
assert.equal(allTombstones[2].name, 'Pants - L');
assert.equal(allTombstones[2].sku, child2.article.sku);
// 4th tombstone: Pants (father deleted in cascade)
// 4th tombstone: Pants (parent deleted in cascade)
assert.equal(allTombstones[3].name, 'Pants');
assert.equal(allTombstones[3].sku, father.sku);
assert.equal(allTombstones[3].sku, parent.sku);
// 6. Test posterminal maxknowntombstoneid column and updates
console.log('6. Testing posterminal maxknowntombstoneid column...');

View File

@@ -35,65 +35,65 @@ async function run() {
const art1 = await store.article.createUnderParent({
googleAuthId,
parentId: company.rootCategoryId,
name: 'Root Father Shirt',
name: 'Root Parent Shirt',
});
const art1Res = await store.article.getBySku(googleAuthId, art1.sku);
assert.equal(art1Res.article.slug, 'root-father-shirt', `Expected slug 'root-father-shirt', got '${art1Res.article.slug}'`);
assert.equal(art1Res.article.slug, 'root-parent-shirt', `Expected slug 'root-parent-shirt', got '${art1Res.article.slug}'`);
const art2 = await store.article.createUnderParent({
googleAuthId,
parentId: company.rootCategoryId,
name: 'Root Father Shirt',
name: 'Root Parent Shirt',
});
const art2Res = await store.article.getBySku(googleAuthId, art2.sku);
assert.equal(art2Res.article.slug, 'root-father-shirt-2', `Expected slug 'root-father-shirt-2', got '${art2Res.article.slug}'`);
assert.equal(art2Res.article.slug, 'root-parent-shirt-2', `Expected slug 'root-parent-shirt-2', got '${art2Res.article.slug}'`);
const art3 = await store.article.createUnderParent({
googleAuthId,
parentId: company.rootCategoryId,
name: 'Root Father Shirt',
name: 'Root Parent Shirt',
});
const art3Res = await store.article.getBySku(googleAuthId, art3.sku);
assert.equal(art3Res.article.slug, 'root-father-shirt-3', `Expected slug 'root-father-shirt-3', got '${art3Res.article.slug}'`);
assert.equal(art3Res.article.slug, 'root-parent-shirt-3', `Expected slug 'root-parent-shirt-3', got '${art3Res.article.slug}'`);
console.log('2. Testing updateBySku self-collision and renaming...');
// Update art2 without changing name
await store.article.updateBySku(googleAuthId, art2.sku, { unitamount: 2 });
let updated2 = await store.article.getBySku(googleAuthId, art2.sku);
assert.equal(updated2.article.slug, 'root-father-shirt-2', `Expected unchanged slug, got '${updated2.article.slug}'`);
assert.equal(updated2.article.slug, 'root-parent-shirt-2', `Expected unchanged slug, got '${updated2.article.slug}'`);
// Update art2 with same name
await store.article.updateBySku(googleAuthId, art2.sku, { name: 'Root Father Shirt' });
await store.article.updateBySku(googleAuthId, art2.sku, { name: 'Root Parent Shirt' });
updated2 = await store.article.getBySku(googleAuthId, art2.sku);
assert.equal(updated2.article.slug, 'root-father-shirt-2', `Expected slug to remain 'root-father-shirt-2', got '${updated2.article.slug}'`);
assert.equal(updated2.article.slug, 'root-parent-shirt-2', `Expected slug to remain 'root-parent-shirt-2', got '${updated2.article.slug}'`);
// Rename art2 to something else
await store.article.updateBySku(googleAuthId, art2.sku, { name: 'Unique Shirt' });
updated2 = await store.article.getBySku(googleAuthId, art2.sku);
assert.equal(updated2.article.slug, 'unique-shirt', `Expected slug 'unique-shirt', got '${updated2.article.slug}'`);
// Rename art3 to 'Root Father Shirt' again: should take root-father-shirt-2 now that it is free
await store.article.updateBySku(googleAuthId, art3.sku, { name: 'Root Father Shirt' });
// Rename art3 to 'Root Parent Shirt' again: should take root-parent-shirt-2 now that it is free
await store.article.updateBySku(googleAuthId, art3.sku, { name: 'Root Parent Shirt' });
const updated3 = await store.article.getBySku(googleAuthId, art3.sku);
assert.equal(updated3.article.slug, 'root-father-shirt-2', `Expected slug to fill gap 'root-father-shirt-2', got '${updated3.article.slug}'`);
assert.equal(updated3.article.slug, 'root-parent-shirt-2', `Expected slug to fill gap 'root-parent-shirt-2', got '${updated3.article.slug}'`);
console.log('3. Testing addChild duplicate slugs...');
const attrName = await store.attributename.create(googleAuthId, { name: 'Größe' });
await store.article.toggleFather(googleAuthId, art1.sku, {
isFather: true,
await store.article.toggleParent(googleAuthId, art1.sku, {
isParent: true,
attributeNameId: attrName.id,
});
const child1 = await store.article.addChild(googleAuthId, art1.sku, {
value: 'M',
});
assert.equal(child1.article.slug, 'root-father-shirt-m', `Expected child slug 'root-father-shirt-m', got '${child1.article.slug}'`);
assert.equal(child1.article.slug, 'root-parent-shirt-m', `Expected child slug 'root-parent-shirt-m', got '${child1.article.slug}'`);
// Add another child with name that yields same slug
const child2 = await store.article.addChild(googleAuthId, art1.sku, {
value: 'L',
name: 'Root Father Shirt - M',
name: 'Root Parent Shirt - M',
});
assert.equal(child2.article.slug, 'root-father-shirt-m-2', `Expected child slug 'root-father-shirt-m-2', got '${child2.article.slug}'`);
assert.equal(child2.article.slug, 'root-parent-shirt-m-2', `Expected child slug 'root-parent-shirt-m-2', got '${child2.article.slug}'`);
console.log('4. Testing importRows duplicate slugs in same batch...');
const importRes = await store.article.importRows({
@@ -128,10 +128,10 @@ async function run() {
const artCompany2 = await store.article.createUnderParent({
googleAuthId,
parentId: company2.rootCategoryId,
name: 'Root Father Shirt',
name: 'Root Parent Shirt',
});
const artCompany2Detail = await store.article.getBySku(googleAuthId, artCompany2.sku);
assert.equal(artCompany2Detail.article.slug, 'root-father-shirt', `Expected 'root-father-shirt' in company 2, got '${artCompany2Detail.article.slug}'`);
assert.equal(artCompany2Detail.article.slug, 'root-parent-shirt', `Expected 'root-parent-shirt' in company 2, got '${artCompany2Detail.article.slug}'`);
console.log('✅ ALL SLUG TESTS PASSED');
} finally {

View File

@@ -6,7 +6,7 @@ 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, Fathersort Reclaim, and Duplicate Protection ---');
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');
@@ -31,35 +31,35 @@ async function run() {
});
await store.google_auth.setSelectedCompany(googleAuthId, company.id);
// Create father article
const father = await store.article.createUnderParent({
// 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.toggleFather(googleAuthId, father.sku, {
isFather: true,
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, father.sku, { value: 'Rot' });
const child2 = await store.article.addChild(googleAuthId, father.sku, { value: 'Blau' });
const child3 = await store.article.addChild(googleAuthId, father.sku, { value: '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, `${father.sku}-1`);
assert.equal(child2.article.sku, `${father.sku}-2`);
assert.equal(child3.article.sku, `${father.sku}-3`);
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, father.sku);
let list = await store.article.listChildren(googleAuthId, parent.sku);
assert.equal(list.length, 3);
assert.deepEqual(list.map((c) => c.fathersort), [0, 1, 2]);
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, father.sku, { value: 'Rot' });
await store.article.addChild(googleAuthId, parent.sku, { value: 'Rot' });
},
(err) => {
assert.match(err.message, /Variant with this attribute value already exists/);
@@ -69,57 +69,57 @@ async function run() {
);
console.log('3. Unlink middle child (child2 / SKU-2) -> leaves orphan holding SKU-2 (Bug A & B)...');
await store.article.removeChild(googleAuthId, father.sku, child2.article.sku, {
await store.article.removeChild(googleAuthId, parent.sku, child2.article.sku, {
deleteArticle: false,
});
// Check that remaining children's fathersort was recompacted
list = await store.article.listChildren(googleAuthId, father.sku);
// 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.fathersort), [0, 1]);
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.fatherArticleId, null);
assert.equal(orphan.article.sku, `${father.sku}-2`);
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, father.sku, { value: 'Gelb' });
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, `${father.sku}-4`);
assert.equal(child4.article.sku, `${parent.sku}-4`);
list = await store.article.listChildren(googleAuthId, father.sku);
list = await store.article.listChildren(googleAuthId, parent.sku);
assert.equal(list.length, 3);
assert.deepEqual(list.map((c) => c.fathersort), [0, 1, 2]);
assert.deepEqual(list.map((c) => c.parentsort), [0, 1, 2]);
console.log('5. Testing gaps in fathersort (Evidence: father with child at fathersort: 1)...');
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, father.sku, child3.article.sku, { deleteArticle: true });
await store.article.removeChild(googleAuthId, father.sku, child4.article.sku, { deleteArticle: true });
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 fathersort: 1 to simulate pre-existing gap state (like father 94)
// Manually force child1's parentsort: 1 to simulate pre-existing gap state (like parent 94)
await provider.query(
`UPDATE article SET fathersort = 1 WHERE sku = @sku`,
`UPDATE article SET parentsort = 1 WHERE sku = @sku`,
{ sku: child1.article.sku }
);
list = await store.article.listChildren(googleAuthId, father.sku);
list = await store.article.listChildren(googleAuthId, parent.sku);
assert.equal(list.length, 1);
assert.equal(list[0].fathersort, 1); // exactly like father 94
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 father with child at fathersort: 1...');
const child5 = await store.article.addChild(googleAuthId, father.sku, { value: 'Schwarz' });
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, father.sku);
list = await store.article.listChildren(googleAuthId, parent.sku);
assert.equal(list.length, 2);
// Fathersort should have picked 0 (next free non-negative integer)
assert.deepEqual(list.map((c) => c.fathersort), [0, 1]);
// 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, father.sku, {
await store.article.addChild(googleAuthId, parent.sku, {
sku: 'SOME-CUSTOM-SKU',
value: 'Lila',
});
@@ -132,15 +132,15 @@ async function run() {
);
console.log('7. Testing attaching existing orphan article (SKU-2) via attachChild...');
const reconnected = await store.article.attachChild(googleAuthId, father.sku, {
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.fatherArticleId, father.id);
assert.equal(reconnected.article.parentArticleId, parent.id);
console.log('8. Testing idempotent attachChild on same father...');
const idempotentResult = await store.article.attachChild(googleAuthId, father.sku, {
console.log('8. Testing idempotent attachChild on same parent...');
const idempotentResult = await store.article.attachChild(googleAuthId, parent.sku, {
sku: child2.article.sku,
value: 'Blau',
});