@forgehive/hive-sdk 0.0.1

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,181 @@
1
+ import axios from 'axios'
2
+ import { HiveLogClient, LogApiResponse, ApiError, isApiError } from '../index'
3
+
4
+ // Mock axios
5
+ jest.mock('axios')
6
+ const mockedAxios = axios as jest.Mocked<typeof axios>
7
+
8
+ describe('HiveLogClient getLog', () => {
9
+ const originalEnv = process.env
10
+ let client: HiveLogClient
11
+
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'
20
+
21
+ // Create client instance
22
+ client = new HiveLogClient('test-project')
23
+
24
+ // Clear all mocks
25
+ jest.clearAllMocks()
26
+ })
27
+
28
+ afterAll(() => {
29
+ process.env = originalEnv
30
+ })
31
+
32
+ describe('successful getLog', () => {
33
+ it('should fetch log successfully and return LogApiResponse', async () => {
34
+ const mockLogResponse: LogApiResponse = {
35
+ uuid: 'test-uuid-123',
36
+ taskName: 'test-task',
37
+ projectName: 'test-project',
38
+ logItem: {
39
+ input: { userId: 123, action: 'login' },
40
+ output: { success: true, sessionId: 'abc123' },
41
+ error: null,
42
+ boundaries: {
43
+ database: [{ input: 'SELECT * FROM users', output: [{ id: 123 }], error: null }]
44
+ }
45
+ },
46
+ replayFrom: 'some-replay-id',
47
+ createdAt: '2023-12-01T10:00:00Z'
48
+ }
49
+
50
+ // Mock successful axios response
51
+ mockedAxios.get.mockResolvedValueOnce({ data: mockLogResponse })
52
+
53
+ const result = await client.getLog('test-task', 'test-uuid-123')
54
+
55
+ expect(result).toEqual(mockLogResponse)
56
+ expect(mockedAxios.get).toHaveBeenCalledTimes(1)
57
+ expect(mockedAxios.get).toHaveBeenCalledWith(
58
+ 'https://test-host.com/api/tasks/test-task/logs/test-uuid-123',
59
+ {
60
+ headers: {
61
+ Authorization: 'Bearer test-api-key:test-api-secret',
62
+ 'Content-Type': 'application/json'
63
+ }
64
+ }
65
+ )
66
+ })
67
+
68
+ it('should handle API error response', async () => {
69
+ const mockErrorResponse: ApiError = {
70
+ error: 'Log not found'
71
+ }
72
+
73
+ mockedAxios.get.mockResolvedValueOnce({ data: mockErrorResponse })
74
+
75
+ const result = await client.getLog('test-task', 'non-existent-uuid')
76
+
77
+ expect(result).toEqual(mockErrorResponse)
78
+ expect(isApiError(result)).toBe(true)
79
+ })
80
+
81
+ it('should handle minimal log response', async () => {
82
+ const mockMinimalResponse: LogApiResponse = {
83
+ uuid: 'minimal-uuid',
84
+ taskName: 'minimal-task',
85
+ projectName: 'test-project',
86
+ logItem: {
87
+ input: 'simple input'
88
+ },
89
+ createdAt: '2023-12-01T10:00:00Z'
90
+ }
91
+
92
+ mockedAxios.get.mockResolvedValueOnce({ data: mockMinimalResponse })
93
+
94
+ const result = await client.getLog('minimal-task', 'minimal-uuid')
95
+
96
+ expect(result).toEqual(mockMinimalResponse)
97
+ expect(isApiError(result)).toBe(false)
98
+ })
99
+ })
100
+
101
+ describe('failed getLog', () => {
102
+ it('should return null when axios throws an error', async () => {
103
+ // Mock axios to throw an error
104
+ mockedAxios.get.mockRejectedValueOnce(new Error('Network error'))
105
+
106
+ const result = await client.getLog('test-task', 'test-uuid')
107
+
108
+ expect(result).toBeNull()
109
+ })
110
+
111
+ it('should return null when server returns 404', async () => {
112
+ // Mock axios to throw a 404 error
113
+ const notFoundError = new Error('Request failed with status code 404')
114
+ mockedAxios.get.mockRejectedValueOnce(notFoundError)
115
+
116
+ const result = await client.getLog('test-task', 'non-existent-uuid')
117
+
118
+ expect(result).toBeNull()
119
+ })
120
+
121
+ it('should return null when server returns 500', async () => {
122
+ // Mock axios to throw a server error
123
+ const serverError = new Error('Server Error')
124
+ mockedAxios.get.mockRejectedValueOnce(serverError)
125
+
126
+ const result = await client.getLog('test-task', 'test-uuid')
127
+
128
+ expect(result).toBeNull()
129
+ })
130
+ })
131
+
132
+ describe('getLog parameters', () => {
133
+ it('should handle special characters in taskName and uuid', async () => {
134
+ const mockResponse: LogApiResponse = {
135
+ uuid: 'uuid-with-special-chars-!@#',
136
+ taskName: 'task-with-special-chars-!@#',
137
+ projectName: 'test-project',
138
+ logItem: { input: 'test' },
139
+ createdAt: '2023-12-01T10:00:00Z'
140
+ }
141
+
142
+ mockedAxios.get.mockResolvedValueOnce({ data: mockResponse })
143
+
144
+ const result = await client.getLog('task-with-special-chars-!@#', 'uuid-with-special-chars-!@#')
145
+
146
+ expect(result).toEqual(mockResponse)
147
+ expect(mockedAxios.get).toHaveBeenCalledWith(
148
+ 'https://test-host.com/api/tasks/task-with-special-chars-!@#/logs/uuid-with-special-chars-!@#',
149
+ expect.any(Object)
150
+ )
151
+ })
152
+ })
153
+ })
154
+
155
+ describe('isApiError type guard', () => {
156
+ it('should return true for ApiError objects', () => {
157
+ const apiError: ApiError = { error: 'Something went wrong' }
158
+ expect(isApiError(apiError)).toBe(true)
159
+ })
160
+
161
+ it('should return false for LogApiResponse objects', () => {
162
+ const logResponse: LogApiResponse = {
163
+ uuid: 'test-uuid',
164
+ taskName: 'test-task',
165
+ projectName: 'test-project',
166
+ logItem: { input: 'test' },
167
+ createdAt: '2023-12-01T10:00:00Z'
168
+ }
169
+ expect(isApiError(logResponse)).toBe(false)
170
+ })
171
+
172
+ it('should return false for null or undefined', () => {
173
+ expect(isApiError(null)).toBeFalsy()
174
+ expect(isApiError(undefined)).toBeFalsy()
175
+ })
176
+
177
+ it('should return false for objects without error property', () => {
178
+ expect(isApiError({ success: true })).toBe(false)
179
+ expect(isApiError({ data: 'some data' })).toBe(false)
180
+ })
181
+ })
@@ -0,0 +1,126 @@
1
+ import { HiveLogClient, createHiveLogClient } from '../index'
2
+
3
+ describe('Hive SDK', () => {
4
+ const originalEnv = process.env
5
+
6
+ beforeEach(() => {
7
+ jest.resetModules()
8
+ process.env = { ...originalEnv }
9
+ })
10
+
11
+ afterAll(() => {
12
+ process.env = originalEnv
13
+ })
14
+
15
+ describe('HiveLogClient constructor', () => {
16
+ it('should create client in silent mode when HIVE_API_KEY is missing', () => {
17
+ delete process.env.HIVE_API_KEY
18
+ process.env.HIVE_API_SECRET = 'test-secret'
19
+ process.env.HIVE_HOST = 'https://test.com'
20
+
21
+ expect(() => new HiveLogClient('test-project')).not.toThrow()
22
+ })
23
+
24
+ it('should create client in silent mode when HIVE_API_SECRET is missing', () => {
25
+ process.env.HIVE_API_KEY = 'test-key'
26
+ delete process.env.HIVE_API_SECRET
27
+ process.env.HIVE_HOST = 'https://test.com'
28
+
29
+ expect(() => new HiveLogClient('test-project')).not.toThrow()
30
+ })
31
+
32
+ it('should create client in silent mode when HIVE_HOST is missing', () => {
33
+ process.env.HIVE_API_KEY = 'test-key'
34
+ process.env.HIVE_API_SECRET = 'test-secret'
35
+ delete process.env.HIVE_HOST
36
+
37
+ expect(() => new HiveLogClient('test-project')).not.toThrow()
38
+ })
39
+
40
+ it('should create client in silent mode when all environment variables are missing', () => {
41
+ delete process.env.HIVE_API_KEY
42
+ delete process.env.HIVE_API_SECRET
43
+ delete process.env.HIVE_HOST
44
+
45
+ expect(() => new HiveLogClient('test-project')).not.toThrow()
46
+ })
47
+
48
+ it('should create client successfully when all environment variables are present', () => {
49
+ process.env.HIVE_API_KEY = 'test-key'
50
+ process.env.HIVE_API_SECRET = 'test-secret'
51
+ process.env.HIVE_HOST = 'https://test.com'
52
+
53
+ expect(() => new HiveLogClient('test-project')).not.toThrow()
54
+ })
55
+ })
56
+
57
+ describe('isActive method', () => {
58
+ it('should return false when credentials are missing', () => {
59
+ delete process.env.HIVE_API_KEY
60
+ delete process.env.HIVE_API_SECRET
61
+ delete process.env.HIVE_HOST
62
+
63
+ const client = new HiveLogClient('test-project')
64
+ expect(client.isActive()).toBe(false)
65
+ })
66
+
67
+ it('should return true when all credentials are present', () => {
68
+ process.env.HIVE_API_KEY = 'test-key'
69
+ process.env.HIVE_API_SECRET = 'test-secret'
70
+ process.env.HIVE_HOST = 'https://test.com'
71
+
72
+ const client = new HiveLogClient('test-project')
73
+ expect(client.isActive()).toBe(true)
74
+ })
75
+ })
76
+
77
+ describe('createHiveLogClient factory function', () => {
78
+ it('should create HiveLogClient instance when env vars are present', () => {
79
+ process.env.HIVE_API_KEY = 'test-key'
80
+ process.env.HIVE_API_SECRET = 'test-secret'
81
+ process.env.HIVE_HOST = 'https://test.com'
82
+
83
+ const client = createHiveLogClient('test-project')
84
+
85
+ expect(client).toBeInstanceOf(HiveLogClient)
86
+ })
87
+
88
+ it('should create client in silent mode when env vars are missing', () => {
89
+ delete process.env.HIVE_API_KEY
90
+ delete process.env.HIVE_API_SECRET
91
+ delete process.env.HIVE_HOST
92
+
93
+ expect(() => createHiveLogClient('test-project')).not.toThrow()
94
+ })
95
+ })
96
+
97
+ describe('Silent mode behavior', () => {
98
+ let silentClient: HiveLogClient
99
+
100
+ beforeEach(() => {
101
+ delete process.env.HIVE_API_KEY
102
+ delete process.env.HIVE_API_SECRET
103
+ delete process.env.HIVE_HOST
104
+
105
+ silentClient = new HiveLogClient('silent-project')
106
+ })
107
+
108
+ it('should return "silent" for sendLog in silent mode', async () => {
109
+ const result = await silentClient.sendLog('test-task', { data: 'test' })
110
+ expect(result).toBe('silent')
111
+ })
112
+
113
+ it('should throw error for getLog in silent mode', async () => {
114
+ await expect(silentClient.getLog('test-task', 'test-uuid')).rejects.toThrow(
115
+ 'Missing Hive API credentials or host, get them at https://forgehive.dev'
116
+ )
117
+ })
118
+
119
+ it('should throw error for setQuality in silent mode', async () => {
120
+ const quality = { score: 8, reason: 'test', suggestions: 'test' }
121
+ await expect(silentClient.setQuality('test-task', 'test-uuid', quality)).rejects.toThrow(
122
+ 'Missing Hive API credentials or host, get them at https://forgehive.dev'
123
+ )
124
+ })
125
+ })
126
+ })
@@ -0,0 +1,148 @@
1
+ import axios from 'axios'
2
+ import { HiveLogClient } from '../index'
3
+
4
+ // Mock axios
5
+ jest.mock('axios')
6
+ const mockedAxios = axios as jest.Mocked<typeof axios>
7
+
8
+ describe('HiveLogClient sendLog', () => {
9
+ const originalEnv = process.env
10
+ let client: HiveLogClient
11
+
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'
20
+
21
+ // Create client instance
22
+ client = new HiveLogClient('test-project')
23
+
24
+ // Clear all mocks
25
+ jest.clearAllMocks()
26
+ })
27
+
28
+ afterAll(() => {
29
+ process.env = originalEnv
30
+ })
31
+
32
+ describe('successful sendLog', () => {
33
+ it('should send log successfully and return true', async () => {
34
+ // Mock successful axios response
35
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
36
+
37
+ const logItem = { input: 'test-input', output: 'test-output' }
38
+ const result = await client.sendLog('test-task', logItem)
39
+
40
+ expect(result).toBe('success')
41
+ expect(mockedAxios.post).toHaveBeenCalledTimes(1)
42
+ expect(mockedAxios.post).toHaveBeenCalledWith(
43
+ 'https://test-host.com/api/tasks/log-ingest',
44
+ {
45
+ projectName: 'test-project',
46
+ taskName: 'test-task',
47
+ logItem: JSON.stringify(logItem)
48
+ },
49
+ {
50
+ headers: {
51
+ Authorization: 'Bearer test-api-key:test-api-secret',
52
+ 'Content-Type': 'application/json'
53
+ }
54
+ }
55
+ )
56
+ })
57
+
58
+ it('should handle complex log items', async () => {
59
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
60
+
61
+ const complexLogItem = {
62
+ input: { userId: 123, action: 'login' },
63
+ output: { success: true, sessionId: 'abc123' },
64
+ error: null,
65
+ boundaries: {
66
+ database: [{ input: 'SELECT * FROM users', output: [{ id: 123 }], error: null }]
67
+ }
68
+ }
69
+
70
+ const result = await client.sendLog('complex-task', complexLogItem)
71
+
72
+ expect(result).toBe('success')
73
+ expect(mockedAxios.post).toHaveBeenCalledWith(
74
+ 'https://test-host.com/api/tasks/log-ingest',
75
+ {
76
+ projectName: 'test-project',
77
+ taskName: 'complex-task',
78
+ logItem: JSON.stringify(complexLogItem)
79
+ },
80
+ {
81
+ headers: {
82
+ Authorization: 'Bearer test-api-key:test-api-secret',
83
+ 'Content-Type': 'application/json'
84
+ }
85
+ }
86
+ )
87
+ })
88
+ })
89
+
90
+ describe('failed sendLog', () => {
91
+ it('should return false when axios throws an error', async () => {
92
+ // Mock axios to throw an error
93
+ mockedAxios.post.mockRejectedValueOnce(new Error('Network error'))
94
+
95
+ const logItem = { input: 'test-input' }
96
+ const result = await client.sendLog('test-task', logItem)
97
+
98
+ expect(result).toBe('error')
99
+ })
100
+
101
+ it('should return false when server returns 500', async () => {
102
+ // Mock axios to throw a server error
103
+ const serverError = new Error('Server Error')
104
+ mockedAxios.post.mockRejectedValueOnce(serverError)
105
+
106
+ const result = await client.sendLog('test-task', { input: 'test' })
107
+
108
+ expect(result).toBe('error')
109
+ })
110
+ })
111
+
112
+ describe('sendLog parameters', () => {
113
+ it('should handle empty log items', async () => {
114
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
115
+
116
+ const result = await client.sendLog('empty-task', {})
117
+
118
+ expect(result).toBe('success')
119
+ expect(mockedAxios.post).toHaveBeenCalledWith(
120
+ 'https://test-host.com/api/tasks/log-ingest',
121
+ {
122
+ projectName: 'test-project',
123
+ taskName: 'empty-task',
124
+ logItem: JSON.stringify({})
125
+ },
126
+ expect.any(Object)
127
+ )
128
+ })
129
+
130
+ it('should handle null/undefined values in log items', async () => {
131
+ mockedAxios.post.mockResolvedValueOnce({ data: { success: true } })
132
+
133
+ const logItem = { input: null, output: undefined, error: 'some error' }
134
+ const result = await client.sendLog('null-task', logItem)
135
+
136
+ expect(result).toBe('success')
137
+ expect(mockedAxios.post).toHaveBeenCalledWith(
138
+ 'https://test-host.com/api/tasks/log-ingest',
139
+ {
140
+ projectName: 'test-project',
141
+ taskName: 'null-task',
142
+ logItem: JSON.stringify(logItem)
143
+ },
144
+ expect.any(Object)
145
+ )
146
+ })
147
+ })
148
+ })