Skip to content

Commit

Permalink
feat: update the existing pull request
Browse files Browse the repository at this point in the history
When a pull request creation is triggered, we need to check if their is not already a pull request open. When a pull request is open we should update the description.

Issue: #3
  • Loading branch information
Joris Conijn committed Feb 7, 2022
1 parent a03d02c commit 19c43ea
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 12 deletions.
59 changes: 59 additions & 0 deletions pull_request_codecommit/aws/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,46 @@ def __execute(self, parameters: List[str]) -> str:

return response.stdout.decode("utf-8").strip("\n")

def get_open_pull_request(
self, repository: str, source: str, destination: str
) -> dict:
response = self.__execute(
[
"codecommit",
"list-pull-requests",
"--repository-name",
repository,
"--pull-request-status",
"OPEN",
]
)
data = json.loads(response)
open_pull_requests = data.get("pullRequestIds")

for pull_request_id in open_pull_requests:
pr = self.__get_pull_request(pull_request_id)
target = pr.get("pullRequestTargets", [{}])[0]

if (
target.get("sourceReference") == f"refs/heads/{source}"
and target.get("destinationReference") == f"refs/heads/{destination}"
):
return pr

return {}

def __get_pull_request(self, pull_request_id: int) -> dict:
response = self.__execute(
[
"codecommit",
"get-pull-request",
"--pull-request-id",
str(pull_request_id),
]
)
data = json.loads(response)
return data.get("pullRequest", {})

def create_pull_request(
self,
title: str,
Expand All @@ -60,6 +100,25 @@ def create_pull_request(

return data.get("pullRequest")

def update_pull_request(
self,
pull_request_id: int,
description: str,
) -> dict:
response = self.__execute(
[
"codecommit",
"update-pull-request-description",
"--pull-request-id",
str(pull_request_id),
"--description",
description,
]
)
data = json.loads(response)

return data.get("pullRequest")

def merge_pull_request(self, repository: str, pull_request_id: int) -> dict:
response = self.__execute(
[
Expand Down
35 changes: 31 additions & 4 deletions pull_request_codecommit/pull_request_codecommit.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ class PullRequestCodeCommit:
def __init__(self, repository: Repository) -> None:
self.__aws_client: Optional[AwsClient] = None
self.__repo = repository
self.__data: dict = {}
self.__raw_data: dict = {}

@property
def __data(self) -> dict:
if not self.__raw_data:
self.__raw_data = self.__client.get_open_pull_request(
repository=self.__repo.remote.name,
source=self.__repo.branch,
destination=self.__repo.destination,
)

return self.__raw_data

@property
def __client(self) -> AwsClient:
Expand All @@ -27,6 +38,14 @@ def __client(self) -> AwsClient:
def pull_request_id(self) -> int:
return self.__data.get("pullRequestId", 0)

@property
def author(self) -> str:
return self.__data.get("authorArn", "").split("/")[-1]

@property
def title(self) -> str:
return self.__data.get("title", "")

@property
def description(self) -> str:
return self.__data.get("description", "").replace("\n\n", "\n")
Expand All @@ -36,7 +55,9 @@ def link(self) -> str:
return self.__repo.remote.pull_request_url(self.pull_request_id)

def save(self, title: str, description: str):
self.__data = self.__create(
self.__update(
description=self.__markdown_conversion(description)
) if self.pull_request_id != 0 else self.__create(
title=title, description=self.__markdown_conversion(description)
)

Expand All @@ -56,11 +77,17 @@ def merge(self) -> str:
def __markdown_conversion(description: str) -> str:
return description.replace("\n", "\n\n")

def __create(self, title: str, description: str) -> dict:
return self.__client.create_pull_request(
def __create(self, title: str, description: str) -> None:
self.__raw_data = self.__client.create_pull_request(
title=title,
description=description,
repository=self.__repo.remote.name,
source=self.__repo.branch,
destination=self.__repo.destination,
)

def __update(self, description: str) -> None:
self.__client.update_pull_request(
pull_request_id=self.pull_request_id,
description=description,
)
29 changes: 28 additions & 1 deletion pull_request_codecommit/pull_request_proposal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
from .git import Commits
from .pull_request_codecommit import PullRequestCodeCommit

SPLITTER = """
#=
#= Existing pull request found: #{{PULL_REQUEST_ID}}
#=
#= Author: {{AUTHOR}}
#=
#= Above you will see the title and description of the existing pull request.
#= Below you will find the newly proposed description.
#=
"""


class PullRequestProposal:
def __init__(self, pull_request: PullRequestCodeCommit, commits: Commits):
Expand All @@ -14,6 +25,9 @@ def title(self) -> str:
if self.__title:
return self.__title

if self.__pull_request.title:
return self.__pull_request.title

return self.__generate_title()

def __generate_title(self) -> str:
Expand All @@ -39,7 +53,10 @@ def description(self) -> str:
):
return self.append_issues(self.__commits.first.message.body)

return self.__generate_description()
return self.__merge_descriptions(
existing_description=self.__pull_request.description,
description=self.__generate_description(),
)

def append_issues(self, description: str) -> str:
if not self.__commits.issues:
Expand All @@ -55,6 +72,16 @@ def __generate_description(self):
"\n".join(list(map(lambda commit: commit.message.subject, self.__commits)))
)

def __merge_descriptions(self, existing_description: str, description: str) -> str:
if not existing_description:
return description

splitter = SPLITTER.replace(
"{{PULL_REQUEST_ID}}", str(self.__pull_request.pull_request_id)
)
splitter = splitter.replace("{{AUTHOR}}", self.__pull_request.author)
return existing_description + splitter + description

def update(self, title: str, description: str):
self.__title = title
self.__description = self.__remove_comments(description)
Expand Down
14 changes: 14 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,18 @@
b"[default]\nbranch: my-main\n[profile my-profile]\nbranch: my-master",
COMMITS,
),
(
"codecommit://my-repository-open-pr",
None,
None,
b"[default]\nbranch: my-main\n[profile my-profile]\nbranch: my-master",
COMMITS,
),
(
"codecommit://my-repository-other-open-pr",
None,
None,
b"[default]\nbranch: my-main\n[profile my-profile]\nbranch: my-master",
COMMITS,
),
]
62 changes: 57 additions & 5 deletions tests/test_command.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from io import TextIOWrapper, BytesIO
from typing import List, Tuple, Any, Optional
from typing import List, Tuple, Optional, Dict, Any
from unittest.mock import patch, MagicMock

import pytest
Expand All @@ -15,17 +15,63 @@ def edit_message(message: str) -> str:
return message


def aws_client_execute_side_effect(parameters, stdout):
def aws_client_execute_side_effect(parameters, stdout) -> MagicMock:
data: Dict[str, Any] = {}
assert -1 == stdout
mock_stdout = MagicMock()

if "create-pull-request" in parameters:
data = {"pullRequest": {"pullRequestId": 1}}
data = {"pullRequest": {"pullRequestId": "1"}}

elif "merge-pull-request-by-fast-forward" in parameters:
status = "OPEN" if parameters[-1] == "my-repository-pr-failure" else "CLOSED"
status = "OPEN" if "my-repository-pr-failure" in parameters else "CLOSED"
data = {"pullRequest": {"pullRequestStatus": status}}

elif "list-pull-requests" in parameters:
repository_name = next(
filter(lambda item: item.startswith("my-repository"), parameters)
)
pull_request_ids: List[int] = {
"my-repository-open-pr": [1],
"my-repository-other-open-pr": [2],
}.get(repository_name, [])

data = {"pullRequestIds": list(map(str, pull_request_ids))}

elif "get-pull-request" in parameters:
pull_request_id = int(parameters[parameters.index("--pull-request-id") + 1])

source = "feat/my-feature" if pull_request_id == 1 else "feat/my-other-feature"
destination = "my-main" if pull_request_id == 1 else "my-development"

data = {
"pullRequest": {
"pullRequestId": str(pull_request_id),
"authorArn": "arn:aws:sts::111122223333:assumed-role/Role/john.doe@acme.com",
"title": "feat: update existing pull request support",
"description": "Update the existing pull request when a pull request already exists",
"pullRequestTargets": [
{
"sourceCommit": "ca45e279EXAMPLE",
"sourceReference": f"refs/heads/{source}",
"mergeBase": "a99f5ddbEXAMPLE",
"destinationReference": f"refs/heads/{destination}",
"mergeMetadata": {"isMerged": "false"},
"destinationCommit": "2abfc6beEXAMPLE",
"repositoryName": "my-repository-open-pr",
}
],
}
}
elif "update-pull-request-description" in parameters:
pull_request_id = int(parameters[parameters.index("--pull-request-id") + 1])
data = {
"pullRequest": {
"pullRequestId": str(pull_request_id),
"authorArn": "arn:aws:sts::111122223333:assumed-role/Role/john.doe@acme.com",
}
}

mock_stdout.stdout = bytes(json.dumps(data), "utf-8")
return mock_stdout

Expand Down Expand Up @@ -67,7 +113,8 @@ def test_invoke(
mock_git_client.return_value.get_commit_messages.return_value = Commits(commits)

mock_git_client.return_value.remote.return_value = remote
mock_git_client.return_value.current_branch.return_value = "feat/my-feature"
mock_git_client.return_value.current_branch = "feat/my-feature"

configparser.open = MagicMock(return_value=TextIOWrapper(BytesIO(config))) # type: ignore
mock_aws_client.side_effect = aws_client_execute_side_effect

Expand Down Expand Up @@ -159,11 +206,13 @@ def test_invoke_github(mock_git_client: MagicMock) -> None:
assert result.exit_code == 1


@patch("pull_request_codecommit.aws.client.subprocess.run")
@patch("pull_request_codecommit.repository.GitClient")
@patch("pull_request_codecommit.click.edit")
def test_invoke_quit_edit(
mock_edit: MagicMock,
mock_git_client: MagicMock,
mock_aws_client: MagicMock,
) -> None:
mock_edit.return_value = None
mock_git_client.return_value.remote.return_value = (
Expand All @@ -172,9 +221,12 @@ def test_invoke_quit_edit(
mock_git_client.return_value.current_branch.return_value = "feat/my-feature"
config = b"[default]\nbranch: my-main\n[profile my-profile]\nbranch: my-master"
configparser.open = MagicMock(return_value=TextIOWrapper(BytesIO(config))) # type: ignore
mock_aws_client.side_effect = aws_client_execute_side_effect

runner = CliRunner()
result = runner.invoke(main)
assert type(result.exception) == SystemExit
assert "Pull request was not created" in result.output
assert result.exit_code == 1


Expand Down
35 changes: 33 additions & 2 deletions tests/test_pull_request.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unittest.mock import MagicMock
from unittest.mock import MagicMock, patch

import pytest

Expand Down Expand Up @@ -65,12 +65,43 @@
),
],
)
@patch("pull_request_codecommit.pull_request_codecommit.AwsClient")
def test_git_client(
commits: Commits, expected_title: str, expected_description: str
mock_aws_client: MagicMock,
commits: Commits,
expected_title: str,
expected_description: str,
) -> None:
mock_aws_client.return_value.get_open_pull_request.return_value = {}

mock_repo = MagicMock()
mock_repo.commits.return_value = commits

pull_request = PullRequest(mock_repo)
assert pull_request.title == expected_title
assert pull_request.description == expected_description


@patch("pull_request_codecommit.pull_request_codecommit.AwsClient")
def test_update_pull_request(mock_aws_client: MagicMock) -> None:
mock_aws_client.return_value.get_open_pull_request.return_value = {
"pullRequestId": "1",
"title": "feat: test update pull request",
"authorArn": "arn:aws:sts::111122223333:assumed-role/Role/john.doe@acme.com",
"description": "my committed description",
}

mock_repo = MagicMock()
mock_repo.commits.return_value = Commits(COMMIT_DETAILED_MESSAGE_ISSUE)
mock_repo.remote.name = "my-repository"
mock_repo.remote.region = "eu-west-1"
pull_request = PullRequest(mock_repo)

assert pull_request.title == "feat: test update pull request"
assert "my committed description" in pull_request.description
assert "Existing pull request found: #1" in pull_request.description
assert "Author: john.doe@acme.com" in pull_request.description
assert "Issues: #1" in pull_request.description

pull_request.proposal.update(pull_request.title, pull_request.description)
pull_request.save()

0 comments on commit 19c43ea

Please sign in to comment.