@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
@@ -0,0 +1,104 @@
1
+ import ScalekitClient from '../src/scalekit';
2
+ import { AuthenticationOptions } from '../src/types/scalekit';
3
+ import { describe, it, expect, beforeEach } from '@jest/globals';
4
+ import { TestDataGenerator } from './utils/test-data';
5
+
6
+ describe('ScalekitClient', () => {
7
+ let client: ScalekitClient;
8
+
9
+ beforeEach(() => {
10
+ // Use global client
11
+ client = global.client;
12
+ });
13
+
14
+ describe('constructor', () => {
15
+ it('should initialize with correct parameters', () => {
16
+ expect(client).toBeInstanceOf(ScalekitClient);
17
+ expect(client.organization).toBeDefined();
18
+ expect(client.user).toBeDefined();
19
+ expect(client.connection).toBeDefined();
20
+ expect(client.directory).toBeDefined();
21
+ expect(client.passwordless).toBeDefined();
22
+ expect(client.domain).toBeDefined();
23
+ });
24
+ });
25
+
26
+ describe('getAuthorizationUrl', () => {
27
+ it('should generate authorization URL with basic parameters', () => {
28
+ const redirectUri = 'https://example.com/callback';
29
+ const url = client.getAuthorizationUrl(redirectUri);
30
+
31
+ expect(url).toContain('oauth/authorize');
32
+ expect(url).toContain(`redirect_uri=${encodeURIComponent(redirectUri)}`);
33
+ expect(url).toContain('response_type=code');
34
+ });
35
+
36
+ it('should include optional parameters when provided', () => {
37
+ const redirectUri = 'https://example.com/callback';
38
+ const options = TestDataGenerator.generateAuthorizationUrlOptions();
39
+
40
+ const url = client.getAuthorizationUrl(redirectUri, options);
41
+
42
+ expect(url).toContain('scope=openid%20profile');
43
+ expect(url).toContain('state=test-state');
44
+ expect(url).toContain('nonce=test-nonce');
45
+ expect(url).toContain('prompt=login');
46
+ });
47
+
48
+ it('should handle PKCE parameters', () => {
49
+ const redirectUri = 'https://example.com/callback';
50
+ const options = TestDataGenerator.generatePKCEParams();
51
+
52
+ const url = client.getAuthorizationUrl(redirectUri, options);
53
+
54
+ expect(url).toContain('code_challenge=test-challenge');
55
+ expect(url).toContain('code_challenge_method=S256');
56
+ });
57
+ });
58
+
59
+ describe('verifyWebhookPayload', () => {
60
+ it('should verify valid webhook payload', () => {
61
+ const webhookData = TestDataGenerator.generateWebhookData();
62
+
63
+ const result = client.verifyWebhookPayload(webhookData.secret, webhookData.headers, webhookData.payload);
64
+ expect(result).toBe(true);
65
+ });
66
+
67
+ it('should throw error for invalid signature', () => {
68
+ const webhookData = TestDataGenerator.generateWebhookData();
69
+
70
+ // Generate invalid signature using wrong payload data
71
+ const crypto = require('crypto');
72
+ const wrongData = `${webhookData.webhookId}.${webhookData.timestamp}.wrong-payload`;
73
+ const hmac = crypto.createHmac('sha256', Buffer.from('test-secret', 'base64'));
74
+ hmac.update(wrongData);
75
+ const wrongSignature = hmac.digest('base64');
76
+ const signature = `v1,${wrongSignature}`;
77
+
78
+ const headers = {
79
+ 'webhook-id': webhookData.webhookId,
80
+ 'webhook-timestamp': webhookData.timestamp,
81
+ 'webhook-signature': signature
82
+ };
83
+
84
+ expect(() => {
85
+ client.verifyWebhookPayload(webhookData.secret, headers, webhookData.payload);
86
+ }).toThrow('Invalid Signature');
87
+ });
88
+ });
89
+
90
+ describe('validateAccessToken', () => {
91
+ it('should validate access token', async () => {
92
+ // Mock token for testing - expected to fail
93
+ const token = 'mock-token';
94
+
95
+ try {
96
+ const result = await client.validateAccessToken(token);
97
+ expect(typeof result).toBe('boolean');
98
+ } catch (error) {
99
+ // Expected failure with mock token
100
+ expect(error).toBeDefined();
101
+ }
102
+ });
103
+ });
104
+ });
package/tests/setup.ts ADDED
@@ -0,0 +1,34 @@
1
+ import ScalekitClient from '../src/scalekit';
2
+ import dotenv from 'dotenv';
3
+
4
+ // Import Jest globals
5
+ import { beforeAll } from '@jest/globals';
6
+
7
+ // Load environment variables
8
+ dotenv.config();
9
+
10
+ // Global test configuration
11
+ declare global {
12
+ var client: ScalekitClient;
13
+ }
14
+
15
+ beforeAll(() => {
16
+ // Validate required environment variables
17
+ const environmentUrl = process.env.SCALEKIT_ENVIRONMENT_URL;
18
+ const clientId = process.env.SCALEKIT_CLIENT_ID;
19
+ const clientSecret = process.env.SCALEKIT_CLIENT_SECRET;
20
+
21
+ // Check for required environment variables
22
+ if (!environmentUrl) {
23
+ throw new Error('SCALEKIT_ENVIRONMENT_URL environment variable is required');
24
+ }
25
+ if (!clientId) {
26
+ throw new Error('SCALEKIT_CLIENT_ID environment variable is required');
27
+ }
28
+ if (!clientSecret) {
29
+ throw new Error('SCALEKIT_CLIENT_SECRET environment variable is required');
30
+ }
31
+
32
+ // Initialize test client
33
+ global.client = new ScalekitClient(environmentUrl, clientId, clientSecret);
34
+ });
@@ -0,0 +1,168 @@
1
+ import ScalekitClient from '../src/scalekit';
2
+ import { CreateUserRequest, UpdateUserRequest } from '../src/types/user';
3
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
4
+ import { TestDataGenerator, TestOrganizationManager, TestUserManager } from './utils/test-data';
5
+
6
+ describe('Users', () => {
7
+ let client: ScalekitClient;
8
+ let testOrg: string;
9
+ let userId: string | null = null;
10
+ let sharedUserData: CreateUserRequest;
11
+
12
+ beforeEach(async () => {
13
+ // Use global client
14
+ client = global.client;
15
+
16
+ // Create test organization for each test
17
+ testOrg = await TestOrganizationManager.createTestOrganization(client);
18
+
19
+ // Create a shared user for testing
20
+ sharedUserData = TestDataGenerator.generateUserData();
21
+ const createResponse = await client.user.createUserAndMembership(testOrg, sharedUserData);
22
+ userId = createResponse.user?.id || null;
23
+ expect(userId).toBeDefined();
24
+ });
25
+
26
+ afterEach(async () => {
27
+ // Clean up test resources
28
+ if (userId) {
29
+ await TestUserManager.cleanupTestUser(client, testOrg, userId);
30
+ userId = null;
31
+ }
32
+
33
+ // Clean up test organization
34
+ await TestOrganizationManager.cleanupTestOrganization(client, testOrg);
35
+ });
36
+
37
+ describe('listOrganizationUsers', () => {
38
+ it('should list users by organization', async () => {
39
+ // List users in the organization
40
+ const usersList = await client.user.listOrganizationUsers(testOrg, TestDataGenerator.generatePaginationParams());
41
+
42
+ expect(usersList).toBeDefined();
43
+ expect(usersList.users).toBeDefined();
44
+ expect(Array.isArray(usersList.users)).toBe(true);
45
+ expect(usersList.users.length).toBeGreaterThan(0);
46
+
47
+ // Verify basic user attributes
48
+ const firstUser = usersList.users[0];
49
+ expect(firstUser.id).toBeDefined();
50
+ expect(firstUser.email).toBeDefined();
51
+ expect(firstUser.environmentId).toBeDefined();
52
+ });
53
+
54
+ it('should handle pagination', async () => {
55
+ const firstPage = await client.user.listOrganizationUsers(testOrg, TestDataGenerator.generatePaginationParams(5));
56
+
57
+ expect(firstPage).toBeDefined();
58
+ expect(firstPage.users.length).toBeLessThanOrEqual(5);
59
+
60
+ if (firstPage.nextPageToken) {
61
+ const secondPage = await client.user.listOrganizationUsers(testOrg, {
62
+ pageSize: 5,
63
+ pageToken: firstPage.nextPageToken
64
+ });
65
+
66
+ expect(secondPage).toBeDefined();
67
+ expect(secondPage.users).toBeDefined();
68
+ }
69
+ });
70
+ });
71
+
72
+ describe('getUser', () => {
73
+ it('should get user by ID', async () => {
74
+ // Retrieve the user by ID
75
+ const user = await client.user.getUser(userId!);
76
+
77
+ expect(user).toBeDefined();
78
+ expect(user.user).toBeDefined();
79
+ expect(user.user?.id).toBe(userId);
80
+ expect(user.user?.email).toBe(sharedUserData.email);
81
+ });
82
+ });
83
+
84
+ describe('createUserAndMembership', () => {
85
+ it('should create user and membership', async () => {
86
+ // Create a new user for this specific test
87
+ const userData = TestDataGenerator.generateUserData();
88
+ let newUserId: string | null = null;
89
+
90
+ try {
91
+ const response = await client.user.createUserAndMembership(testOrg, userData);
92
+
93
+ expect(response).toBeDefined();
94
+ expect(response.user).toBeDefined();
95
+ expect(response.user?.id).toBeDefined();
96
+ expect(response.user?.email).toBe(userData.email);
97
+ expect(response.user?.metadata?.source).toBe('test');
98
+
99
+ newUserId = response.user?.id || null;
100
+ } finally {
101
+ // Clean up the new user created in this test
102
+ if (newUserId) {
103
+ await TestUserManager.cleanupTestUser(client, testOrg, newUserId);
104
+ }
105
+ }
106
+ });
107
+
108
+ it('should throw error when email is missing', async () => {
109
+ const userData = TestDataGenerator.generateUserData({ email: '' }); // Empty email
110
+
111
+ await expect(
112
+ client.user.createUserAndMembership(testOrg, userData)
113
+ ).rejects.toThrow('email is required');
114
+ });
115
+
116
+ it('should throw error when organizationId is missing', async () => {
117
+ const userData = TestDataGenerator.generateUserData({ email: 'test@example.com' });
118
+
119
+ await expect(
120
+ client.user.createUserAndMembership('', userData)
121
+ ).rejects.toThrow('organizationId is required');
122
+ });
123
+ });
124
+
125
+ describe('updateUser', () => {
126
+ it('should update user', async () => {
127
+ // Modify the shared user
128
+ const updateData = TestDataGenerator.generateUserUpdateData();
129
+
130
+ const updatedUser = await client.user.updateUser(userId!, updateData);
131
+
132
+ expect(updatedUser).toBeDefined();
133
+ expect(updatedUser.user).toBeDefined();
134
+ expect(updatedUser.user?.id).toBe(userId);
135
+ expect(updatedUser.user?.userProfile?.firstName).toBe('Updated');
136
+ expect(updatedUser.user?.userProfile?.lastName).toBe('Name');
137
+ });
138
+ });
139
+
140
+ describe('resendInvite', () => {
141
+ it('should resend invite to user', async () => {
142
+ // Resend invite to the shared user
143
+ const resendResponse = await client.user.resendInvite(testOrg, userId!);
144
+
145
+ // Verify the response structure
146
+ expect(resendResponse).toBeDefined();
147
+ expect(resendResponse.invite).toBeDefined();
148
+ expect(resendResponse.invite?.userId).toBe(userId);
149
+ expect(resendResponse.invite?.organizationId).toBe(testOrg);
150
+ expect(resendResponse.invite?.status).toBe('PENDING_INVITE');
151
+ expect(resendResponse.invite?.createdAt).toBeDefined();
152
+ expect(resendResponse.invite?.expiresAt).toBeDefined();
153
+ expect(resendResponse.invite?.resentCount).toBe(1);
154
+ });
155
+
156
+ it('should throw error when organizationId is missing', async () => {
157
+ await expect(
158
+ client.user.resendInvite('', userId!)
159
+ ).rejects.toThrow('organizationId is required');
160
+ });
161
+
162
+ it('should throw error when userId is missing', async () => {
163
+ await expect(
164
+ client.user.resendInvite(testOrg, '')
165
+ ).rejects.toThrow('userId is required');
166
+ });
167
+ });
168
+ });
@@ -0,0 +1,314 @@
1
+ import { CreateUserRequest, UpdateUserRequest } from '../../src/types/user';
2
+ import { TemplateType } from '../../src/pkg/grpc/scalekit/v1/auth/passwordless_pb';
3
+ import { DomainType } from '../../src/pkg/grpc/scalekit/v1/domains/domains_pb';
4
+
5
+ /**
6
+ * Test data generation utilities to reduce redundancy across test files
7
+ */
8
+
9
+ export class TestDataGenerator {
10
+ /**
11
+ * Generate a unique timestamp-based identifier
12
+ */
13
+ static generateUniqueId(): string {
14
+ return Date.now().toString();
15
+ }
16
+
17
+ /**
18
+ * Generate a unique email address for testing
19
+ */
20
+ static generateUniqueEmail(): string {
21
+ return `test.user.${this.generateUniqueId()}@example.com`;
22
+ }
23
+
24
+ /**
25
+ * Generate test organization data
26
+ */
27
+ static generateOrganizationData() {
28
+ const uniqueId = this.generateUniqueId();
29
+ return {
30
+ name: `Test Org ${uniqueId}`,
31
+ externalId: `ext_org_${uniqueId}`
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Generate test user data
37
+ */
38
+ static generateUserData(overrides: Partial<CreateUserRequest> = {}): CreateUserRequest {
39
+ const uniqueEmail = this.generateUniqueEmail();
40
+
41
+ return {
42
+ email: uniqueEmail,
43
+ userProfile: {
44
+ firstName: 'Test',
45
+ lastName: 'User'
46
+ },
47
+ metadata: {
48
+ source: 'test'
49
+ },
50
+ ...overrides
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Generate test user update data
56
+ */
57
+ static generateUserUpdateData(overrides: Partial<UpdateUserRequest> = {}): UpdateUserRequest {
58
+ return {
59
+ userProfile: {
60
+ firstName: 'Updated',
61
+ lastName: 'Name'
62
+ },
63
+ ...overrides
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Generate test passwordless email data
69
+ */
70
+ static generatePasswordlessEmailData(overrides: any = {}) {
71
+ return {
72
+ template: TemplateType.SIGNIN,
73
+ state: 'test-state',
74
+ expiresIn: 3600,
75
+ magiclinkAuthUri: 'https://example.com/auth/callback',
76
+ ...overrides
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Generate test passwordless email with template variables
82
+ */
83
+ static generatePasswordlessEmailWithTemplateData(overrides: any = {}) {
84
+ return {
85
+ template: TemplateType.SIGNUP,
86
+ templateVariables: {
87
+ companyName: 'Test Company',
88
+ appName: 'Test App'
89
+ },
90
+ magiclinkAuthUri: 'https://example.com/auth/callback',
91
+ ...overrides
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Generate test webhook data for verification
97
+ */
98
+ static generateWebhookData() {
99
+ const secret = 'whsec_test-secret';
100
+ const payload = '{"test": "data"}';
101
+ const timestamp = Math.floor(Date.now() / 1000).toString();
102
+ const webhookId = 'msg_test_webhook_id';
103
+
104
+ // Generate valid signature for testing
105
+ const crypto = require('crypto');
106
+ const data = `${webhookId}.${timestamp}.${payload}`;
107
+ const hmac = crypto.createHmac('sha256', Buffer.from('test-secret', 'base64'));
108
+ hmac.update(data);
109
+ const computedSignature = hmac.digest('base64');
110
+ const signature = `v1,${computedSignature}`;
111
+
112
+ return {
113
+ secret,
114
+ payload,
115
+ timestamp,
116
+ webhookId,
117
+ signature,
118
+ headers: {
119
+ 'webhook-id': webhookId,
120
+ 'webhook-timestamp': timestamp,
121
+ 'webhook-signature': signature
122
+ }
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Generate test authorization URL options
128
+ */
129
+ static generateAuthorizationUrlOptions(overrides: any = {}) {
130
+ return {
131
+ scopes: ['openid', 'profile'],
132
+ state: 'test-state',
133
+ nonce: 'test-nonce',
134
+ prompt: 'login',
135
+ ...overrides
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Generate test PKCE parameters
141
+ */
142
+ static generatePKCEParams() {
143
+ return {
144
+ codeChallenge: 'test-challenge',
145
+ codeChallengeMethod: 'S256'
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Generate test pagination parameters
151
+ */
152
+ static generatePaginationParams(pageSize: number = 10) {
153
+ return {
154
+ pageSize,
155
+ pageToken: ''
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Generate test credential data for passwordless verification
161
+ */
162
+ static generateCredentialData(type: 'code' | 'linkToken' = 'code') {
163
+ if (type === 'code') {
164
+ return { code: 'mock-code' };
165
+ } else {
166
+ return { linkToken: 'mock-link-token' };
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Generate test domain data
172
+ */
173
+ static generateDomainData(domainType?: 'allowed' | 'organization') {
174
+ const uniqueId = this.generateUniqueId();
175
+ const baseDomain = `test-domain-${uniqueId}.com`;
176
+
177
+ return {
178
+ domain: baseDomain,
179
+ domainType: domainType === 'allowed' ? 'ALLOWED_EMAIL_DOMAIN' :
180
+ domainType === 'organization' ? 'ORGANIZATION_DOMAIN' : undefined
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Generate unique domain name for testing
186
+ */
187
+ static generateUniqueDomainName(prefix: string = 'test'): string {
188
+ const uniqueId = this.generateUniqueId();
189
+ return `${prefix}-${uniqueId}.com`;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Test organization management utilities
195
+ */
196
+ export class TestOrganizationManager {
197
+ /**
198
+ * Create a test organization and return its ID
199
+ */
200
+ static async createTestOrganization(client: any): Promise<string> {
201
+ const orgData = TestDataGenerator.generateOrganizationData();
202
+ const orgResponse = await client.organization.createOrganization(
203
+ orgData.name,
204
+ { externalId: orgData.externalId }
205
+ );
206
+
207
+ const testOrg = orgResponse.organization?.id || '';
208
+ if (!testOrg) {
209
+ throw new Error('Failed to create test organization');
210
+ }
211
+
212
+ return testOrg;
213
+ }
214
+
215
+ /**
216
+ * Clean up a test organization
217
+ */
218
+ static async cleanupTestOrganization(client: any, testOrg: string): Promise<void> {
219
+ if (testOrg) {
220
+ try {
221
+ await client.organization.deleteOrganization(testOrg);
222
+ } catch (error) {
223
+ // Organization may already be deleted
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Test user management utilities
231
+ */
232
+ export class TestUserManager {
233
+ /**
234
+ * Create a test user and return user data
235
+ */
236
+ static async createTestUser(client: any, testOrg: string, overrides: Partial<CreateUserRequest> = {}) {
237
+ const userData = TestDataGenerator.generateUserData(overrides);
238
+ const createResponse = await client.user.createUserAndMembership(testOrg, userData);
239
+ const createdUserId = createResponse.user?.id;
240
+
241
+ if (!createdUserId) {
242
+ throw new Error('Failed to create test user');
243
+ }
244
+
245
+ return {
246
+ userId: createdUserId,
247
+ userData,
248
+ response: createResponse
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Clean up a test user
254
+ */
255
+ static async cleanupTestUser(client: any, testOrg: string, userId: string): Promise<void> {
256
+ if (userId) {
257
+ try {
258
+ // Remove membership if it exists
259
+ await client.user.deleteMembership(testOrg, userId);
260
+ } catch (error) {
261
+ // Membership may not exist
262
+ }
263
+
264
+ try {
265
+ await client.user.deleteUser(userId);
266
+ } catch (error) {
267
+ // User may already be deleted
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Test domain management utilities
275
+ */
276
+ export class TestDomainManager {
277
+ /**
278
+ * Create a test domain and return domain data
279
+ */
280
+ static async createTestDomain(client: any, testOrg: string, domainType?: 'allowed' | 'organization') {
281
+ const domainName = TestDataGenerator.generateUniqueDomainName();
282
+ const options = domainType ? {
283
+ domainType: domainType === 'allowed' ? DomainType.ALLOWED_EMAIL_DOMAIN : DomainType.ORGANIZATION_DOMAIN
284
+ } : undefined;
285
+
286
+ const response = await client.domain.createDomain(testOrg, domainName, options);
287
+ const createdDomainId = response.domain?.id;
288
+
289
+ if (!createdDomainId) {
290
+ throw new Error('Failed to create test domain');
291
+ }
292
+
293
+ return {
294
+ domainId: createdDomainId,
295
+ domainName,
296
+ domainType: response.domain?.domainType,
297
+ response
298
+ };
299
+ }
300
+
301
+ /**
302
+ * Clean up a test domain (if domain deletion is supported)
303
+ */
304
+ static async cleanupTestDomain(client: any, testOrg: string, domainId: string): Promise<void> {
305
+ if (domainId) {
306
+ try {
307
+ // Note: Domain deletion may not be implemented yet
308
+ // await client.domain.deleteDomain(testOrg, domainId);
309
+ } catch (error) {
310
+ // Domain may already be deleted or deletion not supported
311
+ }
312
+ }
313
+ }
314
+ }