Files
electric-cherry/tests/unit/operation-context.test.ts
T

65 lines
3.4 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { currentOperationSource, withOperationSource } from '../../src/lib/server/hosts/operation-context.js';
import { trackActivity, workerActivity } from '../../src/lib/server/pipeline/activity.js';
describe('host operation source context', () => {
it('isolates overlapping asynchronous sources and restores the caller', async () => {
let release!: () => void;
const barrier = new Promise<void>(resolve => { release = resolve; });
const first = withOperationSource('GET /servers/[name]', async () => {
await barrier;
return currentOperationSource();
});
const second = withOperationSource('Status monitor', async () => {
release();
await Promise.resolve();
return currentOperationSource();
});
expect(currentOperationSource()).toBeNull();
expect(await Promise.all([first, second])).toEqual(['GET /servers/[name]', 'Status monitor']);
expect(currentOperationSource()).toBeNull();
});
it('lets explicit background work override incidental request context without leaking it', async () => {
await withOperationSource('POST /api/servers/[name]/refresh', async () => {
await withOperationSource('Status monitor', async () => {
await Promise.resolve();
expect(currentOperationSource()).toBe('Status monitor');
});
expect(currentOperationSource()).toBe('POST /api/servers/[name]/refresh');
expect(() => withOperationSource('Failing task', () => { throw new Error('failed'); })).toThrow('failed');
expect(currentOperationSource()).toBe('POST /api/servers/[name]/refresh');
});
expect(currentOperationSource()).toBeNull();
});
it('gives background activity a source while preserving its initiating source through nested activity', async () => {
await trackActivity('context-test', 'Reconciling campaigns', async () => {
await Promise.resolve();
expect(currentOperationSource()).toBe('Reconciling campaigns');
await trackActivity('context-test-child', 'Waiting for files.read', async () => {
expect(currentOperationSource()).toBe('Reconciling campaigns');
});
});
await withOperationSource('POST /api/servers/[name]/restart', () =>
trackActivity('context-test-request', 'Processing provisioning requests', async () => {
expect(currentOperationSource()).toBe('POST /api/servers/[name]/restart');
}));
expect(currentOperationSource()).toBeNull();
expect(workerActivity()).toEqual([]);
});
it('restores context and clears activity when asynchronous work rejects', async () => {
await withOperationSource('Manual maintenance check', async () => {
await expect(trackActivity('context-test-error', 'Waiting for files.read', async () => {
await Promise.resolve();
expect(currentOperationSource()).toBe('Manual maintenance check');
throw new Error('unavailable');
})).rejects.toThrow('unavailable');
expect(currentOperationSource()).toBe('Manual maintenance check');
});
expect(currentOperationSource()).toBeNull();
expect(workerActivity()).toEqual([]);
});
});