Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ describe('organization integration invitations', () => {
label: 'Slack',
required: false,
slackBotCredentialId: 'slack-bot',
requiredScopes: ['search:read'],
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,6 @@ describe('organization provider configuration UI', () => {
label: slack.label,
required: slack.required,
slackBotCredentialId: slack.slackBotCredentialId,
requiredScopes: slack.requiredScopes,
},
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/** @vitest-environment node */
import { describe, expect, it } from 'vitest'
import {
type OrganizationAccountsSettings,
updateOrganizationAccountsContract,
} from '@/lib/api/contracts/organization-accounts'
import { getOrganizationAccountUpdateOptions } from '@/lib/credential-groups/organization-account-options'
import { CREDENTIAL_GROUP_STANDARD_OAUTH_PROVIDER_IDS } from '@/lib/credential-groups/providers'

describe('organization account update options', () => {
it.each([undefined, '12345678-1234-4123-8123-123456789012'])(
'satisfies the update contract with stored Slack scopes and bot %s',
(slackBotCredentialId) => {
const group: NonNullable<OrganizationAccountsSettings['credentialGroup']> = {
id: 'group-1',
workspaceId: null,
organizationId: 'org-1',
name: 'Connected accounts',
description: null,
mcpServers: [],
status: 'active',
createdAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:00:00Z',
options: [
...CREDENTIAL_GROUP_STANDARD_OAUTH_PROVIDER_IDS.map((provider) => ({
id: `${provider}-option`,
provider,
label: provider,
required: false,
status: 'active' as const,
configurationStatus: 'ready' as const,
})),
{
id: 'slack-option',
provider: 'slack',
label: 'Slack',
required: true,
status: 'active',
configurationStatus: 'ready',
slackBotCredentialId,
requiredScopes: ['search:read', 'channels:history'],
},
],
}

const options = getOrganizationAccountUpdateOptions(group)
const result = updateOrganizationAccountsContract.body.parse({ options })

expect(result.options).toEqual(options)
expect(result.options?.map(({ id }) => id)).toEqual(group.options.map(({ id }) => id))
expect(result.options?.at(-1)).toEqual({
id: 'slack-option',
provider: 'slack',
label: 'Slack',
required: true,
slackBotCredentialId,
})
expect(group.options.at(-1)).toHaveProperty('requiredScopes', [
'search:read',
'channels:history',
])
}
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
UpdateOrganizationAccountsBody,
} from '@/lib/api/contracts/organization-accounts'

/** Preserves option identities and custom Slack scopes while the server refreshes managed OAuth policies. */
/** Keeps option IDs so the server preserves saved scopes when refreshing provider policies. */
export function getOrganizationAccountUpdateOptions(
group: NonNullable<OrganizationAccountsSettings['credentialGroup']>
): NonNullable<UpdateOrganizationAccountsBody['options']> {
Expand All @@ -14,7 +14,6 @@ export function getOrganizationAccountUpdateOptions(
...common,
provider: 'slack',
slackBotCredentialId: option.slackBotCredentialId,
requiredScopes: option.requiredScopes,
}
: { ...common, provider: option.provider }
})
Expand Down
8 changes: 6 additions & 2 deletions apps/sim/lib/credential-groups/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe('Credential Group service', () => {
}
)

it('validates provider policy through the active update transaction', async () => {
it('preserves stored scopes while validating provider policy in the update transaction', async () => {
const option = {
id: 'option-1',
provider: 'slack' as const,
Expand Down Expand Up @@ -156,8 +156,12 @@ describe('Credential Group service', () => {
})
).resolves.toMatchObject({ id: 'group-1' })

expect(dbChainMockFns.set).toHaveBeenCalledWith(expect.objectContaining({ options: [option] }))
expect(mockGetPolicy).toHaveBeenCalledWith(
expect.objectContaining({ slackBotCredentialId: 'bot-1' }),
expect.objectContaining({
slackBotCredentialId: 'bot-1',
requiredScopes: option.requiredScopes,
}),
{
workspaceId: 'workspace-1',
credentialGroupId: 'group-1',
Expand Down
Loading