Skip to content

Commit 9901d81

Browse files
committed
Add enterprise connection test run resource
1 parent e69797a commit 9901d81

8 files changed

Lines changed: 405 additions & 81 deletions

File tree

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import type {
2+
ClerkResourceReloadParams,
3+
EnterpriseConnectionTestRunJSON,
4+
EnterpriseConnectionTestRunJSONSnapshot,
5+
EnterpriseConnectionTestRunLogResource,
6+
EnterpriseConnectionTestRunOauthPayloadJSON,
7+
EnterpriseConnectionTestRunOauthPayloadResource,
8+
EnterpriseConnectionTestRunParsedUserInfoJSON,
9+
EnterpriseConnectionTestRunParsedUserInfoResource,
10+
EnterpriseConnectionTestRunResource,
11+
EnterpriseConnectionTestRunSamlPayloadJSON,
12+
EnterpriseConnectionTestRunSamlPayloadResource,
13+
} from '@clerk/shared/types';
14+
15+
import { unixEpochToDate } from '../../utils/date';
16+
import { clerkUnsupportedReloadMethod } from '../errors';
17+
import { BaseResource } from './Base';
18+
19+
export class EnterpriseConnectionTestRun extends BaseResource implements EnterpriseConnectionTestRunResource {
20+
pathRoot = '/me';
21+
private enterpriseConnectionId = '';
22+
23+
id!: string;
24+
status!: string;
25+
connectionType!: 'saml' | 'oauth';
26+
parsedUserInfo: EnterpriseConnectionTestRunParsedUserInfoResource | null = null;
27+
logs: EnterpriseConnectionTestRunLogResource[] = [];
28+
saml: EnterpriseConnectionTestRunSamlPayloadResource | null = null;
29+
oauth: EnterpriseConnectionTestRunOauthPayloadResource | null = null;
30+
createdAt: Date | null = null;
31+
32+
constructor(
33+
data: EnterpriseConnectionTestRunJSON | EnterpriseConnectionTestRunJSONSnapshot,
34+
enterpriseConnectionId: string,
35+
) {
36+
super();
37+
this.enterpriseConnectionId = enterpriseConnectionId;
38+
this.fromJSON(data);
39+
}
40+
41+
protected path(): string {
42+
return `${this.pathRoot}/enterprise_connections/${this.enterpriseConnectionId}/test_runs/${this.id}`;
43+
}
44+
45+
reload(_?: ClerkResourceReloadParams): Promise<this> {
46+
clerkUnsupportedReloadMethod('EnterpriseConnectionTestRun');
47+
}
48+
49+
protected fromJSON(data: EnterpriseConnectionTestRunJSON | EnterpriseConnectionTestRunJSONSnapshot | null): this {
50+
if (!data) {
51+
return this;
52+
}
53+
54+
this.id = data.id;
55+
this.status = data.status;
56+
this.connectionType = data.connection_type;
57+
this.parsedUserInfo = parsedUserInfoFromJSON(data.parsed_user_info ?? null);
58+
this.logs = (data.logs ?? []).map(log => ({
59+
level: log.level,
60+
code: log.code,
61+
shortMessage: log.short_message,
62+
message: log.message,
63+
}));
64+
this.saml = samlPayloadFromJSON(data.saml ?? null);
65+
this.oauth = oauthPayloadFromJSON(data.oauth ?? null);
66+
this.createdAt = unixEpochToDate(data.created_at);
67+
68+
return this;
69+
}
70+
71+
public __internal_toSnapshot(): EnterpriseConnectionTestRunJSONSnapshot {
72+
return {
73+
object: 'enterprise_connection_test_run',
74+
id: this.id,
75+
status: this.status,
76+
connection_type: this.connectionType,
77+
parsed_user_info: parsedUserInfoToJSON(this.parsedUserInfo),
78+
logs: this.logs.map(log => ({
79+
level: log.level,
80+
code: log.code,
81+
short_message: log.shortMessage,
82+
message: log.message,
83+
})),
84+
saml: samlPayloadToJSON(this.saml),
85+
oauth: oauthPayloadToJSON(this.oauth),
86+
created_at: this.createdAt?.getTime() ?? 0,
87+
};
88+
}
89+
}
90+
91+
function parsedUserInfoFromJSON(
92+
data: EnterpriseConnectionTestRunParsedUserInfoJSON | null | undefined,
93+
): EnterpriseConnectionTestRunParsedUserInfoResource | null {
94+
if (!data) {
95+
return null;
96+
}
97+
98+
return {
99+
emailAddress: data.email_address,
100+
firstName: data.first_name,
101+
lastName: data.last_name,
102+
userId: data.user_id,
103+
};
104+
}
105+
106+
function parsedUserInfoToJSON(
107+
data: EnterpriseConnectionTestRunParsedUserInfoResource | null,
108+
): EnterpriseConnectionTestRunParsedUserInfoJSON | undefined {
109+
if (!data) {
110+
return undefined;
111+
}
112+
113+
return {
114+
email_address: data.emailAddress,
115+
first_name: data.firstName,
116+
last_name: data.lastName,
117+
user_id: data.userId,
118+
};
119+
}
120+
121+
function samlPayloadFromJSON(
122+
data: EnterpriseConnectionTestRunSamlPayloadJSON | null | undefined,
123+
): EnterpriseConnectionTestRunSamlPayloadResource | null {
124+
if (!data) {
125+
return null;
126+
}
127+
128+
return {
129+
samlRequest: data.saml_request,
130+
samlResponse: data.saml_response,
131+
relayState: data.relay_state,
132+
};
133+
}
134+
135+
function samlPayloadToJSON(
136+
data: EnterpriseConnectionTestRunSamlPayloadResource | null,
137+
): EnterpriseConnectionTestRunSamlPayloadJSON | undefined {
138+
if (!data) {
139+
return undefined;
140+
}
141+
return {
142+
saml_request: data.samlRequest,
143+
saml_response: data.samlResponse,
144+
relay_state: data.relayState,
145+
};
146+
}
147+
148+
function oauthPayloadFromJSON(
149+
data: EnterpriseConnectionTestRunOauthPayloadJSON | null | undefined,
150+
): EnterpriseConnectionTestRunOauthPayloadResource | null {
151+
if (!data) {
152+
return null;
153+
}
154+
return {
155+
idToken: data.id_token,
156+
accessToken: data.access_token,
157+
userInfo: data.user_info,
158+
};
159+
}
160+
161+
function oauthPayloadToJSON(
162+
data: EnterpriseConnectionTestRunOauthPayloadResource | null,
163+
): EnterpriseConnectionTestRunOauthPayloadJSON | undefined {
164+
if (!data) {
165+
return undefined;
166+
}
167+
return {
168+
id_token: data.idToken,
169+
access_token: data.accessToken,
170+
user_info: data.userInfo,
171+
};
172+
}

packages/clerk-js/src/core/resources/User.ts

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getFullName } from '@clerk/shared/internal/clerk-js/user';
2+
import { deepCamelToSnake } from '@clerk/shared/underscore';
23
import type {
34
BackupCodeJSON,
45
BackupCodeResource,
@@ -14,7 +15,14 @@ import type {
1415
EnterpriseConnectionResource,
1516
ExternalAccountJSON,
1617
ExternalAccountResource,
18+
ClerkPaginatedResponse,
1719
CreateMeEnterpriseConnectionParams,
20+
EnterpriseConnectionTestRunInitJSON,
21+
EnterpriseConnectionTestRunJSON,
22+
EnterpriseConnectionTestRunsPaginatedJSON,
23+
EnterpriseConnectionTestRunInitResource,
24+
EnterpriseConnectionTestRunResource,
25+
GetEnterpriseConnectionTestRunsParams,
1826
GetEnterpriseConnectionsParams,
1927
UpdateMeEnterpriseConnectionParams,
2028
GetOrganizationMemberships,
@@ -38,10 +46,7 @@ import type {
3846
} from '@clerk/shared/types';
3947

4048
import { unixEpochToDate } from '../../utils/date';
41-
import {
42-
buildCreateMeEnterpriseConnectionBody,
43-
buildUpdateMeEnterpriseConnectionBody,
44-
} from '../../utils/meEnterpriseConnectionBody';
49+
import { convertPageToOffsetSearchParams } from '../../utils/convertPageToOffsetSearchParams';
4550
import { normalizeUnsafeMetadata } from '../../utils/resourceParams';
4651
import { eventBus, events } from '../events';
4752
import { addPaymentMethod, getPaymentMethods, initializePaymentMethod } from '../modules/billing';
@@ -52,6 +57,7 @@ import {
5257
EmailAddress,
5358
EnterpriseAccount,
5459
EnterpriseConnection,
60+
EnterpriseConnectionTestRun,
5561
ExternalAccount,
5662
Image,
5763
OrganizationMembership,
@@ -329,7 +335,7 @@ export class User extends BaseResource implements UserResource {
329335
await BaseResource._fetch<EnterpriseConnectionJSON>({
330336
path: `${this.path()}/enterprise_connections`,
331337
method: 'POST',
332-
body: buildCreateMeEnterpriseConnectionBody(params) as any,
338+
body: deepCamelToSnake(params) as any,
333339
})
334340
)?.response as unknown as EnterpriseConnectionJSON;
335341

@@ -344,7 +350,7 @@ export class User extends BaseResource implements UserResource {
344350
await BaseResource._fetch<EnterpriseConnectionJSON>({
345351
path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`,
346352
method: 'PATCH',
347-
body: buildUpdateMeEnterpriseConnectionBody(params) as any,
353+
body: deepCamelToSnake(params) as any,
348354
})
349355
)?.response as unknown as EnterpriseConnectionJSON;
350356

@@ -362,6 +368,47 @@ export class User extends BaseResource implements UserResource {
362368
return new DeletedObject(json);
363369
};
364370

371+
createEnterpriseConnectionTestRun = async (
372+
enterpriseConnectionId: string,
373+
): Promise<EnterpriseConnectionTestRunInitResource> => {
374+
const json = (
375+
await BaseResource._fetch<EnterpriseConnectionTestRunInitJSON>({
376+
path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`,
377+
method: 'POST',
378+
})
379+
)?.response as unknown as EnterpriseConnectionTestRunInitJSON;
380+
381+
return { url: json.url };
382+
};
383+
384+
getEnterpriseConnectionTestRuns = async (
385+
enterpriseConnectionId: string,
386+
params?: GetEnterpriseConnectionTestRunsParams,
387+
): Promise<ClerkPaginatedResponse<EnterpriseConnectionTestRunResource>> => {
388+
const { status, ...rest } = params || {};
389+
const search = convertPageToOffsetSearchParams({ ...rest, paginated: true });
390+
if (status?.length) {
391+
for (const s of status) {
392+
search.append('status', s);
393+
}
394+
}
395+
396+
const res = await BaseResource._fetch({
397+
path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`,
398+
method: 'GET',
399+
search,
400+
});
401+
402+
const payload = res?.response as unknown as EnterpriseConnectionTestRunsPaginatedJSON | undefined;
403+
404+
return {
405+
total_count: payload?.total_count ?? 0,
406+
data: (payload?.data ?? []).map(
407+
(row: EnterpriseConnectionTestRunJSON) => new EnterpriseConnectionTestRun(row, enterpriseConnectionId),
408+
),
409+
};
410+
};
411+
365412
initializePaymentMethod: typeof initializePaymentMethod = params => {
366413
return initializePaymentMethod(params);
367414
};

packages/clerk-js/src/core/resources/__tests__/User.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,72 @@ describe('User', () => {
269269
expect(result.deleted).toBe(true);
270270
});
271271

272+
it('creates an enterprise connection test run', async () => {
273+
// @ts-ignore
274+
BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: { url: 'https://example.com/test' } }));
275+
276+
const user = new User({
277+
email_addresses: [],
278+
phone_numbers: [],
279+
web3_wallets: [],
280+
external_accounts: [],
281+
} as unknown as UserJSON);
282+
283+
const init = await user.createEnterpriseConnectionTestRun('ec_123');
284+
285+
// @ts-ignore
286+
expect(BaseResource._fetch).toHaveBeenCalledWith({
287+
method: 'POST',
288+
path: '/me/enterprise_connections/ec_123/test_runs',
289+
});
290+
291+
expect(init.url).toBe('https://example.com/test');
292+
});
293+
294+
it('lists enterprise connection test runs', async () => {
295+
const paginated = {
296+
data: [
297+
{
298+
id: 'run_1',
299+
status: 'success',
300+
connection_type: 'saml' as const,
301+
created_at: 1700000000000,
302+
},
303+
],
304+
total_count: 1,
305+
};
306+
307+
// @ts-ignore
308+
BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: paginated }));
309+
310+
const user = new User({
311+
email_addresses: [],
312+
phone_numbers: [],
313+
web3_wallets: [],
314+
external_accounts: [],
315+
} as unknown as UserJSON);
316+
317+
const result = await user.getEnterpriseConnectionTestRuns('ec_123', {
318+
initialPage: 1,
319+
pageSize: 10,
320+
status: ['pending', 'success'],
321+
});
322+
323+
// @ts-ignore
324+
const call = BaseResource._fetch.mock.calls[0][0];
325+
expect(call.method).toBe('GET');
326+
expect(call.path).toBe('/me/enterprise_connections/ec_123/test_runs');
327+
expect(call.search.get('limit')).toBe('10');
328+
expect(call.search.get('offset')).toBe('0');
329+
expect(call.search.get('paginated')).toBe('true');
330+
expect(call.search.getAll('status')).toEqual(['pending', 'success']);
331+
332+
expect(result.total_count).toBe(1);
333+
expect(result.data).toHaveLength(1);
334+
expect(result.data[0].id).toBe('run_1');
335+
expect(result.data[0].connectionType).toBe('saml');
336+
});
337+
272338
it('creates a web3 wallet', async () => {
273339
const targetWeb3Wallet = '0x0000000000000000000000000000000000000000';
274340
const web3WalletJSON = {

packages/clerk-js/src/core/resources/internal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export * from './DisplayConfig';
1717
export * from './EmailAddress';
1818
export * from './EnterpriseAccount';
1919
export * from './EnterpriseConnection';
20+
export * from './EnterpriseConnectionTestRun';
2021
export * from './Environment';
2122
export * from './ExternalAccount';
2223
export * from './Feature';

0 commit comments

Comments
 (0)