Files
TREK/server/tests/unit/nest/assistant/assistant-render.registry.test.ts
T
Maurice 8b93898b73 feat(assistant): models that read images see the live page
The Studio draws the page on request and uploads it once, each provider gets
the image where it expects it, and a refusal falls back to the text
description. No image is stored.
2026-10-22 21:34:15 +00:00

82 lines
3.7 KiB
TypeScript

/**
* InMemoryAssistantRenderRegistry (ASTRND-*): a turn waits for the page image
* it asked the Studio for; the upload is handed over only to the turn of the
* same user, journey and thread, for the page and version asked for, and only
* while the turn still waits.
*/
import {
InMemoryAssistantRenderRegistry,
type PageImage,
type RenderWait,
} from '../../../../src/nest/assistant/assistant-render.registry';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const REQUEST: RenderWait = {
userId: 7,
journeyId: 12,
threadId: 4,
requestId: '0b6f7c1e-3a52-4d8e-9a7b-2f1e0c9d8a76',
spreadId: 'sp-2',
bookVersion: 3,
};
const PAGE = { spreadId: 'sp-2', bookVersion: 3 };
const IMAGE: PageImage = { mimeType: 'image/jpeg', data: Buffer.from('jpeg'), width: 1280, height: 640 };
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
describe('InMemoryAssistantRenderRegistry', () => {
it('ASTRND-001: the upload of the page asked for reaches the waiting turn once', async () => {
const registry = new InMemoryAssistantRenderRegistry();
const waiting = registry.wait(REQUEST, new AbortController().signal, 15_000);
expect(registry.deliver(REQUEST, PAGE, IMAGE)).toBe('taken');
await expect(waiting).resolves.toBe(IMAGE);
// Taken: a second upload for the same request finds nobody waiting.
expect(registry.deliver(REQUEST, PAGE, IMAGE)).toBe('unknown');
});
it('ASTRND-002: a turn stops waiting after the timeout, and an upload after it is too late', async () => {
const registry = new InMemoryAssistantRenderRegistry();
const waiting = registry.wait(REQUEST, new AbortController().signal, 15_000);
await vi.advanceTimersByTimeAsync(15_000);
await expect(waiting).resolves.toBeNull();
expect(registry.deliver(REQUEST, PAGE, IMAGE)).toBe('unknown');
});
it('ASTRND-003: a stopped turn stops waiting, also when it was stopped before it asked', async () => {
const registry = new InMemoryAssistantRenderRegistry();
const stop = new AbortController();
const waiting = registry.wait(REQUEST, stop.signal, 15_000);
stop.abort();
await expect(waiting).resolves.toBeNull();
expect(registry.deliver(REQUEST, PAGE, IMAGE)).toBe('unknown');
await expect(registry.wait(REQUEST, stop.signal, 15_000)).resolves.toBeNull();
expect(registry.deliver(REQUEST, PAGE, IMAGE)).toBe('unknown');
});
it('ASTRND-004: another user, journey or thread cannot answer the request; the turn goes on waiting', async () => {
const registry = new InMemoryAssistantRenderRegistry();
const waiting = registry.wait(REQUEST, new AbortController().signal, 15_000);
expect(registry.deliver({ ...REQUEST, userId: 8 }, PAGE, IMAGE)).toBe('unknown');
expect(registry.deliver({ ...REQUEST, journeyId: 13 }, PAGE, IMAGE)).toBe('unknown');
expect(registry.deliver({ ...REQUEST, threadId: 5 }, PAGE, IMAGE)).toBe('unknown');
expect(registry.deliver({ ...REQUEST, requestId: 'other' }, PAGE, IMAGE)).toBe('unknown');
expect(registry.deliver(REQUEST, PAGE, IMAGE)).toBe('taken');
await expect(waiting).resolves.toBe(IMAGE);
});
it('ASTRND-005: an image of another page or version is refused, and the right one still comes through', async () => {
const registry = new InMemoryAssistantRenderRegistry();
const waiting = registry.wait(REQUEST, new AbortController().signal, 15_000);
expect(registry.deliver(REQUEST, { ...PAGE, spreadId: 'sp-3' }, IMAGE)).toBe('mismatch');
expect(registry.deliver(REQUEST, { ...PAGE, bookVersion: 2 }, IMAGE)).toBe('mismatch');
expect(registry.deliver(REQUEST, PAGE, IMAGE)).toBe('taken');
await expect(waiting).resolves.toBe(IMAGE);
});
});