Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,12 @@ github-rest-cli create-repo --name my-new-repo --empty

### Delete a repository

Prompts for confirmation unless `--yes` / `-y` is passed:

```shell
github-rest-cli delete-repo --name my-repo
github-rest-cli delete-repo --name my-repo --org my-org
github-rest-cli delete-repo --name my-repo --yes
```

### Dependabot security updates
Expand Down
22 changes: 22 additions & 0 deletions src/github_rest_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ def build_parser() -> argparse.ArgumentParser:
dest="org",
help="The organization name",
)
delete_repo_parser.add_argument(
"-y",
"--yes",
action="store_true",
dest="yes",
help="Skip the confirmation prompt and delete immediately",
)
delete_repo_parser.set_defaults(func=delete_repository)

# Subparser for "dependabot" function
Expand Down Expand Up @@ -218,6 +225,18 @@ def build_parser() -> argparse.ArgumentParser:
return parser


def confirm_delete_repository(
name: str, org: str | None = None, *, yes: bool = False
) -> bool:
"""Return True if deletion should proceed."""
if yes:
return True

target = f"{org}/{name}" if org else name
answer = input(f"Delete repository '{target}'? This cannot be undone. [y/N] ")
return answer.strip().lower() in {"y", "yes"}


def cli():
parser = build_parser()
args = parser.parse_args()
Expand All @@ -235,6 +254,9 @@ def cli():
elif command == "create-repo":
args.func(args.name, args.visibility, args.org, args.empty)
elif command == "delete-repo":
if not confirm_delete_repository(args.name, args.org, yes=args.yes):
print("Aborted.") # noqa: T201
return
args.func(args.name, args.org)
elif command == "dependabot":
args.func(args.name, args.control, args.org)
Expand Down
84 changes: 84 additions & 0 deletions tests/test_delete_repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from github_rest_cli.main import build_parser, confirm_delete_repository, cli
from github_rest_cli import api


GET_HEADERS_FUNCTION = "github_rest_cli.api.get_headers"
FETCH_USER_FUNCTION = "github_rest_cli.api.fetch_user"
REQUEST_HANDLER_FUNCTION = "github_rest_cli.api.request_with_handling"


def test_delete_repo_yes_flag_parses():
parser = build_parser()
args = parser.parse_args(["delete-repo", "--name", "my-repo", "--yes"])

assert args.command == "delete-repo"
assert args.name == "my-repo"
assert args.yes is True


def test_delete_repo_short_yes_flag_parses():
parser = build_parser()
args = parser.parse_args(["delete-repo", "-n", "my-repo", "-y"])

assert args.yes is True


def test_confirm_delete_skips_prompt_with_yes(mocker):
prompt = mocker.patch("github_rest_cli.main.input")

assert confirm_delete_repository("my-repo", yes=True) is True
prompt.assert_not_called()


def test_confirm_delete_accepts_yes(mocker):
mocker.patch("github_rest_cli.main.input", return_value="y")

assert confirm_delete_repository("my-repo", org="my-org") is True


def test_confirm_delete_rejects_other_answers(mocker):
mocker.patch("github_rest_cli.main.input", return_value="n")

assert confirm_delete_repository("my-repo") is False


def test_cli_delete_repo_aborts_without_confirmation(mocker, capsys):
mocker.patch(
"github_rest_cli.main.confirm_delete_repository",
return_value=False,
)
delete_mock = mocker.patch("github_rest_cli.main.delete_repository")
mocker.patch(
"sys.argv",
["github-rest-cli", "delete-repo", "--name", "my-repo"],
)

cli()

delete_mock.assert_not_called()
assert "Aborted." in capsys.readouterr().out


def test_cli_delete_repo_proceeds_with_yes(mocker):
delete_mock = mocker.patch("github_rest_cli.main.delete_repository")
prompt = mocker.patch("github_rest_cli.main.input")
mocker.patch(
"sys.argv",
["github-rest-cli", "delete-repo", "--name", "my-repo", "--yes"],
)

cli()

prompt.assert_not_called()
delete_mock.assert_called_once_with("my-repo", None)


def test_delete_repository_api(mocker):
mocker.patch(GET_HEADERS_FUNCTION, return_value={"Authorization": "token fake"})
mocker.patch(FETCH_USER_FUNCTION, return_value="test-user")
request_mock = mocker.patch(REQUEST_HANDLER_FUNCTION, return_value=None)

api.delete_repository("my-repo")

request_mock.assert_called_once()
assert request_mock.call_args.args[0] == "DELETE"