@dainprotocol/cli 1.2.28 → 1.2.31
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/dist/__tests__/build.test.js +289 -0
- package/dist/__tests__/deploy.test.js +10 -27
- package/dist/__tests__/dev.test.js +189 -0
- package/dist/__tests__/init.test.js +290 -0
- package/dist/__tests__/integration.test.js +5 -22
- package/dist/__tests__/testchat.test.js +214 -0
- package/dist/__tests__/utils.test.js +324 -0
- package/dist/commands/build.js +29 -62
- package/dist/commands/deploy.js +96 -167
- package/dist/commands/dev.js +34 -84
- package/dist/commands/init.js +2 -9
- package/dist/commands/logs.js +35 -117
- package/dist/commands/start.js +15 -4
- package/dist/commands/status.js +14 -65
- package/dist/commands/undeploy.js +10 -66
- package/dist/index.js +0 -7
- package/dist/templates/default/dain.json +1 -1
- package/dist/utils.js +169 -35
- package/package.json +3 -3
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
var path_1 = __importDefault(require("path"));
|
|
7
|
+
/**
|
|
8
|
+
* Init command tests - project initialization, template handling, validation
|
|
9
|
+
*/
|
|
10
|
+
describe('init.ts', function () {
|
|
11
|
+
describe('Node.js version validation', function () {
|
|
12
|
+
function validateNodeVersion(version) {
|
|
13
|
+
var versionNum = Number(version.slice(1).split('.')[0]);
|
|
14
|
+
return { valid: versionNum >= 20, major: versionNum };
|
|
15
|
+
}
|
|
16
|
+
test('accepts Node.js 20+', function () {
|
|
17
|
+
expect(validateNodeVersion('v20.0.0').valid).toBe(true);
|
|
18
|
+
expect(validateNodeVersion('v20.7.0').valid).toBe(true);
|
|
19
|
+
expect(validateNodeVersion('v21.0.0').valid).toBe(true);
|
|
20
|
+
expect(validateNodeVersion('v22.3.0').valid).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
test('rejects Node.js < 20', function () {
|
|
23
|
+
expect(validateNodeVersion('v18.0.0').valid).toBe(false);
|
|
24
|
+
expect(validateNodeVersion('v19.9.0').valid).toBe(false);
|
|
25
|
+
expect(validateNodeVersion('v16.20.0').valid).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
test('parses version correctly', function () {
|
|
28
|
+
expect(validateNodeVersion('v20.7.0').major).toBe(20);
|
|
29
|
+
expect(validateNodeVersion('v18.15.0').major).toBe(18);
|
|
30
|
+
expect(validateNodeVersion('v22.0.0').major).toBe(22);
|
|
31
|
+
});
|
|
32
|
+
test('handles pre-release versions', function () {
|
|
33
|
+
// Pre-release versions have format like v21.0.0-nightly
|
|
34
|
+
expect(validateNodeVersion('v21.0.0-nightly').major).toBe(21);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('project directory handling', function () {
|
|
38
|
+
test('creates project path from cwd and name', function () {
|
|
39
|
+
var cwd = '/home/user/projects';
|
|
40
|
+
var projectName = 'my-service';
|
|
41
|
+
var projectDir = path_1.default.join(cwd, projectName);
|
|
42
|
+
expect(projectDir).toBe('/home/user/projects/my-service');
|
|
43
|
+
});
|
|
44
|
+
test('handles project names with special characters', function () {
|
|
45
|
+
var cwd = '/project';
|
|
46
|
+
expect(path_1.default.join(cwd, 'my-service')).toBe('/project/my-service');
|
|
47
|
+
expect(path_1.default.join(cwd, 'my_service')).toBe('/project/my_service');
|
|
48
|
+
expect(path_1.default.join(cwd, '@scope/pkg')).toBe('/project/@scope/pkg');
|
|
49
|
+
});
|
|
50
|
+
test('handles spaces in project name', function () {
|
|
51
|
+
var cwd = '/project';
|
|
52
|
+
var projectDir = path_1.default.join(cwd, 'my service');
|
|
53
|
+
expect(projectDir).toBe('/project/my service');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('template directory resolution', function () {
|
|
57
|
+
test('resolves template path from __dirname', function () {
|
|
58
|
+
// The actual path is: __dirname/../templates/default
|
|
59
|
+
var dirname = '/path/to/dain-dev-cli/dist/commands';
|
|
60
|
+
var templateDir = path_1.default.join(dirname, '..', '..', 'templates', 'default');
|
|
61
|
+
expect(templateDir).toBe('/path/to/dain-dev-cli/templates/default');
|
|
62
|
+
});
|
|
63
|
+
test('uses "default" template', function () {
|
|
64
|
+
var templateName = 'default';
|
|
65
|
+
expect(templateName).toBe('default');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('package.json modification', function () {
|
|
69
|
+
test('updates package name', function () {
|
|
70
|
+
var packageJson = { name: 'template-name', version: '1.0.0' };
|
|
71
|
+
var projectName = 'my-new-project';
|
|
72
|
+
packageJson.name = projectName;
|
|
73
|
+
expect(packageJson.name).toBe('my-new-project');
|
|
74
|
+
});
|
|
75
|
+
test('preserves other package.json fields', function () {
|
|
76
|
+
var packageJson = {
|
|
77
|
+
name: 'template',
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
dependencies: { 'some-dep': '^1.0.0' },
|
|
80
|
+
scripts: { dev: 'node index.js' },
|
|
81
|
+
};
|
|
82
|
+
var projectName = 'updated-name';
|
|
83
|
+
packageJson.name = projectName;
|
|
84
|
+
expect(packageJson.name).toBe('updated-name');
|
|
85
|
+
expect(packageJson.version).toBe('1.0.0');
|
|
86
|
+
expect(packageJson.dependencies).toEqual({ 'some-dep': '^1.0.0' });
|
|
87
|
+
expect(packageJson.scripts).toEqual({ dev: 'node index.js' });
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('project name validation', function () {
|
|
91
|
+
// Valid npm package name patterns
|
|
92
|
+
function isValidProjectName(name) {
|
|
93
|
+
// Simple validation: non-empty, no path separators at start
|
|
94
|
+
if (!name || name.startsWith('/') || name.startsWith('..')) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
test('accepts valid project names', function () {
|
|
100
|
+
expect(isValidProjectName('my-project')).toBe(true);
|
|
101
|
+
expect(isValidProjectName('my_project')).toBe(true);
|
|
102
|
+
expect(isValidProjectName('myproject123')).toBe(true);
|
|
103
|
+
expect(isValidProjectName('@scope/package')).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
test('rejects empty names', function () {
|
|
106
|
+
expect(isValidProjectName('')).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
test('rejects absolute paths', function () {
|
|
109
|
+
expect(isValidProjectName('/etc/passwd')).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
test('rejects path traversal', function () {
|
|
112
|
+
expect(isValidProjectName('../other')).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('success messages', function () {
|
|
116
|
+
test('includes project name in success message', function () {
|
|
117
|
+
var projectName = 'my-service';
|
|
118
|
+
var message = "Initialized Dain project: ".concat(projectName);
|
|
119
|
+
expect(message).toContain('my-service');
|
|
120
|
+
});
|
|
121
|
+
test('includes project directory in message', function () {
|
|
122
|
+
var projectDir = '/home/user/projects/my-service';
|
|
123
|
+
var message = "Project created at ".concat(projectDir);
|
|
124
|
+
expect(message).toContain('/home/user/projects/my-service');
|
|
125
|
+
});
|
|
126
|
+
test('includes getting started instructions', function () {
|
|
127
|
+
var projectName = 'my-project';
|
|
128
|
+
var commands = ["cd ".concat(projectName), 'npm install', 'npm run dev'];
|
|
129
|
+
expect(commands[0]).toBe('cd my-project');
|
|
130
|
+
expect(commands[1]).toBe('npm install');
|
|
131
|
+
expect(commands[2]).toBe('npm run dev');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('status.ts', function () {
|
|
136
|
+
describe('URL construction', function () {
|
|
137
|
+
test('builds correct status URL', function () {
|
|
138
|
+
var baseUrl = 'https://api.example.com';
|
|
139
|
+
var orgId = 'org123';
|
|
140
|
+
var serviceId = 'svc456';
|
|
141
|
+
var deploymentId = 'dep789';
|
|
142
|
+
var environment = 'production';
|
|
143
|
+
var statusUrl = "".concat(baseUrl, "/codegen-deploy/status/").concat(orgId, "/").concat(serviceId, "/").concat(deploymentId, "/").concat(environment);
|
|
144
|
+
expect(statusUrl).toBe('https://api.example.com/codegen-deploy/status/org123/svc456/dep789/production');
|
|
145
|
+
});
|
|
146
|
+
test('handles different environments', function () {
|
|
147
|
+
var buildUrl = function (env) {
|
|
148
|
+
return "https://api.example.com/codegen-deploy/status/org/svc/dep/".concat(env);
|
|
149
|
+
};
|
|
150
|
+
expect(buildUrl('production')).toContain('/production');
|
|
151
|
+
expect(buildUrl('staging')).toContain('/staging');
|
|
152
|
+
expect(buildUrl('development')).toContain('/development');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('predefined parameters override', function () {
|
|
156
|
+
test('uses predefined values over config', function () {
|
|
157
|
+
var config = {
|
|
158
|
+
'deployment-id': 'config-dep',
|
|
159
|
+
'service-id': 'config-svc',
|
|
160
|
+
environment: 'config-env',
|
|
161
|
+
};
|
|
162
|
+
var predefined = {
|
|
163
|
+
deploymentId: 'pre-dep',
|
|
164
|
+
serviceId: 'pre-svc',
|
|
165
|
+
environment: 'pre-env',
|
|
166
|
+
};
|
|
167
|
+
var deploymentId = predefined.deploymentId || config['deployment-id'];
|
|
168
|
+
var serviceId = predefined.serviceId || config['service-id'];
|
|
169
|
+
var environment = predefined.environment || config.environment;
|
|
170
|
+
expect(deploymentId).toBe('pre-dep');
|
|
171
|
+
expect(serviceId).toBe('pre-svc');
|
|
172
|
+
expect(environment).toBe('pre-env');
|
|
173
|
+
});
|
|
174
|
+
test('falls back to config when predefined is undefined', function () {
|
|
175
|
+
var config = {
|
|
176
|
+
'deployment-id': 'config-dep',
|
|
177
|
+
'service-id': 'config-svc',
|
|
178
|
+
};
|
|
179
|
+
var predefinedDeploymentId = undefined;
|
|
180
|
+
var predefinedServiceId = undefined;
|
|
181
|
+
var deploymentId = predefinedDeploymentId || config['deployment-id'];
|
|
182
|
+
var serviceId = predefinedServiceId || config['service-id'];
|
|
183
|
+
expect(deploymentId).toBe('config-dep');
|
|
184
|
+
expect(serviceId).toBe('config-svc');
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
describe('response handling', function () {
|
|
188
|
+
test('returns result when predefined params are provided', function () {
|
|
189
|
+
var preDefinedDeploymentId = 'dep123';
|
|
190
|
+
var shouldReturn = !!preDefinedDeploymentId;
|
|
191
|
+
expect(shouldReturn).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
test('exits when no predefined params', function () {
|
|
194
|
+
var preDefinedDeploymentId = undefined;
|
|
195
|
+
var shouldExit = !preDefinedDeploymentId;
|
|
196
|
+
expect(shouldExit).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('undeploy.ts', function () {
|
|
201
|
+
describe('URL construction', function () {
|
|
202
|
+
test('builds correct undeploy URL', function () {
|
|
203
|
+
var baseUrl = 'https://api.example.com';
|
|
204
|
+
var orgId = 'org123';
|
|
205
|
+
var serviceId = 'svc456';
|
|
206
|
+
var deploymentId = 'dep789';
|
|
207
|
+
var environment = 'production';
|
|
208
|
+
var undeployUrl = "".concat(baseUrl, "/codegen-deploy/undeploy/").concat(orgId, "/").concat(serviceId, "/").concat(deploymentId, "/").concat(environment);
|
|
209
|
+
expect(undeployUrl).toBe('https://api.example.com/codegen-deploy/undeploy/org123/svc456/dep789/production');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe('HTTP method', function () {
|
|
213
|
+
test('uses DELETE method for undeploy', function () {
|
|
214
|
+
var method = 'DELETE';
|
|
215
|
+
expect(method).toBe('DELETE');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe('validation requirements', function () {
|
|
219
|
+
test('requires org ID', function () {
|
|
220
|
+
var orgId = '';
|
|
221
|
+
var hasError = !orgId;
|
|
222
|
+
expect(hasError).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
test('requires deployment ID', function () {
|
|
225
|
+
var deploymentId = '';
|
|
226
|
+
var hasError = !deploymentId;
|
|
227
|
+
expect(hasError).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
test('requires service ID', function () {
|
|
230
|
+
var serviceId = '';
|
|
231
|
+
var hasError = !serviceId;
|
|
232
|
+
expect(hasError).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
test('passes validation with all required fields', function () {
|
|
235
|
+
var orgId = 'org123';
|
|
236
|
+
var deploymentId = 'dep456';
|
|
237
|
+
var serviceId = 'svc789';
|
|
238
|
+
var allValid = orgId && deploymentId && serviceId;
|
|
239
|
+
expect(allValid).toBeTruthy();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
describe('shared patterns across commands', function () {
|
|
244
|
+
describe('fetchWithTimeout pattern', function () {
|
|
245
|
+
var FETCH_TIMEOUT_MS = 30000;
|
|
246
|
+
test('default timeout is 30 seconds', function () {
|
|
247
|
+
expect(FETCH_TIMEOUT_MS).toBe(30000);
|
|
248
|
+
});
|
|
249
|
+
test('uses AbortController for cancellation', function () {
|
|
250
|
+
var controller = new AbortController();
|
|
251
|
+
expect(controller.signal.aborted).toBe(false);
|
|
252
|
+
controller.abort();
|
|
253
|
+
expect(controller.signal.aborted).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
describe('Authorization header', function () {
|
|
257
|
+
test('formats Bearer token correctly', function () {
|
|
258
|
+
var apiKey = 'sk_agent_test_key';
|
|
259
|
+
var header = "Bearer ".concat(apiKey);
|
|
260
|
+
expect(header).toBe('Bearer sk_agent_test_key');
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
describe('error response handling', function () {
|
|
264
|
+
test('creates error message from response', function () {
|
|
265
|
+
var status = 404;
|
|
266
|
+
var statusText = 'Not Found';
|
|
267
|
+
var errorMessage = "failed: ".concat(status, " ").concat(statusText);
|
|
268
|
+
expect(errorMessage).toBe('failed: 404 Not Found');
|
|
269
|
+
});
|
|
270
|
+
test('handles various HTTP status codes', function () {
|
|
271
|
+
var createError = function (status, text) { return "".concat(status, " ").concat(text); };
|
|
272
|
+
expect(createError(400, 'Bad Request')).toBe('400 Bad Request');
|
|
273
|
+
expect(createError(401, 'Unauthorized')).toBe('401 Unauthorized');
|
|
274
|
+
expect(createError(403, 'Forbidden')).toBe('403 Forbidden');
|
|
275
|
+
expect(createError(404, 'Not Found')).toBe('404 Not Found');
|
|
276
|
+
expect(createError(500, 'Internal Server Error')).toBe('500 Internal Server Error');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
describe('config resolution pattern', function () {
|
|
280
|
+
test('uses platform-base-url from config or default', function () {
|
|
281
|
+
var DEFAULT_PLATFORM_BASE_URL = 'https://default.example.com';
|
|
282
|
+
var config1 = { 'platform-base-url': 'https://custom.example.com' };
|
|
283
|
+
var config2 = {};
|
|
284
|
+
var url1 = config1['platform-base-url'] || DEFAULT_PLATFORM_BASE_URL;
|
|
285
|
+
var url2 = config2['platform-base-url'] || DEFAULT_PLATFORM_BASE_URL;
|
|
286
|
+
expect(url1).toBe('https://custom.example.com');
|
|
287
|
+
expect(url2).toBe('https://default.example.com');
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
@@ -8,30 +8,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
8
8
|
};
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
var path_1 = __importDefault(require("path"));
|
|
11
|
+
var utils_1 = require("../utils");
|
|
11
12
|
describe('Bug Detection Tests', function () {
|
|
12
13
|
describe('ENV parsing - CRITICAL: = in values', function () {
|
|
13
|
-
// This is the ACTUAL parsing logic from deploy.ts
|
|
14
|
-
function parseEnvContent(envContent) {
|
|
15
|
-
return envContent
|
|
16
|
-
.split('\n')
|
|
17
|
-
.filter(function (line) { return line.trim() && !line.trim().startsWith('#'); })
|
|
18
|
-
.map(function (line) {
|
|
19
|
-
var equalsIndex = line.indexOf('=');
|
|
20
|
-
if (equalsIndex === -1)
|
|
21
|
-
return null;
|
|
22
|
-
var name = line.substring(0, equalsIndex).trim();
|
|
23
|
-
var value = line.substring(equalsIndex + 1).trim();
|
|
24
|
-
var unquotedValue = value.replace(/^["']|["']$/g, '');
|
|
25
|
-
return { name: name, value: unquotedValue };
|
|
26
|
-
})
|
|
27
|
-
.filter(function (env) {
|
|
28
|
-
return env !== null && env.name !== '' && env.value !== '';
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
14
|
test('REGRESSION: base64 values with == suffix must be preserved', function () {
|
|
32
15
|
// OLD BUG: line.split('=') would break this into ['JWT', 'eyJ...', '', '']
|
|
33
16
|
var content = 'JWT_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test==';
|
|
34
|
-
var result = parseEnvContent(content);
|
|
17
|
+
var result = (0, utils_1.parseEnvContent)(content);
|
|
35
18
|
expect(result).toHaveLength(1);
|
|
36
19
|
expect(result[0].name).toBe('JWT_SECRET');
|
|
37
20
|
expect(result[0].value).toBe('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test==');
|
|
@@ -39,17 +22,17 @@ describe('Bug Detection Tests', function () {
|
|
|
39
22
|
test('REGRESSION: database URLs with query params', function () {
|
|
40
23
|
// OLD BUG: would only get 'postgresql://user:pass@host:5432/db?sslmode'
|
|
41
24
|
var content = 'DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require';
|
|
42
|
-
var result = parseEnvContent(content);
|
|
25
|
+
var result = (0, utils_1.parseEnvContent)(content);
|
|
43
26
|
expect(result[0].value).toBe('postgresql://user:pass@host:5432/db?sslmode=require');
|
|
44
27
|
});
|
|
45
28
|
test('REGRESSION: multiple = signs in value', function () {
|
|
46
29
|
var content = 'FORMULA=a=b=c=d';
|
|
47
|
-
var result = parseEnvContent(content);
|
|
30
|
+
var result = (0, utils_1.parseEnvContent)(content);
|
|
48
31
|
expect(result[0].value).toBe('a=b=c=d');
|
|
49
32
|
});
|
|
50
33
|
test('handles Windows CRLF line endings', function () {
|
|
51
34
|
var content = 'FOO=bar\r\nBAZ=qux\r\n';
|
|
52
|
-
var result = parseEnvContent(content);
|
|
35
|
+
var result = (0, utils_1.parseEnvContent)(content);
|
|
53
36
|
expect(result).toHaveLength(2);
|
|
54
37
|
// Values should not have trailing \r
|
|
55
38
|
expect(result[0].value.endsWith('\r')).toBe(false);
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Testchat command tests - URL resolution, API key validation, message handling
|
|
4
|
+
*/
|
|
5
|
+
var __assign = (this && this.__assign) || function () {
|
|
6
|
+
__assign = Object.assign || function(t) {
|
|
7
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
8
|
+
s = arguments[i];
|
|
9
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
10
|
+
t[p] = s[p];
|
|
11
|
+
}
|
|
12
|
+
return t;
|
|
13
|
+
};
|
|
14
|
+
return __assign.apply(this, arguments);
|
|
15
|
+
};
|
|
16
|
+
describe('testchat.ts', function () {
|
|
17
|
+
describe('URL resolution', function () {
|
|
18
|
+
function resolveUrl(optionUrl, envUrl) {
|
|
19
|
+
if (optionUrl)
|
|
20
|
+
return { url: optionUrl, source: '--url option' };
|
|
21
|
+
if (envUrl)
|
|
22
|
+
return { url: envUrl, source: 'TEST_CHAT_URL env' };
|
|
23
|
+
return { url: undefined, source: null };
|
|
24
|
+
}
|
|
25
|
+
test('prioritizes --url option over env', function () {
|
|
26
|
+
var result = resolveUrl('http://cli-url.com', 'http://env-url.com');
|
|
27
|
+
expect(result.url).toBe('http://cli-url.com');
|
|
28
|
+
expect(result.source).toBe('--url option');
|
|
29
|
+
});
|
|
30
|
+
test('falls back to env when no option provided', function () {
|
|
31
|
+
var result = resolveUrl(undefined, 'http://env-url.com');
|
|
32
|
+
expect(result.url).toBe('http://env-url.com');
|
|
33
|
+
expect(result.source).toBe('TEST_CHAT_URL env');
|
|
34
|
+
});
|
|
35
|
+
test('returns undefined when neither provided', function () {
|
|
36
|
+
var result = resolveUrl(undefined, undefined);
|
|
37
|
+
expect(result.url).toBeUndefined();
|
|
38
|
+
expect(result.source).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('API key validation', function () {
|
|
42
|
+
test('requires ANTHROPIC_API_KEY', function () {
|
|
43
|
+
var apiKey = '';
|
|
44
|
+
var hasAnthropicKey = !!apiKey;
|
|
45
|
+
expect(hasAnthropicKey).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
test('requires config api-key', function () {
|
|
48
|
+
var config = { 'api-key': '' };
|
|
49
|
+
var hasApiKey = !!config['api-key'];
|
|
50
|
+
expect(hasApiKey).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
test('passes validation with all keys present', function () {
|
|
53
|
+
var anthropicKey = 'sk-ant-test';
|
|
54
|
+
var configApiKey = 'sk_agent_test';
|
|
55
|
+
var hasAllKeys = !!anthropicKey && !!configApiKey;
|
|
56
|
+
expect(hasAllKeys).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('exit command handling', function () {
|
|
60
|
+
test('recognizes "exit" command (lowercase)', function () {
|
|
61
|
+
var input = 'exit';
|
|
62
|
+
var shouldExit = input.toLowerCase() === 'exit';
|
|
63
|
+
expect(shouldExit).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
test('recognizes "EXIT" command (uppercase)', function () {
|
|
66
|
+
var input = 'EXIT';
|
|
67
|
+
var shouldExit = input.toLowerCase() === 'exit';
|
|
68
|
+
expect(shouldExit).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
test('recognizes "Exit" command (mixed case)', function () {
|
|
71
|
+
var input = 'Exit';
|
|
72
|
+
var shouldExit = input.toLowerCase() === 'exit';
|
|
73
|
+
expect(shouldExit).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
test('does not exit on normal input', function () {
|
|
76
|
+
var input = 'hello';
|
|
77
|
+
var shouldExit = input.toLowerCase() === 'exit';
|
|
78
|
+
expect(shouldExit).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
test('does not exit on input containing "exit"', function () {
|
|
81
|
+
var input = 'how do I exit vim';
|
|
82
|
+
var shouldExit = input.toLowerCase() === 'exit';
|
|
83
|
+
expect(shouldExit).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('message handling', function () {
|
|
87
|
+
test('adds user message to array', function () {
|
|
88
|
+
var messages = [];
|
|
89
|
+
var userInput = 'Hello world';
|
|
90
|
+
messages.push({ role: 'user', content: userInput });
|
|
91
|
+
expect(messages).toHaveLength(1);
|
|
92
|
+
expect(messages[0].role).toBe('user');
|
|
93
|
+
expect(messages[0].content).toBe('Hello world');
|
|
94
|
+
});
|
|
95
|
+
test('maintains message history', function () {
|
|
96
|
+
var messages = [];
|
|
97
|
+
messages.push({ role: 'user', content: 'First message' });
|
|
98
|
+
messages.push({ role: 'assistant', content: 'First response' });
|
|
99
|
+
messages.push({ role: 'user', content: 'Second message' });
|
|
100
|
+
expect(messages).toHaveLength(3);
|
|
101
|
+
expect(messages[0].role).toBe('user');
|
|
102
|
+
expect(messages[1].role).toBe('assistant');
|
|
103
|
+
expect(messages[2].role).toBe('user');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('tool response tracking', function () {
|
|
107
|
+
test('sets toolResponseAvailable when tool calls present', function () {
|
|
108
|
+
var toolCalls = [{ toolName: 'test', args: {} }];
|
|
109
|
+
var toolResponseAvailable = toolCalls.length > 0;
|
|
110
|
+
expect(toolResponseAvailable).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
test('clears toolResponseAvailable when no tool calls', function () {
|
|
113
|
+
var toolCalls = [];
|
|
114
|
+
var toolResponseAvailable = toolCalls.length > 0;
|
|
115
|
+
expect(toolResponseAvailable).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
test('skips user input when tool response available', function () {
|
|
118
|
+
var toolResponseAvailable = true;
|
|
119
|
+
var askedForInput = false;
|
|
120
|
+
// Simulates the chat loop logic
|
|
121
|
+
if (!toolResponseAvailable) {
|
|
122
|
+
askedForInput = true;
|
|
123
|
+
}
|
|
124
|
+
expect(askedForInput).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe('toolbox loading', function () {
|
|
128
|
+
test('filters toolboxes without id', function () {
|
|
129
|
+
var toolboxes = [
|
|
130
|
+
{ id: 'box1', tools: {} },
|
|
131
|
+
{ id: null, tools: {} },
|
|
132
|
+
{ id: 'box2', tools: {} },
|
|
133
|
+
{ id: undefined, tools: {} },
|
|
134
|
+
];
|
|
135
|
+
var validToolboxes = toolboxes.filter(function (tb) { return !!tb.id; });
|
|
136
|
+
expect(validToolboxes).toHaveLength(2);
|
|
137
|
+
expect(validToolboxes[0].id).toBe('box1');
|
|
138
|
+
expect(validToolboxes[1].id).toBe('box2');
|
|
139
|
+
});
|
|
140
|
+
test('merges tools from multiple toolboxes', function () {
|
|
141
|
+
var allTools = {};
|
|
142
|
+
var toolbox1 = { tool1: { name: 'Tool 1' } };
|
|
143
|
+
var toolbox2 = { tool2: { name: 'Tool 2' } };
|
|
144
|
+
allTools = __assign(__assign({}, allTools), toolbox1);
|
|
145
|
+
allTools = __assign(__assign({}, allTools), toolbox2);
|
|
146
|
+
expect(Object.keys(allTools)).toEqual(['tool1', 'tool2']);
|
|
147
|
+
});
|
|
148
|
+
test('later toolbox overwrites duplicate tool names', function () {
|
|
149
|
+
var allTools = {};
|
|
150
|
+
var toolbox1 = { duplicate: { name: 'Original' } };
|
|
151
|
+
var toolbox2 = { duplicate: { name: 'Overwritten' } };
|
|
152
|
+
allTools = __assign(__assign({}, allTools), toolbox1);
|
|
153
|
+
allTools = __assign(__assign({}, allTools), toolbox2);
|
|
154
|
+
expect(allTools.duplicate.name).toBe('Overwritten');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('tool output formatting', function () {
|
|
158
|
+
test('formats tool call output', function () {
|
|
159
|
+
var toolName = 'searchWeb';
|
|
160
|
+
var args = { query: 'test' };
|
|
161
|
+
var output = "Tool call: '".concat(toolName, "' ").concat(JSON.stringify(args));
|
|
162
|
+
expect(output).toBe("Tool call: 'searchWeb' {\"query\":\"test\"}");
|
|
163
|
+
});
|
|
164
|
+
test('formats tool response output', function () {
|
|
165
|
+
var toolName = 'searchWeb';
|
|
166
|
+
var result = { results: ['a', 'b'] };
|
|
167
|
+
var output = "Tool response: '".concat(toolName, "' ").concat(JSON.stringify(result));
|
|
168
|
+
expect(output).toBe("Tool response: 'searchWeb' {\"results\":[\"a\",\"b\"]}");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('max steps configuration', function () {
|
|
172
|
+
test('default max steps is 5', function () {
|
|
173
|
+
var maxSteps = 5;
|
|
174
|
+
expect(maxSteps).toBe(5);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe('error handling', function () {
|
|
178
|
+
test('continues chat loop on error', function () {
|
|
179
|
+
var loopContinued = false;
|
|
180
|
+
var errorCaught = false;
|
|
181
|
+
try {
|
|
182
|
+
throw new Error('API Error');
|
|
183
|
+
}
|
|
184
|
+
catch (_a) {
|
|
185
|
+
errorCaught = true;
|
|
186
|
+
// Simulates continuing the loop after error
|
|
187
|
+
loopContinued = true;
|
|
188
|
+
}
|
|
189
|
+
expect(errorCaught).toBe(true);
|
|
190
|
+
expect(loopContinued).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
describe('DainServiceConnection setup', function () {
|
|
195
|
+
test('constructs with url and auth', function () {
|
|
196
|
+
var url = 'https://service.example.com';
|
|
197
|
+
var apiKey = 'sk_agent_test';
|
|
198
|
+
var connection = {
|
|
199
|
+
url: url,
|
|
200
|
+
auth: { apiKey: apiKey },
|
|
201
|
+
};
|
|
202
|
+
expect(connection.url).toBe(url);
|
|
203
|
+
expect(connection.auth.apiKey).toBe(apiKey);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe('system prompt', function () {
|
|
207
|
+
test('includes required instructions', function () {
|
|
208
|
+
var systemPrompt = "Keep your responses brief but insightful.\n use lots of emojis, to show your enthusiasm!\n Your name is Butterfly, Built by the team at Dain.";
|
|
209
|
+
expect(systemPrompt).toContain('brief');
|
|
210
|
+
expect(systemPrompt).toContain('emojis');
|
|
211
|
+
expect(systemPrompt).toContain('Butterfly');
|
|
212
|
+
expect(systemPrompt).toContain('Dain');
|
|
213
|
+
});
|
|
214
|
+
});
|