Fix Google API Availability and a edge case (#18150)

First run shows a real customer account, and the second run is my
account with all the permissions.

<img width="1490" height="386" alt="SCR-20260221-bmfd"
src="https://github.com/user-attachments/assets/1e2bd269-67ab-450d-9bf3-3c4872b90773"
/>


Simulated edge case if user has access to neither

<img width="343" height="162" alt="SCR-20260221-bloo"
src="https://github.com/user-attachments/assets/a697302b-e8b0-432e-bd15-f689da06dc0e"
/>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
neo773
2026-02-25 14:02:19 +01:00
committed by GitHub
co-authored by cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
parent 98e73791a4
commit e7f958c9cf
3 changed files with 125 additions and 3 deletions
@@ -211,6 +211,112 @@ describe('GoogleApisServiceAvailabilityService', () => {
});
});
it('should return messaging unavailable when Google returns generic precondition check failed error', async () => {
mockTwentyConfigService.get.mockImplementation((key) => {
if (key === 'AUTH_GOOGLE_CLIENT_ID') return 'client-id';
if (key === 'AUTH_GOOGLE_CLIENT_SECRET') return 'client-secret';
if (key === 'MESSAGING_PROVIDER_GMAIL_ENABLED') return true;
if (key === 'CALENDAR_PROVIDER_GOOGLE_ENABLED') return true;
return undefined;
});
const preconditionCheckFailedError = {
response: {
status: 400,
data: {
error: {
code: 400,
message: 'Precondition check failed.',
errors: [
{
message: 'Precondition check failed.',
domain: 'global',
reason: 'failedPrecondition',
},
],
status: 'FAILED_PRECONDITION',
},
},
},
};
const mockGmailClient = {
users: {
getProfile: jest.fn().mockRejectedValue(preconditionCheckFailedError),
},
};
const mockCalendarClient = {
events: {
list: jest.fn().mockResolvedValue({ data: {} }),
},
};
(google.gmail as jest.Mock).mockReturnValue(mockGmailClient);
(google.calendar as jest.Mock).mockReturnValue(mockCalendarClient);
const result = await service.checkServicesAvailability('access-token');
expect(result).toEqual({
isMessagingAvailable: false,
isCalendarAvailable: true,
});
});
it('should return Calendar unavailable when Google returns generic precondition check failed error', async () => {
mockTwentyConfigService.get.mockImplementation((key) => {
if (key === 'AUTH_GOOGLE_CLIENT_ID') return 'client-id';
if (key === 'AUTH_GOOGLE_CLIENT_SECRET') return 'client-secret';
if (key === 'MESSAGING_PROVIDER_GMAIL_ENABLED') return true;
if (key === 'CALENDAR_PROVIDER_GOOGLE_ENABLED') return true;
return undefined;
});
const preconditionCheckFailedError = {
response: {
status: 400,
data: {
error: {
code: 400,
message: 'Precondition check failed.',
errors: [
{
message: 'Precondition check failed.',
domain: 'global',
reason: 'failedPrecondition',
},
],
status: 'FAILED_PRECONDITION',
},
},
},
};
const mockGmailClient = {
users: {
getProfile: jest.fn().mockResolvedValue({ data: {} }),
},
};
const mockCalendarClient = {
events: {
list: jest.fn().mockRejectedValue(preconditionCheckFailedError),
},
};
(google.gmail as jest.Mock).mockReturnValue(mockGmailClient);
(google.calendar as jest.Mock).mockReturnValue(mockCalendarClient);
const result = await service.checkServicesAvailability('access-token');
expect(result).toEqual({
isMessagingAvailable: true,
isCalendarAvailable: false,
});
});
it('should throw error for non-service-availability related errors', async () => {
mockTwentyConfigService.get.mockImplementation((key) => {
if (key === 'AUTH_GOOGLE_CLIENT_ID') return 'client-id';
@@ -125,9 +125,18 @@ export class GoogleApisServiceAvailabilityService {
}
const isFailedPrecondition = firstError.reason === 'failedPrecondition';
const isServiceNotEnabled =
firstError.message?.includes('service not enabled') ?? false;
return isFailedPrecondition && isServiceNotEnabled;
const isServiceNotEnabled =
firstError.message?.toLowerCase()?.includes('service not enabled') ??
false;
const isPreconditionCheckFailed =
firstError.message
?.toLowerCase()
?.includes('precondition check failed') ?? false;
return (
isFailedPrecondition && (isServiceNotEnabled || isPreconditionCheckFailed)
);
}
}
@@ -118,6 +118,13 @@ export class GoogleAPIsService {
input.accessToken,
);
if (!isMessagingAvailable && !isCalendarAvailable) {
throw new AuthException(
'Unable to connect: Your Google account does not have access to Gmail or Calendar. Please contact your workspace administrator.',
AuthExceptionCode.INSUFFICIENT_SCOPES,
);
}
const authContext = buildSystemAuthContext(workspaceId);
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(