feat: remove deprecated refreshCategories endpoint and refactor matching logic for improved clarity and performance
Test Suite / test (24.15.0) (push) Has been cancelled

This commit is contained in:
Nils-Johan Gynther
2026-05-09 15:38:08 +02:00
parent 4d5c55f459
commit b09ea28ff0
3 changed files with 170 additions and 608 deletions
@@ -106,8 +106,12 @@ describe('ReceiptImportService test matrix', () => {
});
describe('alias fallback och prioritet', () => {
function makeContext(aliases: any[], products: any[], unitMappings: any[] = [], userId?: number) {
return { userId, aliases, products, unitMappings, categories, aiEnabled: false };
}
it('prioriterar user-alias före global alias för samma receiptName', async () => {
prismaMock.receiptAlias.findMany.mockResolvedValue([
const aliases = [
{
receiptName: 'mjolk 1l',
productId: 501,
@@ -130,32 +134,17 @@ describe('ReceiptImportService test matrix', () => {
categoryRef: { id: 30, name: 'Mejeri' },
},
},
]);
];
prismaMock.product.findMany.mockResolvedValue([]);
const context = makeContext(aliases, [], [], 77);
const result = await (service as any).matchAndEnrichReceiptItem({ rawName: 'MJOLK 1L' }, context);
const result = await (service as any).matchProducts(
[{ rawName: 'MJOLK 1L' }],
77,
);
expect(prismaMock.receiptAlias.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: {
OR: [
{ ownerId: 77, isGlobal: false },
{ isGlobal: true },
],
},
}),
);
expect(result[0].matchedProductId).toBe(501);
expect(result[0].matchedProductName).toBe('Mjolk user');
expect(result.matchedProductId).toBe(501);
expect(result.matchedProductName).toBe('Mjolk user');
});
it('använder global alias när user-alias saknas', async () => {
prismaMock.receiptAlias.findMany.mockResolvedValue([
const aliases = [
{
receiptName: 'snickers',
productId: 222,
@@ -167,24 +156,17 @@ describe('ReceiptImportService test matrix', () => {
categoryRef: { id: 53, name: 'Choklad' },
},
},
]);
];
prismaMock.product.findMany.mockResolvedValue([]);
const context = makeContext(aliases, [], [], 88);
const result = await (service as any).matchAndEnrichReceiptItem({ rawName: 'SNICKERS' }, context);
const result = await (service as any).matchProducts(
[{ rawName: 'SNICKERS' }],
88,
);
expect(result[0].matchedProductId).toBe(222);
expect(result[0].matchedProductName).toBe('Snickers');
expect(result.matchedProductId).toBe(222);
expect(result.matchedProductName).toBe('Snickers');
});
it('flöde: manuell korrigering lär alias och nästa import matchar direkt', async () => {
const aliases: any[] = [];
prismaMock.receiptAlias.findMany.mockImplementation(async () => aliases);
prismaMock.product.findMany.mockResolvedValue([
const products = [
{
id: 700,
name: 'Arla Mjolk 1l',
@@ -192,41 +174,39 @@ describe('ReceiptImportService test matrix', () => {
categoryId: 30,
categoryRef: { id: 30, name: 'Mejeri' },
},
]);
];
const first = await (service as any).matchProducts(
[{ rawName: 'ARLA MJOLK 1L' }],
42,
);
const contextNoAlias = makeContext([], products, [], 42);
const first = await (service as any).matchAndEnrichReceiptItem({ rawName: 'ARLA MJOLK 1L' }, contextNoAlias);
expect(first[0].matchedProductId).toBeUndefined();
expect(first[0].suggestedProductId).toBe(700);
expect(first.matchedProductId).toBeUndefined();
expect(first.suggestedProductId).toBe(700);
// Simulerar att användaren manuellt korrigerar och alias lärs in.
aliases.push({
receiptName: 'arla mjolk 1l',
productId: 700,
product: {
id: 700,
name: 'Arla Mjolk 1l',
canonicalName: 'Mjolk',
categoryId: 30,
categoryRef: { id: 30, name: 'Mejeri' },
const aliases = [
{
receiptName: 'arla mjolk 1l',
productId: 700,
product: {
id: 700,
name: 'Arla Mjolk 1l',
canonicalName: 'Mjolk',
categoryId: 30,
categoryRef: { id: 30, name: 'Mejeri' },
},
},
});
];
const second = await (service as any).matchProducts(
[{ rawName: 'ARLA MJOLK 1L' }],
42,
);
const contextWithAlias = makeContext(aliases, products, [], 42);
const second = await (service as any).matchAndEnrichReceiptItem({ rawName: 'ARLA MJOLK 1L' }, contextWithAlias);
expect(second[0].matchedProductId).toBe(700);
expect(second[0].matchedProductName).toBe('Mjolk');
expect(second[0].suggestedProductId).toBeUndefined();
expect(second.matchedProductId).toBe(700);
expect(second.matchedProductName).toBe('Mjolk');
expect(second.suggestedProductId).toBeUndefined();
});
it('använder inlärd enhetsmappning vid aliasträff', async () => {
prismaMock.receiptAlias.findMany.mockResolvedValue([
const aliases = [
{
receiptName: 'mjolk 1l',
productId: 501,
@@ -238,60 +218,51 @@ describe('ReceiptImportService test matrix', () => {
categoryRef: { id: 30, name: 'Mejeri' },
},
},
]);
];
prismaMock.unitMapping.findMany.mockResolvedValue([
{
productId: 501,
originalUnit: 'l',
preferredUnit: 'st',
},
]);
const unitMappings = [{ productId: 501, originalUnit: 'l', preferredUnit: 'st' }];
const context = makeContext(aliases, [], unitMappings, 77);
const result = await (service as any).matchAndEnrichReceiptItem({ rawName: 'MJOLK 1L', unit: 'L' }, context);
const result = await (service as any).matchProducts(
[{ rawName: 'MJOLK 1L', unit: 'L' }],
77,
);
expect(result[0].matchedProductId).toBe(501);
expect(result[0].unit).toBe('st');
expect(result.matchedProductId).toBe(501);
expect(result.unit).toBe('st');
});
});
describe('matchedVia', () => {
function makeContext(aliases: any[], products: any[], unitMappings: any[] = [], userId?: number) {
return { userId, aliases, products, unitMappings, categories, aiEnabled: false };
}
it('sätter matchedVia: alias vid aliasträff', async () => {
prismaMock.receiptAlias.findMany.mockResolvedValue([
const aliases = [
{
receiptName: 'snickers',
productId: 222,
product: { id: 222, name: 'Snickers', canonicalName: 'Snickers', categoryId: null, categoryRef: null },
},
]);
prismaMock.product.findMany.mockResolvedValue([]);
];
const context = makeContext(aliases, [], [], 10);
const result = await (service as any).matchAndEnrichReceiptItem({ rawName: 'SNICKERS' }, context);
const result = await (service as any).matchProducts([{ rawName: 'SNICKERS' }], 10);
expect(result[0].matchedVia).toBe('alias');
expect(result.matchedVia).toBe('alias');
});
it('sätter matchedVia: wordmatch vid ordbaserad matchning', async () => {
prismaMock.receiptAlias.findMany.mockResolvedValue([]);
prismaMock.product.findMany.mockResolvedValue([
const products = [
{ id: 300, name: 'Mjolk', canonicalName: 'Mjolk', categoryId: null, categoryRef: null },
]);
];
const context = makeContext([], products, [], 10);
const result = await (service as any).matchAndEnrichReceiptItem({ rawName: 'MJOLK 1L' }, context);
const result = await (service as any).matchProducts([{ rawName: 'MJOLK 1L' }], 10);
expect(result[0].matchedVia).toBe('wordmatch');
expect(result.matchedVia).toBe('wordmatch');
});
it('sätter matchedVia: none när ingen matchning finns', async () => {
prismaMock.receiptAlias.findMany.mockResolvedValue([]);
prismaMock.product.findMany.mockResolvedValue([]);
const context = makeContext([], [], [], 10);
const result = await (service as any).matchAndEnrichReceiptItem({ rawName: 'XYZXYZ' }, context);
const result = await (service as any).matchProducts([{ rawName: 'XYZXYZ' }], 10);
expect(result[0].matchedVia).toBe('none');
expect(result.matchedVia).toBe('none');
});
});
});
});