Skip to content

Commit 642f592

Browse files
committed
Payment retry fix test.
1 parent f8791b3 commit 642f592

File tree

2 files changed

+91
-8
lines changed

2 files changed

+91
-8
lines changed

services/IssueService.js

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,43 @@ function parsePrizes(issue) {
5555
return true;
5656
}
5757

58+
/**
59+
* Check challenge data and challenge resources
60+
* @param {Object} event the event
61+
* @param {Object} challenge the challenge
62+
* @param {Object} challengeResources the challenge resources
63+
* @returns {boolean} true if the challenge and challenge resources is valid; or false otherwise
64+
* @private
65+
*/
66+
function checkChallenge(event, challenge, challengeResources) {
67+
// check prize
68+
const validPrize = _.isEqual(challenge.prize.sort(), event.dbIssue.prizes.sort());
69+
70+
// check copilot
71+
const copilot = _.find(challengeResources, { role: 'Copilot' });
72+
const validCopilot = copilot ? copilot.properties.Handle === event.copilot.topcoderUsername : false;
73+
74+
// check assignee
75+
const assignee = _.find(challengeResources, { role: 'Submitter' });
76+
const validAssignee = assignee ? assignee.properties.Handle === event.assigneeMember.topcoderUsername : false;
77+
78+
// check status
79+
const validStatus = challenge.currentStatus.toLowerCase() === 'active';
80+
81+
return validPrize && validCopilot && validAssignee && validStatus;
82+
}
83+
84+
/**
85+
* Check the error object contains some error messages
86+
* @param {Object} err the error object
87+
* @param {Array} validErrorMessages the valid error messages
88+
* @returns {boolean} true if the error contains some valid error messages; or false otherwise
89+
* @private
90+
*/
91+
function checkErrorMessages(err, validErrorMessages) {
92+
return _.some(validErrorMessages, (errorMessage) => err.message && err.message.includes(errorMessage));
93+
}
94+
5895
/**
5996
* handles the event gracefully when there is error processing the event
6097
* @param {Object} event the event
@@ -69,6 +106,16 @@ async function handleEventGracefully(event, issue, err) {
69106
logger.debug('Scheduling event for next retry');
70107
const newEvent = {...event};
71108
newEvent.retryCount += 1;
109+
110+
const validErrorMessages = ['Failed to create challenge.', 'Failed to activate challenge.'];
111+
112+
if (newEvent.event === 'issue.closed' && checkErrorMessages(err, validErrorMessages)) {
113+
const challenge = await topcoderApiHelper.getChallengeById(newEvent.dbIssue.challengeId);
114+
const challengeResources = await topcoderApiHelper.getResourcesFromChallenge(newEvent.dbIssue.challengeId);
115+
116+
newEvent.challengeValid = checkChallenge(newEvent, challenge, challengeResources);
117+
}
118+
72119
delete newEvent.copilot;
73120
setTimeout(async () => {
74121
const kafka = require('../utils/kafka'); // eslint-disable-line
@@ -325,6 +372,8 @@ async function handleIssueClose(event, issue) {
325372
let dbIssue;
326373
try {
327374
dbIssue = await ensureChallengeExists(event, issue);
375+
event.dbIssue = dbIssue;
376+
328377
if (!event.paymentSuccessful) {
329378
let closeChallenge = false;
330379
// if issue is closed without Fix accepted label
@@ -339,7 +388,6 @@ async function handleIssueClose(event, issue) {
339388
closeChallenge = true;
340389
}
341390

342-
343391
// if issue is closed without assignee then do nothing
344392
if (!event.data.assignee.id) {
345393
logger.debug(`This issue ${issue.number} doesn't have assignee so ignoring this event.`);
@@ -354,6 +402,7 @@ async function handleIssueClose(event, issue) {
354402

355403
logger.debug(`Looking up TC handle of git user: ${event.data.assignee.id}`);
356404
const assigneeMember = await userService.getTCUserName(event.provider, event.data.assignee.id);
405+
event.assigneeMember = assigneeMember
357406

358407
// no mapping is found for current assignee remove assign, re-open issue and make comment
359408
// to assignee to login with Topcoder X
@@ -398,7 +447,10 @@ async function handleIssueClose(event, issue) {
398447
await topcoderApiHelper.assignUserAsRegistrant(winnerId, dbIssue.challengeId);
399448

400449
// activate challenge
401-
await topcoderApiHelper.activateChallenge(dbIssue.challengeId);
450+
if (!event.challengeValid) {
451+
await topcoderApiHelper.activateChallenge(dbIssue.challengeId);
452+
}
453+
402454
if (closeChallenge) {
403455
logger.debug(`The associated challenge ${dbIssue.challengeId} is scheduled for cancel`);
404456
setTimeout(async () => {
@@ -413,7 +465,7 @@ async function handleIssueClose(event, issue) {
413465
}
414466
} catch (e) {
415467
event.paymentSuccessful = event.paymentSuccessful === true; // if once paid shouldn't be false
416-
await handleEventGracefully(event, issue, e, event.paymentSuccessful);
468+
await handleEventGracefully(event, issue, e);
417469
return;
418470
}
419471
try {
@@ -425,7 +477,7 @@ async function handleIssueClose(event, issue) {
425477
await dbIssue.save();
426478
await gitHelper.markIssueAsPaid(event, issue.number, dbIssue.challengeId);
427479
} catch (e) {
428-
await handleEventGracefully(event, issue, e, event.paymentSuccessful);
480+
await handleEventGracefully(event, issue, e);
429481
return;
430482
}
431483
}
@@ -657,7 +709,10 @@ process.schema = Joi.object().keys({
657709
labels: Joi.array().items(Joi.string())
658710
}).required(),
659711
retryCount: Joi.number().integer().default(0).optional(),
660-
paymentSuccessful: Joi.boolean().default(false).optional()
712+
paymentSuccessful: Joi.boolean().default(false).optional(),
713+
challengeValid: Joi.boolean().default(false).optional(),
714+
dbIssue: Joi.object().optional(),
715+
assigneeMember: Joi.object().optional(),
661716
});
662717

663718

utils/topcoder-api-helper.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,15 @@ async function getChallengeById(id) {
224224
logger.debug('Getting topcoder challenge details');
225225
try {
226226
const response = await axios.get(`${projectsClient.basePath}/challenges/${id}`, {
227-
authorization: `Bearer ${apiKey}`,
227+
headers: {
228+
authorization: `Bearer ${apiKey}`
229+
},
228230
'Content-Type': 'application/json'
229231
});
230232
const challenge = _.get(response, 'data.result.content');
231233
return challenge;
232-
} catch (er) {
233-
throw errors.convertTopcoderApiError(er, 'Failed to get challenge details by Id');
234+
} catch (err) {
235+
throw errors.convertTopcoderApiError(err, 'Failed to get challenge details by Id');
234236
}
235237
}
236238

@@ -325,6 +327,31 @@ async function addResourceToChallenge(id, resource) {
325327
}
326328
}
327329

330+
/**
331+
* Get challenge resources details by id
332+
* @param {Number} id challenge ID
333+
* @returns {Object} topcoder challenge resources
334+
*/
335+
async function getResourcesFromChallenge(id) {
336+
if (!_.isNumber(id)) {
337+
throw new Error('The challenge id must valid number');
338+
}
339+
const apiKey = await getAccessToken();
340+
logger.debug(`fetch resource from challenge ${id}`);
341+
try {
342+
const response = await axios.get(`${projectsClient.basePath}/challenges/${id}/resources`, {
343+
headers: {
344+
authorization: `Bearer ${apiKey}`
345+
},
346+
'Content-Type': 'application/json'
347+
});
348+
const resources = _.get(response, 'data.result.content');
349+
return resources;
350+
} catch (err) {
351+
throw errors.convertTopcoderApiError(err, 'Failed to fetch resource from the challenge.');
352+
}
353+
}
354+
328355
/**
329356
* unregister user from topcoder challenge
330357
* @param {Number} id the challenge id
@@ -437,6 +464,7 @@ module.exports = {
437464
getProjectBillingAccountId,
438465
getTopcoderMemberId,
439466
addResourceToChallenge,
467+
getResourcesFromChallenge,
440468
unregisterUserFromChallenge,
441469
cancelPrivateContent,
442470
assignUserAsRegistrant,

0 commit comments

Comments
 (0)