Skip to content

Commit 4a96328

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(oauth): refresh resolved connector credential
1 parent ffe495b commit 4a96328

2 files changed

Lines changed: 92 additions & 18 deletions

File tree

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.test.tsx

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,24 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
88
import type { SyncLogData } from '@/lib/api/contracts/knowledge/connectors'
99
import { CONNECTOR_SYNC_STALE_LOCK_TTL_MS } from '@/lib/knowledge/connectors/sync-limits'
1010

11-
const { consumeOAuthReturnContextMock, connectOAuthModalMock, icon, oauthCredentialsState } =
12-
vi.hoisted(() => ({
13-
consumeOAuthReturnContextMock: vi.fn(),
14-
connectOAuthModalMock: vi.fn(),
15-
icon: (name: string) => (props: SVGProps<SVGSVGElement>) => (
16-
<svg data-testid={`icon-${name}`} className={props.className} />
17-
),
18-
oauthCredentialsState: {
19-
current: [] as Array<{ id: string; name: string; provider: string }>,
20-
},
21-
}))
11+
const {
12+
consumeOAuthReturnContextMock,
13+
connectOAuthModalMock,
14+
credentialRefreshTriggersMock,
15+
icon,
16+
oauthCredentialsState,
17+
} = vi.hoisted(() => ({
18+
consumeOAuthReturnContextMock: vi.fn(),
19+
connectOAuthModalMock: vi.fn(),
20+
credentialRefreshTriggersMock: vi.fn(),
21+
icon: (name: string) => (props: SVGProps<SVGSVGElement>) => (
22+
<svg data-testid={`icon-${name}`} className={props.className} />
23+
),
24+
oauthCredentialsState: {
25+
current: [] as Array<{ id: string; name: string; provider: string }>,
26+
isFetching: false,
27+
},
28+
}))
2229

2330
vi.mock('@sim/emcn/icons', () => ({
2431
ChevronDown: icon('chevron-down'),
@@ -101,10 +108,14 @@ vi.mock('@/hooks/queries/kb/connectors', () => ({
101108
useUpdateConnector: vi.fn(() => ({ mutate: vi.fn() })),
102109
}))
103110
vi.mock('@/hooks/queries/oauth/oauth-credentials', () => ({
104-
useOAuthCredentials: vi.fn(() => ({ data: oauthCredentialsState.current })),
111+
useOAuthCredentials: vi.fn(() => ({
112+
data: oauthCredentialsState.current,
113+
isFetching: oauthCredentialsState.isFetching,
114+
refetch: vi.fn(),
115+
})),
105116
}))
106117
vi.mock('@/hooks/use-credential-refresh-triggers', () => ({
107-
useCredentialRefreshTriggers: vi.fn(),
118+
useCredentialRefreshTriggers: credentialRefreshTriggersMock,
108119
}))
109120

110121
import {
@@ -191,6 +202,7 @@ afterEach(() => {
191202
root = null
192203
document.body.innerHTML = ''
193204
oauthCredentialsState.current = []
205+
oauthCredentialsState.isFetching = false
194206
vi.clearAllMocks()
195207
})
196208

@@ -231,6 +243,60 @@ describe('Connector credential reauthorization', () => {
231243
},
232244
})
233245
)
246+
expect(credentialRefreshTriggersMock).toHaveBeenLastCalledWith(
247+
expect.any(Function),
248+
'slack-custom',
249+
'workspace-1'
250+
)
251+
})
252+
253+
it('keeps reauthorization open while the credential query is loading', () => {
254+
oauthCredentialsState.current = [
255+
{ id: 'credential-1', name: 'Workspace Slack', provider: 'slack-custom' },
256+
]
257+
const connector = makeConnector()
258+
const container = renderSection(connector)
259+
const reconnectButton = Array.from(container.querySelectorAll('button')).find(
260+
(button) => button.textContent === 'Reconnect'
261+
)
262+
263+
act(() => reconnectButton?.click())
264+
expect(connectOAuthModalMock).toHaveBeenCalledOnce()
265+
266+
connectOAuthModalMock.mockClear()
267+
oauthCredentialsState.current = []
268+
oauthCredentialsState.isFetching = true
269+
act(() =>
270+
root?.render(
271+
<ConnectorsSection
272+
workspaceId='workspace-1'
273+
knowledgeBaseId='knowledge-1'
274+
connectors={[connector]}
275+
isLoading={false}
276+
canEdit
277+
/>
278+
)
279+
)
280+
281+
expect(consumeOAuthReturnContextMock).not.toHaveBeenCalled()
282+
283+
oauthCredentialsState.current = [
284+
{ id: 'credential-1', name: 'Workspace Slack', provider: 'slack-custom' },
285+
]
286+
oauthCredentialsState.isFetching = false
287+
act(() =>
288+
root?.render(
289+
<ConnectorsSection
290+
workspaceId='workspace-1'
291+
knowledgeBaseId='knowledge-1'
292+
connectors={[connector]}
293+
isLoading={false}
294+
canEdit
295+
/>
296+
)
297+
)
298+
299+
expect(connectOAuthModalMock).toHaveBeenCalledOnce()
234300
})
235301

236302
it('clears the OAuth return context if the credential disappears while open', () => {

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,28 +280,36 @@ function ConnectorCard({
280280
? (connectorDef.auth.requiredScopes ?? EMPTY_REQUIRED_SCOPES)
281281
: EMPTY_REQUIRED_SCOPES
282282

283-
const { data: credentials, refetch: refetchCredentials } = useOAuthCredentials(providerId, {
283+
const {
284+
data: credentials,
285+
isFetching: credentialsLoading,
286+
refetch: refetchCredentials,
287+
} = useOAuthCredentials(providerId, {
284288
workspaceId,
285289
})
286290

287-
useCredentialRefreshTriggers(refetchCredentials, providerId ?? '', workspaceId)
288-
289291
const selectedCredential = useMemo(() => {
290292
if (!credentials || !connector.credentialId) return undefined
291293
return credentials.find((credential) => credential.id === connector.credentialId)
292294
}, [credentials, connector.credentialId])
293295

296+
useCredentialRefreshTriggers(
297+
refetchCredentials,
298+
selectedCredential?.provider ?? providerId ?? '',
299+
workspaceId
300+
)
301+
294302
const missingScopes = useMemo(
295303
() => (selectedCredential ? getMissingRequiredScopes(selectedCredential, requiredScopes) : []),
296304
[selectedCredential, requiredScopes]
297305
)
298306

299307
useEffect(() => {
300-
if (showOAuthModal && connector.credentialId && !selectedCredential) {
308+
if (showOAuthModal && connector.credentialId && !selectedCredential && !credentialsLoading) {
301309
consumeOAuthReturnContext()
302310
setShowOAuthModal(false)
303311
}
304-
}, [showOAuthModal, connector.credentialId, selectedCredential])
312+
}, [showOAuthModal, connector.credentialId, selectedCredential, credentialsLoading])
305313

306314
const { data: detail, isLoading: detailLoading } = useConnectorDetail(
307315
expanded ? knowledgeBaseId : undefined,

0 commit comments

Comments
 (0)