feat: start method on maestro process#365
Conversation
| ): Promise<ProcessStartResponse[]> { | ||
| // Transform SDK field names to API field names (e.g., processKey → releaseKey) | ||
| const apiRequest = transformRequest(request, ProcessMap); | ||
|
|
There was a problem hiding this comment.
These three inline comments explain self-evident code — transformRequest, addPrefixToKeys, and the startInfo body shape are all well-named and need no narration. Per project conventions: "Only add comments where the logic isn't self-evident." Please remove them.
| * | ||
| * @param request - Process start request body (provide either `processKey` or `processName`) | ||
| * @param folderKey - Required folder key | ||
| * @param options - Optional query parameters |
There was a problem hiding this comment.
The {@link ProcessStartResponse} is on a separate line instead of inline with @returns. Per conventions: "Link response types with {@link TypeName} in every method's JSDoc @returns."
* @returns Promise resolving to the created jobs — see {@link ProcessStartResponse}| const result = await service.start(request, MAESTRO_TEST_CONSTANTS.FOLDER_KEY); | ||
|
|
||
| expect(result).toBeDefined(); | ||
| expect(result).toHaveLength(1); |
There was a problem hiding this comment.
Missing transform-completeness assertions. Per conventions: "Validate transform completeness — verify both: (a) transformed fields have correct values AND (b) original PascalCase fields are absent."
After the existing expect(result[0].state).toBe('Running'), add:
// Verify PascalCase originals are absent (transform regression guard)
expect((result[0] as any).Key).toBeUndefined();
expect((result[0] as any).ReleaseName).toBeUndefined();
expect((result[0] as any).State).toBeUndefined();See the established pattern at assets.test.ts:94 and queues.test.ts:88.
|
|
||
| const processKey = config.orchestratorTestProcessKey; | ||
|
|
||
| expect( |
There was a problem hiding this comment.
The guard pattern is inconsistent with the rest of this file. The existing getIncidents test uses:
if (!processKey) {
console.warn('MAESTRO_TEST_PROCESS_KEY not configured, skipping test');
return;
}Per conventions: "Use console.warn() + skip for setup preconditions outside the test's control." expect().toBeDefined() throws and marks the test as failed rather than skipped when the env var is missing. Use if (!processKey) { console.warn(...); return; } to be consistent.
| expect(result[0].id).toBeDefined(); | ||
| } | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Missing a transform validation integration test. Per conventions: "Include a transform validation test for new methods with a transform pipeline. Verify: (a) transformed camelCase fields exist and have values, AND (b) original PascalCase API fields are absent."
Add a second test in this describe('start') block (guarded by the same processKey check):
it('should apply transform pipeline (camelCase fields present, PascalCase absent)', async () => {
if (!processKey) { console.warn('...'); return; }
const result = await maestroProcesses.start({ processKey: processKey! }, config.folderId!);
if (result.length === 0) { console.warn('No jobs returned, skipping transform check'); return; }
// camelCase present
expect(result[0].key).toBeDefined();
expect(result[0].state).toBeDefined();
// PascalCase absent
expect((result[0] as any).Key).toBeUndefined();
expect((result[0] as any).State).toBeUndefined();
expect((result[0] as any).ReleaseName).toBeUndefined();
});|
Reviewed against the project conventions in Required changes:
The core implementation (helper extraction, deduplication, header differences, |
|
|
|
||
| return response.data?.value.map(process => | ||
| transformData(pascalToCamelCaseKeys(process) as ProcessStartResponse, ProcessMap) |
There was a problem hiding this comment.
The optional chain on response.data?.value.map(...) means this can return undefined when response.data is nullish, but the declared return type is Promise<ProcessStartResponse[]>. That's a type lie — callers doing result[0] will get a runtime crash if the API ever returns an empty/null body.
| return response.data?.value.map(process => | |
| transformData(pascalToCamelCaseKeys(process) as ProcessStartResponse, ProcessMap) | |
| return response.data?.value.map(process => | |
| transformData(pascalToCamelCaseKeys(process) as ProcessStartResponse, ProcessMap) | |
| ) ?? []; |
| expect(result[0].processName).toBe(PROCESS_TEST_CONSTANTS.PROCESS_NAME); | ||
| expect(result[0].state).toBe('Running'); | ||
|
|
||
| expect(mockApiClient.post).toHaveBeenCalledWith( | ||
| PROCESS_ENDPOINTS.START_PROCESS, |
There was a problem hiding this comment.
Per conventions, transform completeness tests must verify both that the SDK camelCase fields are present and that the original PascalCase fields from the API are absent. Without the absence check, a regression where pascalToCamelCaseKeys is dropped would still pass (the raw fields would just carry through under different names).
| expect(result[0].processName).toBe(PROCESS_TEST_CONSTANTS.PROCESS_NAME); | |
| expect(result[0].state).toBe('Running'); | |
| expect(mockApiClient.post).toHaveBeenCalledWith( | |
| PROCESS_ENDPOINTS.START_PROCESS, | |
| expect(result[0].key).toBe(PROCESS_TEST_CONSTANTS.JOB_KEY); | |
| expect(result[0].processName).toBe(PROCESS_TEST_CONSTANTS.PROCESS_NAME); | |
| expect(result[0].state).toBe('Running'); | |
| // Verify PascalCase originals were removed by the transform pipeline | |
| expect((result[0] as any).ProcessName).toBeUndefined(); | |
| expect((result[0] as any).State).toBeUndefined(); |
See the established pattern in assets.test.ts:94, queues.test.ts:88, choicesets.test.ts:243.
| ).toBeDefined(); | ||
| expect(config.folderId, 'INTEGRATION_TEST_FOLDER_ID must be configured').toBeDefined(); | ||
|
|
||
| const result = await maestroProcesses.start( | ||
| { | ||
| processKey: processKey!, | ||
| inputArguments: JSON.stringify({ | ||
| testRun: true, | ||
| timestamp: new Date().toISOString(), | ||
| }), | ||
| }, |
There was a problem hiding this comment.
config.folderId maps to the INTEGRATION_TEST_FOLDER_ID env var, which is a numeric Orchestrator folder ID (stored as string). Maestro's start() takes folderKey: string — a named folder key like "Accounting", not a numeric ID. These are semantically different and the API won't accept an ID where a key is expected.
This integration test needs a Maestro-specific string folder key. IntegrationConfig will need a new field (e.g., maestroFolderKey?: string backed by a MAESTRO_FOLDER_KEY env var), or you can reuse an existing string-keyed config field if one already exists in the test environment.
Also, the error message on line 147 says 'INTEGRATION_TEST_FOLDER_ID must be configured' — if you add the new field, update the message to match.


Prompt used
1. Under maestro Processes I want to add a new method named start. It should internally call the same start logic done within Processes. One difference, instead of folderId: number as an argument, take folderKey: string 2. Orchestrator process start and Maestro process start both has same implementation with only headers being the difference. Is there a way to reduce code duplication across both methodsGenerated claude plan
Plan: Deduplicate Orchestrator + Maestro
startprocess logicContext
ProcessService.start(orchestrator) andMaestroProcessesService.start(maestro) currently contain near-identical implementations. The only meaningful difference is the folder header:X-UIPATH-OrganizationUnitId(numericfolderId)X-UIPATH-FolderKey(stringfolderKey)Everything else —
transformRequest(request, ProcessMap), the{ startInfo: ... }body wrap, OData$-prefixing of options, the POST toPROCESS_ENDPOINTS.START_PROCESS, and the response transform pipeline (pascalToCamelCaseKeys+transformData) — is duplicated verbatim. Any future change (new transform field, response shape change, error handling tweak) must currently be made in two places.The goal is to extract the shared body into one helper so both public methods become thin wrappers that only differ in their folder-header construction and
@tracklabel.Approach
Extract a free helper function
startProcessRequest()that takes the work that's identical between the two services. BothProcessService.startandMaestroProcessesService.startkeep their public signatures and@trackdecorators; their bodies shrink to building the headers and delegating.Why a free function (and not a base class / static method)
BaseService.postisprotected, so the helper cannot call it from outside the class hierarchy. The cleanest workaround — already used elsewhere in the codebase (createPaginationServiceAccessonbase.ts:90) — is to have the calling service hand the helper a boundpostcallable. This avoids inheritance gymnastics, keepsBaseServicefree of domain-specific code, and preserves protected-method encapsulation.Helper signature
New file:
src/services/orchestrator/processes/helpers.ts(orchestrator owns the underlying API).Caller shape
src/services/orchestrator/processes/processes.ts—start()becomes:src/services/maestro/processes/processes.ts—start()becomes:After the refactor, the maestro service can drop its now-unused imports of
transformRequest,pascalToCamelCaseKeys,transformData,addPrefixToKeys,ODATA_PREFIX,CollectionResponse,ProcessMap, andPROCESS_ENDPOINTS(onlyRequestOptions, the response/request types,FOLDER_KEY,createHeaders, and the new helper remain). Same housekeeping for orchestrator.Files to modify
src/services/orchestrator/processes/helpers.tsstartProcessRequestsrc/services/orchestrator/processes/processes.tsstart()body delegates to helper; trim importssrc/services/maestro/processes/processes.tsstart()body delegates to helper; trim importsNo model, endpoint, type, or test changes required — public signatures and behavior are unchanged.
Verification
npm run typecheck— cleannpm run lint— 0 warnings/errorsnpm run test:unit— all existing orchestrator + maestro start tests pass unchanged. The 11 maestro tests and 5 orchestrator start tests already exercise: processKey path, processName path, OData option$prefixing, multi-job response, and error propagation. No new tests needed because the public contract is identical.npm run build— Rollup completes without import-resolution errors (the newhelpers.tsis internal to its module and not exported from any subpath).Out of scope
shared/directory — orchestrator owns the endpoint, so co-locating with orchestrator processes is correct.getById/getAll— they don't share enough surface to justify deduplication.Local Testing
Done on playground app