@codeguide/core 0.0.24 → 0.0.25
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/__tests__/services/codespace/codespace-models.test.ts +458 -0
- package/__tests__/services/security-keys.test.ts +587 -0
- package/codeguide.ts +3 -1
- package/dist/codeguide.d.ts +2 -1
- package/dist/codeguide.js +1 -0
- package/dist/services/codespace/codespace-service.d.ts +52 -1
- package/dist/services/codespace/codespace-service.js +87 -0
- package/dist/services/codespace/codespace-types.d.ts +45 -0
- package/dist/services/codespace/index.d.ts +1 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +4 -1
- package/dist/services/security-keys/index.d.ts +3 -0
- package/dist/services/security-keys/index.js +21 -0
- package/dist/services/security-keys/security-keys-service.d.ts +111 -0
- package/dist/services/security-keys/security-keys-service.js +190 -0
- package/dist/services/security-keys/security-keys-types.d.ts +105 -0
- package/dist/services/security-keys/security-keys-types.js +8 -0
- package/package.json +1 -1
- package/services/codespace/codespace-service.ts +104 -0
- package/services/codespace/codespace-types.ts +65 -1
- package/services/codespace/index.ts +12 -1
- package/services/index.ts +2 -0
- package/services/security-keys/index.ts +25 -0
- package/services/security-keys/security-keys-service.ts +229 -0
- package/services/security-keys/security-keys-types.ts +187 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import { SecurityKeysService } from '../../services/security-keys'
|
|
2
|
+
import { APIServiceConfig } from '../../types'
|
|
3
|
+
import {
|
|
4
|
+
CreateProviderAPIKeyRequest,
|
|
5
|
+
CreateGitHubTokenRequest
|
|
6
|
+
} from '../../services/security-keys/security-keys-types'
|
|
7
|
+
import axios from 'axios'
|
|
8
|
+
import axiosMockAdapter from 'axios-mock-adapter'
|
|
9
|
+
|
|
10
|
+
describe('SecurityKeysService', () => {
|
|
11
|
+
let service: SecurityKeysService
|
|
12
|
+
let mock: axiosMockAdapter
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
const config: APIServiceConfig = {
|
|
16
|
+
apiKey: 'test-api-key',
|
|
17
|
+
baseUrl: 'https://api.test.com',
|
|
18
|
+
timeout: 10000,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
service = new SecurityKeysService(config)
|
|
22
|
+
mock = new axiosMockAdapter(axios)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
mock.reset()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Provider API Key Tests
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
describe('Provider API Key Methods', () => {
|
|
34
|
+
describe('createProviderAPIKey', () => {
|
|
35
|
+
it('should create a provider API key successfully', async () => {
|
|
36
|
+
const request = {
|
|
37
|
+
provider_key: 'openai',
|
|
38
|
+
api_key: 'sk-1234567890abcdef1234567890abcdef12345678'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const expectedResponse = {
|
|
42
|
+
status: 'success',
|
|
43
|
+
data: {
|
|
44
|
+
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
45
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
46
|
+
user_id: 'user_abc123',
|
|
47
|
+
name: 'provider_api_key',
|
|
48
|
+
displayed_name: 'OpenAI API Key',
|
|
49
|
+
provider_id: 'openai',
|
|
50
|
+
provider_name: 'OpenAI',
|
|
51
|
+
provider_key: 'openai',
|
|
52
|
+
value_masked: '***************************************5678',
|
|
53
|
+
object_value: {
|
|
54
|
+
provider_id: 'openai'
|
|
55
|
+
},
|
|
56
|
+
encryption: 'fernet',
|
|
57
|
+
key_version: 'v1'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
mock.onPost('/security-keys/provider-api-key', request).reply(201, expectedResponse)
|
|
62
|
+
|
|
63
|
+
const result = await service.createProviderAPIKey(request)
|
|
64
|
+
|
|
65
|
+
expect(result).toEqual(expectedResponse)
|
|
66
|
+
expect(mock.history.post[0].url).toBe('/security-keys/provider-api-key')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should throw error when provider_key is missing', async () => {
|
|
70
|
+
const request = {
|
|
71
|
+
provider_key: '',
|
|
72
|
+
api_key: 'sk-1234567890abcdef1234567890abcdef12345678'
|
|
73
|
+
} as CreateProviderAPIKeyRequest
|
|
74
|
+
|
|
75
|
+
await expect(service.createProviderAPIKey(request)).rejects.toThrow('provider_key is required')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should throw error when api_key is missing', async () => {
|
|
79
|
+
const request = {
|
|
80
|
+
provider_key: 'openai',
|
|
81
|
+
api_key: ''
|
|
82
|
+
} as CreateProviderAPIKeyRequest
|
|
83
|
+
|
|
84
|
+
await expect(service.createProviderAPIKey(request)).rejects.toThrow('api_key is required')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should throw error when api_key is too short', async () => {
|
|
88
|
+
const request = {
|
|
89
|
+
provider_key: 'openai',
|
|
90
|
+
api_key: 'short'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await expect(service.createProviderAPIKey(request)).rejects.toThrow('api_key must be at least 10 characters long')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should handle provider not found error', async () => {
|
|
97
|
+
const request = {
|
|
98
|
+
provider_key: 'invalid_provider',
|
|
99
|
+
api_key: 'sk-1234567890abcdef1234567890abcdef12345678'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const errorResponse = {
|
|
103
|
+
detail: "Provider 'invalid_provider' not found. Please choose a valid provider."
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
mock.onPost('/security-keys/provider-api-key', request).reply(400, errorResponse)
|
|
107
|
+
|
|
108
|
+
await expect(service.createProviderAPIKey(request)).rejects.toThrow("Provider 'invalid_provider' not found. Please choose a valid provider.")
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should handle duplicate key error', async () => {
|
|
112
|
+
const request = {
|
|
113
|
+
provider_key: 'openai',
|
|
114
|
+
api_key: 'sk-1234567890abcdef1234567890abcdef12345678'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const errorResponse = {
|
|
118
|
+
detail: "API key for provider 'openai' already exists. Use update endpoint to change it."
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
mock.onPost('/security-keys/provider-api-key', request).reply(400, errorResponse)
|
|
122
|
+
|
|
123
|
+
await expect(service.createProviderAPIKey(request)).rejects.toThrow("API key for provider 'openai' already exists. Use update endpoint to change it.")
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('getProviderAPIKey', () => {
|
|
128
|
+
it('should get a provider API key without revealing it', async () => {
|
|
129
|
+
const providerKey = 'openai'
|
|
130
|
+
const expectedResponse = {
|
|
131
|
+
status: 'success',
|
|
132
|
+
data: {
|
|
133
|
+
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
134
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
135
|
+
user_id: 'user_abc123',
|
|
136
|
+
name: 'provider_api_key',
|
|
137
|
+
displayed_name: 'OpenAI API Key',
|
|
138
|
+
provider_id: 'openai',
|
|
139
|
+
provider_name: 'OpenAI',
|
|
140
|
+
provider_key: 'openai',
|
|
141
|
+
value_masked: '***************************************5678',
|
|
142
|
+
object_value: {
|
|
143
|
+
provider_id: 'openai'
|
|
144
|
+
},
|
|
145
|
+
encryption: 'fernet',
|
|
146
|
+
key_version: 'v1'
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
mock.onGet(`/security-keys/provider-api-key/${providerKey}`).reply(200, expectedResponse)
|
|
151
|
+
|
|
152
|
+
const result = await service.getProviderAPIKey(providerKey)
|
|
153
|
+
|
|
154
|
+
expect(result).toEqual(expectedResponse)
|
|
155
|
+
expect(mock.history.get[0].url).toBe('/security-keys/provider-api-key/openai')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should get a provider API key with reveal=true', async () => {
|
|
159
|
+
const providerKey = 'openai'
|
|
160
|
+
const expectedResponse = {
|
|
161
|
+
status: 'success',
|
|
162
|
+
data: {
|
|
163
|
+
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
164
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
165
|
+
user_id: 'user_abc123',
|
|
166
|
+
name: 'provider_api_key',
|
|
167
|
+
displayed_name: 'OpenAI API Key',
|
|
168
|
+
provider_id: 'openai',
|
|
169
|
+
provider_name: 'OpenAI',
|
|
170
|
+
provider_key: 'openai',
|
|
171
|
+
value_masked: '***************************************5678',
|
|
172
|
+
value: 'sk-1234567890abcdef1234567890abcdef12345678',
|
|
173
|
+
object_value: {
|
|
174
|
+
provider_id: 'openai'
|
|
175
|
+
},
|
|
176
|
+
encryption: 'fernet',
|
|
177
|
+
key_version: 'v1'
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
mock.onGet(`/security-keys/provider-api-key/${providerKey}?reveal=true`).reply(200, expectedResponse)
|
|
182
|
+
|
|
183
|
+
const result = await service.getProviderAPIKey(providerKey, true)
|
|
184
|
+
|
|
185
|
+
expect(result).toEqual(expectedResponse)
|
|
186
|
+
expect(mock.history.get[0].url).toBe('/security-keys/provider-api-key/openai?reveal=true')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('should handle key not found error', async () => {
|
|
190
|
+
const providerKey = 'anthropic'
|
|
191
|
+
const errorResponse = {
|
|
192
|
+
detail: "No API key found for provider 'anthropic'"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
mock.onGet(`/security-keys/provider-api-key/${providerKey}`).reply(404, errorResponse)
|
|
196
|
+
|
|
197
|
+
await expect(service.getProviderAPIKey(providerKey)).rejects.toThrow("No API key found for provider 'anthropic'")
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should throw error when provider_key is missing', async () => {
|
|
201
|
+
await expect(service.getProviderAPIKey('')).rejects.toThrow('provider_key is required')
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
describe('listProviderAPIKeys', () => {
|
|
206
|
+
it('should list all provider API keys', async () => {
|
|
207
|
+
const expectedResponse = {
|
|
208
|
+
status: 'success',
|
|
209
|
+
data: [
|
|
210
|
+
{
|
|
211
|
+
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
212
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
213
|
+
user_id: 'user_abc123',
|
|
214
|
+
name: 'provider_api_key',
|
|
215
|
+
displayed_name: 'OpenAI API Key',
|
|
216
|
+
provider_id: 'openai',
|
|
217
|
+
provider_name: 'OpenAI',
|
|
218
|
+
provider_key: 'openai',
|
|
219
|
+
provider_logo_src: 'https://cdn.openai.com/API/logo.png',
|
|
220
|
+
value_masked: '***************************************5678',
|
|
221
|
+
object_value: {
|
|
222
|
+
provider_id: 'openai'
|
|
223
|
+
},
|
|
224
|
+
encryption: 'fernet',
|
|
225
|
+
key_version: 'v1'
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: '456e7890-e89b-12d3-a456-426614174001',
|
|
229
|
+
created_at: '2024-01-15T11:15:00.000Z',
|
|
230
|
+
user_id: 'user_abc123',
|
|
231
|
+
name: 'provider_api_key',
|
|
232
|
+
displayed_name: 'Anthropic API Key',
|
|
233
|
+
provider_id: 'anthropic',
|
|
234
|
+
provider_name: 'Anthropic',
|
|
235
|
+
provider_key: 'anthropic',
|
|
236
|
+
provider_logo_src: 'https://anthropic.com/images/logo.png',
|
|
237
|
+
value_masked: '***************************************9012',
|
|
238
|
+
object_value: {
|
|
239
|
+
provider_id: 'anthropic'
|
|
240
|
+
},
|
|
241
|
+
encryption: 'fernet',
|
|
242
|
+
key_version: 'v1'
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
mock.onGet('/security-keys/provider-api-keys').reply(200, expectedResponse)
|
|
248
|
+
|
|
249
|
+
const result = await service.listProviderAPIKeys()
|
|
250
|
+
|
|
251
|
+
expect(result).toEqual(expectedResponse)
|
|
252
|
+
expect(mock.history.get[0].url).toBe('/security-keys/provider-api-keys')
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('should handle empty list response', async () => {
|
|
256
|
+
const expectedResponse = {
|
|
257
|
+
status: 'success',
|
|
258
|
+
data: []
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
mock.onGet('/security-keys/provider-api-keys').reply(200, expectedResponse)
|
|
262
|
+
|
|
263
|
+
const result = await service.listProviderAPIKeys()
|
|
264
|
+
|
|
265
|
+
expect(result).toEqual(expectedResponse)
|
|
266
|
+
expect(result.data).toEqual([])
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('should list all provider API keys with reveal=true', async () => {
|
|
270
|
+
const expectedResponse = {
|
|
271
|
+
status: 'success',
|
|
272
|
+
data: [
|
|
273
|
+
{
|
|
274
|
+
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
275
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
276
|
+
user_id: 'user_123',
|
|
277
|
+
name: 'openai',
|
|
278
|
+
displayed_name: 'OpenAI API Key',
|
|
279
|
+
provider_id: 'provider_123',
|
|
280
|
+
provider_key: 'openai',
|
|
281
|
+
provider_name: 'OpenAI',
|
|
282
|
+
provider_logo_src: 'https://example.com/openai.png',
|
|
283
|
+
object_value: { "provider_id": "provider_123" },
|
|
284
|
+
encryption: 'fernet',
|
|
285
|
+
key_version: 'v1',
|
|
286
|
+
value_masked: 'sk-****CwVP',
|
|
287
|
+
value: 'sk-proj-AbCdEfGhIjKlMnOpQrStUvWxYz1234567890CwVP'
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: null,
|
|
291
|
+
created_at: null,
|
|
292
|
+
user_id: 'user_123',
|
|
293
|
+
name: null,
|
|
294
|
+
displayed_name: null,
|
|
295
|
+
provider_id: 'provider_456',
|
|
296
|
+
provider_key: 'anthropic',
|
|
297
|
+
provider_name: 'Anthropic',
|
|
298
|
+
provider_logo_src: 'https://example.com/anthropic.png',
|
|
299
|
+
object_value: null,
|
|
300
|
+
encryption: null,
|
|
301
|
+
key_version: null,
|
|
302
|
+
value_masked: null,
|
|
303
|
+
value: null
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
mock.onGet('/security-keys/provider-api-keys?reveal=true').reply(200, expectedResponse)
|
|
309
|
+
|
|
310
|
+
const result = await service.listProviderAPIKeys(true)
|
|
311
|
+
|
|
312
|
+
expect(result).toEqual(expectedResponse)
|
|
313
|
+
expect(mock.history.get[0].url).toBe('/security-keys/provider-api-keys?reveal=true')
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
describe('revokeProviderAPIKey', () => {
|
|
317
|
+
it('should revoke a provider API key successfully', async () => {
|
|
318
|
+
const providerKey = 'openai'
|
|
319
|
+
const expectedResponse = {
|
|
320
|
+
status: 'success',
|
|
321
|
+
message: 'Provider API key deleted successfully',
|
|
322
|
+
revoked_provider_id: 'openai'
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
mock.onDelete(`/security-keys/provider-api-key/${providerKey}`).reply(200, expectedResponse)
|
|
326
|
+
|
|
327
|
+
const result = await service.revokeProviderAPIKey(providerKey)
|
|
328
|
+
|
|
329
|
+
expect(result).toEqual(expectedResponse)
|
|
330
|
+
expect(mock.history.delete[0].url).toBe('/security-keys/provider-api-key/openai')
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('should throw error when provider_key is missing', async () => {
|
|
334
|
+
await expect(service.revokeProviderAPIKey('')).rejects.toThrow('provider_key is required')
|
|
335
|
+
await expect(service.revokeProviderAPIKey(null as any)).rejects.toThrow('provider_key is required')
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it('should handle provider API key not found error', async () => {
|
|
339
|
+
const providerKey = 'nonexistent'
|
|
340
|
+
const errorResponse = {
|
|
341
|
+
detail: "No API key found for provider 'nonexistent'"
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
mock.onDelete(`/security-keys/provider-api-key/${providerKey}`).reply(404, errorResponse)
|
|
345
|
+
|
|
346
|
+
await expect(service.revokeProviderAPIKey(providerKey)).rejects.toThrow("No API key found for provider 'nonexistent'")
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('should handle provider API key deletion error', async () => {
|
|
350
|
+
const providerKey = 'openai'
|
|
351
|
+
const errorResponse = {
|
|
352
|
+
detail: 'Failed to delete provider API key'
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
mock.onDelete(`/security-keys/provider-api-key/${providerKey}`).reply(500, errorResponse)
|
|
356
|
+
|
|
357
|
+
await expect(service.revokeProviderAPIKey(providerKey)).rejects.toThrow('Failed to delete provider API key')
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
// ============================================================================
|
|
364
|
+
// GitHub Token Tests
|
|
365
|
+
// ============================================================================
|
|
366
|
+
|
|
367
|
+
describe('GitHub Token Methods', () => {
|
|
368
|
+
describe('createGitHubToken', () => {
|
|
369
|
+
it('should create a GitHub token successfully', async () => {
|
|
370
|
+
const request = {
|
|
371
|
+
github_token: 'ghp_1234567890abcdef1234567890abcdef12345678'
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const expectedResponse = {
|
|
375
|
+
status: 'success',
|
|
376
|
+
data: {
|
|
377
|
+
id: '789e0123-e89b-12d3-a456-426614174003',
|
|
378
|
+
created_at: '2024-01-15T12:30:00.000Z',
|
|
379
|
+
user_id: 'user_abc123',
|
|
380
|
+
name: 'github_token',
|
|
381
|
+
displayed_name: 'GitHub Token',
|
|
382
|
+
value_masked: '***************************************90ab',
|
|
383
|
+
object_value: {
|
|
384
|
+
token_type: 'personal_access_token'
|
|
385
|
+
},
|
|
386
|
+
encryption: 'fernet',
|
|
387
|
+
key_version: 'v1'
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
mock.onPost('/security-keys/github-token', request).reply(201, expectedResponse)
|
|
392
|
+
|
|
393
|
+
const result = await service.createGitHubToken(request)
|
|
394
|
+
|
|
395
|
+
expect(result).toEqual(expectedResponse)
|
|
396
|
+
expect(mock.history.post[0].url).toBe('/security-keys/github-token')
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('should throw error when github_token is missing', async () => {
|
|
400
|
+
const request = {} as CreateGitHubTokenRequest
|
|
401
|
+
|
|
402
|
+
await expect(service.createGitHubToken(request)).rejects.toThrow('github_token is required')
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it('should throw error when github_token has invalid format', async () => {
|
|
406
|
+
const request = {
|
|
407
|
+
github_token: 'invalid_token'
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
await expect(service.createGitHubToken(request)).rejects.toThrow('Invalid GitHub token format. Expected format: ghp_*, gho_*, ghu_*, ghs_*, or ghr_* followed by 36 characters')
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
it('should throw error when github_token is too short', async () => {
|
|
414
|
+
const request = {
|
|
415
|
+
github_token: 'ghp_short'
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
await expect(service.createGitHubToken(request)).rejects.toThrow('GitHub token must be at least 40 characters long')
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('should handle GitHub token validation error', async () => {
|
|
422
|
+
const request = {
|
|
423
|
+
github_token: 'ghp_invalid1234567890abcdef1234567890'
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const errorResponse = {
|
|
427
|
+
detail: 'GitHub token validation failed. Please check that the token is valid and has the necessary permissions.'
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
mock.onPost('/security-keys/github-token', request).reply(400, errorResponse)
|
|
431
|
+
|
|
432
|
+
await expect(service.createGitHubToken(request)).rejects.toThrow('GitHub token validation failed. Please check that the token is valid and has the necessary permissions.')
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('should handle duplicate GitHub token error', async () => {
|
|
436
|
+
const request = {
|
|
437
|
+
github_token: 'ghp_1234567890abcdef1234567890abcdef12345678'
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const errorResponse = {
|
|
441
|
+
detail: 'GitHub token already exists. Use update endpoint to change it.'
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
mock.onPost('/security-keys/github-token', request).reply(400, errorResponse)
|
|
445
|
+
|
|
446
|
+
await expect(service.createGitHubToken(request)).rejects.toThrow('GitHub token already exists. Use update endpoint to change it.')
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
describe('getGitHubToken', () => {
|
|
451
|
+
it('should get GitHub token without revealing it', async () => {
|
|
452
|
+
const expectedResponse = {
|
|
453
|
+
status: 'success',
|
|
454
|
+
data: {
|
|
455
|
+
id: '789e0123-e89b-12d3-a456-426614174003',
|
|
456
|
+
created_at: '2024-01-15T12:30:00.000Z',
|
|
457
|
+
user_id: 'user_abc123',
|
|
458
|
+
name: 'github_token',
|
|
459
|
+
displayed_name: 'GitHub Token',
|
|
460
|
+
value_masked: '***************************************90ab',
|
|
461
|
+
object_value: {
|
|
462
|
+
token_type: 'personal_access_token'
|
|
463
|
+
},
|
|
464
|
+
encryption: 'fernet',
|
|
465
|
+
key_version: 'v1'
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
mock.onGet('/security-keys/github-token').reply(200, expectedResponse)
|
|
470
|
+
|
|
471
|
+
const result = await service.getGitHubToken()
|
|
472
|
+
|
|
473
|
+
expect(result).toEqual(expectedResponse)
|
|
474
|
+
expect(mock.history.get[0].url).toBe('/security-keys/github-token')
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
it('should get GitHub token with reveal=true', async () => {
|
|
478
|
+
const expectedResponse = {
|
|
479
|
+
status: 'success',
|
|
480
|
+
data: {
|
|
481
|
+
id: '789e0123-e89b-12d3-a456-426614174003',
|
|
482
|
+
created_at: '2024-01-15T12:30:00.000Z',
|
|
483
|
+
user_id: 'user_abc123',
|
|
484
|
+
name: 'github_token',
|
|
485
|
+
displayed_name: 'GitHub Token',
|
|
486
|
+
value_masked: '***************************************90ab',
|
|
487
|
+
value: 'ghp_1234567890abcdef1234567890abcdef12345678',
|
|
488
|
+
object_value: {
|
|
489
|
+
token_type: 'personal_access_token'
|
|
490
|
+
},
|
|
491
|
+
encryption: 'fernet',
|
|
492
|
+
key_version: 'v1'
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
mock.onGet('/security-keys/github-token?reveal=true').reply(200, expectedResponse)
|
|
497
|
+
|
|
498
|
+
const result = await service.getGitHubToken(true)
|
|
499
|
+
|
|
500
|
+
expect(result).toEqual(expectedResponse)
|
|
501
|
+
expect(mock.history.get[0].url).toBe('/security-keys/github-token?reveal=true')
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
it('should handle GitHub token not found error', async () => {
|
|
505
|
+
const errorResponse = {
|
|
506
|
+
detail: 'No GitHub token found'
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
mock.onGet('/security-keys/github-token').reply(404, errorResponse)
|
|
510
|
+
|
|
511
|
+
await expect(service.getGitHubToken()).rejects.toThrow('No GitHub token found')
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
describe('revokeGitHubToken', () => {
|
|
515
|
+
it('should revoke GitHub token successfully', async () => {
|
|
516
|
+
const expectedResponse = {
|
|
517
|
+
status: 'success',
|
|
518
|
+
message: 'GitHub token deleted successfully',
|
|
519
|
+
revoked_at: '2024-01-15T14:30:00.000Z'
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
mock.onDelete('/security-keys/github-token').reply(200, expectedResponse)
|
|
523
|
+
|
|
524
|
+
const result = await service.revokeGitHubToken()
|
|
525
|
+
|
|
526
|
+
expect(result).toEqual(expectedResponse)
|
|
527
|
+
expect(mock.history.delete[0].url).toBe('/security-keys/github-token')
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
it('should handle GitHub token not found error', async () => {
|
|
531
|
+
const errorResponse = {
|
|
532
|
+
detail: 'No GitHub token found'
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
mock.onDelete('/security-keys/github-token').reply(404, errorResponse)
|
|
536
|
+
|
|
537
|
+
await expect(service.revokeGitHubToken()).rejects.toThrow('No GitHub token found')
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
it('should handle GitHub token deletion error', async () => {
|
|
541
|
+
const errorResponse = {
|
|
542
|
+
detail: 'Failed to delete GitHub token'
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
mock.onDelete('/security-keys/github-token').reply(500, errorResponse)
|
|
546
|
+
|
|
547
|
+
await expect(service.revokeGitHubToken()).rejects.toThrow('Failed to delete GitHub token')
|
|
548
|
+
})
|
|
549
|
+
})
|
|
550
|
+
})
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
// ============================================================================
|
|
554
|
+
// Validation Tests
|
|
555
|
+
// ============================================================================
|
|
556
|
+
|
|
557
|
+
describe('Validation Tests', () => {
|
|
558
|
+
describe('GitHub Token Validation', () => {
|
|
559
|
+
it('should accept valid GitHub token prefixes', () => {
|
|
560
|
+
const validTokens = [
|
|
561
|
+
'ghp_1234567890abcdef1234567890abcdef12345678',
|
|
562
|
+
'gho_1234567890abcdef1234567890abcdef12345678',
|
|
563
|
+
'ghu_1234567890abcdef1234567890abcdef12345678',
|
|
564
|
+
'ghs_1234567890abcdef1234567890abcdef12345678',
|
|
565
|
+
'ghr_1234567890abcdef1234567890abcdef12345678',
|
|
566
|
+
'github_pat_1234567890abcdef1234567890abcdef12345678'
|
|
567
|
+
]
|
|
568
|
+
|
|
569
|
+
validTokens.forEach(token => {
|
|
570
|
+
expect(() => service['validateGitHubTokenRequest']({ github_token: token })).not.toThrow()
|
|
571
|
+
})
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
it('should reject invalid GitHub token prefixes', () => {
|
|
575
|
+
const invalidTokens = [
|
|
576
|
+
'invalid_1234567890abcdef1234567890abcdef12345678',
|
|
577
|
+
'abc_1234567890abcdef1234567890abcdef12345678',
|
|
578
|
+
'1234567890abcdef1234567890abcdef12345678'
|
|
579
|
+
]
|
|
580
|
+
|
|
581
|
+
invalidTokens.forEach(token => {
|
|
582
|
+
expect(() => service['validateGitHubTokenRequest']({ github_token: token })).toThrow('Invalid GitHub token format')
|
|
583
|
+
})
|
|
584
|
+
})
|
|
585
|
+
})
|
|
586
|
+
})
|
|
587
|
+
})
|
package/codeguide.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
CancellationFunnelService,
|
|
18
18
|
CodespaceService,
|
|
19
19
|
ExternalTokenService,
|
|
20
|
+
SecurityKeysService,
|
|
20
21
|
} from './services'
|
|
21
22
|
import { APIServiceConfig, CodeGuideOptions } from './types'
|
|
22
23
|
|
|
@@ -31,6 +32,7 @@ export class CodeGuide {
|
|
|
31
32
|
public cancellationFunnel: CancellationFunnelService
|
|
32
33
|
public codespace: CodespaceService
|
|
33
34
|
public externalTokens: ExternalTokenService
|
|
35
|
+
public securityKeys: SecurityKeysService
|
|
34
36
|
private options: CodeGuideOptions
|
|
35
37
|
|
|
36
38
|
constructor(config: APIServiceConfig, options: CodeGuideOptions = {}) {
|
|
@@ -47,6 +49,7 @@ export class CodeGuide {
|
|
|
47
49
|
this.cancellationFunnel = new CancellationFunnelService(config)
|
|
48
50
|
this.codespace = new CodespaceService(config)
|
|
49
51
|
this.externalTokens = new ExternalTokenService(config)
|
|
52
|
+
this.securityKeys = new SecurityKeysService(config)
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
// Convenience method for backward compatibility
|
|
@@ -90,7 +93,6 @@ export class CodeGuide {
|
|
|
90
93
|
return this.tasks.createTaskGroupWithCodespace(request, this.codespace)
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
|
|
94
96
|
setOptions(options: Partial<CodeGuideOptions>): void {
|
|
95
97
|
this.options = { ...this.options, ...options }
|
|
96
98
|
}
|
package/dist/codeguide.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GenerationService, ProjectService, UsageService, RepositoryAnalysisService, TaskService, ApiKeyEnhancedService, SubscriptionService, CancellationFunnelService, CodespaceService, ExternalTokenService } from './services';
|
|
1
|
+
import { GenerationService, ProjectService, UsageService, RepositoryAnalysisService, TaskService, ApiKeyEnhancedService, SubscriptionService, CancellationFunnelService, CodespaceService, ExternalTokenService, SecurityKeysService } from './services';
|
|
2
2
|
import { APIServiceConfig, CodeGuideOptions } from './types';
|
|
3
3
|
export declare class CodeGuide {
|
|
4
4
|
generation: GenerationService;
|
|
@@ -11,6 +11,7 @@ export declare class CodeGuide {
|
|
|
11
11
|
cancellationFunnel: CancellationFunnelService;
|
|
12
12
|
codespace: CodespaceService;
|
|
13
13
|
externalTokens: ExternalTokenService;
|
|
14
|
+
securityKeys: SecurityKeysService;
|
|
14
15
|
private options;
|
|
15
16
|
constructor(config: APIServiceConfig, options?: CodeGuideOptions);
|
|
16
17
|
getGuidance(prompt: string): Promise<any>;
|
package/dist/codeguide.js
CHANGED
|
@@ -26,6 +26,7 @@ class CodeGuide {
|
|
|
26
26
|
this.cancellationFunnel = new services_1.CancellationFunnelService(config);
|
|
27
27
|
this.codespace = new services_1.CodespaceService(config);
|
|
28
28
|
this.externalTokens = new services_1.ExternalTokenService(config);
|
|
29
|
+
this.securityKeys = new services_1.SecurityKeysService(config);
|
|
29
30
|
}
|
|
30
31
|
// Convenience method for backward compatibility
|
|
31
32
|
async getGuidance(prompt) {
|