Skip to content

Commit 5b9478f

Browse files
committed
Adding a test for extractWorkflowStateData returning an undefined workflow state object
1 parent 08331e6 commit 5b9478f

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

packages/mcp-workflow/tests/tools/orchestrator/orchestratorTool.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,79 @@ Gather user input for platform and projectName.
10461046
expect(threadId).toBe(existingThreadId);
10471047
});
10481048

1049+
it('should generate a new thread when extractWorkflowStateData returns undefined', async () => {
1050+
// Define a schema where sessionState is truly optional (no .default())
1051+
const OPTIONAL_STATE_SCHEMA = z.object({
1052+
payload: z.unknown().optional(),
1053+
sessionState: z
1054+
.object({
1055+
thread_id: z.string(),
1056+
})
1057+
.optional(),
1058+
});
1059+
1060+
type OptionalStateInput = z.infer<typeof OPTIONAL_STATE_SCHEMA>;
1061+
1062+
class OptionalStateOrchestrator extends OrchestratorTool<typeof OPTIONAL_STATE_SCHEMA> {
1063+
constructor(
1064+
mcpServer: McpServer,
1065+
config: OrchestratorConfig<typeof OPTIONAL_STATE_SCHEMA>
1066+
) {
1067+
super(mcpServer, config);
1068+
}
1069+
1070+
protected extractUserInput(input: OptionalStateInput): unknown | undefined {
1071+
return input.payload;
1072+
}
1073+
1074+
protected extractWorkflowStateData(
1075+
input: OptionalStateInput
1076+
): { thread_id: string } | undefined {
1077+
return input.sessionState;
1078+
}
1079+
}
1080+
1081+
const workflow = new StateGraph(TestState)
1082+
.addNode('start', (_state: State) => ({
1083+
messages: ['Started workflow'],
1084+
}))
1085+
.addEdge(START, 'start')
1086+
.addEdge('start', END);
1087+
1088+
const config: OrchestratorConfig<typeof OPTIONAL_STATE_SCHEMA> = {
1089+
toolId: 'optional-state-orchestrator',
1090+
title: 'Optional State Orchestrator',
1091+
description: 'Tests undefined extractWorkflowStateData',
1092+
workflow,
1093+
inputSchema: OPTIONAL_STATE_SCHEMA,
1094+
stateManager: new WorkflowStateManager({ environment: 'test' }),
1095+
logger: mockLogger,
1096+
};
1097+
1098+
const orchestrator = new OptionalStateOrchestrator(server, config);
1099+
1100+
// Omit sessionState entirely — extractWorkflowStateData should return undefined
1101+
const result = await orchestrator.handleRequest(
1102+
{ payload: { test: 'data' } },
1103+
createMockExtra()
1104+
);
1105+
1106+
expect(result).toBeDefined();
1107+
expect(result.content).toBeDefined();
1108+
1109+
// Should have started a new workflow (not a resumption)
1110+
expect(mockLogger.hasLoggedMessage('Starting new workflow execution', 'info')).toBe(true);
1111+
1112+
// The generated thread ID should follow the mmw- prefix convention
1113+
const processingLog = mockLogger.logs.find(log =>
1114+
log.message.includes('Processing orchestrator request')
1115+
);
1116+
expect(processingLog).toBeDefined();
1117+
const threadId = (processingLog?.data as { threadId?: string })?.threadId;
1118+
expect(threadId).toBeDefined();
1119+
expect(threadId).toMatch(/^mmw-/);
1120+
});
1121+
10491122
it('should handle interrupt/resume cycle with custom schema', async () => {
10501123
const mockFs = new MockFileSystem();
10511124
const testProjectPath = '/test/project';

0 commit comments

Comments
 (0)