@spaceflow/review 0.29.3 → 0.31.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 +68 -0
- package/dist/index.js +4596 -5042
- package/package.json +3 -10
- package/src/deletion-impact.service.spec.ts +8 -19
- package/src/deletion-impact.service.ts +0 -2
- package/src/index.ts +123 -24
- package/src/issue-verify.service.spec.ts +7 -23
- package/src/issue-verify.service.ts +0 -2
- package/src/locales/en/review.json +1 -0
- package/src/locales/zh-cn/review.json +1 -0
- package/src/mcp/index.ts +191 -0
- package/src/review-report/index.ts +0 -1
- package/src/review-report/review-report.service.ts +0 -2
- package/src/review-spec/index.ts +0 -1
- package/src/review-spec/review-spec.service.spec.ts +3 -8
- package/src/review-spec/review-spec.service.ts +1 -4
- package/src/review.config.ts +61 -21
- package/src/review.service.spec.ts +156 -144
- package/src/review.service.ts +163 -64
- package/tsconfig.json +1 -1
- package/src/dto/mcp.dto.ts +0 -42
- package/src/review-report/review-report.module.ts +0 -8
- package/src/review-spec/review-spec.module.ts +0 -10
- package/src/review.command.ts +0 -244
- package/src/review.mcp.ts +0 -184
- package/src/review.module.ts +0 -52
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
import { vi, type
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
ConfigService,
|
|
5
|
-
ConfigReaderService,
|
|
6
|
-
GitProviderService,
|
|
7
|
-
ClaudeSetupService,
|
|
8
|
-
LlmProxyService,
|
|
9
|
-
GitSdkService,
|
|
10
|
-
parseChangedLinesFromPatch,
|
|
11
|
-
} from "@spaceflow/core";
|
|
12
|
-
import { ReviewSpecService } from "./review-spec";
|
|
13
|
-
import { ReviewReportService } from "./review-report";
|
|
1
|
+
import { vi, type Mock } from "vitest";
|
|
2
|
+
import { parseChangedLinesFromPatch } from "@spaceflow/core";
|
|
14
3
|
import { readFile } from "fs/promises";
|
|
15
4
|
import { ReviewService, ReviewContext, ReviewPrompt } from "./review.service";
|
|
16
|
-
import {
|
|
17
|
-
import { DeletionImpactService } from "./deletion-impact.service";
|
|
18
|
-
import type { ReviewOptions } from "./review.command";
|
|
5
|
+
import type { ReviewOptions } from "./review.config";
|
|
19
6
|
|
|
20
7
|
vi.mock("c12");
|
|
21
8
|
vi.mock("@anthropic-ai/claude-agent-sdk", () => ({
|
|
@@ -56,38 +43,45 @@ vi.mock("openai", () => {
|
|
|
56
43
|
|
|
57
44
|
describe("ReviewService", () => {
|
|
58
45
|
let service: ReviewService;
|
|
59
|
-
let gitProvider:
|
|
60
|
-
let configService:
|
|
46
|
+
let gitProvider: any;
|
|
47
|
+
let configService: any;
|
|
61
48
|
let mockReviewSpecService: any;
|
|
62
49
|
let mockDeletionImpactService: any;
|
|
63
50
|
let mockGitSdkService: any;
|
|
51
|
+
let mockLlmProxyService: any;
|
|
52
|
+
let mockConfigReaderService: any;
|
|
64
53
|
|
|
65
|
-
beforeEach(
|
|
66
|
-
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.clearAllMocks();
|
|
56
|
+
gitProvider = {
|
|
67
57
|
validateConfig: vi.fn(),
|
|
68
58
|
getPullRequest: vi.fn(),
|
|
69
59
|
getCommit: vi.fn(),
|
|
70
60
|
getPullRequestCommits: vi.fn(),
|
|
71
61
|
getPullRequestFiles: vi.fn(),
|
|
72
62
|
getFileContent: vi.fn(),
|
|
73
|
-
listPullReviews: vi.fn(),
|
|
74
|
-
createPullReview: vi.fn(),
|
|
75
|
-
deletePullReview: vi.fn(),
|
|
63
|
+
listPullReviews: vi.fn().mockResolvedValue([]),
|
|
64
|
+
createPullReview: vi.fn().mockResolvedValue({}),
|
|
65
|
+
deletePullReview: vi.fn().mockResolvedValue(undefined),
|
|
66
|
+
deletePullReviewComment: vi.fn().mockResolvedValue(undefined),
|
|
67
|
+
listResolvedThreads: vi.fn().mockResolvedValue([]),
|
|
76
68
|
editPullRequest: vi.fn(),
|
|
77
69
|
getCommitDiff: vi.fn(),
|
|
78
70
|
listPullReviewComments: vi.fn(),
|
|
79
71
|
searchUsers: vi.fn().mockResolvedValue([]),
|
|
80
72
|
getIssueCommentReactions: vi.fn().mockResolvedValue([]),
|
|
73
|
+
getPullReviewCommentReactions: vi.fn().mockResolvedValue([]),
|
|
74
|
+
listIssueComments: vi.fn().mockResolvedValue([]),
|
|
75
|
+
createIssueComment: vi.fn().mockResolvedValue({}),
|
|
76
|
+
updateIssueComment: vi.fn().mockResolvedValue({}),
|
|
77
|
+
deleteIssueComment: vi.fn().mockResolvedValue(undefined),
|
|
78
|
+
updatePullReview: vi.fn().mockResolvedValue({}),
|
|
81
79
|
};
|
|
82
80
|
|
|
83
|
-
|
|
81
|
+
configService = {
|
|
84
82
|
get: vi.fn(),
|
|
85
83
|
};
|
|
86
84
|
|
|
87
|
-
const mockClaudeSetupService = {
|
|
88
|
-
configure: vi.fn(),
|
|
89
|
-
};
|
|
90
|
-
|
|
91
85
|
mockReviewSpecService = {
|
|
92
86
|
resolveSpecSources: vi.fn().mockResolvedValue(["/mock/spec/dir"]),
|
|
93
87
|
loadReviewSpecs: vi.fn().mockResolvedValue([
|
|
@@ -139,63 +133,29 @@ describe("ReviewService", () => {
|
|
|
139
133
|
getCommitDiff: vi.fn().mockReturnValue([]),
|
|
140
134
|
};
|
|
141
135
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
provide: ConfigService,
|
|
151
|
-
useValue: mockConfigService,
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
provide: ClaudeSetupService,
|
|
155
|
-
useValue: mockClaudeSetupService,
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
provide: ReviewSpecService,
|
|
159
|
-
useValue: mockReviewSpecService,
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
provide: LlmProxyService,
|
|
163
|
-
useValue: {
|
|
164
|
-
chat: vi.fn(),
|
|
165
|
-
chatStream: vi.fn(),
|
|
166
|
-
createSession: vi.fn(),
|
|
167
|
-
getAvailableAdapters: vi.fn().mockReturnValue(["claude-code", "openai"]),
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
provide: ReviewReportService,
|
|
172
|
-
useValue: mockReviewReportService,
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
provide: IssueVerifyService,
|
|
176
|
-
useValue: mockIssueVerifyService,
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
provide: DeletionImpactService,
|
|
180
|
-
useValue: mockDeletionImpactService,
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
provide: GitSdkService,
|
|
184
|
-
useValue: mockGitSdkService,
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
provide: ConfigReaderService,
|
|
188
|
-
useValue: {
|
|
189
|
-
getPluginConfig: vi.fn().mockReturnValue({}),
|
|
190
|
-
getSystemConfig: vi.fn().mockReturnValue({}),
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
],
|
|
194
|
-
}).compile();
|
|
136
|
+
mockLlmProxyService = {
|
|
137
|
+
chat: vi.fn(),
|
|
138
|
+
chatStream: vi.fn(),
|
|
139
|
+
createSession: vi.fn(),
|
|
140
|
+
getAvailableAdapters: vi.fn().mockReturnValue(["claude-code", "openai"]),
|
|
141
|
+
};
|
|
195
142
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
143
|
+
mockConfigReaderService = {
|
|
144
|
+
getPluginConfig: vi.fn().mockReturnValue({}),
|
|
145
|
+
getSystemConfig: vi.fn().mockReturnValue({}),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
service = new ReviewService(
|
|
149
|
+
gitProvider as any,
|
|
150
|
+
configService as any,
|
|
151
|
+
mockConfigReaderService as any,
|
|
152
|
+
mockReviewSpecService as any,
|
|
153
|
+
mockLlmProxyService as any,
|
|
154
|
+
mockReviewReportService as any,
|
|
155
|
+
mockIssueVerifyService as any,
|
|
156
|
+
mockDeletionImpactService as any,
|
|
157
|
+
mockGitSdkService as any,
|
|
158
|
+
);
|
|
199
159
|
});
|
|
200
160
|
|
|
201
161
|
afterEach(() => {
|
|
@@ -364,12 +324,13 @@ describe("ReviewService", () => {
|
|
|
364
324
|
gitProvider.getPullRequestCommits.mockResolvedValue(mockCommits);
|
|
365
325
|
gitProvider.getPullRequestFiles.mockResolvedValue(mockFiles);
|
|
366
326
|
gitProvider.getFileContent.mockResolvedValue("const test = 1;");
|
|
327
|
+
gitProvider.listIssueComments.mockResolvedValue([]);
|
|
367
328
|
gitProvider.listPullReviews.mockResolvedValue([]);
|
|
368
|
-
gitProvider.
|
|
329
|
+
gitProvider.createIssueComment.mockResolvedValue({});
|
|
369
330
|
|
|
370
331
|
await service.execute(context);
|
|
371
332
|
|
|
372
|
-
expect(gitProvider.
|
|
333
|
+
expect(gitProvider.createIssueComment).toHaveBeenCalledWith(
|
|
373
334
|
"owner",
|
|
374
335
|
"repo",
|
|
375
336
|
123,
|
|
@@ -549,14 +510,15 @@ describe("ReviewService", () => {
|
|
|
549
510
|
gitProvider.getPullRequest.mockResolvedValue(mockPR as any);
|
|
550
511
|
gitProvider.getPullRequestCommits.mockResolvedValue([]);
|
|
551
512
|
gitProvider.getPullRequestFiles.mockResolvedValue([]);
|
|
513
|
+
gitProvider.listIssueComments.mockResolvedValue([]);
|
|
552
514
|
gitProvider.listPullReviews.mockResolvedValue([]);
|
|
553
|
-
gitProvider.
|
|
515
|
+
gitProvider.createIssueComment.mockResolvedValue({});
|
|
554
516
|
|
|
555
517
|
const result = await service.execute(context);
|
|
556
518
|
|
|
557
519
|
expect(result.success).toBe(true);
|
|
558
520
|
expect(mockDeletionImpactService.analyzeDeletionImpact).toHaveBeenCalled();
|
|
559
|
-
expect(gitProvider.
|
|
521
|
+
expect(gitProvider.createIssueComment).toHaveBeenCalled();
|
|
560
522
|
});
|
|
561
523
|
});
|
|
562
524
|
|
|
@@ -1463,12 +1425,12 @@ describe("ReviewService", () => {
|
|
|
1463
1425
|
expect(result.skippedCount).toBe(1);
|
|
1464
1426
|
});
|
|
1465
1427
|
|
|
1466
|
-
it("should
|
|
1428
|
+
it("should also filter invalid existing issues to prevent repeated reporting", () => {
|
|
1467
1429
|
const newIssues = [{ file: "a.ts", line: "1", ruleId: "R1" }];
|
|
1468
1430
|
const existingIssues = [{ file: "a.ts", line: "1", ruleId: "R1", valid: "false" }];
|
|
1469
1431
|
const result = (service as any).filterDuplicateIssues(newIssues, existingIssues);
|
|
1470
|
-
expect(result.filteredIssues).toHaveLength(
|
|
1471
|
-
expect(result.skippedCount).toBe(
|
|
1432
|
+
expect(result.filteredIssues).toHaveLength(0);
|
|
1433
|
+
expect(result.skippedCount).toBe(1);
|
|
1472
1434
|
});
|
|
1473
1435
|
});
|
|
1474
1436
|
|
|
@@ -1505,7 +1467,7 @@ describe("ReviewService", () => {
|
|
|
1505
1467
|
|
|
1506
1468
|
describe("ReviewService.getExistingReviewResult", () => {
|
|
1507
1469
|
it("should return null when no AI review exists", async () => {
|
|
1508
|
-
gitProvider.
|
|
1470
|
+
gitProvider.listIssueComments.mockResolvedValue([{ body: "normal comment" }] as any);
|
|
1509
1471
|
const result = await (service as any).getExistingReviewResult("o", "r", 1);
|
|
1510
1472
|
expect(result).toBeNull();
|
|
1511
1473
|
});
|
|
@@ -1514,7 +1476,7 @@ describe("ReviewService", () => {
|
|
|
1514
1476
|
const mockResult = { issues: [], summary: [] };
|
|
1515
1477
|
const mockReviewReportService = (service as any).reviewReportService;
|
|
1516
1478
|
mockReviewReportService.parseMarkdown.mockReturnValue({ result: mockResult });
|
|
1517
|
-
gitProvider.
|
|
1479
|
+
gitProvider.listIssueComments.mockResolvedValue([
|
|
1518
1480
|
{ body: "<!-- spaceflow-review --> review content" },
|
|
1519
1481
|
] as any);
|
|
1520
1482
|
const result = await (service as any).getExistingReviewResult("o", "r", 1);
|
|
@@ -1522,26 +1484,49 @@ describe("ReviewService", () => {
|
|
|
1522
1484
|
});
|
|
1523
1485
|
|
|
1524
1486
|
it("should return null on error", async () => {
|
|
1525
|
-
gitProvider.
|
|
1487
|
+
gitProvider.listIssueComments.mockRejectedValue(new Error("API error"));
|
|
1526
1488
|
const result = await (service as any).getExistingReviewResult("o", "r", 1);
|
|
1527
1489
|
expect(result).toBeNull();
|
|
1528
1490
|
});
|
|
1529
1491
|
});
|
|
1530
1492
|
|
|
1531
1493
|
describe("ReviewService.deleteExistingAiReviews", () => {
|
|
1532
|
-
it("should delete AI reviews", async () => {
|
|
1494
|
+
it("should delete AI reviews via review API", async () => {
|
|
1533
1495
|
gitProvider.listPullReviews.mockResolvedValue([
|
|
1534
1496
|
{ id: 1, body: "<!-- spaceflow-review --> old review" },
|
|
1535
1497
|
{ id: 2, body: "normal review" },
|
|
1536
1498
|
] as any);
|
|
1499
|
+
gitProvider.listIssueComments.mockResolvedValue([] as any);
|
|
1537
1500
|
gitProvider.deletePullReview.mockResolvedValue(undefined as any);
|
|
1538
1501
|
await (service as any).deleteExistingAiReviews("o", "r", 1);
|
|
1539
1502
|
expect(gitProvider.deletePullReview).toHaveBeenCalledWith("o", "r", 1, 1);
|
|
1540
1503
|
expect(gitProvider.deletePullReview).toHaveBeenCalledTimes(1);
|
|
1541
1504
|
});
|
|
1542
1505
|
|
|
1543
|
-
it("should
|
|
1506
|
+
it("should delete AI reviews via issue comment API", async () => {
|
|
1507
|
+
gitProvider.listPullReviews.mockResolvedValue([] as any);
|
|
1508
|
+
gitProvider.listIssueComments.mockResolvedValue([
|
|
1509
|
+
{ id: 10, body: "<!-- spaceflow-review --> old comment" },
|
|
1510
|
+
{ id: 11, body: "normal comment" },
|
|
1511
|
+
] as any);
|
|
1512
|
+
gitProvider.deleteIssueComment.mockResolvedValue(undefined as any);
|
|
1513
|
+
await (service as any).deleteExistingAiReviews("o", "r", 1);
|
|
1514
|
+
expect(gitProvider.deleteIssueComment).toHaveBeenCalledWith("o", "r", 10);
|
|
1515
|
+
expect(gitProvider.deleteIssueComment).toHaveBeenCalledTimes(1);
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
it("should handle review API error gracefully", async () => {
|
|
1544
1519
|
gitProvider.listPullReviews.mockRejectedValue(new Error("fail"));
|
|
1520
|
+
gitProvider.listIssueComments.mockResolvedValue([] as any);
|
|
1521
|
+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1522
|
+
await (service as any).deleteExistingAiReviews("o", "r", 1);
|
|
1523
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
1524
|
+
consoleSpy.mockRestore();
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1527
|
+
it("should handle issue comment API error gracefully", async () => {
|
|
1528
|
+
gitProvider.listPullReviews.mockResolvedValue([] as any);
|
|
1529
|
+
gitProvider.listIssueComments.mockRejectedValue(new Error("fail"));
|
|
1545
1530
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1546
1531
|
await (service as any).deleteExistingAiReviews("o", "r", 1);
|
|
1547
1532
|
expect(consoleSpy).toHaveBeenCalled();
|
|
@@ -1773,35 +1758,35 @@ describe("ReviewService", () => {
|
|
|
1773
1758
|
it("should post review comment", async () => {
|
|
1774
1759
|
const configReader = (service as any).configReader;
|
|
1775
1760
|
configReader.getPluginConfig.mockReturnValue({});
|
|
1776
|
-
gitProvider.
|
|
1761
|
+
gitProvider.listIssueComments.mockResolvedValue([] as any);
|
|
1777
1762
|
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
1778
1763
|
gitProvider.getPullRequest.mockResolvedValue({ head: { sha: "abc123" } } as any);
|
|
1779
|
-
gitProvider.
|
|
1764
|
+
gitProvider.createIssueComment.mockResolvedValue({} as any);
|
|
1780
1765
|
const result = { issues: [], summary: [], round: 1 };
|
|
1781
1766
|
await (service as any).postOrUpdateReviewComment("o", "r", 1, result);
|
|
1782
|
-
expect(gitProvider.
|
|
1767
|
+
expect(gitProvider.createIssueComment).toHaveBeenCalled();
|
|
1783
1768
|
});
|
|
1784
1769
|
|
|
1785
1770
|
it("should update PR title when autoUpdatePrTitle enabled", async () => {
|
|
1786
1771
|
const configReader = (service as any).configReader;
|
|
1787
1772
|
configReader.getPluginConfig.mockReturnValue({ autoUpdatePrTitle: true });
|
|
1788
|
-
gitProvider.
|
|
1773
|
+
gitProvider.listIssueComments.mockResolvedValue([] as any);
|
|
1789
1774
|
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
1790
1775
|
gitProvider.editPullRequest.mockResolvedValue({} as any);
|
|
1791
1776
|
gitProvider.getPullRequest.mockResolvedValue({ head: { sha: "abc123" } } as any);
|
|
1792
|
-
gitProvider.
|
|
1777
|
+
gitProvider.createIssueComment.mockResolvedValue({} as any);
|
|
1793
1778
|
const result = { issues: [], summary: [], round: 1, title: "New Title" };
|
|
1794
1779
|
await (service as any).postOrUpdateReviewComment("o", "r", 1, result);
|
|
1795
1780
|
expect(gitProvider.editPullRequest).toHaveBeenCalledWith("o", "r", 1, { title: "New Title" });
|
|
1796
1781
|
});
|
|
1797
1782
|
|
|
1798
|
-
it("should handle
|
|
1783
|
+
it("should handle createIssueComment error gracefully", async () => {
|
|
1799
1784
|
const configReader = (service as any).configReader;
|
|
1800
1785
|
configReader.getPluginConfig.mockReturnValue({});
|
|
1801
|
-
gitProvider.
|
|
1786
|
+
gitProvider.listIssueComments.mockResolvedValue([] as any);
|
|
1802
1787
|
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
1803
1788
|
gitProvider.getPullRequest.mockResolvedValue({ head: { sha: "abc123" } } as any);
|
|
1804
|
-
gitProvider.
|
|
1789
|
+
gitProvider.createIssueComment.mockRejectedValue(new Error("fail") as any);
|
|
1805
1790
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1806
1791
|
const result = { issues: [], summary: [], round: 1 };
|
|
1807
1792
|
await (service as any).postOrUpdateReviewComment("o", "r", 1, result);
|
|
@@ -1813,9 +1798,10 @@ describe("ReviewService", () => {
|
|
|
1813
1798
|
const configReader = (service as any).configReader;
|
|
1814
1799
|
configReader.getPluginConfig.mockReturnValue({ lineComments: true });
|
|
1815
1800
|
mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
|
|
1816
|
-
gitProvider.
|
|
1801
|
+
gitProvider.listIssueComments.mockResolvedValue([] as any);
|
|
1817
1802
|
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
1818
1803
|
gitProvider.getPullRequest.mockResolvedValue({ head: { sha: "abc123" } } as any);
|
|
1804
|
+
gitProvider.createIssueComment.mockResolvedValue({} as any);
|
|
1819
1805
|
gitProvider.createPullReview.mockResolvedValue({} as any);
|
|
1820
1806
|
const result = {
|
|
1821
1807
|
issues: [
|
|
@@ -1832,6 +1818,7 @@ describe("ReviewService", () => {
|
|
|
1832
1818
|
round: 1,
|
|
1833
1819
|
};
|
|
1834
1820
|
await (service as any).postOrUpdateReviewComment("o", "r", 1, result);
|
|
1821
|
+
expect(gitProvider.createPullReview.mock.calls.length).toBeGreaterThan(0);
|
|
1835
1822
|
const callArgs = gitProvider.createPullReview.mock.calls[0];
|
|
1836
1823
|
expect(callArgs[3].comments.length).toBeGreaterThan(0);
|
|
1837
1824
|
});
|
|
@@ -1840,39 +1827,32 @@ describe("ReviewService", () => {
|
|
|
1840
1827
|
describe("ReviewService.syncResolvedComments", () => {
|
|
1841
1828
|
it("should mark matched issues as fixed", async () => {
|
|
1842
1829
|
mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
|
|
1843
|
-
gitProvider.
|
|
1844
|
-
{
|
|
1845
|
-
] as any);
|
|
1846
|
-
gitProvider.listPullReviewComments.mockResolvedValue([
|
|
1847
|
-
{ path: "test.ts", position: 10, resolver: { login: "user1" } },
|
|
1830
|
+
gitProvider.listResolvedThreads.mockResolvedValue([
|
|
1831
|
+
{ path: "test.ts", line: 10, resolvedBy: { login: "user1" } },
|
|
1848
1832
|
] as any);
|
|
1849
1833
|
const result = { issues: [{ file: "test.ts", line: "10" }] };
|
|
1850
1834
|
await (service as any).syncResolvedComments("o", "r", 1, result);
|
|
1851
1835
|
expect((result.issues[0] as any).fixed).toBeDefined();
|
|
1852
1836
|
});
|
|
1853
1837
|
|
|
1854
|
-
it("should skip
|
|
1855
|
-
|
|
1856
|
-
gitProvider.listPullReviews.mockResolvedValue([
|
|
1857
|
-
{ id: 1, body: "<!-- spaceflow-review --> content" },
|
|
1858
|
-
] as any);
|
|
1859
|
-
gitProvider.listPullReviewComments.mockResolvedValue([
|
|
1860
|
-
{ path: "test.ts", position: 10, resolver: null },
|
|
1861
|
-
] as any);
|
|
1838
|
+
it("should skip when no resolved threads", async () => {
|
|
1839
|
+
gitProvider.listResolvedThreads.mockResolvedValue([] as any);
|
|
1862
1840
|
const result = { issues: [{ file: "test.ts", line: "10" }] };
|
|
1863
1841
|
await (service as any).syncResolvedComments("o", "r", 1, result);
|
|
1864
1842
|
expect((result.issues[0] as any).fixed).toBeUndefined();
|
|
1865
1843
|
});
|
|
1866
1844
|
|
|
1867
|
-
it("should skip
|
|
1868
|
-
gitProvider.
|
|
1869
|
-
|
|
1845
|
+
it("should skip threads without path", async () => {
|
|
1846
|
+
gitProvider.listResolvedThreads.mockResolvedValue([
|
|
1847
|
+
{ path: undefined, line: 10, resolvedBy: { login: "user1" } },
|
|
1848
|
+
] as any);
|
|
1849
|
+
const result = { issues: [{ file: "test.ts", line: "10" }] };
|
|
1870
1850
|
await (service as any).syncResolvedComments("o", "r", 1, result);
|
|
1871
|
-
expect(result.issues[0].fixed).
|
|
1851
|
+
expect((result.issues[0] as any).fixed).toBeUndefined();
|
|
1872
1852
|
});
|
|
1873
1853
|
|
|
1874
1854
|
it("should handle error gracefully", async () => {
|
|
1875
|
-
gitProvider.
|
|
1855
|
+
gitProvider.listResolvedThreads.mockRejectedValue(new Error("fail"));
|
|
1876
1856
|
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
1877
1857
|
const result = { issues: [] };
|
|
1878
1858
|
await (service as any).syncResolvedComments("o", "r", 1, result);
|
|
@@ -1939,9 +1919,10 @@ describe("ReviewService", () => {
|
|
|
1939
1919
|
const mockReviewReportService = (service as any).reviewReportService;
|
|
1940
1920
|
mockReviewReportService.parseMarkdown.mockReturnValue({ result: mockResult });
|
|
1941
1921
|
mockReviewReportService.formatStatsTerminal = vi.fn().mockReturnValue("stats");
|
|
1942
|
-
gitProvider.
|
|
1943
|
-
{ id:
|
|
1922
|
+
gitProvider.listIssueComments.mockResolvedValue([
|
|
1923
|
+
{ id: 10, body: "<!-- spaceflow-review --> content" },
|
|
1944
1924
|
] as any);
|
|
1925
|
+
gitProvider.listPullReviews.mockResolvedValue([] as any);
|
|
1945
1926
|
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
1946
1927
|
gitProvider.getPullRequestCommits.mockResolvedValue([] as any);
|
|
1947
1928
|
gitProvider.getPullRequest.mockResolvedValue({} as any);
|
|
@@ -1952,6 +1933,34 @@ describe("ReviewService", () => {
|
|
|
1952
1933
|
});
|
|
1953
1934
|
});
|
|
1954
1935
|
|
|
1936
|
+
describe("ReviewService.execute - flush mode", () => {
|
|
1937
|
+
it("should route to executeCollectOnly when flush is true", async () => {
|
|
1938
|
+
const mockResult = { issues: [{ file: "a.ts", line: "1", ruleId: "R1" }], summary: [] };
|
|
1939
|
+
const mockReviewReportService = (service as any).reviewReportService;
|
|
1940
|
+
mockReviewReportService.parseMarkdown.mockReturnValue({ result: mockResult });
|
|
1941
|
+
mockReviewReportService.formatStatsTerminal = vi.fn().mockReturnValue("stats");
|
|
1942
|
+
gitProvider.listIssueComments.mockResolvedValue([
|
|
1943
|
+
{ id: 10, body: "<!-- spaceflow-review --> content" },
|
|
1944
|
+
] as any);
|
|
1945
|
+
gitProvider.listPullReviews.mockResolvedValue([] as any);
|
|
1946
|
+
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
1947
|
+
gitProvider.getPullRequestCommits.mockResolvedValue([] as any);
|
|
1948
|
+
gitProvider.getPullRequest.mockResolvedValue({} as any);
|
|
1949
|
+
const context = {
|
|
1950
|
+
owner: "o",
|
|
1951
|
+
repo: "r",
|
|
1952
|
+
prNumber: 1,
|
|
1953
|
+
ci: false,
|
|
1954
|
+
dryRun: false,
|
|
1955
|
+
flush: true,
|
|
1956
|
+
specSources: [],
|
|
1957
|
+
};
|
|
1958
|
+
const result = await service.execute(context as any);
|
|
1959
|
+
expect(result.issues).toHaveLength(1);
|
|
1960
|
+
expect(result.stats).toBeDefined();
|
|
1961
|
+
});
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1955
1964
|
describe("ReviewService.executeDeletionOnly", () => {
|
|
1956
1965
|
it("should throw when no llmMode", async () => {
|
|
1957
1966
|
const context = { owner: "o", repo: "r", prNumber: 1, ci: false, dryRun: false };
|
|
@@ -1973,10 +1982,11 @@ describe("ReviewService", () => {
|
|
|
1973
1982
|
gitProvider.getPullRequestFiles.mockResolvedValue([
|
|
1974
1983
|
{ filename: "a.ts", status: "modified" },
|
|
1975
1984
|
] as any);
|
|
1985
|
+
gitProvider.listIssueComments.mockResolvedValue([] as any);
|
|
1976
1986
|
gitProvider.listPullReviews.mockResolvedValue([] as any);
|
|
1977
1987
|
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
1978
1988
|
gitProvider.getPullRequest.mockResolvedValue({ head: { sha: "abc" } } as any);
|
|
1979
|
-
gitProvider.
|
|
1989
|
+
gitProvider.createIssueComment.mockResolvedValue({} as any);
|
|
1980
1990
|
const configReader = (service as any).configReader;
|
|
1981
1991
|
configReader.getPluginConfig.mockReturnValue({});
|
|
1982
1992
|
const context = {
|
|
@@ -2007,10 +2017,11 @@ describe("ReviewService", () => {
|
|
|
2007
2017
|
gitProvider.getPullRequestFiles.mockResolvedValue([
|
|
2008
2018
|
{ filename: "a.ts", status: "modified" },
|
|
2009
2019
|
] as any);
|
|
2020
|
+
gitProvider.listIssueComments.mockResolvedValue([] as any);
|
|
2010
2021
|
gitProvider.listPullReviews.mockResolvedValue([] as any);
|
|
2011
2022
|
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
2012
2023
|
gitProvider.getPullRequest.mockResolvedValue({ head: { sha: "abc" } } as any);
|
|
2013
|
-
gitProvider.
|
|
2024
|
+
gitProvider.createIssueComment.mockResolvedValue({} as any);
|
|
2014
2025
|
const configReader = (service as any).configReader;
|
|
2015
2026
|
configReader.getPluginConfig.mockReturnValue({});
|
|
2016
2027
|
const context = {
|
|
@@ -2025,7 +2036,7 @@ describe("ReviewService", () => {
|
|
|
2025
2036
|
};
|
|
2026
2037
|
const result = await (service as any).executeDeletionOnly(context);
|
|
2027
2038
|
expect(result.success).toBe(true);
|
|
2028
|
-
expect(gitProvider.
|
|
2039
|
+
expect(gitProvider.createIssueComment).toHaveBeenCalled();
|
|
2029
2040
|
});
|
|
2030
2041
|
});
|
|
2031
2042
|
|
|
@@ -2189,21 +2200,21 @@ describe("ReviewService", () => {
|
|
|
2189
2200
|
expect(consoleSpy).toHaveBeenCalled();
|
|
2190
2201
|
consoleSpy.mockRestore();
|
|
2191
2202
|
});
|
|
2192
|
-
|
|
2193
2203
|
it("should mark issue as invalid on thumbs down from reviewer", async () => {
|
|
2194
2204
|
mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
|
|
2195
2205
|
gitProvider.listPullReviews.mockResolvedValue([
|
|
2196
|
-
{ id: 1, body: "<!-- spaceflow-review --> content", user: { login: "bot" } },
|
|
2206
|
+
{ id: 1, body: "<!-- spaceflow-review-lines --> content", user: { login: "bot" } },
|
|
2197
2207
|
{ id: 2, body: "LGTM", user: { login: "reviewer1" } },
|
|
2198
2208
|
] as any);
|
|
2199
2209
|
gitProvider.getPullRequest.mockResolvedValue({
|
|
2210
|
+
head: { sha: "abc" },
|
|
2200
2211
|
requested_reviewers: [],
|
|
2201
2212
|
requested_reviewers_teams: [],
|
|
2202
2213
|
} as any);
|
|
2203
2214
|
gitProvider.listPullReviewComments.mockResolvedValue([
|
|
2204
2215
|
{ id: 100, path: "test.ts", position: 10 },
|
|
2205
2216
|
] as any);
|
|
2206
|
-
gitProvider.
|
|
2217
|
+
gitProvider.getPullReviewCommentReactions.mockResolvedValue([
|
|
2207
2218
|
{ content: "-1", user: { login: "reviewer1" } },
|
|
2208
2219
|
] as any);
|
|
2209
2220
|
const result = { issues: [{ file: "test.ts", line: "10", valid: "true" }] };
|
|
@@ -2214,7 +2225,7 @@ describe("ReviewService", () => {
|
|
|
2214
2225
|
it("should add requested_reviewers to reviewers set", async () => {
|
|
2215
2226
|
mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
|
|
2216
2227
|
gitProvider.listPullReviews.mockResolvedValue([
|
|
2217
|
-
{ id: 1, body: "<!-- spaceflow-review --> content", user: { login: "bot" } },
|
|
2228
|
+
{ id: 1, body: "<!-- spaceflow-review-lines --> content", user: { login: "bot" } },
|
|
2218
2229
|
] as any);
|
|
2219
2230
|
gitProvider.getPullRequest.mockResolvedValue({
|
|
2220
2231
|
requested_reviewers: [{ login: "req-reviewer" }],
|
|
@@ -2223,7 +2234,7 @@ describe("ReviewService", () => {
|
|
|
2223
2234
|
gitProvider.listPullReviewComments.mockResolvedValue([
|
|
2224
2235
|
{ id: 100, path: "test.ts", position: 10 },
|
|
2225
2236
|
] as any);
|
|
2226
|
-
gitProvider.
|
|
2237
|
+
gitProvider.getPullReviewCommentReactions.mockResolvedValue([
|
|
2227
2238
|
{ content: "-1", user: { login: "req-reviewer" } },
|
|
2228
2239
|
] as any);
|
|
2229
2240
|
const result = { issues: [{ file: "test.ts", line: "10", valid: "true" }] };
|
|
@@ -2234,7 +2245,7 @@ describe("ReviewService", () => {
|
|
|
2234
2245
|
it("should skip comments without id", async () => {
|
|
2235
2246
|
mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
|
|
2236
2247
|
gitProvider.listPullReviews.mockResolvedValue([
|
|
2237
|
-
{ id: 1, body: "<!-- spaceflow-review --> content" },
|
|
2248
|
+
{ id: 1, body: "<!-- spaceflow-review-lines --> content" },
|
|
2238
2249
|
] as any);
|
|
2239
2250
|
gitProvider.getPullRequest.mockResolvedValue({
|
|
2240
2251
|
requested_reviewers: [],
|
|
@@ -2251,7 +2262,7 @@ describe("ReviewService", () => {
|
|
|
2251
2262
|
it("should skip when reactions are empty", async () => {
|
|
2252
2263
|
mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
|
|
2253
2264
|
gitProvider.listPullReviews.mockResolvedValue([
|
|
2254
|
-
{ id: 1, body: "<!-- spaceflow-review --> content" },
|
|
2265
|
+
{ id: 1, body: "<!-- spaceflow-review-lines --> content" },
|
|
2255
2266
|
] as any);
|
|
2256
2267
|
gitProvider.getPullRequest.mockResolvedValue({
|
|
2257
2268
|
requested_reviewers: [],
|
|
@@ -2260,7 +2271,7 @@ describe("ReviewService", () => {
|
|
|
2260
2271
|
gitProvider.listPullReviewComments.mockResolvedValue([
|
|
2261
2272
|
{ id: 100, path: "test.ts", position: 10 },
|
|
2262
2273
|
] as any);
|
|
2263
|
-
gitProvider.
|
|
2274
|
+
gitProvider.getPullReviewCommentReactions.mockResolvedValue([] as any);
|
|
2264
2275
|
const result = { issues: [{ file: "test.ts", line: "10", reactions: [] }] };
|
|
2265
2276
|
await (service as any).syncReactionsToIssues("o", "r", 1, result);
|
|
2266
2277
|
expect(result.issues[0].reactions).toHaveLength(0);
|
|
@@ -2269,7 +2280,7 @@ describe("ReviewService", () => {
|
|
|
2269
2280
|
it("should store multiple reaction types", async () => {
|
|
2270
2281
|
mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
|
|
2271
2282
|
gitProvider.listPullReviews.mockResolvedValue([
|
|
2272
|
-
{ id: 1, body: "<!-- spaceflow-review --> content" },
|
|
2283
|
+
{ id: 1, body: "<!-- spaceflow-review-lines --> content" },
|
|
2273
2284
|
] as any);
|
|
2274
2285
|
gitProvider.getPullRequest.mockResolvedValue({
|
|
2275
2286
|
requested_reviewers: [],
|
|
@@ -2278,7 +2289,7 @@ describe("ReviewService", () => {
|
|
|
2278
2289
|
gitProvider.listPullReviewComments.mockResolvedValue([
|
|
2279
2290
|
{ id: 100, path: "test.ts", position: 10 },
|
|
2280
2291
|
] as any);
|
|
2281
|
-
gitProvider.
|
|
2292
|
+
gitProvider.getPullReviewCommentReactions.mockResolvedValue([
|
|
2282
2293
|
{ content: "+1", user: { login: "user1" } },
|
|
2283
2294
|
{ content: "+1", user: { login: "user2" } },
|
|
2284
2295
|
{ content: "heart", user: { login: "user1" } },
|
|
@@ -2291,7 +2302,7 @@ describe("ReviewService", () => {
|
|
|
2291
2302
|
it("should not mark as invalid when thumbs down from non-reviewer", async () => {
|
|
2292
2303
|
mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
|
|
2293
2304
|
gitProvider.listPullReviews.mockResolvedValue([
|
|
2294
|
-
{ id: 1, body: "<!-- spaceflow-review --> content" },
|
|
2305
|
+
{ id: 1, body: "<!-- spaceflow-review-lines --> content" },
|
|
2295
2306
|
] as any);
|
|
2296
2307
|
gitProvider.getPullRequest.mockResolvedValue({
|
|
2297
2308
|
requested_reviewers: [],
|
|
@@ -2300,7 +2311,7 @@ describe("ReviewService", () => {
|
|
|
2300
2311
|
gitProvider.listPullReviewComments.mockResolvedValue([
|
|
2301
2312
|
{ id: 100, path: "test.ts", position: 10 },
|
|
2302
2313
|
] as any);
|
|
2303
|
-
gitProvider.
|
|
2314
|
+
gitProvider.getPullReviewCommentReactions.mockResolvedValue([
|
|
2304
2315
|
{ content: "-1", user: { login: "random-user" } },
|
|
2305
2316
|
] as any);
|
|
2306
2317
|
const result = { issues: [{ file: "test.ts", line: "10", valid: "true", reactions: [] }] };
|
|
@@ -2765,19 +2776,20 @@ describe("ReviewService", () => {
|
|
|
2765
2776
|
mockReviewReportService.parseMarkdown.mockReturnValue({ result: mockResult });
|
|
2766
2777
|
mockReviewReportService.formatStatsTerminal = vi.fn().mockReturnValue("stats");
|
|
2767
2778
|
mockReviewReportService.formatMarkdown.mockReturnValue("report");
|
|
2768
|
-
gitProvider.
|
|
2769
|
-
{ id:
|
|
2779
|
+
gitProvider.listIssueComments.mockResolvedValue([
|
|
2780
|
+
{ id: 10, body: "<!-- spaceflow-review --> content" },
|
|
2770
2781
|
] as any);
|
|
2782
|
+
gitProvider.listPullReviews.mockResolvedValue([] as any);
|
|
2771
2783
|
gitProvider.listPullReviewComments.mockResolvedValue([] as any);
|
|
2772
2784
|
gitProvider.getPullRequestCommits.mockResolvedValue([] as any);
|
|
2773
2785
|
gitProvider.getPullRequest.mockResolvedValue({ head: { sha: "abc" } } as any);
|
|
2774
|
-
gitProvider.
|
|
2786
|
+
gitProvider.updateIssueComment.mockResolvedValue({} as any);
|
|
2775
2787
|
const configReader = (service as any).configReader;
|
|
2776
2788
|
configReader.getPluginConfig.mockReturnValue({});
|
|
2777
2789
|
const context = { owner: "o", repo: "r", prNumber: 1, ci: true, dryRun: false, verbose: 1 };
|
|
2778
2790
|
const result = await (service as any).executeCollectOnly(context);
|
|
2779
2791
|
expect(result.issues).toHaveLength(1);
|
|
2780
|
-
expect(gitProvider.
|
|
2792
|
+
expect(gitProvider.updateIssueComment).toHaveBeenCalled();
|
|
2781
2793
|
});
|
|
2782
2794
|
});
|
|
2783
2795
|
|