Skip to content

Commit cf96cd3

Browse files
authored
Merge pull request #3061 from CarnegieLearningWeb/bugfix/mooclet-assign-error-fix
log mooclet assignment errors but do not throw, treat as default
2 parents 27fd895 + 69338f2 commit cf96cd3

6 files changed

Lines changed: 74 additions & 23 deletions

File tree

packages/backend/src/api/services/ExperimentAssignmentService.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1993,7 +1993,17 @@ export class ExperimentAssignmentService {
19931993
private async getConditionFromMoocletProxy(experiment: Experiment, user: ExperimentUser, logger: UpgradeLogger) {
19941994
const userId = user.id;
19951995

1996-
return await this.moocletExperimentService.getConditionFromMoocletProxy(experiment, userId, logger);
1996+
try {
1997+
return await this.moocletExperimentService.getConditionFromMoocletProxy(experiment, userId, logger);
1998+
} catch (err) {
1999+
logger.error({
2000+
message: 'Error getting condition from Mooclet proxy; experiment will return no condition for this user',
2001+
experimentId: experiment.id,
2002+
userId,
2003+
error: err,
2004+
});
2005+
return undefined;
2006+
}
19972007
}
19982008

19992009
private assignRandom(

packages/backend/src/api/services/MoocletDataService.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,6 @@ export class MoocletDataService {
345345
logger: UpgradeLogger
346346
): Promise<any> {
347347
const { method, url, body } = requestParams;
348-
let jsonResponse = '';
349-
350348
if (method && url) {
351349
const headers: HeadersInit = {
352350
Authorization: this.apiToken,
@@ -366,18 +364,30 @@ export class MoocletDataService {
366364
};
367365

368366
const res = await axios.request(options);
369-
370-
if (res?.status?.toString().startsWith('2')) {
371-
jsonResponse = res.data;
372-
return jsonResponse;
367+
return res.data;
368+
} catch (err) {
369+
if (err instanceof MoocletError) {
370+
throw err;
371+
}
372+
if (axios.isAxiosError(err)) {
373+
logger.error({
374+
message: 'Error fetching data from Mooclets API',
375+
url,
376+
method,
377+
status: err.response?.status,
378+
responseBody: err.response?.data,
379+
error: err,
380+
});
381+
throw new MoocletError(`Mooclet server returned non-2xx status: ${err.response?.status}`);
373382
} else {
374-
return {
375-
error: res,
376-
};
383+
logger.error({
384+
message: 'Error fetching data from Mooclets API',
385+
url,
386+
method,
387+
error: err,
388+
});
389+
throw new MoocletError('Failed to communicate with Mooclet server');
377390
}
378-
} catch (err) {
379-
logger.error({ message: 'Error fetching data from Mooclets API', url, method });
380-
throw new MoocletError('Failed to communicate with Mooclet server');
381391
}
382392
}
383393
}

packages/backend/src/api/services/MoocletExperimentService.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,11 @@ export class MoocletExperimentService extends ExperimentService {
13031303
logger: UpgradeLogger
13041304
): Promise<ExperimentCondition> {
13051305
const moocletExperimentRef = await this.getMoocletExperimentRefByUpgradeExperimentId(experiment.id);
1306+
1307+
if (!moocletExperimentRef) {
1308+
throw new MoocletError(`MoocletExperimentRef not found for experiment id ${experiment.id}`);
1309+
}
1310+
13061311
const versionResponse = await this.moocletDataService.getVersionForNewLearner(
13071312
moocletExperimentRef.moocletId,
13081313
userId,

packages/backend/test/unit/services/ExperimentAssignmentService.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,4 +1729,17 @@ describe('Experiment Assignment Service Test', () => {
17291729
});
17301730
});
17311731
});
1732+
1733+
it('[getConditionFromMoocletProxy] should return undefined and log error when mooclet proxy throws', async () => {
1734+
const userDoc = { id: 'user123', group: {}, workingGroup: {} };
1735+
const exp = structuredClone(simpleIndividualAssignmentExperiment);
1736+
const mockError = new Error('Mooclet proxy error');
1737+
1738+
moocletExperimentServiceMock.getConditionFromMoocletProxy.rejects(mockError);
1739+
1740+
const result = await (testedModule as any).getConditionFromMoocletProxy(exp, userDoc, loggerMock);
1741+
1742+
expect(result).toBeUndefined();
1743+
sinon.assert.calledOnce(loggerMock.error);
1744+
});
17321745
});

packages/backend/test/unit/services/MoocletDataService.test.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ describe('#MoocletDataService', () => {
249249
const mockPolicyParameters: MoocletTSConfigurablePolicyParametersDTO = {
250250
assignmentAlgorithm: ASSIGNMENT_ALGORITHM.MOOCLET_TS_CONFIGURABLE,
251251
prior: {
252-
failure: 1,
253-
success: 1,
252+
1: { failure: 1, success: 1 },
253+
2: { failure: 1, success: 1 },
254254
},
255255
batch_size: 1,
256256
max_rating: 1,
@@ -421,23 +421,28 @@ describe('#MoocletDataService', () => {
421421
expect(response).toEqual(mockResponse.data);
422422
});
423423

424-
it('should return an error object when the request fails with a non-2xx status', async () => {
424+
it('should throw a MoocletError when the request fails with a non-2xx status', async () => {
425425
const mockRequestParams: MoocletProxyRequestParams = {
426426
method: 'POST',
427427
url: 'https://api.example.com/mooclet',
428428
apiToken: 'test-token',
429429
body: { ...mockRequest },
430430
};
431431

432-
const mockErrorResponse = {
433-
status: 400,
434-
data: { error: 'Bad Request' },
435-
};
432+
const mockAxiosError = Object.assign(new Error('Request failed with status code 400'), {
433+
response: {
434+
status: 400,
435+
data: { error: 'Bad Request' },
436+
},
437+
isAxiosError: true,
438+
});
436439

437-
(axios.request as jest.Mock).mockResolvedValue(mockErrorResponse);
440+
(axios.request as jest.Mock).mockRejectedValue(mockAxiosError);
441+
(axios as any).isAxiosError.mockReturnValueOnce(true);
438442

439-
const response = await moocletDataService.fetchExternalMoocletsData(mockRequestParams, logger);
440-
expect(response).toEqual({ error: mockErrorResponse });
443+
await expect(moocletDataService.fetchExternalMoocletsData(mockRequestParams, logger)).rejects.toThrow(
444+
'Mooclet server returned non-2xx status: 400'
445+
);
441446
});
442447

443448
it('should handle and log errors when the request fails', async () => {

packages/backend/test/unit/services/MoocletExperimentService.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,14 @@ describe('#MoocletExperimentService', () => {
14911491
);
14921492
});
14931493

1494+
it('should throw MoocletError if moocletExperimentRef is not found', async () => {
1495+
jest.spyOn(moocletExperimentService, 'getMoocletExperimentRefByUpgradeExperimentId').mockResolvedValue(undefined);
1496+
1497+
await expect(
1498+
moocletExperimentService.getConditionFromMoocletProxy(mockExperiment, mockUserId, logger)
1499+
).rejects.toThrow(`MoocletExperimentRef not found for experiment id ${mockExperiment.id}`);
1500+
});
1501+
14941502
it('should throw error if version not found in maps', async () => {
14951503
const mockVersionResponse = { id: 999, name: 'unknown' };
14961504

0 commit comments

Comments
 (0)