github-issue-tower-defence-management 1.65.0 → 1.67.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/CHANGELOG.md +14 -0
- package/README.md +7 -7
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +5 -0
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
- package/bin/domain/entities/WorkflowStatus.js +2 -6
- package/bin/domain/entities/WorkflowStatus.js.map +1 -1
- package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +14 -1
- package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +59 -121
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +1 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +14 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +5 -0
- package/src/domain/entities/ClaudeTokenUsage.ts +1 -0
- package/src/domain/entities/WorkflowStatus.ts +2 -5
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +432 -36
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +31 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +87 -306
- package/src/domain/usecases/StartPreparationUseCase.ts +80 -175
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
- package/types/domain/entities/ClaudeTokenUsage.d.ts +1 -0
- package/types/domain/entities/ClaudeTokenUsage.d.ts.map +1 -1
- package/types/domain/entities/WorkflowStatus.d.ts +1 -1
- package/types/domain/entities/WorkflowStatus.d.ts.map +1 -1
- package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts +3 -1
- package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +2 -6
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
|
@@ -1221,6 +1221,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1221
1221
|
name: 'token-a',
|
|
1222
1222
|
token: 'token-a',
|
|
1223
1223
|
fiveHourUtilization: 0,
|
|
1224
|
+
sevenDayUtilization: 0,
|
|
1224
1225
|
blocked: false,
|
|
1225
1226
|
rejected: false,
|
|
1226
1227
|
modelWeeklyLimits: {},
|
|
@@ -1229,6 +1230,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1229
1230
|
name: 'token-b',
|
|
1230
1231
|
token: 'token-b',
|
|
1231
1232
|
fiveHourUtilization: 0,
|
|
1233
|
+
sevenDayUtilization: 0,
|
|
1232
1234
|
blocked: false,
|
|
1233
1235
|
rejected: false,
|
|
1234
1236
|
modelWeeklyLimits: {},
|
|
@@ -1241,7 +1243,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1241
1243
|
defaultLlmModelName: 'claude-sonnet-4-6',
|
|
1242
1244
|
defaultLlmAgentName: null,
|
|
1243
1245
|
configFilePath: '/path/to/config.yml',
|
|
1244
|
-
maximumPreparingIssuesCount:
|
|
1246
|
+
maximumPreparingIssuesCount: 12,
|
|
1245
1247
|
utilizationPercentageThreshold: 90,
|
|
1246
1248
|
allowedIssueAuthors: null,
|
|
1247
1249
|
codexHomeCandidates: null,
|
|
@@ -1284,6 +1286,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1284
1286
|
name: 'token-a',
|
|
1285
1287
|
token: 'token-a',
|
|
1286
1288
|
fiveHourUtilization: 0,
|
|
1289
|
+
sevenDayUtilization: 0,
|
|
1287
1290
|
blocked: false,
|
|
1288
1291
|
rejected: false,
|
|
1289
1292
|
modelWeeklyLimits: {},
|
|
@@ -1292,6 +1295,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1292
1295
|
name: 'token-b',
|
|
1293
1296
|
token: 'token-b',
|
|
1294
1297
|
fiveHourUtilization: 0,
|
|
1298
|
+
sevenDayUtilization: 0,
|
|
1295
1299
|
blocked: false,
|
|
1296
1300
|
rejected: false,
|
|
1297
1301
|
modelWeeklyLimits: {},
|
|
@@ -1712,9 +1716,6 @@ describe('StartPreparationUseCase', () => {
|
|
|
1712
1716
|
stderr: '',
|
|
1713
1717
|
exitCode: 0,
|
|
1714
1718
|
});
|
|
1715
|
-
const consoleWarnSpy = jest
|
|
1716
|
-
.spyOn(console, 'warn')
|
|
1717
|
-
.mockImplementation(() => {});
|
|
1718
1719
|
|
|
1719
1720
|
await useCase.run({
|
|
1720
1721
|
projectUrl: 'https://github.com/user/repo',
|
|
@@ -1735,10 +1736,6 @@ describe('StartPreparationUseCase', () => {
|
|
|
1735
1736
|
status: 'Preparation',
|
|
1736
1737
|
});
|
|
1737
1738
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
1738
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
1739
|
-
`Skipping issue https://github.com/user/repo/issues/2: author 'user3' is not in the allowedIssueAuthors list.`,
|
|
1740
|
-
);
|
|
1741
|
-
consoleWarnSpy.mockRestore();
|
|
1742
1739
|
});
|
|
1743
1740
|
|
|
1744
1741
|
it('should process all issues when allowedIssueAuthors is null', async () => {
|
|
@@ -1811,9 +1808,6 @@ describe('StartPreparationUseCase', () => {
|
|
|
1811
1808
|
stderr: '',
|
|
1812
1809
|
exitCode: 0,
|
|
1813
1810
|
});
|
|
1814
|
-
const consoleWarnSpy = jest
|
|
1815
|
-
.spyOn(console, 'warn')
|
|
1816
|
-
.mockImplementation(() => {});
|
|
1817
1811
|
|
|
1818
1812
|
await useCase.run({
|
|
1819
1813
|
projectUrl: 'https://github.com/user/repo',
|
|
@@ -1833,10 +1827,6 @@ describe('StartPreparationUseCase', () => {
|
|
|
1833
1827
|
url: 'https://github.com/user/repo/issues/2',
|
|
1834
1828
|
});
|
|
1835
1829
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
1836
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
1837
|
-
'Skipping issue https://github.com/user/repo/issues/1: author is unknown (empty string); deny-by-default when allowedIssueAuthors is configured.',
|
|
1838
|
-
);
|
|
1839
|
-
consoleWarnSpy.mockRestore();
|
|
1840
1830
|
});
|
|
1841
1831
|
|
|
1842
1832
|
it('should skip issue with empty author when allowedIssueAuthors is set', async () => {
|
|
@@ -1857,9 +1847,6 @@ describe('StartPreparationUseCase', () => {
|
|
|
1857
1847
|
stderr: '',
|
|
1858
1848
|
exitCode: 0,
|
|
1859
1849
|
});
|
|
1860
|
-
const consoleWarnSpy = jest
|
|
1861
|
-
.spyOn(console, 'warn')
|
|
1862
|
-
.mockImplementation(() => {});
|
|
1863
1850
|
|
|
1864
1851
|
await useCase.run({
|
|
1865
1852
|
projectUrl: 'https://github.com/user/repo',
|
|
@@ -1876,10 +1863,6 @@ describe('StartPreparationUseCase', () => {
|
|
|
1876
1863
|
|
|
1877
1864
|
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
1878
1865
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
1879
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
1880
|
-
'Skipping issue https://github.com/user/repo/issues/1: author is unknown (empty string); deny-by-default when allowedIssueAuthors is configured.',
|
|
1881
|
-
);
|
|
1882
|
-
consoleWarnSpy.mockRestore();
|
|
1883
1866
|
});
|
|
1884
1867
|
|
|
1885
1868
|
it('should not pass --codexHome when codexHomeCandidates is null', async () => {
|
|
@@ -2299,6 +2282,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2299
2282
|
name: 'token-a',
|
|
2300
2283
|
token: 'token-a',
|
|
2301
2284
|
fiveHourUtilization: 0,
|
|
2285
|
+
sevenDayUtilization: 0,
|
|
2302
2286
|
blocked: false,
|
|
2303
2287
|
rejected: false,
|
|
2304
2288
|
modelWeeklyLimits: {},
|
|
@@ -2374,6 +2358,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2374
2358
|
name: 'token-a',
|
|
2375
2359
|
token: 'token-a',
|
|
2376
2360
|
fiveHourUtilization: 0,
|
|
2361
|
+
sevenDayUtilization: 0,
|
|
2377
2362
|
blocked: false,
|
|
2378
2363
|
rejected: false,
|
|
2379
2364
|
modelWeeklyLimits: {},
|
|
@@ -2382,6 +2367,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2382
2367
|
name: 'token-b',
|
|
2383
2368
|
token: 'token-b',
|
|
2384
2369
|
fiveHourUtilization: 0,
|
|
2370
|
+
sevenDayUtilization: 0,
|
|
2385
2371
|
blocked: false,
|
|
2386
2372
|
rejected: false,
|
|
2387
2373
|
modelWeeklyLimits: {},
|
|
@@ -2464,7 +2450,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2464
2450
|
).toHaveLength(0);
|
|
2465
2451
|
});
|
|
2466
2452
|
|
|
2467
|
-
it('should pick the
|
|
2453
|
+
it('should pick the token with the lowest 7-day utilization first', async () => {
|
|
2468
2454
|
const awaitingIssue = createMockIssue({
|
|
2469
2455
|
url: 'url1',
|
|
2470
2456
|
title: 'Issue 1',
|
|
@@ -2484,17 +2470,19 @@ describe('StartPreparationUseCase', () => {
|
|
|
2484
2470
|
});
|
|
2485
2471
|
mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
|
|
2486
2472
|
{
|
|
2487
|
-
name: 'token-high',
|
|
2488
|
-
token: 'token-high',
|
|
2489
|
-
fiveHourUtilization: 0.
|
|
2473
|
+
name: 'token-high-7d',
|
|
2474
|
+
token: 'token-high-7d',
|
|
2475
|
+
fiveHourUtilization: 0.1,
|
|
2476
|
+
sevenDayUtilization: 0.7,
|
|
2490
2477
|
blocked: false,
|
|
2491
2478
|
rejected: false,
|
|
2492
2479
|
modelWeeklyLimits: {},
|
|
2493
2480
|
},
|
|
2494
2481
|
{
|
|
2495
|
-
name: 'token-low',
|
|
2496
|
-
token: 'token-low',
|
|
2497
|
-
fiveHourUtilization: 0.
|
|
2482
|
+
name: 'token-low-7d',
|
|
2483
|
+
token: 'token-low-7d',
|
|
2484
|
+
fiveHourUtilization: 0.5,
|
|
2485
|
+
sevenDayUtilization: 0.2,
|
|
2498
2486
|
blocked: false,
|
|
2499
2487
|
rejected: false,
|
|
2500
2488
|
modelWeeklyLimits: {},
|
|
@@ -2517,7 +2505,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2517
2505
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
2518
2506
|
expect(mockLocalCommandRunner.runCommand.mock.calls[0][2]).toEqual({
|
|
2519
2507
|
env: {
|
|
2520
|
-
CLAUDE_CODE_OAUTH_TOKEN: 'token-low',
|
|
2508
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-low-7d',
|
|
2521
2509
|
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8787',
|
|
2522
2510
|
},
|
|
2523
2511
|
});
|
|
@@ -2546,6 +2534,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2546
2534
|
name: 'token-blocked',
|
|
2547
2535
|
token: 'token-blocked',
|
|
2548
2536
|
fiveHourUtilization: 0.05,
|
|
2537
|
+
sevenDayUtilization: 0,
|
|
2549
2538
|
blocked: true,
|
|
2550
2539
|
rejected: false,
|
|
2551
2540
|
modelWeeklyLimits: {},
|
|
@@ -2554,6 +2543,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2554
2543
|
name: 'token-ok',
|
|
2555
2544
|
token: 'token-ok',
|
|
2556
2545
|
fiveHourUtilization: 0.5,
|
|
2546
|
+
sevenDayUtilization: 0,
|
|
2557
2547
|
blocked: false,
|
|
2558
2548
|
rejected: false,
|
|
2559
2549
|
modelWeeklyLimits: {},
|
|
@@ -2605,6 +2595,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2605
2595
|
name: 'token-a',
|
|
2606
2596
|
token: 'token-a',
|
|
2607
2597
|
fiveHourUtilization: 0.05,
|
|
2598
|
+
sevenDayUtilization: 0,
|
|
2608
2599
|
blocked: true,
|
|
2609
2600
|
rejected: false,
|
|
2610
2601
|
modelWeeklyLimits: {},
|
|
@@ -2613,6 +2604,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2613
2604
|
name: 'token-b',
|
|
2614
2605
|
token: 'token-b',
|
|
2615
2606
|
fiveHourUtilization: 0.08,
|
|
2607
|
+
sevenDayUtilization: 0,
|
|
2616
2608
|
blocked: true,
|
|
2617
2609
|
rejected: false,
|
|
2618
2610
|
modelWeeklyLimits: {},
|
|
@@ -2670,6 +2662,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2670
2662
|
name: 'token-a',
|
|
2671
2663
|
token: 'token-a',
|
|
2672
2664
|
fiveHourUtilization: 0.95,
|
|
2665
|
+
sevenDayUtilization: 0,
|
|
2673
2666
|
blocked: false,
|
|
2674
2667
|
rejected: false,
|
|
2675
2668
|
modelWeeklyLimits: {},
|
|
@@ -2678,6 +2671,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2678
2671
|
name: 'token-b',
|
|
2679
2672
|
token: 'token-b',
|
|
2680
2673
|
fiveHourUtilization: 0.97,
|
|
2674
|
+
sevenDayUtilization: 0,
|
|
2681
2675
|
blocked: false,
|
|
2682
2676
|
rejected: false,
|
|
2683
2677
|
modelWeeklyLimits: {},
|
|
@@ -2707,12 +2701,12 @@ describe('StartPreparationUseCase', () => {
|
|
|
2707
2701
|
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
2708
2702
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
2709
2703
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
2710
|
-
expect.stringContaining('5h utilization >=
|
|
2704
|
+
expect.stringContaining('5h utilization >= 90%'),
|
|
2711
2705
|
);
|
|
2712
2706
|
consoleWarnSpy.mockRestore();
|
|
2713
2707
|
});
|
|
2714
2708
|
|
|
2715
|
-
it('should
|
|
2709
|
+
it('should sort tokens by 7-day utilization ascending when all have full process capacity', async () => {
|
|
2716
2710
|
const awaitingIssues: Issue[] = [
|
|
2717
2711
|
createMockIssue({
|
|
2718
2712
|
url: 'url1',
|
|
@@ -2750,25 +2744,28 @@ describe('StartPreparationUseCase', () => {
|
|
|
2750
2744
|
});
|
|
2751
2745
|
mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
|
|
2752
2746
|
{
|
|
2753
|
-
name: 'token-mid',
|
|
2754
|
-
token: 'token-mid',
|
|
2755
|
-
fiveHourUtilization: 0.
|
|
2747
|
+
name: 'token-7d-mid',
|
|
2748
|
+
token: 'token-7d-mid',
|
|
2749
|
+
fiveHourUtilization: 0.1,
|
|
2750
|
+
sevenDayUtilization: 0.5,
|
|
2756
2751
|
blocked: false,
|
|
2757
2752
|
rejected: false,
|
|
2758
2753
|
modelWeeklyLimits: {},
|
|
2759
2754
|
},
|
|
2760
2755
|
{
|
|
2761
|
-
name: 'token-low',
|
|
2762
|
-
token: 'token-low',
|
|
2763
|
-
fiveHourUtilization: 0.
|
|
2756
|
+
name: 'token-7d-low',
|
|
2757
|
+
token: 'token-7d-low',
|
|
2758
|
+
fiveHourUtilization: 0.5,
|
|
2759
|
+
sevenDayUtilization: 0.1,
|
|
2764
2760
|
blocked: false,
|
|
2765
2761
|
rejected: false,
|
|
2766
2762
|
modelWeeklyLimits: {},
|
|
2767
2763
|
},
|
|
2768
2764
|
{
|
|
2769
|
-
name: 'token-high',
|
|
2770
|
-
token: 'token-high',
|
|
2771
|
-
fiveHourUtilization: 0.
|
|
2765
|
+
name: 'token-7d-high',
|
|
2766
|
+
token: 'token-7d-high',
|
|
2767
|
+
fiveHourUtilization: 0.3,
|
|
2768
|
+
sevenDayUtilization: 0.7,
|
|
2772
2769
|
blocked: false,
|
|
2773
2770
|
rejected: false,
|
|
2774
2771
|
modelWeeklyLimits: {},
|
|
@@ -2790,17 +2787,17 @@ describe('StartPreparationUseCase', () => {
|
|
|
2790
2787
|
|
|
2791
2788
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(3);
|
|
2792
2789
|
expect(mockLocalCommandRunner.runCommand.mock.calls[0][2]).toMatchObject({
|
|
2793
|
-
env: { CLAUDE_CODE_OAUTH_TOKEN: 'token-low' },
|
|
2790
|
+
env: { CLAUDE_CODE_OAUTH_TOKEN: 'token-7d-low' },
|
|
2794
2791
|
});
|
|
2795
2792
|
expect(mockLocalCommandRunner.runCommand.mock.calls[1][2]).toMatchObject({
|
|
2796
|
-
env: { CLAUDE_CODE_OAUTH_TOKEN: 'token-mid' },
|
|
2793
|
+
env: { CLAUDE_CODE_OAUTH_TOKEN: 'token-7d-mid' },
|
|
2797
2794
|
});
|
|
2798
2795
|
expect(mockLocalCommandRunner.runCommand.mock.calls[2][2]).toMatchObject({
|
|
2799
|
-
env: { CLAUDE_CODE_OAUTH_TOKEN: 'token-high' },
|
|
2796
|
+
env: { CLAUDE_CODE_OAUTH_TOKEN: 'token-7d-high' },
|
|
2800
2797
|
});
|
|
2801
2798
|
});
|
|
2802
2799
|
|
|
2803
|
-
it('should
|
|
2800
|
+
it('should cap total tasks to the sum of per-token 7-day adaptive concurrent limits', async () => {
|
|
2804
2801
|
const awaitingIssues: Issue[] = Array.from({ length: 10 }, (_, i) =>
|
|
2805
2802
|
createMockIssue({
|
|
2806
2803
|
url: `url${i + 1}`,
|
|
@@ -2822,33 +2819,10 @@ describe('StartPreparationUseCase', () => {
|
|
|
2822
2819
|
});
|
|
2823
2820
|
mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
|
|
2824
2821
|
{
|
|
2825
|
-
name: 'token-
|
|
2826
|
-
token: 'token-
|
|
2827
|
-
fiveHourUtilization: 0.
|
|
2828
|
-
|
|
2829
|
-
rejected: false,
|
|
2830
|
-
modelWeeklyLimits: {},
|
|
2831
|
-
},
|
|
2832
|
-
{
|
|
2833
|
-
name: 'token-two-slots',
|
|
2834
|
-
token: 'token-two-slots',
|
|
2835
|
-
fiveHourUtilization: 0.85,
|
|
2836
|
-
blocked: false,
|
|
2837
|
-
rejected: false,
|
|
2838
|
-
modelWeeklyLimits: {},
|
|
2839
|
-
},
|
|
2840
|
-
{
|
|
2841
|
-
name: 'token-one-slot',
|
|
2842
|
-
token: 'token-one-slot',
|
|
2843
|
-
fiveHourUtilization: 0.9,
|
|
2844
|
-
blocked: false,
|
|
2845
|
-
rejected: false,
|
|
2846
|
-
modelWeeklyLimits: {},
|
|
2847
|
-
},
|
|
2848
|
-
{
|
|
2849
|
-
name: 'token-zero-slots',
|
|
2850
|
-
token: 'token-zero-slots',
|
|
2851
|
-
fiveHourUtilization: 0.95,
|
|
2822
|
+
name: 'token-at-90-percent-7d',
|
|
2823
|
+
token: 'token-at-90-percent-7d',
|
|
2824
|
+
fiveHourUtilization: 0.1,
|
|
2825
|
+
sevenDayUtilization: 0.9,
|
|
2852
2826
|
blocked: false,
|
|
2853
2827
|
rejected: false,
|
|
2854
2828
|
modelWeeklyLimits: {},
|
|
@@ -2868,20 +2842,13 @@ describe('StartPreparationUseCase', () => {
|
|
|
2868
2842
|
allowIssueCacheMinutes: 0,
|
|
2869
2843
|
});
|
|
2870
2844
|
|
|
2871
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(
|
|
2845
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(3);
|
|
2872
2846
|
const spawnedTokens = mockLocalCommandRunner.runCommand.mock.calls.map(
|
|
2873
2847
|
(call) => call[2]?.env?.CLAUDE_CODE_OAUTH_TOKEN,
|
|
2874
2848
|
);
|
|
2875
2849
|
expect(
|
|
2876
|
-
spawnedTokens.filter((token) => token === 'token-
|
|
2877
|
-
).toHaveLength(
|
|
2878
|
-
expect(
|
|
2879
|
-
spawnedTokens.filter((token) => token === 'token-two-slots'),
|
|
2880
|
-
).toHaveLength(2);
|
|
2881
|
-
expect(
|
|
2882
|
-
spawnedTokens.filter((token) => token === 'token-one-slot'),
|
|
2883
|
-
).toHaveLength(1);
|
|
2884
|
-
expect(spawnedTokens).not.toContain('token-zero-slots');
|
|
2850
|
+
spawnedTokens.filter((token) => token === 'token-at-90-percent-7d'),
|
|
2851
|
+
).toHaveLength(3);
|
|
2885
2852
|
});
|
|
2886
2853
|
|
|
2887
2854
|
it('should exclude a rejected token from rotation', async () => {
|
|
@@ -2907,6 +2874,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2907
2874
|
name: 'token-rejected',
|
|
2908
2875
|
token: 'token-rejected',
|
|
2909
2876
|
fiveHourUtilization: 0.1,
|
|
2877
|
+
sevenDayUtilization: 0,
|
|
2910
2878
|
blocked: false,
|
|
2911
2879
|
rejected: true,
|
|
2912
2880
|
modelWeeklyLimits: {},
|
|
@@ -2915,6 +2883,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2915
2883
|
name: 'token-ok',
|
|
2916
2884
|
token: 'token-ok',
|
|
2917
2885
|
fiveHourUtilization: 0.5,
|
|
2886
|
+
sevenDayUtilization: 0,
|
|
2918
2887
|
blocked: false,
|
|
2919
2888
|
rejected: false,
|
|
2920
2889
|
modelWeeklyLimits: {},
|
|
@@ -2966,6 +2935,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2966
2935
|
name: 'token-reset',
|
|
2967
2936
|
token: 'token-reset',
|
|
2968
2937
|
fiveHourUtilization: 0,
|
|
2938
|
+
sevenDayUtilization: 0,
|
|
2969
2939
|
blocked: false,
|
|
2970
2940
|
rejected: false,
|
|
2971
2941
|
modelWeeklyLimits: {},
|
|
@@ -2974,6 +2944,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2974
2944
|
name: 'token-busy',
|
|
2975
2945
|
token: 'token-busy',
|
|
2976
2946
|
fiveHourUtilization: 0.5,
|
|
2947
|
+
sevenDayUtilization: 0,
|
|
2977
2948
|
blocked: false,
|
|
2978
2949
|
rejected: false,
|
|
2979
2950
|
modelWeeklyLimits: {},
|
|
@@ -3025,6 +2996,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3025
2996
|
name: 'token-saturated',
|
|
3026
2997
|
token: 'token-saturated',
|
|
3027
2998
|
fiveHourUtilization: 0.95,
|
|
2999
|
+
sevenDayUtilization: 0,
|
|
3028
3000
|
blocked: false,
|
|
3029
3001
|
rejected: true,
|
|
3030
3002
|
modelWeeklyLimits: {},
|
|
@@ -3033,6 +3005,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3033
3005
|
name: 'token-ok',
|
|
3034
3006
|
token: 'token-ok',
|
|
3035
3007
|
fiveHourUtilization: 0.2,
|
|
3008
|
+
sevenDayUtilization: 0,
|
|
3036
3009
|
blocked: false,
|
|
3037
3010
|
rejected: false,
|
|
3038
3011
|
modelWeeklyLimits: {},
|
|
@@ -3084,6 +3057,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3084
3057
|
name: 'token-a',
|
|
3085
3058
|
token: 'token-a',
|
|
3086
3059
|
fiveHourUtilization: 0.1,
|
|
3060
|
+
sevenDayUtilization: 0,
|
|
3087
3061
|
blocked: false,
|
|
3088
3062
|
rejected: true,
|
|
3089
3063
|
modelWeeklyLimits: {},
|
|
@@ -3092,6 +3066,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3092
3066
|
name: 'token-b',
|
|
3093
3067
|
token: 'token-b',
|
|
3094
3068
|
fiveHourUtilization: 0.2,
|
|
3069
|
+
sevenDayUtilization: 0,
|
|
3095
3070
|
blocked: false,
|
|
3096
3071
|
rejected: true,
|
|
3097
3072
|
modelWeeklyLimits: {},
|
|
@@ -3169,7 +3144,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3169
3144
|
expect(mockLocalCommandRunner.runCommand.mock.calls[0][2]).toBeUndefined();
|
|
3170
3145
|
});
|
|
3171
3146
|
|
|
3172
|
-
it('should
|
|
3147
|
+
it('should exclude a token whose seven_day_sonnet weekly limit is rejected when the model is sonnet', async () => {
|
|
3173
3148
|
const awaitingIssue = createMockIssue({
|
|
3174
3149
|
url: 'url1',
|
|
3175
3150
|
title: 'Issue 1',
|
|
@@ -3193,6 +3168,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3193
3168
|
name: 'token-sonnet-exhausted',
|
|
3194
3169
|
token: 'token-sonnet-exhausted',
|
|
3195
3170
|
fiveHourUtilization: 0.1,
|
|
3171
|
+
sevenDayUtilization: 0,
|
|
3196
3172
|
blocked: false,
|
|
3197
3173
|
rejected: false,
|
|
3198
3174
|
modelWeeklyLimits: {
|
|
@@ -3203,6 +3179,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3203
3179
|
name: 'token-ok',
|
|
3204
3180
|
token: 'token-ok',
|
|
3205
3181
|
fiveHourUtilization: 0.5,
|
|
3182
|
+
sevenDayUtilization: 0,
|
|
3206
3183
|
blocked: false,
|
|
3207
3184
|
rejected: false,
|
|
3208
3185
|
modelWeeklyLimits: {},
|
|
@@ -3225,13 +3202,10 @@ describe('StartPreparationUseCase', () => {
|
|
|
3225
3202
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
3226
3203
|
expect(mockLocalCommandRunner.runCommand.mock.calls[0][2]).toEqual({
|
|
3227
3204
|
env: {
|
|
3228
|
-
CLAUDE_CODE_OAUTH_TOKEN: 'token-
|
|
3205
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-ok',
|
|
3229
3206
|
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8787',
|
|
3230
3207
|
},
|
|
3231
3208
|
});
|
|
3232
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls[0][1][2]).toBe(
|
|
3233
|
-
'claude-opus-4-6',
|
|
3234
|
-
);
|
|
3235
3209
|
});
|
|
3236
3210
|
|
|
3237
3211
|
it('should re-admit a token whose seven_day_sonnet rejection has been cleared by stale-reset expiry', async () => {
|
|
@@ -3258,6 +3232,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3258
3232
|
name: 'token-recovered',
|
|
3259
3233
|
token: 'token-recovered',
|
|
3260
3234
|
fiveHourUtilization: 0.1,
|
|
3235
|
+
sevenDayUtilization: 0,
|
|
3261
3236
|
blocked: false,
|
|
3262
3237
|
rejected: false,
|
|
3263
3238
|
modelWeeklyLimits: {
|
|
@@ -3268,6 +3243,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3268
3243
|
name: 'token-busy',
|
|
3269
3244
|
token: 'token-busy',
|
|
3270
3245
|
fiveHourUtilization: 0.5,
|
|
3246
|
+
sevenDayUtilization: 0,
|
|
3271
3247
|
blocked: false,
|
|
3272
3248
|
rejected: false,
|
|
3273
3249
|
modelWeeklyLimits: {},
|
|
@@ -3320,6 +3296,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3320
3296
|
name: 'token-sonnet-exhausted',
|
|
3321
3297
|
token: 'token-sonnet-exhausted',
|
|
3322
3298
|
fiveHourUtilization: 0.1,
|
|
3299
|
+
sevenDayUtilization: 0,
|
|
3323
3300
|
blocked: false,
|
|
3324
3301
|
rejected: false,
|
|
3325
3302
|
modelWeeklyLimits: {
|
|
@@ -3330,6 +3307,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3330
3307
|
name: 'token-higher-util',
|
|
3331
3308
|
token: 'token-higher-util',
|
|
3332
3309
|
fiveHourUtilization: 0.5,
|
|
3310
|
+
sevenDayUtilization: 0,
|
|
3333
3311
|
blocked: false,
|
|
3334
3312
|
rejected: false,
|
|
3335
3313
|
modelWeeklyLimits: {},
|
|
@@ -3382,6 +3360,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3382
3360
|
name: 'token-weekly-exhausted',
|
|
3383
3361
|
token: 'token-weekly-exhausted',
|
|
3384
3362
|
fiveHourUtilization: 0.1,
|
|
3363
|
+
sevenDayUtilization: 0,
|
|
3385
3364
|
blocked: false,
|
|
3386
3365
|
rejected: false,
|
|
3387
3366
|
modelWeeklyLimits: {
|
|
@@ -3392,6 +3371,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
3392
3371
|
name: 'token-ok',
|
|
3393
3372
|
token: 'token-ok',
|
|
3394
3373
|
fiveHourUtilization: 0.5,
|
|
3374
|
+
sevenDayUtilization: 0,
|
|
3395
3375
|
blocked: false,
|
|
3396
3376
|
rejected: false,
|
|
3397
3377
|
modelWeeklyLimits: {},
|
|
@@ -3419,211 +3399,6 @@ describe('StartPreparationUseCase', () => {
|
|
|
3419
3399
|
},
|
|
3420
3400
|
});
|
|
3421
3401
|
});
|
|
3422
|
-
|
|
3423
|
-
it('should include a token in rotation with opus model when seven_day_sonnet is rejected but seven_day_opus is available', async () => {
|
|
3424
|
-
const awaitingIssue = createMockIssue({
|
|
3425
|
-
url: 'url1',
|
|
3426
|
-
title: 'Issue 1',
|
|
3427
|
-
labels: ['category:impl'],
|
|
3428
|
-
status: 'Awaiting Workspace',
|
|
3429
|
-
number: 1,
|
|
3430
|
-
itemId: 'item-1',
|
|
3431
|
-
});
|
|
3432
|
-
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
3433
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
3434
|
-
createMockStoryObjectMap([awaitingIssue]),
|
|
3435
|
-
);
|
|
3436
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
3437
|
-
stdout: '',
|
|
3438
|
-
stderr: '',
|
|
3439
|
-
exitCode: 0,
|
|
3440
|
-
});
|
|
3441
|
-
const futureReset = Math.floor(Date.now() / 1000) + 3600;
|
|
3442
|
-
mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
|
|
3443
|
-
{
|
|
3444
|
-
token: 'token-sonnet-exhausted',
|
|
3445
|
-
fiveHourUtilization: 0.1,
|
|
3446
|
-
blocked: false,
|
|
3447
|
-
rejected: false,
|
|
3448
|
-
modelWeeklyLimits: {
|
|
3449
|
-
seven_day_sonnet: { rejected: true, resetsAt: futureReset },
|
|
3450
|
-
},
|
|
3451
|
-
},
|
|
3452
|
-
]);
|
|
3453
|
-
|
|
3454
|
-
await useCase.run({
|
|
3455
|
-
projectUrl: 'https://github.com/user/repo',
|
|
3456
|
-
defaultAgentName: 'agent1',
|
|
3457
|
-
defaultLlmModelName: 'claude-sonnet-4-6',
|
|
3458
|
-
defaultLlmAgentName: null,
|
|
3459
|
-
configFilePath: '/path/to/config.yml',
|
|
3460
|
-
maximumPreparingIssuesCount: null,
|
|
3461
|
-
utilizationPercentageThreshold: 90,
|
|
3462
|
-
allowedIssueAuthors: null,
|
|
3463
|
-
codexHomeCandidates: null,
|
|
3464
|
-
allowIssueCacheMinutes: 0,
|
|
3465
|
-
});
|
|
3466
|
-
|
|
3467
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
3468
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls[0][1][2]).toBe(
|
|
3469
|
-
'claude-opus-4-6',
|
|
3470
|
-
);
|
|
3471
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls[0][2]).toEqual({
|
|
3472
|
-
env: {
|
|
3473
|
-
CLAUDE_CODE_OAUTH_TOKEN: 'token-sonnet-exhausted',
|
|
3474
|
-
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8787',
|
|
3475
|
-
},
|
|
3476
|
-
});
|
|
3477
|
-
});
|
|
3478
|
-
|
|
3479
|
-
it('should exclude a token from rotation when both seven_day_sonnet and seven_day_opus weekly limits are rejected', async () => {
|
|
3480
|
-
const awaitingIssue = createMockIssue({
|
|
3481
|
-
url: 'url1',
|
|
3482
|
-
title: 'Issue 1',
|
|
3483
|
-
labels: ['category:impl'],
|
|
3484
|
-
status: 'Awaiting Workspace',
|
|
3485
|
-
number: 1,
|
|
3486
|
-
itemId: 'item-1',
|
|
3487
|
-
});
|
|
3488
|
-
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
3489
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
3490
|
-
createMockStoryObjectMap([awaitingIssue]),
|
|
3491
|
-
);
|
|
3492
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
3493
|
-
stdout: '',
|
|
3494
|
-
stderr: '',
|
|
3495
|
-
exitCode: 0,
|
|
3496
|
-
});
|
|
3497
|
-
const futureReset = Math.floor(Date.now() / 1000) + 3600;
|
|
3498
|
-
mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
|
|
3499
|
-
{
|
|
3500
|
-
token: 'token-all-exhausted',
|
|
3501
|
-
fiveHourUtilization: 0.1,
|
|
3502
|
-
blocked: false,
|
|
3503
|
-
rejected: false,
|
|
3504
|
-
modelWeeklyLimits: {
|
|
3505
|
-
seven_day_sonnet: { rejected: true, resetsAt: futureReset },
|
|
3506
|
-
seven_day_opus: { rejected: true, resetsAt: futureReset },
|
|
3507
|
-
},
|
|
3508
|
-
},
|
|
3509
|
-
]);
|
|
3510
|
-
|
|
3511
|
-
await useCase.run({
|
|
3512
|
-
projectUrl: 'https://github.com/user/repo',
|
|
3513
|
-
defaultAgentName: 'agent1',
|
|
3514
|
-
defaultLlmModelName: 'claude-sonnet-4-6',
|
|
3515
|
-
defaultLlmAgentName: null,
|
|
3516
|
-
configFilePath: '/path/to/config.yml',
|
|
3517
|
-
maximumPreparingIssuesCount: null,
|
|
3518
|
-
utilizationPercentageThreshold: 90,
|
|
3519
|
-
allowedIssueAuthors: null,
|
|
3520
|
-
codexHomeCandidates: null,
|
|
3521
|
-
allowIssueCacheMinutes: 0,
|
|
3522
|
-
});
|
|
3523
|
-
|
|
3524
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
3525
|
-
});
|
|
3526
|
-
|
|
3527
|
-
it('should use default model for a token with no modelWeeklyLimits entries', async () => {
|
|
3528
|
-
const awaitingIssue = createMockIssue({
|
|
3529
|
-
url: 'url1',
|
|
3530
|
-
title: 'Issue 1',
|
|
3531
|
-
labels: ['category:impl'],
|
|
3532
|
-
status: 'Awaiting Workspace',
|
|
3533
|
-
number: 1,
|
|
3534
|
-
itemId: 'item-1',
|
|
3535
|
-
});
|
|
3536
|
-
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
3537
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
3538
|
-
createMockStoryObjectMap([awaitingIssue]),
|
|
3539
|
-
);
|
|
3540
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
3541
|
-
stdout: '',
|
|
3542
|
-
stderr: '',
|
|
3543
|
-
exitCode: 0,
|
|
3544
|
-
});
|
|
3545
|
-
mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
|
|
3546
|
-
{
|
|
3547
|
-
token: 'token-no-limits',
|
|
3548
|
-
fiveHourUtilization: 0.1,
|
|
3549
|
-
blocked: false,
|
|
3550
|
-
rejected: false,
|
|
3551
|
-
modelWeeklyLimits: {},
|
|
3552
|
-
},
|
|
3553
|
-
]);
|
|
3554
|
-
|
|
3555
|
-
await useCase.run({
|
|
3556
|
-
projectUrl: 'https://github.com/user/repo',
|
|
3557
|
-
defaultAgentName: 'agent1',
|
|
3558
|
-
defaultLlmModelName: 'claude-sonnet-4-6',
|
|
3559
|
-
defaultLlmAgentName: null,
|
|
3560
|
-
configFilePath: '/path/to/config.yml',
|
|
3561
|
-
maximumPreparingIssuesCount: null,
|
|
3562
|
-
utilizationPercentageThreshold: 90,
|
|
3563
|
-
allowedIssueAuthors: null,
|
|
3564
|
-
codexHomeCandidates: null,
|
|
3565
|
-
allowIssueCacheMinutes: 0,
|
|
3566
|
-
});
|
|
3567
|
-
|
|
3568
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
3569
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls[0][1][2]).toBe(
|
|
3570
|
-
'claude-sonnet-4-6',
|
|
3571
|
-
);
|
|
3572
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls[0][2]).toEqual({
|
|
3573
|
-
env: {
|
|
3574
|
-
CLAUDE_CODE_OAUTH_TOKEN: 'token-no-limits',
|
|
3575
|
-
ANTHROPIC_BASE_URL: 'http://127.0.0.1:8787',
|
|
3576
|
-
},
|
|
3577
|
-
});
|
|
3578
|
-
});
|
|
3579
|
-
|
|
3580
|
-
it('should exclude a token when seven_day general limit is rejected even if per-model limits are available', async () => {
|
|
3581
|
-
const awaitingIssue = createMockIssue({
|
|
3582
|
-
url: 'url1',
|
|
3583
|
-
title: 'Issue 1',
|
|
3584
|
-
labels: ['category:impl'],
|
|
3585
|
-
status: 'Awaiting Workspace',
|
|
3586
|
-
number: 1,
|
|
3587
|
-
itemId: 'item-1',
|
|
3588
|
-
});
|
|
3589
|
-
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
3590
|
-
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
3591
|
-
createMockStoryObjectMap([awaitingIssue]),
|
|
3592
|
-
);
|
|
3593
|
-
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
3594
|
-
stdout: '',
|
|
3595
|
-
stderr: '',
|
|
3596
|
-
exitCode: 0,
|
|
3597
|
-
});
|
|
3598
|
-
const futureReset = Math.floor(Date.now() / 1000) + 3600;
|
|
3599
|
-
mockClaudeTokenUsageRepository.getAvailableTokenUsages.mockResolvedValue([
|
|
3600
|
-
{
|
|
3601
|
-
token: 'token-general-limit-rejected',
|
|
3602
|
-
fiveHourUtilization: 0.1,
|
|
3603
|
-
blocked: false,
|
|
3604
|
-
rejected: false,
|
|
3605
|
-
modelWeeklyLimits: {
|
|
3606
|
-
seven_day: { rejected: true, resetsAt: futureReset },
|
|
3607
|
-
seven_day_sonnet: { rejected: false, resetsAt: futureReset },
|
|
3608
|
-
},
|
|
3609
|
-
},
|
|
3610
|
-
]);
|
|
3611
|
-
|
|
3612
|
-
await useCase.run({
|
|
3613
|
-
projectUrl: 'https://github.com/user/repo',
|
|
3614
|
-
defaultAgentName: 'agent1',
|
|
3615
|
-
defaultLlmModelName: 'claude-sonnet-4-6',
|
|
3616
|
-
defaultLlmAgentName: null,
|
|
3617
|
-
configFilePath: '/path/to/config.yml',
|
|
3618
|
-
maximumPreparingIssuesCount: null,
|
|
3619
|
-
utilizationPercentageThreshold: 90,
|
|
3620
|
-
allowedIssueAuthors: null,
|
|
3621
|
-
codexHomeCandidates: null,
|
|
3622
|
-
allowIssueCacheMinutes: 0,
|
|
3623
|
-
});
|
|
3624
|
-
|
|
3625
|
-
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
3626
|
-
});
|
|
3627
3402
|
});
|
|
3628
3403
|
|
|
3629
3404
|
describe('StartPreparationUseCase.buildRotationOrder', () => {
|
|
@@ -3669,20 +3444,22 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
|
|
|
3669
3444
|
mockClaudeTokenUsageRepositoryForRotation,
|
|
3670
3445
|
);
|
|
3671
3446
|
|
|
3672
|
-
it('lists selected tokens first in ascending utilization order then excluded tokens', () => {
|
|
3447
|
+
it('lists selected tokens first in ascending 7-day utilization order then excluded tokens', () => {
|
|
3673
3448
|
const tokenUsages = [
|
|
3674
3449
|
{
|
|
3675
|
-
name: 'high-util',
|
|
3450
|
+
name: 'high-7d-util',
|
|
3676
3451
|
token: 'sk-ant-high',
|
|
3677
|
-
fiveHourUtilization: 0.
|
|
3452
|
+
fiveHourUtilization: 0.1,
|
|
3453
|
+
sevenDayUtilization: 0.8,
|
|
3678
3454
|
blocked: false,
|
|
3679
3455
|
rejected: false,
|
|
3680
3456
|
modelWeeklyLimits: {},
|
|
3681
3457
|
},
|
|
3682
3458
|
{
|
|
3683
|
-
name: 'low-util',
|
|
3459
|
+
name: 'low-7d-util',
|
|
3684
3460
|
token: 'sk-ant-low',
|
|
3685
|
-
fiveHourUtilization: 0.
|
|
3461
|
+
fiveHourUtilization: 0.5,
|
|
3462
|
+
sevenDayUtilization: 0.1,
|
|
3686
3463
|
blocked: false,
|
|
3687
3464
|
rejected: false,
|
|
3688
3465
|
modelWeeklyLimits: {},
|
|
@@ -3691,6 +3468,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
|
|
|
3691
3468
|
name: 'blocked-token',
|
|
3692
3469
|
token: 'sk-ant-blocked',
|
|
3693
3470
|
fiveHourUtilization: 0.0,
|
|
3471
|
+
sevenDayUtilization: 0,
|
|
3694
3472
|
blocked: true,
|
|
3695
3473
|
rejected: false,
|
|
3696
3474
|
modelWeeklyLimits: {},
|
|
@@ -3698,8 +3476,8 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
|
|
|
3698
3476
|
];
|
|
3699
3477
|
const result = useCase.buildRotationOrder(tokenUsages, 90, null);
|
|
3700
3478
|
|
|
3701
|
-
expect(result[0].name).toBe('low-util');
|
|
3702
|
-
expect(result[1].name).toBe('high-util');
|
|
3479
|
+
expect(result[0].name).toBe('low-7d-util');
|
|
3480
|
+
expect(result[1].name).toBe('high-7d-util');
|
|
3703
3481
|
expect(result[2].name).toBe('blocked-token');
|
|
3704
3482
|
expect(result[2].blocked).toBe(true);
|
|
3705
3483
|
expect(result[2].thresholdExcluded).toBe(false);
|
|
@@ -3711,6 +3489,7 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
|
|
|
3711
3489
|
name: 'my-token',
|
|
3712
3490
|
token: 'sk-ant-secret-value',
|
|
3713
3491
|
fiveHourUtilization: 0.1,
|
|
3492
|
+
sevenDayUtilization: 0,
|
|
3714
3493
|
blocked: false,
|
|
3715
3494
|
rejected: false,
|
|
3716
3495
|
modelWeeklyLimits: {},
|
|
@@ -3723,12 +3502,13 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
|
|
|
3723
3502
|
expect(result[0].name).toBe('my-token');
|
|
3724
3503
|
});
|
|
3725
3504
|
|
|
3726
|
-
it('marks thresholdExcluded true when token
|
|
3505
|
+
it('marks thresholdExcluded true when token 5h utilization meets or exceeds the threshold', () => {
|
|
3727
3506
|
const tokenUsages = [
|
|
3728
3507
|
{
|
|
3729
3508
|
name: 'over-threshold',
|
|
3730
3509
|
token: 'sk-ant-over',
|
|
3731
3510
|
fiveHourUtilization: 0.95,
|
|
3511
|
+
sevenDayUtilization: 0,
|
|
3732
3512
|
blocked: false,
|
|
3733
3513
|
rejected: false,
|
|
3734
3514
|
modelWeeklyLimits: {},
|
|
@@ -3742,12 +3522,13 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
|
|
|
3742
3522
|
expect(result[0].rejected).toBe(false);
|
|
3743
3523
|
});
|
|
3744
3524
|
|
|
3745
|
-
it('
|
|
3525
|
+
it('marks thresholdExcluded true for tokens at or above the 5h utilization threshold', () => {
|
|
3746
3526
|
const tokenUsages = [
|
|
3747
3527
|
{
|
|
3748
|
-
name: '
|
|
3749
|
-
token: 'sk-ant-
|
|
3750
|
-
fiveHourUtilization: 0.
|
|
3528
|
+
name: 'at-threshold',
|
|
3529
|
+
token: 'sk-ant-at',
|
|
3530
|
+
fiveHourUtilization: 0.9,
|
|
3531
|
+
sevenDayUtilization: 0,
|
|
3751
3532
|
blocked: false,
|
|
3752
3533
|
rejected: false,
|
|
3753
3534
|
modelWeeklyLimits: {},
|
|
@@ -3756,9 +3537,9 @@ describe('StartPreparationUseCase.buildRotationOrder', () => {
|
|
|
3756
3537
|
const result = useCase.buildRotationOrder(tokenUsages, 90, null);
|
|
3757
3538
|
|
|
3758
3539
|
expect(result).toHaveLength(1);
|
|
3759
|
-
expect(result[0].thresholdExcluded).toBe(
|
|
3540
|
+
expect(result[0].thresholdExcluded).toBe(true);
|
|
3760
3541
|
expect(result[0].blocked).toBe(false);
|
|
3761
3542
|
expect(result[0].rejected).toBe(false);
|
|
3762
|
-
expect(result[0].fiveHourUtilization).toBe(0.
|
|
3543
|
+
expect(result[0].fiveHourUtilization).toBe(0.9);
|
|
3763
3544
|
});
|
|
3764
3545
|
});
|