-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathserve-automation-summary.test.ts
More file actions
100 lines (90 loc) · 4.34 KB
/
Copy pathserve-automation-summary.test.ts
File metadata and controls
100 lines (90 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
//
// Startup-banner automation summary (2026-07-17 third-party eval).
//
// Flow registration and trigger binding happen entirely inside serve's
// boot-quiet stdout window, so the automation engine's own logs never reach
// the terminal — a project whose flows silently failed to arm looked exactly
// like one whose flows armed fine. `collectAutomationSummary` gathers the
// live binding facts after stdout is restored so the banner can answer
// "did my flows actually arm?" — including the three silent author mistakes:
// engine not enabled, trigger not registered, and objectName mismatch.
import { describe, it, expect } from 'vitest';
import { collectAutomationSummary } from './serve.js';
type FlowState = {
name: string;
enabled: boolean;
bound: boolean;
status?: string;
triggerType?: string;
object?: string;
};
function fakeKernel(services: Record<string, unknown>) {
return {
getService(name: string) {
if (!(name in services)) throw new Error(`Service '${name}' not found`);
return services[name];
},
};
}
function fakeAutomation(states: FlowState[], triggerTypes: string[] = ['record_change']) {
return {
getFlowRuntimeStates: () => states,
getRegisteredTriggerTypes: () => triggerTypes,
getTriggerBindingAudit: () =>
states
.filter((s) => s.enabled && !s.bound && s.triggerType)
.map((s) => ({
flowName: s.name,
triggerType: s.triggerType!,
reason: `no '${s.triggerType}' trigger is registered`,
})),
};
}
describe('collectAutomationSummary', () => {
it('flags declared flows when the automation engine is not enabled at all', () => {
const summary = collectAutomationSummary(fakeKernel({}), 2);
expect(summary).toMatchObject({ enabled: false, declaredFlowCount: 2 });
});
it('returns undefined when there is nothing automation-related to show', () => {
expect(collectAutomationSummary(fakeKernel({}), 0)).toBeUndefined();
});
it('reports bound/unbound counts and surfaces the unbound audit', () => {
const automation = fakeAutomation([
{ name: 'wired', enabled: true, bound: true, status: 'active', triggerType: 'record_change', object: 'task' },
{ name: 'orphan', enabled: true, bound: false, status: 'active', triggerType: 'record_change', object: 'task' },
]);
const summary = collectAutomationSummary(fakeKernel({ automation }), 2)!;
expect(summary.enabled).toBe(true);
expect(summary.flowCount).toBe(2);
expect(summary.boundCount).toBe(1);
expect(summary.unbound).toHaveLength(1);
expect(summary.unbound[0].flowName).toBe('orphan');
});
it('flags a bound record-change flow whose target object is unknown (dead binding)', () => {
const automation = fakeAutomation([
{ name: 'dead', enabled: true, bound: true, status: 'active', triggerType: 'record_change', object: 'candidate' },
{ name: 'alive', enabled: true, bound: true, status: 'active', triggerType: 'record_change', object: 'eval_app_candidate' },
]);
const ql = { getObject: (n: string) => (n === 'eval_app_candidate' ? { name: n } : undefined) };
const summary = collectAutomationSummary(fakeKernel({ automation, objectql: ql }), 2)!;
expect(summary.unknownObject).toEqual([{ flowName: 'dead', object: 'candidate' }]);
});
it('counts enabled draft flows (draft still fires — make it visible)', () => {
const automation = fakeAutomation([
{ name: 'd1', enabled: true, bound: true, status: 'draft', triggerType: 'record_change', object: 'task' },
{ name: 'implicit', enabled: true, bound: true, status: undefined, triggerType: 'record_change', object: 'task' },
{ name: 'a1', enabled: true, bound: true, status: 'active', triggerType: 'record_change', object: 'task' },
]);
const summary = collectAutomationSummary(fakeKernel({ automation }), 3)!;
expect(summary.draftCount).toBe(2);
});
it('degrades gracefully on an older engine without the audit APIs', () => {
const automation = { getFlowRuntimeStates: () => [{ name: 'x', enabled: true, bound: true }] };
const summary = collectAutomationSummary(fakeKernel({ automation }), 1)!;
expect(summary.enabled).toBe(true);
expect(summary.flowCount).toBe(1);
expect(summary.unbound).toEqual([]);
expect(summary.triggerTypes).toEqual([]);
});
});