@elevasis/core 0.11.1 → 0.12.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.
Files changed (38) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +10 -11
  3. package/dist/organization-model/index.d.ts +2 -2
  4. package/dist/organization-model/index.js +10 -11
  5. package/dist/test-utils/index.d.ts +10 -3
  6. package/dist/test-utils/index.js +6 -6
  7. package/package.json +1 -1
  8. package/src/__tests__/template-core-compatibility.test.ts +6 -15
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +27 -270
  10. package/src/auth/multi-tenancy/credentials/server/encryption.ts +83 -39
  11. package/src/auth/multi-tenancy/credentials/server/kek-loader.ts +47 -0
  12. package/src/auth/multi-tenancy/index.ts +3 -0
  13. package/src/auth/multi-tenancy/invitations/api-schemas.ts +104 -107
  14. package/src/auth/multi-tenancy/memberships/api-schemas.ts +6 -5
  15. package/src/auth/multi-tenancy/memberships/membership.ts +130 -138
  16. package/src/auth/multi-tenancy/role-management/api-schemas.ts +78 -0
  17. package/src/auth/multi-tenancy/role-management/index.ts +16 -0
  18. package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +299 -293
  19. package/src/execution/engine/tools/integration/service.test.ts +214 -0
  20. package/src/execution/engine/tools/integration/service.ts +169 -161
  21. package/src/integrations/credentials/__tests__/api-schemas.test.ts +420 -496
  22. package/src/integrations/credentials/api-schemas.ts +127 -143
  23. package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +327 -318
  24. package/src/integrations/webhook-endpoints/api-schemas.ts +103 -102
  25. package/src/integrations/webhook-endpoints/types.ts +58 -51
  26. package/src/operations/activities/api-schemas.ts +80 -79
  27. package/src/operations/activities/types.ts +64 -63
  28. package/src/organization-model/contracts.ts +1 -1
  29. package/src/organization-model/defaults.ts +6 -6
  30. package/src/organization-model/domains/navigation.ts +38 -37
  31. package/src/organization-model/foundation.ts +2 -3
  32. package/src/organization-model/published.ts +3 -3
  33. package/src/platform/constants/versions.ts +1 -1
  34. package/src/reference/_generated/contracts.md +27 -270
  35. package/src/scaffold-registry/__tests__/index.test.ts +72 -7
  36. package/src/scaffold-registry/index.ts +159 -26
  37. package/src/server.ts +281 -272
  38. package/src/supabase/database.types.ts +7 -3
@@ -1,293 +1,299 @@
1
- import { describe, it, expect, beforeAll, vi } from 'vitest'
2
- import { ApifyAdapter } from '../apify-adapter'
3
- import type { ExecutionContext } from '../../../../../../base/types'
4
- import { createClient } from '@supabase/supabase-js'
5
- import type { Database } from '../../../../../../../../supabase/database.types'
6
- import { decryptCredentialValue } from '../../../../../../../../auth/multi-tenancy/credentials/server/service'
7
- import type { RunActorResult } from '../fetch/run-actor'
8
-
9
- /**
10
- * Apify Actor Integration Test Suite
11
- *
12
- * Tests the complete runActor flow:
13
- * 1. Start actor with input
14
- * 2. Poll for completion
15
- * 3. Fetch dataset results
16
- * 4. Handle various actor statuses
17
- *
18
- * Prerequisites:
19
- * - Supabase database with credentials table
20
- * - Credential 'elevasis-apify' with valid Apify API token
21
- * - Organization ID: f9aa5a56-8c13-4cd1-9161-8827ae7b452b
22
- * - SUPABASE_URL and SUPABASE_SERVICE_KEY env vars set
23
- * - SECRETS_ENCRYPTION_KEY env var set
24
- * - Apify account accessible with provided credentials
25
- *
26
- * Run: pnpm test apify-run-actor.integration.test.ts
27
- */
28
-
29
- const SKIP_TESTS = !process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_KEY || !process.env.SECRETS_ENCRYPTION_KEY
30
-
31
- describe.skipIf(SKIP_TESTS)('Apify Run Actor Integration Tests', () => {
32
- const adapter = new ApifyAdapter()
33
- const organizationId = 'f9aa5a56-8c13-4cd1-9161-8827ae7b452b'
34
- const credentialName = 'elevasis-apify'
35
-
36
- const context: ExecutionContext = {
37
- organizationId,
38
- executionId: 'apify-integration-test',
39
- resourceId: 'apify-test-agent',
40
- resourceType: 'agent',
41
- logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), child: vi.fn().mockReturnThis() }
42
- }
43
-
44
- let supabase: ReturnType<typeof createClient<Database>>
45
- let credentials: Record<string, unknown>
46
-
47
- beforeAll(async () => {
48
- console.log('\n=== Apify Actor Integration Test Suite ===')
49
- console.log(`Organization: ${organizationId}`)
50
- console.log(`Credential: ${credentialName}\n`)
51
-
52
- // Initialize Supabase
53
- supabase = createClient<Database>(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!)
54
-
55
- // Fetch credential from database
56
- const { data: credRow, error } = await supabase
57
- .from('credentials')
58
- .select('encrypted_value')
59
- .eq('organization_id', organizationId)
60
- .eq('name', credentialName)
61
- .single()
62
-
63
- if (error || !credRow) {
64
- throw new Error(`Credential '${credentialName}' not found for org ${organizationId}. Error: ${error?.message}`)
65
- }
66
-
67
- // Decrypt credentials
68
- credentials = decryptCredentialValue(credRow.encrypted_value)
69
-
70
- console.log(' Credentials loaded and decrypted')
71
- console.log('Credential fields:', Object.keys(credentials))
72
- console.log(
73
- 'Credential values (masked):',
74
- Object.keys(credentials).reduce(
75
- (acc, key) => {
76
- acc[key] = typeof credentials[key] === 'string' ? credentials[key].substring(0, 10) + '...' : credentials[key]
77
- return acc
78
- },
79
- {} as Record<string, unknown>
80
- )
81
- )
82
- const tokenPresent = !!(credentials.apiToken || credentials.apiKey || credentials.api_token || credentials.token)
83
- console.log(`✓ API Token present: ${tokenPresent}`)
84
-
85
- // Validate credentials
86
- const isValid = adapter.validateCredentials(credentials)
87
- if (!isValid) {
88
- throw new Error('Credential validation failed')
89
- }
90
- console.log('✓ Credentials validated')
91
- console.log('\n✓ Ready to run tests')
92
- })
93
-
94
- it('should run a simple actor and return results', { timeout: 60000 }, async () => {
95
- console.log('\n[Test] Run simple hello-world actor')
96
-
97
- // Using apify/hello-world - a simple actor that runs quickly
98
- const result = (await adapter.call(
99
- 'runActor',
100
- {
101
- actorId: 'apify/hello-world',
102
- input: {
103
- message: 'Integration test from Elevasis'
104
- },
105
- timeoutSecs: 60,
106
- pollIntervalSecs: 5
107
- },
108
- credentials,
109
- context
110
- )) as RunActorResult
111
-
112
- console.log(`✓ Actor completed with status: ${result.status}`)
113
- console.log(`✓ Run ID: ${result.runId}`)
114
- console.log(`✓ Dataset ID: ${result.datasetId}`)
115
- console.log(`✓ Items returned: ${result.totalCount}`)
116
- console.log(`✓ Execution time: ${result.executionTimeMs}ms`)
117
-
118
- expect(result).toBeDefined()
119
- expect(result.status).toBe('SUCCEEDED')
120
- expect(result.runId).toBeDefined()
121
- expect(result.datasetId).toBeDefined()
122
- expect(result.items).toBeInstanceOf(Array)
123
- expect(result.totalCount).toBeGreaterThanOrEqual(0)
124
- expect(result.executionTimeMs).toBeGreaterThan(0)
125
- })
126
-
127
- it('should handle actor with custom input', { timeout: 60000 }, async () => {
128
- console.log('\n[Test] Run actor with custom input')
129
-
130
- const result = (await adapter.call(
131
- 'runActor',
132
- {
133
- actorId: 'apify/hello-world',
134
- input: {
135
- message: 'Custom test message',
136
- outputDatasetItems: 5
137
- },
138
- timeoutSecs: 60,
139
- pollIntervalSecs: 5
140
- },
141
- credentials,
142
- context
143
- )) as RunActorResult
144
-
145
- console.log(`✓ Actor completed: ${result.status}`)
146
- console.log(`✓ Items in dataset: ${result.totalCount}`)
147
-
148
- expect(result).toBeDefined()
149
- expect(result.status).toBe('SUCCEEDED')
150
- expect(result.items).toBeInstanceOf(Array)
151
- })
152
-
153
- it('should handle maxItems parameter', { timeout: 60000 }, async () => {
154
- console.log('\n[Test] Run actor with maxItems limit')
155
-
156
- const maxItems = 3
157
-
158
- const result = (await adapter.call(
159
- 'runActor',
160
- {
161
- actorId: 'apify/hello-world',
162
- input: {
163
- outputDatasetItems: 10
164
- },
165
- maxItems: maxItems,
166
- timeoutSecs: 60,
167
- pollIntervalSecs: 5
168
- },
169
- credentials,
170
- context
171
- )) as RunActorResult
172
-
173
- console.log(`✓ Actor completed: ${result.status}`)
174
- console.log(`✓ Items returned (limited): ${result.totalCount}`)
175
-
176
- expect(result).toBeDefined()
177
- expect(result.status).toBe('SUCCEEDED')
178
- expect(result.items).toBeInstanceOf(Array)
179
- expect(result.items.length).toBeLessThanOrEqual(maxItems)
180
- })
181
-
182
- it('should handle timeout gracefully', { timeout: 15000 }, async () => {
183
- console.log('\n[Test] Handle actor timeout')
184
-
185
- // Use very short timeout to trigger TIMED_OUT status
186
- const result = (await adapter.call(
187
- 'runActor',
188
- {
189
- actorId: 'apify/hello-world',
190
- input: {},
191
- timeoutSecs: 1, // Very short timeout
192
- pollIntervalSecs: 1
193
- },
194
- credentials,
195
- context
196
- )) as RunActorResult
197
-
198
- console.log(`✓ Actor status: ${result.status}`)
199
- console.log(`✓ Timeout handled gracefully`)
200
-
201
- expect(result).toBeDefined()
202
- // Actor might succeed, timeout, or fail depending on external state
203
- expect(['SUCCEEDED', 'TIMED_OUT', 'FAILED']).toContain(result.status)
204
-
205
- if (result.status === 'TIMED_OUT') {
206
- expect(result.items).toEqual([])
207
- expect(result.totalCount).toBe(0)
208
- console.log('✓ TIMED_OUT status returns empty items as expected')
209
- }
210
- })
211
-
212
- it('should handle invalid actor ID gracefully', async () => {
213
- console.log('\n[Test] Handle invalid actor ID')
214
-
215
- await expect(
216
- adapter.call(
217
- 'runActor',
218
- {
219
- actorId: 'invalid/nonexistent-actor-xyz-123',
220
- input: {},
221
- timeoutSecs: 30,
222
- pollIntervalSecs: 5
223
- },
224
- credentials,
225
- context
226
- )
227
- ).rejects.toThrow()
228
-
229
- console.log('✓ Invalid actor ID rejected as expected')
230
- })
231
-
232
- it('should handle missing actorId parameter', async () => {
233
- console.log('\n[Test] Handle missing actorId')
234
-
235
- await expect(
236
- adapter.call(
237
- 'runActor',
238
- {
239
- // Missing actorId
240
- input: {},
241
- timeoutSecs: 30
242
- },
243
- credentials,
244
- context
245
- )
246
- ).rejects.toThrow('Missing required parameter: actorId')
247
-
248
- console.log('✓ Missing actorId parameter rejected as expected')
249
- })
250
-
251
- it('should handle invalid credentials gracefully', async () => {
252
- console.log('\n[Test] Handle invalid credentials')
253
-
254
- const invalidCreds = { apiToken: 'invalid-token-12345' }
255
-
256
- await expect(
257
- adapter.call(
258
- 'runActor',
259
- {
260
- actorId: 'apify/hello-world',
261
- input: {},
262
- timeoutSecs: 30
263
- },
264
- invalidCreds,
265
- context
266
- )
267
- ).rejects.toThrow()
268
-
269
- console.log('✓ Invalid credentials rejected as expected')
270
- })
271
-
272
- it('should validate credentials correctly', () => {
273
- console.log('\n[Test] Validate credentials')
274
-
275
- // Valid credentials
276
- const validCreds = { apiToken: 'test-token' }
277
- expect(adapter.validateCredentials(validCreds)).toBe(true)
278
-
279
- // Invalid credentials (missing apiToken)
280
- const invalidCreds1 = {}
281
- expect(adapter.validateCredentials(invalidCreds1)).toBe(false)
282
-
283
- // Invalid credentials (empty apiToken)
284
- const invalidCreds2 = { apiToken: '' }
285
- expect(adapter.validateCredentials(invalidCreds2)).toBe(false)
286
-
287
- // Invalid credentials (wrong type)
288
- const invalidCreds3 = { apiToken: 123 }
289
- expect(adapter.validateCredentials(invalidCreds3)).toBe(false)
290
-
291
- console.log('✓ Credential validation working correctly')
292
- })
293
- })
1
+ import { describe, it, expect, beforeAll, vi } from 'vitest'
2
+ import { ApifyAdapter } from '../apify-adapter'
3
+ import type { ExecutionContext } from '../../../../../../base/types'
4
+ import { createClient } from '@supabase/supabase-js'
5
+ import type { Database } from '../../../../../../../../supabase/database.types'
6
+ import { decryptCredentialValue } from '../../../../../../../../auth/multi-tenancy/credentials/server/service'
7
+ import { loadCredentialKEKs } from '../../../../../../../../auth/multi-tenancy/credentials/server/kek-loader'
8
+ import type { RunActorResult } from '../fetch/run-actor'
9
+
10
+ /**
11
+ * Apify Actor Integration Test Suite
12
+ *
13
+ * Tests the complete runActor flow:
14
+ * 1. Start actor with input
15
+ * 2. Poll for completion
16
+ * 3. Fetch dataset results
17
+ * 4. Handle various actor statuses
18
+ *
19
+ * Prerequisites:
20
+ * - Supabase database with credentials table
21
+ * - Credential 'elevasis-apify' with valid Apify API token
22
+ * - Organization ID: f9aa5a56-8c13-4cd1-9161-8827ae7b452b
23
+ * - SUPABASE_URL and SUPABASE_SERVICE_KEY env vars set
24
+ * - SECRETS_ENCRYPTION_KEY env var set
25
+ * - Apify account accessible with provided credentials
26
+ *
27
+ * Run: pnpm test apify-run-actor.integration.test.ts
28
+ */
29
+
30
+ const SKIP_TESTS = !process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_KEY || !process.env.SECRETS_ENCRYPTION_KEY
31
+
32
+ describe.skipIf(SKIP_TESTS)('Apify Run Actor Integration Tests', () => {
33
+ const adapter = new ApifyAdapter()
34
+ const organizationId = 'f9aa5a56-8c13-4cd1-9161-8827ae7b452b'
35
+ const credentialName = 'elevasis-apify'
36
+
37
+ const context: ExecutionContext = {
38
+ organizationId,
39
+ executionId: 'apify-integration-test',
40
+ resourceId: 'apify-test-agent',
41
+ resourceType: 'agent',
42
+ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), child: vi.fn().mockReturnThis() }
43
+ }
44
+
45
+ let supabase: ReturnType<typeof createClient<Database>>
46
+ let credentials: Record<string, unknown>
47
+
48
+ beforeAll(async () => {
49
+ console.log('\n=== Apify Actor Integration Test Suite ===')
50
+ console.log(`Organization: ${organizationId}`)
51
+ console.log(`Credential: ${credentialName}\n`)
52
+
53
+ // Initialize Supabase
54
+ supabase = createClient<Database>(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!)
55
+
56
+ // Load Vault KEK so platform-v1 ciphertext (post-Wave-B4 re-encrypted rows)
57
+ // decrypts. Without this, the env-fallback would register the legacy key
58
+ // under platform-v1 and AES-GCM auth-tag verification would fail.
59
+ await loadCredentialKEKs()
60
+
61
+ // Fetch credential from database
62
+ const { data: credRow, error } = await supabase
63
+ .from('credentials')
64
+ .select('encrypted_value')
65
+ .eq('organization_id', organizationId)
66
+ .eq('name', credentialName)
67
+ .single()
68
+
69
+ if (error || !credRow) {
70
+ throw new Error(`Credential '${credentialName}' not found for org ${organizationId}. Error: ${error?.message}`)
71
+ }
72
+
73
+ // Decrypt credentials
74
+ credentials = decryptCredentialValue(credRow.encrypted_value)
75
+
76
+ console.log('✓ Credentials loaded and decrypted')
77
+ console.log('Credential fields:', Object.keys(credentials))
78
+ console.log(
79
+ 'Credential values (masked):',
80
+ Object.keys(credentials).reduce(
81
+ (acc, key) => {
82
+ acc[key] = typeof credentials[key] === 'string' ? credentials[key].substring(0, 10) + '...' : credentials[key]
83
+ return acc
84
+ },
85
+ {} as Record<string, unknown>
86
+ )
87
+ )
88
+ const tokenPresent = !!(credentials.apiToken || credentials.apiKey || credentials.api_token || credentials.token)
89
+ console.log(`✓ API Token present: ${tokenPresent}`)
90
+
91
+ // Validate credentials
92
+ const isValid = adapter.validateCredentials(credentials)
93
+ if (!isValid) {
94
+ throw new Error('Credential validation failed')
95
+ }
96
+ console.log('✓ Credentials validated')
97
+ console.log('\n✓ Ready to run tests')
98
+ })
99
+
100
+ it('should run a simple actor and return results', { timeout: 60000 }, async () => {
101
+ console.log('\n[Test] Run simple hello-world actor')
102
+
103
+ // Using apify/hello-world - a simple actor that runs quickly
104
+ const result = (await adapter.call(
105
+ 'runActor',
106
+ {
107
+ actorId: 'apify/hello-world',
108
+ input: {
109
+ message: 'Integration test from Elevasis'
110
+ },
111
+ timeoutSecs: 60,
112
+ pollIntervalSecs: 5
113
+ },
114
+ credentials,
115
+ context
116
+ )) as RunActorResult
117
+
118
+ console.log(`✓ Actor completed with status: ${result.status}`)
119
+ console.log(`✓ Run ID: ${result.runId}`)
120
+ console.log(`✓ Dataset ID: ${result.datasetId}`)
121
+ console.log(`✓ Items returned: ${result.totalCount}`)
122
+ console.log(`✓ Execution time: ${result.executionTimeMs}ms`)
123
+
124
+ expect(result).toBeDefined()
125
+ expect(result.status).toBe('SUCCEEDED')
126
+ expect(result.runId).toBeDefined()
127
+ expect(result.datasetId).toBeDefined()
128
+ expect(result.items).toBeInstanceOf(Array)
129
+ expect(result.totalCount).toBeGreaterThanOrEqual(0)
130
+ expect(result.executionTimeMs).toBeGreaterThan(0)
131
+ })
132
+
133
+ it('should handle actor with custom input', { timeout: 60000 }, async () => {
134
+ console.log('\n[Test] Run actor with custom input')
135
+
136
+ const result = (await adapter.call(
137
+ 'runActor',
138
+ {
139
+ actorId: 'apify/hello-world',
140
+ input: {
141
+ message: 'Custom test message',
142
+ outputDatasetItems: 5
143
+ },
144
+ timeoutSecs: 60,
145
+ pollIntervalSecs: 5
146
+ },
147
+ credentials,
148
+ context
149
+ )) as RunActorResult
150
+
151
+ console.log(`✓ Actor completed: ${result.status}`)
152
+ console.log(`✓ Items in dataset: ${result.totalCount}`)
153
+
154
+ expect(result).toBeDefined()
155
+ expect(result.status).toBe('SUCCEEDED')
156
+ expect(result.items).toBeInstanceOf(Array)
157
+ })
158
+
159
+ it('should handle maxItems parameter', { timeout: 60000 }, async () => {
160
+ console.log('\n[Test] Run actor with maxItems limit')
161
+
162
+ const maxItems = 3
163
+
164
+ const result = (await adapter.call(
165
+ 'runActor',
166
+ {
167
+ actorId: 'apify/hello-world',
168
+ input: {
169
+ outputDatasetItems: 10
170
+ },
171
+ maxItems: maxItems,
172
+ timeoutSecs: 60,
173
+ pollIntervalSecs: 5
174
+ },
175
+ credentials,
176
+ context
177
+ )) as RunActorResult
178
+
179
+ console.log(`✓ Actor completed: ${result.status}`)
180
+ console.log(`✓ Items returned (limited): ${result.totalCount}`)
181
+
182
+ expect(result).toBeDefined()
183
+ expect(result.status).toBe('SUCCEEDED')
184
+ expect(result.items).toBeInstanceOf(Array)
185
+ expect(result.items.length).toBeLessThanOrEqual(maxItems)
186
+ })
187
+
188
+ it('should handle timeout gracefully', { timeout: 15000 }, async () => {
189
+ console.log('\n[Test] Handle actor timeout')
190
+
191
+ // Use very short timeout to trigger TIMED_OUT status
192
+ const result = (await adapter.call(
193
+ 'runActor',
194
+ {
195
+ actorId: 'apify/hello-world',
196
+ input: {},
197
+ timeoutSecs: 1, // Very short timeout
198
+ pollIntervalSecs: 1
199
+ },
200
+ credentials,
201
+ context
202
+ )) as RunActorResult
203
+
204
+ console.log(`✓ Actor status: ${result.status}`)
205
+ console.log(`✓ Timeout handled gracefully`)
206
+
207
+ expect(result).toBeDefined()
208
+ // Actor might succeed, timeout, or fail depending on external state
209
+ expect(['SUCCEEDED', 'TIMED_OUT', 'FAILED']).toContain(result.status)
210
+
211
+ if (result.status === 'TIMED_OUT') {
212
+ expect(result.items).toEqual([])
213
+ expect(result.totalCount).toBe(0)
214
+ console.log('✓ TIMED_OUT status returns empty items as expected')
215
+ }
216
+ })
217
+
218
+ it('should handle invalid actor ID gracefully', async () => {
219
+ console.log('\n[Test] Handle invalid actor ID')
220
+
221
+ await expect(
222
+ adapter.call(
223
+ 'runActor',
224
+ {
225
+ actorId: 'invalid/nonexistent-actor-xyz-123',
226
+ input: {},
227
+ timeoutSecs: 30,
228
+ pollIntervalSecs: 5
229
+ },
230
+ credentials,
231
+ context
232
+ )
233
+ ).rejects.toThrow()
234
+
235
+ console.log('✓ Invalid actor ID rejected as expected')
236
+ })
237
+
238
+ it('should handle missing actorId parameter', async () => {
239
+ console.log('\n[Test] Handle missing actorId')
240
+
241
+ await expect(
242
+ adapter.call(
243
+ 'runActor',
244
+ {
245
+ // Missing actorId
246
+ input: {},
247
+ timeoutSecs: 30
248
+ },
249
+ credentials,
250
+ context
251
+ )
252
+ ).rejects.toThrow('Missing required parameter: actorId')
253
+
254
+ console.log('✓ Missing actorId parameter rejected as expected')
255
+ })
256
+
257
+ it('should handle invalid credentials gracefully', async () => {
258
+ console.log('\n[Test] Handle invalid credentials')
259
+
260
+ const invalidCreds = { apiToken: 'invalid-token-12345' }
261
+
262
+ await expect(
263
+ adapter.call(
264
+ 'runActor',
265
+ {
266
+ actorId: 'apify/hello-world',
267
+ input: {},
268
+ timeoutSecs: 30
269
+ },
270
+ invalidCreds,
271
+ context
272
+ )
273
+ ).rejects.toThrow()
274
+
275
+ console.log('✓ Invalid credentials rejected as expected')
276
+ })
277
+
278
+ it('should validate credentials correctly', () => {
279
+ console.log('\n[Test] Validate credentials')
280
+
281
+ // Valid credentials
282
+ const validCreds = { apiToken: 'test-token' }
283
+ expect(adapter.validateCredentials(validCreds)).toBe(true)
284
+
285
+ // Invalid credentials (missing apiToken)
286
+ const invalidCreds1 = {}
287
+ expect(adapter.validateCredentials(invalidCreds1)).toBe(false)
288
+
289
+ // Invalid credentials (empty apiToken)
290
+ const invalidCreds2 = { apiToken: '' }
291
+ expect(adapter.validateCredentials(invalidCreds2)).toBe(false)
292
+
293
+ // Invalid credentials (wrong type)
294
+ const invalidCreds3 = { apiToken: 123 }
295
+ expect(adapter.validateCredentials(invalidCreds3)).toBe(false)
296
+
297
+ console.log('✓ Credential validation working correctly')
298
+ })
299
+ })