fix(gmail-poller): resolve OAuth credential provider aliases (#5770)

This commit is contained in:
Justin Blumencranz
2026-07-20 13:18:25 -07:00
committed by GitHub
parent 4d76385915
commit 031a29546f
2 changed files with 47 additions and 7 deletions
+40 -4
View File
@@ -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<SubBlockConfig>[]): { 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')
})
})
+7 -3
View File
@@ -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<string | null> {
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))
)
)