-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathrole-hook-resolver.test.ts
More file actions
98 lines (86 loc) · 3.83 KB
/
Copy pathrole-hook-resolver.test.ts
File metadata and controls
98 lines (86 loc) · 3.83 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
// Class-scoped regression guard for #350: the orchestrator's roleHook
// dispatch must treat declared-but-unprovided hooks as advisory (skip),
// not as a hard registration-time failure. This is what allows the
// PlaywrightEmitter to declare `roleHooks: ['deployment']` once and
// remain usable from configs that don't ship a deploymentGateway role
// bundle (e.g. camunda-hub, where 0/9 operations are multipart).
//
// Each `test(...)` block pins one named property of the contract
// documented in `materializer/src/roleHookResolver.ts`.
import {
_resetRegistriesForTests,
type EmitterStrategy,
type RoleHookProvider,
registerRoleHookProvider,
} from '@camunda8/emitter-sdk';
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
import {
RoleHookConflictError,
resolveRoleExtras,
} from '../../materializer/src/roleHookResolver.ts';
const CTX = { repoRoot: '/tmp/never-read', configName: 'unit-test' };
function emitter(roleHooks: string[]): Pick<EmitterStrategy, 'id' | 'roleHooks'> {
return { id: 'stub', roleHooks };
}
function provider(
hook: string,
role: string,
extras: Record<string, unknown> | undefined,
): RoleHookProvider {
return {
hook,
role,
async compute() {
return extras;
},
};
}
beforeEach(() => {
_resetRegistriesForTests();
});
afterEach(() => {
_resetRegistriesForTests();
});
describe('resolveRoleExtras (#350 — advisory roleHooks)', () => {
test('declared hook without a registered provider is skipped (no throw)', async () => {
// The pre-#350 orchestrator called process.exit(1) here. The contract
// now is: the emitter's declaration is advisory, and configs that
// don't dispatch any operation to the role need not vendor a provider.
const result = await resolveRoleExtras(emitter(['deployment']), CTX);
expect(result).toBeUndefined();
});
test('declared hook with a registered provider populates roleExtras under provider.role', async () => {
registerRoleHookProvider(provider('deployment', 'deploymentGateway', { extracts: ['x'] }));
const result = await resolveRoleExtras(emitter(['deployment']), CTX);
expect(result).toBeDefined();
expect(result?.get('deploymentGateway')).toEqual({ extracts: ['x'] });
});
test('provider returning undefined is treated as "nothing to contribute" — role omitted', async () => {
registerRoleHookProvider(provider('deployment', 'deploymentGateway', undefined));
const result = await resolveRoleExtras(emitter(['deployment']), CTX);
expect(result).toBeUndefined();
});
test('mixed: one missing provider + one populated provider — populated wins, missing skipped', async () => {
// Pins the class-scope guarantee: a single missing provider does not
// poison the loop. Other hooks in the same emitter still run.
registerRoleHookProvider(provider('cleanup', 'cleanupRole', { token: 'abc' }));
const result = await resolveRoleExtras(emitter(['deployment', 'cleanup']), CTX);
expect(result?.size).toBe(1);
expect(result?.get('cleanupRole')).toEqual({ token: 'abc' });
expect(result?.has('deploymentGateway')).toBe(false);
});
test('two providers populating the same role throw RoleHookConflictError (no silent overwrite)', async () => {
registerRoleHookProvider(provider('hookA', 'sharedRole', { a: 1 }));
registerRoleHookProvider(provider('hookB', 'sharedRole', { b: 2 }));
await expect(resolveRoleExtras(emitter(['hookA', 'hookB']), CTX)).rejects.toThrowError(
RoleHookConflictError,
);
await expect(resolveRoleExtras(emitter(['hookA', 'hookB']), CTX)).rejects.toThrowError(
/attempted to overwrite extras for role "sharedRole"/,
);
});
test('emitter without roleHooks returns undefined (no allocation, no work)', async () => {
const result = await resolveRoleExtras({ id: 'stub' }, CTX);
expect(result).toBeUndefined();
});
});