Skip to content

Commit 51ba348

Browse files
committed
test(integrations): pin OAuth service resolution for all catalog integrations; fix credential branding reverse lookup
1 parent 275391a commit 51ba348

3 files changed

Lines changed: 93 additions & 10 deletions

File tree

apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration
6666
(c) =>
6767
(c.type === 'oauth' || c.type === 'service_account') &&
6868
c.providerId &&
69-
getServiceConfigByProviderId(c.providerId)?.name.toLowerCase() ===
70-
oauthService.serviceName.toLowerCase()
69+
getServiceConfigByProviderId(c.providerId)?.providerId === oauthService.providerId
7170
)
7271
}, [credentials, oauthService])
7372
const [serviceAccountOpen, setServiceAccountOpen] = useState(false)

apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from '@/components/emcn'
1616
import { ArrowLeft } from '@/components/emcn/icons'
1717
import { writeOAuthReturnContext } from '@/lib/credentials/client-state'
18-
import { INTEGRATIONS } from '@/lib/integrations'
18+
import { INTEGRATIONS, resolveOAuthServiceForIntegration } from '@/lib/integrations'
1919
import { getServiceConfigByProviderId } from '@/lib/oauth'
2020
import {
2121
AddPeopleModal,
@@ -98,15 +98,20 @@ export function ConnectedCredentialDetail({
9898
}, [credential])
9999

100100
/**
101-
* Resolve the integration block type from the OAuth service name so the
102-
* header tile can render with the same brand background used by the rows on
103-
* the integrations list page.
101+
* Resolve the integration block type from the credential's OAuth service so
102+
* the header tile can render with the same brand background used by the rows
103+
* on the integrations list page. Several integrations can share one service
104+
* (e.g. Jira and Jira Service Management); the one named after the service
105+
* is preferred since it is the service's canonical integration.
104106
*/
105107
const integrationBlockType = useMemo(() => {
106-
const name = serviceConfig?.name.toLowerCase()
107-
if (!name) return ''
108-
const match = INTEGRATIONS.find((i) => i.name.toLowerCase() === name)
109-
return match?.type ?? ''
108+
if (!serviceConfig) return ''
109+
const candidates = INTEGRATIONS.filter(
110+
(i) => resolveOAuthServiceForIntegration(i)?.providerId === serviceConfig.providerId
111+
)
112+
const serviceName = serviceConfig.name.toLowerCase()
113+
const canonical = candidates.find((i) => i.name.toLowerCase() === serviceName)
114+
return (canonical ?? candidates[0])?.type ?? ''
110115
}, [serviceConfig])
111116

112117
const handleReconnectOAuth = async () => {

apps/sim/lib/integrations/oauth-service.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,64 @@ import type { Integration } from '@/lib/integrations/types'
88

99
const INTEGRATIONS = integrationsJson.integrations as readonly Integration[]
1010

11+
/**
12+
* Pinned slug → OAuth providerId mapping for every OAuth integration in the
13+
* catalog. Guards against silent drift between block `serviceId`s, the
14+
* generated catalog, and `OAUTH_PROVIDERS` — the failure mode that made
15+
* Jira Service Management, Google Slides, and Monday fall back to the
16+
* API-key connect path.
17+
*/
18+
const EXPECTED_PROVIDER_BY_SLUG: Record<string, string> = {
19+
airtable: 'airtable',
20+
asana: 'asana',
21+
attio: 'attio',
22+
'azure-ad': 'microsoft-ad',
23+
box: 'box',
24+
'cal-com': 'calcom',
25+
confluence: 'confluence',
26+
docusign: 'docusign',
27+
dropbox: 'dropbox',
28+
gmail: 'google-email',
29+
'google-ads': 'google-ads',
30+
'google-bigquery': 'google-bigquery',
31+
'google-calendar': 'google-calendar',
32+
'google-contacts': 'google-contacts',
33+
'google-docs': 'google-docs',
34+
'google-drive': 'google-drive',
35+
'google-forms': 'google-forms',
36+
'google-groups': 'google-groups',
37+
'google-meet': 'google-meet',
38+
'google-sheets': 'google-sheets',
39+
'google-slides': 'google-drive',
40+
'google-tasks': 'google-tasks',
41+
'google-vault': 'google-vault',
42+
hubspot: 'hubspot',
43+
jira: 'jira',
44+
'jira-service-management': 'jira',
45+
linear: 'linear',
46+
linkedin: 'linkedin',
47+
'microsoft-dataverse': 'microsoft-dataverse',
48+
'microsoft-excel': 'microsoft-excel',
49+
'microsoft-planner': 'microsoft-planner',
50+
'microsoft-teams': 'microsoft-teams',
51+
monday: 'monday',
52+
notion: 'notion',
53+
onedrive: 'onedrive',
54+
outlook: 'outlook',
55+
pipedrive: 'pipedrive',
56+
reddit: 'reddit',
57+
salesforce: 'salesforce',
58+
sharepoint: 'sharepoint',
59+
shopify: 'shopify',
60+
slack: 'slack',
61+
trello: 'trello',
62+
wealthbox: 'wealthbox',
63+
webflow: 'webflow',
64+
wordpress: 'wordpress',
65+
x: 'x',
66+
zoom: 'zoom',
67+
}
68+
1169
describe('resolveOAuthServiceForSlug', () => {
1270
it.concurrent('resolves integrations whose name differs from the OAuth service name', () => {
1371
const jsm = resolveOAuthServiceForSlug('jira-service-management')
@@ -49,4 +107,25 @@ describe('resolveOAuthServiceForSlug', () => {
49107
.map((entry) => entry.slug)
50108
expect(unresolved).toEqual([])
51109
})
110+
111+
it.concurrent('resolves the pinned provider for every enumerated OAuth integration', () => {
112+
const resolved = Object.fromEntries(
113+
Object.keys(EXPECTED_PROVIDER_BY_SLUG).map((slug) => [
114+
slug,
115+
resolveOAuthServiceForSlug(slug)?.providerId ?? null,
116+
])
117+
)
118+
expect(resolved).toEqual(EXPECTED_PROVIDER_BY_SLUG)
119+
})
120+
121+
it.concurrent('carries oauthServiceId for exactly the OAuth catalog entries', () => {
122+
const missing = INTEGRATIONS.filter(
123+
(entry) => entry.authType === 'oauth' && !entry.oauthServiceId
124+
).map((entry) => entry.slug)
125+
const unexpected = INTEGRATIONS.filter(
126+
(entry) => entry.authType !== 'oauth' && entry.oauthServiceId
127+
).map((entry) => entry.slug)
128+
expect(missing).toEqual([])
129+
expect(unexpected).toEqual([])
130+
})
52131
})

0 commit comments

Comments
 (0)