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
10 changes: 10 additions & 0 deletions tests/bookshop/srv/programmatic-service.cds
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,14 @@ service ProgrammaticService {

action getInstanceIDForGetOutputs(ID: UUID,
status: many String) returns many AttributeEntry;

// Generic ProcessService actions (using cds.connect.to('ProcessService'))
action genericStart(definitionId: String, businessKey: String, context: LargeString);
action genericCancel(businessKey: String, cascade: Boolean);
action genericSuspend(businessKey: String, cascade: Boolean);
action genericResume(businessKey: String, cascade: Boolean);
action genericGetInstancesByBusinessKey(businessKey: String,
status: many String) returns many ProcessInstance;
action genericGetAttributes(processInstanceId: String) returns many ProcessAttribute;
action genericGetOutputs(processInstanceId: String) returns ProcessOutputs;
}
58 changes: 58 additions & 0 deletions tests/bookshop/srv/programmatic-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,64 @@ class ProgrammaticService extends cds.ApplicationService {
return outputs;
});

// Generic ProcessService handlers (using cds.connect.to('ProcessService'))
this.on('genericStart', async (req: cds.Request) => {
const { definitionId, businessKey, context } = req.data;
const processService = await cds.connect.to('ProcessService');
const queuedProcessService = cds.queued(processService);
const parsedContext = context ? JSON.parse(context) : {};
await queuedProcessService.emit(
'start',
{ definitionId, context: parsedContext },
{ businessKey },
);
});

this.on('genericCancel', async (req: cds.Request) => {
const { businessKey, cascade } = req.data;
const processService = await cds.connect.to('ProcessService');
const queuedProcessService = cds.queued(processService);
await queuedProcessService.emit('cancel', { businessKey, cascade: cascade ?? false });
});

this.on('genericSuspend', async (req: cds.Request) => {
const { businessKey, cascade } = req.data;
const processService = await cds.connect.to('ProcessService');
const queuedProcessService = cds.queued(processService);
await queuedProcessService.emit('suspend', { businessKey, cascade: cascade ?? false });
});

this.on('genericResume', async (req: cds.Request) => {
const { businessKey, cascade } = req.data;
const processService = await cds.connect.to('ProcessService');
const queuedProcessService = cds.queued(processService);
await queuedProcessService.emit('resume', { businessKey, cascade: cascade ?? false });
});

this.on('genericGetInstancesByBusinessKey', async (req: cds.Request) => {
const { businessKey, status } = req.data;
const processService = await cds.connect.to('ProcessService');
const result = await processService.send('getInstancesByBusinessKey', {
businessKey,
status,
});
return result;
});

this.on('genericGetAttributes', async (req: cds.Request) => {
const { processInstanceId } = req.data;
const processService = await cds.connect.to('ProcessService');
const result = await processService.send('getAttributes', { processInstanceId });
return result;
});

this.on('genericGetOutputs', async (req: cds.Request) => {
const { processInstanceId } = req.data;
const processService = await cds.connect.to('ProcessService');
const result = await processService.send('getOutputs', { processInstanceId });
return result;
});

await super.init();
}
}
Expand Down
144 changes: 109 additions & 35 deletions tests/hybrid/programmaticApproach.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,63 @@ describe('Programmatic Approach Hybrid Tests', () => {
);
}

async function startOutputProcess(
ID: string,
mandatory_datetime: string,
mandatory_string: string,
optional_string?: string,
optional_datetime?: string,
) {
return POST('/odata/v4/programmatic/startForGetOutputs', {
ID,
mandatory_datetime,
mandatory_string,
optional_string,
optional_datetime,
});
}

async function getOutputInstances(ID: string, status?: string[]): Promise<any[]> {
const res = await POST('/odata/v4/programmatic/getInstanceIDForGetOutputs', { ID, status });
return res.data?.value ?? res.data ?? [];
}

async function waitForOutputInstances(
ID: string,
status: string[],
expectedCount = 1,
maxRetries = 8,
): Promise<any[]> {
for (let i = 0; i < maxRetries; i++) {
const instances = await getOutputInstances(ID, status);
if (instances.length >= expectedCount) return instances;
await new Promise((r) => setTimeout(r, 10000));
}
throw new Error(
`Timed out waiting for ${expectedCount} output instance(s) with status [${status}] for ID ${ID}`,
);
}

async function getAttributes(ID: string): Promise<any[]> {
const res = await POST('/odata/v4/programmatic/getAttributes', { ID });
return res.data?.value ?? res.data ?? [];
}

async function getOutputs(instanceId: string): Promise<any> {
const res = await POST('/odata/v4/programmatic/getOutputs', { instanceId });
return res.data;
}

async function genericGetAttributes(processInstanceId: string): Promise<any[]> {
const res = await POST('/odata/v4/programmatic/genericGetAttributes', { processInstanceId });
return res.data?.value ?? res.data ?? [];
}

async function genericGetOutputs(processInstanceId: string): Promise<any> {
const res = await POST('/odata/v4/programmatic/genericGetOutputs', { processInstanceId });
return res.data;
}

describe('Process Start', () => {
it('should start a process and verify it is RUNNING on SBPA', async () => {
const ID = generateID();
Expand Down Expand Up @@ -197,48 +249,70 @@ describe('Programmatic Approach Hybrid Tests', () => {
});

describe('Get Outputs', () => {
async function startOutputProcess(
ID: string,
mandatory_datetime: string,
mandatory_string: string,
optional_string?: string,
optional_datetime?: string,
) {
return POST('/odata/v4/programmatic/startForGetOutputs', {
it('should retrieve outputs from a completed process', async () => {
const ID = generateID();
const mandatory_datetime = new Date().toISOString();
const mandatory_string = 'test-output-string';

await startOutputProcess(ID, mandatory_datetime, mandatory_string);

const instances = await waitForOutputInstances(ID, ['COMPLETED']);
expect(instances.length).toBe(1);
expect(instances[0]).toHaveProperty('workflowId');

const outputs = await getOutputs(instances[0].workflowId);

expect(outputs).toHaveProperty('mandatory_string');
expect(outputs).toHaveProperty('mandatory_datetime');
expect(outputs.mandatory_string).toBeDefined();
expect(outputs.mandatory_datetime).toBeDefined();
});

it('should return optional fields in outputs when provided', async () => {
const ID = generateID();
const mandatory_datetime = new Date().toISOString();
const mandatory_string = 'test-mandatory';
const optional_string = 'test-optional';
const optional_datetime = new Date().toISOString();

await startOutputProcess(
ID,
mandatory_datetime,
mandatory_string,
optional_string,
optional_datetime,
});
}
);

async function getOutputInstances(ID: string, status?: string[]): Promise<any[]> {
const res = await POST('/odata/v4/programmatic/getInstanceIDForGetOutputs', { ID, status });
return res.data?.value ?? res.data ?? [];
}
const instances = await waitForOutputInstances(ID, ['COMPLETED']);
expect(instances.length).toBe(1);

async function waitForOutputInstances(
ID: string,
status: string[],
expectedCount = 1,
maxRetries = 8,
): Promise<any[]> {
for (let i = 0; i < maxRetries; i++) {
const instances = await getOutputInstances(ID, status);
if (instances.length >= expectedCount) return instances;
await new Promise((r) => setTimeout(r, 10000));
}
throw new Error(
`Timed out waiting for ${expectedCount} output instance(s) with status [${status}] for ID ${ID}`,
);
}
const outputs = await getOutputs(instances[0].workflowId);

async function getOutputs(instanceId: string): Promise<any> {
const res = await POST('/odata/v4/programmatic/getOutputs', { instanceId });
return res.data;
}
expect(outputs).toHaveProperty('mandatory_string');
expect(outputs).toHaveProperty('mandatory_datetime');
expect(outputs).toHaveProperty('optional_string');
expect(outputs).toHaveProperty('optional_datetime');
});
});

describe('Generic Get Attributes', () => {
it('should return attributes for a running process', async () => {
const ID = generateID();
await startProcess(ID);
const instances = await waitForInstances(ID, ['RUNNING']);
expect(instances.length).toBe(1);

const attributes = await genericGetAttributes(instances[0].id);

expect(Array.isArray(attributes)).toBe(true);
expect(attributes.length).toBeGreaterThan(0);
expect(attributes[0]).toHaveProperty('id');
expect(attributes[0]).toHaveProperty('value');
expect(attributes[0]).toHaveProperty('type');
});
});

describe('Generic Get Outputs', () => {
it('should retrieve outputs from a completed process', async () => {
const ID = generateID();
const mandatory_datetime = new Date().toISOString();
Expand All @@ -250,7 +324,7 @@ describe('Programmatic Approach Hybrid Tests', () => {
expect(instances.length).toBe(1);
expect(instances[0]).toHaveProperty('workflowId');

const outputs = await getOutputs(instances[0].workflowId);
const outputs = await genericGetOutputs(instances[0].workflowId);

expect(outputs).toHaveProperty('mandatory_string');
expect(outputs).toHaveProperty('mandatory_datetime');
Expand All @@ -276,7 +350,7 @@ describe('Programmatic Approach Hybrid Tests', () => {
const instances = await waitForOutputInstances(ID, ['COMPLETED']);
expect(instances.length).toBe(1);

const outputs = await getOutputs(instances[0].workflowId);
const outputs = await genericGetOutputs(instances[0].workflowId);

expect(outputs).toHaveProperty('mandatory_string');
expect(outputs).toHaveProperty('mandatory_datetime');
Expand Down
Loading
Loading