The store keeps a Menu Set's state in the directory layout (drafts/ versus published/) and its metadata in a manifest, with publishing as a single directory rename. Editing is allowed on Drafts only, so a published menu can never change under a customer. Stages 2 and 3 of docs/plan.md, with tests.
67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
import { mkdir, mkdtemp } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { createHash } from 'node:crypto';
|
|
import { SetStore, type ImagePipeline } from '../../src/storage/sets.ts';
|
|
|
|
export const SECTIONS = ['bar', 'food'] as const;
|
|
|
|
/** Deterministic stand-in for sharp, so storage tests need no image work. */
|
|
export class FakePipeline implements ImagePipeline {
|
|
calls = 0;
|
|
|
|
async process(data: Buffer, _originalName: string) {
|
|
this.calls += 1;
|
|
const hash = createHash('sha256').update(data).digest('hex').slice(0, 8);
|
|
return {
|
|
sourceExt: 'jpg' as const,
|
|
hash,
|
|
width: 3000,
|
|
height: 2000,
|
|
original: data,
|
|
webp: Buffer.from(`webp:${hash}`),
|
|
jpeg: Buffer.from(`jpeg:${hash}`),
|
|
thumb: Buffer.from(`thumb:${hash}`),
|
|
};
|
|
}
|
|
}
|
|
|
|
export function testClock(start = '2026-01-01T00:00:00.000Z') {
|
|
let time = Date.parse(start);
|
|
return {
|
|
now: () => new Date(time),
|
|
advance: (ms: number) => {
|
|
time += ms;
|
|
},
|
|
set: (iso: string) => {
|
|
time = Date.parse(iso);
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function tempDir(prefix: string): Promise<string> {
|
|
const base = path.join(process.cwd(), '.tmp');
|
|
await mkdir(base, { recursive: true });
|
|
return mkdtemp(path.join(base, `${prefix}-`));
|
|
}
|
|
|
|
export function makeStore(
|
|
dataDir: string,
|
|
options: { clock?: ReturnType<typeof testClock>; maxPagesPerSet?: number } = {},
|
|
) {
|
|
const pipeline = new FakePipeline();
|
|
const clock = options.clock ?? testClock();
|
|
const store = new SetStore({
|
|
dataDir,
|
|
pipeline,
|
|
sections: [...SECTIONS],
|
|
maxPagesPerSet: options.maxPagesPerSet ?? 40,
|
|
timeZone: 'Asia/Kolkata',
|
|
now: clock.now,
|
|
});
|
|
return { store, pipeline, clock };
|
|
}
|
|
|
|
export function pngBytes(label: string): Buffer {
|
|
return Buffer.from(`fake-image:${label}`);
|
|
}
|