wraptc 1.0.2 → 1.0.4

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.
Files changed (71) hide show
  1. package/bin/wraptc +4 -4
  2. package/package.json +2 -2
  3. package/src/cli/__tests__/cli.test.ts +337 -0
  4. package/src/cli/index.ts +149 -0
  5. package/src/core/__tests__/fixtures/configs/project-config.json +14 -0
  6. package/src/core/__tests__/fixtures/configs/system-config.json +14 -0
  7. package/src/core/__tests__/fixtures/configs/user-config.json +15 -0
  8. package/src/core/__tests__/integration/integration.test.ts +241 -0
  9. package/src/core/__tests__/integration/mock-coder-adapter.test.ts +243 -0
  10. package/src/core/__tests__/test-utils.ts +136 -0
  11. package/src/core/__tests__/unit/adapters/runner.test.ts +302 -0
  12. package/src/core/__tests__/unit/basic-test.test.ts +44 -0
  13. package/src/core/__tests__/unit/basic.test.ts +12 -0
  14. package/src/core/__tests__/unit/config.test.ts +244 -0
  15. package/src/core/__tests__/unit/error-patterns.test.ts +181 -0
  16. package/src/core/__tests__/unit/memory-monitor.test.ts +354 -0
  17. package/src/core/__tests__/unit/plugin/registry.test.ts +356 -0
  18. package/src/core/__tests__/unit/providers/codex.test.ts +173 -0
  19. package/src/core/__tests__/unit/providers/configurable.test.ts +429 -0
  20. package/src/core/__tests__/unit/providers/gemini.test.ts +251 -0
  21. package/src/core/__tests__/unit/providers/opencode.test.ts +258 -0
  22. package/src/core/__tests__/unit/providers/qwen-code.test.ts +195 -0
  23. package/src/core/__tests__/unit/providers/simple-codex.test.ts +18 -0
  24. package/src/core/__tests__/unit/router.test.ts +967 -0
  25. package/src/core/__tests__/unit/state.test.ts +1079 -0
  26. package/src/core/__tests__/unit/unified/capabilities.test.ts +186 -0
  27. package/src/core/__tests__/unit/wrap-terminalcoder.test.ts +32 -0
  28. package/src/core/adapters/builtin/codex.ts +35 -0
  29. package/src/core/adapters/builtin/gemini.ts +34 -0
  30. package/src/core/adapters/builtin/index.ts +31 -0
  31. package/src/core/adapters/builtin/mock-coder.ts +148 -0
  32. package/src/core/adapters/builtin/qwen.ts +34 -0
  33. package/src/core/adapters/define.ts +48 -0
  34. package/src/core/adapters/index.ts +43 -0
  35. package/src/core/adapters/loader.ts +143 -0
  36. package/src/core/adapters/provider-bridge.ts +190 -0
  37. package/src/core/adapters/runner.ts +437 -0
  38. package/src/core/adapters/types.ts +172 -0
  39. package/src/core/config.ts +290 -0
  40. package/src/core/define-provider.ts +212 -0
  41. package/src/core/error-patterns.ts +147 -0
  42. package/src/core/index.ts +130 -0
  43. package/src/core/memory-monitor.ts +171 -0
  44. package/src/core/plugin/builtin.ts +87 -0
  45. package/src/core/plugin/index.ts +34 -0
  46. package/src/core/plugin/registry.ts +350 -0
  47. package/src/core/plugin/types.ts +209 -0
  48. package/src/core/provider-factory.ts +397 -0
  49. package/src/core/provider-loader.ts +171 -0
  50. package/src/core/providers/codex.ts +56 -0
  51. package/src/core/providers/configurable.ts +637 -0
  52. package/src/core/providers/custom.ts +261 -0
  53. package/src/core/providers/gemini.ts +41 -0
  54. package/src/core/providers/index.ts +383 -0
  55. package/src/core/providers/opencode.ts +168 -0
  56. package/src/core/providers/qwen-code.ts +41 -0
  57. package/src/core/router.ts +370 -0
  58. package/src/core/state.ts +258 -0
  59. package/src/core/types.ts +206 -0
  60. package/src/core/unified/capabilities.ts +184 -0
  61. package/src/core/unified/errors.ts +141 -0
  62. package/src/core/unified/index.ts +29 -0
  63. package/src/core/unified/output.ts +189 -0
  64. package/src/core/wrap-terminalcoder.ts +245 -0
  65. package/src/mcp/__tests__/server.test.ts +295 -0
  66. package/src/mcp/server.ts +284 -0
  67. package/src/test-fixtures/mock-coder.sh +194 -0
  68. package/dist/cli/index.js +0 -16501
  69. package/dist/core/index.js +0 -7531
  70. package/dist/mcp/server.js +0 -14568
  71. package/dist/wraptc-1.0.2.tgz +0 -0
@@ -0,0 +1,181 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ DEFAULT_ERROR_PATTERNS,
4
+ classifyErrorDefault,
5
+ createErrorClassifier,
6
+ mergeErrorPatterns,
7
+ } from "../../error-patterns";
8
+
9
+ describe("error-patterns", () => {
10
+ describe("DEFAULT_ERROR_PATTERNS", () => {
11
+ test("should have patterns for all error kinds", () => {
12
+ const expectedKinds = [
13
+ "OUT_OF_CREDITS",
14
+ "RATE_LIMIT",
15
+ "BAD_REQUEST",
16
+ "UNAUTHORIZED",
17
+ "FORBIDDEN",
18
+ "NOT_FOUND",
19
+ "TIMEOUT",
20
+ "CONTEXT_LENGTH",
21
+ "CONTENT_FILTER",
22
+ "INTERNAL",
23
+ "TRANSIENT",
24
+ "UNKNOWN",
25
+ ];
26
+
27
+ for (const kind of expectedKinds) {
28
+ expect(DEFAULT_ERROR_PATTERNS).toHaveProperty(kind);
29
+ expect(
30
+ Array.isArray(DEFAULT_ERROR_PATTERNS[kind as keyof typeof DEFAULT_ERROR_PATTERNS]),
31
+ ).toBe(true);
32
+ }
33
+ });
34
+
35
+ test("should have empty patterns for UNKNOWN", () => {
36
+ expect(DEFAULT_ERROR_PATTERNS.UNKNOWN).toEqual([]);
37
+ });
38
+ });
39
+
40
+ describe("classifyErrorDefault", () => {
41
+ test("should classify OUT_OF_CREDITS errors", () => {
42
+ expect(classifyErrorDefault("quota exceeded")).toBe("OUT_OF_CREDITS");
43
+ expect(classifyErrorDefault("out of credits")).toBe("OUT_OF_CREDITS");
44
+ expect(classifyErrorDefault("insufficient quota")).toBe("OUT_OF_CREDITS");
45
+ });
46
+
47
+ test("should classify RATE_LIMIT errors", () => {
48
+ expect(classifyErrorDefault("rate limit exceeded")).toBe("RATE_LIMIT");
49
+ expect(classifyErrorDefault("429 too many requests")).toBe("RATE_LIMIT");
50
+ expect(classifyErrorDefault("", "slow down please")).toBe("RATE_LIMIT");
51
+ });
52
+
53
+ test("should classify BAD_REQUEST errors", () => {
54
+ expect(classifyErrorDefault("400 bad request")).toBe("BAD_REQUEST");
55
+ expect(classifyErrorDefault("invalid request format")).toBe("BAD_REQUEST");
56
+ expect(classifyErrorDefault("malformed json")).toBe("BAD_REQUEST");
57
+ });
58
+
59
+ test("should classify UNAUTHORIZED errors", () => {
60
+ expect(classifyErrorDefault("401 unauthorized")).toBe("UNAUTHORIZED");
61
+ expect(classifyErrorDefault("invalid api key")).toBe("UNAUTHORIZED");
62
+ expect(classifyErrorDefault("authentication failed")).toBe("UNAUTHORIZED");
63
+ });
64
+
65
+ test("should classify FORBIDDEN errors", () => {
66
+ expect(classifyErrorDefault("403 forbidden")).toBe("FORBIDDEN");
67
+ expect(classifyErrorDefault("permission denied")).toBe("FORBIDDEN");
68
+ expect(classifyErrorDefault("access denied")).toBe("FORBIDDEN");
69
+ });
70
+
71
+ test("should classify NOT_FOUND errors", () => {
72
+ expect(classifyErrorDefault("404 not found")).toBe("NOT_FOUND");
73
+ expect(classifyErrorDefault("no such file or directory")).toBe("NOT_FOUND");
74
+ expect(classifyErrorDefault("", "", 127)).toBe("NOT_FOUND");
75
+ });
76
+
77
+ test("should classify TIMEOUT errors", () => {
78
+ expect(classifyErrorDefault("request timeout")).toBe("TIMEOUT");
79
+ expect(classifyErrorDefault("operation timed out")).toBe("TIMEOUT");
80
+ expect(classifyErrorDefault("deadline exceeded")).toBe("TIMEOUT");
81
+ });
82
+
83
+ test("should classify CONTEXT_LENGTH errors", () => {
84
+ expect(classifyErrorDefault("context length exceeded")).toBe("CONTEXT_LENGTH");
85
+ expect(classifyErrorDefault("input too long")).toBe("CONTEXT_LENGTH");
86
+ expect(classifyErrorDefault("max tokens exceeded")).toBe("CONTEXT_LENGTH");
87
+ });
88
+
89
+ test("should classify CONTENT_FILTER errors", () => {
90
+ expect(classifyErrorDefault("content filter triggered")).toBe("CONTENT_FILTER");
91
+ expect(classifyErrorDefault("request blocked by safety")).toBe("CONTENT_FILTER");
92
+ expect(classifyErrorDefault("policy violation")).toBe("CONTENT_FILTER");
93
+ });
94
+
95
+ test("should classify INTERNAL errors", () => {
96
+ expect(classifyErrorDefault("500 internal server error")).toBe("INTERNAL");
97
+ expect(classifyErrorDefault("502 bad gateway")).toBe("INTERNAL");
98
+ expect(classifyErrorDefault("internal error occurred")).toBe("INTERNAL");
99
+ });
100
+
101
+ test("should classify TRANSIENT errors", () => {
102
+ expect(classifyErrorDefault("connection refused")).toBe("TRANSIENT");
103
+ expect(classifyErrorDefault("econnrefused")).toBe("TRANSIENT");
104
+ expect(classifyErrorDefault("network error")).toBe("TRANSIENT");
105
+ });
106
+
107
+ test("should default to TRANSIENT for unmatched errors", () => {
108
+ expect(classifyErrorDefault("some random error")).toBe("TRANSIENT");
109
+ expect(classifyErrorDefault("")).toBe("TRANSIENT");
110
+ });
111
+
112
+ test("should combine stderr and stdout", () => {
113
+ expect(classifyErrorDefault("some stderr", "quota exceeded")).toBe("OUT_OF_CREDITS");
114
+ expect(classifyErrorDefault("rate limit", "some stdout")).toBe("RATE_LIMIT");
115
+ });
116
+
117
+ test("should handle undefined values", () => {
118
+ expect(classifyErrorDefault(undefined as any, undefined as any)).toBe("TRANSIENT");
119
+ expect(classifyErrorDefault("", "")).toBe("TRANSIENT");
120
+ });
121
+ });
122
+
123
+ describe("mergeErrorPatterns", () => {
124
+ test("should merge provider patterns with defaults", () => {
125
+ const providerPatterns = {
126
+ OUT_OF_CREDITS: ["plan limit", "subscription required"],
127
+ RATE_LIMIT: ["throttled"],
128
+ };
129
+
130
+ const merged = mergeErrorPatterns(providerPatterns);
131
+
132
+ // Provider patterns should come first
133
+ expect(merged.OUT_OF_CREDITS[0]).toBe("plan limit");
134
+ expect(merged.OUT_OF_CREDITS[1]).toBe("subscription required");
135
+ // Default patterns should follow
136
+ expect(merged.OUT_OF_CREDITS).toContain("quota exceeded");
137
+
138
+ // RATE_LIMIT should have both
139
+ expect(merged.RATE_LIMIT[0]).toBe("throttled");
140
+ expect(merged.RATE_LIMIT).toContain("rate limit");
141
+
142
+ // Untouched kinds should remain default
143
+ expect(merged.TIMEOUT).toEqual(DEFAULT_ERROR_PATTERNS.TIMEOUT);
144
+ });
145
+
146
+ test("should handle empty provider patterns", () => {
147
+ const merged = mergeErrorPatterns({});
148
+ expect(merged).toEqual(DEFAULT_ERROR_PATTERNS);
149
+ });
150
+ });
151
+
152
+ describe("createErrorClassifier", () => {
153
+ test("should create classifier with default patterns", () => {
154
+ const classify = createErrorClassifier();
155
+
156
+ expect(classify("quota exceeded")).toBe("OUT_OF_CREDITS");
157
+ expect(classify("rate limit")).toBe("RATE_LIMIT");
158
+ expect(classify("random error")).toBe("TRANSIENT");
159
+ });
160
+
161
+ test("should create classifier with custom patterns", () => {
162
+ const classify = createErrorClassifier({
163
+ OUT_OF_CREDITS: ["custom credit error"],
164
+ });
165
+
166
+ expect(classify("custom credit error")).toBe("OUT_OF_CREDITS");
167
+ // Should still match default patterns
168
+ expect(classify("quota exceeded")).toBe("OUT_OF_CREDITS");
169
+ });
170
+
171
+ test("should handle exit code 127 as NOT_FOUND", () => {
172
+ const classify = createErrorClassifier();
173
+ expect(classify("", "", 127)).toBe("NOT_FOUND");
174
+ });
175
+
176
+ test("should ignore exit code when patterns match", () => {
177
+ const classify = createErrorClassifier();
178
+ expect(classify("rate limit", "", 127)).toBe("RATE_LIMIT");
179
+ });
180
+ });
181
+ });
@@ -0,0 +1,354 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
2
+ import {
3
+ MemoryMonitor,
4
+ type MemoryStats,
5
+ type MemoryThresholds,
6
+ formatBytes,
7
+ memoryMonitor,
8
+ } from "../../memory-monitor";
9
+
10
+ // Mock process.memoryUsage
11
+ const mockMemoryUsage = mock(() => ({
12
+ heapUsed: 50 * 1024 * 1024, // 50MB
13
+ heapTotal: 100 * 1024 * 1024, // 100MB
14
+ external: 10 * 1024 * 1024, // 10MB
15
+ rss: 150 * 1024 * 1024, // 150MB
16
+ arrayBuffers: 5 * 1024 * 1024, // 5MB
17
+ }));
18
+
19
+ // Mock console methods
20
+ const mockConsoleLog = mock();
21
+ const mockConsoleWarn = mock();
22
+ const mockConsoleError = mock();
23
+
24
+ describe("MemoryMonitor", () => {
25
+ let monitor: MemoryMonitor;
26
+
27
+ beforeEach(() => {
28
+ // Reset singleton instance
29
+ (MemoryMonitor as any).instance = null;
30
+ monitor = MemoryMonitor.getInstance();
31
+
32
+ // Mock process.memoryUsage
33
+ (process as any).memoryUsage = mockMemoryUsage;
34
+
35
+ // Mock console methods
36
+ spyOn(console, "log").mockImplementation(mockConsoleLog);
37
+ spyOn(console, "warn").mockImplementation(mockConsoleWarn);
38
+ spyOn(console, "error").mockImplementation(mockConsoleError);
39
+
40
+ // Clear all mocks
41
+ mockMemoryUsage.mockClear();
42
+ mockConsoleLog.mockClear();
43
+ mockConsoleWarn.mockClear();
44
+ mockConsoleError.mockClear();
45
+ });
46
+
47
+ afterEach(() => {
48
+ // Stop monitoring if running
49
+ monitor.stopMonitoring();
50
+ });
51
+
52
+ describe("Singleton Pattern", () => {
53
+ test("should return the same instance", () => {
54
+ const instance1 = MemoryMonitor.getInstance();
55
+ const instance2 = MemoryMonitor.getInstance();
56
+ expect(instance1).toBe(instance2);
57
+ });
58
+
59
+ test("should export singleton instance", () => {
60
+ // Reset singleton and get fresh instance
61
+ (MemoryMonitor as any).instance = null;
62
+ const freshInstance = MemoryMonitor.getInstance();
63
+ expect(freshInstance).toBeInstanceOf(MemoryMonitor);
64
+ expect(freshInstance).toBe(MemoryMonitor.getInstance());
65
+ });
66
+ });
67
+
68
+ describe("getMemoryStats", () => {
69
+ test("should return formatted memory statistics", () => {
70
+ const stats = monitor.getMemoryStats();
71
+
72
+ expect(stats).toEqual({
73
+ heapUsed: 50, // 50MB
74
+ heapTotal: 100, // 100MB
75
+ external: 10, // 10MB
76
+ rss: 150, // 150MB
77
+ arrayBuffers: 5, // 5MB
78
+ });
79
+
80
+ expect(process.memoryUsage).toHaveBeenCalledTimes(1);
81
+ });
82
+
83
+ test("should round values to nearest MB", () => {
84
+ spyOn(process, "memoryUsage").mockReturnValueOnce({
85
+ heapUsed: 50.7 * 1024 * 1024, // 50.7MB
86
+ heapTotal: 100.3 * 1024 * 1024, // 100.3MB
87
+ external: 10.9 * 1024 * 1024, // 10.9MB
88
+ rss: 150.1 * 1024 * 1024, // 150.1MB
89
+ arrayBuffers: 5.5 * 1024 * 1024, // 5.5MB
90
+ });
91
+
92
+ const stats = monitor.getMemoryStats();
93
+
94
+ expect(stats).toEqual({
95
+ heapUsed: 51, // Rounded up
96
+ heapTotal: 100, // Rounded down
97
+ external: 11, // Rounded up
98
+ rss: 150, // Rounded down
99
+ arrayBuffers: 6, // Rounded up
100
+ });
101
+ });
102
+ });
103
+
104
+ describe("checkMemoryLimit", () => {
105
+ test("should return true when usage is below threshold", () => {
106
+ // 50MB used / 100MB total = 50% < 85%
107
+ expect(monitor.checkMemoryLimit(0.85)).toBe(true);
108
+ });
109
+
110
+ test("should return false when usage exceeds threshold", () => {
111
+ // 50MB used / 100MB total = 50% < 30%
112
+ expect(monitor.checkMemoryLimit(0.3)).toBe(false);
113
+ });
114
+
115
+ test("should use default threshold of 0.85", () => {
116
+ expect(monitor.checkMemoryLimit()).toBe(true);
117
+ });
118
+ });
119
+
120
+ describe("getMemoryUsagePercent", () => {
121
+ test("should calculate and round memory usage percentage", () => {
122
+ // 50MB / 100MB = 50%
123
+ expect(monitor.getMemoryUsagePercent()).toBe(50);
124
+ });
125
+
126
+ test("should handle decimal percentages", () => {
127
+ spyOn(process, "memoryUsage").mockReturnValueOnce({
128
+ heapUsed: 33 * 1024 * 1024, // 33MB
129
+ heapTotal: 100 * 1024 * 1024, // 100MB
130
+ external: 0,
131
+ rss: 0,
132
+ arrayBuffers: 0,
133
+ });
134
+
135
+ // 33MB / 100MB = 33%
136
+ expect(monitor.getMemoryUsagePercent()).toBe(33);
137
+ });
138
+ });
139
+
140
+ describe("formatMemoryStats", () => {
141
+ test("should format memory stats with provided stats", () => {
142
+ const customStats: MemoryStats = {
143
+ heapUsed: 25,
144
+ heapTotal: 50,
145
+ external: 5,
146
+ rss: 75,
147
+ arrayBuffers: 2,
148
+ };
149
+
150
+ spyOn(monitor, "getMemoryUsagePercent").mockReturnValue(50);
151
+
152
+ const result = monitor.formatMemoryStats(customStats);
153
+ expect(result).toBe("Heap: 25MB/50MB (50%) | RSS: 75MB | Buffers: 2MB");
154
+ });
155
+
156
+ test("should use current stats when none provided", () => {
157
+ spyOn(monitor, "getMemoryUsagePercent").mockReturnValue(50);
158
+
159
+ const result = monitor.formatMemoryStats();
160
+ expect(result).toBe("Heap: 50MB/100MB (50%) | RSS: 150MB | Buffers: 5MB");
161
+ });
162
+ });
163
+
164
+ describe("startMonitoring", () => {
165
+ test("should start periodic monitoring", () => {
166
+ monitor.startMonitoring(100);
167
+
168
+ expect(monitor.isMonitoring).toBe(true);
169
+ expect(monitor.intervalId).not.toBeNull();
170
+ expect(console.log).toHaveBeenCalledWith(
171
+ "[MemoryMonitor] Started monitoring memory usage every 100ms",
172
+ );
173
+ });
174
+
175
+ test("should not start if already monitoring", () => {
176
+ monitor.startMonitoring(100);
177
+ expect(mockConsoleLog).toHaveBeenCalledWith(
178
+ "[MemoryMonitor] Started monitoring memory usage every 100ms",
179
+ );
180
+
181
+ // Try to start again
182
+ monitor.startMonitoring(100);
183
+ expect(mockConsoleLog).toHaveBeenCalledWith(
184
+ "[MemoryMonitor] Already monitoring memory usage",
185
+ );
186
+ expect(mockConsoleLog).toHaveBeenCalledTimes(2);
187
+ });
188
+
189
+ test("should log info level by default", async () => {
190
+ monitor.startMonitoring(50); // Short interval for testing
191
+
192
+ // Wait for at least one interval
193
+ await new Promise((resolve) => setTimeout(resolve, 60));
194
+
195
+ monitor.stopMonitoring();
196
+
197
+ expect(mockConsoleLog).toHaveBeenCalledWith(
198
+ "[MemoryMonitor] Heap: 50MB/100MB (50%) | RSS: 150MB | Buffers: 5MB",
199
+ );
200
+ });
201
+
202
+ test("should trigger warning alert when above warning threshold", async () => {
203
+ monitor.setThresholds({ warning: 0.3, critical: 0.9 }); // 30% warning
204
+
205
+ monitor.startMonitoring(50);
206
+
207
+ // Wait for monitoring to trigger
208
+ await new Promise((resolve) => setTimeout(resolve, 60));
209
+
210
+ monitor.stopMonitoring();
211
+
212
+ expect(mockConsoleWarn).toHaveBeenCalledWith(
213
+ "[MemoryMonitor] WARNING: Heap: 50MB/100MB (50%) | RSS: 150MB | Buffers: 5MB",
214
+ );
215
+ });
216
+
217
+ test("should trigger critical alert when above critical threshold", async () => {
218
+ monitor.setThresholds({ warning: 0.3, critical: 0.4 }); // 40% critical
219
+
220
+ monitor.startMonitoring(50);
221
+
222
+ // Wait for monitoring to trigger
223
+ await new Promise((resolve) => setTimeout(resolve, 60));
224
+
225
+ monitor.stopMonitoring();
226
+
227
+ expect(mockConsoleError).toHaveBeenCalledWith(
228
+ "[MemoryMonitor] CRITICAL: Heap: 50MB/100MB (50%) | RSS: 150MB | Buffers: 5MB",
229
+ );
230
+ });
231
+ });
232
+
233
+ describe("stopMonitoring", () => {
234
+ test("should stop monitoring and clear interval", () => {
235
+ monitor.startMonitoring(100);
236
+ expect(monitor.isMonitoring).toBe(true);
237
+
238
+ monitor.stopMonitoring();
239
+
240
+ expect(monitor.isMonitoring).toBe(false);
241
+ expect(monitor.intervalId).toBeNull();
242
+ expect(console.log).toHaveBeenCalledWith("[MemoryMonitor] Stopped monitoring");
243
+ });
244
+
245
+ test("should handle stopping when not monitoring", () => {
246
+ monitor.stopMonitoring();
247
+
248
+ expect(console.log).toHaveBeenCalledWith("[MemoryMonitor] Stopped monitoring");
249
+ });
250
+ });
251
+
252
+ describe("setThresholds", () => {
253
+ test("should update memory thresholds", () => {
254
+ const newThresholds: MemoryThresholds = { warning: 0.7, critical: 0.85 };
255
+
256
+ monitor.setThresholds(newThresholds);
257
+
258
+ expect(monitor.thresholds).toEqual(newThresholds);
259
+ });
260
+ });
261
+
262
+ describe("forceGC", () => {
263
+ test("should trigger garbage collection when available", () => {
264
+ (global as any).gc = mock();
265
+
266
+ monitor.forceGC();
267
+
268
+ expect((global as any).gc).toHaveBeenCalledTimes(1);
269
+ expect(console.log).toHaveBeenCalledWith("[MemoryMonitor] Garbage collection triggered");
270
+ });
271
+
272
+ test("should log when GC is not available", () => {
273
+ (global as any).gc = undefined;
274
+
275
+ monitor.forceGC();
276
+
277
+ expect(console.log).toHaveBeenCalledWith(
278
+ "[MemoryMonitor] Garbage collection not available (run with --expose-gc flag)",
279
+ );
280
+ });
281
+ });
282
+
283
+ describe("logMemoryReport", () => {
284
+ test("should log detailed memory report", () => {
285
+ spyOn(monitor, "getMemoryUsagePercent").mockReturnValue(50);
286
+
287
+ monitor.logMemoryReport();
288
+
289
+ expect(console.log).toHaveBeenCalledWith("[MemoryMonitor] === MEMORY REPORT ===");
290
+ expect(console.log).toHaveBeenCalledWith("Heap Used: 50MB");
291
+ expect(console.log).toHaveBeenCalledWith("Heap Total: 100MB");
292
+ expect(console.log).toHaveBeenCalledWith("RSS: 150MB");
293
+ expect(console.log).toHaveBeenCalledWith("External: 10MB");
294
+ expect(console.log).toHaveBeenCalledWith("Array Buffers: 5MB");
295
+ expect(console.log).toHaveBeenCalledWith("Usage: 50%");
296
+ expect(console.log).toHaveBeenCalledWith("[MemoryMonitor] ====================");
297
+ });
298
+ });
299
+
300
+ describe("triggerMemoryAlert", () => {
301
+ test("should log critical alert message", () => {
302
+ const stats: MemoryStats = {
303
+ heapUsed: 90,
304
+ heapTotal: 100,
305
+ external: 10,
306
+ rss: 150,
307
+ arrayBuffers: 5,
308
+ };
309
+
310
+ (monitor as any).triggerMemoryAlert("critical", stats);
311
+
312
+ expect(console.error).toHaveBeenCalledWith(
313
+ "[MemoryMonitor] Memory usage critical! Consider:\n 1. Restarting the application\n 2. Reducing concurrent requests\n 3. Implementing streaming limits\n 4. Adding more memory monitoring",
314
+ );
315
+ });
316
+
317
+ test("should not log for warning alerts", () => {
318
+ const stats: MemoryStats = {
319
+ heapUsed: 80,
320
+ heapTotal: 100,
321
+ external: 10,
322
+ rss: 150,
323
+ arrayBuffers: 5,
324
+ };
325
+
326
+ (monitor as any).triggerMemoryAlert("warning", stats);
327
+
328
+ expect(mockConsoleError).not.toHaveBeenCalled();
329
+ });
330
+ });
331
+ });
332
+
333
+ describe("formatBytes", () => {
334
+ test("should format bytes correctly", () => {
335
+ expect(formatBytes(0)).toBe("0 Bytes");
336
+ expect(formatBytes(1024)).toBe("1 KB");
337
+ expect(formatBytes(1024 * 1024)).toBe("1 MB");
338
+ expect(formatBytes(1024 * 1024 * 1024)).toBe("1 GB");
339
+ expect(formatBytes(1024 * 1024 * 1024 * 1024)).toBe("1 TB");
340
+ });
341
+
342
+ test("should handle decimal places", () => {
343
+ expect(formatBytes(1536, 1)).toBe("1.5 KB");
344
+ expect(formatBytes(1536)).toBe("1.5 KB");
345
+ });
346
+
347
+ test("should handle negative decimals", () => {
348
+ expect(formatBytes(1536, -1)).toBe("2 KB");
349
+ });
350
+
351
+ test("should handle large numbers", () => {
352
+ expect(formatBytes(1024 * 1024 * 1024 * 1024 * 2)).toBe("2 TB");
353
+ });
354
+ });