Skip to content

Commit 45bcf01

Browse files
fix(credentials): preserve completed reconnect state
1 parent d6edc92 commit 45bcf01

2 files changed

Lines changed: 47 additions & 2 deletions

File tree

apps/sim/lib/credential-groups/oauth.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ vi.mock('@/lib/credential-groups/provider-registry', () => ({
2121
getCredentialGroupProviderAdapter: () => adapter,
2222
}))
2323

24+
vi.mock('@/lib/credentials/managed-oauth', () => ({
25+
decryptManagedOAuthTokenSet: vi.fn(),
26+
encryptManagedOAuthTokenSet: vi.fn().mockResolvedValue('encrypted-token-set'),
27+
}))
28+
2429
import { completeCredentialGroupOAuth } from '@/lib/credential-groups/oauth'
2530

2631
const POLICY = {
@@ -97,4 +102,44 @@ describe('credential group OAuth persistence', () => {
97102
expect(dbChainMockFns.update).not.toHaveBeenCalled()
98103
expect(dbChainMockFns.insert).not.toHaveBeenCalled()
99104
})
105+
106+
it('preserves completed enrollment state when an account reconnects', async () => {
107+
dbChainMockFns.limit.mockResolvedValueOnce([{ status: 'completed' }]).mockResolvedValueOnce([
108+
{
109+
id: 'credential-1',
110+
providerSubjectId: 'google-subject-1',
111+
encryptedOauthTokenSet: null,
112+
refreshTokenExpiresAt: null,
113+
},
114+
])
115+
dbChainMockFns.returning
116+
.mockResolvedValueOnce([{ id: 'credential-1' }])
117+
.mockResolvedValueOnce([{ id: CONTEXT.enrollmentId }])
118+
119+
await completeCredentialGroupOAuth(
120+
{ ...CONTEXT, enrollmentStatus: 'completed' },
121+
{
122+
state: 'state-1',
123+
provider: 'gmail',
124+
nonceHash: 'nonce-hash',
125+
enrollmentId: CONTEXT.enrollmentId,
126+
credentialGroupId: CONTEXT.credentialGroupId,
127+
optionId: CONTEXT.option.id,
128+
authorizationAppId: POLICY.authorizationAppId,
129+
scopeVersion: POLICY.scopeVersion,
130+
requiredScopes: POLICY.requiredScopes,
131+
redirectUri: 'https://sim.ai/api/credential-groups/oauth/gmail/callback',
132+
codeVerifier: 'verifier',
133+
invitationToken: 'invitation-token',
134+
createdAt: Date.now(),
135+
},
136+
'authorization-code'
137+
)
138+
139+
const enrollmentUpdate = dbChainMockFns.set.mock.calls[1]?.[0]
140+
expect(enrollmentUpdate).toEqual(
141+
expect.objectContaining({ status: 'completed', updatedAt: expect.any(Date) })
142+
)
143+
expect(enrollmentUpdate).not.toHaveProperty('completedAt')
144+
})
100145
})

apps/sim/lib/credential-groups/oauth.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ async function persistGrant(
202202
const [updatedEnrollment] = await tx
203203
.update(credentialGroupEnrollment)
204204
.set({
205-
status: 'in_progress',
206-
completedAt: null,
205+
status: enrollment.status === 'completed' ? 'completed' : 'in_progress',
206+
...(enrollment.status === 'completed' ? {} : { completedAt: null }),
207207
updatedAt: now,
208208
})
209209
.where(

0 commit comments

Comments
 (0)