Files
taskbuffer/test/test.12.labels-and-events.ts

250 lines
6.9 KiB
TypeScript

import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as taskbuffer from '../ts/index.js';
import type { ITaskEvent } from '../ts/index.js';
// ─── Labels ───
tap.test('should accept labels in constructor', async () => {
const task = new taskbuffer.Task({
name: 'labelled-task',
taskFunction: async () => 'ok',
labels: { userId: 'u1', tenantId: 't1' },
});
expect(task.labels).toEqual({ userId: 'u1', tenantId: 't1' });
});
tap.test('should default labels to empty object', async () => {
const task = new taskbuffer.Task({
name: 'no-labels-task',
taskFunction: async () => 'ok',
});
expect(task.labels).toEqual({});
});
tap.test('setLabel / getLabel / removeLabel / hasLabel should work', async () => {
const task = new taskbuffer.Task({
name: 'label-helpers-task',
taskFunction: async () => 'ok',
});
task.setLabel('env', 'prod');
expect(task.getLabel('env')).toEqual('prod');
expect(task.hasLabel('env')).toBeTrue();
expect(task.hasLabel('env', 'prod')).toBeTrue();
expect(task.hasLabel('env', 'dev')).toBeFalse();
expect(task.hasLabel('missing')).toBeFalse();
const removed = task.removeLabel('env');
expect(removed).toBeTrue();
expect(task.getLabel('env')).toBeUndefined();
const removedAgain = task.removeLabel('env');
expect(removedAgain).toBeFalse();
});
tap.test('getMetadata() should include labels', async () => {
const task = new taskbuffer.Task({
name: 'metadata-labels-task',
taskFunction: async () => 'ok',
labels: { region: 'eu' },
});
const meta = task.getMetadata();
expect(meta.labels).toEqual({ region: 'eu' });
// Returned labels should be a copy
meta.labels!['region'] = 'us';
expect(task.labels['region']).toEqual('eu');
});
tap.test('TaskManager.getTasksByLabel should filter correctly', async () => {
const manager = new taskbuffer.TaskManager();
const t1 = new taskbuffer.Task({
name: 'label-filter-1',
taskFunction: async () => 'ok',
labels: { userId: 'alice' },
});
const t2 = new taskbuffer.Task({
name: 'label-filter-2',
taskFunction: async () => 'ok',
labels: { userId: 'bob' },
});
const t3 = new taskbuffer.Task({
name: 'label-filter-3',
taskFunction: async () => 'ok',
labels: { userId: 'alice' },
});
manager.addTask(t1);
manager.addTask(t2);
manager.addTask(t3);
const aliceTasks = manager.getTasksByLabel('userId', 'alice');
expect(aliceTasks.length).toEqual(2);
expect(aliceTasks.map((t) => t.name).sort()).toEqual(['label-filter-1', 'label-filter-3']);
const bobMeta = manager.getTasksMetadataByLabel('userId', 'bob');
expect(bobMeta.length).toEqual(1);
expect(bobMeta[0].name).toEqual('label-filter-2');
await manager.stop();
});
// ─── Events ───
tap.test('should emit started + completed on successful trigger', async () => {
const events: ITaskEvent[] = [];
const task = new taskbuffer.Task({
name: 'event-success-task',
taskFunction: async () => 'ok',
});
task.eventSubject.subscribe((e) => events.push(e));
await task.trigger();
expect(events.length).toEqual(2);
expect(events[0].type).toEqual('started');
expect(events[1].type).toEqual('completed');
expect(events[0].task.name).toEqual('event-success-task');
expect(typeof events[0].timestamp).toEqual('number');
});
tap.test('should emit step events on notifyStep', async () => {
const steps = [
{ name: 'build', description: 'Build artifacts', percentage: 50 },
{ name: 'deploy', description: 'Deploy to prod', percentage: 50 },
] as const;
const events: ITaskEvent[] = [];
const task = new taskbuffer.Task({
name: 'step-event-task',
steps,
taskFunction: async () => {
task.notifyStep('build');
task.notifyStep('deploy');
return 'done';
},
});
task.eventSubject.subscribe((e) => events.push(e));
await task.trigger();
const stepEvents = events.filter((e) => e.type === 'step');
expect(stepEvents.length).toEqual(2);
expect(stepEvents[0].stepName).toEqual('build');
expect(stepEvents[1].stepName).toEqual('deploy');
});
tap.test('should emit started + failed on error', async () => {
const events: ITaskEvent[] = [];
const task = new taskbuffer.Task({
name: 'event-fail-task',
taskFunction: async () => {
throw new Error('boom');
},
});
task.eventSubject.subscribe((e) => events.push(e));
try {
await task.trigger();
} catch {
// expected
}
expect(events.length).toEqual(2);
expect(events[0].type).toEqual('started');
expect(events[1].type).toEqual('failed');
expect(events[1].error).toEqual('boom');
});
tap.test('should emit failed via done.then path when catchErrors is true', async () => {
const events: ITaskEvent[] = [];
const task = new taskbuffer.Task({
name: 'event-catch-fail-task',
catchErrors: true,
taskFunction: async () => {
throw new Error('swallowed');
},
});
task.eventSubject.subscribe((e) => events.push(e));
await task.trigger();
const types = events.map((e) => e.type);
expect(types).toContain('started');
expect(types).toContain('failed');
});
tap.test('TaskManager.taskSubject should aggregate events from added tasks', async () => {
const manager = new taskbuffer.TaskManager();
const events: ITaskEvent[] = [];
const t1 = new taskbuffer.Task({
name: 'agg-task-1',
taskFunction: async () => 'a',
});
const t2 = new taskbuffer.Task({
name: 'agg-task-2',
taskFunction: async () => 'b',
});
manager.addTask(t1);
manager.addTask(t2);
manager.taskSubject.subscribe((e) => events.push(e));
await t1.trigger();
await t2.trigger();
const names = [...new Set(events.map((e) => e.task.name))];
expect(names.sort()).toEqual(['agg-task-1', 'agg-task-2']);
expect(events.filter((e) => e.type === 'started').length).toEqual(2);
expect(events.filter((e) => e.type === 'completed').length).toEqual(2);
await manager.stop();
});
tap.test('events should stop after removeTask', async () => {
const manager = new taskbuffer.TaskManager();
const events: ITaskEvent[] = [];
const task = new taskbuffer.Task({
name: 'removable-event-task',
taskFunction: async () => 'ok',
});
manager.addTask(task);
manager.taskSubject.subscribe((e) => events.push(e));
await task.trigger();
const countBefore = events.length;
expect(countBefore).toBeGreaterThan(0);
manager.removeTask(task);
// Trigger again — events should NOT appear on manager subject
await task.trigger();
expect(events.length).toEqual(countBefore);
await manager.stop();
});
tap.test('event metadata snapshots should include correct labels', async () => {
const events: ITaskEvent[] = [];
const task = new taskbuffer.Task({
name: 'labelled-event-task',
taskFunction: async () => 'ok',
labels: { team: 'platform' },
});
task.eventSubject.subscribe((e) => events.push(e));
await task.trigger();
for (const e of events) {
expect(e.task.labels).toEqual({ team: 'platform' });
}
});
export default tap.start();