From bed7e29ddc1ba5498faa2c7cc33ec3b127947dcf Mon Sep 17 00:00:00 2001 From: Andrea Lamparelli Date: Thu, 27 Jul 2023 12:31:04 +0200 Subject: [PATCH] feat(issue-70): additional pr comments --- README.md | 1 + action.yml | 3 + dist/cli/index.js | 32 +++++++- dist/gha/index.js | 31 +++++++- src/service/args/args-parser.ts | 1 + src/service/args/args-utils.ts | 6 ++ src/service/args/args.types.ts | 1 + src/service/args/cli/cli-args-parser.ts | 4 +- src/service/args/gha/gha-args-parser.ts | 3 +- .../configs/pullrequest/pr-configs-parser.ts | 2 +- src/service/git/github/github-client.ts | 13 ++++ src/service/git/gitlab/gitlab-client.ts | 12 +++ test/service/args/cli/cli-args-parser.test.ts | 31 ++++++++ test/service/args/gha/gha-args-parser.test.ts | 26 +++++++ .../github-pr-configs-parser.test.ts | 75 +++++++++++++++++++ .../gitlab-pr-configs-parser.test.ts | 74 ++++++++++++++++++ test/service/git/gitlab/gitlab-client.test.ts | 14 ++-- test/service/runner/cli-github-runner.test.ts | 47 ++++++++++++ test/service/runner/gha-github-runner.test.ts | 45 +++++++++++ test/support/mock/git-client-mock-support.ts | 19 ++++- 20 files changed, 424 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 15d8d58..1988232 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ This tool comes with some inputs that allow users to override the default behavi | No squash | --no-squash | N | If provided the backporting will try to backport all pull request commits without squashing | false | | Strategy | --strategy | N | Cherry pick merging strategy, see [git-merge](https://git-scm.com/docs/git-merge#_merge_strategies) doc for all possible values | "recursive" | | Strategy Option | --strategy-option | N | Cherry pick merging strategy option, see [git-merge](https://git-scm.com/docs/git-merge#_merge_strategies) doc for all possible values | "theirs" | +| Additional comments | --comments | N | Semicolon separated list of additional comments to be posted to the backported pull request | [] | | Dry Run | -d, --dry-run | N | If enabled the tool does not push nor create anything remotely, use this to skip PR creation | false | > **NOTE**: `pull request` and `target branch` are *mandatory*, they must be provided as CLI options or as part of the configuration file (if used). diff --git a/action.yml b/action.yml index 90bc2d5..5deca80 100644 --- a/action.yml +++ b/action.yml @@ -67,6 +67,9 @@ inputs: description: "Cherry-pick merge strategy option" required: false default: "theirs" + comments: + description: "Semicolon separated list of additional comments to be posted to the backported pull request" + required: false runs: using: node16 diff --git a/dist/cli/index.js b/dist/cli/index.js index 4d4cb2b..87e057f 100755 --- a/dist/cli/index.js +++ b/dist/cli/index.js @@ -63,6 +63,7 @@ class ArgsParser { squash: this.getOrDefault(args.squash, true), strategy: this.getOrDefault(args.strategy), strategyOption: this.getOrDefault(args.strategyOption), + comments: this.getOrDefault(args.comments) }; } } @@ -100,7 +101,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getAsBooleanOrDefault = exports.getAsCommaSeparatedList = exports.getAsCleanedCommaSeparatedList = exports.getOrUndefined = exports.readConfigFile = exports.parseArgs = void 0; +exports.getAsBooleanOrDefault = exports.getAsSemicolonSeparatedList = exports.getAsCommaSeparatedList = exports.getAsCleanedCommaSeparatedList = exports.getOrUndefined = exports.readConfigFile = exports.parseArgs = void 0; const fs = __importStar(__nccwpck_require__(7147)); /** * Parse the input configuation string as json object and @@ -145,6 +146,12 @@ function getAsCommaSeparatedList(value) { return trimmed !== "" ? trimmed.split(",").map(v => v.trim()) : undefined; } exports.getAsCommaSeparatedList = getAsCommaSeparatedList; +function getAsSemicolonSeparatedList(value) { + // trim the value + const trimmed = value.trim(); + return trimmed !== "" ? trimmed.split(";").map(v => v.trim()) : undefined; +} +exports.getAsSemicolonSeparatedList = getAsSemicolonSeparatedList; function getAsBooleanOrDefault(value) { const trimmed = value.trim(); return trimmed !== "" ? trimmed.toLowerCase() === "true" : undefined; @@ -191,6 +198,7 @@ class CLIArgsParser extends args_parser_1.default { .option("--no-squash", "if provided the tool will backport all commits as part of the pull request") .option("--strategy ", "cherry-pick merge strategy, default to 'recursive'", undefined) .option("--strategy-option ", "cherry-pick merge strategy option, default to 'theirs'") + .option("--comments ", "semicolon separated list of additional comments to be posted to the backported pull request", args_utils_1.getAsSemicolonSeparatedList) .option("-cf, --config-file ", "configuration file containing all valid options, the json must match Args interface"); } readArgs() { @@ -223,6 +231,7 @@ class CLIArgsParser extends args_parser_1.default { squash: opts.squash, strategy: opts.strategy, strategyOption: opts.strategyOption, + comments: opts.comments, }; } return args; @@ -356,7 +365,7 @@ class PullRequestConfigsParser extends configs_parser_1.default { reviewers: [...new Set(reviewers)], assignees: [...new Set(args.assignees)], labels: [...new Set(labels)], - comments: [], // TODO fix comments + comments: args.comments ?? [], }; } } @@ -721,6 +730,16 @@ class GitHubClient { assignees: backport.assignees, }).catch(error => this.logger.error(`Error setting assignees: ${error}`))); } + if (backport.comments.length > 0) { + backport.comments.forEach(c => { + promises.push(this.octokit.issues.createComment({ + owner: backport.owner, + repo: backport.repo, + issue_number: data.number, + body: c, + }).catch(error => this.logger.error(`Error posting comment: ${error}`))); + }); + } await Promise.all(promises); return data.html_url; } @@ -917,6 +936,15 @@ class GitLabClient { labels: backport.labels.join(","), }).catch(error => this.logger.warn("Failure trying to update labels. " + error))); } + // comments + if (backport.comments.length > 0) { + this.logger.info("Posting comments: " + backport.comments); + backport.comments.forEach(c => { + promises.push(this.client.post(`/projects/${projectId}/merge_requests/${mr.iid}/notes`, { + body: c, + }).catch(error => this.logger.warn("Failure trying to post comment. " + error))); + }); + } // reviewers const reviewerIds = await Promise.all(backport.reviewers.map(async (r) => { this.logger.debug("Retrieving user: " + r); diff --git a/dist/gha/index.js b/dist/gha/index.js index c89d340..a4d41a2 100755 --- a/dist/gha/index.js +++ b/dist/gha/index.js @@ -63,6 +63,7 @@ class ArgsParser { squash: this.getOrDefault(args.squash, true), strategy: this.getOrDefault(args.strategy), strategyOption: this.getOrDefault(args.strategyOption), + comments: this.getOrDefault(args.comments) }; } } @@ -100,7 +101,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getAsBooleanOrDefault = exports.getAsCommaSeparatedList = exports.getAsCleanedCommaSeparatedList = exports.getOrUndefined = exports.readConfigFile = exports.parseArgs = void 0; +exports.getAsBooleanOrDefault = exports.getAsSemicolonSeparatedList = exports.getAsCommaSeparatedList = exports.getAsCleanedCommaSeparatedList = exports.getOrUndefined = exports.readConfigFile = exports.parseArgs = void 0; const fs = __importStar(__nccwpck_require__(7147)); /** * Parse the input configuation string as json object and @@ -145,6 +146,12 @@ function getAsCommaSeparatedList(value) { return trimmed !== "" ? trimmed.split(",").map(v => v.trim()) : undefined; } exports.getAsCommaSeparatedList = getAsCommaSeparatedList; +function getAsSemicolonSeparatedList(value) { + // trim the value + const trimmed = value.trim(); + return trimmed !== "" ? trimmed.split(";").map(v => v.trim()) : undefined; +} +exports.getAsSemicolonSeparatedList = getAsSemicolonSeparatedList; function getAsBooleanOrDefault(value) { const trimmed = value.trim(); return trimmed !== "" ? trimmed.toLowerCase() === "true" : undefined; @@ -194,6 +201,7 @@ class GHAArgsParser extends args_parser_1.default { squash: !(0, args_utils_1.getAsBooleanOrDefault)((0, core_1.getInput)("no-squash")), strategy: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("strategy")), strategyOption: (0, args_utils_1.getOrUndefined)((0, core_1.getInput)("strategy-option")), + comments: (0, args_utils_1.getAsSemicolonSeparatedList)((0, core_1.getInput)("comments")), }; } return args; @@ -327,7 +335,7 @@ class PullRequestConfigsParser extends configs_parser_1.default { reviewers: [...new Set(reviewers)], assignees: [...new Set(args.assignees)], labels: [...new Set(labels)], - comments: [], // TODO fix comments + comments: args.comments ?? [], }; } } @@ -692,6 +700,16 @@ class GitHubClient { assignees: backport.assignees, }).catch(error => this.logger.error(`Error setting assignees: ${error}`))); } + if (backport.comments.length > 0) { + backport.comments.forEach(c => { + promises.push(this.octokit.issues.createComment({ + owner: backport.owner, + repo: backport.repo, + issue_number: data.number, + body: c, + }).catch(error => this.logger.error(`Error posting comment: ${error}`))); + }); + } await Promise.all(promises); return data.html_url; } @@ -888,6 +906,15 @@ class GitLabClient { labels: backport.labels.join(","), }).catch(error => this.logger.warn("Failure trying to update labels. " + error))); } + // comments + if (backport.comments.length > 0) { + this.logger.info("Posting comments: " + backport.comments); + backport.comments.forEach(c => { + promises.push(this.client.post(`/projects/${projectId}/merge_requests/${mr.iid}/notes`, { + body: c, + }).catch(error => this.logger.warn("Failure trying to post comment. " + error))); + }); + } // reviewers const reviewerIds = await Promise.all(backport.reviewers.map(async (r) => { this.logger.debug("Retrieving user: " + r); diff --git a/src/service/args/args-parser.ts b/src/service/args/args-parser.ts index d70fa27..5aee4a6 100644 --- a/src/service/args/args-parser.ts +++ b/src/service/args/args-parser.ts @@ -41,6 +41,7 @@ export default abstract class ArgsParser { squash: this.getOrDefault(args.squash, true), strategy: this.getOrDefault(args.strategy), strategyOption: this.getOrDefault(args.strategyOption), + comments: this.getOrDefault(args.comments) }; } } \ No newline at end of file diff --git a/src/service/args/args-utils.ts b/src/service/args/args-utils.ts index 13a3606..9f6165c 100644 --- a/src/service/args/args-utils.ts +++ b/src/service/args/args-utils.ts @@ -44,6 +44,12 @@ export function getAsCommaSeparatedList(value: string): string[] | undefined { return trimmed !== "" ? trimmed.split(",").map(v => v.trim()) : undefined; } +export function getAsSemicolonSeparatedList(value: string): string[] | undefined { + // trim the value + const trimmed: string = value.trim(); + return trimmed !== "" ? trimmed.split(";").map(v => v.trim()) : undefined; +} + export function getAsBooleanOrDefault(value: string): boolean | undefined { const trimmed = value.trim(); return trimmed !== "" ? trimmed.toLowerCase() === "true" : undefined; diff --git a/src/service/args/args.types.ts b/src/service/args/args.types.ts index b3c1860..880257c 100644 --- a/src/service/args/args.types.ts +++ b/src/service/args/args.types.ts @@ -21,4 +21,5 @@ export interface Args { squash?: boolean, // if false use squashed/merged commit otherwise backport all commits as part of the pr strategy?: string, // cherry-pick merge strategy strategyOption?: string, // cherry-pick merge strategy option + comments?: string[], // additional comments to be posted } \ No newline at end of file diff --git a/src/service/args/cli/cli-args-parser.ts b/src/service/args/cli/cli-args-parser.ts index a6065dc..0e8b4e3 100644 --- a/src/service/args/cli/cli-args-parser.ts +++ b/src/service/args/cli/cli-args-parser.ts @@ -2,7 +2,7 @@ import ArgsParser from "@bp/service/args/args-parser"; import { Args } from "@bp/service/args/args.types"; import { Command } from "commander"; import { name, version, description } from "@bp/../package.json"; -import { getAsCleanedCommaSeparatedList, getAsCommaSeparatedList, readConfigFile } from "@bp/service/args/args-utils"; +import { getAsCleanedCommaSeparatedList, getAsCommaSeparatedList, getAsSemicolonSeparatedList, readConfigFile } from "@bp/service/args/args-utils"; export default class CLIArgsParser extends ArgsParser { @@ -29,6 +29,7 @@ export default class CLIArgsParser extends ArgsParser { .option("--no-squash", "if provided the tool will backport all commits as part of the pull request") .option("--strategy ", "cherry-pick merge strategy, default to 'recursive'", undefined) .option("--strategy-option ", "cherry-pick merge strategy option, default to 'theirs'") + .option("--comments ", "semicolon separated list of additional comments to be posted to the backported pull request", getAsSemicolonSeparatedList) .option("-cf, --config-file ", "configuration file containing all valid options, the json must match Args interface"); } @@ -62,6 +63,7 @@ export default class CLIArgsParser extends ArgsParser { squash: opts.squash, strategy: opts.strategy, strategyOption: opts.strategyOption, + comments: opts.comments, }; } diff --git a/src/service/args/gha/gha-args-parser.ts b/src/service/args/gha/gha-args-parser.ts index e3d5ba0..a6c2534 100644 --- a/src/service/args/gha/gha-args-parser.ts +++ b/src/service/args/gha/gha-args-parser.ts @@ -1,7 +1,7 @@ import ArgsParser from "@bp/service/args/args-parser"; import { Args } from "@bp/service/args/args.types"; import { getInput } from "@actions/core"; -import { getAsBooleanOrDefault, getAsCleanedCommaSeparatedList, getAsCommaSeparatedList, getOrUndefined, readConfigFile } from "@bp/service/args/args-utils"; +import { getAsBooleanOrDefault, getAsCleanedCommaSeparatedList, getAsCommaSeparatedList, getAsSemicolonSeparatedList, getOrUndefined, readConfigFile } from "@bp/service/args/args-utils"; export default class GHAArgsParser extends ArgsParser { @@ -32,6 +32,7 @@ export default class GHAArgsParser extends ArgsParser { squash: !getAsBooleanOrDefault(getInput("no-squash")), strategy: getOrUndefined(getInput("strategy")), strategyOption: getOrUndefined(getInput("strategy-option")), + comments: getAsSemicolonSeparatedList(getInput("comments")), }; } diff --git a/src/service/configs/pullrequest/pr-configs-parser.ts b/src/service/configs/pullrequest/pr-configs-parser.ts index 4eb1111..3cc81ce 100644 --- a/src/service/configs/pullrequest/pr-configs-parser.ts +++ b/src/service/configs/pullrequest/pr-configs-parser.ts @@ -92,7 +92,7 @@ export default class PullRequestConfigsParser extends ConfigsParser { reviewers: [...new Set(reviewers)], assignees: [...new Set(args.assignees)], labels: [...new Set(labels)], - comments: [], // TODO fix comments + comments: args.comments ?? [], }; } } \ No newline at end of file diff --git a/src/service/git/github/github-client.ts b/src/service/git/github/github-client.ts index 0da22df..297052d 100644 --- a/src/service/git/github/github-client.ts +++ b/src/service/git/github/github-client.ts @@ -117,6 +117,19 @@ export default class GitHubClient implements GitClient { ); } + if (backport.comments.length > 0) { + backport.comments.forEach(c => { + promises.push( + this.octokit.issues.createComment({ + owner: backport.owner, + repo: backport.repo, + issue_number: (data as PullRequest).number, + body: c, + }).catch(error => this.logger.error(`Error posting comment: ${error}`)) + ); + }); + } + await Promise.all(promises); return data.html_url; diff --git a/src/service/git/gitlab/gitlab-client.ts b/src/service/git/gitlab/gitlab-client.ts index af4c626..a6d21b8 100644 --- a/src/service/git/gitlab/gitlab-client.ts +++ b/src/service/git/gitlab/gitlab-client.ts @@ -96,6 +96,18 @@ export default class GitLabClient implements GitClient { ); } + // comments + if (backport.comments.length > 0) { + this.logger.info("Posting comments: " + backport.comments); + backport.comments.forEach(c => { + promises.push( + this.client.post(`/projects/${projectId}/merge_requests/${mr.iid}/notes`, { + body: c, + }).catch(error => this.logger.warn("Failure trying to post comment. " + error)) + ); + }); + } + // reviewers const reviewerIds = await Promise.all(backport.reviewers.map(async r => { this.logger.debug("Retrieving user: " + r); diff --git a/test/service/args/cli/cli-args-parser.test.ts b/test/service/args/cli/cli-args-parser.test.ts index 4f64187..2951bf3 100644 --- a/test/service/args/cli/cli-args-parser.test.ts +++ b/test/service/args/cli/cli-args-parser.test.ts @@ -402,4 +402,35 @@ describe("cli args parser", () => { expect(args.strategy).toEqual("ort"); expect(args.strategyOption).toEqual("ours"); }); + + test("additional pr comments", () => { + addProcessArgs([ + "--target-branch", + "target", + "--pull-request", + "https://localhost/whatever/pulls/1", + "--comments", + "first comment;second comment", + ]); + + const args: Args = parser.parse(); + expect(args.dryRun).toEqual(false); + expect(args.auth).toEqual(undefined); + expect(args.gitUser).toEqual(undefined); + expect(args.gitEmail).toEqual(undefined); + expect(args.folder).toEqual(undefined); + expect(args.targetBranch).toEqual("target"); + expect(args.pullRequest).toEqual("https://localhost/whatever/pulls/1"); + expect(args.title).toEqual(undefined); + expect(args.body).toEqual(undefined); + expect(args.bodyPrefix).toEqual(undefined); + expect(args.bpBranchName).toEqual(undefined); + expect(args.reviewers).toEqual([]); + expect(args.assignees).toEqual([]); + expect(args.inheritReviewers).toEqual(true); + expect(args.labels).toEqual([]); + expect(args.inheritLabels).toEqual(false); + expect(args.squash).toEqual(true); + expectArrayEqual(args.comments!,["first comment", "second comment"]); + }); }); \ No newline at end of file diff --git a/test/service/args/gha/gha-args-parser.test.ts b/test/service/args/gha/gha-args-parser.test.ts index ebe6e81..76efa13 100644 --- a/test/service/args/gha/gha-args-parser.test.ts +++ b/test/service/args/gha/gha-args-parser.test.ts @@ -236,4 +236,30 @@ describe("gha args parser", () => { expect(args.strategy).toEqual("ort"); expect(args.strategyOption).toEqual("ours"); }); + + test("additional pr comments", () => { + spyGetInput({ + "target-branch": "target", + "pull-request": "https://localhost/whatever/pulls/1", + "comments": "first comment;second comment", + }); + + const args: Args = parser.parse(); + expect(args.dryRun).toEqual(false); + expect(args.auth).toEqual(undefined); + expect(args.gitUser).toEqual(undefined); + expect(args.gitEmail).toEqual(undefined); + expect(args.folder).toEqual(undefined); + expect(args.targetBranch).toEqual("target"); + expect(args.pullRequest).toEqual("https://localhost/whatever/pulls/1"); + expect(args.title).toEqual(undefined); + expect(args.body).toEqual(undefined); + expect(args.reviewers).toEqual([]); + expect(args.assignees).toEqual([]); + expect(args.inheritReviewers).toEqual(true); + expect(args.labels).toEqual([]); + expect(args.inheritLabels).toEqual(false); + expect(args.squash).toEqual(true); + expectArrayEqual(args.comments!,["first comment", "second comment"]); + }); }); \ No newline at end of file diff --git a/test/service/configs/pullrequest/github-pr-configs-parser.test.ts b/test/service/configs/pullrequest/github-pr-configs-parser.test.ts index ce432b9..bca5d75 100644 --- a/test/service/configs/pullrequest/github-pr-configs-parser.test.ts +++ b/test/service/configs/pullrequest/github-pr-configs-parser.test.ts @@ -725,4 +725,79 @@ describe("github pull request config parser", () => { comments: [], }); }); + + test("override backport pr with additional comments", async () => { + const args: Args = { + dryRun: false, + auth: "", + pullRequest: mergedPRUrl, + targetBranch: "prod", + gitUser: "Me", + gitEmail: "me@email.com", + title: "New Title", + body: "New Body", + bodyPrefix: "New Body Prefix -", + reviewers: [], + assignees: ["user3", "user4"], + inheritReviewers: false, + labels: [], + inheritLabels: false, + comments: ["First comment", "Second comment"], + }; + + const configs: Configs = await configParser.parseAndValidate(args); + + expect(GitHubClient.prototype.getPullRequest).toBeCalledTimes(1); + expect(GitHubClient.prototype.getPullRequest).toBeCalledWith("owner", "reponame", 2368, true); + expect(GitHubMapper.prototype.mapPullRequest).toBeCalledTimes(1); + expect(GitHubMapper.prototype.mapPullRequest).toBeCalledWith(expect.anything(), []); + + expect(configs.dryRun).toEqual(false); + expect(configs.git).toEqual({ + user: "Me", + email: "me@email.com" + }); + expect(configs.auth).toEqual(""); + expect(configs.targetBranch).toEqual("prod"); + expect(configs.folder).toEqual(process.cwd() + "/bp"); + expect(configs.originalPullRequest).toEqual({ + number: 2368, + author: "gh-user", + url: "https://api.github.com/repos/owner/reponame/pulls/2368", + htmlUrl: "https://github.com/owner/reponame/pull/2368", + state: "closed", + merged: true, + mergedBy: "that-s-a-user", + title: "PR Title", + body: "Please review and merge", + reviewers: ["requested-gh-user", "gh-user"], + assignees: [], + labels: ["original-label"], + targetRepo: { + owner: "owner", + project: "reponame", + cloneUrl: "https://github.com/owner/reponame.git" + }, + sourceRepo: { + owner: "fork", + project: "reponame", + cloneUrl: "https://github.com/fork/reponame.git" + }, + bpBranchName: undefined, + nCommits: 2, + commits: ["28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc"], + }); + expect(configs.backportPullRequest).toEqual({ + owner: "owner", + repo: "reponame", + head: "bp-prod-28f63db", + base: "prod", + title: "New Title", + body: "New Body Prefix -New Body", + reviewers: [], + assignees: ["user3", "user4"], + labels: [], + comments: ["First comment", "Second comment"], + }); + }); }); \ No newline at end of file diff --git a/test/service/configs/pullrequest/gitlab-pr-configs-parser.test.ts b/test/service/configs/pullrequest/gitlab-pr-configs-parser.test.ts index 8eb269b..691f061 100644 --- a/test/service/configs/pullrequest/gitlab-pr-configs-parser.test.ts +++ b/test/service/configs/pullrequest/gitlab-pr-configs-parser.test.ts @@ -723,4 +723,78 @@ describe("gitlab merge request config parser", () => { comments: [], }); }); + + test("override backport pr with additional comments", async () => { + const args: Args = { + dryRun: false, + auth: "", + pullRequest: mergedPRUrl, + targetBranch: "prod", + gitUser: "Me", + gitEmail: "me@email.com", + title: "New Title", + body: "New Body", + bodyPrefix: "New Body Prefix -", + reviewers: [], + assignees: ["user3", "user4"], + inheritReviewers: false, + labels: [], + inheritLabels: false, + comments: ["First comment", "Second comment"], + }; + + const configs: Configs = await configParser.parseAndValidate(args); + + expect(GitLabClient.prototype.getPullRequest).toBeCalledTimes(1); + expect(GitLabClient.prototype.getPullRequest).toBeCalledWith("superuser", "backporting-example", 1, true); + expect(GitLabMapper.prototype.mapPullRequest).toBeCalledTimes(1); + expect(GitLabMapper.prototype.mapPullRequest).toBeCalledWith(expect.anything(), []); + + expect(configs.dryRun).toEqual(false); + expect(configs.git).toEqual({ + user: "Me", + email: "me@email.com" + }); + expect(configs.auth).toEqual(""); + expect(configs.targetBranch).toEqual("prod"); + expect(configs.folder).toEqual(process.cwd() + "/bp"); + expect(configs.originalPullRequest).toEqual({ + number: 1, + author: "superuser", + url: "https://my.gitlab.host.com/superuser/backporting-example/-/merge_requests/1", + htmlUrl: "https://my.gitlab.host.com/superuser/backporting-example/-/merge_requests/1", + state: "merged", + merged: true, + mergedBy: "superuser", + title: "Update test.txt", + body: "This is the body", + reviewers: ["superuser1", "superuser2"], + assignees: ["superuser"], + labels: ["gitlab-original-label"], + targetRepo: { + owner: "superuser", + project: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git" + }, + sourceRepo: { + owner: "superuser", + project: "backporting-example", + cloneUrl: "https://my.gitlab.host.com/superuser/backporting-example.git" + }, + nCommits: 1, + commits: ["ebb1eca696c42fd067658bd9b5267709f78ef38e"] + }); + expect(configs.backportPullRequest).toEqual({ + owner: "superuser", + repo: "backporting-example", + head: "bp-prod-ebb1eca", + base: "prod", + title: "New Title", + body: "New Body Prefix -New Body", + reviewers: [], + assignees: ["user3", "user4"], + labels: [], + comments: ["First comment", "Second comment"], + }); + }); }); \ No newline at end of file diff --git a/test/service/git/gitlab/gitlab-client.test.ts b/test/service/git/gitlab/gitlab-client.test.ts index c4f4816..57aa125 100644 --- a/test/service/git/gitlab/gitlab-client.test.ts +++ b/test/service/git/gitlab/gitlab-client.test.ts @@ -305,7 +305,7 @@ describe("github service", () => { expect(url).toStrictEqual("https://my.gitlab.host.com/superuser/backporting-example/-/merge_requests/" + SECOND_NEW_GITLAB_MR_ID); // check axios invocation - expect(axiosInstanceSpy.post).toBeCalledTimes(1); + expect(axiosInstanceSpy.post).toBeCalledTimes(3); // also comments expect(axiosInstanceSpy.post).toBeCalledWith("/projects/superuser%2Fbackporting-example/merge_requests", expect.objectContaining({ source_branch: "bp-branch-2", target_branch: "old/branch", @@ -315,10 +315,12 @@ describe("github service", () => { assignee_ids: [], })); expect(axiosInstanceSpy.get).toBeCalledTimes(0); - // FIXME - // expect(axiosInstanceSpy.put).toBeCalledTimes(1); // just comments - // expect(axiosInstanceSpy.put).toBeCalledWith("/projects/superuser%2Fbackporting-example/merge_requests/" + SECOND_NEW_GITLAB_MR_ID, { - // labels: "label1,label2", - // }); + + expect(axiosInstanceSpy.post).toBeCalledWith("/projects/superuser%2Fbackporting-example/merge_requests/" + SECOND_NEW_GITLAB_MR_ID + "/notes", { + body: "this is first comment", + }); + expect(axiosInstanceSpy.post).toBeCalledWith("/projects/superuser%2Fbackporting-example/merge_requests/" + SECOND_NEW_GITLAB_MR_ID + "/notes", { + body: "this is second comment", + }); }); }); \ No newline at end of file diff --git a/test/service/runner/cli-github-runner.test.ts b/test/service/runner/cli-github-runner.test.ts index 3aa82ef..27c78aa 100644 --- a/test/service/runner/cli-github-runner.test.ts +++ b/test/service/runner/cli-github-runner.test.ts @@ -793,4 +793,51 @@ describe("cli runner", () => { } ); }); + + test("additional pr comments", async () => { + addProcessArgs([ + "-tb", + "target", + "-pr", + "https://github.com/owner/reponame/pull/8632", + "--comments", + "first comment; second comment" + ]); + + await runner.execute(); + + const cwd = process.cwd() + "/bp"; + + expect(GitClientFactory.getOrCreate).toBeCalledTimes(1); + expect(GitClientFactory.getOrCreate).toBeCalledWith(GitClientType.GITHUB, undefined, "https://api.github.com"); + + expect(GitCLIService.prototype.clone).toBeCalledTimes(1); + expect(GitCLIService.prototype.clone).toBeCalledWith("https://github.com/owner/reponame.git", cwd, "target"); + + expect(GitCLIService.prototype.createLocalBranch).toBeCalledTimes(1); + expect(GitCLIService.prototype.createLocalBranch).toBeCalledWith(cwd, "bp-target-28f63db"); + + expect(GitCLIService.prototype.fetch).toBeCalledTimes(0); + + expect(GitCLIService.prototype.cherryPick).toBeCalledTimes(1); + expect(GitCLIService.prototype.cherryPick).toBeCalledWith(cwd, "28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc", undefined, undefined); + + expect(GitCLIService.prototype.push).toBeCalledTimes(1); + expect(GitCLIService.prototype.push).toBeCalledWith(cwd, "bp-target-28f63db"); + + expect(GitHubClient.prototype.createPullRequest).toBeCalledTimes(1); + expect(GitHubClient.prototype.createPullRequest).toBeCalledWith({ + owner: "owner", + repo: "reponame", + head: "bp-target-28f63db", + base: "target", + title: "[target] PR Title", + body: expect.stringContaining("**Backport:** https://github.com/owner/reponame/pull/8632"), + reviewers: ["gh-user", "that-s-a-user"], + assignees: [], + labels: [], + comments: ["first comment", "second comment"], + } + ); + }); }); \ No newline at end of file diff --git a/test/service/runner/gha-github-runner.test.ts b/test/service/runner/gha-github-runner.test.ts index 1f412ba..44e11a3 100644 --- a/test/service/runner/gha-github-runner.test.ts +++ b/test/service/runner/gha-github-runner.test.ts @@ -554,4 +554,49 @@ describe("gha runner", () => { } ); }); + + test("additional pr comments", async () => { + spyGetInput({ + "target-branch": "target", + "pull-request": "https://github.com/owner/reponame/pull/2368", + "comments": "first comment; second comment", + }); + + await runner.execute(); + + const cwd = process.cwd() + "/bp"; + + expect(GitClientFactory.getOrCreate).toBeCalledTimes(1); + expect(GitClientFactory.getOrCreate).toBeCalledWith(GitClientType.GITHUB, undefined, "https://api.github.com"); + + expect(GitCLIService.prototype.clone).toBeCalledTimes(1); + expect(GitCLIService.prototype.clone).toBeCalledWith("https://github.com/owner/reponame.git", cwd, "target"); + + expect(GitCLIService.prototype.createLocalBranch).toBeCalledTimes(1); + expect(GitCLIService.prototype.createLocalBranch).toBeCalledWith(cwd, "bp-target-28f63db"); + + expect(GitCLIService.prototype.fetch).toBeCalledTimes(1); + expect(GitCLIService.prototype.fetch).toBeCalledWith(cwd, "pull/2368/head:pr/2368"); + + expect(GitCLIService.prototype.cherryPick).toBeCalledTimes(1); + expect(GitCLIService.prototype.cherryPick).toBeCalledWith(cwd, "28f63db774185f4ec4b57cd9aaeb12dbfb4c9ecc", undefined, undefined); + + expect(GitCLIService.prototype.push).toBeCalledTimes(1); + expect(GitCLIService.prototype.push).toBeCalledWith(cwd, "bp-target-28f63db"); + + expect(GitHubClient.prototype.createPullRequest).toBeCalledTimes(1); + expect(GitHubClient.prototype.createPullRequest).toBeCalledWith({ + owner: "owner", + repo: "reponame", + head: "bp-target-28f63db", + base: "target", + title: "[target] PR Title", + body: expect.stringContaining("**Backport:** https://github.com/owner/reponame/pull/2368"), + reviewers: ["gh-user", "that-s-a-user"], + assignees: [], + labels: [], + comments: ["first comment", "second comment"], + } + ); + }); }); \ No newline at end of file diff --git a/test/support/mock/git-client-mock-support.ts b/test/support/mock/git-client-mock-support.ts index 02597f7..e36db5e 100644 --- a/test/support/mock/git-client-mock-support.ts +++ b/test/support/mock/git-client-mock-support.ts @@ -34,18 +34,24 @@ export const getAxiosMocked = (url: string) => { export const NEW_GITLAB_MR_ID = 999; export const SECOND_NEW_GITLAB_MR_ID = 1000; -export const postAxiosMocked = (_url: string, data?: {source_branch: string,}) => { +export const postAxiosMocked = async (url: string, data?: {source_branch: string,}) => { let responseData = undefined; // gitlab - if (data?.source_branch === "bp-branch") { + if (url.includes("notes")) { + // creating comments + responseData = { + // we do not need the whole response + iid: NEW_GITLAB_MR_ID, + }; + } else if (data?.source_branch === "bp-branch") { responseData = { // we do not need the whole response iid: NEW_GITLAB_MR_ID, web_url: "https://my.gitlab.host.com/superuser/backporting-example/-/merge_requests/" + NEW_GITLAB_MR_ID }; - } if (data?.source_branch === "bp-branch-2") { + } else if (data?.source_branch === "bp-branch-2") { responseData = { // we do not need the whole response iid: SECOND_NEW_GITLAB_MR_ID, @@ -169,6 +175,13 @@ export const mockGitHubClient = (apiUrl = "https://api.github.com"): Moctokit => data: {} }); + mock.rest.issues + .createComment() + .reply({ + status: 201, + data: {} + }); + // invalid requests mock.rest.pulls .get({