65 lines
3.4 KiB
TypeScript
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([]);
|
|
});
|
|
});
|