8733 refactor gmailhandleerrorservice (#8901)

Closes #8733 
- Refactor `GmailHandleErrorService`
- Add tests and mocks for the errors
This commit is contained in:
Raphaël Bosi 2024-12-05 17:57:57 +01:00 committed by GitHub
parent 680366e998
commit de56c01206
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 878 additions and 49 deletions

View File

@ -5,8 +5,8 @@ import chunk from 'lodash.chunk';
import compact from 'lodash.compact';
import { Any, EntityManager, Repository } from 'typeorm';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@ -24,7 +24,6 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { isWorkEmail } from 'src/utils/is-work-email';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
@Injectable()
export class CreateCompanyAndContactService {
@ -36,8 +35,6 @@ export class CreateCompanyAndContactService {
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}

View File

@ -0,0 +1,117 @@
// Gaxios Network Error Mocks
const gaxiosErrorMocks = {
// Connection Reset Error
connectionReset: {
code: 'ECONNRESET',
name: 'GaxiosError',
message: 'socket hang up',
status: null,
config: {
method: 'GET',
url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages',
headers: {
Authorization: 'Bearer [TOKEN]',
Accept: 'application/json',
},
timeout: 5000,
responseType: 'json',
},
response: undefined,
},
// Host Not Found Error
hostNotFound: {
code: 'ENOTFOUND',
name: 'GaxiosError',
message: 'getaddrinfo ENOTFOUND gmail.googleapis.com',
status: null,
config: {
method: 'GET',
url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages',
headers: {
Authorization: 'Bearer [TOKEN]',
Accept: 'application/json',
},
timeout: 5000,
responseType: 'json',
},
response: undefined,
},
// Connection Aborted Error
connectionAborted: {
code: 'ECONNABORTED',
name: 'GaxiosError',
message: 'The request was aborted due to a timeout',
status: null,
config: {
method: 'GET',
url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages',
headers: {
Authorization: 'Bearer [TOKEN]',
Accept: 'application/json',
},
timeout: 5000,
responseType: 'json',
},
response: undefined,
},
// Timeout Error
timeout: {
code: 'ETIMEDOUT',
name: 'GaxiosError',
message: 'Connection timed out after 5000ms',
status: null,
config: {
method: 'GET',
url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages',
headers: {
Authorization: 'Bearer [TOKEN]',
Accept: 'application/json',
},
timeout: 5000,
responseType: 'json',
},
response: undefined,
},
// Network Error
networkError: {
code: 'ERR_NETWORK',
name: 'GaxiosError',
message: 'Network Error',
status: null,
config: {
method: 'GET',
url: 'https://gmail.googleapis.com/gmail/v1/users/me/messages',
headers: {
Authorization: 'Bearer [TOKEN]',
Accept: 'application/json',
},
timeout: 5000,
responseType: 'json',
},
response: undefined,
},
// Helper function to get error by code
getError: function (code: string) {
switch (code) {
case 'ECONNRESET':
return this.connectionReset;
case 'ENOTFOUND':
return this.hostNotFound;
case 'ECONNABORTED':
return this.connectionAborted;
case 'ETIMEDOUT':
return this.timeout;
case 'ERR_NETWORK':
return this.networkError;
default:
throw new Error(`Unknown error code: ${code}`);
}
},
};
export default gaxiosErrorMocks;

View File

@ -0,0 +1,240 @@
// Gmail API Error Response Mocks for users.messages.list
const gmailApiErrorMocks = {
// 400 Bad Request - Invalid query parameters
badRequest: {
error: {
code: 400,
errors: [
{
domain: 'global',
location: 'orderBy',
locationType: 'parameter',
message:
'Sorting is not supported for queries with fullText terms. Results are always in descending relevance order.',
reason: 'badRequest',
},
],
message:
'Sorting is not supported for queries with fullText terms. Results are always in descending relevance order.',
},
},
// 400 Invalid Grant
invalidGrant: {
error: {
code: 400,
errors: [
{
domain: 'global',
reason: 'invalid_grant',
message: 'Invalid Credentials',
},
],
message: 'Invalid Credentials',
},
},
// 400 Failed Precondition
failedPrecondition: {
error: {
code: 400,
errors: [
{
domain: 'global',
reason: 'failedPrecondition',
message: 'Failed Precondition',
},
],
message: 'Failed Precondition',
},
},
// 401 Invalid Credentials
invalidCredentials: {
error: {
errors: [
{
domain: 'global',
reason: 'authError',
message: 'Invalid Credentials',
locationType: 'header',
location: 'Authorization',
},
],
code: 401,
message: 'Invalid Credentials',
},
},
// 404 Not Found
notFound: {
error: {
errors: [
{
domain: 'global',
reason: 'notFound',
message: 'Resource not found: userId',
location: 'userId',
locationType: 'parameter',
},
],
code: 404,
message: 'Resource not found: userId',
},
},
// 410 Gone
gone: {
error: {
errors: [
{
domain: 'global',
reason: 'resourceGone',
message: 'Resource has been deleted',
location: 'messageId',
locationType: 'parameter',
},
],
code: 410,
message: 'Resource has been deleted',
},
},
// 403 Daily Limit Exceeded
dailyLimitExceeded: {
error: {
errors: [
{
domain: 'usageLimits',
reason: 'dailyLimitExceeded',
message: 'Daily Limit Exceeded',
},
],
code: 403,
message: 'Daily Limit Exceeded',
},
},
// 403 User Rate Limit Exceeded
userRateLimitExceeded: {
error: {
errors: [
{
domain: 'usageLimits',
reason: 'userRateLimitExceeded',
message: 'User Rate Limit Exceeded',
},
],
code: 403,
message: 'User Rate Limit Exceeded',
},
},
// 403 Rate Limit Exceeded
rateLimitExceeded: {
error: {
errors: [
{
domain: 'usageLimits',
reason: 'rateLimitExceeded',
message: 'Rate Limit Exceeded',
},
],
code: 403,
message: 'Rate Limit Exceeded',
},
},
// 403 Domain Policy Error
domainPolicyError: {
error: {
errors: [
{
domain: 'global',
reason: 'domainPolicy',
message: 'The domain administrators have disabled Gmail apps.',
},
],
code: 403,
message: 'The domain administrators have disabled Gmail apps.',
},
},
// 429 Too Many Requests (Concurrent Requests)
tooManyConcurrentRequests: {
error: {
errors: [
{
domain: 'global',
reason: 'rateLimitExceeded',
message: 'Too many concurrent requests for user',
},
],
code: 429,
message: 'Too many concurrent requests for user',
},
},
// 500 Backend Error
backendError: {
error: {
errors: [
{
domain: 'global',
reason: 'backendError',
message: 'Backend Error',
},
],
code: 500,
message: 'Backend Error',
},
},
getError: function (code: number, type?: string) {
switch (code) {
case 400:
switch (type) {
case 'invalid_grant':
return this.invalidGrant;
case 'failedPrecondition':
return this.failedPrecondition;
default:
return this.badRequest;
}
case 401:
return this.invalidCredentials;
case 403:
switch (type) {
case 'dailyLimit':
return this.dailyLimitExceeded;
case 'userRateLimit':
return this.userRateLimitExceeded;
case 'rateLimit':
return this.rateLimitExceeded;
case 'domainPolicy':
return this.domainPolicyError;
default:
return this.rateLimitExceeded;
}
case 404:
return this.notFound;
case 410:
return this.gone;
case 429:
switch (type) {
case 'concurrent':
return this.tooManyConcurrentRequests;
case 'mailSending':
return this.mailSendingLimitExceeded;
default:
return this.tooManyConcurrentRequests;
}
case 500:
return this.backendError;
default:
throw new Error(`Unknown error code: ${code}`);
}
},
};
export default gmailApiErrorMocks;

View File

@ -36,7 +36,7 @@ export class GmailGetHistoryService {
labelId,
})
.catch((error) => {
this.gmailHandleErrorService.handleError(error);
this.gmailHandleErrorService.handleGmailMessageListFetchError(error);
return {
data: {

View File

@ -19,6 +19,7 @@ import {
GetPartialMessageListResponse,
} from 'src/modules/messaging/message-import-manager/services/messaging-get-message-list.service';
import { assertNotNull } from 'src/utils/assert';
import { isDefined } from 'src/utils/is-defined';
@Injectable()
export class GmailGetMessageListService {
@ -53,7 +54,7 @@ export class GmailGetMessageListService {
),
})
.catch((error) => {
this.gmailHandleErrorService.handleError(error);
this.gmailHandleErrorService.handleGmailMessageListFetchError(error);
return {
data: {
@ -79,13 +80,23 @@ export class GmailGetMessageListService {
messageExternalIds.push(...messages.map((message) => message.id));
}
if (!isDefined(firstMessageExternalId)) {
throw new MessageImportDriverException(
`No firstMessageExternalId found for connected account ${connectedAccount.id}`,
MessageImportDriverExceptionCode.UNKNOWN,
);
}
const firstMessageContent = await gmailClient.users.messages
.get({
userId: 'me',
id: firstMessageExternalId,
})
.catch((error) => {
this.gmailHandleErrorService.handleError(error);
this.gmailHandleErrorService.handleGmailMessagesImportError(
error,
firstMessageExternalId as string,
);
});
const nextSyncCursor = firstMessageContent?.data?.historyId;

View File

@ -77,11 +77,7 @@ export class GmailGetMessagesService {
const messages = parsedResponses.map((response, index) => {
if ('error' in response) {
if (response.error.code === 404) {
return null;
}
this.gmailHandleErrorService.handleError(
this.gmailHandleErrorService.handleGmailMessagesImportError(
response.error,
messageIds[index],
);

View File

@ -1,33 +1,37 @@
import { Injectable } from '@nestjs/common';
import { parseGaxiosError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gaxios-error.util';
import { parseGmailError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-error.util';
import { parseGmailMessageListFetchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message-list-fetch-error.util';
import { parseGmailMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-messages-import-error.util';
@Injectable()
export class GmailHandleErrorService {
constructor() {}
public handleError(error: any, messageExternalId?: string): void {
if (
error.code &&
[
'ECONNRESET',
'ENOTFOUND',
'ECONNABORTED',
'ETIMEDOUT',
'ERR_NETWORK',
].includes(error.code)
) {
throw parseGaxiosError(error);
}
if (error.code != 410) {
const gmailError = {
code: error.code,
reason: `${error?.errors?.[0].reason || error.response?.data?.error || ''}`,
message: `${error?.errors?.[0].message || error.response?.data?.error_description || ''}${messageExternalId ? ` for message with externalId: ${messageExternalId}` : ''}`,
};
public handleGmailMessageListFetchError(error: any): void {
const gaxiosError = parseGaxiosError(error);
throw parseGmailError(gmailError);
if (gaxiosError) {
throw gaxiosError;
}
throw parseGmailMessageListFetchError(error);
}
public handleGmailMessagesImportError(
error: any,
messageExternalId: string,
): void {
const gaxiosError = parseGaxiosError(error);
if (gaxiosError) {
throw gaxiosError;
}
const gmailError = parseGmailMessagesImportError(error, messageExternalId);
if (gmailError) {
throw gmailError;
}
}
}

View File

@ -0,0 +1,60 @@
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import gaxiosErrorMocks from 'src/modules/messaging/message-import-manager/drivers/gmail/mocks/gaxios-error-mocks';
import { parseGaxiosError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gaxios-error.util';
describe('parseGaxiosError', () => {
it('should return a MessageImportDriverException for ECONNRESET', () => {
const error = gaxiosErrorMocks.getError('ECONNRESET');
const result = parseGaxiosError(error);
expect(result).toBeInstanceOf(MessageImportDriverException);
expect(result?.message).toBe(error.message);
expect(result?.code).toBe(MessageImportDriverExceptionCode.TEMPORARY_ERROR);
});
it('should return a MessageImportDriverException for ENOTFOUND', () => {
const error = gaxiosErrorMocks.getError('ENOTFOUND');
const result = parseGaxiosError(error);
expect(result).toBeInstanceOf(MessageImportDriverException);
expect(result?.message).toBe(error.message);
expect(result?.code).toBe(MessageImportDriverExceptionCode.TEMPORARY_ERROR);
});
it('should return a MessageImportDriverException for ECONNABORTED', () => {
const error = gaxiosErrorMocks.getError('ECONNABORTED');
const result = parseGaxiosError(error);
expect(result).toBeInstanceOf(MessageImportDriverException);
expect(result?.message).toBe(error.message);
expect(result?.code).toBe(MessageImportDriverExceptionCode.TEMPORARY_ERROR);
});
it('should return a MessageImportDriverException for ETIMEDOUT', () => {
const error = gaxiosErrorMocks.getError('ETIMEDOUT');
const result = parseGaxiosError(error);
expect(result).toBeInstanceOf(MessageImportDriverException);
expect(result?.message).toBe(error.message);
expect(result?.code).toBe(MessageImportDriverExceptionCode.TEMPORARY_ERROR);
});
it('should return a MessageImportDriverException for ERR_NETWORK', () => {
const error = gaxiosErrorMocks.getError('ERR_NETWORK');
const result = parseGaxiosError(error);
expect(result).toBeInstanceOf(MessageImportDriverException);
expect(result?.message).toBe(error.message);
expect(result?.code).toBe(MessageImportDriverExceptionCode.TEMPORARY_ERROR);
});
it('should return undefined for unknown error codes', () => {
const error = { code: 'UNKNOWN_ERROR' } as any;
const result = parseGaxiosError(error);
expect(result).toBeUndefined();
});
});

View File

@ -0,0 +1,122 @@
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import gmailApiErrorMocks from 'src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks';
import { parseGmailMessageListFetchError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message-list-fetch-error.util';
describe('parseGmailMessageListFetchError', () => {
it('should handle 400 Bad Request', () => {
const error = gmailApiErrorMocks.getError(400);
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(MessageImportDriverExceptionCode.UNKNOWN);
});
it('should handle 400 Invalid Grant', () => {
const error = gmailApiErrorMocks.getError(400, 'invalid_grant');
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
});
it('should handle 400 Failed Precondition', () => {
const error = gmailApiErrorMocks.getError(400, 'failedPrecondition');
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
it('should handle 401 Invalid Credentials', () => {
const error = gmailApiErrorMocks.getError(401);
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
});
it('should handle 403 Daily Limit Exceeded', () => {
const error = gmailApiErrorMocks.getError(403, 'dailyLimit');
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
it('should handle 403 User Rate Limit Exceeded', () => {
const error = gmailApiErrorMocks.getError(403, 'userRateLimit');
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
it('should handle 403 Rate Limit Exceeded', () => {
const error = gmailApiErrorMocks.getError(403, 'rateLimit');
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
it('should handle 403 Domain Policy Error', () => {
const error = gmailApiErrorMocks.getError(403, 'domainPolicy');
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
});
it('should handle 404 Not Found', () => {
const error = gmailApiErrorMocks.getError(404);
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(MessageImportDriverExceptionCode.NOT_FOUND);
});
it('should handle 410 Gone', () => {
const error = gmailApiErrorMocks.getError(410);
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(MessageImportDriverExceptionCode.UNKNOWN);
});
it('should handle 429 Too Many Requests', () => {
const error = gmailApiErrorMocks.getError(429, 'concurrent');
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
it('should handle 500 Backend Error', () => {
const error = gmailApiErrorMocks.getError(500);
const exception = parseGmailMessageListFetchError(error.error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
});

View File

@ -0,0 +1,185 @@
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import gmailApiErrorMocks from 'src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks';
import { parseGmailMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-messages-import-error.util';
const messageExternalId = '123';
describe('parseGmailMessagesImportError', () => {
it('should handle 400 Bad Request', () => {
const error = gmailApiErrorMocks.getError(400);
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(MessageImportDriverExceptionCode.UNKNOWN);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
it('should handle 400 Invalid Grant', () => {
const error = gmailApiErrorMocks.getError(400, 'invalid_grant');
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
it('should handle 400 Failed Precondition', () => {
const error = gmailApiErrorMocks.getError(400, 'failedPrecondition');
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
it('should handle 401 Invalid Credentials', () => {
const error = gmailApiErrorMocks.getError(401);
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
it('should handle 403 Daily Limit Exceeded', () => {
const error = gmailApiErrorMocks.getError(403, 'dailyLimit');
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
it('should handle 403 User Rate Limit Exceeded', () => {
const error = gmailApiErrorMocks.getError(403, 'userRateLimit');
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
it('should handle 403 Rate Limit Exceeded', () => {
const error = gmailApiErrorMocks.getError(403, 'rateLimit');
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
it('should handle 403 Domain Policy Error', () => {
const error = gmailApiErrorMocks.getError(403, 'domainPolicy');
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
it('should handle 404 Not Found', () => {
const error = gmailApiErrorMocks.getError(404);
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBe(undefined);
});
it('should handle 410 Gone', () => {
const error = gmailApiErrorMocks.getError(410);
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBe(undefined);
});
it('should handle 429 Too Many Requests', () => {
const error = gmailApiErrorMocks.getError(429, 'concurrent');
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
it('should handle 500 Backend Error', () => {
const error = gmailApiErrorMocks.getError(500);
const exception = parseGmailMessagesImportError(
error.error,
messageExternalId,
);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception?.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
expect(exception?.message).toBe(
`${error.error.errors[0].message} for message with externalId: ${messageExternalId}`,
);
});
});

View File

@ -7,7 +7,7 @@ import {
export const parseGaxiosError = (
error: GaxiosError,
): MessageImportDriverException => {
): MessageImportDriverException | undefined => {
const { code } = error;
switch (code) {
@ -22,9 +22,6 @@ export const parseGaxiosError = (
);
default:
return new MessageImportDriverException(
error.message,
MessageImportDriverExceptionCode.UNKNOWN,
);
return undefined;
}
};

View File

@ -3,12 +3,17 @@ import {
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
export const parseGmailError = (error: {
export const parseGmailMessageListFetchError = (error: {
code?: number;
reason: string;
message: string;
errors: {
reason: string;
message: string;
}[];
}): MessageImportDriverException => {
const { code, reason, message } = error;
const { code, errors } = error;
const reason = errors?.[0]?.reason;
const message = errors?.[0]?.message;
switch (code) {
case 400:
@ -45,19 +50,23 @@ export const parseGmailError = (error: {
case 403:
if (
reason === 'rateLimitExceeded' ||
reason === 'userRateLimitExceeded'
reason === 'userRateLimitExceeded' ||
reason === 'dailyLimitExceeded'
) {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
} else {
}
if (reason === 'domainPolicy') {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
break;
case 401:
return new MessageImportDriverException(
message,
@ -70,13 +79,10 @@ export const parseGmailError = (error: {
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
} else {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.UNKNOWN,
);
}
break;
default:
break;
}

View File

@ -0,0 +1,94 @@
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
export const parseGmailMessagesImportError = (
error: {
code?: number;
errors: {
reason: string;
message: string;
}[];
},
messageExternalId: string,
): MessageImportDriverException | undefined => {
const { code, errors } = error;
const reason = errors?.[0]?.reason;
const message = `${errors?.[0]?.message} for message with externalId: ${messageExternalId}`;
switch (code) {
case 400:
if (reason === 'invalid_grant') {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
if (reason === 'failedPrecondition') {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.UNKNOWN,
);
case 404:
case 410:
return undefined;
case 429:
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
case 403:
if (
reason === 'rateLimitExceeded' ||
reason === 'userRateLimitExceeded' ||
reason === 'dailyLimitExceeded'
) {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
if (reason === 'domainPolicy') {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
break;
case 401:
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
case 500:
if (reason === 'backendError') {
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
break;
default:
break;
}
return new MessageImportDriverException(
message,
MessageImportDriverExceptionCode.UNKNOWN,
);
};