@mdrv/opencode-quota 262.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.
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/bin/copilot-quota.ts +374 -0
- package/bin/glm-quota.ts +467 -0
- package/bin/install.js +439 -0
- package/bin/kimi-quota.ts +314 -0
- package/dist/bin/copilot-quota.d.ts +8 -0
- package/dist/bin/copilot-quota.d.ts.map +1 -0
- package/dist/bin/copilot-quota.js +298 -0
- package/dist/bin/copilot-quota.js.map +1 -0
- package/dist/bin/glm-quota.d.ts +8 -0
- package/dist/bin/glm-quota.d.ts.map +1 -0
- package/dist/bin/glm-quota.js +367 -0
- package/dist/bin/glm-quota.js.map +1 -0
- package/dist/bin/kimi-quota.d.ts +3 -0
- package/dist/bin/kimi-quota.d.ts.map +1 -0
- package/dist/bin/kimi-quota.js +241 -0
- package/dist/bin/kimi-quota.js.map +1 -0
- package/dist/src/api/client.d.ts +76 -0
- package/dist/src/api/client.d.ts.map +1 -0
- package/dist/src/api/client.js +203 -0
- package/dist/src/api/client.js.map +1 -0
- package/dist/src/api/endpoints.d.ts +22 -0
- package/dist/src/api/endpoints.d.ts.map +1 -0
- package/dist/src/api/endpoints.js +41 -0
- package/dist/src/api/endpoints.js.map +1 -0
- package/dist/src/api/platforms.d.ts +20 -0
- package/dist/src/api/platforms.d.ts.map +1 -0
- package/dist/src/api/platforms.js +38 -0
- package/dist/src/api/platforms.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +723 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/shared/logging.d.ts +7 -0
- package/dist/src/shared/logging.d.ts.map +1 -0
- package/dist/src/shared/logging.js +29 -0
- package/dist/src/shared/logging.js.map +1 -0
- package/dist/src/utils/box-constants.d.ts +43 -0
- package/dist/src/utils/box-constants.d.ts.map +1 -0
- package/dist/src/utils/box-constants.js +43 -0
- package/dist/src/utils/box-constants.js.map +1 -0
- package/dist/src/utils/date-formatter.d.ts +17 -0
- package/dist/src/utils/date-formatter.d.ts.map +1 -0
- package/dist/src/utils/date-formatter.js +33 -0
- package/dist/src/utils/date-formatter.js.map +1 -0
- package/dist/src/utils/error-formatter.d.ts +17 -0
- package/dist/src/utils/error-formatter.d.ts.map +1 -0
- package/dist/src/utils/error-formatter.js +60 -0
- package/dist/src/utils/error-formatter.js.map +1 -0
- package/dist/src/utils/progress-bar.d.ts +35 -0
- package/dist/src/utils/progress-bar.d.ts.map +1 -0
- package/dist/src/utils/progress-bar.js +43 -0
- package/dist/src/utils/progress-bar.js.map +1 -0
- package/dist/src/utils/reset-timer.d.ts +11 -0
- package/dist/src/utils/reset-timer.d.ts.map +1 -0
- package/dist/src/utils/reset-timer.js +32 -0
- package/dist/src/utils/reset-timer.js.map +1 -0
- package/dist/src/utils/time-window.d.ts +30 -0
- package/dist/src/utils/time-window.d.ts.map +1 -0
- package/dist/src/utils/time-window.js +34 -0
- package/dist/src/utils/time-window.js.map +1 -0
- package/dist/tests/error-handling/api-errors.test.d.ts +7 -0
- package/dist/tests/error-handling/api-errors.test.d.ts.map +1 -0
- package/dist/tests/error-handling/api-errors.test.js +110 -0
- package/dist/tests/error-handling/api-errors.test.js.map +1 -0
- package/dist/tests/error-handling/auth-errors.test.d.ts +7 -0
- package/dist/tests/error-handling/auth-errors.test.d.ts.map +1 -0
- package/dist/tests/error-handling/auth-errors.test.js +110 -0
- package/dist/tests/error-handling/auth-errors.test.js.map +1 -0
- package/dist/tests/error-handling/network-errors.test.d.ts +7 -0
- package/dist/tests/error-handling/network-errors.test.d.ts.map +1 -0
- package/dist/tests/error-handling/network-errors.test.js +94 -0
- package/dist/tests/error-handling/network-errors.test.js.map +1 -0
- package/dist/tests/error-handling/parse-errors.test.d.ts +7 -0
- package/dist/tests/error-handling/parse-errors.test.d.ts.map +1 -0
- package/dist/tests/error-handling/parse-errors.test.js +87 -0
- package/dist/tests/error-handling/parse-errors.test.js.map +1 -0
- package/dist/tests/error-handling/token-sanitization.test.d.ts +2 -0
- package/dist/tests/error-handling/token-sanitization.test.d.ts.map +1 -0
- package/dist/tests/error-handling/token-sanitization.test.js +59 -0
- package/dist/tests/error-handling/token-sanitization.test.js.map +1 -0
- package/dist/tests/functional/date-formatter.test.d.ts +5 -0
- package/dist/tests/functional/date-formatter.test.d.ts.map +1 -0
- package/dist/tests/functional/date-formatter.test.js +46 -0
- package/dist/tests/functional/date-formatter.test.js.map +1 -0
- package/dist/tests/functional/progress-bar.test.d.ts +5 -0
- package/dist/tests/functional/progress-bar.test.d.ts.map +1 -0
- package/dist/tests/functional/progress-bar.test.js +82 -0
- package/dist/tests/functional/progress-bar.test.js.map +1 -0
- package/dist/tests/functional/reset-timer.test.d.ts +6 -0
- package/dist/tests/functional/reset-timer.test.d.ts.map +1 -0
- package/dist/tests/functional/reset-timer.test.js +67 -0
- package/dist/tests/functional/reset-timer.test.js.map +1 -0
- package/dist/tests/functional/time-window.test.d.ts +5 -0
- package/dist/tests/functional/time-window.test.d.ts.map +1 -0
- package/dist/tests/functional/time-window.test.js +46 -0
- package/dist/tests/functional/time-window.test.js.map +1 -0
- package/dist/tests/integration/box-alignment.test.d.ts +8 -0
- package/dist/tests/integration/box-alignment.test.d.ts.map +1 -0
- package/dist/tests/integration/box-alignment.test.js +238 -0
- package/dist/tests/integration/box-alignment.test.js.map +1 -0
- package/dist/tests/integration/error-handling.test.d.ts +2 -0
- package/dist/tests/integration/error-handling.test.d.ts.map +1 -0
- package/dist/tests/integration/error-handling.test.js +36 -0
- package/dist/tests/integration/error-handling.test.js.map +1 -0
- package/dist/tests/integration/installer-config.test.d.ts +2 -0
- package/dist/tests/integration/installer-config.test.d.ts.map +1 -0
- package/dist/tests/integration/installer-config.test.js +65 -0
- package/dist/tests/integration/installer-config.test.js.map +1 -0
- package/dist/tests/integration/plugin-catch-block.test.d.ts +2 -0
- package/dist/tests/integration/plugin-catch-block.test.d.ts.map +1 -0
- package/dist/tests/integration/plugin-catch-block.test.js +134 -0
- package/dist/tests/integration/plugin-catch-block.test.js.map +1 -0
- package/dist/tests/integration/reset-time-display.test.d.ts +6 -0
- package/dist/tests/integration/reset-time-display.test.d.ts.map +1 -0
- package/dist/tests/integration/reset-time-display.test.js +138 -0
- package/dist/tests/integration/reset-time-display.test.js.map +1 -0
- package/dist/tests/module/http-client.test.d.ts +2 -0
- package/dist/tests/module/http-client.test.d.ts.map +1 -0
- package/dist/tests/module/http-client.test.js +49 -0
- package/dist/tests/module/http-client.test.js.map +1 -0
- package/dist/tests/module/platform-detection.test.d.ts +5 -0
- package/dist/tests/module/platform-detection.test.d.ts.map +1 -0
- package/dist/tests/module/platform-detection.test.js +48 -0
- package/dist/tests/module/platform-detection.test.js.map +1 -0
- package/integration/agents/copilot-quota-exec.md +20 -0
- package/integration/agents/glm-quota-exec.md +20 -0
- package/integration/command/copilot_quota.md +6 -0
- package/integration/command/glm_quota.md +6 -0
- package/integration/skills/copilot-quota/SKILL.md +11 -0
- package/integration/skills/glm-quota/SKILL.md +11 -0
- package/package.json +69 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Error Handling Tests
|
|
3
|
+
* Tests for HTTP 429 rate limiting and 500+ server errors
|
|
4
|
+
* Following TDD methodology with AAA pattern
|
|
5
|
+
*/
|
|
6
|
+
import * as assert from 'node:assert';
|
|
7
|
+
import { describe, it } from 'node:test';
|
|
8
|
+
import { formatApiError } from '../../src/api/client.js';
|
|
9
|
+
import { BOX_WIDTH } from '../../src/utils/box-constants.js';
|
|
10
|
+
describe('API Error Handling', () => {
|
|
11
|
+
describe('formatApiError - 429 Rate Limit', () => {
|
|
12
|
+
it('should format 429 error with boxed message', () => {
|
|
13
|
+
// Arrange
|
|
14
|
+
const statusCode = 429;
|
|
15
|
+
const responseBody = 'Too Many Requests';
|
|
16
|
+
const authToken = 'test-token';
|
|
17
|
+
// Act
|
|
18
|
+
const formatted = formatApiError(statusCode, responseBody, authToken);
|
|
19
|
+
// Assert
|
|
20
|
+
assert.ok(formatted.message.includes('Too many requests'));
|
|
21
|
+
assert.ok(formatted.message.includes('╔')); // Has box drawing chars
|
|
22
|
+
assert.ok(formatted.message.includes('║'));
|
|
23
|
+
assert.ok(formatted.message.includes('╚'));
|
|
24
|
+
});
|
|
25
|
+
it('should include helpful message for 429 errors', () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
const statusCode = 429;
|
|
28
|
+
const responseBody = 'Rate limit exceeded';
|
|
29
|
+
const authToken = 'test-token';
|
|
30
|
+
// Act
|
|
31
|
+
const formatted = formatApiError(statusCode, responseBody, authToken);
|
|
32
|
+
// Assert
|
|
33
|
+
assert.ok(formatted.message.includes('Too many requests'));
|
|
34
|
+
assert.ok(formatted.message.includes('Please try again later'));
|
|
35
|
+
});
|
|
36
|
+
it('should sanitize token from 429 error messages', () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
const authToken = 'sk-secret-789';
|
|
39
|
+
const statusCode = 429;
|
|
40
|
+
const responseBody = `Rate limit exceeded for token ${authToken}`;
|
|
41
|
+
// Act
|
|
42
|
+
const formatted = formatApiError(statusCode, responseBody, authToken);
|
|
43
|
+
// Assert
|
|
44
|
+
assert.ok(!formatted.message.includes(authToken));
|
|
45
|
+
assert.ok(formatted.message.includes('***'));
|
|
46
|
+
});
|
|
47
|
+
it('should create 60-character wide boxed error for 429', () => {
|
|
48
|
+
// Arrange
|
|
49
|
+
const statusCode = 429;
|
|
50
|
+
const responseBody = 'Rate limit exceeded';
|
|
51
|
+
const authToken = 'test-token';
|
|
52
|
+
// Act
|
|
53
|
+
const formatted = formatApiError(statusCode, responseBody, authToken);
|
|
54
|
+
// Assert
|
|
55
|
+
const lines = formatted.message.split('\n');
|
|
56
|
+
assert.strictEqual(lines[0].length, BOX_WIDTH.TOTAL); // Top border
|
|
57
|
+
assert.strictEqual(lines[lines.length - 1].length, BOX_WIDTH.TOTAL); // Bottom border
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('formatApiError - 500+ Server Errors', () => {
|
|
61
|
+
it('should format 500 error with boxed message', () => {
|
|
62
|
+
// Arrange
|
|
63
|
+
const statusCode = 500;
|
|
64
|
+
const responseBody = 'Internal Server Error';
|
|
65
|
+
const authToken = 'test-token';
|
|
66
|
+
// Act
|
|
67
|
+
const formatted = formatApiError(statusCode, responseBody, authToken);
|
|
68
|
+
// Assert
|
|
69
|
+
assert.ok(formatted.message.includes('Server error'));
|
|
70
|
+
assert.ok(formatted.message.includes('╔')); // Has box drawing chars
|
|
71
|
+
assert.ok(formatted.message.includes('║'));
|
|
72
|
+
assert.ok(formatted.message.includes('╚'));
|
|
73
|
+
});
|
|
74
|
+
it('should include helpful message for 500+ errors', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const statusCode = 503;
|
|
77
|
+
const responseBody = 'Service Unavailable';
|
|
78
|
+
const authToken = 'test-token';
|
|
79
|
+
// Act
|
|
80
|
+
const formatted = formatApiError(statusCode, responseBody, authToken);
|
|
81
|
+
// Assert
|
|
82
|
+
assert.ok(formatted.message.includes('Server error'));
|
|
83
|
+
assert.ok(formatted.message.includes('Please try again later'));
|
|
84
|
+
});
|
|
85
|
+
it('should sanitize token from 500+ error messages', () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const authToken = 'sk-secret-999';
|
|
88
|
+
const statusCode = 502;
|
|
89
|
+
const responseBody = `Bad Gateway: authentication with token ${authToken} failed`;
|
|
90
|
+
// Act
|
|
91
|
+
const formatted = formatApiError(statusCode, responseBody, authToken);
|
|
92
|
+
// Assert
|
|
93
|
+
assert.ok(!formatted.message.includes(authToken));
|
|
94
|
+
assert.ok(formatted.message.includes('***'));
|
|
95
|
+
});
|
|
96
|
+
it('should create 60-character wide boxed error for 500+', () => {
|
|
97
|
+
// Arrange
|
|
98
|
+
const statusCode = 500;
|
|
99
|
+
const responseBody = 'Internal Server Error';
|
|
100
|
+
const authToken = 'test-token';
|
|
101
|
+
// Act
|
|
102
|
+
const formatted = formatApiError(statusCode, responseBody, authToken);
|
|
103
|
+
// Assert
|
|
104
|
+
const lines = formatted.message.split('\n');
|
|
105
|
+
assert.strictEqual(lines[0].length, BOX_WIDTH.TOTAL); // Top border
|
|
106
|
+
assert.strictEqual(lines[lines.length - 1].length, BOX_WIDTH.TOTAL); // Bottom border
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=api-errors.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-errors.test.js","sourceRoot":"","sources":["../../../tests/error-handling/api-errors.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-errors.test.d.ts","sourceRoot":"","sources":["../../../tests/error-handling/auth-errors.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Error Handling Tests
|
|
3
|
+
* Tests for HTTP 401/403 authentication errors
|
|
4
|
+
* Following TDD methodology with AAA pattern
|
|
5
|
+
*/
|
|
6
|
+
import * as assert from 'node:assert';
|
|
7
|
+
import { describe, it } from 'node:test';
|
|
8
|
+
import { formatAuthError } from '../../src/api/client.js';
|
|
9
|
+
import { BOX_WIDTH } from '../../src/utils/box-constants.js';
|
|
10
|
+
describe('Authentication Error Handling', () => {
|
|
11
|
+
describe('formatAuthError - 401 Unauthorized', () => {
|
|
12
|
+
it('should format 401 error with boxed message', () => {
|
|
13
|
+
// Arrange
|
|
14
|
+
const statusCode = 401;
|
|
15
|
+
const responseBody = 'Unauthorized';
|
|
16
|
+
const authToken = 'test-token';
|
|
17
|
+
// Act
|
|
18
|
+
const formatted = formatAuthError(statusCode, responseBody, authToken);
|
|
19
|
+
// Assert
|
|
20
|
+
assert.ok(formatted.message.includes('Authentication failed'));
|
|
21
|
+
assert.ok(formatted.message.includes('╔')); // Has box drawing chars
|
|
22
|
+
assert.ok(formatted.message.includes('║'));
|
|
23
|
+
assert.ok(formatted.message.includes('╚'));
|
|
24
|
+
});
|
|
25
|
+
it('should include helpful message for 401 errors', () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
const statusCode = 401;
|
|
28
|
+
const responseBody = 'Unauthorized';
|
|
29
|
+
const authToken = 'test-token';
|
|
30
|
+
// Act
|
|
31
|
+
const formatted = formatAuthError(statusCode, responseBody, authToken);
|
|
32
|
+
// Assert
|
|
33
|
+
assert.ok(formatted.message.includes('Authentication failed'));
|
|
34
|
+
assert.ok(formatted.message.includes('Please check your credentials'));
|
|
35
|
+
});
|
|
36
|
+
it('should sanitize token from 401 error messages', () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
const authToken = 'sk-secret-123';
|
|
39
|
+
const statusCode = 401;
|
|
40
|
+
const responseBody = `Unauthorized: Invalid token ${authToken}`;
|
|
41
|
+
// Act
|
|
42
|
+
const formatted = formatAuthError(statusCode, responseBody, authToken);
|
|
43
|
+
// Assert
|
|
44
|
+
assert.ok(!formatted.message.includes(authToken));
|
|
45
|
+
assert.ok(formatted.message.includes('***'));
|
|
46
|
+
});
|
|
47
|
+
it('should create 60-character wide boxed error for 401', () => {
|
|
48
|
+
// Arrange
|
|
49
|
+
const statusCode = 401;
|
|
50
|
+
const responseBody = 'Unauthorized';
|
|
51
|
+
const authToken = 'test-token';
|
|
52
|
+
// Act
|
|
53
|
+
const formatted = formatAuthError(statusCode, responseBody, authToken);
|
|
54
|
+
// Assert
|
|
55
|
+
const lines = formatted.message.split('\n');
|
|
56
|
+
assert.strictEqual(lines[0].length, BOX_WIDTH.TOTAL); // Top border
|
|
57
|
+
assert.strictEqual(lines[lines.length - 1].length, BOX_WIDTH.TOTAL); // Bottom border
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('formatAuthError - 403 Forbidden', () => {
|
|
61
|
+
it('should format 403 error with boxed message', () => {
|
|
62
|
+
// Arrange
|
|
63
|
+
const statusCode = 403;
|
|
64
|
+
const responseBody = 'Forbidden';
|
|
65
|
+
const authToken = 'test-token';
|
|
66
|
+
// Act
|
|
67
|
+
const formatted = formatAuthError(statusCode, responseBody, authToken);
|
|
68
|
+
// Assert
|
|
69
|
+
assert.ok(formatted.message.includes('Access denied'));
|
|
70
|
+
assert.ok(formatted.message.includes('╔')); // Has box drawing chars
|
|
71
|
+
assert.ok(formatted.message.includes('║'));
|
|
72
|
+
assert.ok(formatted.message.includes('╚'));
|
|
73
|
+
});
|
|
74
|
+
it('should include helpful message for 403 errors', () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const statusCode = 403;
|
|
77
|
+
const responseBody = 'Forbidden';
|
|
78
|
+
const authToken = 'test-token';
|
|
79
|
+
// Act
|
|
80
|
+
const formatted = formatAuthError(statusCode, responseBody, authToken);
|
|
81
|
+
// Assert
|
|
82
|
+
assert.ok(formatted.message.includes('Access denied'));
|
|
83
|
+
assert.ok(formatted.message.includes("You don't have permission"));
|
|
84
|
+
});
|
|
85
|
+
it('should sanitize token from 403 error messages', () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const authToken = 'sk-secret-456';
|
|
88
|
+
const statusCode = 403;
|
|
89
|
+
const responseBody = `Forbidden: Token ${authToken} lacks required permissions`;
|
|
90
|
+
// Act
|
|
91
|
+
const formatted = formatAuthError(statusCode, responseBody, authToken);
|
|
92
|
+
// Assert
|
|
93
|
+
assert.ok(!formatted.message.includes(authToken));
|
|
94
|
+
assert.ok(formatted.message.includes('***'));
|
|
95
|
+
});
|
|
96
|
+
it('should create 60-character wide boxed error for 403', () => {
|
|
97
|
+
// Arrange
|
|
98
|
+
const statusCode = 403;
|
|
99
|
+
const responseBody = 'Forbidden';
|
|
100
|
+
const authToken = 'test-token';
|
|
101
|
+
// Act
|
|
102
|
+
const formatted = formatAuthError(statusCode, responseBody, authToken);
|
|
103
|
+
// Assert
|
|
104
|
+
const lines = formatted.message.split('\n');
|
|
105
|
+
assert.strictEqual(lines[0].length, BOX_WIDTH.TOTAL); // Top border
|
|
106
|
+
assert.strictEqual(lines[lines.length - 1].length, BOX_WIDTH.TOTAL); // Bottom border
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=auth-errors.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-errors.test.js","sourceRoot":"","sources":["../../../tests/error-handling/auth-errors.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-errors.test.d.ts","sourceRoot":"","sources":["../../../tests/error-handling/network-errors.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network Error Handling Tests
|
|
3
|
+
* Tests for network-related errors (timeout, connection refused, etc.)
|
|
4
|
+
* Following TDD methodology with AAA pattern
|
|
5
|
+
*/
|
|
6
|
+
import * as assert from 'node:assert';
|
|
7
|
+
import { describe, it } from 'node:test';
|
|
8
|
+
import { createBoxedError, formatNetworkError } from '../../src/api/client.js';
|
|
9
|
+
import { BOX_WIDTH } from '../../src/utils/box-constants.js';
|
|
10
|
+
describe('Network Error Handling', () => {
|
|
11
|
+
describe('formatNetworkError', () => {
|
|
12
|
+
it('should format ETIMEDOUT error with boxed message', () => {
|
|
13
|
+
// Arrange
|
|
14
|
+
const mockError = Object.assign(new Error('Request timeout'), {
|
|
15
|
+
code: 'ETIMEDOUT',
|
|
16
|
+
});
|
|
17
|
+
const authToken = 'test-token';
|
|
18
|
+
// Act
|
|
19
|
+
const formatted = formatNetworkError(mockError, authToken);
|
|
20
|
+
// Assert
|
|
21
|
+
assert.ok(formatted.message.includes('Request timed out'));
|
|
22
|
+
assert.strictEqual(formatted.code, 'ETIMEDOUT');
|
|
23
|
+
});
|
|
24
|
+
it('should format ECONNREFUSED error with boxed message', () => {
|
|
25
|
+
// Arrange
|
|
26
|
+
const mockError = Object.assign(new Error('Connection refused'), {
|
|
27
|
+
code: 'ECONNREFUSED',
|
|
28
|
+
});
|
|
29
|
+
const authToken = 'test-token';
|
|
30
|
+
// Act
|
|
31
|
+
const formatted = formatNetworkError(mockError, authToken);
|
|
32
|
+
// Assert
|
|
33
|
+
assert.ok(formatted.message.includes('Unable to connect to server'));
|
|
34
|
+
assert.strictEqual(formatted.code, 'ECONNREFUSED');
|
|
35
|
+
});
|
|
36
|
+
it('should sanitize token for other error types', () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
const authToken = 'sk-secret-123';
|
|
39
|
+
const mockError = Object.assign(new Error(`Error with token ${authToken}`), {
|
|
40
|
+
code: 'ENOTFOUND',
|
|
41
|
+
});
|
|
42
|
+
// Act
|
|
43
|
+
const formatted = formatNetworkError(mockError, authToken);
|
|
44
|
+
// Assert
|
|
45
|
+
assert.ok(!formatted.message.includes(authToken));
|
|
46
|
+
assert.ok(formatted.message.includes('***'));
|
|
47
|
+
});
|
|
48
|
+
it('should preserve error code in formatted errors', () => {
|
|
49
|
+
// Arrange
|
|
50
|
+
const mockError = Object.assign(new Error('Some error'), {
|
|
51
|
+
code: 'ETIMEDOUT',
|
|
52
|
+
});
|
|
53
|
+
// Act
|
|
54
|
+
const formatted = formatNetworkError(mockError, 'token');
|
|
55
|
+
// Assert
|
|
56
|
+
assert.strictEqual(formatted.code, 'ETIMEDOUT');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('createBoxedError', () => {
|
|
60
|
+
it('should create boxed error with 60-character width', () => {
|
|
61
|
+
// Arrange
|
|
62
|
+
const message = 'Request timed out. Please try again.';
|
|
63
|
+
// Act
|
|
64
|
+
const boxed = createBoxedError(message);
|
|
65
|
+
// Assert
|
|
66
|
+
const lines = boxed.split('\n');
|
|
67
|
+
assert.strictEqual(lines[0].length, BOX_WIDTH.TOTAL); // Top border
|
|
68
|
+
assert.strictEqual(lines[lines.length - 1].length, BOX_WIDTH.TOTAL); // Bottom border
|
|
69
|
+
});
|
|
70
|
+
it('should include box drawing characters', () => {
|
|
71
|
+
// Arrange
|
|
72
|
+
const message = 'Test message';
|
|
73
|
+
// Act
|
|
74
|
+
const boxed = createBoxedError(message);
|
|
75
|
+
// Assert
|
|
76
|
+
assert.ok(boxed.includes('╔'));
|
|
77
|
+
assert.ok(boxed.includes('║'));
|
|
78
|
+
assert.ok(boxed.includes('╚'));
|
|
79
|
+
});
|
|
80
|
+
it('should wrap long messages across multiple lines', () => {
|
|
81
|
+
// Arrange
|
|
82
|
+
const message = 'This is a very long error message that should wrap across multiple lines in the boxed format';
|
|
83
|
+
// Act
|
|
84
|
+
const boxed = createBoxedError(message);
|
|
85
|
+
// Assert
|
|
86
|
+
const lines = boxed.split('\n');
|
|
87
|
+
assert.ok(lines.length > 3); // Top + content lines + bottom
|
|
88
|
+
lines.forEach(line => {
|
|
89
|
+
assert.strictEqual(line.length, BOX_WIDTH.TOTAL);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=network-errors.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-errors.test.js","sourceRoot":"","sources":["../../../tests/error-handling/network-errors.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-errors.test.d.ts","sourceRoot":"","sources":["../../../tests/error-handling/parse-errors.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Parse Error Handling Tests
|
|
3
|
+
* Tests for invalid JSON responses and malformed content
|
|
4
|
+
* Following TDD methodology with AAA pattern
|
|
5
|
+
*/
|
|
6
|
+
import * as assert from 'node:assert';
|
|
7
|
+
import { describe, it } from 'node:test';
|
|
8
|
+
import { BOX_WIDTH } from '../../src/utils/box-constants.js';
|
|
9
|
+
// Import the parse error formatting function (will be created in client.ts)
|
|
10
|
+
import { formatParseError } from '../../src/api/client.js';
|
|
11
|
+
describe('JSON Parse Error Handling', () => {
|
|
12
|
+
describe('formatParseError', () => {
|
|
13
|
+
it('should format parse error with boxed message', () => {
|
|
14
|
+
// Arrange
|
|
15
|
+
const responseBody = 'This is not JSON';
|
|
16
|
+
const authToken = 'test-token';
|
|
17
|
+
// Act
|
|
18
|
+
const formatted = formatParseError(responseBody, authToken);
|
|
19
|
+
// Assert
|
|
20
|
+
assert.ok(formatted.message.includes('Invalid JSON'), 'Should mention invalid JSON');
|
|
21
|
+
assert.ok(formatted.message.includes('╔'), 'Should have top border');
|
|
22
|
+
assert.ok(formatted.message.includes('║'), 'Should have side borders');
|
|
23
|
+
assert.ok(formatted.message.includes('╚'), 'Should have bottom border');
|
|
24
|
+
});
|
|
25
|
+
it('should create 60-character wide boxed error', () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
const responseBody = '<html>Not JSON</html>';
|
|
28
|
+
const authToken = 'test-token';
|
|
29
|
+
// Act
|
|
30
|
+
const formatted = formatParseError(responseBody, authToken);
|
|
31
|
+
// Assert
|
|
32
|
+
const lines = formatted.message.split('\n');
|
|
33
|
+
assert.strictEqual(lines[0].length, BOX_WIDTH.TOTAL, 'Top border should be 60 chars');
|
|
34
|
+
assert.strictEqual(lines[lines.length - 1].length, BOX_WIDTH.TOTAL, 'Bottom border should be 60 chars');
|
|
35
|
+
// All lines should be exactly 60 characters
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
assert.strictEqual(line.length, BOX_WIDTH.TOTAL, 'All lines should be 60 chars');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
it('should sanitize token from parse error response body', () => {
|
|
41
|
+
// Arrange
|
|
42
|
+
const authToken = 'sk-secret-parse-123';
|
|
43
|
+
const responseBody = `Invalid JSON with token ${authToken} exposed`;
|
|
44
|
+
// Act
|
|
45
|
+
const formatted = formatParseError(responseBody, authToken);
|
|
46
|
+
// Assert
|
|
47
|
+
assert.ok(!formatted.message.includes(authToken), 'Should not contain raw token');
|
|
48
|
+
assert.ok(formatted.message.includes('***'), 'Should contain sanitization marker');
|
|
49
|
+
});
|
|
50
|
+
it('should truncate very long invalid response bodies', () => {
|
|
51
|
+
// Arrange
|
|
52
|
+
const longResponse = 'A'.repeat(500) + ' not JSON';
|
|
53
|
+
const authToken = 'test-token';
|
|
54
|
+
// Act
|
|
55
|
+
const formatted = formatParseError(longResponse, authToken);
|
|
56
|
+
// Assert
|
|
57
|
+
const lines = formatted.message.split('\n');
|
|
58
|
+
// All lines should be exactly 60 characters
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
assert.strictEqual(line.length, BOX_WIDTH.TOTAL, 'All lines should be 60 chars');
|
|
61
|
+
}
|
|
62
|
+
// Should mention invalid JSON
|
|
63
|
+
assert.ok(formatted.message.includes('Invalid JSON'), 'Should mention invalid JSON');
|
|
64
|
+
});
|
|
65
|
+
it('should handle empty response body', () => {
|
|
66
|
+
// Arrange
|
|
67
|
+
const responseBody = '';
|
|
68
|
+
const authToken = 'test-token';
|
|
69
|
+
// Act
|
|
70
|
+
const formatted = formatParseError(responseBody, authToken);
|
|
71
|
+
// Assert
|
|
72
|
+
assert.ok(formatted.message.includes('Invalid JSON'), 'Should mention invalid JSON');
|
|
73
|
+
assert.ok(formatted.message.includes('╔'), 'Should be boxed');
|
|
74
|
+
});
|
|
75
|
+
it('should handle malformed JSON with syntax errors', () => {
|
|
76
|
+
// Arrange
|
|
77
|
+
const responseBody = '{"incomplete": "json"';
|
|
78
|
+
const authToken = 'test-token';
|
|
79
|
+
// Act
|
|
80
|
+
const formatted = formatParseError(responseBody, authToken);
|
|
81
|
+
// Assert
|
|
82
|
+
assert.ok(formatted.message.includes('Invalid JSON'), 'Should mention invalid JSON');
|
|
83
|
+
assert.ok(formatted.message.includes('╔'), 'Should be boxed');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=parse-errors.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-errors.test.js","sourceRoot":"","sources":["../../../tests/error-handling/parse-errors.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-sanitization.test.d.ts","sourceRoot":"","sources":["../../../tests/error-handling/token-sanitization.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as assert from 'node:assert';
|
|
2
|
+
import { describe, test } from 'node:test';
|
|
3
|
+
import { sanitizeToken } from '../../src/utils/error-formatter.js';
|
|
4
|
+
describe('sanitizeToken', () => {
|
|
5
|
+
test('replaces token with *** when token appears in message', () => {
|
|
6
|
+
// Arrange
|
|
7
|
+
const message = 'Authentication failed with token abc123def456';
|
|
8
|
+
const token = 'abc123def456';
|
|
9
|
+
// Act
|
|
10
|
+
const result = sanitizeToken(message, token);
|
|
11
|
+
// Assert
|
|
12
|
+
assert.strictEqual(result, 'Authentication failed with token ***');
|
|
13
|
+
});
|
|
14
|
+
test('returns original message when token is undefined', () => {
|
|
15
|
+
// Arrange
|
|
16
|
+
const message = 'Authentication failed';
|
|
17
|
+
// Act
|
|
18
|
+
const result = sanitizeToken(message, undefined);
|
|
19
|
+
// Assert
|
|
20
|
+
assert.strictEqual(result, 'Authentication failed');
|
|
21
|
+
});
|
|
22
|
+
test('returns original message when token is not in message', () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
const message = 'Authentication failed';
|
|
25
|
+
const token = 'abc123def456';
|
|
26
|
+
// Act
|
|
27
|
+
const result = sanitizeToken(message, token);
|
|
28
|
+
// Assert
|
|
29
|
+
assert.strictEqual(result, 'Authentication failed');
|
|
30
|
+
});
|
|
31
|
+
test('replaces all occurrences of token in message', () => {
|
|
32
|
+
// Arrange
|
|
33
|
+
const message = 'Token abc123 used in header. Token abc123 expired.';
|
|
34
|
+
const token = 'abc123';
|
|
35
|
+
// Act
|
|
36
|
+
const result = sanitizeToken(message, token);
|
|
37
|
+
// Assert
|
|
38
|
+
assert.strictEqual(result, 'Token *** used in header. Token *** expired.');
|
|
39
|
+
});
|
|
40
|
+
test('handles empty token gracefully', () => {
|
|
41
|
+
// Arrange
|
|
42
|
+
const message = 'Authentication failed';
|
|
43
|
+
const token = '';
|
|
44
|
+
// Act
|
|
45
|
+
const result = sanitizeToken(message, token);
|
|
46
|
+
// Assert
|
|
47
|
+
assert.strictEqual(result, 'Authentication failed');
|
|
48
|
+
});
|
|
49
|
+
test('is case-sensitive when matching tokens', () => {
|
|
50
|
+
// Arrange
|
|
51
|
+
const message = 'Token ABC123 is valid';
|
|
52
|
+
const token = 'abc123';
|
|
53
|
+
// Act
|
|
54
|
+
const result = sanitizeToken(message, token);
|
|
55
|
+
// Assert
|
|
56
|
+
assert.strictEqual(result, 'Token ABC123 is valid');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=token-sanitization.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-sanitization.test.js","sourceRoot":"","sources":["../../../tests/error-handling/token-sanitization.test.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AAElE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC9B,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAClE,UAAU;QACV,MAAM,OAAO,GAAG,+CAA+C,CAAA;QAC/D,MAAM,KAAK,GAAG,cAAc,CAAA;QAE5B,MAAM;QACN,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAE5C,SAAS;QACT,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,sCAAsC,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC7D,UAAU;QACV,MAAM,OAAO,GAAG,uBAAuB,CAAA;QAEvC,MAAM;QACN,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEhD,SAAS;QACT,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAClE,UAAU;QACV,MAAM,OAAO,GAAG,uBAAuB,CAAA;QACvC,MAAM,KAAK,GAAG,cAAc,CAAA;QAE5B,MAAM;QACN,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAE5C,SAAS;QACT,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACzD,UAAU;QACV,MAAM,OAAO,GAAG,oDAAoD,CAAA;QACpE,MAAM,KAAK,GAAG,QAAQ,CAAA;QAEtB,MAAM;QACN,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAE5C,SAAS;QACT,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,8CAA8C,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;QAC3C,UAAU;QACV,MAAM,OAAO,GAAG,uBAAuB,CAAA;QACvC,MAAM,KAAK,GAAG,EAAE,CAAA;QAEhB,MAAM;QACN,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAE5C,SAAS;QACT,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACnD,UAAU;QACV,MAAM,OAAO,GAAG,uBAAuB,CAAA;QACvC,MAAM,KAAK,GAAG,QAAQ,CAAA;QAEtB,MAAM;QACN,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAE5C,SAAS;QACT,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-formatter.test.d.ts","sourceRoot":"","sources":["../../../tests/functional/date-formatter.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functional tests for date formatter module
|
|
3
|
+
*/
|
|
4
|
+
import assert from 'node:assert';
|
|
5
|
+
import { describe, test } from 'node:test';
|
|
6
|
+
import { formatDateTime, parseDateTime } from '../../src/utils/date-formatter.js';
|
|
7
|
+
describe('formatDateTime', () => {
|
|
8
|
+
test('formats date as yyyy-MM-dd HH:mm:ss', () => {
|
|
9
|
+
const date = new Date(2026, 0, 13, 14, 30, 45);
|
|
10
|
+
const result = formatDateTime(date);
|
|
11
|
+
assert.strictEqual(result, '2026-01-13 14:30:45');
|
|
12
|
+
});
|
|
13
|
+
test('pads single digit month with leading zero', () => {
|
|
14
|
+
const date = new Date(2026, 0, 5, 9, 5, 5);
|
|
15
|
+
const result = formatDateTime(date);
|
|
16
|
+
assert.strictEqual(result, '2026-01-05 09:05:05');
|
|
17
|
+
});
|
|
18
|
+
test('handles end of month', () => {
|
|
19
|
+
const date = new Date(2026, 11, 31, 23, 59, 59);
|
|
20
|
+
const result = formatDateTime(date);
|
|
21
|
+
assert.strictEqual(result, '2026-12-31 23:59:59');
|
|
22
|
+
});
|
|
23
|
+
test('handles start of month', () => {
|
|
24
|
+
const date = new Date(2026, 0, 1, 0, 0, 0);
|
|
25
|
+
const result = formatDateTime(date);
|
|
26
|
+
assert.strictEqual(result, '2026-01-01 00:00:00');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('parseDateTime', () => {
|
|
30
|
+
test('parses valid datetime string', () => {
|
|
31
|
+
const result = parseDateTime('2026-01-13 14:30:45');
|
|
32
|
+
assert.strictEqual(result.getFullYear(), 2026);
|
|
33
|
+
assert.strictEqual(result.getMonth(), 0); // January (0-indexed)
|
|
34
|
+
assert.strictEqual(result.getDate(), 13);
|
|
35
|
+
assert.strictEqual(result.getHours(), 14);
|
|
36
|
+
assert.strictEqual(result.getMinutes(), 30);
|
|
37
|
+
assert.strictEqual(result.getSeconds(), 45);
|
|
38
|
+
});
|
|
39
|
+
test('throws error for invalid format', () => {
|
|
40
|
+
assert.throws(() => parseDateTime('invalid'), /Invalid datetime format/);
|
|
41
|
+
});
|
|
42
|
+
test('throws error for empty string', () => {
|
|
43
|
+
assert.throws(() => parseDateTime(''), /Invalid datetime format/);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=date-formatter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date-formatter.test.js","sourceRoot":"","sources":["../../../tests/functional/date-formatter.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress-bar.test.d.ts","sourceRoot":"","sources":["../../../tests/functional/progress-bar.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|