adaptive-memory-multi-model-router 2.6.0 → 2.8.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,310 @@
1
+ /**
2
+ * A3M Router - BudgetEnforcer Tests
3
+ */
4
+
5
+ import { BudgetEnforcer, BudgetConfig, SpendRecord } from '../src/cost/budgetEnforcer';
6
+
7
+ interface TestResult {
8
+ passed: boolean;
9
+ name: string;
10
+ error?: string;
11
+ }
12
+
13
+ const results: TestResult[] = [];
14
+
15
+ function assertEqual<T>(name: string, actual: T, expected: T): void {
16
+ const passed = JSON.stringify(actual) === JSON.stringify(expected);
17
+ results.push({
18
+ passed,
19
+ name,
20
+ error: passed ? undefined : `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
21
+ });
22
+ }
23
+
24
+ function assertTrue(name: string, actual: boolean): void {
25
+ assertEqual(name, actual, true);
26
+ }
27
+
28
+ function assertFalse(name: string, actual: boolean): void {
29
+ assertEqual(name, actual, false);
30
+ }
31
+
32
+ function assertThrows(name: string, fn: () => void): void {
33
+ try {
34
+ fn();
35
+ results.push({ passed: false, name, error: 'Expected function to throw, but it did not' });
36
+ } catch {
37
+ results.push({ passed: true, name });
38
+ }
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Test Suite
43
+ // ---------------------------------------------------------------------------
44
+
45
+ async function runTests() {
46
+ console.log('Running BudgetEnforcer tests...\n');
47
+
48
+ // Test: Basic budget checking
49
+ {
50
+ const enforcer = new BudgetEnforcer();
51
+ enforcer.setBudget('test-key', 10000, { hardCap: true }); // $100.00 = 10000 cents
52
+
53
+ const result = enforcer.checkBudget('test-key', 5000); // $50
54
+ assertTrue('checkBudget allows request under budget', result.allowed);
55
+ assertEqual('remaining after $50 of $100 budget', result.remaining, 5000);
56
+ }
57
+
58
+ // Test: Spend recording
59
+ {
60
+ const enforcer = new BudgetEnforcer();
61
+ enforcer.setBudget('test-key', 10000);
62
+
63
+ enforcer.recordSpend('test-key', 3000);
64
+ const spend = enforcer.getSpend('test-key');
65
+
66
+ assertEqual('spent after recording $30', spend?.spent, 3000);
67
+ assertEqual('remaining after recording $30 of $100', spend?.remaining, 7000);
68
+ }
69
+
70
+ // Test: Cumulative spend tracking
71
+ {
72
+ const enforcer = new BudgetEnforcer();
73
+ enforcer.setBudget('test-key', 10000);
74
+
75
+ enforcer.recordSpend('test-key', 3000);
76
+ enforcer.recordSpend('test-key', 2000);
77
+ const spend = enforcer.getSpend('test-key');
78
+
79
+ assertEqual('total spent after two recordings', spend?.spent, 5000);
80
+ assertEqual('remaining after two recordings', spend?.remaining, 5000);
81
+ }
82
+
83
+ // Test: Threshold alerts
84
+ {
85
+ const enforcer = new BudgetEnforcer();
86
+ let alertFired = false;
87
+ let alertData: any = null;
88
+
89
+ enforcer.on('budget:warning', (data: any) => {
90
+ alertFired = true;
91
+ alertData = data;
92
+ });
93
+
94
+ enforcer.setBudget('test-key', 10000, {
95
+ alertThresholds: [0.5, 0.8],
96
+ });
97
+
98
+ // Spend to hit 50% threshold
99
+ enforcer.recordSpend('test-key', 5000);
100
+
101
+ assertTrue('alert fired at 50% threshold', alertFired);
102
+ assertEqual('alert threshold value', alertData?.threshold, 0.5);
103
+ assertEqual('alert spent value', alertData?.spent, 5000);
104
+ }
105
+
106
+ // Test: Threshold only fires once
107
+ {
108
+ const enforcer = new BudgetEnforcer();
109
+ let alertCount = 0;
110
+
111
+ enforcer.on('budget:warning', () => {
112
+ alertCount++;
113
+ });
114
+
115
+ enforcer.setBudget('test-key', 10000, {
116
+ alertThresholds: [0.5],
117
+ });
118
+
119
+ enforcer.recordSpend('test-key', 5000);
120
+ enforcer.recordSpend('test-key', 1000); // More spend, still above 50%
121
+ enforcer.recordSpend('test-key', 1000);
122
+
123
+ assertEqual('alert only fires once per threshold', alertCount, 1);
124
+ }
125
+
126
+ // Test: Hard cap rejection
127
+ {
128
+ const enforcer = new BudgetEnforcer();
129
+ enforcer.setBudget('test-key', 10000, { hardCap: true });
130
+
131
+ // Spend most of budget
132
+ enforcer.recordSpend('test-key', 9000);
133
+
134
+ const result = enforcer.checkBudget('test-key', 2000); // Would exceed
135
+ assertFalse('checkBudget rejects request when hard cap would be exceeded', result.allowed);
136
+ assertTrue('reason provided for rejection', result.reason?.includes('Budget exceeded') ?? false);
137
+ }
138
+
139
+ // Test: Hard cap allows if under budget
140
+ {
141
+ const enforcer = new BudgetEnforcer();
142
+ enforcer.setBudget('test-key', 10000, { hardCap: true });
143
+
144
+ enforcer.recordSpend('test-key', 5000);
145
+
146
+ const result = enforcer.checkBudget('test-key', 3000);
147
+ assertTrue('checkBudget allows request under hard cap', result.allowed);
148
+ assertEqual('correct remaining', result.remaining, 2000);
149
+ }
150
+
151
+ // Test: Budget reset
152
+ {
153
+ const enforcer = new BudgetEnforcer();
154
+ enforcer.setBudget('test-key', 10000);
155
+
156
+ enforcer.recordSpend('test-key', 8000);
157
+ enforcer.resetBudget('test-key');
158
+
159
+ const spend = enforcer.getSpend('test-key');
160
+ assertEqual('spent reset to 0', spend?.spent, 0);
161
+ assertEqual('remaining reset to full budget', spend?.remaining, 10000);
162
+ }
163
+
164
+ // Test: Set budget on unknown key (permissive)
165
+ {
166
+ const enforcer = new BudgetEnforcer();
167
+ const result = enforcer.checkBudget('unknown-key', 5000);
168
+ assertTrue('unknown key allowed by default', result.allowed);
169
+ assertEqual('unknown key has infinite remaining', result.remaining, Infinity);
170
+ }
171
+
172
+ // Test: Update limit
173
+ {
174
+ const enforcer = new BudgetEnforcer();
175
+ enforcer.setBudget('test-key', 10000);
176
+ enforcer.recordSpend('test-key', 5000);
177
+
178
+ enforcer.updateLimit('test-key', 20000);
179
+
180
+ const spend = enforcer.getSpend('test-key');
181
+ assertEqual('budget increased', spend?.budget, 20000);
182
+ assertEqual('remaining recalculated', spend?.remaining, 15000);
183
+ }
184
+
185
+ // Test: Get all spend records
186
+ {
187
+ const enforcer = new BudgetEnforcer();
188
+ enforcer.setBudget('key1', 10000);
189
+ enforcer.setBudget('key2', 20000);
190
+ enforcer.recordSpend('key1', 1000);
191
+ enforcer.recordSpend('key2', 2000);
192
+
193
+ const all = enforcer.getAllSpend();
194
+ assertEqual('getAllSpend returns 2 records', all.length, 2);
195
+ }
196
+
197
+ // Test: Remove budget
198
+ {
199
+ const enforcer = new BudgetEnforcer();
200
+ enforcer.setBudget('test-key', 10000);
201
+ enforcer.removeBudget('test-key');
202
+
203
+ const config = enforcer.getBudgetConfig('test-key');
204
+ assertEqual('config removed', config, undefined);
205
+ }
206
+
207
+ // Test: 100% threshold event
208
+ {
209
+ const enforcer = new BudgetEnforcer();
210
+ let exceededEvent = false;
211
+
212
+ enforcer.on('budget:exceeded', () => {
213
+ exceededEvent = true;
214
+ });
215
+
216
+ enforcer.setBudget('test-key', 10000, { hardCap: true });
217
+ enforcer.recordSpend('test-key', 10000);
218
+
219
+ assertTrue('budget:exceeded event fired', exceededEvent);
220
+ }
221
+
222
+ // Test: Soft cap allows request but warns
223
+ {
224
+ const enforcer = new BudgetEnforcer();
225
+ let warningCount = 0;
226
+
227
+ enforcer.on('budget:warning', () => {
228
+ warningCount++;
229
+ });
230
+
231
+ enforcer.setBudget('test-key', 10000, { hardCap: false });
232
+
233
+ // Spend to exceed
234
+ enforcer.recordSpend('test-key', 11000);
235
+
236
+ assertTrue('soft cap allows over-budget spend', warningCount >= 1);
237
+ }
238
+
239
+ // Test: Days until reset
240
+ {
241
+ const enforcer = new BudgetEnforcer();
242
+ enforcer.setBudget('test-key', 10000);
243
+
244
+ const days = enforcer.getDaysUntilReset('test-key');
245
+ assertTrue('days until reset is positive', (days ?? 0) > 0);
246
+ assertTrue('days until reset is <= 31', (days ?? 0) <= 31);
247
+ }
248
+
249
+ // Test: getSpend returns undefined for unknown key
250
+ {
251
+ const enforcer = new BudgetEnforcer();
252
+ const spend = enforcer.getSpend('unknown-key');
253
+ assertEqual('getSpend returns undefined for unknown key', spend, undefined);
254
+ }
255
+
256
+ // Test: Multiple threshold progression
257
+ {
258
+ const enforcer = new BudgetEnforcer();
259
+ const alerts: number[] = [];
260
+
261
+ enforcer.on('budget:warning', (data: any) => {
262
+ alerts.push(data.threshold);
263
+ });
264
+
265
+ enforcer.setBudget('test-key', 10000, {
266
+ alertThresholds: [0.5, 0.8, 1.0],
267
+ });
268
+
269
+ enforcer.recordSpend('test-key', 5000); // 50%
270
+ enforcer.recordSpend('test-key', 3000); // 80%
271
+ enforcer.recordSpend('test-key', 2000); // 100%
272
+
273
+ assertEqual('alerts at 50%, 80%, 100%', JSON.stringify(alerts), JSON.stringify([0.5, 0.8, 1.0]));
274
+ }
275
+
276
+ // Test: Factory function
277
+ {
278
+ const enforcer = createBudgetEnforcer();
279
+ enforcer.setBudget('test-key', 10000);
280
+ const result = enforcer.checkBudget('test-key', 1000);
281
+ assertTrue('factory created enforcer works', result.allowed);
282
+ }
283
+
284
+ // Print results
285
+ console.log('\n--- Test Results ---');
286
+ let passed = 0;
287
+ let failed = 0;
288
+
289
+ for (const r of results) {
290
+ if (r.passed) {
291
+ console.log(` PASS: ${r.name}`);
292
+ passed++;
293
+ } else {
294
+ console.log(` FAIL: ${r.name}`);
295
+ if (r.error) console.log(` ${r.error}`);
296
+ failed++;
297
+ }
298
+ }
299
+
300
+ console.log(`\n${passed} passed, ${failed} failed out of ${results.length} tests`);
301
+
302
+ if (failed > 0) {
303
+ process.exit(1);
304
+ }
305
+ }
306
+
307
+ runTests().catch((err) => {
308
+ console.error('Test runner error:', err);
309
+ process.exit(1);
310
+ });