Merge pull request #125 from github/prevent-duplicate-approvals
Prevent Duplicate Approvals
This commit is contained in:
Коммит
245059b04a
|
@ -6,6 +6,14 @@ import * as core from "@actions/core";
|
|||
jest.spyOn(core, "debug").mockImplementation(() => {});
|
||||
jest.spyOn(core, "info").mockImplementation(() => {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
nock("https://api.github.com")
|
||||
.persist()
|
||||
.get("/user")
|
||||
.reply(200, { login: "octocat" });
|
||||
});
|
||||
|
||||
test("It creates an approved review", async () => {
|
||||
process.env["GITHUB_REPOSITORY"] = "foo/bar";
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const nock = require("nock");
|
||||
import { GitHubProvider } from "../src/github-provider";
|
||||
import { PrivilegedRequester } from "../src/privileged-requester";
|
||||
|
||||
|
@ -6,6 +7,14 @@ import * as core from "@actions/core";
|
|||
jest.spyOn(core, "debug").mockImplementation(() => {});
|
||||
jest.spyOn(core, "info").mockImplementation(() => {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
nock("https://api.github.com")
|
||||
.persist()
|
||||
.get("/user")
|
||||
.reply(200, { login: "octocat" });
|
||||
});
|
||||
|
||||
test("We receive the expected config content", async () => {
|
||||
let configContent = `---
|
||||
requesters:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { GitHubProvider } from "../src/github-provider";
|
||||
import { PullRequest } from "../src/pull-request";
|
||||
import * as core from "@actions/core";
|
||||
const nock = require("nock");
|
||||
|
||||
// jest spy on to silence output
|
||||
jest.spyOn(core, "info").mockImplementation(() => {});
|
||||
|
@ -10,13 +11,37 @@ jest.spyOn(core, "debug").mockImplementation(() => {});
|
|||
jest.spyOn(core, "setFailed").mockImplementation(() => {});
|
||||
jest.spyOn(core, "setOutput").mockImplementation(() => {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
nock("https://api.github.com")
|
||||
.persist()
|
||||
.get("/user")
|
||||
.reply(200, { login: "octocat" });
|
||||
});
|
||||
|
||||
test("We can create a review", async () => {
|
||||
let provider = new GitHubProvider("token");
|
||||
jest.spyOn(provider, "hasAlreadyApproved").mockImplementation(() => false);
|
||||
jest.spyOn(provider, "createReview").mockImplementation(() => true);
|
||||
expect(provider.createReview()).toBe(true);
|
||||
|
||||
let pullRequest = new PullRequest(provider);
|
||||
let approval = await pullRequest.approve();
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
"Approving the PR for a privileged reviewer.",
|
||||
);
|
||||
expect(approval).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
test("We attempt to create a review but we already approved in a previous workflow run", async () => {
|
||||
let provider = new GitHubProvider("token");
|
||||
jest.spyOn(provider, "hasAlreadyApproved").mockImplementation(() => true);
|
||||
|
||||
let pullRequest = new PullRequest(provider);
|
||||
let approval = await pullRequest.approve();
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
"PR has already been approved by this Action, skipping duplicate approval.",
|
||||
);
|
||||
expect(approval).toStrictEqual(undefined);
|
||||
});
|
||||
|
||||
|
|
|
@ -4,6 +4,13 @@ import { PullRequest } from "../src/pull-request";
|
|||
import { Runner } from "../src/runner";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
const nock = require("nock");
|
||||
|
||||
nock("https://api.github.com")
|
||||
.persist()
|
||||
.get("/user")
|
||||
.reply(200, { login: "octocat" });
|
||||
|
||||
let provider = new GitHubProvider("token");
|
||||
let pullRequest = new PullRequest(provider);
|
||||
let runner = new Runner(pullRequest);
|
||||
|
|
|
@ -37664,6 +37664,49 @@ class GitHubProvider {
|
|||
this.configContent = false;
|
||||
}
|
||||
|
||||
async getCurrentUser() {
|
||||
const { data: currentUser } =
|
||||
await this.octokit.rest.users.getAuthenticated();
|
||||
return currentUser.login;
|
||||
}
|
||||
|
||||
// check if the current authenticated user (login) has an active APPROVED review on the PR
|
||||
// returns true if the user has an active APPROVED review, false otherwise
|
||||
// note: if the user had an active APPROVED review but it was dismissed, this will return false
|
||||
async hasAlreadyApproved(prNumber) {
|
||||
// get the login of the current authenticated user
|
||||
const login = await this.getCurrentUser();
|
||||
core.info(
|
||||
`checking if ${login} has already approved PR #${prNumber} in a previous workflow run`,
|
||||
);
|
||||
|
||||
const { data: reviews } = await this.octokit.rest.pulls.listReviews({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
});
|
||||
|
||||
// filter out all reviews that are not APPROVED
|
||||
const approvedReviews = reviews.filter(
|
||||
(review) => review.state === "APPROVED",
|
||||
);
|
||||
|
||||
// filter out all reviews that are not by the current authenticated user via login
|
||||
const approvedReviewsByUser = approvedReviews.filter(
|
||||
(review) => review.user.login.toLowerCase() === login.toLowerCase(),
|
||||
);
|
||||
|
||||
const approved = approvedReviewsByUser.length > 0;
|
||||
if (approved) {
|
||||
core.info(
|
||||
`${login} has already approved PR #${prNumber} in a previous workflow run`,
|
||||
);
|
||||
}
|
||||
|
||||
// if there are any reviews left, then login (this Action) has already approved the PR and we should not approve it again
|
||||
return approved;
|
||||
}
|
||||
|
||||
async createReview(prNumber, reviewEvent) {
|
||||
core.debug(`prNumber: ${prNumber}`);
|
||||
core.debug(`reviewEvent: ${reviewEvent}`);
|
||||
|
@ -37784,6 +37827,15 @@ class PullRequest {
|
|||
|
||||
async approve() {
|
||||
try {
|
||||
// before we approved the PR, check to see if this workflow has already approved the PR in a previous run
|
||||
if (await this.github.hasAlreadyApproved(this.prNumber)) {
|
||||
lib_core.info(
|
||||
"PR has already been approved by this Action, skipping duplicate approval.",
|
||||
);
|
||||
lib_core.setOutput("approved", "true"); // set to true as we have already approved the PR at some point
|
||||
return;
|
||||
}
|
||||
|
||||
lib_core.info("Approving the PR for a privileged reviewer.");
|
||||
await this.github.createReview(this.prNumber, "APPROVE");
|
||||
lib_core.info("PR approved, all set!");
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -10,6 +10,49 @@ class GitHubProvider {
|
|||
this.configContent = false;
|
||||
}
|
||||
|
||||
async getCurrentUser() {
|
||||
const { data: currentUser } =
|
||||
await this.octokit.rest.users.getAuthenticated();
|
||||
return currentUser.login;
|
||||
}
|
||||
|
||||
// check if the current authenticated user (login) has an active APPROVED review on the PR
|
||||
// returns true if the user has an active APPROVED review, false otherwise
|
||||
// note: if the user had an active APPROVED review but it was dismissed, this will return false
|
||||
async hasAlreadyApproved(prNumber) {
|
||||
// get the login of the current authenticated user
|
||||
const login = await this.getCurrentUser();
|
||||
core.info(
|
||||
`checking if ${login} has already approved PR #${prNumber} in a previous workflow run`,
|
||||
);
|
||||
|
||||
const { data: reviews } = await this.octokit.rest.pulls.listReviews({
|
||||
owner: github.context.repo.owner,
|
||||
repo: github.context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
});
|
||||
|
||||
// filter out all reviews that are not APPROVED
|
||||
const approvedReviews = reviews.filter(
|
||||
(review) => review.state === "APPROVED",
|
||||
);
|
||||
|
||||
// filter out all reviews that are not by the current authenticated user via login
|
||||
const approvedReviewsByUser = approvedReviews.filter(
|
||||
(review) => review.user.login.toLowerCase() === login.toLowerCase(),
|
||||
);
|
||||
|
||||
const approved = approvedReviewsByUser.length > 0;
|
||||
if (approved) {
|
||||
core.info(
|
||||
`${login} has already approved PR #${prNumber} in a previous workflow run`,
|
||||
);
|
||||
}
|
||||
|
||||
// if there are any reviews left, then login (this Action) has already approved the PR and we should not approve it again
|
||||
return approved;
|
||||
}
|
||||
|
||||
async createReview(prNumber, reviewEvent) {
|
||||
core.debug(`prNumber: ${prNumber}`);
|
||||
core.debug(`reviewEvent: ${reviewEvent}`);
|
||||
|
|
|
@ -15,6 +15,15 @@ class PullRequest {
|
|||
|
||||
async approve() {
|
||||
try {
|
||||
// before we approved the PR, check to see if this workflow has already approved the PR in a previous run
|
||||
if (await this.github.hasAlreadyApproved(this.prNumber)) {
|
||||
core.info(
|
||||
"PR has already been approved by this Action, skipping duplicate approval.",
|
||||
);
|
||||
core.setOutput("approved", "true"); // set to true as we have already approved the PR at some point
|
||||
return;
|
||||
}
|
||||
|
||||
core.info("Approving the PR for a privileged reviewer.");
|
||||
await this.github.createReview(this.prNumber, "APPROVE");
|
||||
core.info("PR approved, all set!");
|
||||
|
|
Загрузка…
Ссылка в новой задаче