203 lines
5.9 KiB
TypeScript
203 lines
5.9 KiB
TypeScript
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||
|
|
import { pauseQueue } from '../src/proxymodel/pause-queue.js';
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
// Reset state between tests
|
||
|
|
pauseQueue.setPaused(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('PauseQueue', () => {
|
||
|
|
it('returns transformed content immediately when not paused', async () => {
|
||
|
|
const result = await pauseQueue.enqueue({
|
||
|
|
sessionId: 's1',
|
||
|
|
projectName: 'proj',
|
||
|
|
contentType: 'toolResult',
|
||
|
|
sourceName: 'test/tool',
|
||
|
|
original: 'raw content',
|
||
|
|
transformed: 'processed content',
|
||
|
|
});
|
||
|
|
expect(result).toBe('processed content');
|
||
|
|
expect(pauseQueue.size).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('holds content when paused', async () => {
|
||
|
|
pauseQueue.setPaused(true);
|
||
|
|
|
||
|
|
let resolved = false;
|
||
|
|
const promise = pauseQueue.enqueue({
|
||
|
|
sessionId: 's1',
|
||
|
|
projectName: 'proj',
|
||
|
|
contentType: 'toolResult',
|
||
|
|
sourceName: 'test/tool',
|
||
|
|
original: 'raw',
|
||
|
|
transformed: 'processed',
|
||
|
|
}).then((r) => { resolved = true; return r; });
|
||
|
|
|
||
|
|
// Should not resolve immediately
|
||
|
|
await new Promise((r) => setTimeout(r, 50));
|
||
|
|
expect(resolved).toBe(false);
|
||
|
|
expect(pauseQueue.size).toBe(1);
|
||
|
|
|
||
|
|
// Release it
|
||
|
|
const items = pauseQueue.getItems();
|
||
|
|
expect(items).toHaveLength(1);
|
||
|
|
pauseQueue.releaseOne(items[0]!.id);
|
||
|
|
|
||
|
|
const result = await promise;
|
||
|
|
expect(result).toBe('processed');
|
||
|
|
expect(resolved).toBe(true);
|
||
|
|
expect(pauseQueue.size).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('editAndRelease sends edited content', async () => {
|
||
|
|
pauseQueue.setPaused(true);
|
||
|
|
|
||
|
|
const promise = pauseQueue.enqueue({
|
||
|
|
sessionId: 's1',
|
||
|
|
projectName: 'proj',
|
||
|
|
contentType: 'toolResult',
|
||
|
|
sourceName: 'test/tool',
|
||
|
|
original: 'raw',
|
||
|
|
transformed: 'auto-processed',
|
||
|
|
});
|
||
|
|
|
||
|
|
const items = pauseQueue.getItems();
|
||
|
|
pauseQueue.editAndRelease(items[0]!.id, 'manually-edited');
|
||
|
|
|
||
|
|
const result = await promise;
|
||
|
|
expect(result).toBe('manually-edited');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('dropOne sends empty content', async () => {
|
||
|
|
pauseQueue.setPaused(true);
|
||
|
|
|
||
|
|
const promise = pauseQueue.enqueue({
|
||
|
|
sessionId: 's1',
|
||
|
|
projectName: 'proj',
|
||
|
|
contentType: 'toolResult',
|
||
|
|
sourceName: 'test/tool',
|
||
|
|
original: 'raw',
|
||
|
|
transformed: 'processed',
|
||
|
|
});
|
||
|
|
|
||
|
|
const items = pauseQueue.getItems();
|
||
|
|
pauseQueue.dropOne(items[0]!.id);
|
||
|
|
|
||
|
|
const result = await promise;
|
||
|
|
expect(result).toBe('');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('setPaused(false) releases all queued items', async () => {
|
||
|
|
pauseQueue.setPaused(true);
|
||
|
|
|
||
|
|
const p1 = pauseQueue.enqueue({
|
||
|
|
sessionId: 's1', projectName: 'proj', contentType: 'toolResult',
|
||
|
|
sourceName: 'tool1', original: 'r1', transformed: 't1',
|
||
|
|
});
|
||
|
|
const p2 = pauseQueue.enqueue({
|
||
|
|
sessionId: 's2', projectName: 'proj', contentType: 'toolResult',
|
||
|
|
sourceName: 'tool2', original: 'r2', transformed: 't2',
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(pauseQueue.size).toBe(2);
|
||
|
|
|
||
|
|
// Resume releases all
|
||
|
|
pauseQueue.setPaused(false);
|
||
|
|
|
||
|
|
const [r1, r2] = await Promise.all([p1, p2]);
|
||
|
|
expect(r1).toBe('t1');
|
||
|
|
expect(r2).toBe('t2');
|
||
|
|
expect(pauseQueue.size).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('releaseAll releases all items and returns count', async () => {
|
||
|
|
pauseQueue.setPaused(true);
|
||
|
|
|
||
|
|
const p1 = pauseQueue.enqueue({
|
||
|
|
sessionId: 's1', projectName: 'proj', contentType: 'toolResult',
|
||
|
|
sourceName: 'tool1', original: 'r1', transformed: 't1',
|
||
|
|
});
|
||
|
|
const p2 = pauseQueue.enqueue({
|
||
|
|
sessionId: 's1', projectName: 'proj', contentType: 'toolResult',
|
||
|
|
sourceName: 'tool2', original: 'r2', transformed: 't2',
|
||
|
|
});
|
||
|
|
|
||
|
|
const count = pauseQueue.releaseAll();
|
||
|
|
expect(count).toBe(2);
|
||
|
|
|
||
|
|
const [r1, r2] = await Promise.all([p1, p2]);
|
||
|
|
expect(r1).toBe('t1');
|
||
|
|
expect(r2).toBe('t2');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns false for operations on non-existent IDs', () => {
|
||
|
|
expect(pauseQueue.releaseOne('nonexistent')).toBe(false);
|
||
|
|
expect(pauseQueue.editAndRelease('nonexistent', 'x')).toBe(false);
|
||
|
|
expect(pauseQueue.dropOne('nonexistent')).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('getItems strips internal resolve function', async () => {
|
||
|
|
pauseQueue.setPaused(true);
|
||
|
|
|
||
|
|
pauseQueue.enqueue({
|
||
|
|
sessionId: 's1', projectName: 'proj', contentType: 'toolResult',
|
||
|
|
sourceName: 'tool', original: 'raw', transformed: 'processed',
|
||
|
|
});
|
||
|
|
|
||
|
|
const items = pauseQueue.getItems();
|
||
|
|
expect(items).toHaveLength(1);
|
||
|
|
const item = items[0]!;
|
||
|
|
expect(item.id).toBeTruthy();
|
||
|
|
expect(item.sessionId).toBe('s1');
|
||
|
|
expect(item.projectName).toBe('proj');
|
||
|
|
expect(item.contentType).toBe('toolResult');
|
||
|
|
expect(item.sourceName).toBe('tool');
|
||
|
|
expect(item.original).toBe('raw');
|
||
|
|
expect(item.transformed).toBe('processed');
|
||
|
|
expect(item.timestamp).toBeGreaterThan(0);
|
||
|
|
// Must not expose resolve
|
||
|
|
expect((item as Record<string, unknown>)['resolve']).toBeUndefined();
|
||
|
|
|
||
|
|
// Cleanup
|
||
|
|
pauseQueue.releaseAll();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('notifies subscribers on enqueue and release', async () => {
|
||
|
|
const notifications: number[] = [];
|
||
|
|
const unsub = pauseQueue.subscribe((items) => notifications.push(items.length));
|
||
|
|
|
||
|
|
pauseQueue.setPaused(true);
|
||
|
|
|
||
|
|
pauseQueue.enqueue({
|
||
|
|
sessionId: 's1', projectName: 'proj', contentType: 'toolResult',
|
||
|
|
sourceName: 'tool', original: 'r', transformed: 't',
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(notifications).toContain(1);
|
||
|
|
|
||
|
|
pauseQueue.releaseAll();
|
||
|
|
expect(notifications).toContain(0);
|
||
|
|
|
||
|
|
unsub();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('each item gets a unique ID', async () => {
|
||
|
|
pauseQueue.setPaused(true);
|
||
|
|
|
||
|
|
pauseQueue.enqueue({
|
||
|
|
sessionId: 's1', projectName: 'proj', contentType: 'toolResult',
|
||
|
|
sourceName: 'tool1', original: 'r1', transformed: 't1',
|
||
|
|
});
|
||
|
|
pauseQueue.enqueue({
|
||
|
|
sessionId: 's1', projectName: 'proj', contentType: 'toolResult',
|
||
|
|
sourceName: 'tool2', original: 'r2', transformed: 't2',
|
||
|
|
});
|
||
|
|
|
||
|
|
const items = pauseQueue.getItems();
|
||
|
|
expect(items).toHaveLength(2);
|
||
|
|
expect(items[0]!.id).not.toBe(items[1]!.id);
|
||
|
|
|
||
|
|
pauseQueue.releaseAll();
|
||
|
|
});
|
||
|
|
});
|