github-issue-tower-defence-management 1.60.1 → 1.63.1
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/publish.yml +13 -0
- package/.github/workflows/test.yml +0 -4
- package/CHANGELOG.md +14 -0
- package/README.md +53 -10
- package/bin/adapter/entry-points/cli/index.js +11 -11
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js +3 -22
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +8 -22
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/rotationOrderFileWriter.js +56 -0
- package/bin/adapter/entry-points/handlers/rotationOrderFileWriter.js.map +1 -0
- package/bin/adapter/entry-points/handlers/situationFileWriter.js +5 -0
- package/bin/adapter/entry-points/handlers/situationFileWriter.js.map +1 -1
- package/bin/adapter/proxy/TokenListLoader.js +21 -6
- package/bin/adapter/proxy/TokenListLoader.js.map +1 -1
- package/bin/adapter/proxy/proxyEntry.js +1 -0
- package/bin/adapter/proxy/proxyEntry.js.map +1 -1
- package/bin/adapter/repositories/BaseGitHubRepository.js +1 -113
- package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -1
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +5 -3
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +8 -7
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js +19 -9
- package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +15 -3
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js +8 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +5 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js +32 -1
- package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +91 -12
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -4
- package/src/adapter/entry-points/cli/index.test.ts +16 -16
- package/src/adapter/entry-points/cli/index.ts +8 -11
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +2 -55
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +1 -11
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +6 -56
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +7 -11
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +177 -0
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +20 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +36 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.ts +8 -0
- package/src/adapter/proxy/TokenListLoader.test.ts +50 -1
- package/src/adapter/proxy/TokenListLoader.ts +25 -5
- package/src/adapter/proxy/proxyEntry.test.ts +270 -1
- package/src/adapter/proxy/proxyEntry.ts +2 -1
- package/src/adapter/repositories/BaseGitHubRepository.test.ts +1 -186
- package/src/adapter/repositories/BaseGitHubRepository.ts +1 -139
- package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -1
- package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +4 -1
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +60 -19
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +6 -4
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +23 -13
- package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -1
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -8
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -1
- package/src/domain/entities/ClaudeTokenUsage.ts +1 -0
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +196 -11
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +32 -15
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +21 -5
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +153 -0
- package/src/domain/usecases/IssueRejectionEvaluator.ts +8 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +175 -31
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +7 -1
- package/src/domain/usecases/RevertNotReadyAwaitingQualityCheckUseCase.test.ts +32 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +39 -5
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +1 -1
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +139 -20
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +62 -2
- package/src/domain/usecases/StartPreparationUseCase.test.ts +404 -21
- package/src/domain/usecases/StartPreparationUseCase.ts +152 -16
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +16 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/rotationOrderFileWriter.d.ts +3 -0
- package/types/adapter/entry-points/handlers/rotationOrderFileWriter.d.ts.map +1 -0
- package/types/adapter/entry-points/handlers/situationFileWriter.d.ts +1 -0
- package/types/adapter/entry-points/handlers/situationFileWriter.d.ts.map +1 -1
- package/types/adapter/proxy/TokenListLoader.d.ts +5 -0
- package/types/adapter/proxy/TokenListLoader.d.ts.map +1 -1
- package/types/adapter/proxy/proxyEntry.d.ts +2 -1
- package/types/adapter/proxy/proxyEntry.d.ts.map +1 -1
- package/types/adapter/repositories/BaseGitHubRepository.d.ts +1 -23
- package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -1
- package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +14 -5
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.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/usecases/CreateNewStoryByLabelUseCase.d.ts +4 -2
- package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +5 -2
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts +5 -2
- package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +15 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +14 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js +0 -136
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +0 -1
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +0 -1606
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +0 -1
- package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +0 -6552
- package/src/adapter/repositories/issue/CheerioIssueRepository.ts +0 -142
- package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.test.ts +0 -118
- package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +0 -584
- package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts +0 -40
- package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +0 -1
- package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts +0 -220
- package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +0 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { IssueRejectionEvaluator } from './IssueRejectionEvaluator';
|
|
2
|
+
import { RelatedPullRequest } from './adapter-interfaces/IssueRepository';
|
|
3
|
+
|
|
4
|
+
const createReadyPr = (
|
|
5
|
+
url = 'https://github.com/user/repo/pull/1',
|
|
6
|
+
overrides: Partial<RelatedPullRequest> = {},
|
|
7
|
+
): RelatedPullRequest => ({
|
|
8
|
+
url,
|
|
9
|
+
branchName: 'feature-branch',
|
|
10
|
+
createdAt: new Date('2000-01-01T00:00:00Z'),
|
|
11
|
+
isDraft: false,
|
|
12
|
+
isConflicted: false,
|
|
13
|
+
isPassedAllCiJob: true,
|
|
14
|
+
isCiStateSuccess: true,
|
|
15
|
+
isResolvedAllReviewComments: true,
|
|
16
|
+
isBranchOutOfDate: false,
|
|
17
|
+
missingRequiredCheckNames: [],
|
|
18
|
+
...overrides,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('IssueRejectionEvaluator', () => {
|
|
22
|
+
let mockIssueRepository: {
|
|
23
|
+
findRelatedOpenPRs: jest.Mock;
|
|
24
|
+
getOpenPullRequest: jest.Mock;
|
|
25
|
+
};
|
|
26
|
+
let evaluator: IssueRejectionEvaluator;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
jest.resetAllMocks();
|
|
30
|
+
|
|
31
|
+
mockIssueRepository = {
|
|
32
|
+
findRelatedOpenPRs: jest.fn(),
|
|
33
|
+
getOpenPullRequest: jest.fn(),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
evaluator = new IssueRejectionEvaluator(mockIssueRepository);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('evaluate', () => {
|
|
40
|
+
it('should return no rejections when PR is ready and not draft', async () => {
|
|
41
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
42
|
+
createReadyPr(),
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const result = await evaluator.evaluate({
|
|
46
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
47
|
+
labels: [],
|
|
48
|
+
isPr: false,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(result.rejections).toHaveLength(0);
|
|
52
|
+
expect(result.approvedPrUrl).toBe('https://github.com/user/repo/pull/1');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should reject with PULL_REQUEST_IS_DRAFT when PR is in draft state', async () => {
|
|
56
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
57
|
+
createReadyPr('https://github.com/user/repo/pull/1', { isDraft: true }),
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const result = await evaluator.evaluate({
|
|
61
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
62
|
+
labels: [],
|
|
63
|
+
isPr: false,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(result.rejections).toHaveLength(1);
|
|
67
|
+
expect(result.rejections[0].type).toBe('PULL_REQUEST_IS_DRAFT');
|
|
68
|
+
expect(result.rejections[0].detail).toContain('PULL_REQUEST_IS_DRAFT');
|
|
69
|
+
expect(result.rejections[0].detail).toContain(
|
|
70
|
+
'https://github.com/user/repo/pull/1',
|
|
71
|
+
);
|
|
72
|
+
expect(result.approvedPrUrl).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should not approve a draft PR even when all other checks pass', async () => {
|
|
76
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
77
|
+
createReadyPr('https://github.com/user/repo/pull/1', { isDraft: true }),
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const result = await evaluator.evaluate({
|
|
81
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
82
|
+
labels: [],
|
|
83
|
+
isPr: false,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(result.approvedPrUrl).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should accumulate PULL_REQUEST_IS_DRAFT alongside PULL_REQUEST_CONFLICTED when draft PR is also conflicted', async () => {
|
|
90
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
91
|
+
createReadyPr('https://github.com/user/repo/pull/1', {
|
|
92
|
+
isDraft: true,
|
|
93
|
+
isConflicted: true,
|
|
94
|
+
}),
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
const result = await evaluator.evaluate({
|
|
98
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
99
|
+
labels: [],
|
|
100
|
+
isPr: false,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const rejectionTypes = result.rejections.map((r) => r.type);
|
|
104
|
+
expect(rejectionTypes).toContain('PULL_REQUEST_IS_DRAFT');
|
|
105
|
+
expect(rejectionTypes).toContain('PULL_REQUEST_CONFLICTED');
|
|
106
|
+
expect(result.approvedPrUrl).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should reject with PULL_REQUEST_IS_DRAFT when PR item (isPr=true) is in draft state', async () => {
|
|
110
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue(
|
|
111
|
+
createReadyPr('https://github.com/user/repo/pull/10', {
|
|
112
|
+
isDraft: true,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const result = await evaluator.evaluate({
|
|
117
|
+
url: 'https://github.com/user/repo/pull/10',
|
|
118
|
+
labels: [],
|
|
119
|
+
isPr: true,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
123
|
+
'https://github.com/user/repo/pull/10',
|
|
124
|
+
);
|
|
125
|
+
expect(result.rejections).toHaveLength(1);
|
|
126
|
+
expect(result.rejections[0].type).toBe('PULL_REQUEST_IS_DRAFT');
|
|
127
|
+
expect(result.approvedPrUrl).toBeNull();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should not reject for draft state when issue has llm-agent label', async () => {
|
|
131
|
+
const result = await evaluator.evaluate({
|
|
132
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
133
|
+
labels: ['llm-agent'],
|
|
134
|
+
isPr: false,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
138
|
+
expect(result.rejections).toHaveLength(0);
|
|
139
|
+
expect(result.approvedPrUrl).toBeNull();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should not reject for draft state when issue has non-e2e category label', async () => {
|
|
143
|
+
const result = await evaluator.evaluate({
|
|
144
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
145
|
+
labels: ['category:frontend'],
|
|
146
|
+
isPr: false,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
150
|
+
expect(result.rejections).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
export type PrRejectedReasonType =
|
|
7
7
|
| 'PULL_REQUEST_NOT_FOUND'
|
|
8
8
|
| 'MULTIPLE_PULL_REQUESTS_FOUND'
|
|
9
|
+
| 'PULL_REQUEST_IS_DRAFT'
|
|
9
10
|
| 'PULL_REQUEST_CONFLICTED'
|
|
10
11
|
| 'ANY_CI_JOB_FAILED_OR_IN_PROGRESS'
|
|
11
12
|
| 'REQUIRED_CI_JOB_NEVER_STARTED'
|
|
@@ -59,6 +60,12 @@ export class IssueRejectionEvaluator {
|
|
|
59
60
|
});
|
|
60
61
|
} else {
|
|
61
62
|
const pr = prsToCheck[0];
|
|
63
|
+
if (pr.isDraft) {
|
|
64
|
+
rejections.push({
|
|
65
|
+
type: 'PULL_REQUEST_IS_DRAFT',
|
|
66
|
+
detail: `PULL_REQUEST_IS_DRAFT: ${pr.url}`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
62
69
|
if (pr.isConflicted) {
|
|
63
70
|
rejections.push({
|
|
64
71
|
type: 'PULL_REQUEST_CONFLICTED',
|
|
@@ -90,6 +97,7 @@ export class IssueRejectionEvaluator {
|
|
|
90
97
|
});
|
|
91
98
|
}
|
|
92
99
|
if (
|
|
100
|
+
!pr.isDraft &&
|
|
93
101
|
!pr.isConflicted &&
|
|
94
102
|
pr.isPassedAllCiJob &&
|
|
95
103
|
pr.isResolvedAllReviewComments
|
|
@@ -77,7 +77,7 @@ const createMockIssue = (overrides: Partial<Issue> = {}): Issue => ({
|
|
|
77
77
|
|
|
78
78
|
const createMockComment = (overrides: Partial<Comment> = {}): Comment => ({
|
|
79
79
|
author: 'test-user',
|
|
80
|
-
content: 'From: Test comment',
|
|
80
|
+
content: 'From: :robot: Test comment',
|
|
81
81
|
createdAt: new Date(),
|
|
82
82
|
...overrides,
|
|
83
83
|
});
|
|
@@ -108,7 +108,12 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
108
108
|
beforeEach(() => {
|
|
109
109
|
jest.resetAllMocks();
|
|
110
110
|
|
|
111
|
-
mockProject = createMockProject(
|
|
111
|
+
mockProject = createMockProject({
|
|
112
|
+
dependedIssueUrlSeparatedByComma: {
|
|
113
|
+
name: 'Depended Issue URL',
|
|
114
|
+
fieldId: 'depended-field-id',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
112
117
|
|
|
113
118
|
mockProjectRepository = {
|
|
114
119
|
getByUrl: jest.fn(),
|
|
@@ -150,7 +155,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
150
155
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
151
156
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
152
157
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
153
|
-
createMockComment({ content: 'From: Test report' }),
|
|
158
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
154
159
|
]);
|
|
155
160
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
156
161
|
{
|
|
@@ -205,7 +210,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
205
210
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
206
211
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
207
212
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
208
|
-
createMockComment({ content: 'From: Agent report' }),
|
|
213
|
+
createMockComment({ content: 'From: :robot: Agent report' }),
|
|
209
214
|
]);
|
|
210
215
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
211
216
|
{
|
|
@@ -511,6 +516,53 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
511
516
|
);
|
|
512
517
|
});
|
|
513
518
|
|
|
519
|
+
it('should reject with NO_REPORT_FROM_AGENT_BOT when last comment is a cross-issue notification starting with From: :warning:', async () => {
|
|
520
|
+
const issue = createMockIssue({
|
|
521
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
522
|
+
status: 'Preparation',
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
526
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
527
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
528
|
+
createMockComment({
|
|
529
|
+
content:
|
|
530
|
+
'From: :warning: This message is from https://github.com/user/repo/tree/i999 AI HS Implement AI Agent (claude-sonnet-4-6)',
|
|
531
|
+
}),
|
|
532
|
+
]);
|
|
533
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
534
|
+
{
|
|
535
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
536
|
+
isConflicted: false,
|
|
537
|
+
isPassedAllCiJob: true,
|
|
538
|
+
isCiStateSuccess: true,
|
|
539
|
+
isResolvedAllReviewComments: true,
|
|
540
|
+
isBranchOutOfDate: false,
|
|
541
|
+
missingRequiredCheckNames: [],
|
|
542
|
+
},
|
|
543
|
+
]);
|
|
544
|
+
|
|
545
|
+
await useCase.run({
|
|
546
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
547
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
548
|
+
thresholdForAutoReject: 3,
|
|
549
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
553
|
+
expect.objectContaining({
|
|
554
|
+
status: 'Awaiting Workspace',
|
|
555
|
+
}),
|
|
556
|
+
mockProject,
|
|
557
|
+
);
|
|
558
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
559
|
+
expect.objectContaining({
|
|
560
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
561
|
+
}),
|
|
562
|
+
expect.stringContaining('NO_REPORT_FROM_AGENT_BOT'),
|
|
563
|
+
);
|
|
564
|
+
});
|
|
565
|
+
|
|
514
566
|
it('should reject and set status to Awaiting Workspace when no comments exist', async () => {
|
|
515
567
|
const issue = createMockIssue({
|
|
516
568
|
url: 'https://github.com/user/repo/issues/1',
|
|
@@ -559,7 +611,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
559
611
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
560
612
|
createMockComment({
|
|
561
613
|
content:
|
|
562
|
-
'From: Agent report\n```json\n{"nextStep": "Fix the tests"}\n```',
|
|
614
|
+
'From: :robot: Agent report\n```json\n{"nextStep": "Fix the tests"}\n```',
|
|
563
615
|
}),
|
|
564
616
|
]);
|
|
565
617
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
@@ -601,7 +653,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
601
653
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
602
654
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
603
655
|
createMockComment({
|
|
604
|
-
content: 'From: Agent report\n```json\n{"nextStep": null}\n```',
|
|
656
|
+
content: 'From: :robot: Agent report\n```json\n{"nextStep": null}\n```',
|
|
605
657
|
}),
|
|
606
658
|
]);
|
|
607
659
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
@@ -691,7 +743,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
691
743
|
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
692
744
|
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
693
745
|
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
694
|
-
createMockComment({ content: 'From: Agent final report' }),
|
|
746
|
+
createMockComment({ content: 'From: :robot: Agent final report' }),
|
|
695
747
|
]);
|
|
696
748
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
697
749
|
{
|
|
@@ -915,7 +967,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
915
967
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
916
968
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
917
969
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
918
|
-
createMockComment({ content: 'From: Test report' }),
|
|
970
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
919
971
|
]);
|
|
920
972
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
921
973
|
|
|
@@ -949,7 +1001,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
949
1001
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
950
1002
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
951
1003
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
952
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1004
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
953
1005
|
]);
|
|
954
1006
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
955
1007
|
{
|
|
@@ -1002,7 +1054,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1002
1054
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1003
1055
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1004
1056
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1005
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1057
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1006
1058
|
]);
|
|
1007
1059
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1008
1060
|
{
|
|
@@ -1046,7 +1098,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1046
1098
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1047
1099
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1048
1100
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1049
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1101
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1050
1102
|
]);
|
|
1051
1103
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1052
1104
|
{
|
|
@@ -1090,7 +1142,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1090
1142
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1091
1143
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1092
1144
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1093
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1145
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1094
1146
|
]);
|
|
1095
1147
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1096
1148
|
{
|
|
@@ -1140,7 +1192,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1140
1192
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1141
1193
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1142
1194
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1143
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1195
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1144
1196
|
]);
|
|
1145
1197
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1146
1198
|
{
|
|
@@ -1190,7 +1242,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1190
1242
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1191
1243
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1192
1244
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1193
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1245
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1194
1246
|
]);
|
|
1195
1247
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1196
1248
|
{
|
|
@@ -1228,7 +1280,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1228
1280
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1229
1281
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1230
1282
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1231
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1283
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1232
1284
|
]);
|
|
1233
1285
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1234
1286
|
{
|
|
@@ -1263,6 +1315,51 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1263
1315
|
);
|
|
1264
1316
|
});
|
|
1265
1317
|
|
|
1318
|
+
it('should reject when PR is in draft state', async () => {
|
|
1319
|
+
const issue = createMockIssue({
|
|
1320
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1321
|
+
status: 'Preparation',
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1325
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1326
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1327
|
+
createMockComment({ content: 'From: Test report' }),
|
|
1328
|
+
]);
|
|
1329
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1330
|
+
{
|
|
1331
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
1332
|
+
isDraft: true,
|
|
1333
|
+
isConflicted: false,
|
|
1334
|
+
isPassedAllCiJob: true,
|
|
1335
|
+
isCiStateSuccess: true,
|
|
1336
|
+
isResolvedAllReviewComments: true,
|
|
1337
|
+
isBranchOutOfDate: false,
|
|
1338
|
+
missingRequiredCheckNames: [],
|
|
1339
|
+
},
|
|
1340
|
+
]);
|
|
1341
|
+
|
|
1342
|
+
await useCase.run({
|
|
1343
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1344
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1345
|
+
thresholdForAutoReject: 3,
|
|
1346
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1350
|
+
expect.objectContaining({
|
|
1351
|
+
status: 'Awaiting Workspace',
|
|
1352
|
+
}),
|
|
1353
|
+
mockProject,
|
|
1354
|
+
);
|
|
1355
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
1356
|
+
expect.objectContaining({
|
|
1357
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1358
|
+
}),
|
|
1359
|
+
expect.stringContaining('PULL_REQUEST_IS_DRAFT'),
|
|
1360
|
+
);
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1266
1363
|
it('should skip PR checks and update to Awaiting Quality Check when issue has category label', async () => {
|
|
1267
1364
|
const issue = createMockIssue({
|
|
1268
1365
|
url: 'https://github.com/user/repo/issues/1',
|
|
@@ -1273,7 +1370,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1273
1370
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1274
1371
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1275
1372
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1276
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1373
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1277
1374
|
]);
|
|
1278
1375
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
1279
1376
|
|
|
@@ -1302,7 +1399,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1302
1399
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1303
1400
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1304
1401
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1305
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1402
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1306
1403
|
]);
|
|
1307
1404
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1308
1405
|
{
|
|
@@ -1379,7 +1476,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1379
1476
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1380
1477
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1381
1478
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1382
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1479
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1383
1480
|
]);
|
|
1384
1481
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
1385
1482
|
|
|
@@ -1406,7 +1503,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1406
1503
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1407
1504
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1408
1505
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1409
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1506
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1410
1507
|
]);
|
|
1411
1508
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
1412
1509
|
|
|
@@ -1466,7 +1563,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1466
1563
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1467
1564
|
mockIssueRepository.get.mockResolvedValue(prIssue);
|
|
1468
1565
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1469
|
-
createMockComment({ content: 'From: Agent report' }),
|
|
1566
|
+
createMockComment({ content: 'From: :robot: Agent report' }),
|
|
1470
1567
|
]);
|
|
1471
1568
|
mockIssueRepository.getOpenPullRequest.mockResolvedValue({
|
|
1472
1569
|
url: 'https://github.com/user/repo/pull/10',
|
|
@@ -1665,6 +1762,53 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1665
1762
|
'https://github.com/user/repo/issues/1',
|
|
1666
1763
|
);
|
|
1667
1764
|
});
|
|
1765
|
+
|
|
1766
|
+
it('should log a warning and skip setDependedIssueUrl when dependedIssueUrlSeparatedByComma is not configured in project', async () => {
|
|
1767
|
+
const projectWithoutDependedField = createMockProject({
|
|
1768
|
+
dependedIssueUrlSeparatedByComma: null,
|
|
1769
|
+
});
|
|
1770
|
+
const issue = createMockIssue({
|
|
1771
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1772
|
+
status: 'Preparation',
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
mockProjectRepository.getByUrl.mockResolvedValue(
|
|
1776
|
+
projectWithoutDependedField,
|
|
1777
|
+
);
|
|
1778
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1779
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1780
|
+
createMockComment({ content: 'From: Agent report' }),
|
|
1781
|
+
]);
|
|
1782
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1783
|
+
{
|
|
1784
|
+
url: 'https://github.com/user/repo/pull/10',
|
|
1785
|
+
isConflicted: false,
|
|
1786
|
+
isPassedAllCiJob: true,
|
|
1787
|
+
isCiStateSuccess: true,
|
|
1788
|
+
isResolvedAllReviewComments: true,
|
|
1789
|
+
isBranchOutOfDate: false,
|
|
1790
|
+
missingRequiredCheckNames: [],
|
|
1791
|
+
},
|
|
1792
|
+
]);
|
|
1793
|
+
|
|
1794
|
+
const consoleWarnSpy = jest
|
|
1795
|
+
.spyOn(console, 'warn')
|
|
1796
|
+
.mockImplementation(() => {});
|
|
1797
|
+
|
|
1798
|
+
await useCase.run({
|
|
1799
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1800
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1801
|
+
thresholdForAutoReject: 3,
|
|
1802
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1805
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
1806
|
+
expect.stringContaining('dependedIssueUrlSeparatedByComma'),
|
|
1807
|
+
);
|
|
1808
|
+
expect(mockIssueRepository.setDependedIssueUrl).not.toHaveBeenCalled();
|
|
1809
|
+
|
|
1810
|
+
consoleWarnSpy.mockRestore();
|
|
1811
|
+
});
|
|
1668
1812
|
});
|
|
1669
1813
|
|
|
1670
1814
|
describe('workflow blocker webhook notification', () => {
|
|
@@ -1713,7 +1857,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1713
1857
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1714
1858
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1715
1859
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1716
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1860
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1717
1861
|
]);
|
|
1718
1862
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1719
1863
|
{
|
|
@@ -1794,7 +1938,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1794
1938
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1795
1939
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1796
1940
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1797
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1941
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1798
1942
|
]);
|
|
1799
1943
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1800
1944
|
{
|
|
@@ -1831,7 +1975,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1831
1975
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1832
1976
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1833
1977
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1834
|
-
createMockComment({ content: 'From: Test report' }),
|
|
1978
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1835
1979
|
]);
|
|
1836
1980
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1837
1981
|
{
|
|
@@ -1864,7 +2008,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1864
2008
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1865
2009
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1866
2010
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1867
|
-
createMockComment({ content: 'From: Test report' }),
|
|
2011
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1868
2012
|
]);
|
|
1869
2013
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1870
2014
|
{
|
|
@@ -1919,7 +2063,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1919
2063
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1920
2064
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1921
2065
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1922
|
-
createMockComment({ content: 'From: Test report' }),
|
|
2066
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1923
2067
|
]);
|
|
1924
2068
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1925
2069
|
{
|
|
@@ -1974,7 +2118,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1974
2118
|
new Error('Story map unavailable'),
|
|
1975
2119
|
);
|
|
1976
2120
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1977
|
-
createMockComment({ content: 'From: Test report' }),
|
|
2121
|
+
createMockComment({ content: 'From: :robot: Test report' }),
|
|
1978
2122
|
]);
|
|
1979
2123
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1980
2124
|
{
|
|
@@ -2019,7 +2163,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
2019
2163
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
2020
2164
|
mockIssueRepository.get.mockResolvedValue(prIssue);
|
|
2021
2165
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2022
|
-
createMockComment({ content: 'From: Agent report' }),
|
|
2166
|
+
createMockComment({ content: 'From: :robot: Agent report' }),
|
|
2023
2167
|
]);
|
|
2024
2168
|
mockIssueRepository.getOpenPullRequest.mockResolvedValue(null);
|
|
2025
2169
|
|
|
@@ -2053,7 +2197,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
2053
2197
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
2054
2198
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2055
2199
|
createMockComment({
|
|
2056
|
-
content: 'From: Agent report\n```json\n{invalid json}\n```',
|
|
2200
|
+
content: 'From: :robot: Agent report\n```json\n{invalid json}\n```',
|
|
2057
2201
|
}),
|
|
2058
2202
|
]);
|
|
2059
2203
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
@@ -2091,7 +2235,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
2091
2235
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
2092
2236
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2093
2237
|
createMockComment({
|
|
2094
|
-
content: 'From: Agent report\n```json\nnull\n```',
|
|
2238
|
+
content: 'From: :robot: Agent report\n```json\nnull\n```',
|
|
2095
2239
|
}),
|
|
2096
2240
|
]);
|
|
2097
2241
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
@@ -2130,7 +2274,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
2130
2274
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2131
2275
|
createMockComment({
|
|
2132
2276
|
content:
|
|
2133
|
-
'From: Agent report\n```json\n{"status": "done", "result": "success"}\n```',
|
|
2277
|
+
'From: :robot: Agent report\n```json\n{"status": "done", "result": "success"}\n```',
|
|
2134
2278
|
}),
|
|
2135
2279
|
]);
|
|
2136
2280
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
@@ -2168,7 +2312,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
2168
2312
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
2169
2313
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2170
2314
|
createMockComment({
|
|
2171
|
-
content: 'From: Agent report\n```json\n"just a string"\n```',
|
|
2315
|
+
content: 'From: :robot: Agent report\n```json\n"just a string"\n```',
|
|
2172
2316
|
}),
|
|
2173
2317
|
]);
|
|
2174
2318
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
@@ -266,7 +266,7 @@ export class NotifyFinishedIssuePreparationUseCase {
|
|
|
266
266
|
const rejections: { type: RejectedReasonType; detail: string }[] = [];
|
|
267
267
|
|
|
268
268
|
const lastComment = comments[comments.length - 1];
|
|
269
|
-
if (!lastComment || !lastComment.content.startsWith('From:')) {
|
|
269
|
+
if (!lastComment || !lastComment.content.startsWith('From: :robot:')) {
|
|
270
270
|
rejections.push({
|
|
271
271
|
type: 'NO_REPORT_FROM_AGENT_BOT',
|
|
272
272
|
detail: 'NO_REPORT_FROM_AGENT_BOT',
|
|
@@ -313,6 +313,12 @@ export class NotifyFinishedIssuePreparationUseCase {
|
|
|
313
313
|
issueUrl: string,
|
|
314
314
|
project: Parameters<IssueRepository['get']>[1],
|
|
315
315
|
): Promise<void> => {
|
|
316
|
+
if (!project.dependedIssueUrlSeparatedByComma) {
|
|
317
|
+
console.warn(
|
|
318
|
+
`dependedIssueUrlSeparatedByComma field not configured in project, skipping depended issue URL update for issue ${issueUrl}`,
|
|
319
|
+
);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
316
322
|
const openPRs = issue.isPr
|
|
317
323
|
? await this.resolveOpenPrsForPrItem(issue.url)
|
|
318
324
|
: await this.issueRepository.findRelatedOpenPRs(issue.url);
|
|
@@ -304,6 +304,38 @@ describe('RevertNotReadyAwaitingQualityCheckUseCase', () => {
|
|
|
304
304
|
);
|
|
305
305
|
});
|
|
306
306
|
|
|
307
|
+
it('should revert issue when linked PR is in draft state', async () => {
|
|
308
|
+
const issue = createMockIssue({
|
|
309
|
+
status: 'Awaiting Quality Check',
|
|
310
|
+
});
|
|
311
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
312
|
+
issues: [issue],
|
|
313
|
+
cacheUsed: false,
|
|
314
|
+
});
|
|
315
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
316
|
+
{ ...createReadyPr(), isDraft: true },
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
await useCase.run({
|
|
320
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
321
|
+
allowIssueCacheMinutes: 10,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
|
|
325
|
+
mockProject,
|
|
326
|
+
issue,
|
|
327
|
+
'awaiting-workspace-id',
|
|
328
|
+
);
|
|
329
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
330
|
+
issue,
|
|
331
|
+
expect.stringContaining('Auto Status Check: REJECTED'),
|
|
332
|
+
);
|
|
333
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
334
|
+
issue,
|
|
335
|
+
expect.stringContaining('PULL_REQUEST_IS_DRAFT'),
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
307
339
|
it('should revert issue when multiple linked open PRs are found', async () => {
|
|
308
340
|
const issue = createMockIssue({
|
|
309
341
|
status: 'Awaiting Quality Check',
|