diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks.ts index 6f2efa7bfc..7f7570b531 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks.ts @@ -3,112 +3,171 @@ import { type GmailApiError } from 'src/modules/messaging/message-import-manager const gmailApiErrorMocks = { // 400 Bad Request - Invalid query parameters badRequest: { - code: '400', + code: 400, message: 'badRequest', }, // 400 Invalid Grant invalidGrant: { - code: '400', + code: 400, message: 'invalid_grant', }, // 400 Failed Precondition failedPrecondition: { - code: '400', + code: 400, message: 'failedPrecondition', }, invalidCredentials: { - code: '401', + code: 401, message: 'authError', }, notFound: { - code: '404', + code: 404, message: 'notFound', }, gone: { - code: '410', + code: 410, message: 'resourceGone', }, dailyLimitExceeded: { - code: '403', + code: 403, message: 'dailyLimitExceeded', }, userRateLimitExceeded: { - code: '403', + code: 403, message: 'userRateLimitExceeded', }, rateLimitExceeded: { - code: '403', + code: 403, message: 'rateLimitExceeded', }, domainPolicyError: { - code: '403', + code: 403, message: 'domainPolicy', }, tooManyConcurrentRequests: { - code: '429', + code: 429, message: 'tooManyConcurrentRequests', }, backendError: { - code: '500', + code: 500, message: 'backendError', }, - - getError: function (code: number, type?: string): GmailApiError { - 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; +const convertToErrorWithErrorCodeStringOrNumber = ({ + error, + errorCodeAsString, +}: { + error: GmailApiError; + errorCodeAsString: boolean; +}): GmailApiError => { + return { + code: errorCodeAsString ? error.code.toString() : error.code, + message: error.message, + }; +}; + +export const getGmailApiError = ({ + code, + type, + errorCodeAsString = false, +}: { + code: number; + type?: string; + errorCodeAsString?: boolean; +}): GmailApiError => { + switch (code) { + case 400: + switch (type) { + case 'invalid_grant': + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.invalidGrant, + errorCodeAsString, + }); + case 'failedPrecondition': + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.failedPrecondition, + errorCodeAsString, + }); + default: + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.badRequest, + errorCodeAsString, + }); + } + case 401: + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.invalidCredentials, + errorCodeAsString, + }); + case 403: + switch (type) { + case 'dailyLimit': + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.dailyLimitExceeded, + errorCodeAsString, + }); + case 'userRateLimit': + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.userRateLimitExceeded, + errorCodeAsString, + }); + case 'rateLimit': + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.rateLimitExceeded, + errorCodeAsString, + }); + case 'domainPolicy': + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.domainPolicyError, + errorCodeAsString, + }); + default: + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.rateLimitExceeded, + errorCodeAsString, + }); + } + case 404: + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.notFound, + errorCodeAsString, + }); + case 410: + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.gone, + errorCodeAsString, + }); + case 429: + switch (type) { + case 'concurrent': + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.tooManyConcurrentRequests, + errorCodeAsString, + }); + default: + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.tooManyConcurrentRequests, + errorCodeAsString, + }); + } + case 500: + return convertToErrorWithErrorCodeStringOrNumber({ + error: gmailApiErrorMocks.backendError, + errorCodeAsString, + }); + default: + throw new Error(`Unknown error code: ${code}`); + } +}; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type.ts index efe35f0757..22132d2cc7 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type.ts @@ -1,4 +1,4 @@ export type GmailApiError = { - code: string; + code: number | string; message: string; }; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/parse-gmail-message-list-fetch-error.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/parse-gmail-message-list-fetch-error.spec.ts index 08d16fffb4..5706e25365 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/parse-gmail-message-list-fetch-error.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/parse-gmail-message-list-fetch-error.spec.ts @@ -2,12 +2,15 @@ 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 { getGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/mocks/gmail-api-error-mocks'; import { parseGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util'; describe('parseGmailApiError', () => { it('should handle 400 Bad Request', () => { - const error = gmailApiErrorMocks.getError(400); + const error = getGmailApiError({ + code: 400, + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -15,7 +18,11 @@ describe('parseGmailApiError', () => { }); it('should handle 400 Invalid Grant', () => { - const error = gmailApiErrorMocks.getError(400, 'invalid_grant'); + const error = getGmailApiError({ + code: 400, + type: 'invalid_grant', + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -25,7 +32,11 @@ describe('parseGmailApiError', () => { }); it('should handle 400 Failed Precondition', () => { - const error = gmailApiErrorMocks.getError(400, 'failedPrecondition'); + const error = getGmailApiError({ + code: 400, + type: 'failedPrecondition', + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -35,7 +46,10 @@ describe('parseGmailApiError', () => { }); it('should handle 401 Invalid Credentials', () => { - const error = gmailApiErrorMocks.getError(401); + const error = getGmailApiError({ + code: 401, + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -45,7 +59,11 @@ describe('parseGmailApiError', () => { }); it('should handle 403 Daily Limit Exceeded', () => { - const error = gmailApiErrorMocks.getError(403, 'dailyLimit'); + const error = getGmailApiError({ + code: 403, + type: 'dailyLimit', + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -55,7 +73,11 @@ describe('parseGmailApiError', () => { }); it('should handle 403 User Rate Limit Exceeded', () => { - const error = gmailApiErrorMocks.getError(403, 'userRateLimit'); + const error = getGmailApiError({ + code: 403, + type: 'userRateLimit', + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -65,7 +87,11 @@ describe('parseGmailApiError', () => { }); it('should handle 403 Rate Limit Exceeded', () => { - const error = gmailApiErrorMocks.getError(403, 'rateLimit'); + const error = getGmailApiError({ + code: 403, + type: 'rateLimit', + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -75,7 +101,11 @@ describe('parseGmailApiError', () => { }); it('should handle 403 Domain Policy Error', () => { - const error = gmailApiErrorMocks.getError(403, 'domainPolicy'); + const error = getGmailApiError({ + code: 403, + type: 'domainPolicy', + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -85,7 +115,10 @@ describe('parseGmailApiError', () => { }); it('should handle 404 as sync cursor error', () => { - const error = gmailApiErrorMocks.getError(404); + const error = getGmailApiError({ + code: 404, + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -95,7 +128,10 @@ describe('parseGmailApiError', () => { }); it('should handle 410 Gone', () => { - const error = gmailApiErrorMocks.getError(410); + const error = getGmailApiError({ + code: 410, + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -103,7 +139,11 @@ describe('parseGmailApiError', () => { }); it('should handle 429 Too Many Requests', () => { - const error = gmailApiErrorMocks.getError(429, 'concurrent'); + const error = getGmailApiError({ + code: 429, + type: 'concurrent', + errorCodeAsString: false, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); @@ -113,7 +153,169 @@ describe('parseGmailApiError', () => { }); it('should handle 500 Backend Error', () => { - const error = gmailApiErrorMocks.getError(500); + const error = getGmailApiError({ + code: 500, + errorCodeAsString: false, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); + }); + + it('should handle 400 Bad Request with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 400, + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe(MessageImportDriverExceptionCode.UNKNOWN); + }); + + it('should handle 400 Invalid Grant with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 400, + type: 'invalid_grant', + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, + ); + }); + + it('should handle 400 Failed Precondition with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 400, + type: 'failedPrecondition', + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); + }); + + it('should handle 401 Invalid Credentials with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 401, + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, + ); + }); + + it('should handle 403 Daily Limit Exceeded with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 403, + type: 'dailyLimit', + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); + }); + + it('should handle 403 User Rate Limit Exceeded with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 403, + type: 'userRateLimit', + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); + }); + + it('should handle 403 Rate Limit Exceeded with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 403, + type: 'rateLimit', + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); + }); + + it('should handle 403 Domain Policy Error with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 403, + type: 'domainPolicy', + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, + ); + }); + + it('should handle 404 as sync cursor error with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 404, + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR, + ); + }); + + it('should handle 410 Gone with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 410, + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe(MessageImportDriverExceptionCode.UNKNOWN); + }); + + it('should handle 429 Too Many Requests with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 429, + type: 'concurrent', + errorCodeAsString: true, + }); + const exception = parseGmailApiError(error); + + expect(exception).toBeInstanceOf(MessageImportDriverException); + expect(exception.code).toBe( + MessageImportDriverExceptionCode.TEMPORARY_ERROR, + ); + }); + + it('should handle 500 Backend Error with errorCodeAsString', () => { + const error = getGmailApiError({ + code: 500, + errorCodeAsString: true, + }); const exception = parseGmailApiError(error); expect(exception).toBeInstanceOf(MessageImportDriverException); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error-error.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error-error.util.ts index 5e675532f3..48db413567 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error-error.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error-error.util.ts @@ -7,7 +7,7 @@ export const isGmailApiError = (error: unknown): error is GmailApiError => { if ( !('code' in error) || - typeof error.code !== 'string' || + (typeof error.code !== 'number' && typeof error.code !== 'string') || !('message' in error) || typeof error.message !== 'string' ) { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util.ts index 6721e39423..a424869de7 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util.ts @@ -9,8 +9,10 @@ export const parseGmailApiError = ( ): MessageImportDriverException => { const { code, message } = error; - switch (code) { - case '400': + const codeAsNumber = Number(code); + + switch (codeAsNumber) { + case 400: if (message === 'invalid_grant') { return new MessageImportDriverException( message, @@ -36,19 +38,19 @@ export const parseGmailApiError = ( MessageImportDriverExceptionCode.UNKNOWN, ); - case '404': + case 404: return new MessageImportDriverException( message, MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR, ); - case '429': + case 429: return new MessageImportDriverException( message, MessageImportDriverExceptionCode.TEMPORARY_ERROR, ); - case '403': + case 403: if ( message === 'rateLimitExceeded' || message === 'userRateLimitExceeded' || @@ -68,21 +70,21 @@ export const parseGmailApiError = ( break; - case '401': + case 401: return new MessageImportDriverException( message, MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS, ); - case '503': + case 503: return new MessageImportDriverException( message, MessageImportDriverExceptionCode.TEMPORARY_ERROR, ); - case '500': - case '502': - case '504': + case 500: + case 502: + case 504: if (message === 'backendError') { return new MessageImportDriverException( message,