From 3943e5e89b9f32c3f22ab8547e8207777100762f Mon Sep 17 00:00:00 2001 From: Justin Blumencranz <96924014+j15z@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:15:38 -0700 Subject: [PATCH] fix(gmail-poller): resolve OAuth credential provider aliases --- apps/sim/lib/webhooks/deploy.test.ts | 44 +++++++++++++++++++++++++--- apps/sim/lib/webhooks/deploy.ts | 10 +++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/apps/sim/lib/webhooks/deploy.test.ts b/apps/sim/lib/webhooks/deploy.test.ts index 3958b74ab21..c5a96499b86 100644 --- a/apps/sim/lib/webhooks/deploy.test.ts +++ b/apps/sim/lib/webhooks/deploy.test.ts @@ -1,10 +1,28 @@ /** * @vitest-environment node */ +import { credential } from '@sim/db/schema' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { SubBlockConfig } from '@/blocks/types' import type { BlockState } from '@/stores/workflows/workflow/types' +const { mockEq, mockFrom, mockLimit, mockSelect, mockWhere } = vi.hoisted(() => ({ + mockEq: vi.fn((left: unknown, right: unknown) => ({ left, right })), + mockFrom: vi.fn(), + mockLimit: vi.fn(), + mockSelect: vi.fn(), + mockWhere: vi.fn(), +})) + +vi.mock('@sim/db', () => ({ db: { select: mockSelect } })) +vi.mock('drizzle-orm', () => ({ + and: vi.fn((...conditions: unknown[]) => ({ conditions })), + eq: mockEq, + inArray: vi.fn((...args: unknown[]) => ({ args })), + isNull: vi.fn((value: unknown) => ({ value })), + or: vi.fn((...conditions: unknown[]) => ({ conditions })), +})) + // deploy.ts pulls in the trigger/block/provider registries at module load; none are exercised by // buildProviderConfig (a pure function), so stub them to keep this unit test fast and isolated. vi.mock('@/blocks', () => ({ getBlock: vi.fn() })) @@ -22,7 +40,7 @@ vi.mock('@/lib/webhooks/pending-verification', () => ({ PendingWebhookVerificationTracker: vi.fn(), })) -import { buildProviderConfig } from '@/lib/webhooks/deploy' +import { buildProviderConfig, resolveTriggerCredentialId } from '@/lib/webhooks/deploy' const trigger = (subBlocks: Partial[]): { subBlocks: SubBlockConfig[] } => ({ subBlocks: subBlocks as SubBlockConfig[], @@ -59,16 +77,22 @@ function makeBlock( } as unknown as BlockState } -describe('buildProviderConfig canonical collapse', () => { - beforeEach(() => vi.clearAllMocks()) +beforeEach(() => { + vi.clearAllMocks() + mockSelect.mockReturnValue({ from: mockFrom }) + mockFrom.mockReturnValue({ where: mockWhere }) + mockWhere.mockReturnValue({ limit: mockLimit }) + mockLimit.mockResolvedValue([{ id: 'credential-1' }]) +}) +describe('buildProviderConfig canonical collapse', () => { it('writes the basic value under the canonical key in basic mode', () => { const block = makeBlock('google_drive_poller', { folderId: 'BASIC' }) const { providerConfig } = buildProviderConfig(block, 'google_drive_poller', driveTrigger) expect(providerConfig.folderId).toBe('BASIC') }) - it('returns the credential reference and canonical OAuth service for deploy validation', () => { + it('returns the credential reference and OAuth service for deploy validation', () => { const block = makeBlock('google_drive_poller', { triggerCredentials: 'credential-1' }) const result = buildProviderConfig(block, 'google_drive_poller', driveTrigger) @@ -132,3 +156,15 @@ describe('buildProviderConfig canonical collapse', () => { expect(providerConfig.tableId).toBe('ACTIVE') }) }) + +describe('resolveTriggerCredentialId', () => { + it('canonicalizes an OAuth service alias at the credential lookup boundary', async () => { + await resolveTriggerCredentialId('credential-1', 'workspace-1', 'gmail') + + expect(mockEq).toHaveBeenCalledWith(credential.workspaceId, 'workspace-1') + expect(mockEq).toHaveBeenCalledWith(credential.type, 'oauth') + expect(mockEq).toHaveBeenCalledWith(credential.providerId, 'google-email') + expect(mockEq).toHaveBeenCalledWith(credential.id, 'credential-1') + expect(mockEq).toHaveBeenCalledWith(credential.accountId, 'credential-1') + }) +}) diff --git a/apps/sim/lib/webhooks/deploy.ts b/apps/sim/lib/webhooks/deploy.ts index c866ee7d9a7..d667f0391d5 100644 --- a/apps/sim/lib/webhooks/deploy.ts +++ b/apps/sim/lib/webhooks/deploy.ts @@ -5,6 +5,7 @@ import { getErrorMessage } from '@sim/utils/errors' import { generateShortId } from '@sim/utils/id' import { and, eq, inArray, isNull, or } from 'drizzle-orm' import type { NextRequest } from 'next/server' +import { getProviderIdFromServiceId } from '@/lib/oauth' import { WebhookPathClaimConflictError } from '@/lib/webhooks/path-claims' import { PendingWebhookVerificationTracker } from '@/lib/webhooks/pending-verification' import { @@ -334,13 +335,16 @@ export function buildProviderConfig( /** * Resolves a trigger credential reference to its canonical platform credential ID while enforcing - * that the credential belongs to the deployed workflow's workspace and OAuth service. + * that the credential belongs to the deployed workflow's workspace and OAuth provider. + * + * Exported for unit testing the service-to-provider boundary; not part of the public deploy API. */ -async function resolveTriggerCredentialId( +export async function resolveTriggerCredentialId( credentialReference: string, workspaceId: string, serviceId: string ): Promise { + const providerId = getProviderIdFromServiceId(serviceId) const [resolvedCredential] = await db .select({ id: credential.id }) .from(credential) @@ -348,7 +352,7 @@ async function resolveTriggerCredentialId( and( eq(credential.workspaceId, workspaceId), eq(credential.type, 'oauth'), - eq(credential.providerId, serviceId), + eq(credential.providerId, providerId), or(eq(credential.id, credentialReference), eq(credential.accountId, credentialReference)) ) )