Add inactive item fallback for dashboard change feed lookups
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-04-12 22:46:28 +02:00
parent 79f4138b95
commit 6ca09cdf1f
4 changed files with 98 additions and 4 deletions
+30
View File
@@ -16,6 +16,7 @@ vi.mock('../../src/api/client.js', () => ({
const {
adjustStockEntry,
applyItemUpsert,
getStockEntry,
listGroupedStockEntries,
listKitchenChanges,
listStockEntries,
@@ -125,6 +126,35 @@ describe('api/stock', () => {
);
});
it('getStockEntry fetches item without allow_inactive by default', async () => {
apiRequestMock.mockResolvedValueOnce({ uuid_b64: 'item-1', name: 'Milk' });
const result = await getStockEntry({ config: { database: 'db' } }, 'item-1');
expect(result).toEqual({ uuid_b64: 'item-1', name: 'Milk' });
expect(apiRequestMock).toHaveBeenCalledWith(
{ config: { database: 'db' } },
'kitchen/items/item-1',
);
});
it('getStockEntry forwards allow_inactive when requested', async () => {
apiRequestMock.mockResolvedValueOnce({ uuid_b64: 'item-2', active: false });
const result = await getStockEntry(
{ config: { database: 'db' } },
'item-2',
{ allowInactive: true },
);
expect(result).toEqual({ uuid_b64: 'item-2', active: false });
expect(apiRequestMock).toHaveBeenCalledWith(
{ config: { database: 'db' } },
'kitchen/items/item-2',
{ query: { allow_inactive: 1 } },
);
});
it('listKitchenChanges returns normalized changes payload', async () => {
apiRequestMock.mockResolvedValueOnce({
since: 'cursor-1',
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';
const listKitchenChangesMock = vi.fn();
const getStockEntryMock = vi.fn();
@@ -16,6 +16,12 @@ vi.mock('../../../src/api/locations.js', () => ({
const { dashboardPageData, renderDashboardPage } = await import('../../../src/features/dashboard/dashboard-page.js');
describe('features/dashboard/dashboard-page', () => {
beforeEach(() => {
listKitchenChangesMock.mockReset();
getStockEntryMock.mockReset();
fetchLocationsMock.mockReset();
});
it('renders dashboard with recent changes section', () => {
const html = renderDashboardPage();
expect(html).toContain('Recent changes');
@@ -107,6 +113,50 @@ describe('features/dashboard/dashboard-page', () => {
expect(getStockEntryMock).toHaveBeenCalledWith(expect.anything(), 'item-uuid-1');
});
it('retries stock event item lookup with allowInactive after 404', async () => {
listKitchenChangesMock.mockResolvedValueOnce({
since: null,
nextCursor: null,
changes: [{
type: 'stock',
action: 'upsert',
timestamp: '2026-04-10T10:00:00Z',
stock: {
item_uuid_b64: 'item-uuid-2',
quantity: 1,
uom_symbol: 'pcs',
},
}],
});
getStockEntryMock
.mockRejectedValueOnce(Object.assign(new Error('Not found'), { status: 404 }))
.mockResolvedValueOnce({
uuid_b64: 'item-uuid-2',
name: 'Archived pasta',
stock_type: 'measured',
});
fetchLocationsMock.mockResolvedValueOnce({ flat: [] });
const store = {
isConnected: true,
setActiveKitchen: vi.fn(),
addAlert: vi.fn(),
};
const data = dashboardPageData(store);
await data.refreshChanges();
expect(getStockEntryMock).toHaveBeenNthCalledWith(1, store, 'item-uuid-2');
expect(getStockEntryMock).toHaveBeenNthCalledWith(
2,
store,
'item-uuid-2',
{ allowInactive: true },
);
expect(data.changeHeadline(data.recentChanges[0])).toBe('Stock saved: Archived pasta');
expect(data.changeStateLine(data.recentChanges[0])).toContain('Quantity: 1 pcs');
});
it('keeps empty state when API returns no changes', async () => {
listKitchenChangesMock.mockResolvedValueOnce({
since: null,