mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
* fix(executor): skip Response block formatting for internal JWT callers
The workflow executor tool received `{error: true}` despite successful child
workflow execution when the child had a Response block. This happened because
`createHttpResponseFromBlock()` hijacked the response with raw user-defined
data, and the executor's `transformResponse` expected the standard
`{success, executionId, output, metadata}` wrapper.
Fix: skip Response block formatting when `authType === INTERNAL_JWT` since
Response blocks are designed for external API consumers, not internal
workflow-to-workflow calls. Also extract `AuthType` constants from magic
strings across all auth type comparisons in the codebase.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(executor): add route-level tests for Response block auth gating
Verify that internal JWT callers receive standard format while external
callers (API key, session) get Response block formatting. Tests the
server-side condition directly using workflowHasResponseBlock and
createHttpResponseFromBlock with AuthType constants.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(testing): add AuthType to all hybrid auth test mocks
Route code now imports AuthType from @/lib/auth/hybrid, so test mocks
must export it too. Added AuthTypeMock to @sim/testing and included it
in all 15 test files that mock the hybrid auth module.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
153 lines
3.9 KiB
TypeScript
153 lines
3.9 KiB
TypeScript
/**
|
|
* Tests for OAuth credentials API route
|
|
*
|
|
* @vitest-environment node
|
|
*/
|
|
|
|
import { NextRequest } from 'next/server'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { mockCheckSessionOrInternalAuth, mockLogger } = vi.hoisted(() => {
|
|
const logger = {
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
debug: vi.fn(),
|
|
trace: vi.fn(),
|
|
fatal: vi.fn(),
|
|
child: vi.fn(),
|
|
}
|
|
return {
|
|
mockCheckSessionOrInternalAuth: vi.fn(),
|
|
mockLogger: logger,
|
|
}
|
|
})
|
|
|
|
vi.mock('@/lib/auth/hybrid', () => ({
|
|
AuthType: { SESSION: 'session', API_KEY: 'api_key', INTERNAL_JWT: 'internal_jwt' },
|
|
checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth,
|
|
}))
|
|
|
|
vi.mock('@/lib/core/utils/request', () => ({
|
|
generateRequestId: vi.fn().mockReturnValue('mock-request-id'),
|
|
}))
|
|
|
|
vi.mock('@/lib/credentials/oauth', () => ({
|
|
syncWorkspaceOAuthCredentialsForUser: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/lib/workflows/utils', () => ({
|
|
authorizeWorkflowByWorkspacePermission: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/lib/workspaces/permissions/utils', () => ({
|
|
checkWorkspaceAccess: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@sim/db/schema', () => ({
|
|
account: {
|
|
userId: 'userId',
|
|
providerId: 'providerId',
|
|
id: 'id',
|
|
scope: 'scope',
|
|
updatedAt: 'updatedAt',
|
|
},
|
|
credential: {
|
|
id: 'id',
|
|
workspaceId: 'workspaceId',
|
|
type: 'type',
|
|
displayName: 'displayName',
|
|
providerId: 'providerId',
|
|
accountId: 'accountId',
|
|
},
|
|
credentialMember: {
|
|
id: 'id',
|
|
credentialId: 'credentialId',
|
|
userId: 'userId',
|
|
status: 'status',
|
|
},
|
|
user: { email: 'email', id: 'id' },
|
|
}))
|
|
|
|
vi.mock('@sim/logger', () => ({
|
|
createLogger: vi.fn().mockReturnValue(mockLogger),
|
|
}))
|
|
|
|
import { GET } from '@/app/api/auth/oauth/credentials/route'
|
|
|
|
describe('OAuth Credentials API Route', () => {
|
|
function createMockRequestWithQuery(method = 'GET', queryParams = ''): NextRequest {
|
|
const url = `http://localhost:3000/api/auth/oauth/credentials${queryParams}`
|
|
return new NextRequest(new URL(url), { method })
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('should handle unauthenticated user', async () => {
|
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
|
success: false,
|
|
error: 'Authentication required',
|
|
})
|
|
|
|
const req = createMockRequestWithQuery('GET', '?provider=google')
|
|
|
|
const response = await GET(req)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(401)
|
|
expect(data.error).toBe('User not authenticated')
|
|
expect(mockLogger.warn).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle missing provider parameter', async () => {
|
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
|
success: true,
|
|
userId: 'user-123',
|
|
authType: 'session',
|
|
})
|
|
|
|
const req = createMockRequestWithQuery('GET')
|
|
|
|
const response = await GET(req)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(400)
|
|
expect(data.error).toBe('Provider or credentialId is required')
|
|
expect(mockLogger.warn).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should handle no credentials found', async () => {
|
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
|
success: true,
|
|
userId: 'user-123',
|
|
authType: 'session',
|
|
})
|
|
|
|
const req = createMockRequestWithQuery('GET', '?provider=github')
|
|
|
|
const response = await GET(req)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.credentials).toHaveLength(0)
|
|
})
|
|
|
|
it('should return empty credentials when no workspace context', async () => {
|
|
mockCheckSessionOrInternalAuth.mockResolvedValueOnce({
|
|
success: true,
|
|
userId: 'user-123',
|
|
authType: 'session',
|
|
})
|
|
|
|
const req = createMockRequestWithQuery('GET', '?provider=google-email')
|
|
|
|
const response = await GET(req)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.credentials).toHaveLength(0)
|
|
})
|
|
})
|