github-issue-tower-defence-management 1.116.2 → 1.116.5
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/.github/workflows/commit-lint.yml +1 -1
- package/.github/workflows/configs/commitlint.config.cjs +32 -0
- package/.github/workflows/umino-project.yml +9 -3
- package/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js +18 -6
- package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +74 -49
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/githubRateLimitRetry.js +77 -0
- package/bin/adapter/repositories/issue/githubRateLimitRetry.js.map +1 -0
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +6 -6
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
- package/bin/domain/usecases/OauthTokenSelectUseCase.js +1 -1
- package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
- package/package.json +1 -1
- package/renovate.json +1 -0
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +11 -11
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +69 -7
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +19 -8
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +111 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +243 -193
- package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +167 -0
- package/src/adapter/repositories/issue/githubRateLimitRetry.ts +105 -0
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +25 -3
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +7 -7
- package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +13 -4
- package/src/domain/usecases/OauthTokenSelectUseCase.ts +1 -1
- package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +1 -1
- package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts +1 -0
- package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +4 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts +10 -0
- package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts.map +1 -0
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +1 -1
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts +1 -1
- package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts.map +1 -1
|
@@ -593,6 +593,113 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
593
593
|
});
|
|
594
594
|
});
|
|
595
595
|
|
|
596
|
+
describe('rate-limit-aware retry on console operations', () => {
|
|
597
|
+
afterEach(() => {
|
|
598
|
+
jest.restoreAllMocks();
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('retries a transient 403 rate-limit response and resolves the operation after a success', async () => {
|
|
602
|
+
const fetchSpy = jest
|
|
603
|
+
.spyOn(global, 'fetch')
|
|
604
|
+
.mockResolvedValueOnce(
|
|
605
|
+
new Response(JSON.stringify({ message: 'API rate limit exceeded' }), {
|
|
606
|
+
status: 403,
|
|
607
|
+
headers: { 'x-ratelimit-remaining': '0' },
|
|
608
|
+
}),
|
|
609
|
+
)
|
|
610
|
+
.mockResolvedValueOnce(
|
|
611
|
+
new Response(JSON.stringify({ state: 'closed' }), { status: 200 }),
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
const { repository, sleep } = createApiV3CheerioRestIssueRepository();
|
|
615
|
+
await repository.closeIssueByUrl(
|
|
616
|
+
'https://github.com/HiromiShikata/test-repository/issues/42',
|
|
617
|
+
'completed',
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
621
|
+
expect(sleep).toHaveBeenCalledTimes(1);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it('retries a transient 429 secondary-rate-limit response and resolves the operation after a success', async () => {
|
|
625
|
+
const fetchSpy = jest
|
|
626
|
+
.spyOn(global, 'fetch')
|
|
627
|
+
.mockResolvedValueOnce(
|
|
628
|
+
new Response(
|
|
629
|
+
JSON.stringify({
|
|
630
|
+
message: 'You have exceeded a secondary rate limit',
|
|
631
|
+
}),
|
|
632
|
+
{ status: 429, headers: { 'retry-after': '1' } },
|
|
633
|
+
),
|
|
634
|
+
)
|
|
635
|
+
.mockResolvedValueOnce(
|
|
636
|
+
new Response(JSON.stringify({ state: 'open', merged: false }), {
|
|
637
|
+
status: 200,
|
|
638
|
+
}),
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
const { repository, sleep } = createApiV3CheerioRestIssueRepository();
|
|
642
|
+
const result = await repository.getIssueOrPullRequestState(
|
|
643
|
+
'https://github.com/HiromiShikata/test-repository/issues/42',
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
expect(result.state).toBe('open');
|
|
647
|
+
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
648
|
+
expect(sleep).toHaveBeenCalledTimes(1);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it('does not retry a genuine permission 403 and surfaces a clear permission message', async () => {
|
|
652
|
+
const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue(
|
|
653
|
+
new Response(
|
|
654
|
+
JSON.stringify({
|
|
655
|
+
message: 'Resource not accessible by integration',
|
|
656
|
+
}),
|
|
657
|
+
{ status: 403, headers: { 'x-ratelimit-remaining': '4999' } },
|
|
658
|
+
),
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
const { repository, sleep } = createApiV3CheerioRestIssueRepository();
|
|
662
|
+
await expect(
|
|
663
|
+
repository.closeIssueByUrl(
|
|
664
|
+
'https://github.com/HiromiShikata/test-repository/issues/42',
|
|
665
|
+
'completed',
|
|
666
|
+
),
|
|
667
|
+
).rejects.toThrow(
|
|
668
|
+
'Failed to close issue https://github.com/HiromiShikata/test-repository/issues/42: HTTP 403 permission denied, the token cannot perform this operation Resource not accessible by integration',
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
672
|
+
expect(sleep).not.toHaveBeenCalled();
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('surfaces a clear rate-limit message including the reset time after the bounded retries are exhausted', async () => {
|
|
676
|
+
const resetEpochSeconds = 1700000000;
|
|
677
|
+
const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue(
|
|
678
|
+
new Response(JSON.stringify({ message: 'API rate limit exceeded' }), {
|
|
679
|
+
status: 403,
|
|
680
|
+
headers: {
|
|
681
|
+
'x-ratelimit-remaining': '0',
|
|
682
|
+
'x-ratelimit-reset': String(resetEpochSeconds),
|
|
683
|
+
},
|
|
684
|
+
}),
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
const { repository, sleep } = createApiV3CheerioRestIssueRepository();
|
|
688
|
+
const expectedResetIso = new Date(resetEpochSeconds * 1000).toISOString();
|
|
689
|
+
await expect(
|
|
690
|
+
repository.closeIssueByUrl(
|
|
691
|
+
'https://github.com/HiromiShikata/test-repository/issues/42',
|
|
692
|
+
'completed',
|
|
693
|
+
),
|
|
694
|
+
).rejects.toThrow(
|
|
695
|
+
`Failed to close issue https://github.com/HiromiShikata/test-repository/issues/42: HTTP 403 GitHub rate limit exceeded, please retry shortly (resets at ${expectedResetIso})`,
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
expect(fetchSpy).toHaveBeenCalledTimes(4);
|
|
699
|
+
expect(sleep).toHaveBeenCalledTimes(3);
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
|
|
596
703
|
describe('requestChangesWithInlineComment', () => {
|
|
597
704
|
afterEach(() => {
|
|
598
705
|
jest.restoreAllMocks();
|
|
@@ -1636,6 +1743,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
1636
1743
|
const graphqlProjectItemRepository = mock<GraphqlProjectItemRepository>();
|
|
1637
1744
|
const localStorageCacheRepository = mock<LocalStorageCacheRepository>();
|
|
1638
1745
|
const localStorageRepository = mock<LocalStorageRepository>();
|
|
1746
|
+
const sleep = jest.fn().mockResolvedValue(undefined);
|
|
1639
1747
|
|
|
1640
1748
|
const repository = new ApiV3CheerioRestIssueRepository(
|
|
1641
1749
|
apiV3IssueRepository,
|
|
@@ -1643,6 +1751,8 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
1643
1751
|
graphqlProjectItemRepository,
|
|
1644
1752
|
localStorageCacheRepository,
|
|
1645
1753
|
localStorageRepository,
|
|
1754
|
+
'dummy',
|
|
1755
|
+
sleep,
|
|
1646
1756
|
);
|
|
1647
1757
|
restIssueRepository.getIssue.mockResolvedValue({
|
|
1648
1758
|
labels: [],
|
|
@@ -1659,6 +1769,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
1659
1769
|
restIssueRepository,
|
|
1660
1770
|
graphqlProjectItemRepository,
|
|
1661
1771
|
localStorageCacheRepository,
|
|
1772
|
+
sleep,
|
|
1662
1773
|
};
|
|
1663
1774
|
};
|
|
1664
1775
|
});
|