Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/modules/temporal/activities/nodes/base.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
export interface NodeExecutionResult {
success: boolean;
nextNodeId?: string;
nextNodeHandle?: string; // Branch routing: the edge.sourceHandle the executor should follow (conditional/split nodes)
error?: string;
variables?: Record<string, any>;
executionTime?: number;
Expand Down
55 changes: 55 additions & 0 deletions src/modules/temporal/activities/nodes/split.node.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { SplitNode } from './split.node';

describe('SplitNode (EVO-1828)', () => {
let node: SplitNode;

const make = (
variants?: Array<{ id: string; name: string; percentage: number }>,
contactId = 'contact-1',
) => ({
nodeId: 'split-1',
contactId,
sessionId: 'session-1',
nodeData: { variants },
});

beforeEach(() => {
node = new SplitNode();
const logger = (node as any).logger;
for (const m of ['log', 'debug', 'warn', 'error']) {
jest.spyOn(logger, m).mockImplementation(() => undefined);
}
});

it('returns a nextNodeHandle pointing at the selected variant (was dropped before the fix)', async () => {
const res = await node.execute(make());
expect(res.success).toBe(true);
expect(res.nextNodeHandle).toMatch(/^split-variant-(variant-a|variant-b)$/);
});

it('routes every contact to variant A when A is 100%', async () => {
const variants = [
{ id: 'variant-a', name: 'A', percentage: 100 },
{ id: 'variant-b', name: 'B', percentage: 0 },
];
for (const c of ['c1', 'c2', 'whatever']) {
const res = await node.execute(make(variants, c));
expect(res.nextNodeHandle).toBe('split-variant-variant-a');
}
});

it('routes every contact to variant B when B is 100%', async () => {
const variants = [
{ id: 'variant-a', name: 'A', percentage: 0 },
{ id: 'variant-b', name: 'B', percentage: 100 },
];
const res = await node.execute(make(variants, 'c1'));
expect(res.nextNodeHandle).toBe('split-variant-variant-b');
});

it('selects deterministically per contactId', async () => {
const a = await node.execute(make(undefined, 'same-contact'));
const b = await node.execute(make(undefined, 'same-contact'));
expect(a.nextNodeHandle).toBe(b.nextNodeHandle);
});
});
16 changes: 11 additions & 5 deletions src/modules/temporal/activities/nodes/split.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,17 @@ export class SplitNode extends BaseNode {
};
})
.then(({ result, executionTime }) => {
return this.createSuccessResult(input, executionTime, {
[`node_${input.nodeId}_selected_variant`]: result.selectedVariant,
[`node_${input.nodeId}_variant_id`]: result.variantId,
[`node_${input.nodeId}_random_value`]: result.random,
});
// EVO-1828: carry the routing decision so the executor branches to the
// selected variant's edge (sourceHandle = split-variant-<id>) instead of
// always falling back to the first edge.
return {
...this.createSuccessResult(input, executionTime, {
[`node_${input.nodeId}_selected_variant`]: result.selectedVariant,
[`node_${input.nodeId}_variant_id`]: result.variantId,
[`node_${input.nodeId}_random_value`]: result.random,
}),
nextNodeHandle: result.nextNodeHandle,
};
})
.catch((error) => {
const executionTime = Date.now();
Expand Down
Loading