From 3937c1b0aa85693b80a3b539d559eb637be11709 Mon Sep 17 00:00:00 2001 From: Kostyuk1998 <48120201+Kostyuk1998@users.noreply.github.com> Date: Tue, 14 Jul 2026 06:55:14 +0000 Subject: [PATCH] fix(webhook): remove dismissed reviews from scoring --- .../webhook/handlers/review.handler.spec.ts | 94 +++++++++++++++++++ .../src/webhook/handlers/review.handler.ts | 15 ++- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 packages/das/src/webhook/handlers/review.handler.spec.ts diff --git a/packages/das/src/webhook/handlers/review.handler.spec.ts b/packages/das/src/webhook/handlers/review.handler.spec.ts new file mode 100644 index 0000000..0915c9f --- /dev/null +++ b/packages/das/src/webhook/handlers/review.handler.spec.ts @@ -0,0 +1,94 @@ +import { ReviewHandler } from "./review.handler"; + +type HandlerHarness = { + handler: ReviewHandler; + reviewRepo: { + delete: jest.Mock; + upsert: jest.Mock; + }; + repoRepo: { + update: jest.Mock; + }; +}; + +function makeHandler(): HandlerHarness { + const reviewRepo = { + delete: jest.fn().mockResolvedValue(undefined), + upsert: jest.fn().mockResolvedValue(undefined), + }; + const repoRepo = { + update: jest.fn().mockResolvedValue(undefined), + }; + + return { + handler: new ReviewHandler(reviewRepo as never, repoRepo as never), + reviewRepo, + repoRepo, + }; +} + +function reviewPayload(action: string): Record { + return { + action, + repository: { full_name: "acme/widgets" }, + pull_request: { number: 42 }, + review: { + user: { id: 123, login: "reviewer" }, + author_association: "MEMBER", + state: "changes_requested", + submitted_at: "2026-07-01T12:00:00Z", + }, + }; +} + +describe("ReviewHandler", () => { + it("deletes a dismissed review by its stored natural key", async () => { + const { handler, reviewRepo, repoRepo } = makeHandler(); + + await handler.handle(reviewPayload("dismissed")); + + expect(reviewRepo.delete).toHaveBeenCalledWith({ + repoFullName: "acme/widgets", + prNumber: 42, + reviewerGithubId: "123", + submittedAt: "2026-07-01T12:00:00Z", + }); + expect(reviewRepo.upsert).not.toHaveBeenCalled(); + expect(repoRepo.update).toHaveBeenCalledWith("acme/widgets", { + lastEventAt: expect.any(String), + }); + }); + + it("continues to upsert submitted reviews", async () => { + const { handler, reviewRepo, repoRepo } = makeHandler(); + + await handler.handle(reviewPayload("submitted")); + + expect(reviewRepo.delete).not.toHaveBeenCalled(); + expect(reviewRepo.upsert).toHaveBeenCalledWith( + { + repoFullName: "acme/widgets", + prNumber: 42, + reviewerGithubId: "123", + reviewerLogin: "reviewer", + reviewerAssociation: "MEMBER", + reviewState: "CHANGES_REQUESTED", + submittedAt: "2026-07-01T12:00:00Z", + }, + ["repoFullName", "prNumber", "reviewerGithubId", "submittedAt"], + ); + expect(repoRepo.update).toHaveBeenCalledWith("acme/widgets", { + lastEventAt: expect.any(String), + }); + }); + + it("ignores review actions that are neither submitted nor dismissed", async () => { + const { handler, reviewRepo, repoRepo } = makeHandler(); + + await handler.handle(reviewPayload("edited")); + + expect(reviewRepo.delete).not.toHaveBeenCalled(); + expect(reviewRepo.upsert).not.toHaveBeenCalled(); + expect(repoRepo.update).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/das/src/webhook/handlers/review.handler.ts b/packages/das/src/webhook/handlers/review.handler.ts index 3a5f573..6dcf2ad 100644 --- a/packages/das/src/webhook/handlers/review.handler.ts +++ b/packages/das/src/webhook/handlers/review.handler.ts @@ -17,7 +17,20 @@ export class ReviewHandler { const review = payload.review; const repoFullName: string = payload.repository.full_name; - // Only store submitted reviews (not pending/dismissed) + if (payload.action === "dismissed") { + await this.reviewRepo.delete({ + repoFullName, + prNumber: payload.pull_request.number, + reviewerGithubId: String(review.user.id), + submittedAt: review.submitted_at, + }); + await this.repoRepo.update(repoFullName, { + lastEventAt: new Date().toISOString(), + }); + return; + } + + // Only store submitted reviews (not pending) if (payload.action !== "submitted") return; const data: Partial = {