@scalekit-sdk/node 2.0.0 → 2.1.0
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.
- package/jest.config.js +15 -0
- package/lib/core.d.ts +1 -1
- package/lib/core.js +31 -31
- package/lib/core.js.map +1 -1
- package/lib/errors/base-exception.d.ts +32 -0
- package/lib/errors/base-exception.js +238 -0
- package/lib/errors/base-exception.js.map +1 -0
- package/lib/errors/index.d.ts +2 -0
- package/lib/errors/index.js +20 -0
- package/lib/errors/index.js.map +1 -0
- package/lib/errors/specific-exceptions.d.ts +39 -0
- package/lib/errors/specific-exceptions.js +90 -0
- package/lib/errors/specific-exceptions.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.d.ts +34 -87
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js +31 -120
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.d.ts +19 -10
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js +18 -9
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.d.ts +209 -6
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js +272 -5
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.d.ts +29 -0
- package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.js +40 -1
- package/lib/pkg/grpc/scalekit/v1/domains/domains_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.d.ts +25 -0
- package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.js +38 -1
- package/lib/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.d.ts +21 -1
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.js +20 -0
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.d.ts +110 -5
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js +164 -5
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/users/users_connect.d.ts +48 -1
- package/lib/pkg/grpc/scalekit/v1/users/users_connect.js +47 -0
- package/lib/pkg/grpc/scalekit/v1/users/users_connect.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.d.ts +280 -4
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.js +449 -11
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.js.map +1 -1
- package/lib/scalekit.d.ts +29 -8
- package/lib/scalekit.js +78 -28
- package/lib/scalekit.js.map +1 -1
- package/lib/types/scalekit.d.ts +5 -0
- package/lib/types/user.d.ts +1 -1
- package/lib/user.d.ts +10 -3
- package/lib/user.js +26 -5
- package/lib/user.js.map +1 -1
- package/package.json +6 -2
- package/src/core.ts +31 -32
- package/src/errors/base-exception.ts +262 -0
- package/src/errors/index.ts +3 -0
- package/src/errors/specific-exceptions.ts +88 -0
- package/src/index.ts +3 -1
- package/src/pkg/grpc/scalekit/v1/commons/commons_pb.ts +49 -129
- package/src/pkg/grpc/scalekit/v1/connections/connections_connect.ts +19 -10
- package/src/pkg/grpc/scalekit/v1/connections/connections_pb.ts +377 -8
- package/src/pkg/grpc/scalekit/v1/domains/domains_pb.ts +44 -0
- package/src/pkg/grpc/scalekit/v1/errdetails/errdetails_pb.ts +49 -0
- package/src/pkg/grpc/scalekit/v1/organizations/organizations_connect.ts +21 -1
- package/src/pkg/grpc/scalekit/v1/organizations/organizations_pb.ts +218 -5
- package/src/pkg/grpc/scalekit/v1/users/users_connect.ts +48 -1
- package/src/pkg/grpc/scalekit/v1/users/users_pb.ts +558 -6
- package/src/scalekit.ts +95 -30
- package/src/types/scalekit.ts +6 -0
- package/src/types/user.ts +1 -1
- package/src/user.ts +34 -7
- package/tests/README.md +25 -0
- package/tests/connection.test.ts +42 -0
- package/tests/directory.test.ts +46 -0
- package/tests/organization.test.ts +65 -0
- package/tests/passwordless.test.ts +108 -0
- package/tests/scalekit.test.ts +104 -0
- package/tests/setup.ts +34 -0
- package/tests/users.test.ts +168 -0
- package/tests/utils/test-data.ts +248 -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,248 @@
|
|
|
1
|
+
import { CreateUserRequest, UpdateUserRequest } from '../../src/types/user';
|
|
2
|
+
import { TemplateType } from '../../src/pkg/grpc/scalekit/v1/auth/passwordless_pb';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Test data generation utilities to reduce redundancy across test files
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class TestDataGenerator {
|
|
9
|
+
/**
|
|
10
|
+
* Generate a unique timestamp-based identifier
|
|
11
|
+
*/
|
|
12
|
+
static generateUniqueId(): string {
|
|
13
|
+
return Date.now().toString();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate a unique email address for testing
|
|
18
|
+
*/
|
|
19
|
+
static generateUniqueEmail(): string {
|
|
20
|
+
return `test.user.${this.generateUniqueId()}@example.com`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate test organization data
|
|
25
|
+
*/
|
|
26
|
+
static generateOrganizationData() {
|
|
27
|
+
const uniqueId = this.generateUniqueId();
|
|
28
|
+
return {
|
|
29
|
+
name: `Test Org ${uniqueId}`,
|
|
30
|
+
externalId: `ext_org_${uniqueId}`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate test user data
|
|
36
|
+
*/
|
|
37
|
+
static generateUserData(overrides: Partial<CreateUserRequest> = {}): CreateUserRequest {
|
|
38
|
+
const uniqueEmail = this.generateUniqueEmail();
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
email: uniqueEmail,
|
|
42
|
+
userProfile: {
|
|
43
|
+
firstName: 'Test',
|
|
44
|
+
lastName: 'User'
|
|
45
|
+
},
|
|
46
|
+
metadata: {
|
|
47
|
+
source: 'test'
|
|
48
|
+
},
|
|
49
|
+
...overrides
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generate test user update data
|
|
55
|
+
*/
|
|
56
|
+
static generateUserUpdateData(overrides: Partial<UpdateUserRequest> = {}): UpdateUserRequest {
|
|
57
|
+
return {
|
|
58
|
+
userProfile: {
|
|
59
|
+
firstName: 'Updated',
|
|
60
|
+
lastName: 'Name'
|
|
61
|
+
},
|
|
62
|
+
...overrides
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate test passwordless email data
|
|
68
|
+
*/
|
|
69
|
+
static generatePasswordlessEmailData(overrides: any = {}) {
|
|
70
|
+
return {
|
|
71
|
+
template: TemplateType.SIGNIN,
|
|
72
|
+
state: 'test-state',
|
|
73
|
+
expiresIn: 3600,
|
|
74
|
+
magiclinkAuthUri: 'https://example.com/auth/callback',
|
|
75
|
+
...overrides
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate test passwordless email with template variables
|
|
81
|
+
*/
|
|
82
|
+
static generatePasswordlessEmailWithTemplateData(overrides: any = {}) {
|
|
83
|
+
return {
|
|
84
|
+
template: TemplateType.SIGNUP,
|
|
85
|
+
templateVariables: {
|
|
86
|
+
companyName: 'Test Company',
|
|
87
|
+
appName: 'Test App'
|
|
88
|
+
},
|
|
89
|
+
magiclinkAuthUri: 'https://example.com/auth/callback',
|
|
90
|
+
...overrides
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate test webhook data for verification
|
|
96
|
+
*/
|
|
97
|
+
static generateWebhookData() {
|
|
98
|
+
const secret = 'whsec_test-secret';
|
|
99
|
+
const payload = '{"test": "data"}';
|
|
100
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
101
|
+
const webhookId = 'msg_test_webhook_id';
|
|
102
|
+
|
|
103
|
+
// Generate valid signature for testing
|
|
104
|
+
const crypto = require('crypto');
|
|
105
|
+
const data = `${webhookId}.${timestamp}.${payload}`;
|
|
106
|
+
const hmac = crypto.createHmac('sha256', Buffer.from('test-secret', 'base64'));
|
|
107
|
+
hmac.update(data);
|
|
108
|
+
const computedSignature = hmac.digest('base64');
|
|
109
|
+
const signature = `v1,${computedSignature}`;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
secret,
|
|
113
|
+
payload,
|
|
114
|
+
timestamp,
|
|
115
|
+
webhookId,
|
|
116
|
+
signature,
|
|
117
|
+
headers: {
|
|
118
|
+
'webhook-id': webhookId,
|
|
119
|
+
'webhook-timestamp': timestamp,
|
|
120
|
+
'webhook-signature': signature
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate test authorization URL options
|
|
127
|
+
*/
|
|
128
|
+
static generateAuthorizationUrlOptions(overrides: any = {}) {
|
|
129
|
+
return {
|
|
130
|
+
scopes: ['openid', 'profile'],
|
|
131
|
+
state: 'test-state',
|
|
132
|
+
nonce: 'test-nonce',
|
|
133
|
+
prompt: 'login',
|
|
134
|
+
...overrides
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Generate test PKCE parameters
|
|
140
|
+
*/
|
|
141
|
+
static generatePKCEParams() {
|
|
142
|
+
return {
|
|
143
|
+
codeChallenge: 'test-challenge',
|
|
144
|
+
codeChallengeMethod: 'S256'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generate test pagination parameters
|
|
150
|
+
*/
|
|
151
|
+
static generatePaginationParams(pageSize: number = 10) {
|
|
152
|
+
return {
|
|
153
|
+
pageSize,
|
|
154
|
+
pageToken: ''
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Generate test credential data for passwordless verification
|
|
160
|
+
*/
|
|
161
|
+
static generateCredentialData(type: 'code' | 'linkToken' = 'code') {
|
|
162
|
+
if (type === 'code') {
|
|
163
|
+
return { code: 'mock-code' };
|
|
164
|
+
} else {
|
|
165
|
+
return { linkToken: 'mock-link-token' };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Test organization management utilities
|
|
172
|
+
*/
|
|
173
|
+
export class TestOrganizationManager {
|
|
174
|
+
/**
|
|
175
|
+
* Create a test organization and return its ID
|
|
176
|
+
*/
|
|
177
|
+
static async createTestOrganization(client: any): Promise<string> {
|
|
178
|
+
const orgData = TestDataGenerator.generateOrganizationData();
|
|
179
|
+
const orgResponse = await client.organization.createOrganization(
|
|
180
|
+
orgData.name,
|
|
181
|
+
{ externalId: orgData.externalId }
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const testOrg = orgResponse.organization?.id || '';
|
|
185
|
+
if (!testOrg) {
|
|
186
|
+
throw new Error('Failed to create test organization');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return testOrg;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Clean up a test organization
|
|
194
|
+
*/
|
|
195
|
+
static async cleanupTestOrganization(client: any, testOrg: string): Promise<void> {
|
|
196
|
+
if (testOrg) {
|
|
197
|
+
try {
|
|
198
|
+
await client.organization.deleteOrganization(testOrg);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
// Organization may already be deleted
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Test user management utilities
|
|
208
|
+
*/
|
|
209
|
+
export class TestUserManager {
|
|
210
|
+
/**
|
|
211
|
+
* Create a test user and return user data
|
|
212
|
+
*/
|
|
213
|
+
static async createTestUser(client: any, testOrg: string, overrides: Partial<CreateUserRequest> = {}) {
|
|
214
|
+
const userData = TestDataGenerator.generateUserData(overrides);
|
|
215
|
+
const createResponse = await client.user.createUserAndMembership(testOrg, userData);
|
|
216
|
+
const createdUserId = createResponse.user?.id;
|
|
217
|
+
|
|
218
|
+
if (!createdUserId) {
|
|
219
|
+
throw new Error('Failed to create test user');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
userId: createdUserId,
|
|
224
|
+
userData,
|
|
225
|
+
response: createResponse
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Clean up a test user
|
|
231
|
+
*/
|
|
232
|
+
static async cleanupTestUser(client: any, testOrg: string, userId: string): Promise<void> {
|
|
233
|
+
if (userId) {
|
|
234
|
+
try {
|
|
235
|
+
// Remove membership if it exists
|
|
236
|
+
await client.user.deleteMembership(testOrg, userId);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
// Membership may not exist
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
await client.user.deleteUser(userId);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
// User may already be deleted
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|