voyageai-cli 1.6.1 → 1.7.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.
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ const { describe, it, after } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const http = require('http');
6
+ const { Command } = require('commander');
7
+ const { registerPlayground, createPlaygroundServer } = require('../../src/commands/playground');
8
+
9
+ describe('playground command', () => {
10
+ let server;
11
+ let port;
12
+
13
+ // Start server once for all tests
14
+ const serverReady = new Promise((resolve) => {
15
+ server = createPlaygroundServer();
16
+ server.listen(0, () => {
17
+ port = server.address().port;
18
+ resolve();
19
+ });
20
+ });
21
+
22
+ after(() => {
23
+ return new Promise((resolve) => {
24
+ if (server) server.close(resolve);
25
+ else resolve();
26
+ });
27
+ });
28
+
29
+ it('registers correctly on a program', () => {
30
+ const program = new Command();
31
+ registerPlayground(program);
32
+ const cmd = program.commands.find(c => c.name() === 'playground');
33
+ assert.ok(cmd, 'playground command should be registered');
34
+ assert.ok(cmd.description().includes('playground') || cmd.description().includes('Playground') || cmd.description().includes('web'),
35
+ 'should have a relevant description');
36
+ });
37
+
38
+ it('serves HTML on GET /', async () => {
39
+ await serverReady;
40
+ const body = await httpGet(`http://localhost:${port}/`);
41
+ assert.ok(body.includes('<!DOCTYPE html>'), 'should return HTML');
42
+ assert.ok(body.includes('Voyage AI Playground'), 'should include playground title');
43
+ });
44
+
45
+ it('returns JSON from GET /api/models', async () => {
46
+ await serverReady;
47
+ const body = await httpGet(`http://localhost:${port}/api/models`);
48
+ const data = JSON.parse(body);
49
+ assert.ok(Array.isArray(data.models), 'models should be an array');
50
+ assert.ok(data.models.length > 0, 'should have at least one model');
51
+ assert.ok(data.models.every(m => !m.legacy), 'should not include legacy models');
52
+ // Check a known model exists
53
+ const names = data.models.map(m => m.name);
54
+ assert.ok(names.includes('voyage-4-large'), 'should include voyage-4-large');
55
+ });
56
+
57
+ it('returns config with hasKey boolean from GET /api/config', async () => {
58
+ await serverReady;
59
+ const body = await httpGet(`http://localhost:${port}/api/config`);
60
+ const data = JSON.parse(body);
61
+ assert.ok(typeof data.hasKey === 'boolean', 'hasKey should be a boolean');
62
+ assert.ok(typeof data.baseUrl === 'string', 'baseUrl should be a string');
63
+ // Should never expose the actual key
64
+ assert.ok(!data.apiKey, 'should not expose apiKey');
65
+ assert.ok(!data.key, 'should not expose key');
66
+ });
67
+
68
+ it('returns 404 for unknown routes', async () => {
69
+ await serverReady;
70
+ const { statusCode } = await httpGetFull(`http://localhost:${port}/nonexistent`);
71
+ assert.equal(statusCode, 404);
72
+ });
73
+
74
+ it('returns 400 for POST /api/embed with invalid body', async () => {
75
+ await serverReady;
76
+ const { statusCode, body } = await httpPostFull(`http://localhost:${port}/api/embed`, { texts: 'not-an-array' });
77
+ assert.equal(statusCode, 400);
78
+ const data = JSON.parse(body);
79
+ assert.ok(data.error, 'should return an error message');
80
+ });
81
+ });
82
+
83
+ /**
84
+ * Simple HTTP GET that returns the body string.
85
+ */
86
+ function httpGet(url) {
87
+ return new Promise((resolve, reject) => {
88
+ http.get(url, (res) => {
89
+ const chunks = [];
90
+ res.on('data', c => chunks.push(c));
91
+ res.on('end', () => resolve(Buffer.concat(chunks).toString()));
92
+ res.on('error', reject);
93
+ }).on('error', reject);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * HTTP GET returning { statusCode, body }.
99
+ */
100
+ function httpGetFull(url) {
101
+ return new Promise((resolve, reject) => {
102
+ http.get(url, (res) => {
103
+ const chunks = [];
104
+ res.on('data', c => chunks.push(c));
105
+ res.on('end', () => resolve({ statusCode: res.statusCode, body: Buffer.concat(chunks).toString() }));
106
+ res.on('error', reject);
107
+ }).on('error', reject);
108
+ });
109
+ }
110
+
111
+ /**
112
+ * HTTP POST returning { statusCode, body }.
113
+ */
114
+ function httpPostFull(url, data) {
115
+ return new Promise((resolve, reject) => {
116
+ const payload = JSON.stringify(data);
117
+ const parsed = new URL(url);
118
+ const req = http.request({
119
+ hostname: parsed.hostname,
120
+ port: parsed.port,
121
+ path: parsed.pathname,
122
+ method: 'POST',
123
+ headers: {
124
+ 'Content-Type': 'application/json',
125
+ 'Content-Length': Buffer.byteLength(payload),
126
+ },
127
+ }, (res) => {
128
+ const chunks = [];
129
+ res.on('data', c => chunks.push(c));
130
+ res.on('end', () => resolve({ statusCode: res.statusCode, body: Buffer.concat(chunks).toString() }));
131
+ res.on('error', reject);
132
+ });
133
+ req.on('error', reject);
134
+ req.write(payload);
135
+ req.end();
136
+ });
137
+ }