mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(gmail-poller): resolve OAuth credential provider aliases (#5770)
This commit is contained in:
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user