@scalekit-sdk/node 2.0.1 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/jest.config.js +15 -0
  2. package/lib/core.d.ts +1 -1
  3. package/lib/core.js +31 -31
  4. package/lib/core.js.map +1 -1
  5. package/lib/domain.d.ts +6 -2
  6. package/lib/domain.js +4 -4
  7. package/lib/domain.js.map +1 -1
  8. package/lib/errors/base-exception.d.ts +32 -0
  9. package/lib/errors/base-exception.js +238 -0
  10. package/lib/errors/base-exception.js.map +1 -0
  11. package/lib/errors/index.d.ts +2 -0
  12. package/lib/errors/index.js +20 -0
  13. package/lib/errors/index.js.map +1 -0
  14. package/lib/errors/specific-exceptions.d.ts +39 -0
  15. package/lib/errors/specific-exceptions.js +90 -0
  16. package/lib/errors/specific-exceptions.js.map +1 -0
  17. package/lib/index.d.ts +1 -0
  18. package/lib/index.js +1 -0
  19. package/lib/index.js.map +1 -1
  20. package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.d.ts +34 -87
  21. package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js +31 -120
  22. package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js.map +1 -1
  23. package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.d.ts +19 -10
  24. package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js +18 -9
  25. package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js.map +1 -1
  26. package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.d.ts +213 -6
  27. package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js +273 -5
  28. package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js.map +1 -1
  29. package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.d.ts +34 -1
  30. package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.js +45 -1
  31. package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.js.map +1 -1
  32. package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.d.ts +29 -0
  33. package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.js +43 -1
  34. package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.js.map +1 -1
  35. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.d.ts +21 -1
  36. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.js +20 -0
  37. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.js.map +1 -1
  38. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.d.ts +110 -5
  39. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js +164 -5
  40. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js.map +1 -1
  41. package/lib/pkg/grpc/scalekit/v1/users/users_connect.d.ts +48 -1
  42. package/lib/pkg/grpc/scalekit/v1/users/users_connect.js +47 -0
  43. package/lib/pkg/grpc/scalekit/v1/users/users_connect.js.map +1 -1
  44. package/lib/pkg/grpc/scalekit/v1/users/users_pb.d.ts +289 -4
  45. package/lib/pkg/grpc/scalekit/v1/users/users_pb.js +452 -11
  46. package/lib/pkg/grpc/scalekit/v1/users/users_pb.js.map +1 -1
  47. package/lib/scalekit.d.ts +3 -3
  48. package/lib/scalekit.js +35 -22
  49. package/lib/scalekit.js.map +1 -1
  50. package/lib/types/user.d.ts +1 -1
  51. package/lib/user.d.ts +10 -3
  52. package/lib/user.js +26 -5
  53. package/lib/user.js.map +1 -1
  54. package/package.json +6 -2
  55. package/src/core.ts +31 -32
  56. package/src/domain.ts +6 -3
  57. package/src/errors/base-exception.ts +262 -0
  58. package/src/errors/index.ts +3 -0
  59. package/src/errors/specific-exceptions.ts +88 -0
  60. package/src/index.ts +3 -1
  61. package/src/pkg/grpc/scalekit/v1/commons/commons_pb.ts +49 -129
  62. package/src/pkg/grpc/scalekit/v1/connections/connections_connect.ts +19 -10
  63. package/src/pkg/grpc/scalekit/v1/connections/connections_pb.ts +383 -8
  64. package/src/pkg/grpc/scalekit/v1/domains/domains_pb.ts +50 -0
  65. package/src/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.ts +55 -0
  66. package/src/pkg/grpc/scalekit/v1/organizations/organizations_connect.ts +21 -1
  67. package/src/pkg/grpc/scalekit/v1/organizations/organizations_pb.ts +218 -5
  68. package/src/pkg/grpc/scalekit/v1/users/users_connect.ts +48 -1
  69. package/src/pkg/grpc/scalekit/v1/users/users_pb.ts +571 -6
  70. package/src/scalekit.ts +39 -23
  71. package/src/types/user.ts +1 -1
  72. package/src/user.ts +34 -7
  73. package/tests/README.md +25 -0
  74. package/tests/connection.test.ts +42 -0
  75. package/tests/directory.test.ts +46 -0
  76. package/tests/domain.test.ts +176 -0
  77. package/tests/organization.test.ts +65 -0
  78. package/tests/passwordless.test.ts +108 -0
  79. package/tests/scalekit.test.ts +104 -0
  80. package/tests/setup.ts +34 -0
  81. package/tests/users.test.ts +168 -0
  82. package/tests/utils/test-data.ts +314 -0
package/src/scalekit.ts CHANGED
@@ -12,6 +12,7 @@ import PasswordlessClient from './passwordless';
12
12
  import UserClient from './user';
13
13
  import { IdpInitiatedLoginClaims, IdTokenClaim, User } from './types/auth';
14
14
  import { AuthenticationOptions, AuthenticationResponse, AuthorizationUrlOptions, GrantType, LogoutUrlOptions, RefreshTokenResponse ,TokenValidationOptions } from './types/scalekit';
15
+ import { WebhookVerificationError, ScalekitValidateTokenFailureException } from './errors/base-exception';
15
16
 
16
17
  const authorizeEndpoint = "oauth/authorize";
17
18
  const logoutEndpoint = "oidc/logout";
@@ -226,7 +227,7 @@ export default class ScalekitClient {
226
227
  }
227
228
 
228
229
  /**
229
- * Verifies the payload of the webhook
230
+ * Verify webhook payload
230
231
  *
231
232
  * @param {string} secret The secret
232
233
  * @param {Record<string, string>} headers The headers
@@ -237,25 +238,40 @@ export default class ScalekitClient {
237
238
  const webhookId = headers['webhook-id'];
238
239
  const webhookTimestamp = headers['webhook-timestamp'];
239
240
  const webhookSignature = headers['webhook-signature'];
241
+
240
242
  if (!webhookId || !webhookTimestamp || !webhookSignature) {
241
- throw new Error("Missing required headers");
243
+ throw new WebhookVerificationError("Missing required headers");
244
+ }
245
+
246
+ const secretParts = secret.split("_");
247
+ if (secretParts.length < 2) {
248
+ throw new WebhookVerificationError("Invalid secret");
242
249
  }
243
- const timestamp = this.verifyTimestamp(webhookTimestamp);
244
- const data = `${webhookId}.${Math.floor(timestamp.getTime() / 1000)}.${payload}`;
245
- const secretBytes = Buffer.from(secret.split("_")[1], 'base64');
246
- const computedSignature = this.computeSignature(secretBytes, data);
247
- const receivedSignatures = webhookSignature.split(" ");
248
- for (const versionedSignature of receivedSignatures) {
249
- const [version, signature] = versionedSignature.split(",");
250
- if (version !== WEBHOOK_SIGNATURE_VERSION) {
251
- continue;
250
+
251
+ try {
252
+ const timestamp = this.verifyTimestamp(webhookTimestamp);
253
+ const data = `${webhookId}.${Math.floor(timestamp.getTime() / 1000)}.${payload}`;
254
+ const secretBytes = Buffer.from(secretParts[1], 'base64');
255
+ const computedSignature = this.computeSignature(secretBytes, data);
256
+ const receivedSignatures = webhookSignature.split(" ");
257
+
258
+ for (const versionedSignature of receivedSignatures) {
259
+ const [version, signature] = versionedSignature.split(",");
260
+ if (version !== WEBHOOK_SIGNATURE_VERSION) {
261
+ continue;
262
+ }
263
+ if (crypto.timingSafeEqual(Buffer.from(signature, 'base64'), Buffer.from(computedSignature, 'base64'))) {
264
+ return true;
265
+ }
252
266
  }
253
- if (crypto.timingSafeEqual(Buffer.from(signature, 'base64'), Buffer.from(computedSignature, 'base64'))) {
254
- return true;
267
+
268
+ throw new WebhookVerificationError("Invalid signature");
269
+ } catch (error) {
270
+ if (error instanceof WebhookVerificationError) {
271
+ throw error;
255
272
  }
273
+ throw new WebhookVerificationError("Invalid signature");
256
274
  }
257
-
258
- throw new Error("Invalid Signature");
259
275
  }
260
276
 
261
277
  /**
@@ -265,7 +281,7 @@ export default class ScalekitClient {
265
281
  * @param {string} token The token to be validated
266
282
  * @param {TokenValidationOptions} options Optional validation options for issuer, audience, and scopes
267
283
  * @return {Promise<T>} Returns the token payload if valid
268
- * @throws {Error} If token is invalid or missing required scopes
284
+ * @throws {ScalekitValidateTokenFailureException} If token is invalid or missing required scopes
269
285
  */
270
286
  async validateToken<T>(token: string, options?: TokenValidationOptions): Promise<T> {
271
287
  await this.coreClient.getJwks();
@@ -283,8 +299,8 @@ export default class ScalekitClient {
283
299
  }
284
300
 
285
301
  return payload;
286
- } catch (_) {
287
- throw new Error("Invalid token");
302
+ } catch (error) {
303
+ throw new ScalekitValidateTokenFailureException(error);
288
304
  }
289
305
  }
290
306
 
@@ -294,7 +310,7 @@ export default class ScalekitClient {
294
310
  * @param {string} token The token to verify
295
311
  * @param {string[]} requiredScopes The scopes that must be present in the token
296
312
  * @return {boolean} Returns true if all required scopes are present
297
- * @throws {Error} If required scopes are missing, with details about which scopes are missing
313
+ * @throws {ScalekitValidateTokenFailureException} If required scopes are missing, with details about which scopes are missing
298
314
  */
299
315
  verifyScopes(token: string, requiredScopes: string[]): boolean {
300
316
  const payload = jose.decodeJwt(token);
@@ -303,7 +319,7 @@ export default class ScalekitClient {
303
319
  const missingScopes = requiredScopes.filter(scope => !scopes.includes(scope));
304
320
 
305
321
  if (missingScopes.length > 0) {
306
- throw new Error(`Token missing required scopes: ${missingScopes.join(', ')}`);
322
+ throw new ScalekitValidateTokenFailureException(`Token missing required scopes: ${missingScopes.join(', ')}`);
307
323
  }
308
324
 
309
325
  return true;
@@ -332,13 +348,13 @@ export default class ScalekitClient {
332
348
  const now = Math.floor(Date.now() / 1000);
333
349
  const timestamp = parseInt(timestampStr, 10);
334
350
  if (isNaN(timestamp)) {
335
- throw new Error("Invalid timestamp");
351
+ throw new WebhookVerificationError("Invalid Signature Headers");
336
352
  }
337
353
  if (now - timestamp > WEBHOOK_TOLERANCE_IN_SECONDS) {
338
- throw new Error("Message timestamp too old");
354
+ throw new WebhookVerificationError("Message timestamp too old");
339
355
  }
340
356
  if (timestamp > now + WEBHOOK_TOLERANCE_IN_SECONDS) {
341
- throw new Error("Message timestamp too new");
357
+ throw new WebhookVerificationError("Message timestamp too new");
342
358
  }
343
359
 
344
360
  return new Date(timestamp * 1000);
package/src/types/user.ts CHANGED
@@ -7,7 +7,7 @@ export interface CreateUserRequest {
7
7
  lastName?: string;
8
8
  };
9
9
  metadata?: Record<string, string>;
10
- sendActivationEmail?: boolean;
10
+ sendInvitationEmail?: boolean;
11
11
  }
12
12
 
13
13
  export interface UpdateUserRequest {
package/src/user.ts CHANGED
@@ -25,7 +25,9 @@ import {
25
25
  ListOrganizationUsersRequest,
26
26
  ListOrganizationUsersResponse,
27
27
  CreateMembership,
28
- UpdateMembership
28
+ UpdateMembership,
29
+ ResendInviteRequest,
30
+ ResendInviteResponse
29
31
  } from './pkg/grpc/scalekit/v1/users/users_pb';
30
32
  import { CreateUserRequest, UpdateUserRequest as UpdateUserRequestType } from './types/user';
31
33
 
@@ -67,8 +69,8 @@ export default class UserClient {
67
69
  user
68
70
  };
69
71
 
70
- if (options.sendActivationEmail !== undefined) {
71
- request.sendActivationEmail = options.sendActivationEmail;
72
+ if (options.sendInvitationEmail !== undefined) {
73
+ request.sendInvitationEmail = options.sendInvitationEmail;
72
74
  }
73
75
 
74
76
  const response = await this.coreClient.connectExec(
@@ -171,7 +173,7 @@ export default class UserClient {
171
173
  * @param {object} options The membership options
172
174
  * @param {string[]} options.roles The roles to assign
173
175
  * @param {Record<string, string>} options.metadata Optional metadata
174
- * @param {boolean} options.sendActivationEmail Whether to send activation email
176
+ * @param {boolean} options.sendInvitationEmail Whether to send invitation email
175
177
  * @returns {Promise<CreateMembershipResponse>} The response with updated user
176
178
  */
177
179
  async createMembership(
@@ -180,7 +182,7 @@ export default class UserClient {
180
182
  options: {
181
183
  roles?: string[],
182
184
  metadata?: Record<string, string>,
183
- sendActivationEmail?: boolean
185
+ sendInvitationEmail?: boolean
184
186
  } = {}
185
187
  ): Promise<CreateMembershipResponse> {
186
188
  const membership = new CreateMembership({
@@ -197,8 +199,8 @@ export default class UserClient {
197
199
  membership
198
200
  };
199
201
 
200
- if (options.sendActivationEmail !== undefined) {
201
- request.sendActivationEmail = options.sendActivationEmail;
202
+ if (options.sendInvitationEmail !== undefined) {
203
+ request.sendInvitationEmail = options.sendInvitationEmail;
202
204
  }
203
205
 
204
206
  return this.coreClient.connectExec(
@@ -288,4 +290,29 @@ export default class UserClient {
288
290
  }
289
291
  );
290
292
  }
293
+
294
+ /**
295
+ * Resend an invitation to a user
296
+ * @param {string} organizationId The organization id
297
+ * @param {string} userId The user id
298
+ * @returns {Promise<ResendInviteResponse>} The response with the invite
299
+ */
300
+ async resendInvite(organizationId: string, userId: string): Promise<ResendInviteResponse> {
301
+ if (!organizationId) {
302
+ throw new Error('organizationId is required');
303
+ }
304
+ if (!userId) {
305
+ throw new Error('userId is required');
306
+ }
307
+
308
+ const request = new ResendInviteRequest({
309
+ organizationId,
310
+ id: userId
311
+ });
312
+
313
+ return this.coreClient.connectExec(
314
+ this.client.resendInvite,
315
+ request
316
+ );
317
+ }
291
318
  }
@@ -0,0 +1,25 @@
1
+ # Scalekit Node SDK Tests
2
+
3
+ This directory contains the test suite for the Scalekit Node SDK.
4
+
5
+ ## Setup
6
+
7
+ 1. Create a `.env` file in the root directory with the following environment variables:
8
+
9
+ ```env
10
+ # Required for client initialization
11
+ SCALEKIT_ENVIRONMENT_URL= "Your Scalekit environment URL."
12
+ SCALEKIT_CLIENT_ID= " Your Scalekit environment client id "
13
+ SCALEKIT_CLIENT_SECRET= " Your Scalekit environment client secret "
14
+ ```
15
+
16
+ 2. Install dependencies:
17
+ ```bash
18
+ npm install
19
+ ```
20
+
21
+ ## Running Tests
22
+
23
+ - Run all tests: `npm test`
24
+ - Run tests in watch mode: `npm run test:watch`
25
+
@@ -0,0 +1,42 @@
1
+ import ScalekitClient from '../src/scalekit';
2
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
3
+ import { TestOrganizationManager } from './utils/test-data';
4
+
5
+ describe('Connections', () => {
6
+ let client: ScalekitClient;
7
+ let testOrg: string;
8
+
9
+ beforeEach(async () => {
10
+ // Use global client
11
+ client = global.client;
12
+
13
+ // Create test organization for each test
14
+ testOrg = await TestOrganizationManager.createTestOrganization(client);
15
+ });
16
+
17
+ afterEach(async () => {
18
+ // Clean up test organization
19
+ await TestOrganizationManager.cleanupTestOrganization(client, testOrg);
20
+ });
21
+
22
+ describe('listConnections', () => {
23
+ it('should list connections by organization', async () => {
24
+ const connections = await client.connection.listConnections(testOrg);
25
+
26
+ expect(connections).toBeDefined();
27
+ expect(connections.connections).toBeDefined();
28
+ expect(Array.isArray(connections.connections)).toBe(true);
29
+ });
30
+ });
31
+
32
+ describe('listConnectionsByDomain', () => {
33
+ it('should list connections by domain', async () => {
34
+ const domain = 'example.com';
35
+ const connections = await client.connection.listConnectionsByDomain(domain);
36
+
37
+ expect(connections).toBeDefined();
38
+ expect(connections.connections).toBeDefined();
39
+ expect(Array.isArray(connections.connections)).toBe(true);
40
+ });
41
+ });
42
+ });
@@ -0,0 +1,46 @@
1
+ import ScalekitClient from '../src/scalekit';
2
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
3
+ import { TestOrganizationManager } from './utils/test-data';
4
+
5
+ describe('Directories', () => {
6
+ let client: ScalekitClient;
7
+ let testOrg: string;
8
+
9
+ beforeEach(async () => {
10
+ // Use global client
11
+ client = global.client;
12
+
13
+ // Create test organization for each test
14
+ testOrg = await TestOrganizationManager.createTestOrganization(client);
15
+ });
16
+
17
+ afterEach(async () => {
18
+ // Clean up test organization
19
+ await TestOrganizationManager.cleanupTestOrganization(client, testOrg);
20
+ });
21
+
22
+ describe('listDirectories', () => {
23
+ it('should list directories', async () => {
24
+ const directories = await client.directory.listDirectories(testOrg);
25
+
26
+ expect(directories).toBeDefined();
27
+ expect(directories.directories).toBeDefined();
28
+ expect(Array.isArray(directories.directories)).toBe(true);
29
+ });
30
+ });
31
+
32
+ describe('getPrimaryDirectoryByOrganizationId', () => {
33
+ it('should get primary directory by organization id', async () => {
34
+ try {
35
+ const primaryDirectory = await client.directory.getPrimaryDirectoryByOrganizationId(testOrg);
36
+
37
+ expect(primaryDirectory).toBeDefined();
38
+ expect(primaryDirectory.id).toBeDefined();
39
+ expect(primaryDirectory.organizationId).toBe(testOrg);
40
+ } catch (error) {
41
+ // Expected when no directories exist
42
+ expect(error).toBeDefined();
43
+ }
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,176 @@
1
+ import ScalekitClient from '../src/scalekit';
2
+ import { DomainType } from '../src/pkg/grpc/scalekit/v1/domains/domains_pb';
3
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
4
+ import { TestDataGenerator, TestOrganizationManager, TestDomainManager } from './utils/test-data';
5
+
6
+ describe('Domains', () => {
7
+ let client: ScalekitClient;
8
+ let testOrg: string;
9
+
10
+ beforeEach(async () => {
11
+ // Use global client
12
+ client = global.client;
13
+
14
+ // Create test organization for each test
15
+ testOrg = await TestOrganizationManager.createTestOrganization(client);
16
+ });
17
+
18
+ afterEach(async () => {
19
+ // Clean up test organization
20
+ await TestOrganizationManager.cleanupTestOrganization(client, testOrg);
21
+ });
22
+
23
+ describe('createDomain', () => {
24
+ it('should create domain without domainType (backward compatibility)', async () => {
25
+ const domainName = TestDataGenerator.generateUniqueDomainName('backward-compat');
26
+
27
+ const response = await client.domain.createDomain(testOrg, domainName);
28
+
29
+ expect(response).toBeDefined();
30
+ expect(response.domain).toBeDefined();
31
+ expect(response.domain?.domain).toBe(domainName);
32
+ expect(response.domain?.id).toBeDefined();
33
+ expect(response.domain?.organizationId).toBe(testOrg);
34
+ });
35
+
36
+ it('should create domain with ALLOWED_EMAIL_DOMAIN type', async () => {
37
+ const domainName = TestDataGenerator.generateUniqueDomainName('allowed-email');
38
+
39
+ const response = await client.domain.createDomain(testOrg, domainName, {
40
+ domainType: DomainType.ALLOWED_EMAIL_DOMAIN
41
+ });
42
+
43
+ expect(response).toBeDefined();
44
+ expect(response.domain).toBeDefined();
45
+ expect(response.domain?.domain).toBe(domainName);
46
+ expect(response.domain?.domainType).toBe(DomainType.ALLOWED_EMAIL_DOMAIN);
47
+ expect(response.domain?.id).toBeDefined();
48
+ expect(response.domain?.organizationId).toBe(testOrg);
49
+ });
50
+
51
+ it('should create domain with ORGANIZATION_DOMAIN type', async () => {
52
+ const domainName = TestDataGenerator.generateUniqueDomainName('org-domain');
53
+
54
+ const response = await client.domain.createDomain(testOrg, domainName, {
55
+ domainType: DomainType.ORGANIZATION_DOMAIN
56
+ });
57
+
58
+ expect(response).toBeDefined();
59
+ expect(response.domain).toBeDefined();
60
+ expect(response.domain?.domain).toBe(domainName);
61
+ expect(response.domain?.domainType).toBe(DomainType.ORGANIZATION_DOMAIN);
62
+ expect(response.domain?.id).toBeDefined();
63
+ expect(response.domain?.organizationId).toBe(testOrg);
64
+ });
65
+
66
+ it('should create multiple domains of different types', async () => {
67
+ const allowedDomainName = TestDataGenerator.generateUniqueDomainName('allowed');
68
+ const orgDomainName = TestDataGenerator.generateUniqueDomainName('org');
69
+
70
+ // Create allowed email domain
71
+ const allowedResponse = await client.domain.createDomain(testOrg, allowedDomainName, {
72
+ domainType: DomainType.ALLOWED_EMAIL_DOMAIN
73
+ });
74
+
75
+ // Create organization domain
76
+ const orgResponse = await client.domain.createDomain(testOrg, orgDomainName, {
77
+ domainType: DomainType.ORGANIZATION_DOMAIN
78
+ });
79
+
80
+ expect(allowedResponse.domain?.domainType).toBe(DomainType.ALLOWED_EMAIL_DOMAIN);
81
+ expect(orgResponse.domain?.domainType).toBe(DomainType.ORGANIZATION_DOMAIN);
82
+ expect(allowedResponse.domain?.domain).toBe(allowedDomainName);
83
+ expect(orgResponse.domain?.domain).toBe(orgDomainName);
84
+ });
85
+
86
+ it('should create domain using TestDomainManager utility', async () => {
87
+ const { domainId, domainName, domainType, response } = await TestDomainManager.createTestDomain(
88
+ client,
89
+ testOrg,
90
+ 'allowed'
91
+ );
92
+
93
+ expect(domainId).toBeDefined();
94
+ expect(domainName).toBeDefined();
95
+ expect(domainType).toBe(DomainType.ALLOWED_EMAIL_DOMAIN);
96
+ expect(response.domain?.domain).toBe(domainName);
97
+ });
98
+ });
99
+
100
+ describe('listDomains', () => {
101
+ it('should list domains for organization', async () => {
102
+ // Create a test domain first
103
+ const { domainName } = await TestDomainManager.createTestDomain(client, testOrg, 'allowed');
104
+
105
+ const response = await client.domain.listDomains(testOrg);
106
+
107
+ expect(response).toBeDefined();
108
+ expect(response.domains).toBeDefined();
109
+ expect(Array.isArray(response.domains)).toBe(true);
110
+ expect(response.domains.length).toBeGreaterThan(0);
111
+
112
+ // Verify basic domain attributes
113
+ const firstDomain = response.domains[0];
114
+ expect(firstDomain.id).toBeDefined();
115
+ expect(firstDomain.domain).toBeDefined();
116
+ expect(firstDomain.organizationId).toBe(testOrg);
117
+ });
118
+
119
+ it('should return empty list when no domains exist', async () => {
120
+ // Use a fresh organization without any domains
121
+ const freshOrg = await TestOrganizationManager.createTestOrganization(client);
122
+
123
+ try {
124
+ const response = await client.domain.listDomains(freshOrg);
125
+
126
+ expect(response).toBeDefined();
127
+ expect(response.domains).toBeDefined();
128
+ expect(Array.isArray(response.domains)).toBe(true);
129
+ expect(response.domains.length).toBe(0);
130
+ } finally {
131
+ await TestOrganizationManager.cleanupTestOrganization(client, freshOrg);
132
+ }
133
+ });
134
+
135
+ it('should list domains with different types', async () => {
136
+ // Create domains of different types
137
+ const { domainName: allowedDomainName } = await TestDomainManager.createTestDomain(client, testOrg, 'allowed');
138
+ const { domainName: orgDomainName } = await TestDomainManager.createTestDomain(client, testOrg, 'organization');
139
+
140
+ const response = await client.domain.listDomains(testOrg);
141
+
142
+ expect(response.domains.length).toBeGreaterThanOrEqual(2);
143
+
144
+ const domainNames = response.domains.map(d => d.domain);
145
+ const domainTypes = response.domains.map(d => d.domainType);
146
+
147
+ expect(domainNames).toContain(allowedDomainName);
148
+ expect(domainNames).toContain(orgDomainName);
149
+ expect(domainTypes).toContain(DomainType.ALLOWED_EMAIL_DOMAIN);
150
+ expect(domainTypes).toContain(DomainType.ORGANIZATION_DOMAIN);
151
+ });
152
+ });
153
+
154
+ describe('error handling', () => {
155
+ it('should handle invalid organization ID', async () => {
156
+ const invalidOrgId = 'invalid-org-id';
157
+ const domainName = TestDataGenerator.generateUniqueDomainName('error-test');
158
+
159
+ await expect(
160
+ client.domain.createDomain(invalidOrgId, domainName)
161
+ ).rejects.toThrow();
162
+
163
+ await expect(
164
+ client.domain.listDomains(invalidOrgId)
165
+ ).rejects.toThrow();
166
+ });
167
+
168
+ it('should handle invalid domain name format', async () => {
169
+ const invalidDomainName = 'invalid-domain-name';
170
+
171
+ await expect(
172
+ client.domain.createDomain(testOrg, invalidDomainName)
173
+ ).rejects.toThrow();
174
+ });
175
+ });
176
+ });
@@ -0,0 +1,65 @@
1
+ import ScalekitClient from '../src/scalekit';
2
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
3
+ import { TestDataGenerator, TestOrganizationManager } from './utils/test-data';
4
+
5
+ describe('Organizations', () => {
6
+ let client: ScalekitClient;
7
+ let testOrg: string;
8
+
9
+ beforeEach(async () => {
10
+ // Use global client
11
+ client = global.client;
12
+
13
+ // Create test organization for each test
14
+ testOrg = await TestOrganizationManager.createTestOrganization(client);
15
+ });
16
+
17
+ afterEach(async () => {
18
+ // Clean up test organization
19
+ await TestOrganizationManager.cleanupTestOrganization(client, testOrg);
20
+ });
21
+
22
+ describe('listOrganization', () => {
23
+ it('should list organizations', async () => {
24
+ const organizations = await client.organization.listOrganization(TestDataGenerator.generatePaginationParams());
25
+
26
+ expect(organizations).toBeDefined();
27
+ expect(organizations.organizations).toBeDefined();
28
+ expect(Array.isArray(organizations.organizations)).toBe(true);
29
+ expect(organizations.organizations.length).toBeGreaterThan(0);
30
+
31
+ // Verify basic organization attributes
32
+ const firstOrg = organizations.organizations[0];
33
+ expect(firstOrg.id).toBeDefined();
34
+ expect(firstOrg.displayName).toBeDefined();
35
+ });
36
+
37
+ it('should handle pagination', async () => {
38
+ const firstPage = await client.organization.listOrganization(TestDataGenerator.generatePaginationParams(5));
39
+
40
+ expect(firstPage).toBeDefined();
41
+ expect(firstPage.organizations.length).toBeLessThanOrEqual(5);
42
+
43
+ if (firstPage.nextPageToken) {
44
+ const secondPage = await client.organization.listOrganization({
45
+ pageSize: 5,
46
+ pageToken: firstPage.nextPageToken
47
+ });
48
+
49
+ expect(secondPage).toBeDefined();
50
+ expect(secondPage.organizations).toBeDefined();
51
+ }
52
+ });
53
+ });
54
+
55
+ describe('getOrganization', () => {
56
+ it('should get organization by ID', async () => {
57
+ const organization = await client.organization.getOrganization(testOrg);
58
+
59
+ expect(organization).toBeDefined();
60
+ expect(organization.organization).toBeDefined();
61
+ expect(organization.organization?.id).toBe(testOrg);
62
+ expect(organization.organization?.displayName).toBeDefined();
63
+ });
64
+ });
65
+ });
@@ -0,0 +1,108 @@
1
+ import ScalekitClient from '../src/scalekit';
2
+ import { TemplateType } from '../src/pkg/grpc/scalekit/v1/auth/passwordless_pb';
3
+ import { describe, it, expect, beforeEach } from '@jest/globals';
4
+ import { TestDataGenerator } from './utils/test-data';
5
+
6
+ describe('Passwordless', () => {
7
+ let client: ScalekitClient;
8
+
9
+ beforeEach(() => {
10
+ // Use global client
11
+ client = global.client;
12
+ });
13
+
14
+ describe('sendPasswordlessEmail', () => {
15
+ it('should send passwordless email with basic parameters', async () => {
16
+ const email = TestDataGenerator.generateUniqueEmail();
17
+
18
+ const response = await client.passwordless.sendPasswordlessEmail(email, TestDataGenerator.generatePasswordlessEmailData());
19
+
20
+ expect(response).toBeDefined();
21
+ expect(response.authRequestId).toBeDefined();
22
+ expect(response.expiresAt).toBeDefined();
23
+ expect(response.expiresIn).toBe(3600);
24
+ expect(response.passwordlessType).toBeDefined();
25
+ });
26
+
27
+ it('should send passwordless email with template variables', async () => {
28
+ const email = TestDataGenerator.generateUniqueEmail();
29
+
30
+ const response = await client.passwordless.sendPasswordlessEmail(email, TestDataGenerator.generatePasswordlessEmailWithTemplateData());
31
+
32
+ expect(response).toBeDefined();
33
+ expect(response.authRequestId).toBeDefined();
34
+ });
35
+
36
+ it('should throw error for invalid email', async () => {
37
+ await expect(
38
+ client.passwordless.sendPasswordlessEmail('', {
39
+ template: TemplateType.SIGNIN
40
+ })
41
+ ).rejects.toThrow('Email must be a valid string');
42
+ });
43
+
44
+ it('should throw error for invalid template type', async () => {
45
+ const email = TestDataGenerator.generateUniqueEmail();
46
+
47
+ await expect(
48
+ client.passwordless.sendPasswordlessEmail(email, {
49
+ template: 'INVALID_TEMPLATE' as any
50
+ })
51
+ ).rejects.toThrow('Invalid template type');
52
+ });
53
+ });
54
+
55
+ describe('verifyPasswordlessEmail', () => {
56
+ it('should throw error when neither code nor linkToken is provided', async () => {
57
+ await expect(
58
+ client.passwordless.verifyPasswordlessEmail({})
59
+ ).rejects.toThrow('Either code or linkToken must be provided');
60
+ });
61
+
62
+ it('should verify with code', async () => {
63
+ // Mock code for testing - expected to fail
64
+ const credential = TestDataGenerator.generateCredentialData('code');
65
+
66
+ try {
67
+ const response = await client.passwordless.verifyPasswordlessEmail(credential);
68
+ expect(response).toBeDefined();
69
+ } catch (error) {
70
+ // Expected failure with mock code
71
+ expect(error).toBeDefined();
72
+ }
73
+ });
74
+
75
+ it('should verify with linkToken', async () => {
76
+ // Mock linkToken for testing - expected to fail
77
+ const credential = TestDataGenerator.generateCredentialData('linkToken');
78
+
79
+ try {
80
+ const response = await client.passwordless.verifyPasswordlessEmail(credential);
81
+ expect(response).toBeDefined();
82
+ } catch (error) {
83
+ // Expected failure with mock linkToken
84
+ expect(error).toBeDefined();
85
+ }
86
+ });
87
+ });
88
+
89
+ describe('resendPasswordlessEmail', () => {
90
+ it('should resend passwordless email', async () => {
91
+ // Send initial passwordless email
92
+ const email = TestDataGenerator.generateUniqueEmail();
93
+
94
+ const sendResponse = await client.passwordless.sendPasswordlessEmail(email, TestDataGenerator.generatePasswordlessEmailData());
95
+
96
+ // Resend the email
97
+ const resendResponse = await client.passwordless.resendPasswordlessEmail(
98
+ sendResponse.authRequestId
99
+ );
100
+
101
+ expect(resendResponse).toBeDefined();
102
+ expect(resendResponse.authRequestId).toBeDefined();
103
+ expect(resendResponse.expiresAt).toBeDefined();
104
+ expect(resendResponse.expiresIn).toBeDefined();
105
+ expect(resendResponse.passwordlessType).toBeDefined();
106
+ });
107
+ });
108
+ });