@app-connect/core 0.0.2 → 1.5.8

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.
@@ -0,0 +1,271 @@
1
+ const adapterRegistry = require('../../adapter/registry');
2
+
3
+ describe('AdapterRegistry Interface Registration with Composition', () => {
4
+ beforeEach(() => {
5
+ // Clear the registry before each test
6
+ adapterRegistry.adapters.clear();
7
+ adapterRegistry.manifests.clear();
8
+ adapterRegistry.platformInterfaces.clear();
9
+ });
10
+
11
+ test('should register interface functions for a platform', () => {
12
+ const mockFunction = jest.fn();
13
+
14
+ adapterRegistry.registerAdapterInterface('testPlatform', 'testInterface', mockFunction);
15
+
16
+ expect(adapterRegistry.hasPlatformInterface('testPlatform', 'testInterface')).toBe(true);
17
+ expect(adapterRegistry.getPlatformInterfaces('testPlatform').get('testInterface')).toBe(mockFunction);
18
+ });
19
+
20
+ test('should throw error when registering non-function as interface', () => {
21
+ expect(() => {
22
+ adapterRegistry.registerAdapterInterface('testPlatform', 'testInterface', 'not a function');
23
+ }).toThrow('Interface function must be a function, got: string');
24
+ });
25
+
26
+ test('should return original adapter when no interfaces are registered', () => {
27
+ const mockAdapter = {
28
+ getAuthType: () => 'apiKey',
29
+ createCallLog: jest.fn(),
30
+ updateCallLog: jest.fn()
31
+ };
32
+
33
+ adapterRegistry.registerAdapter('testPlatform', mockAdapter);
34
+
35
+ const retrievedAdapter = adapterRegistry.getAdapter('testPlatform');
36
+ expect(retrievedAdapter).toBe(mockAdapter);
37
+ });
38
+
39
+ test('should return composed adapter with interface functions when interfaces are registered', () => {
40
+ const mockInterface = jest.fn();
41
+ const mockAdapter = {
42
+ getAuthType: () => 'apiKey',
43
+ createCallLog: jest.fn(),
44
+ updateCallLog: jest.fn()
45
+ };
46
+
47
+ // Register interface function first
48
+ adapterRegistry.registerAdapterInterface('testPlatform', 'customMethod', mockInterface);
49
+
50
+ // Register adapter
51
+ adapterRegistry.registerAdapter('testPlatform', mockAdapter);
52
+
53
+ // Get composed adapter
54
+ const composedAdapter = adapterRegistry.getAdapter('testPlatform');
55
+
56
+ // Should be a different object (composed)
57
+ expect(composedAdapter).not.toBe(mockAdapter);
58
+
59
+ // Should have the interface function
60
+ expect(composedAdapter.customMethod).toBe(mockInterface);
61
+
62
+ // Should still have original methods
63
+ expect(composedAdapter.getAuthType).toBe(mockAdapter.getAuthType);
64
+ expect(composedAdapter.createCallLog).toBe(mockAdapter.createCallLog);
65
+ });
66
+
67
+ test('should not override existing adapter methods when composing interfaces', () => {
68
+ const existingMethod = jest.fn();
69
+ const mockAdapter = {
70
+ getAuthType: () => 'apiKey',
71
+ createCallLog: jest.fn(),
72
+ updateCallLog: jest.fn(),
73
+ existingMethod: existingMethod
74
+ };
75
+
76
+ // Register adapter first
77
+ adapterRegistry.registerAdapter('testPlatform', mockAdapter);
78
+
79
+ // Try to register interface with same name as existing method
80
+ const newMethod = jest.fn();
81
+ adapterRegistry.registerAdapterInterface('testPlatform', 'existingMethod', newMethod);
82
+
83
+ // Get composed adapter
84
+ const composedAdapter = adapterRegistry.getAdapter('testPlatform');
85
+
86
+ // Should not override the existing method
87
+ expect(composedAdapter.existingMethod).toBe(existingMethod);
88
+ expect(composedAdapter.existingMethod).not.toBe(newMethod);
89
+ });
90
+
91
+ test('should preserve original adapter when composing interfaces', () => {
92
+ const mockInterface = jest.fn();
93
+ const mockAdapter = {
94
+ getAuthType: () => 'apiKey',
95
+ createCallLog: jest.fn(),
96
+ updateCallLog: jest.fn()
97
+ };
98
+
99
+ adapterRegistry.registerAdapterInterface('testPlatform', 'customMethod', mockInterface);
100
+ adapterRegistry.registerAdapter('testPlatform', mockAdapter);
101
+
102
+ // Get original adapter
103
+ const originalAdapter = adapterRegistry.getOriginalAdapter('testPlatform');
104
+
105
+ // Original adapter should be unchanged
106
+ expect(originalAdapter).toBe(mockAdapter);
107
+ expect(originalAdapter.customMethod).toBeUndefined();
108
+
109
+ // Composed adapter should have the interface
110
+ const composedAdapter = adapterRegistry.getAdapter('testPlatform');
111
+ expect(composedAdapter.customMethod).toBe(mockInterface);
112
+ });
113
+
114
+ test('should unregister interface functions', () => {
115
+ const mockFunction = jest.fn();
116
+
117
+ adapterRegistry.registerAdapterInterface('testPlatform', 'testInterface', mockFunction);
118
+ expect(adapterRegistry.hasPlatformInterface('testPlatform', 'testInterface')).toBe(true);
119
+
120
+ adapterRegistry.unregisterAdapterInterface('testPlatform', 'testInterface');
121
+ expect(adapterRegistry.hasPlatformInterface('testPlatform', 'testInterface')).toBe(false);
122
+ });
123
+
124
+ test('should return empty map for non-existent platform interfaces', () => {
125
+ const interfaces = adapterRegistry.getPlatformInterfaces('nonExistentPlatform');
126
+ expect(interfaces).toBeInstanceOf(Map);
127
+ expect(interfaces.size).toBe(0);
128
+ });
129
+
130
+ test('should return false for non-existent platform interface', () => {
131
+ expect(adapterRegistry.hasPlatformInterface('nonExistentPlatform', 'anyInterface')).toBe(false);
132
+ });
133
+
134
+ test('should handle multiple interface functions for same platform', () => {
135
+ const mockFunction1 = jest.fn();
136
+ const mockFunction2 = jest.fn();
137
+ const mockAdapter = {
138
+ getAuthType: () => 'apiKey',
139
+ createCallLog: jest.fn(),
140
+ updateCallLog: jest.fn()
141
+ };
142
+
143
+ adapterRegistry.registerAdapterInterface('testPlatform', 'interface1', mockFunction1);
144
+ adapterRegistry.registerAdapterInterface('testPlatform', 'interface2', mockFunction2);
145
+ adapterRegistry.registerAdapter('testPlatform', mockAdapter);
146
+
147
+ const platformInterfaces = adapterRegistry.getPlatformInterfaces('testPlatform');
148
+ expect(platformInterfaces.size).toBe(2);
149
+ expect(platformInterfaces.get('interface1')).toBe(mockFunction1);
150
+ expect(platformInterfaces.get('interface2')).toBe(mockFunction2);
151
+
152
+ // Check composed adapter has both interfaces
153
+ const composedAdapter = adapterRegistry.getAdapter('testPlatform');
154
+ expect(composedAdapter.interface1).toBe(mockFunction1);
155
+ expect(composedAdapter.interface2).toBe(mockFunction2);
156
+ });
157
+
158
+ test('should clean up platform interfaces when unregistering adapter', () => {
159
+ const mockFunction = jest.fn();
160
+ const mockAdapter = {
161
+ getAuthType: () => 'apiKey',
162
+ createCallLog: jest.fn(),
163
+ updateCallLog: jest.fn()
164
+ };
165
+
166
+ adapterRegistry.registerAdapterInterface('testPlatform', 'testInterface', mockFunction);
167
+ adapterRegistry.registerAdapter('testPlatform', mockAdapter);
168
+
169
+ expect(adapterRegistry.hasPlatformInterface('testPlatform', 'testInterface')).toBe(true);
170
+
171
+ adapterRegistry.unregisterAdapter('testPlatform');
172
+
173
+ expect(adapterRegistry.hasPlatformInterface('testPlatform', 'testInterface')).toBe(false);
174
+ });
175
+
176
+ test('should get adapter capabilities correctly', () => {
177
+ const mockInterface = jest.fn();
178
+ const mockAdapter = {
179
+ getAuthType: () => 'apiKey',
180
+ createCallLog: jest.fn(),
181
+ updateCallLog: jest.fn()
182
+ };
183
+
184
+ adapterRegistry.registerAdapterInterface('testPlatform', 'customMethod', mockInterface);
185
+ adapterRegistry.registerAdapter('testPlatform', mockAdapter);
186
+
187
+ const capabilities = adapterRegistry.getAdapterCapabilities('testPlatform');
188
+
189
+ expect(capabilities.platform).toBe('testPlatform');
190
+ expect(capabilities.originalMethods).toContain('getAuthType');
191
+ expect(capabilities.originalMethods).toContain('createCallLog');
192
+ expect(capabilities.originalMethods).toContain('updateCallLog');
193
+ expect(capabilities.composedMethods).toContain('customMethod');
194
+ expect(capabilities.registeredInterfaces).toContain('customMethod');
195
+ expect(capabilities.authType).toBe('apiKey');
196
+ });
197
+
198
+ test('should handle interface registration after adapter registration', () => {
199
+ const mockAdapter = {
200
+ getAuthType: () => 'apiKey',
201
+ createCallLog: jest.fn(),
202
+ updateCallLog: jest.fn()
203
+ };
204
+
205
+ // Register adapter first
206
+ adapterRegistry.registerAdapter('testPlatform', mockAdapter);
207
+
208
+ // Register interface function after
209
+ const mockInterface = jest.fn();
210
+ adapterRegistry.registerAdapterInterface('testPlatform', 'customMethod', mockInterface);
211
+
212
+ // Get composed adapter
213
+ const composedAdapter = adapterRegistry.getAdapter('testPlatform');
214
+
215
+ // Should have the interface function
216
+ expect(composedAdapter.customMethod).toBe(mockInterface);
217
+
218
+ // Original adapter should be unchanged
219
+ const originalAdapter = adapterRegistry.getOriginalAdapter('testPlatform');
220
+ expect(originalAdapter.customMethod).toBeUndefined();
221
+ });
222
+
223
+ test('should return interface-only adapter when no base adapter is registered', () => {
224
+ const mockInterface1 = jest.fn();
225
+ const mockInterface2 = jest.fn();
226
+
227
+ // Register only interface functions, no base adapter
228
+ adapterRegistry.registerAdapterInterface('interfaceOnlyPlatform', 'method1', mockInterface1);
229
+ adapterRegistry.registerAdapterInterface('interfaceOnlyPlatform', 'method2', mockInterface2);
230
+
231
+ // Get adapter - should return interface-only object
232
+ const interfaceOnlyAdapter = adapterRegistry.getAdapter('interfaceOnlyPlatform');
233
+
234
+ // Should have interface functions
235
+ expect(interfaceOnlyAdapter.method1).toBe(mockInterface1);
236
+ expect(interfaceOnlyAdapter.method2).toBe(mockInterface2);
237
+
238
+ // Should not have base adapter methods
239
+ expect(interfaceOnlyAdapter.getAuthType).toBeUndefined();
240
+
241
+ // Should be a plain object, not inherited from any adapter
242
+ expect(Object.getPrototypeOf(interfaceOnlyAdapter)).toBe(Object.prototype);
243
+ });
244
+
245
+ test('should throw error when no adapter and no interfaces are registered', () => {
246
+ expect(() => {
247
+ adapterRegistry.getAdapter('nonExistentPlatform');
248
+ }).toThrow('Adapter not found for platform: nonExistentPlatform');
249
+ });
250
+
251
+ test('should handle mixed scenarios correctly', () => {
252
+ // Scenario 1: Only interfaces, no adapter
253
+ adapterRegistry.registerAdapterInterface('mixedPlatform', 'interfaceMethod', jest.fn());
254
+ const interfaceOnly = adapterRegistry.getAdapter('mixedPlatform');
255
+ expect(interfaceOnly.interfaceMethod).toBeDefined();
256
+ expect(interfaceOnly.getAuthType).toBeUndefined();
257
+
258
+ // Scenario 2: Add adapter later
259
+ const mockAdapter = {
260
+ getAuthType: () => 'apiKey',
261
+ createCallLog: jest.fn(),
262
+ updateCallLog: jest.fn()
263
+ };
264
+ adapterRegistry.registerAdapter('mixedPlatform', mockAdapter);
265
+
266
+ const composedAdapter = adapterRegistry.getAdapter('mixedPlatform');
267
+ expect(composedAdapter.interfaceMethod).toBeDefined();
268
+ expect(composedAdapter.getAuthType).toBeDefined();
269
+ expect(composedAdapter.getAuthType()).toBe('apiKey');
270
+ });
271
+ });
@@ -0,0 +1,231 @@
1
+ const authHandler = require('../../handlers/auth');
2
+ const adapterRegistry = require('../../adapter/registry');
3
+
4
+ // Mock the adapter registry
5
+ jest.mock('../../adapter/registry');
6
+
7
+ describe('Auth Handler', () => {
8
+ beforeEach(() => {
9
+ // Reset mocks
10
+ jest.clearAllMocks();
11
+ global.testUtils.resetAdapterRegistry();
12
+ });
13
+
14
+ describe('onApiKeyLogin', () => {
15
+ test('should handle successful API key login', async () => {
16
+ // Arrange
17
+ const mockUserInfo = {
18
+ successful: true,
19
+ platformUserInfo: {
20
+ id: 'test-user-id',
21
+ name: 'Test User',
22
+ timezoneName: 'America/Los_Angeles',
23
+ timezoneOffset: 0,
24
+ platformAdditionalInfo: {}
25
+ },
26
+ returnMessage: {
27
+ messageType: 'success',
28
+ message: 'Login successful',
29
+ ttl: 1000
30
+ }
31
+ };
32
+
33
+ const mockAdapter = global.testUtils.createMockAdapter({
34
+ getBasicAuth: jest.fn().mockReturnValue('dGVzdC1hcGkta2V5Og=='),
35
+ getUserInfo: jest.fn().mockResolvedValue(mockUserInfo)
36
+ });
37
+
38
+ adapterRegistry.getAdapter.mockReturnValue(mockAdapter);
39
+
40
+ const requestData = {
41
+ platform: 'testCRM',
42
+ hostname: 'test.example.com',
43
+ apiKey: 'test-api-key',
44
+ additionalInfo: {}
45
+ };
46
+
47
+ // Act
48
+ const result = await authHandler.onApiKeyLogin(requestData);
49
+
50
+ // Assert
51
+ expect(result.userInfo).toBeDefined();
52
+ expect(result.userInfo.id).toBe('test-user-id');
53
+ expect(result.userInfo.name).toBe('Test User');
54
+ expect(result.returnMessage).toEqual(mockUserInfo.returnMessage);
55
+ expect(mockAdapter.getBasicAuth).toHaveBeenCalledWith({ apiKey: 'test-api-key' });
56
+ expect(mockAdapter.getUserInfo).toHaveBeenCalledWith({
57
+ authHeader: 'Basic dGVzdC1hcGkta2V5Og==',
58
+ hostname: 'test.example.com',
59
+ additionalInfo: {},
60
+ apiKey: 'test-api-key'
61
+ });
62
+ });
63
+
64
+ test('should handle failed API key login', async () => {
65
+ // Arrange
66
+ const mockUserInfo = {
67
+ successful: false,
68
+ platformUserInfo: null,
69
+ returnMessage: {
70
+ messageType: 'error',
71
+ message: 'Invalid API key',
72
+ ttl: 3000
73
+ }
74
+ };
75
+
76
+ const mockAdapter = global.testUtils.createMockAdapter({
77
+ getBasicAuth: jest.fn().mockReturnValue('dGVzdC1hcGkta2V5Og=='),
78
+ getUserInfo: jest.fn().mockResolvedValue(mockUserInfo)
79
+ });
80
+
81
+ adapterRegistry.getAdapter.mockReturnValue(mockAdapter);
82
+
83
+ const requestData = {
84
+ platform: 'testCRM',
85
+ hostname: 'test.example.com',
86
+ apiKey: 'invalid-api-key',
87
+ additionalInfo: {}
88
+ };
89
+
90
+ // Act
91
+ const result = await authHandler.onApiKeyLogin(requestData);
92
+
93
+ // Assert
94
+ expect(result.userInfo).toBeNull();
95
+ expect(result.returnMessage).toEqual(mockUserInfo.returnMessage);
96
+ });
97
+
98
+ test('should throw error when adapter not found', async () => {
99
+ // Arrange
100
+ adapterRegistry.getAdapter.mockImplementation(() => {
101
+ throw new Error('Adapter not found for platform: testCRM');
102
+ });
103
+
104
+ const requestData = {
105
+ platform: 'testCRM',
106
+ hostname: 'test.example.com',
107
+ apiKey: 'test-api-key',
108
+ additionalInfo: {}
109
+ };
110
+
111
+ // Act & Assert
112
+ await expect(authHandler.onApiKeyLogin(requestData))
113
+ .rejects.toThrow('Adapter not found for platform: testCRM');
114
+ });
115
+ });
116
+
117
+ describe('authValidation', () => {
118
+ test('should validate user authentication successfully', async () => {
119
+ // Arrange
120
+ const mockUser = global.testUtils.createMockUser();
121
+ const mockValidationResponse = {
122
+ successful: true,
123
+ returnMessage: {
124
+ messageType: 'success',
125
+ message: 'Authentication valid',
126
+ ttl: 1000
127
+ },
128
+ status: 200
129
+ };
130
+
131
+ const mockAdapter = global.testUtils.createMockAdapter({
132
+ getOauthInfo: jest.fn().mockResolvedValue({}),
133
+ authValidation: jest.fn().mockResolvedValue(mockValidationResponse)
134
+ });
135
+
136
+ adapterRegistry.getAdapter.mockReturnValue(mockAdapter);
137
+
138
+ // Mock UserModel.findOne to return a user
139
+ const { UserModel } = require('../../models/userModel');
140
+ jest.spyOn(UserModel, 'findOne').mockResolvedValue(mockUser);
141
+
142
+ // Mock oauth.checkAndRefreshAccessToken
143
+ const oauth = require('../../lib/oauth');
144
+ jest.spyOn(oauth, 'checkAndRefreshAccessToken').mockResolvedValue(mockUser);
145
+
146
+ const requestData = {
147
+ platform: 'testCRM',
148
+ userId: 'test-user-id'
149
+ };
150
+
151
+ // Act
152
+ const result = await authHandler.authValidation(requestData);
153
+
154
+ // Assert
155
+ expect(result).toEqual({
156
+ ...mockValidationResponse,
157
+ failReason: ''
158
+ });
159
+ expect(mockAdapter.authValidation).toHaveBeenCalledWith({ user: mockUser });
160
+ });
161
+
162
+ test('should handle user not found in database', async () => {
163
+ // Arrange
164
+ const mockAdapter = global.testUtils.createMockAdapter();
165
+ adapterRegistry.getAdapter.mockReturnValue(mockAdapter);
166
+
167
+ // Mock UserModel.findOne to return null (user not found)
168
+ const { UserModel } = require('../../models/userModel');
169
+ jest.spyOn(UserModel, 'findOne').mockResolvedValue(null);
170
+
171
+ const requestData = {
172
+ platform: 'testCRM',
173
+ userId: 'non-existent-user'
174
+ };
175
+
176
+ // Act
177
+ const result = await authHandler.authValidation(requestData);
178
+
179
+ // Assert
180
+ expect(result).toEqual({
181
+ successful: false,
182
+ status: 404,
183
+ failReason: 'App Connect. User not found in database'
184
+ });
185
+ });
186
+
187
+ test('should handle validation failure', async () => {
188
+ // Arrange
189
+ const mockUser = global.testUtils.createMockUser();
190
+ const mockValidationResponse = {
191
+ successful: false,
192
+ returnMessage: {
193
+ messageType: 'error',
194
+ message: 'Authentication failed',
195
+ ttl: 3000
196
+ },
197
+ status: 401
198
+ };
199
+
200
+ const mockAdapter = global.testUtils.createMockAdapter({
201
+ getOauthInfo: jest.fn().mockResolvedValue({}),
202
+ authValidation: jest.fn().mockResolvedValue(mockValidationResponse)
203
+ });
204
+
205
+ adapterRegistry.getAdapter.mockReturnValue(mockAdapter);
206
+
207
+ // Mock UserModel.findOne to return a user
208
+ const { UserModel } = require('../../models/userModel');
209
+ jest.spyOn(UserModel, 'findOne').mockResolvedValue(mockUser);
210
+
211
+ // Mock oauth.checkAndRefreshAccessToken
212
+ const oauth = require('../../lib/oauth');
213
+ jest.spyOn(oauth, 'checkAndRefreshAccessToken').mockResolvedValue(mockUser);
214
+
215
+ const requestData = {
216
+ platform: 'testCRM',
217
+ userId: 'test-user-id'
218
+ };
219
+
220
+ // Act
221
+ const result = await authHandler.authValidation(requestData);
222
+
223
+ // Assert
224
+ expect(result).toEqual({
225
+ ...mockValidationResponse,
226
+ failReason: 'CRM. API failed'
227
+ });
228
+ expect(result.successful).toBe(false);
229
+ });
230
+ });
231
+ });
@@ -0,0 +1,161 @@
1
+ const jwt = require('../../lib/jwt');
2
+
3
+ describe('JWT Utility', () => {
4
+ beforeEach(() => {
5
+ // Reset environment
6
+ process.env.APP_SERVER_SECRET_KEY = 'test-secret-key';
7
+ });
8
+
9
+ describe('generateJwt', () => {
10
+ test('should generate JWT token from payload', () => {
11
+ // Arrange
12
+ const payload = {
13
+ id: 'test-user-id',
14
+ platform: 'testCRM'
15
+ };
16
+
17
+ // Act
18
+ const token = jwt.generateJwt(payload);
19
+
20
+ // Assert
21
+ expect(token).toBeDefined();
22
+ expect(typeof token).toBe('string');
23
+ expect(token.split('.')).toHaveLength(3); // JWT has 3 parts
24
+ });
25
+
26
+ test('should generate different tokens for different payloads', () => {
27
+ // Arrange
28
+ const payload1 = { id: 'user1', platform: 'testCRM' };
29
+ const payload2 = { id: 'user2', platform: 'testCRM' };
30
+
31
+ // Act
32
+ const token1 = jwt.generateJwt(payload1);
33
+ const token2 = jwt.generateJwt(payload2);
34
+
35
+ // Assert
36
+ expect(token1).not.toBe(token2);
37
+ });
38
+ });
39
+
40
+ describe('decodeJwt', () => {
41
+ test('should decode valid JWT token', () => {
42
+ // Arrange
43
+ const payload = {
44
+ id: 'test-user-id',
45
+ platform: 'testCRM'
46
+ };
47
+ const token = jwt.generateJwt(payload);
48
+
49
+ // Act
50
+ const decoded = jwt.decodeJwt(token);
51
+
52
+ // Assert
53
+ expect(decoded).toMatchObject(payload);
54
+ expect(decoded).toHaveProperty('exp');
55
+ expect(decoded).toHaveProperty('iat');
56
+ });
57
+
58
+ test('should return null for invalid token', () => {
59
+ // Arrange
60
+ const invalidToken = 'invalid.jwt.token';
61
+
62
+ // Act
63
+ const decoded = jwt.decodeJwt(invalidToken);
64
+
65
+ // Assert
66
+ expect(decoded).toBeNull();
67
+ });
68
+
69
+ test('should return null for malformed token', () => {
70
+ // Arrange
71
+ const malformedToken = 'not-a-jwt-token';
72
+
73
+ // Act
74
+ const decoded = jwt.decodeJwt(malformedToken);
75
+
76
+ // Assert
77
+ expect(decoded).toBeNull();
78
+ });
79
+
80
+ test('should return null for token with wrong secret', () => {
81
+ // Arrange
82
+ const payload = { id: 'test-user-id', platform: 'testCRM' };
83
+ const token = jwt.generateJwt(payload);
84
+
85
+ // Change secret temporarily
86
+ const originalSecret = process.env.APP_SERVER_SECRET_KEY;
87
+ process.env.APP_SERVER_SECRET_KEY = 'different-secret';
88
+
89
+ // Act
90
+ const decoded = jwt.decodeJwt(token);
91
+
92
+ // Restore secret
93
+ process.env.APP_SERVER_SECRET_KEY = originalSecret;
94
+
95
+ // Assert
96
+ expect(decoded).toBeNull();
97
+ });
98
+ });
99
+
100
+ describe('generateJwt and decodeJwt round trip', () => {
101
+ test('should successfully generate and decode complex payload', () => {
102
+ // Arrange
103
+ const complexPayload = {
104
+ id: 'test-user-id',
105
+ platform: 'testCRM',
106
+ timestamp: Date.now(),
107
+ metadata: {
108
+ timezone: 'America/Los_Angeles',
109
+ preferences: {
110
+ autoLog: true,
111
+ callPop: false
112
+ }
113
+ }
114
+ };
115
+
116
+ // Act
117
+ const token = jwt.generateJwt(complexPayload);
118
+ const decoded = jwt.decodeJwt(token);
119
+
120
+ // Assert
121
+ expect(decoded).toMatchObject(complexPayload);
122
+ expect(decoded).toHaveProperty('exp');
123
+ expect(decoded).toHaveProperty('iat');
124
+ });
125
+
126
+ test('should handle empty payload', () => {
127
+ // Arrange
128
+ const emptyPayload = {};
129
+
130
+ // Act
131
+ const token = jwt.generateJwt(emptyPayload);
132
+ const decoded = jwt.decodeJwt(token);
133
+
134
+ // Assert
135
+ expect(decoded).toMatchObject(emptyPayload);
136
+ expect(decoded).toHaveProperty('exp');
137
+ expect(decoded).toHaveProperty('iat');
138
+ });
139
+ });
140
+
141
+ describe('error handling', () => {
142
+ test('should handle missing secret key', () => {
143
+ // Arrange
144
+ const payload = { id: 'test-user-id' };
145
+ delete process.env.APP_SERVER_SECRET_KEY;
146
+
147
+ // Act & Assert
148
+ expect(() => jwt.generateJwt(payload)).toThrow();
149
+ });
150
+
151
+ test('should handle null payload', () => {
152
+ // Act & Assert
153
+ expect(() => jwt.generateJwt(null)).toThrow();
154
+ });
155
+
156
+ test('should handle undefined payload', () => {
157
+ // Act & Assert
158
+ expect(() => jwt.generateJwt(undefined)).toThrow();
159
+ });
160
+ });
161
+ });