Skip to content

Commit 34d7aac

Browse files
authored
fix: Add tests for retry cancelled collection jobs (#1214)
* some fixes for retry cancelled * add test for retry cancelled jobs * fix for fetching test ids * sort test ids array to prevent diff problems
1 parent fb8c341 commit 34d7aac

File tree

4 files changed

+179
-11
lines changed

4 files changed

+179
-11
lines changed

client/components/ManageBotRunDialog/queries.js

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ export const RETRY_CANCELED_COLLECTIONS = gql`
5252
retryCanceledCollections {
5353
id
5454
status
55+
testStatus {
56+
status
57+
}
5558
}
5659
}
5760
}

server/models/services/CollectionJobService.js

+38-9
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,13 @@ const getCollectionJobs = async ({
388388
const triggerWorkflow = async (job, testIds, atVersion, { transaction }) => {
389389
const { testPlanVersion } = job.testPlanRun.testPlanReport;
390390
const { gitSha, directory } = testPlanVersion;
391+
391392
try {
392393
if (isGithubWorkflowEnabled()) {
393394
// TODO: pass the reduced list of testIds along / deal with them somehow
394395
await createGithubWorkflow({ job, directory, gitSha, atVersion });
395396
} else {
396-
await startCollectionJobSimulation(job, atVersion, transaction);
397+
await startCollectionJobSimulation(job, testIds, atVersion, transaction);
397398
}
398399
} catch (error) {
399400
console.error(error);
@@ -479,14 +480,12 @@ const retryCanceledCollections = async ({ collectionJob }, { transaction }) => {
479480
throw new Error('collectionJob is required to retry cancelled tests');
480481
}
481482

482-
const cancelledTests = collectionJob.testPlanRun.testResults.filter(
483-
testResult =>
484-
// Find tests that don't have complete output
485-
!testResult?.scenarioResults?.every(scenario => scenario?.output !== null)
483+
const cancelledTests = collectionJob.testStatus.filter(
484+
testStatus => testStatus.status === COLLECTION_JOB_STATUS.CANCELLED
486485
);
487486

488487
const testPlanReport = await getTestPlanReportById({
489-
id: job.testPlanRun.testPlanReportId,
488+
id: collectionJob.testPlanRun.testPlanReportId,
490489
transaction
491490
});
492491

@@ -497,10 +496,22 @@ const retryCanceledCollections = async ({ collectionJob }, { transaction }) => {
497496
transaction
498497
);
499498

500-
const testIds = cancelledTests.map(test => test.id);
499+
const testIds = cancelledTests.map(test => test.testId);
501500

502-
const job = await getCollectionJobById({
501+
const job = await updateCollectionJobById({
503502
id: collectionJob.id,
503+
values: { status: COLLECTION_JOB_STATUS.QUEUED },
504+
transaction
505+
});
506+
507+
await updateCollectionJobTestStatusByQuery({
508+
where: {
509+
collectionJobId: job.id,
510+
status: COLLECTION_JOB_STATUS.CANCELLED
511+
},
512+
values: {
513+
status: COLLECTION_JOB_STATUS.QUEUED
514+
},
504515
transaction
505516
});
506517

@@ -652,6 +663,15 @@ const restartCollectionJob = async ({ id }, { transaction }) => {
652663
},
653664
transaction
654665
});
666+
await updateCollectionJobTestStatusByQuery({
667+
where: {
668+
collectionJobId: id
669+
},
670+
values: {
671+
status: COLLECTION_JOB_STATUS.QUEUED
672+
},
673+
transaction
674+
});
655675

656676
if (!job) {
657677
return null;
@@ -669,7 +689,16 @@ const restartCollectionJob = async ({ id }, { transaction }) => {
669689
transaction
670690
);
671691

672-
return triggerWorkflow(job, [], atVersion, { transaction });
692+
const tests = await runnableTestsResolver(testPlanReport, null, {
693+
transaction
694+
});
695+
696+
return triggerWorkflow(
697+
job,
698+
tests.map(test => test.id),
699+
atVersion,
700+
{ transaction }
701+
);
673702
};
674703

675704
/**

server/tests/integration/automation-scheduler.test.js

+129-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const startSupertestServer = require('../util/api-server');
22
const automationRoutes = require('../../routes/automation');
33
const {
4-
setupMockAutomationSchedulerServer
4+
setupMockAutomationSchedulerServer,
5+
startCollectionJobSimulation
56
} = require('../util/mock-automation-scheduler-server');
67
const db = require('../../models/index');
78
const { query, mutate } = require('../util/graphql-test-utilities');
@@ -197,6 +198,22 @@ const restartCollectionJobByMutation = async (jobId, { transaction }) =>
197198
{ transaction }
198199
);
199200

201+
const retryCanceledCollectionJobByMutation = async (jobId, { transaction }) =>
202+
await mutate(
203+
`
204+
mutation {
205+
collectionJob(id: "${jobId}") {
206+
retryCanceledCollections {
207+
id
208+
status
209+
testStatus {
210+
status
211+
}
212+
}
213+
}
214+
} `,
215+
{ transaction }
216+
);
200217
const cancelCollectionJobByMutation = async (jobId, { transaction }) =>
201218
await mutate(
202219
`
@@ -237,6 +254,18 @@ describe('Automation controller', () => {
237254
expect(storedJob.status).toEqual('QUEUED');
238255
expect(storedJob.testPlanRun.testPlanReport.id).toEqual(testPlanReportId);
239256
expect(storedJob.testPlanRun.testResults.length).toEqual(0);
257+
const collectionJob = await getCollectionJobById({
258+
id: storedJob.id,
259+
transaction
260+
});
261+
const tests =
262+
collectionJob.testPlanRun.testPlanReport.testPlanVersion.tests.filter(
263+
test => test.at.key === 'voiceover_macos'
264+
);
265+
// check testIds - order doesn't matter, so we sort them
266+
expect(
267+
startCollectionJobSimulation.lastCallParams.testIds.sort()
268+
).toEqual(tests.map(test => test.id).sort());
240269
});
241270
});
242271

@@ -260,6 +289,105 @@ describe('Automation controller', () => {
260289
});
261290
});
262291

292+
it('should retry a cancelled job with only remaining tests', async () => {
293+
await apiServer.sessionAgentDbCleaner(async transaction => {
294+
const { scheduleCollectionJob: job } =
295+
await scheduleCollectionJobByMutation({ transaction });
296+
const collectionJob = await getCollectionJobById({
297+
id: job.id,
298+
transaction
299+
});
300+
const secret = await getJobSecret(collectionJob.id, { transaction });
301+
302+
// start "RUNNING" the job
303+
const response1 = await sessionAgent
304+
.post(`/api/jobs/${collectionJob.id}`)
305+
.send({ status: 'RUNNING' })
306+
.set('x-automation-secret', secret)
307+
.set('x-transaction-id', transaction.id);
308+
expect(response1.statusCode).toBe(200);
309+
310+
// simulate a response for a test
311+
const automatedTestResponse = 'AUTOMATED TEST RESPONSE';
312+
const ats = await AtLoader().getAll({ transaction });
313+
const browsers = await BrowserLoader().getAll({ transaction });
314+
const at = ats.find(
315+
at => at.id === collectionJob.testPlanRun.testPlanReport.at.id
316+
);
317+
const browser = browsers.find(
318+
browser =>
319+
browser.id === collectionJob.testPlanRun.testPlanReport.browser.id
320+
);
321+
const { tests } =
322+
collectionJob.testPlanRun.testPlanReport.testPlanVersion;
323+
const selectedTestIndex = 2;
324+
325+
const selectedTest = tests[selectedTestIndex];
326+
const selectedTestRowNumber = selectedTest.rowNumber;
327+
328+
const numberOfScenarios = selectedTest.scenarios.filter(
329+
scenario => scenario.atId === at.id
330+
).length;
331+
const response2 = await sessionAgent
332+
.post(`/api/jobs/${collectionJob.id}/test/${selectedTestRowNumber}`)
333+
.send({
334+
capabilities: {
335+
atName: at.name,
336+
atVersion: at.atVersions[0].name,
337+
browserName: browser.name,
338+
browserVersion: browser.browserVersions[0].name
339+
},
340+
responses: new Array(numberOfScenarios).fill(automatedTestResponse)
341+
})
342+
.set('x-automation-secret', secret)
343+
.set('x-transaction-id', transaction.id);
344+
expect(response2.statusCode).toBe(200);
345+
// cancel the job
346+
const {
347+
collectionJob: { cancelCollectionJob: cancelledCollectionJob }
348+
} = await cancelCollectionJobByMutation(collectionJob.id, {
349+
transaction
350+
});
351+
352+
// check canceled status
353+
354+
expect(cancelledCollectionJob.status).toEqual('CANCELLED');
355+
const { collectionJob: storedCollectionJob } = await getTestCollectionJob(
356+
collectionJob.id,
357+
{ transaction }
358+
);
359+
expect(storedCollectionJob.status).toEqual('CANCELLED');
360+
for (const test of storedCollectionJob.testStatus) {
361+
const expectedStatus =
362+
test.test.id == selectedTest.id ? 'COMPLETED' : 'CANCELLED';
363+
expect(test.status).toEqual(expectedStatus);
364+
}
365+
366+
// retry job
367+
const data = await retryCanceledCollectionJobByMutation(
368+
collectionJob.id,
369+
{ transaction }
370+
);
371+
expect(data.collectionJob.retryCanceledCollections.status).toBe('QUEUED');
372+
const { collectionJob: restartedCollectionJob } =
373+
await getTestCollectionJob(collectionJob.id, { transaction });
374+
// check restarted status
375+
expect(restartedCollectionJob.status).toEqual('QUEUED');
376+
for (const test of restartedCollectionJob.testStatus) {
377+
const expectedStatus =
378+
test.test.id == selectedTest.id ? 'COMPLETED' : 'QUEUED';
379+
expect(test.status).toEqual(expectedStatus);
380+
}
381+
const expectedTests = tests.filter(
382+
test => test.at.key === 'voiceover_macos' && test.id != selectedTest.id
383+
);
384+
// check testIds - order doesn't matter, so we sort them
385+
expect(
386+
startCollectionJobSimulation.lastCallParams.testIds.sort()
387+
).toEqual(expectedTests.map(test => test.id).sort());
388+
});
389+
});
390+
263391
it('should gracefully reject request to cancel a job that does not exist', async () => {
264392
await dbCleaner(async transaction => {
265393
expect.assertions(1); // Make sure an assertion is made

server/tests/util/mock-automation-scheduler-server.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,18 @@ const simulateResultCompletion = async (
136136
}
137137
};
138138

139-
const startCollectionJobSimulation = async (job, atVersion, transaction) => {
139+
const startCollectionJobSimulation = async (
140+
job,
141+
testIds,
142+
atVersion,
143+
transaction
144+
) => {
140145
if (!mockSchedulerEnabled) throw new Error('mock scheduler is not enabled');
141146
if (process.env.ENVIRONMENT === 'test') {
142147
// stub behavior in test suite
148+
149+
startCollectionJobSimulation.lastCallParams = { job, testIds, atVersion };
150+
143151
return { status: COLLECTION_JOB_STATUS.QUEUED };
144152
} else {
145153
const { data } = await apolloServer.executeOperation(

0 commit comments

Comments
 (0)