|
1 | 1 | /** |
2 | 2 | * @vitest-environment node |
3 | 3 | */ |
4 | | -import { dbChainMockFns, resetDbChainMock } from '@sim/testing' |
| 4 | +import { dbChainMockFns, resetDbChainMock, resetEnvMock, setEnv } from '@sim/testing' |
5 | 5 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' |
6 | 6 |
|
7 | 7 | const mocks = vi.hoisted(() => ({ |
@@ -45,6 +45,7 @@ import { |
45 | 45 | SLACK_SEARCH_USER_SCOPES, |
46 | 46 | } from '@/lib/credential-groups/slack-managed-user-scopes' |
47 | 47 | import { slackCredentialGroupProviderAdapter } from '@/lib/credential-groups/slack-provider' |
| 48 | +import { createStandardOAuthCredentialGroupProviderAdapter } from '@/lib/credential-groups/standard-oauth-provider' |
48 | 49 | import { rejectManagedOAuthToken, resolveManagedOAuthToken } from '@/lib/credentials/managed-oauth' |
49 | 50 |
|
50 | 51 | function mondayCredentialRow() { |
@@ -102,6 +103,61 @@ describe('managed OAuth token resolution', () => { |
102 | 103 |
|
103 | 104 | afterEach(() => { |
104 | 105 | vi.useRealTimers() |
| 106 | + resetEnvMock() |
| 107 | + }) |
| 108 | + |
| 109 | + it('resolves a scopeless GitHub grant through the canonical provider policy', async () => { |
| 110 | + setEnv({ GITHUB_APP_CLIENT_ID: 'fixture-client', GITHUB_APP_CLIENT_SECRET: 'fixture-secret' }) |
| 111 | + const adapter = createStandardOAuthCredentialGroupProviderAdapter('github-repositories') |
| 112 | + const policy = await adapter.getPolicy(undefined, { workspaceId: 'workspace-1' }) |
| 113 | + expect(policy.requiredScopes).toEqual([]) |
| 114 | + mocks.getAdapter.mockReturnValue(adapter) |
| 115 | + dbChainMockFns.limit.mockResolvedValueOnce([ |
| 116 | + { |
| 117 | + ...mondayCredentialRow(), |
| 118 | + providerId: policy.providerId, |
| 119 | + authorizationAppId: policy.authorizationAppId, |
| 120 | + managedOauthScopeVersion: policy.scopeVersion, |
| 121 | + grantedScopes: [], |
| 122 | + accessTokenExpiresAt: new Date('2026-09-01T13:00:00Z'), |
| 123 | + }, |
| 124 | + ]) |
| 125 | + await expect( |
| 126 | + resolveManagedOAuthToken({ |
| 127 | + ...mondayTokenResolutionParams(), |
| 128 | + expectedProviderId: policy.providerId, |
| 129 | + requiredScopes: [], |
| 130 | + }) |
| 131 | + ).resolves.toMatchObject({ refreshed: false }) |
| 132 | + expect(mocks.decryptSecret).toHaveBeenCalledWith('encrypted-token-set') |
| 133 | + }) |
| 134 | + |
| 135 | + it.each([null, undefined])( |
| 136 | + 'rejects missing granted-scope metadata: %s', |
| 137 | + async (grantedScopes) => { |
| 138 | + dbChainMockFns.limit.mockResolvedValueOnce([{ ...mondayCredentialRow(), grantedScopes }]) |
| 139 | + await expect(resolveManagedOAuthToken(mondayTokenResolutionParams())).rejects.toMatchObject({ |
| 140 | + code: 'MANAGED_CREDENTIAL_INVALID_TOKEN_SET', |
| 141 | + }) |
| 142 | + expect(mocks.decryptSecret).not.toHaveBeenCalled() |
| 143 | + } |
| 144 | + ) |
| 145 | + |
| 146 | + it('rejects an empty grant when the provider policy requires scopes', async () => { |
| 147 | + mocks.getAdapter.mockReturnValue({ |
| 148 | + getPolicy: vi.fn().mockResolvedValue({ |
| 149 | + authorizationAppId: 'monday:monday-client-1', |
| 150 | + scopeVersion: 1, |
| 151 | + requiredScopes: ['boards:read', 'me:read'], |
| 152 | + }), |
| 153 | + hasRequiredScopes: (granted: string[], required: string[]) => |
| 154 | + required.every((scope) => granted.includes(scope)), |
| 155 | + }) |
| 156 | + dbChainMockFns.limit.mockResolvedValueOnce([{ ...mondayCredentialRow(), grantedScopes: [] }]) |
| 157 | + await expect( |
| 158 | + resolveManagedOAuthToken({ ...mondayTokenResolutionParams(), requiredScopes: [] }) |
| 159 | + ).rejects.toMatchObject({ code: 'MANAGED_CREDENTIAL_INSUFFICIENT_SCOPE' }) |
| 160 | + expect(mocks.decryptSecret).not.toHaveBeenCalled() |
105 | 161 | }) |
106 | 162 |
|
107 | 163 | function seedSlackSearchCredential( |
|
0 commit comments