@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.
@@ -1,21 +1,8 @@
1
- import { vi, type Mocked, type Mock } from "vitest";
2
- import { Test, TestingModule } from "@nestjs/testing";
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 { IssueVerifyService } from "./issue-verify.service";
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: Mocked<GitProviderService>;
60
- let configService: Mocked<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(async () => {
66
- const mockGitProvider = {
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
- const mockConfigService = {
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
- const module: TestingModule = await Test.createTestingModule({
143
- providers: [
144
- ReviewService,
145
- {
146
- provide: GitProviderService,
147
- useValue: mockGitProvider,
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
- service = module.get<ReviewService>(ReviewService);
197
- gitProvider = module.get(GitProviderService) as Mocked<GitProviderService>;
198
- configService = module.get(ConfigService) as Mocked<ConfigService>;
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.createPullReview.mockResolvedValue({});
329
+ gitProvider.createIssueComment.mockResolvedValue({});
369
330
 
370
331
  await service.execute(context);
371
332
 
372
- expect(gitProvider.createPullReview).toHaveBeenCalledWith(
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.createPullReview.mockResolvedValue({});
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.createPullReview).toHaveBeenCalled();
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 not filter if existing issue is not valid", () => {
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(1);
1471
- expect(result.skippedCount).toBe(0);
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.listPullReviews.mockResolvedValue([{ body: "normal review" }] as any);
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.listPullReviews.mockResolvedValue([
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.listPullReviews.mockRejectedValue(new Error("API error"));
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 handle error gracefully", async () => {
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.listPullReviews.mockResolvedValue([] as any);
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.createPullReview.mockResolvedValue({} as any);
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.createPullReview).toHaveBeenCalled();
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.listPullReviews.mockResolvedValue([] as any);
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.createPullReview.mockResolvedValue({} as any);
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 createPullReview error gracefully", async () => {
1783
+ it("should handle createIssueComment error gracefully", async () => {
1799
1784
  const configReader = (service as any).configReader;
1800
1785
  configReader.getPluginConfig.mockReturnValue({});
1801
- gitProvider.listPullReviews.mockResolvedValue([] as any);
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.createPullReview.mockRejectedValue(new Error("fail") as any);
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.listPullReviews.mockResolvedValue([] as any);
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.listPullReviews.mockResolvedValue([
1844
- { id: 1, body: "<!-- spaceflow-review --> content" },
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 resolved comments with no resolver", async () => {
1855
- mockReviewSpecService.parseLineRange = vi.fn().mockReturnValue([10]);
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 when no AI review found", async () => {
1868
- gitProvider.listPullReviews.mockResolvedValue([{ id: 1, body: "normal review" }] as any);
1869
- const result = { issues: [{ file: "test.ts", line: "10", fixed: false }] };
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).toBe(false);
1851
+ expect((result.issues[0] as any).fixed).toBeUndefined();
1872
1852
  });
1873
1853
 
1874
1854
  it("should handle error gracefully", async () => {
1875
- gitProvider.listPullReviews.mockRejectedValue(new Error("fail"));
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.listPullReviews.mockResolvedValue([
1943
- { id: 1, body: "<!-- spaceflow-review --> content" },
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.createPullReview.mockResolvedValue({} as any);
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.createPullReview.mockResolvedValue({} as any);
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.createPullReview).toHaveBeenCalled();
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.getIssueCommentReactions.mockResolvedValue([
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.getIssueCommentReactions.mockResolvedValue([
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.getIssueCommentReactions.mockResolvedValue([] as any);
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.getIssueCommentReactions.mockResolvedValue([
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.getIssueCommentReactions.mockResolvedValue([
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.listPullReviews.mockResolvedValue([
2769
- { id: 1, body: "<!-- spaceflow-review --> content" },
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.createPullReview.mockResolvedValue({} as any);
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.createPullReview).toHaveBeenCalled();
2792
+ expect(gitProvider.updateIssueComment).toHaveBeenCalled();
2781
2793
  });
2782
2794
  });
2783
2795