@forgehive/hive-sdk 0.0.1 → 0.0.2

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,467 @@
1
+ import axios from 'axios'
2
+ import { HiveLogClient, createHiveLogClient, Metadata } from '../index'
3
+
4
+ // Mock axios
5
+ jest.mock('axios')
6
+ const mockedAxios = axios as jest.Mocked<typeof axios>
7
+
8
+ describe('HiveLogClient Metadata', () => {
9
+ const testConfig = {
10
+ projectName: 'test-project',
11
+ apiKey: 'test-api-key',
12
+ apiSecret: 'test-api-secret',
13
+ host: 'https://test-host.com'
14
+ }
15
+
16
+ beforeEach(() => {
17
+ // Clear all mocks
18
+ jest.clearAllMocks()
19
+ })
20
+
21
+ describe('Constructor with base metadata', () => {
22
+ it('should create client without base metadata', () => {
23
+ const client = new HiveLogClient(testConfig)
24
+ expect(client).toBeInstanceOf(HiveLogClient)
25
+ expect(client.isActive()).toBe(true)
26
+ })
27
+
28
+ it('should create client with base metadata', () => {
29
+ const baseMetadata: Metadata = {
30
+ environment: 'production',
31
+ version: '1.0.0',
32
+ service: 'test-service'
33
+ }
34
+
35
+ const client = new HiveLogClient({ ...testConfig, metadata: baseMetadata })
36
+ expect(client).toBeInstanceOf(HiveLogClient)
37
+ expect(client.isActive()).toBe(true)
38
+ })
39
+
40
+ it('should handle empty base metadata object', () => {
41
+ const client = new HiveLogClient({ ...testConfig, metadata: {} })
42
+ expect(client).toBeInstanceOf(HiveLogClient)
43
+ expect(client.isActive()).toBe(true)
44
+ })
45
+
46
+ it('should handle undefined base metadata', () => {
47
+ const client = new HiveLogClient({ ...testConfig, metadata: undefined })
48
+ expect(client).toBeInstanceOf(HiveLogClient)
49
+ expect(client.isActive()).toBe(true)
50
+ })
51
+ })
52
+
53
+ describe('createHiveLogClient factory with metadata', () => {
54
+ it('should create client without base metadata', () => {
55
+ const client = createHiveLogClient(testConfig)
56
+ expect(client).toBeInstanceOf(HiveLogClient)
57
+ })
58
+
59
+ it('should create client with base metadata', () => {
60
+ const baseMetadata: Metadata = {
61
+ environment: 'development',
62
+ team: 'backend'
63
+ }
64
+
65
+ const client = createHiveLogClient({ ...testConfig, metadata: baseMetadata })
66
+ expect(client).toBeInstanceOf(HiveLogClient)
67
+ })
68
+ })
69
+
70
+ describe('sendLog with metadata parameter', () => {
71
+ let client: HiveLogClient
72
+
73
+ beforeEach(() => {
74
+ client = new HiveLogClient(testConfig)
75
+ })
76
+
77
+ it('should send log without metadata parameter', async () => {
78
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
79
+
80
+ const logItem = { input: 'test-input', output: 'test-output' }
81
+ const result = await client.sendLog('test-task', logItem)
82
+
83
+ expect(result).toBe('success')
84
+ expect(mockedAxios.post).toHaveBeenCalledWith(
85
+ 'https://test-host.com/api/tasks/log-ingest',
86
+ {
87
+ projectName: 'test-project',
88
+ taskName: 'test-task',
89
+ logItem: JSON.stringify({
90
+ input: 'test-input',
91
+ output: 'test-output',
92
+ metadata: {}
93
+ })
94
+ },
95
+ {
96
+ headers: {
97
+ Authorization: 'Bearer test-api-key:test-api-secret',
98
+ 'Content-Type': 'application/json'
99
+ }
100
+ }
101
+ )
102
+ })
103
+
104
+ it('should send log with metadata parameter', async () => {
105
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
106
+
107
+ const logItem = { input: 'test-input', output: 'test-output' }
108
+ const metadata: Metadata = {
109
+ requestId: 'req-123',
110
+ userId: 'user-456'
111
+ }
112
+
113
+ const result = await client.sendLog('test-task', logItem, metadata)
114
+
115
+ expect(result).toBe('success')
116
+ expect(mockedAxios.post).toHaveBeenCalledWith(
117
+ 'https://test-host.com/api/tasks/log-ingest',
118
+ {
119
+ projectName: 'test-project',
120
+ taskName: 'test-task',
121
+ logItem: JSON.stringify({
122
+ input: 'test-input',
123
+ output: 'test-output',
124
+ metadata: {
125
+ requestId: 'req-123',
126
+ userId: 'user-456'
127
+ }
128
+ })
129
+ },
130
+ {
131
+ headers: {
132
+ Authorization: 'Bearer test-api-key:test-api-secret',
133
+ 'Content-Type': 'application/json'
134
+ }
135
+ }
136
+ )
137
+ })
138
+
139
+ it('should handle logItem with only input property', async () => {
140
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
141
+
142
+ const metadata: Metadata = { type: 'minimal' }
143
+ const result = await client.sendLog('test-task', { input: 'simple input' }, metadata)
144
+
145
+ expect(result).toBe('success')
146
+ expect(mockedAxios.post).toHaveBeenCalledWith(
147
+ 'https://test-host.com/api/tasks/log-ingest',
148
+ {
149
+ projectName: 'test-project',
150
+ taskName: 'test-task',
151
+ logItem: JSON.stringify({
152
+ input: 'simple input',
153
+ metadata: { type: 'minimal' }
154
+ })
155
+ },
156
+ expect.any(Object)
157
+ )
158
+ })
159
+
160
+ it('should handle logItem with null input values', async () => {
161
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
162
+
163
+ const metadata: Metadata = { type: 'null-test' }
164
+ const result = await client.sendLog('test-task', { input: null }, metadata)
165
+
166
+ expect(result).toBe('success')
167
+ expect(mockedAxios.post).toHaveBeenCalledWith(
168
+ 'https://test-host.com/api/tasks/log-ingest',
169
+ {
170
+ projectName: 'test-project',
171
+ taskName: 'test-task',
172
+ logItem: JSON.stringify({
173
+ input: null,
174
+ metadata: { type: 'null-test' }
175
+ })
176
+ },
177
+ expect.any(Object)
178
+ )
179
+ })
180
+ })
181
+
182
+ describe('Metadata priority system', () => {
183
+ it('should use only base metadata when no other metadata provided', async () => {
184
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
185
+
186
+ const baseMetadata: Metadata = {
187
+ environment: 'production',
188
+ version: '1.0.0'
189
+ }
190
+
191
+ const client = new HiveLogClient({ ...testConfig, metadata: baseMetadata })
192
+ const logItem = { input: 'test' }
193
+
194
+ await client.sendLog('test-task', logItem)
195
+
196
+ const expectedLogItem = {
197
+ input: 'test',
198
+ metadata: {
199
+ environment: 'production',
200
+ version: '1.0.0'
201
+ }
202
+ }
203
+
204
+ expect(mockedAxios.post).toHaveBeenCalledWith(
205
+ 'https://test-host.com/api/tasks/log-ingest',
206
+ {
207
+ projectName: 'test-project',
208
+ taskName: 'test-task',
209
+ logItem: JSON.stringify(expectedLogItem)
210
+ },
211
+ expect.any(Object)
212
+ )
213
+ })
214
+
215
+ it('should merge logItem metadata with base metadata', async () => {
216
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
217
+
218
+ const baseMetadata: Metadata = {
219
+ environment: 'production',
220
+ version: '1.0.0'
221
+ }
222
+
223
+ const client = new HiveLogClient({ ...testConfig, metadata: baseMetadata })
224
+ const logItem = {
225
+ input: 'test',
226
+ metadata: {
227
+ sessionId: 'session-123',
228
+ version: '1.1.0' // This should override base version
229
+ }
230
+ }
231
+
232
+ await client.sendLog('test-task', logItem)
233
+
234
+ const expectedLogItem = {
235
+ input: 'test',
236
+ metadata: {
237
+ environment: 'production', // from base
238
+ version: '1.1.0', // from logItem (overrides base)
239
+ sessionId: 'session-123' // from logItem
240
+ }
241
+ }
242
+
243
+ expect(mockedAxios.post).toHaveBeenCalledWith(
244
+ 'https://test-host.com/api/tasks/log-ingest',
245
+ {
246
+ projectName: 'test-project',
247
+ taskName: 'test-task',
248
+ logItem: JSON.stringify(expectedLogItem)
249
+ },
250
+ expect.any(Object)
251
+ )
252
+ })
253
+
254
+ it('should give sendLog metadata highest priority', async () => {
255
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
256
+
257
+ const baseMetadata: Metadata = {
258
+ environment: 'production',
259
+ version: '1.0.0',
260
+ priority: 'base'
261
+ }
262
+
263
+ const client = new HiveLogClient({ ...testConfig, metadata: baseMetadata })
264
+ const logItem = {
265
+ input: 'test',
266
+ metadata: {
267
+ sessionId: 'session-123',
268
+ version: '1.1.0',
269
+ priority: 'logItem'
270
+ }
271
+ }
272
+
273
+ const sendLogMetadata: Metadata = {
274
+ requestId: 'req-456',
275
+ version: '1.2.0',
276
+ priority: 'sendLog'
277
+ }
278
+
279
+ await client.sendLog('test-task', logItem, sendLogMetadata)
280
+
281
+ const expectedLogItem = {
282
+ input: 'test',
283
+ metadata: {
284
+ environment: 'production', // from base
285
+ version: '1.2.0', // from sendLog (highest priority)
286
+ priority: 'sendLog', // from sendLog (highest priority)
287
+ sessionId: 'session-123', // from logItem
288
+ requestId: 'req-456' // from sendLog
289
+ }
290
+ }
291
+
292
+ expect(mockedAxios.post).toHaveBeenCalledWith(
293
+ 'https://test-host.com/api/tasks/log-ingest',
294
+ {
295
+ projectName: 'test-project',
296
+ taskName: 'test-task',
297
+ logItem: JSON.stringify(expectedLogItem)
298
+ },
299
+ expect.any(Object)
300
+ )
301
+ })
302
+
303
+ it('should handle all three metadata sources with complex merging', async () => {
304
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
305
+
306
+ const baseMetadata: Metadata = {
307
+ environment: 'production',
308
+ service: 'api-gateway',
309
+ version: '1.0.0',
310
+ datacenter: 'us-west-2'
311
+ }
312
+
313
+ const client = new HiveLogClient({ ...testConfig, metadata: baseMetadata })
314
+ const logItem = {
315
+ input: { query: 'search' },
316
+ output: { results: [] },
317
+ metadata: {
318
+ algorithm: 'fuzzy-search',
319
+ processingTime: '250',
320
+ version: '1.1.0' // overrides base
321
+ }
322
+ }
323
+
324
+ const sendLogMetadata: Metadata = {
325
+ requestId: 'req-789',
326
+ userId: 'user-123',
327
+ version: '1.2.0' // overrides both base and logItem
328
+ }
329
+
330
+ await client.sendLog('search-task', logItem, sendLogMetadata)
331
+
332
+ const expectedLogItem = {
333
+ input: { query: 'search' },
334
+ output: { results: [] },
335
+ metadata: {
336
+ environment: 'production', // from base
337
+ service: 'api-gateway', // from base
338
+ version: '1.2.0', // from sendLog (highest priority)
339
+ datacenter: 'us-west-2', // from base
340
+ algorithm: 'fuzzy-search', // from logItem
341
+ processingTime: '250', // from logItem
342
+ requestId: 'req-789', // from sendLog
343
+ userId: 'user-123' // from sendLog
344
+ }
345
+ }
346
+
347
+ expect(mockedAxios.post).toHaveBeenCalledWith(
348
+ 'https://test-host.com/api/tasks/log-ingest',
349
+ {
350
+ projectName: 'test-project',
351
+ taskName: 'search-task',
352
+ logItem: JSON.stringify(expectedLogItem)
353
+ },
354
+ expect.any(Object)
355
+ )
356
+ })
357
+ })
358
+
359
+ describe('Edge cases and error handling', () => {
360
+ it('should handle logItem without metadata property', async () => {
361
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
362
+
363
+ const baseMetadata: Metadata = { environment: 'test' }
364
+ const client = new HiveLogClient({ ...testConfig, metadata: baseMetadata })
365
+
366
+ const logItem = {
367
+ input: 'test'
368
+ // No metadata property
369
+ }
370
+
371
+ await client.sendLog('test-task', logItem)
372
+
373
+ const expectedLogItem = {
374
+ input: 'test',
375
+ metadata: {
376
+ environment: 'test' // Only base metadata should be used
377
+ }
378
+ }
379
+
380
+ expect(mockedAxios.post).toHaveBeenCalledWith(
381
+ 'https://test-host.com/api/tasks/log-ingest',
382
+ {
383
+ projectName: 'test-project',
384
+ taskName: 'test-task',
385
+ logItem: JSON.stringify(expectedLogItem)
386
+ },
387
+ expect.any(Object)
388
+ )
389
+ })
390
+
391
+ it('should handle logItem with undefined metadata', async () => {
392
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
393
+
394
+ const baseMetadata: Metadata = { environment: 'test' }
395
+ const client = new HiveLogClient({ ...testConfig, metadata: baseMetadata })
396
+
397
+ const logItem = {
398
+ input: 'test',
399
+ metadata: undefined
400
+ }
401
+
402
+ await client.sendLog('test-task', logItem)
403
+
404
+ const expectedLogItem = {
405
+ input: 'test',
406
+ metadata: {
407
+ environment: 'test' // Only base metadata should be used
408
+ }
409
+ }
410
+
411
+ expect(mockedAxios.post).toHaveBeenCalledWith(
412
+ 'https://test-host.com/api/tasks/log-ingest',
413
+ {
414
+ projectName: 'test-project',
415
+ taskName: 'test-task',
416
+ logItem: JSON.stringify(expectedLogItem)
417
+ },
418
+ expect.any(Object)
419
+ )
420
+ })
421
+
422
+ it('should handle empty metadata objects', async () => {
423
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
424
+
425
+ const client = new HiveLogClient({ ...testConfig, metadata: {} })
426
+ const logItem = { input: 'test', metadata: {} }
427
+
428
+ await client.sendLog('test-task', logItem, {})
429
+
430
+ const expectedLogItem = {
431
+ input: 'test',
432
+ metadata: {} // All empty metadata objects result in empty final metadata
433
+ }
434
+
435
+ expect(mockedAxios.post).toHaveBeenCalledWith(
436
+ 'https://test-host.com/api/tasks/log-ingest',
437
+ {
438
+ projectName: 'test-project',
439
+ taskName: 'test-task',
440
+ logItem: JSON.stringify(expectedLogItem)
441
+ },
442
+ expect.any(Object)
443
+ )
444
+ })
445
+
446
+ it('should work in silent mode with metadata', async () => {
447
+ const baseMetadata: Metadata = { environment: 'test' }
448
+ const silentClient = new HiveLogClient({ projectName: 'silent-project', metadata: baseMetadata })
449
+
450
+ const result = await silentClient.sendLog('test-task', { input: 'test' }, { requestId: 'req-123' })
451
+
452
+ expect(result).toBe('silent')
453
+ expect(mockedAxios.post).not.toHaveBeenCalled()
454
+ })
455
+
456
+ it('should handle network errors with metadata', async () => {
457
+ mockedAxios.post.mockRejectedValueOnce(new Error('Network error'))
458
+
459
+ const baseMetadata: Metadata = { environment: 'test' }
460
+ const client = new HiveLogClient({ ...testConfig, metadata: baseMetadata })
461
+
462
+ const result = await client.sendLog('test-task', { input: 'test' }, { requestId: 'req-123' })
463
+
464
+ expect(result).toBe('error')
465
+ })
466
+ })
467
+ })
@@ -6,29 +6,23 @@ jest.mock('axios')
6
6
  const mockedAxios = axios as jest.Mocked<typeof axios>
7
7
 
8
8
  describe('HiveLogClient sendLog', () => {
9
- const originalEnv = process.env
10
9
  let client: HiveLogClient
11
10
 
12
- beforeEach(() => {
13
- jest.resetModules()
14
- process.env = { ...originalEnv }
15
-
16
- // Set up environment variables
17
- process.env.HIVE_API_KEY = 'test-api-key'
18
- process.env.HIVE_API_SECRET = 'test-api-secret'
19
- process.env.HIVE_HOST = 'https://test-host.com'
11
+ const testConfig = {
12
+ projectName: 'test-project',
13
+ apiKey: 'test-api-key',
14
+ apiSecret: 'test-api-secret',
15
+ host: 'https://test-host.com'
16
+ }
20
17
 
21
- // Create client instance
22
- client = new HiveLogClient('test-project')
18
+ beforeEach(() => {
19
+ // Create client instance with config
20
+ client = new HiveLogClient(testConfig)
23
21
 
24
22
  // Clear all mocks
25
23
  jest.clearAllMocks()
26
24
  })
27
25
 
28
- afterAll(() => {
29
- process.env = originalEnv
30
- })
31
-
32
26
  describe('successful sendLog', () => {
33
27
  it('should send log successfully and return true', async () => {
34
28
  // Mock successful axios response
@@ -44,7 +38,7 @@ describe('HiveLogClient sendLog', () => {
44
38
  {
45
39
  projectName: 'test-project',
46
40
  taskName: 'test-task',
47
- logItem: JSON.stringify(logItem)
41
+ logItem: JSON.stringify({ ...logItem, metadata: {} })
48
42
  },
49
43
  {
50
44
  headers: {
@@ -75,7 +69,7 @@ describe('HiveLogClient sendLog', () => {
75
69
  {
76
70
  projectName: 'test-project',
77
71
  taskName: 'complex-task',
78
- logItem: JSON.stringify(complexLogItem)
72
+ logItem: JSON.stringify({ ...complexLogItem, metadata: {} })
79
73
  },
80
74
  {
81
75
  headers: {
@@ -110,18 +104,18 @@ describe('HiveLogClient sendLog', () => {
110
104
  })
111
105
 
112
106
  describe('sendLog parameters', () => {
113
- it('should handle empty log items', async () => {
107
+ it('should handle log items with minimal input', async () => {
114
108
  mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
115
109
 
116
- const result = await client.sendLog('empty-task', {})
110
+ const result = await client.sendLog('minimal-task', { input: 'minimal input' })
117
111
 
118
112
  expect(result).toBe('success')
119
113
  expect(mockedAxios.post).toHaveBeenCalledWith(
120
114
  'https://test-host.com/api/tasks/log-ingest',
121
115
  {
122
116
  projectName: 'test-project',
123
- taskName: 'empty-task',
124
- logItem: JSON.stringify({})
117
+ taskName: 'minimal-task',
118
+ logItem: JSON.stringify({ input: 'minimal input', metadata: {} })
125
119
  },
126
120
  expect.any(Object)
127
121
  )
@@ -139,7 +133,7 @@ describe('HiveLogClient sendLog', () => {
139
133
  {
140
134
  projectName: 'test-project',
141
135
  taskName: 'null-task',
142
- logItem: JSON.stringify(logItem)
136
+ logItem: JSON.stringify({ ...logItem, metadata: {} })
143
137
  },
144
138
  expect.any(Object)
145
139
  )
@@ -6,29 +6,23 @@ jest.mock('axios')
6
6
  const mockedAxios = axios as jest.Mocked<typeof axios>
7
7
 
8
8
  describe('HiveLogClient setQuality', () => {
9
- const originalEnv = process.env
10
9
  let client: HiveLogClient
11
10
 
12
- beforeEach(() => {
13
- jest.resetModules()
14
- process.env = { ...originalEnv }
15
-
16
- // Set up environment variables
17
- process.env.HIVE_API_KEY = 'test-api-key'
18
- process.env.HIVE_API_SECRET = 'test-api-secret'
19
- process.env.HIVE_HOST = 'https://test-host.com'
11
+ const testConfig = {
12
+ projectName: 'test-project',
13
+ apiKey: 'test-api-key',
14
+ apiSecret: 'test-api-secret',
15
+ host: 'https://test-host.com'
16
+ }
20
17
 
21
- // Create client instance
22
- client = new HiveLogClient('test-project')
18
+ beforeEach(() => {
19
+ // Create client instance with config
20
+ client = new HiveLogClient(testConfig)
23
21
 
24
22
  // Clear all mocks
25
23
  jest.clearAllMocks()
26
24
  })
27
25
 
28
- afterAll(() => {
29
- process.env = originalEnv
30
- })
31
-
32
26
  describe('successful setQuality', () => {
33
27
  it('should set quality successfully and return true', async () => {
34
28
  // Mock successful axios response
@@ -245,11 +239,7 @@ describe('HiveLogClient setQuality', () => {
245
239
  describe('silent mode behavior', () => {
246
240
  it('should throw error when credentials are missing', async () => {
247
241
  // Create client without credentials
248
- process.env.HIVE_API_KEY = ''
249
- process.env.HIVE_API_SECRET = ''
250
- process.env.HIVE_HOST = ''
251
-
252
- const silentClient = new HiveLogClient('silent-project')
242
+ const silentClient = new HiveLogClient({ projectName: 'silent-project' })
253
243
 
254
244
  const quality: Quality = {
255
245
  score: 8.0,
@@ -259,7 +249,7 @@ describe('HiveLogClient setQuality', () => {
259
249
 
260
250
  await expect(silentClient.setQuality('test-task', 'test-uuid', quality))
261
251
  .rejects
262
- .toThrow('Missing Hive API credentials or host, get them at https://forgehive.dev')
252
+ .toThrow('Missing Hive API credentials or host, get them at https://www.forgehive.cloud')
263
253
 
264
254
  // Verify axios was never called
265
255
  expect(mockedAxios.post).not.toHaveBeenCalled()