|
| 1 | +import { ClerkAPIResponseError } from '@clerk/shared/error'; |
1 | 2 | import type { InstanceType, OrganizationJSON, SessionJSON } from '@clerk/shared/types'; |
2 | 3 | import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; |
3 | 4 |
|
@@ -1085,4 +1086,151 @@ describe('Session', () => { |
1085 | 1086 | expect(isAuthorized).toBe(true); |
1086 | 1087 | }); |
1087 | 1088 | }); |
| 1089 | + |
| 1090 | + describe('origin outage mode fallback', () => { |
| 1091 | + let dispatchSpy: ReturnType<typeof vi.spyOn>; |
| 1092 | + let fetchSpy: ReturnType<typeof vi.spyOn>; |
| 1093 | + |
| 1094 | + beforeEach(() => { |
| 1095 | + SessionTokenCache.clear(); |
| 1096 | + dispatchSpy = vi.spyOn(eventBus, 'emit'); |
| 1097 | + fetchSpy = vi.spyOn(BaseResource, '_fetch' as any); |
| 1098 | + BaseResource.clerk = clerkMock() as any; |
| 1099 | + }); |
| 1100 | + |
| 1101 | + afterEach(() => { |
| 1102 | + dispatchSpy?.mockRestore(); |
| 1103 | + fetchSpy?.mockRestore(); |
| 1104 | + BaseResource.clerk = null as any; |
| 1105 | + }); |
| 1106 | + |
| 1107 | + it('should retry with expired token when API returns 422 with missing_expired_token error', async () => { |
| 1108 | + const session = new Session({ |
| 1109 | + status: 'active', |
| 1110 | + id: 'session_1', |
| 1111 | + object: 'session', |
| 1112 | + user: createUser({}), |
| 1113 | + last_active_organization_id: null, |
| 1114 | + last_active_token: { object: 'token', jwt: mockJwt }, |
| 1115 | + actor: null, |
| 1116 | + created_at: new Date().getTime(), |
| 1117 | + updated_at: new Date().getTime(), |
| 1118 | + } as SessionJSON); |
| 1119 | + |
| 1120 | + SessionTokenCache.clear(); |
| 1121 | + |
| 1122 | + const errorResponse = new ClerkAPIResponseError('Missing expired token', { |
| 1123 | + data: [ |
| 1124 | + { code: 'missing_expired_token', message: 'Missing expired token', long_message: 'Missing expired token' }, |
| 1125 | + ], |
| 1126 | + status: 422, |
| 1127 | + }); |
| 1128 | + fetchSpy.mockRejectedValueOnce(errorResponse); |
| 1129 | + |
| 1130 | + fetchSpy.mockResolvedValueOnce({ object: 'token', jwt: mockJwt }); |
| 1131 | + |
| 1132 | + await session.getToken(); |
| 1133 | + |
| 1134 | + expect(fetchSpy).toHaveBeenCalledTimes(2); |
| 1135 | + |
| 1136 | + expect(fetchSpy.mock.calls[0][0]).toMatchObject({ |
| 1137 | + path: '/client/sessions/session_1/tokens', |
| 1138 | + method: 'POST', |
| 1139 | + body: { organizationId: null }, |
| 1140 | + }); |
| 1141 | + |
| 1142 | + expect(fetchSpy.mock.calls[1][0]).toMatchObject({ |
| 1143 | + path: '/client/sessions/session_1/tokens', |
| 1144 | + method: 'POST', |
| 1145 | + body: { organizationId: null }, |
| 1146 | + search: { expired_token: mockJwt }, |
| 1147 | + }); |
| 1148 | + }); |
| 1149 | + |
| 1150 | + it('should not retry with expired token when lastActiveToken is not available', async () => { |
| 1151 | + const session = new Session({ |
| 1152 | + status: 'active', |
| 1153 | + id: 'session_1', |
| 1154 | + object: 'session', |
| 1155 | + user: createUser({}), |
| 1156 | + last_active_organization_id: null, |
| 1157 | + last_active_token: null, |
| 1158 | + actor: null, |
| 1159 | + created_at: new Date().getTime(), |
| 1160 | + updated_at: new Date().getTime(), |
| 1161 | + } as unknown as SessionJSON); |
| 1162 | + |
| 1163 | + SessionTokenCache.clear(); |
| 1164 | + |
| 1165 | + const errorResponse = new ClerkAPIResponseError('Missing expired token', { |
| 1166 | + data: [ |
| 1167 | + { code: 'missing_expired_token', message: 'Missing expired token', long_message: 'Missing expired token' }, |
| 1168 | + ], |
| 1169 | + status: 422, |
| 1170 | + }); |
| 1171 | + fetchSpy.mockRejectedValue(errorResponse); |
| 1172 | + |
| 1173 | + await expect(session.getToken()).rejects.toMatchObject({ |
| 1174 | + status: 422, |
| 1175 | + errors: [{ code: 'missing_expired_token' }], |
| 1176 | + }); |
| 1177 | + |
| 1178 | + expect(fetchSpy).toHaveBeenCalledTimes(1); |
| 1179 | + }); |
| 1180 | + |
| 1181 | + it('should not retry with expired token for non-422 errors', async () => { |
| 1182 | + const session = new Session({ |
| 1183 | + status: 'active', |
| 1184 | + id: 'session_1', |
| 1185 | + object: 'session', |
| 1186 | + user: createUser({}), |
| 1187 | + last_active_organization_id: null, |
| 1188 | + last_active_token: { object: 'token', jwt: mockJwt }, |
| 1189 | + actor: null, |
| 1190 | + created_at: new Date().getTime(), |
| 1191 | + updated_at: new Date().getTime(), |
| 1192 | + } as SessionJSON); |
| 1193 | + |
| 1194 | + SessionTokenCache.clear(); |
| 1195 | + |
| 1196 | + const errorResponse = new ClerkAPIResponseError('Bad request', { |
| 1197 | + data: [{ code: 'bad_request', message: 'Bad request', long_message: 'Bad request' }], |
| 1198 | + status: 400, |
| 1199 | + }); |
| 1200 | + fetchSpy.mockRejectedValueOnce(errorResponse); |
| 1201 | + |
| 1202 | + await expect(session.getToken()).rejects.toThrow(ClerkAPIResponseError); |
| 1203 | + |
| 1204 | + expect(fetchSpy).toHaveBeenCalledTimes(1); |
| 1205 | + }); |
| 1206 | + |
| 1207 | + it('should not retry with expired token when error code is different', async () => { |
| 1208 | + const session = new Session({ |
| 1209 | + status: 'active', |
| 1210 | + id: 'session_1', |
| 1211 | + object: 'session', |
| 1212 | + user: createUser({}), |
| 1213 | + last_active_organization_id: null, |
| 1214 | + last_active_token: { object: 'token', jwt: mockJwt }, |
| 1215 | + actor: null, |
| 1216 | + created_at: new Date().getTime(), |
| 1217 | + updated_at: new Date().getTime(), |
| 1218 | + } as unknown as SessionJSON); |
| 1219 | + |
| 1220 | + SessionTokenCache.clear(); |
| 1221 | + |
| 1222 | + const errorResponse = new ClerkAPIResponseError('Validation failed', { |
| 1223 | + data: [{ code: 'validation_error', message: 'Validation failed', long_message: 'Validation failed' }], |
| 1224 | + status: 422, |
| 1225 | + }); |
| 1226 | + fetchSpy.mockRejectedValue(errorResponse); |
| 1227 | + |
| 1228 | + await expect(session.getToken()).rejects.toMatchObject({ |
| 1229 | + status: 422, |
| 1230 | + errors: [{ code: 'validation_error' }], |
| 1231 | + }); |
| 1232 | + |
| 1233 | + expect(fetchSpy).toHaveBeenCalledTimes(1); |
| 1234 | + }); |
| 1235 | + }); |
1088 | 1236 | }); |
0 commit comments