valenceai 0.5.0 → 1.0.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.
@@ -10,11 +10,11 @@ describe('AsyncAudio', () => {
10
10
  beforeEach(() => {
11
11
  process.env = { ...originalEnv };
12
12
  process.env.VALENCE_API_KEY = 'test-api-key';
13
- process.env.VALENCE_ASYNCH_URL = 'https://test-api.com';
14
-
13
+ process.env.VALENCE_API_BASE_URL = 'https://test-api.com';
14
+
15
15
  fsMock = jest.spyOn(fs, 'existsSync');
16
16
  statMock = jest.spyOn(fs, 'statSync');
17
-
17
+
18
18
  // Mock createReadStream to simulate file chunks
19
19
  jest.spyOn(fs, 'createReadStream').mockReturnValue({
20
20
  [Symbol.asyncIterator]: async function* () {
@@ -44,9 +44,9 @@ describe('AsyncAudio', () => {
44
44
  ]
45
45
  };
46
46
 
47
- // Mock initiate request
47
+ // Mock initiate request (v1 endpoint)
48
48
  nock('https://test-api.com')
49
- .get('/upload/initiate')
49
+ .get('/v1/asynch/emotion/upload/initiate')
50
50
  .query({ file_name: 'test.wav', part_count: 2 })
51
51
  .reply(200, mockInitiateResponse);
52
52
 
@@ -59,9 +59,9 @@ describe('AsyncAudio', () => {
59
59
  .put('/part2')
60
60
  .reply(200, '', { etag: '"etag2"' });
61
61
 
62
- // Mock complete request
62
+ // Mock complete request (v1 endpoint)
63
63
  nock('https://test-api.com')
64
- .post('/upload/complete', {
64
+ .post('/v1/asynch/emotion/upload/complete', {
65
65
  request_id: 'req-123',
66
66
  upload_id: 'upload-123',
67
67
  parts: [
@@ -71,21 +71,21 @@ describe('AsyncAudio', () => {
71
71
  })
72
72
  .reply(200);
73
73
 
74
- const client = new ValenceClient();
74
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
75
75
  const result = await client.asynch.upload('test.wav');
76
-
76
+
77
77
  expect(result).toBe('req-123');
78
78
  });
79
79
 
80
80
  test('should throw error when filePath is missing', async () => {
81
- const client = new ValenceClient();
81
+ const client = new ValenceClient({ apiKey: 'test-key' });
82
82
  await expect(client.asynch.upload()).rejects.toThrow(
83
83
  'filePath is required and must be a string'
84
84
  );
85
85
  });
86
86
 
87
87
  test('should throw error when filePath is not a string', async () => {
88
- const client = new ValenceClient();
88
+ const client = new ValenceClient({ apiKey: 'test-key' });
89
89
  await expect(client.asynch.upload(123)).rejects.toThrow(
90
90
  'filePath is required and must be a string'
91
91
  );
@@ -93,11 +93,10 @@ describe('AsyncAudio', () => {
93
93
 
94
94
  test('should throw error when API key is missing', async () => {
95
95
  delete process.env.VALENCE_API_KEY;
96
-
97
- const client = new ValenceClient();
98
- await expect(client.asynch.upload('test.wav')).rejects.toThrow(
99
- 'VALENCE_API_KEY is required'
100
- );
96
+
97
+ expect(() => {
98
+ new ValenceClient();
99
+ }).toThrow('API key not provided');
101
100
  });
102
101
 
103
102
  test('should throw error when file does not exist', async () => {
@@ -180,83 +179,88 @@ describe('AsyncAudio', () => {
180
179
  });
181
180
 
182
181
  describe('getEmotions', () => {
183
- test('should successfully get emotions', async () => {
182
+ test('should successfully get emotions with timeline data', async () => {
184
183
  const mockResponse = {
185
- emotions: {
186
- happy: 0.7,
187
- sad: 0.2,
188
- angry: 0.1
189
- }
184
+ emotions: [
185
+ {
186
+ timestamp: 0.5,
187
+ start_time: 0.0,
188
+ end_time: 1.0,
189
+ emotion: 'happy',
190
+ confidence: 0.9,
191
+ all_predictions: { happy: 0.9, sad: 0.1 }
192
+ },
193
+ {
194
+ timestamp: 1.5,
195
+ start_time: 1.0,
196
+ end_time: 2.0,
197
+ emotion: 'sad',
198
+ confidence: 0.8,
199
+ all_predictions: { happy: 0.2, sad: 0.8 }
200
+ }
201
+ ],
202
+ status: 'completed'
190
203
  };
191
204
 
205
+ // v1 endpoint with request_id in path
192
206
  nock('https://test-api.com')
193
- .get('/prediction')
194
- .query({ request_id: 'req-123' })
207
+ .get('/v1/asynch/emotion/status/req-123')
195
208
  .reply(200, mockResponse);
196
209
 
197
- const client = new ValenceClient();
210
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
198
211
  const result = await client.asynch.emotions('req-123');
199
-
212
+
200
213
  expect(result).toEqual(mockResponse);
214
+ expect(result.emotions.length).toBe(2);
215
+ expect(result.emotions[0].emotion).toBe('happy');
201
216
  });
202
217
 
203
218
  test('should poll until success', async () => {
204
- const mockResponse = { emotions: { happy: 1.0 } };
219
+ const mockResponse = { emotions: [], status: 'completed' };
205
220
 
206
221
  nock('https://test-api.com')
207
- .get('/prediction')
208
- .query({ request_id: 'req-123' })
222
+ .get('/v1/asynch/emotion/status/req-123')
209
223
  .reply(202) // Processing
210
- .get('/prediction')
211
- .query({ request_id: 'req-123' })
224
+ .get('/v1/asynch/emotion/status/req-123')
212
225
  .reply(200, mockResponse); // Success
213
226
 
214
- const client = new ValenceClient();
227
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
215
228
  const result = await client.asynch.emotions('req-123', 5, 100); // Short interval for testing
216
-
229
+
217
230
  expect(result).toEqual(mockResponse);
218
231
  });
219
232
 
220
233
  test('should throw error when requestId is missing', async () => {
221
- const client = new ValenceClient();
234
+ const client = new ValenceClient({ apiKey: 'test-key' });
222
235
  await expect(client.asynch.emotions()).rejects.toThrow(
223
236
  'requestId is required and must be a string'
224
237
  );
225
238
  });
226
239
 
227
240
  test('should throw error when requestId is not a string', async () => {
228
- const client = new ValenceClient();
241
+ const client = new ValenceClient({ apiKey: 'test-key' });
229
242
  await expect(client.asynch.emotions(123)).rejects.toThrow(
230
243
  'requestId is required and must be a string'
231
244
  );
232
245
  });
233
246
 
234
- test('should throw error when API key is missing', async () => {
235
- delete process.env.VALENCE_API_KEY;
236
-
237
- const client = new ValenceClient();
238
- await expect(client.asynch.emotions('req-123')).rejects.toThrow(
239
- 'VALENCE_API_KEY is required'
240
- );
241
- });
242
-
243
247
  test('should throw error with invalid maxTries', async () => {
244
- const client = new ValenceClient();
248
+ const client = new ValenceClient({ apiKey: 'test-key' });
245
249
  await expect(client.asynch.emotions('req-123', 0)).rejects.toThrow(
246
250
  'maxTries must be between 1 and 100'
247
251
  );
248
-
252
+
249
253
  await expect(client.asynch.emotions('req-123', 101)).rejects.toThrow(
250
254
  'maxTries must be between 1 and 100'
251
255
  );
252
256
  });
253
257
 
254
258
  test('should throw error with invalid intervalMs', async () => {
255
- const client = new ValenceClient();
259
+ const client = new ValenceClient({ apiKey: 'test-key' });
256
260
  await expect(client.asynch.emotions('req-123', 5, 500)).rejects.toThrow(
257
261
  'intervalMs must be between 1000 and 60000'
258
262
  );
259
-
263
+
260
264
  await expect(client.asynch.emotions('req-123', 5, 70000)).rejects.toThrow(
261
265
  'intervalMs must be between 1000 and 60000'
262
266
  );
@@ -264,11 +268,10 @@ describe('AsyncAudio', () => {
264
268
 
265
269
  test('should handle 404 errors', async () => {
266
270
  nock('https://test-api.com')
267
- .get('/prediction')
268
- .query({ request_id: 'invalid-id' })
271
+ .get('/v1/asynch/emotion/status/invalid-id')
269
272
  .reply(404);
270
273
 
271
- const client = new ValenceClient();
274
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
272
275
  await expect(client.asynch.emotions('invalid-id')).rejects.toThrow(
273
276
  'Request ID not found: invalid-id'
274
277
  );
@@ -276,11 +279,10 @@ describe('AsyncAudio', () => {
276
279
 
277
280
  test('should handle client errors', async () => {
278
281
  nock('https://test-api.com')
279
- .get('/prediction')
280
- .query({ request_id: 'req-123' })
282
+ .get('/v1/asynch/emotion/status/req-123')
281
283
  .reply(400, { message: 'Bad request' });
282
284
 
283
- const client = new ValenceClient();
285
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
284
286
  await expect(client.asynch.emotions('req-123')).rejects.toThrow(
285
287
  'Client error (400): Bad request'
286
288
  );
@@ -288,12 +290,11 @@ describe('AsyncAudio', () => {
288
290
 
289
291
  test('should timeout after max attempts', async () => {
290
292
  nock('https://test-api.com')
291
- .get('/prediction')
292
- .query({ request_id: 'req-123' })
293
+ .get('/v1/asynch/emotion/status/req-123')
293
294
  .times(3)
294
295
  .reply(202); // Always processing
295
296
 
296
- const client = new ValenceClient();
297
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
297
298
  await expect(client.asynch.emotions('req-123', 3, 100)).rejects.toThrow(
298
299
  'Prediction not available after 3 attempts. The request may still be processing.'
299
300
  );
@@ -301,31 +302,87 @@ describe('AsyncAudio', () => {
301
302
 
302
303
  test('should handle server errors gracefully', async () => {
303
304
  nock('https://test-api.com')
304
- .get('/prediction')
305
- .query({ request_id: 'req-123' })
305
+ .get('/v1/asynch/emotion/status/req-123')
306
306
  .reply(500)
307
- .get('/prediction')
308
- .query({ request_id: 'req-123' })
309
- .reply(200, { emotions: { happy: 1.0 } });
307
+ .get('/v1/asynch/emotion/status/req-123')
308
+ .reply(200, { emotions: [], status: 'completed' });
310
309
 
311
- const client = new ValenceClient();
310
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
312
311
  const result = await client.asynch.emotions('req-123', 5, 100);
313
-
314
- expect(result).toEqual({ emotions: { happy: 1.0 } });
312
+
313
+ expect(result).toEqual({ emotions: [], status: 'completed' });
315
314
  });
316
315
 
317
316
  test('should include correct headers', async () => {
318
317
  const scope = nock('https://test-api.com')
319
- .get('/prediction')
318
+ .get('/v1/asynch/emotion/status/req-123')
320
319
  .matchHeader('x-api-key', 'test-api-key')
321
- .matchHeader('User-Agent', 'valenceai/0.4.1')
322
- .query({ request_id: 'req-123' })
323
- .reply(200, { emotions: {} });
320
+ .matchHeader('User-Agent', 'valenceai/1.0.0')
321
+ .reply(200, { emotions: [], status: 'completed' });
324
322
 
325
- const client = new ValenceClient();
323
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
326
324
  await client.asynch.emotions('req-123');
327
-
325
+
328
326
  expect(scope.isDone()).toBe(true);
329
327
  });
328
+
329
+ test('should get timeline data', async () => {
330
+ const mockResponse = {
331
+ emotions: [
332
+ { timestamp: 0.5, emotion: 'happy' },
333
+ { timestamp: 1.5, emotion: 'sad' }
334
+ ],
335
+ status: 'completed'
336
+ };
337
+
338
+ nock('https://test-api.com')
339
+ .get('/v1/asynch/emotion/status/req-123')
340
+ .reply(200, mockResponse);
341
+
342
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
343
+ const timeline = await client.asynch.getTimeline('req-123');
344
+
345
+ expect(timeline.length).toBe(2);
346
+ expect(timeline[0].emotion).toBe('happy');
347
+ });
348
+
349
+ test('should get emotion at specific time', async () => {
350
+ const mockResponse = {
351
+ emotions: [
352
+ { timestamp: 0.5, start_time: 0.0, end_time: 1.0, emotion: 'happy' },
353
+ { timestamp: 1.5, start_time: 1.0, end_time: 2.0, emotion: 'sad' }
354
+ ],
355
+ status: 'completed'
356
+ };
357
+
358
+ nock('https://test-api.com')
359
+ .get('/v1/asynch/emotion/status/req-123')
360
+ .reply(200, mockResponse);
361
+
362
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
363
+ const emotion = await client.asynch.getEmotionAtTime('req-123', 0.7);
364
+
365
+ expect(emotion.emotion).toBe('happy');
366
+ });
367
+
368
+ test('should get dominant emotion', async () => {
369
+ const mockResponse = {
370
+ emotions: [
371
+ { timestamp: 0.5, emotion: 'happy' },
372
+ { timestamp: 1.5, emotion: 'happy' },
373
+ { timestamp: 2.5, emotion: 'sad' }
374
+ ],
375
+ status: 'completed'
376
+ };
377
+
378
+ nock('https://test-api.com')
379
+ .get('/v1/asynch/emotion/status/req-123')
380
+ .reply(200, mockResponse);
381
+
382
+ const client = new ValenceClient({ apiKey: 'test-api-key' });
383
+ const dominant = await client.asynch.getDominantEmotion('req-123');
384
+
385
+ expect(dominant).toBe('happy');
386
+ });
330
387
  });
331
388
  });
@@ -1,42 +1,27 @@
1
- import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
2
- import { getHeaders } from '../src/client.js';
1
+ import { describe, test, expect } from '@jest/globals';
3
2
 
4
3
  describe('Client', () => {
5
- const originalEnv = process.env;
6
-
7
- beforeEach(() => {
8
- process.env = { ...originalEnv };
9
- });
10
-
11
- afterEach(() => {
12
- process.env = originalEnv;
13
- });
14
-
15
4
  describe('getHeaders', () => {
16
5
  test('should return headers with API key', async () => {
6
+ // Test with a specific API key set in environment
7
+ const originalKey = process.env.VALENCE_API_KEY;
17
8
  process.env.VALENCE_API_KEY = 'test-api-key';
18
-
9
+
19
10
  // Re-import to get fresh config
20
11
  const { getHeaders: freshGetHeaders } = await import('../src/client.js?' + Math.random());
21
-
12
+
22
13
  const headers = freshGetHeaders();
23
-
14
+
24
15
  expect(headers).toEqual({
25
16
  'x-api-key': 'test-api-key',
26
17
  'User-Agent': 'valenceai/0.4.1'
27
18
  });
28
- });
29
19
 
30
- test('should throw error when API key is missing', () => {
31
- delete process.env.VALENCE_API_KEY;
32
-
33
- expect(() => getHeaders()).toThrow('VALENCE_API_KEY is required but not configured');
20
+ // Restore original
21
+ process.env.VALENCE_API_KEY = originalKey;
34
22
  });
35
23
 
36
- test('should throw error when API key is empty string', () => {
37
- process.env.VALENCE_API_KEY = '';
38
-
39
- expect(() => getHeaders()).toThrow('VALENCE_API_KEY is required but not configured');
40
- });
24
+ // Note: API key validation is thoroughly tested in ValenceClient tests
25
+ // getHeaders() is an internal utility function that's always called with a valid config
41
26
  });
42
27
  });
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
2
- import { config, validateConfig } from '../src/config.js';
2
+ import { validateConfig } from '../src/config.js';
3
3
 
4
4
  describe('Config', () => {
5
5
  const originalEnv = process.env;
@@ -15,37 +15,37 @@ describe('Config', () => {
15
15
  describe('config object', () => {
16
16
  test('should load from environment variables', async () => {
17
17
  process.env.VALENCE_API_KEY = 'test-key';
18
- process.env.VALENCE_DISCRETE_URL = 'https://test-short.com';
19
- process.env.VALENCE_ASYNCH_URL = 'https://test-long.com';
18
+ process.env.VALENCE_API_BASE_URL = 'https://test-base.com';
19
+ process.env.VALENCE_WEBSOCKET_URL = 'wss://test-websocket.com';
20
20
  process.env.VALENCE_LOG_LEVEL = 'debug';
21
21
 
22
22
  // Re-import to get fresh config
23
23
  const { config: freshConfig } = await import('../src/config.js?' + Math.random());
24
24
 
25
25
  expect(freshConfig.apiKey).toBe('test-key');
26
- expect(freshConfig.discreteAudioUrl).toBe('https://test-short.com');
27
- expect(freshConfig.asyncAudioUrl).toBe('https://test-long.com');
26
+ expect(freshConfig.baseUrl).toBe('https://test-base.com');
27
+ expect(freshConfig.websocketUrl).toBe('wss://test-websocket.com');
28
28
  expect(freshConfig.logLevel).toBe('debug');
29
29
  });
30
30
 
31
31
  test('should use default URLs when not provided', async () => {
32
32
  process.env.VALENCE_API_KEY = 'test-key';
33
- delete process.env.VALENCE_DISCRETE_URL;
34
- delete process.env.VALENCE_ASYNCH_URL;
33
+ delete process.env.VALENCE_API_BASE_URL;
34
+ delete process.env.VALENCE_WEBSOCKET_URL;
35
35
 
36
36
  // Re-import to get fresh config
37
37
  const { config: freshConfig } = await import('../src/config.js?' + Math.random());
38
38
 
39
- expect(freshConfig.discreteAudioUrl).toBe('https://xc8n2bo4f0.execute-api.us-west-2.amazonaws.com/emotionprediction');
40
- expect(freshConfig.asyncAudioUrl).toBe('https://wsgol61783.execute-api.us-west-2.amazonaws.com/prod');
39
+ expect(freshConfig.baseUrl).toBe('https://demo.getvalenceai.com');
40
+ expect(freshConfig.websocketUrl).toBe('wss://demo.getvalenceai.com');
41
41
  });
42
42
 
43
43
  test('should use default log level when not provided', async () => {
44
44
  delete process.env.VALENCE_LOG_LEVEL;
45
-
45
+
46
46
  // Re-import to get fresh config
47
47
  const { config: freshConfig } = await import('../src/config.js?' + Math.random());
48
-
48
+
49
49
  expect(freshConfig.logLevel).toBe('info');
50
50
  });
51
51
  });
@@ -53,8 +53,8 @@ describe('Config', () => {
53
53
  describe('validateConfig', () => {
54
54
  test('should pass with valid configuration', () => {
55
55
  process.env.VALENCE_API_KEY = 'test-key';
56
- process.env.VALENCE_DISCRETE_URL = 'https://test-short.com';
57
- process.env.VALENCE_ASYNCH_URL = 'https://test-long.com';
56
+ process.env.VALENCE_API_BASE_URL = 'https://test-base.com';
57
+ process.env.VALENCE_WEBSOCKET_URL = 'wss://test-websocket.com';
58
58
  process.env.VALENCE_LOG_LEVEL = 'info';
59
59
 
60
60
  expect(() => validateConfig()).not.toThrow();
@@ -63,28 +63,28 @@ describe('Config', () => {
63
63
  test('should throw when API key is missing', async () => {
64
64
  delete process.env.VALENCE_API_KEY;
65
65
  const { validateConfig: freshValidateConfig } = await import('../src/config.js?' + Math.random());
66
- expect(() => freshValidateConfig()).toThrow('VALENCE_API_KEY environment variable is required');
66
+ expect(() => freshValidateConfig()).toThrow('VALENCE_API_KEY is required');
67
67
  });
68
68
 
69
- test('should throw when short URL is not HTTPS', async () => {
69
+ test('should throw when base URL is not HTTPS', async () => {
70
70
  process.env.VALENCE_API_KEY = 'test-key';
71
- process.env.VALENCE_DISCRETE_URL = 'http://test-short.com';
71
+ process.env.VALENCE_API_BASE_URL = 'http://test-base.com';
72
72
  const { validateConfig: freshValidateConfig } = await import('../src/config.js?' + Math.random());
73
- expect(() => freshValidateConfig()).toThrow('VALENCE_DISCRETE_URL must be a valid HTTPS URL');
73
+ expect(() => freshValidateConfig()).toThrow('baseUrl must be a valid HTTPS URL');
74
74
  });
75
75
 
76
- test('should throw when long URL is not HTTPS', async () => {
76
+ test('should throw when websocket URL is not WSS', async () => {
77
77
  process.env.VALENCE_API_KEY = 'test-key';
78
- process.env.VALENCE_ASYNCH_URL = 'http://test-long.com';
78
+ process.env.VALENCE_WEBSOCKET_URL = 'ws://test-websocket.com';
79
79
  const { validateConfig: freshValidateConfig } = await import('../src/config.js?' + Math.random());
80
- expect(() => freshValidateConfig()).toThrow('VALENCE_ASYNCH_URL must be a valid HTTPS URL');
80
+ expect(() => freshValidateConfig()).toThrow('websocketUrl must be a valid WSS URL');
81
81
  });
82
82
 
83
83
  test('should throw with invalid log level', async () => {
84
84
  process.env.VALENCE_API_KEY = 'test-key';
85
85
  process.env.VALENCE_LOG_LEVEL = 'invalid';
86
86
  const { validateConfig: freshValidateConfig } = await import('../src/config.js?' + Math.random());
87
- expect(() => freshValidateConfig()).toThrow('VALENCE_LOG_LEVEL must be one of: debug, info, warn, error');
87
+ expect(() => freshValidateConfig()).toThrow('logLevel must be one of: debug, info, warn, error');
88
88
  });
89
89
  });
90
90
  });