github-issue-tower-defence-management 1.42.2 → 1.42.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.
- package/.github/workflows/create-pr.yml +1 -10
- package/.github/workflows/umino-project.yml +11 -4
- package/CHANGELOG.md +15 -0
- package/bin/adapter/repositories/GoogleSpreadsheetRepository.js +25 -21
- package/bin/adapter/repositories/GoogleSpreadsheetRepository.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +6 -1
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter/repositories/GoogleSpreadsheetRepository.integration.test.ts +120 -0
- package/src/adapter/repositories/GoogleSpreadsheetRepository.test.ts +273 -71
- package/src/adapter/repositories/GoogleSpreadsheetRepository.ts +82 -20
- package/src/domain/usecases/StartPreparationUseCase.test.ts +160 -48
- package/src/domain/usecases/StartPreparationUseCase.ts +16 -2
- package/types/adapter/repositories/GoogleSpreadsheetRepository.d.ts +67 -1
- package/types/adapter/repositories/GoogleSpreadsheetRepository.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
|
@@ -85,7 +85,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
85
85
|
IssueRepository,
|
|
86
86
|
| 'getAllOpened'
|
|
87
87
|
| 'getStoryObjectMap'
|
|
88
|
-
| '
|
|
88
|
+
| 'updateStatus'
|
|
89
89
|
| 'findRelatedOpenPRs'
|
|
90
90
|
| 'getOpenPullRequest'
|
|
91
91
|
>
|
|
@@ -107,7 +107,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
107
107
|
mockIssueRepository = {
|
|
108
108
|
getAllOpened: jest.fn(),
|
|
109
109
|
getStoryObjectMap: jest.fn().mockResolvedValue(new Map()),
|
|
110
|
-
|
|
110
|
+
updateStatus: jest.fn(),
|
|
111
111
|
findRelatedOpenPRs: jest.fn().mockResolvedValue([]),
|
|
112
112
|
getOpenPullRequest: jest.fn().mockResolvedValue(null),
|
|
113
113
|
};
|
|
@@ -194,12 +194,13 @@ describe('StartPreparationUseCase', () => {
|
|
|
194
194
|
allowedIssueAuthors: null,
|
|
195
195
|
codexHomeCandidates: null,
|
|
196
196
|
});
|
|
197
|
-
expect(mockIssueRepository.
|
|
198
|
-
expect(mockIssueRepository.
|
|
197
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
198
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][0]).toBe(mockProject);
|
|
199
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
199
200
|
url: 'url1',
|
|
200
201
|
status: 'Preparation',
|
|
201
202
|
});
|
|
202
|
-
expect(mockIssueRepository.
|
|
203
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][2]).toBe('2');
|
|
203
204
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
204
205
|
expect(mockLocalCommandRunner.runCommand.mock.calls[0]).toEqual([
|
|
205
206
|
'aw',
|
|
@@ -363,7 +364,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
363
364
|
codexHomeCandidates: null,
|
|
364
365
|
});
|
|
365
366
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
366
|
-
expect(mockIssueRepository.
|
|
367
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
367
368
|
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
368
369
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
369
370
|
'Skipping non-OPEN PR https://github.com/user/repo/pull/999: wrapper requires an open PR.',
|
|
@@ -411,7 +412,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
411
412
|
codexHomeCandidates: null,
|
|
412
413
|
});
|
|
413
414
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
414
|
-
expect(mockIssueRepository.
|
|
415
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
415
416
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
416
417
|
'Skipping PR https://github.com/user/repo/pull/999: head branch is unavailable.',
|
|
417
418
|
);
|
|
@@ -458,7 +459,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
458
459
|
codexHomeCandidates: null,
|
|
459
460
|
});
|
|
460
461
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
461
|
-
expect(mockIssueRepository.
|
|
462
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
462
463
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
463
464
|
expect.stringContaining('branch name contains unexpected characters'),
|
|
464
465
|
);
|
|
@@ -516,7 +517,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
516
517
|
codexHomeCandidates: null,
|
|
517
518
|
});
|
|
518
519
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
519
|
-
expect(mockIssueRepository.
|
|
520
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
520
521
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
521
522
|
'Skipping issue url1: 2 related open PRs found (ambiguous).',
|
|
522
523
|
);
|
|
@@ -566,7 +567,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
566
567
|
codexHomeCandidates: null,
|
|
567
568
|
});
|
|
568
569
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
569
|
-
expect(mockIssueRepository.
|
|
570
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
570
571
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
571
572
|
'Skipping issue url1: related open PR has unavailable head branch.',
|
|
572
573
|
);
|
|
@@ -611,16 +612,19 @@ describe('StartPreparationUseCase', () => {
|
|
|
611
612
|
codexHomeCandidates: null,
|
|
612
613
|
});
|
|
613
614
|
// Both awaiting issues should be updated (forward iteration: url1 first, then url2)
|
|
614
|
-
expect(mockIssueRepository.
|
|
615
|
-
expect(mockIssueRepository.
|
|
615
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(2);
|
|
616
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][0]).toBe(mockProject);
|
|
617
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
616
618
|
url: 'url1',
|
|
617
619
|
status: 'Preparation',
|
|
618
620
|
});
|
|
619
|
-
expect(mockIssueRepository.
|
|
621
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][2]).toBe('2');
|
|
622
|
+
expect(mockIssueRepository.updateStatus.mock.calls[1][0]).toBe(mockProject);
|
|
623
|
+
expect(mockIssueRepository.updateStatus.mock.calls[1][1]).toMatchObject({
|
|
620
624
|
url: 'url2',
|
|
621
625
|
status: 'Preparation',
|
|
622
626
|
});
|
|
623
|
-
expect(mockIssueRepository.
|
|
627
|
+
expect(mockIssueRepository.updateStatus.mock.calls[1][2]).toBe('2');
|
|
624
628
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(2);
|
|
625
629
|
});
|
|
626
630
|
it('should stop assigning after maximum preparing issues count is reached', async () => {
|
|
@@ -669,7 +673,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
669
673
|
codexHomeCandidates: null,
|
|
670
674
|
});
|
|
671
675
|
// Loop doesn't run because we're already at max (6 >= 6)
|
|
672
|
-
expect(mockIssueRepository.
|
|
676
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
673
677
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
674
678
|
});
|
|
675
679
|
it('should pass configFilePath to aw command', async () => {
|
|
@@ -1084,7 +1088,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1084
1088
|
codexHomeCandidates: null,
|
|
1085
1089
|
});
|
|
1086
1090
|
// No issues are in 'Awaiting Workspace' status, so no updates should happen
|
|
1087
|
-
expect(mockIssueRepository.
|
|
1091
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
1088
1092
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
1089
1093
|
});
|
|
1090
1094
|
it('should use custom maximumPreparingIssuesCount when provided', async () => {
|
|
@@ -1119,7 +1123,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1119
1123
|
allowedIssueAuthors: null,
|
|
1120
1124
|
codexHomeCandidates: null,
|
|
1121
1125
|
});
|
|
1122
|
-
expect(mockIssueRepository.
|
|
1126
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(3);
|
|
1123
1127
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(3);
|
|
1124
1128
|
});
|
|
1125
1129
|
it('should use default maximumPreparingIssuesCount of 6 when null is provided', async () => {
|
|
@@ -1154,7 +1158,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1154
1158
|
allowedIssueAuthors: null,
|
|
1155
1159
|
codexHomeCandidates: null,
|
|
1156
1160
|
});
|
|
1157
|
-
expect(mockIssueRepository.
|
|
1161
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(6);
|
|
1158
1162
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(6);
|
|
1159
1163
|
});
|
|
1160
1164
|
|
|
@@ -1223,9 +1227,9 @@ describe('StartPreparationUseCase', () => {
|
|
|
1223
1227
|
codexHomeCandidates: null,
|
|
1224
1228
|
});
|
|
1225
1229
|
|
|
1226
|
-
expect(mockIssueRepository.
|
|
1227
|
-
const updatedUrls = mockIssueRepository.
|
|
1228
|
-
(call) => call[
|
|
1230
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(2);
|
|
1231
|
+
const updatedUrls = mockIssueRepository.updateStatus.mock.calls.map(
|
|
1232
|
+
(call) => call[1].url,
|
|
1229
1233
|
);
|
|
1230
1234
|
expect(updatedUrls).toContain('https://github.com/user/repo/issues/100');
|
|
1231
1235
|
expect(updatedUrls).toContain('https://github.com/user/repo/issues/101');
|
|
@@ -1264,7 +1268,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1264
1268
|
codexHomeCandidates: null,
|
|
1265
1269
|
});
|
|
1266
1270
|
|
|
1267
|
-
expect(mockIssueRepository.
|
|
1271
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
1268
1272
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
1269
1273
|
expect(mockProjectRepository.getByUrl).not.toHaveBeenCalled();
|
|
1270
1274
|
});
|
|
@@ -1308,7 +1312,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1308
1312
|
codexHomeCandidates: null,
|
|
1309
1313
|
});
|
|
1310
1314
|
|
|
1311
|
-
expect(mockIssueRepository.
|
|
1315
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
1312
1316
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
1313
1317
|
});
|
|
1314
1318
|
|
|
@@ -1360,7 +1364,9 @@ describe('StartPreparationUseCase', () => {
|
|
|
1360
1364
|
defaultMax * Math.pow(1 - normalizedUtilizationBeyondThreshold, 2),
|
|
1361
1365
|
);
|
|
1362
1366
|
expect(mockProjectRepository.getByUrl).toHaveBeenCalled();
|
|
1363
|
-
expect(mockIssueRepository.
|
|
1367
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(
|
|
1368
|
+
expectedMax,
|
|
1369
|
+
);
|
|
1364
1370
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(
|
|
1365
1371
|
expectedMax,
|
|
1366
1372
|
);
|
|
@@ -1398,7 +1404,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1398
1404
|
codexHomeCandidates: null,
|
|
1399
1405
|
});
|
|
1400
1406
|
|
|
1401
|
-
expect(mockIssueRepository.
|
|
1407
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
1402
1408
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
1403
1409
|
});
|
|
1404
1410
|
|
|
@@ -1440,7 +1446,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1440
1446
|
codexHomeCandidates: null,
|
|
1441
1447
|
});
|
|
1442
1448
|
|
|
1443
|
-
expect(mockIssueRepository.
|
|
1449
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(6);
|
|
1444
1450
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(6);
|
|
1445
1451
|
});
|
|
1446
1452
|
|
|
@@ -1478,7 +1484,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1478
1484
|
});
|
|
1479
1485
|
|
|
1480
1486
|
expect(mockProjectRepository.getByUrl).not.toHaveBeenCalled();
|
|
1481
|
-
expect(mockIssueRepository.
|
|
1487
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
1482
1488
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
1483
1489
|
});
|
|
1484
1490
|
|
|
@@ -1525,7 +1531,9 @@ describe('StartPreparationUseCase', () => {
|
|
|
1525
1531
|
const expectedMax = Math.floor(
|
|
1526
1532
|
6 * Math.pow(1 - normalizedUtilizationBeyondThreshold, 2),
|
|
1527
1533
|
);
|
|
1528
|
-
expect(mockIssueRepository.
|
|
1534
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(
|
|
1535
|
+
expectedMax,
|
|
1536
|
+
);
|
|
1529
1537
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(
|
|
1530
1538
|
expectedMax,
|
|
1531
1539
|
);
|
|
@@ -1552,7 +1560,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1552
1560
|
}),
|
|
1553
1561
|
).rejects.toThrow('Claude credentials file not found');
|
|
1554
1562
|
|
|
1555
|
-
expect(mockIssueRepository.
|
|
1563
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
1556
1564
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
1557
1565
|
});
|
|
1558
1566
|
|
|
@@ -1589,7 +1597,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1589
1597
|
codexHomeCandidates: null,
|
|
1590
1598
|
});
|
|
1591
1599
|
|
|
1592
|
-
expect(mockIssueRepository.
|
|
1600
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
1593
1601
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
1594
1602
|
expect(mockProjectRepository.getByUrl).not.toHaveBeenCalled();
|
|
1595
1603
|
});
|
|
@@ -1632,7 +1640,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
1632
1640
|
codexHomeCandidates: null,
|
|
1633
1641
|
});
|
|
1634
1642
|
|
|
1635
|
-
expect(mockIssueRepository.
|
|
1643
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
1636
1644
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
1637
1645
|
});
|
|
1638
1646
|
|
|
@@ -1680,8 +1688,8 @@ describe('StartPreparationUseCase', () => {
|
|
|
1680
1688
|
codexHomeCandidates: null,
|
|
1681
1689
|
});
|
|
1682
1690
|
|
|
1683
|
-
expect(mockIssueRepository.
|
|
1684
|
-
expect(mockIssueRepository.
|
|
1691
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
1692
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
1685
1693
|
url: 'https://github.com/user/repo/issues/3',
|
|
1686
1694
|
status: 'Preparation',
|
|
1687
1695
|
});
|
|
@@ -1739,8 +1747,8 @@ describe('StartPreparationUseCase', () => {
|
|
|
1739
1747
|
codexHomeCandidates: null,
|
|
1740
1748
|
});
|
|
1741
1749
|
|
|
1742
|
-
expect(mockIssueRepository.
|
|
1743
|
-
expect(mockIssueRepository.
|
|
1750
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
1751
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
1744
1752
|
url: 'https://github.com/user/repo/issues/2',
|
|
1745
1753
|
status: 'Preparation',
|
|
1746
1754
|
});
|
|
@@ -1801,8 +1809,8 @@ describe('StartPreparationUseCase', () => {
|
|
|
1801
1809
|
codexHomeCandidates: null,
|
|
1802
1810
|
});
|
|
1803
1811
|
|
|
1804
|
-
expect(mockIssueRepository.
|
|
1805
|
-
expect(mockIssueRepository.
|
|
1812
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
1813
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
1806
1814
|
url: 'https://github.com/user/repo/issues/2',
|
|
1807
1815
|
status: 'Preparation',
|
|
1808
1816
|
});
|
|
@@ -1852,8 +1860,8 @@ describe('StartPreparationUseCase', () => {
|
|
|
1852
1860
|
codexHomeCandidates: null,
|
|
1853
1861
|
});
|
|
1854
1862
|
|
|
1855
|
-
expect(mockIssueRepository.
|
|
1856
|
-
expect(mockIssueRepository.
|
|
1863
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
1864
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
1857
1865
|
url: 'https://github.com/user/repo/issues/1',
|
|
1858
1866
|
status: 'Preparation',
|
|
1859
1867
|
});
|
|
@@ -1903,8 +1911,8 @@ describe('StartPreparationUseCase', () => {
|
|
|
1903
1911
|
codexHomeCandidates: null,
|
|
1904
1912
|
});
|
|
1905
1913
|
|
|
1906
|
-
expect(mockIssueRepository.
|
|
1907
|
-
expect(mockIssueRepository.
|
|
1914
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
1915
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
1908
1916
|
url: 'https://github.com/user/repo/issues/1',
|
|
1909
1917
|
status: 'Preparation',
|
|
1910
1918
|
});
|
|
@@ -1954,8 +1962,8 @@ describe('StartPreparationUseCase', () => {
|
|
|
1954
1962
|
codexHomeCandidates: null,
|
|
1955
1963
|
});
|
|
1956
1964
|
|
|
1957
|
-
expect(mockIssueRepository.
|
|
1958
|
-
expect(mockIssueRepository.
|
|
1965
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
1966
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
1959
1967
|
url: 'https://github.com/user/repo/issues/1',
|
|
1960
1968
|
status: 'Preparation',
|
|
1961
1969
|
});
|
|
@@ -2012,8 +2020,8 @@ describe('StartPreparationUseCase', () => {
|
|
|
2012
2020
|
codexHomeCandidates: null,
|
|
2013
2021
|
});
|
|
2014
2022
|
|
|
2015
|
-
expect(mockIssueRepository.
|
|
2016
|
-
expect(mockIssueRepository.
|
|
2023
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
2024
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
2017
2025
|
url: 'https://github.com/user/repo/issues/1',
|
|
2018
2026
|
status: 'Preparation',
|
|
2019
2027
|
});
|
|
@@ -2061,7 +2069,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2061
2069
|
codexHomeCandidates: null,
|
|
2062
2070
|
});
|
|
2063
2071
|
|
|
2064
|
-
expect(mockIssueRepository.
|
|
2072
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(2);
|
|
2065
2073
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(2);
|
|
2066
2074
|
});
|
|
2067
2075
|
|
|
@@ -2109,7 +2117,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2109
2117
|
codexHomeCandidates: null,
|
|
2110
2118
|
});
|
|
2111
2119
|
|
|
2112
|
-
expect(mockIssueRepository.
|
|
2120
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(2);
|
|
2113
2121
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(2);
|
|
2114
2122
|
});
|
|
2115
2123
|
|
|
@@ -2159,7 +2167,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
2159
2167
|
codexHomeCandidates: null,
|
|
2160
2168
|
});
|
|
2161
2169
|
|
|
2162
|
-
expect(mockIssueRepository.
|
|
2170
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
2163
2171
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
2164
2172
|
});
|
|
2165
2173
|
|
|
@@ -2396,4 +2404,108 @@ describe('StartPreparationUseCase', () => {
|
|
|
2396
2404
|
],
|
|
2397
2405
|
).toBe('.codex-dev1');
|
|
2398
2406
|
});
|
|
2407
|
+
|
|
2408
|
+
it('should persist Preparation status via updateStatus with resolved status option id (regression for issue #519)', async () => {
|
|
2409
|
+
const awaitingIssue = createMockIssue({
|
|
2410
|
+
url: 'https://github.com/user/repo/issues/519',
|
|
2411
|
+
title: 'Regression issue',
|
|
2412
|
+
labels: ['category:impl'],
|
|
2413
|
+
status: 'Awaiting Workspace',
|
|
2414
|
+
itemId: 'item-regression',
|
|
2415
|
+
});
|
|
2416
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
2417
|
+
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
2418
|
+
createMockStoryObjectMap([awaitingIssue]),
|
|
2419
|
+
);
|
|
2420
|
+
mockIssueRepository.getAllOpened.mockResolvedValueOnce([awaitingIssue]);
|
|
2421
|
+
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
2422
|
+
stdout: '',
|
|
2423
|
+
stderr: '',
|
|
2424
|
+
exitCode: 0,
|
|
2425
|
+
});
|
|
2426
|
+
|
|
2427
|
+
await useCase.run({
|
|
2428
|
+
projectUrl: 'https://github.com/user/repo',
|
|
2429
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
2430
|
+
preparationStatus: 'Preparation',
|
|
2431
|
+
defaultAgentName: 'agent1',
|
|
2432
|
+
defaultLlmModelName: 'claude-opus',
|
|
2433
|
+
defaultLlmAgentName: null,
|
|
2434
|
+
configFilePath: '/path/to/config.yml',
|
|
2435
|
+
maximumPreparingIssuesCount: null,
|
|
2436
|
+
utilizationPercentageThreshold: 90,
|
|
2437
|
+
allowedIssueAuthors: null,
|
|
2438
|
+
codexHomeCandidates: null,
|
|
2439
|
+
});
|
|
2440
|
+
|
|
2441
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(1);
|
|
2442
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][0]).toBe(mockProject);
|
|
2443
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][1]).toMatchObject({
|
|
2444
|
+
url: 'https://github.com/user/repo/issues/519',
|
|
2445
|
+
itemId: 'item-regression',
|
|
2446
|
+
});
|
|
2447
|
+
expect(mockIssueRepository.updateStatus.mock.calls[0][2]).toBe('2');
|
|
2448
|
+
const updateStatusCallOrder =
|
|
2449
|
+
mockIssueRepository.updateStatus.mock.invocationCallOrder[0];
|
|
2450
|
+
const runCommandCallOrder =
|
|
2451
|
+
mockLocalCommandRunner.runCommand.mock.invocationCallOrder[0];
|
|
2452
|
+
expect(updateStatusCallOrder).toBeLessThan(runCommandCallOrder);
|
|
2453
|
+
});
|
|
2454
|
+
|
|
2455
|
+
it('should return early and log an error when preparationStatus option is not in the project', async () => {
|
|
2456
|
+
const projectWithoutPreparation: Project = {
|
|
2457
|
+
...createMockProject(),
|
|
2458
|
+
status: {
|
|
2459
|
+
name: 'Status',
|
|
2460
|
+
fieldId: 'status-field-id',
|
|
2461
|
+
statuses: [
|
|
2462
|
+
{
|
|
2463
|
+
id: '1',
|
|
2464
|
+
name: 'Awaiting Workspace',
|
|
2465
|
+
color: 'GRAY',
|
|
2466
|
+
description: '',
|
|
2467
|
+
},
|
|
2468
|
+
{ id: '3', name: 'Done', color: 'GREEN', description: '' },
|
|
2469
|
+
],
|
|
2470
|
+
},
|
|
2471
|
+
};
|
|
2472
|
+
const awaitingIssue = createMockIssue({
|
|
2473
|
+
url: 'url-missing-option',
|
|
2474
|
+
title: 'Missing Preparation Option',
|
|
2475
|
+
labels: ['category:impl'],
|
|
2476
|
+
status: 'Awaiting Workspace',
|
|
2477
|
+
});
|
|
2478
|
+
mockProjectRepository.getByUrl.mockResolvedValue(projectWithoutPreparation);
|
|
2479
|
+
mockProjectRepository.prepareStatus.mockImplementation(() =>
|
|
2480
|
+
Promise.resolve(projectWithoutPreparation),
|
|
2481
|
+
);
|
|
2482
|
+
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
2483
|
+
createMockStoryObjectMap([awaitingIssue]),
|
|
2484
|
+
);
|
|
2485
|
+
mockIssueRepository.getAllOpened.mockResolvedValueOnce([awaitingIssue]);
|
|
2486
|
+
const consoleErrorSpy = jest
|
|
2487
|
+
.spyOn(console, 'error')
|
|
2488
|
+
.mockImplementation(() => {});
|
|
2489
|
+
|
|
2490
|
+
await useCase.run({
|
|
2491
|
+
projectUrl: 'https://github.com/user/repo',
|
|
2492
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
2493
|
+
preparationStatus: 'Preparation',
|
|
2494
|
+
defaultAgentName: 'agent1',
|
|
2495
|
+
defaultLlmModelName: 'claude-opus',
|
|
2496
|
+
defaultLlmAgentName: null,
|
|
2497
|
+
configFilePath: '/path/to/config.yml',
|
|
2498
|
+
maximumPreparingIssuesCount: null,
|
|
2499
|
+
utilizationPercentageThreshold: 90,
|
|
2500
|
+
allowedIssueAuthors: null,
|
|
2501
|
+
codexHomeCandidates: null,
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
2505
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
|
|
2506
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
2507
|
+
"Preparation status option 'Preparation' not found in project.",
|
|
2508
|
+
);
|
|
2509
|
+
consoleErrorSpy.mockRestore();
|
|
2510
|
+
});
|
|
2399
2511
|
});
|
|
@@ -13,7 +13,7 @@ export class StartPreparationUseCase {
|
|
|
13
13
|
IssueRepository,
|
|
14
14
|
| 'getAllOpened'
|
|
15
15
|
| 'getStoryObjectMap'
|
|
16
|
-
| '
|
|
16
|
+
| 'updateStatus'
|
|
17
17
|
| 'findRelatedOpenPRs'
|
|
18
18
|
| 'getOpenPullRequest'
|
|
19
19
|
>,
|
|
@@ -95,6 +95,16 @@ export class StartPreparationUseCase {
|
|
|
95
95
|
await this.issueRepository.getStoryObjectMap(project);
|
|
96
96
|
const allIssues = await this.issueRepository.getAllOpened(project);
|
|
97
97
|
|
|
98
|
+
const preparationStatusOption = project.status.statuses.find(
|
|
99
|
+
(s) => s.name === params.preparationStatus,
|
|
100
|
+
);
|
|
101
|
+
if (!preparationStatusOption) {
|
|
102
|
+
console.error(
|
|
103
|
+
`Preparation status option '${params.preparationStatus}' not found in project.`,
|
|
104
|
+
);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
98
108
|
const awaitingWorkspaceIssues = Array.from(storyObjectMap.values())
|
|
99
109
|
.map((storyObject) => storyObject.issues)
|
|
100
110
|
.flat()
|
|
@@ -211,8 +221,12 @@ export class StartPreparationUseCase {
|
|
|
211
221
|
continue;
|
|
212
222
|
}
|
|
213
223
|
|
|
224
|
+
await this.issueRepository.updateStatus(
|
|
225
|
+
project,
|
|
226
|
+
issue,
|
|
227
|
+
preparationStatusOption.id,
|
|
228
|
+
);
|
|
214
229
|
issue.status = params.preparationStatus;
|
|
215
|
-
await this.issueRepository.update(issue, project);
|
|
216
230
|
|
|
217
231
|
const awArgs: string[] = [
|
|
218
232
|
issue.url,
|
|
@@ -1,13 +1,79 @@
|
|
|
1
1
|
import { SpreadsheetRepository } from '../../domain/usecases/adapter-interfaces/SpreadsheetRepository';
|
|
2
2
|
import { LocalStorageRepository } from './LocalStorageRepository';
|
|
3
|
+
interface SheetsApiClient {
|
|
4
|
+
spreadsheets: {
|
|
5
|
+
get(params: {
|
|
6
|
+
spreadsheetId: string;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
status: number;
|
|
9
|
+
data: {
|
|
10
|
+
sheets?: Array<{
|
|
11
|
+
properties?: {
|
|
12
|
+
title?: string | null;
|
|
13
|
+
} | null;
|
|
14
|
+
}> | null;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
values: {
|
|
18
|
+
get(params: {
|
|
19
|
+
spreadsheetId: string;
|
|
20
|
+
range: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
status: number;
|
|
23
|
+
data: {
|
|
24
|
+
values?: unknown[][] | null;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
|
27
|
+
update(params: {
|
|
28
|
+
spreadsheetId: string;
|
|
29
|
+
range: string;
|
|
30
|
+
valueInputOption: string;
|
|
31
|
+
requestBody: {
|
|
32
|
+
values: string[][];
|
|
33
|
+
};
|
|
34
|
+
}): Promise<{
|
|
35
|
+
status: number;
|
|
36
|
+
data: unknown;
|
|
37
|
+
}>;
|
|
38
|
+
append(params: {
|
|
39
|
+
spreadsheetId: string;
|
|
40
|
+
range: string;
|
|
41
|
+
valueInputOption: string;
|
|
42
|
+
requestBody: {
|
|
43
|
+
values: string[][];
|
|
44
|
+
};
|
|
45
|
+
}): Promise<{
|
|
46
|
+
status: number;
|
|
47
|
+
data: unknown;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
batchUpdate(params: {
|
|
51
|
+
spreadsheetId: string;
|
|
52
|
+
requestBody: {
|
|
53
|
+
requests: Array<{
|
|
54
|
+
addSheet?: {
|
|
55
|
+
properties?: {
|
|
56
|
+
title?: string;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}>;
|
|
60
|
+
};
|
|
61
|
+
}): Promise<{
|
|
62
|
+
status: number;
|
|
63
|
+
data: unknown;
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
3
67
|
export declare class GoogleSpreadsheetRepository implements SpreadsheetRepository {
|
|
4
68
|
readonly localStorageRepository: LocalStorageRepository;
|
|
5
69
|
keyFile: string;
|
|
6
|
-
|
|
70
|
+
private readonly sheetsClient;
|
|
71
|
+
constructor(localStorageRepository: LocalStorageRepository, serviceAccountKey?: string, sheetsClientFactory?: () => SheetsApiClient);
|
|
7
72
|
getSpreadsheetId: (spreadsheetUrl: string) => string;
|
|
8
73
|
getSheet: (spreadsheetUrl: string, sheetName: string) => Promise<string[][] | null>;
|
|
9
74
|
updateCell: (spreadsheetUrl: string, sheetName: string, row: number, column: number, value: string) => Promise<void>;
|
|
10
75
|
createNewSheetIfNotExists: (spreadsheetUrl: string, sheetName: string) => Promise<void>;
|
|
11
76
|
appendSheetValues: (spreadsheetUrl: string, sheetName: string, values: string[][]) => Promise<void>;
|
|
12
77
|
}
|
|
78
|
+
export {};
|
|
13
79
|
//# sourceMappingURL=GoogleSpreadsheetRepository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleSpreadsheetRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GoogleSpreadsheetRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,gEAAgE,CAAC;AAEvG,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAIlE,qBAAa,2BAA4B,YAAW,qBAAqB;
|
|
1
|
+
{"version":3,"file":"GoogleSpreadsheetRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GoogleSpreadsheetRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ export declare class StartPreparationUseCase {
|
|
|
7
7
|
private readonly issueRepository;
|
|
8
8
|
private readonly claudeRepository;
|
|
9
9
|
private readonly localCommandRunner;
|
|
10
|
-
constructor(projectRepository: Pick<ProjectRepository, 'getByUrl' | 'prepareStatus'>, issueRepository: Pick<IssueRepository, 'getAllOpened' | 'getStoryObjectMap' | '
|
|
10
|
+
constructor(projectRepository: Pick<ProjectRepository, 'getByUrl' | 'prepareStatus'>, issueRepository: Pick<IssueRepository, 'getAllOpened' | 'getStoryObjectMap' | 'updateStatus' | 'findRelatedOpenPRs' | 'getOpenPullRequest'>, claudeRepository: Pick<ClaudeRepository, 'getUsage'>, localCommandRunner: LocalCommandRunner);
|
|
11
11
|
run: (params: {
|
|
12
12
|
projectUrl: string;
|
|
13
13
|
awaitingWorkspaceStatus: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAQhC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAblB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,eAAe,CAC7B,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,mBAAmB,GACnB,
|
|
1
|
+
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAEzE,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAIlC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAQhC,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAblB,iBAAiB,EAAE,IAAI,CACtC,iBAAiB,EACjB,UAAU,GAAG,eAAe,CAC7B,EACgB,eAAe,EAAE,IAAI,CACpC,eAAe,EACb,cAAc,GACd,mBAAmB,GACnB,cAAc,GACd,oBAAoB,GACpB,oBAAoB,CACvB,EACgB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EACpD,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,cAAc,EAAE,MAAM,CAAC;QACvB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3C,8BAA8B,EAAE,MAAM,CAAC;QACvC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACrC,mBAAmB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;KACtC,KAAG,OAAO,CAAC,IAAI,CAAC,CA0Nf;CACH"}
|