bmsdna-devtools 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/PKG-INFO +7 -1
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/README.md +6 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/cli.py +19 -11
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/gh_pr.py +28 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/pr_build.py +54 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/pyproject.toml +1 -1
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/skills/bmsdna-devtools/SKILL.md +3 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/tests/test_gh_pr.py +11 -1
- bmsdna_devtools-0.2.2/tests/test_pr_build.py +97 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/uv.lock +1 -1
- bmsdna_devtools-0.2.1/tests/test_pr_build.py +0 -38
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/.github/workflows/python-publish.yml +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/.github/workflows/python-test.yml +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/.gitignore +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/.python-version +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/__init__.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/ado_auth.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/app_service_logs.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/cli_tools.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/commit.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/env_config.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/gitrepo.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/logs.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/bmsdna/devtools/worktree.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/justfile +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/tests/test_app_service_logs.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/tests/test_commit.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/tests/test_env_config.py +0 -0
- {bmsdna_devtools-0.2.1 → bmsdna_devtools-0.2.2}/tests/test_gitrepo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bmsdna-devtools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Shared Azure DevOps / GitHub / git / Azure Monitor developer tooling for BMS projects
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: requests>=2.32.0
|
|
@@ -77,6 +77,12 @@ title/body from commit info so it never blocks on an interactive prompt).
|
|
|
77
77
|
Extra arguments pass through either way, e.g.
|
|
78
78
|
`bdt pr create --target main -- --title "..."`.
|
|
79
79
|
|
|
80
|
+
If `--target` has a build policy configured (an Azure DevOps Build policy,
|
|
81
|
+
or a GitHub branch protection rule requiring status checks), a successful
|
|
82
|
+
create prints a reminder to run `bdt pr status` afterward to check whether
|
|
83
|
+
the CI build passes. This is a best-effort check — failures reading policy
|
|
84
|
+
config (auth, permissions) fail open and simply skip the reminder.
|
|
85
|
+
|
|
80
86
|
## `bdt worktree`
|
|
81
87
|
|
|
82
88
|
```bash
|
|
@@ -67,6 +67,12 @@ title/body from commit info so it never blocks on an interactive prompt).
|
|
|
67
67
|
Extra arguments pass through either way, e.g.
|
|
68
68
|
`bdt pr create --target main -- --title "..."`.
|
|
69
69
|
|
|
70
|
+
If `--target` has a build policy configured (an Azure DevOps Build policy,
|
|
71
|
+
or a GitHub branch protection rule requiring status checks), a successful
|
|
72
|
+
create prints a reminder to run `bdt pr status` afterward to check whether
|
|
73
|
+
the CI build passes. This is a best-effort check — failures reading policy
|
|
74
|
+
config (auth, permissions) fail open and simply skip the reminder.
|
|
75
|
+
|
|
70
76
|
## `bdt worktree`
|
|
71
77
|
|
|
72
78
|
```bash
|
|
@@ -38,17 +38,25 @@ def pr_create(
|
|
|
38
38
|
"""Create a PR from the current branch into --target (Azure DevOps or GitHub, auto-detected)."""
|
|
39
39
|
remote = current_remote()
|
|
40
40
|
if isinstance(remote, GitHubRemote):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
az
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
gh = require_gh()
|
|
42
|
+
returncode = gh_pr.create(gh, target, args or [])
|
|
43
|
+
build_policy = gh_pr.has_build_policy(gh, target)
|
|
44
|
+
else:
|
|
45
|
+
az = require_az()
|
|
46
|
+
cmd = [
|
|
47
|
+
az, "repos", "pr", "create",
|
|
48
|
+
"--target-branch", target,
|
|
49
|
+
"--source-branch", current_branch(),
|
|
50
|
+
"--auto-complete", "false",
|
|
51
|
+
*(args or []),
|
|
52
|
+
]
|
|
53
|
+
returncode = subprocess.run(cmd).returncode
|
|
54
|
+
build_policy = pr_build.has_build_policy(remote, target)
|
|
55
|
+
|
|
56
|
+
if returncode == 0 and build_policy:
|
|
57
|
+
print("\nRun `bdt pr status` to check whether the CI build passes.")
|
|
58
|
+
|
|
59
|
+
raise typer.Exit(returncode)
|
|
52
60
|
|
|
53
61
|
|
|
54
62
|
@pr_app.command("status")
|
|
@@ -138,3 +138,31 @@ def create(gh: str, target: str, extra_args: list[str]) -> int:
|
|
|
138
138
|
"""
|
|
139
139
|
cmd = [gh, "pr", "create", "--base", target, "--fill", *extra_args]
|
|
140
140
|
return subprocess.run(cmd).returncode
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def protection_requires_status_checks(protection: dict) -> bool:
|
|
144
|
+
"""True if a branch's `.../protection` response requires status checks to pass."""
|
|
145
|
+
return bool(protection.get("required_status_checks"))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def has_build_policy(gh: str, branch: str) -> bool:
|
|
149
|
+
"""Best-effort check for whether `branch` has branch protection requiring status checks.
|
|
150
|
+
|
|
151
|
+
Only used to decide whether to print a `bdt pr status` reminder after
|
|
152
|
+
`pr create` — `{owner}`/`{repo}` are resolved by `gh` from the current
|
|
153
|
+
repo, and any failure (no permission to read protection settings, branch
|
|
154
|
+
not protected at all, etc.) fails open (returns False) rather than
|
|
155
|
+
blocking PR creation.
|
|
156
|
+
"""
|
|
157
|
+
r = subprocess.run(
|
|
158
|
+
[gh, "api", f"repos/{{owner}}/{{repo}}/branches/{branch}/protection"],
|
|
159
|
+
capture_output=True,
|
|
160
|
+
encoding="utf-8",
|
|
161
|
+
)
|
|
162
|
+
if r.returncode != 0:
|
|
163
|
+
return False
|
|
164
|
+
try:
|
|
165
|
+
data = json.loads(r.stdout)
|
|
166
|
+
except json.JSONDecodeError:
|
|
167
|
+
return False
|
|
168
|
+
return protection_requires_status_checks(data)
|
|
@@ -28,6 +28,60 @@ def _base_url(remote: AdoRemote) -> str:
|
|
|
28
28
|
return f"https://dev.azure.com/{remote.org}/{quote(remote.project, safe='')}"
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
# PolicyType.id for the built-in "Build" policy (branch policy requiring a
|
|
32
|
+
# build to pass) — same GUID across every ADO organization.
|
|
33
|
+
BUILD_POLICY_TYPE_ID = "0609b952-1397-4640-95ec-e00a01b2c241"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _scope_matches(scope: dict, repo_id: str, target_ref: str, default_branch: str | None) -> bool:
|
|
37
|
+
if scope.get("repositoryId") not in (None, repo_id):
|
|
38
|
+
return False
|
|
39
|
+
if scope.get("matchKind") == "DefaultBranch":
|
|
40
|
+
return default_branch == target_ref
|
|
41
|
+
return scope.get("refName") in (None, target_ref)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def policy_configs_include_branch(configs: list, repo_id: str, branch: str, default_branch: str | None) -> bool:
|
|
45
|
+
"""True if any enabled, non-deleted Build-type policy configuration's scope covers `branch`."""
|
|
46
|
+
target_ref = f"refs/heads/{branch}"
|
|
47
|
+
for config in configs:
|
|
48
|
+
if not config.get("isEnabled") or config.get("isDeleted"):
|
|
49
|
+
continue
|
|
50
|
+
if config.get("type", {}).get("id") != BUILD_POLICY_TYPE_ID:
|
|
51
|
+
continue
|
|
52
|
+
scopes = config.get("settings", {}).get("scope", [])
|
|
53
|
+
if any(_scope_matches(scope, repo_id, target_ref, default_branch) for scope in scopes):
|
|
54
|
+
return True
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def has_build_policy(remote: AdoRemote, branch: str, pat: str | None = None) -> bool:
|
|
59
|
+
"""Best-effort check for whether `branch` has an enabled Build policy configured.
|
|
60
|
+
|
|
61
|
+
Only used to decide whether to print a `bdt pr status` reminder after
|
|
62
|
+
`pr create` — failures here (auth, permissions, network) fail open
|
|
63
|
+
(return False) rather than blocking PR creation.
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
session = requests.Session()
|
|
67
|
+
session.headers.update(auth_header(pat))
|
|
68
|
+
|
|
69
|
+
r = session.get(
|
|
70
|
+
f"{_base_url(remote)}/_apis/git/repositories/{quote(remote.repo, safe='')}",
|
|
71
|
+
params={"api-version": "7.1"},
|
|
72
|
+
)
|
|
73
|
+
r.raise_for_status()
|
|
74
|
+
repo = r.json()
|
|
75
|
+
|
|
76
|
+
r = session.get(f"{_base_url(remote)}/_apis/policy/configurations", params={"api-version": "7.1"})
|
|
77
|
+
r.raise_for_status()
|
|
78
|
+
configs = r.json().get("value", [])
|
|
79
|
+
except (requests.RequestException, SystemExit):
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
return policy_configs_include_branch(configs, repo.get("id"), branch, repo.get("defaultBranch"))
|
|
83
|
+
|
|
84
|
+
|
|
31
85
|
def merge_conflict_message(pr: dict) -> str | None:
|
|
32
86
|
"""None if the PR's mergeStatus is fine; else a human-readable description of the problem."""
|
|
33
87
|
merge_status = pr.get("mergeStatus")
|
|
@@ -10,7 +10,7 @@ packages = ["bmsdna"]
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "bmsdna-devtools"
|
|
13
|
-
version = "0.2.
|
|
13
|
+
version = "0.2.2"
|
|
14
14
|
description = "Shared Azure DevOps / GitHub / git / Azure Monitor developer tooling for BMS projects"
|
|
15
15
|
readme = "README.md"
|
|
16
16
|
requires-python = ">=3.11"
|
|
@@ -66,6 +66,9 @@ info, so it never blocks waiting on an interactive prompt). Extra args pass
|
|
|
66
66
|
straight through either way, e.g.
|
|
67
67
|
`bdt pr create --target main -- --title "..." --description "..."`.
|
|
68
68
|
|
|
69
|
+
After creating a PR, use `bdt pr status` (see above) to check whether the
|
|
70
|
+
CI build passes.
|
|
71
|
+
|
|
69
72
|
## Creating a worktree
|
|
70
73
|
|
|
71
74
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from bmsdna.devtools.gh_pr import check_bucket, check_label, merge_conflict_message
|
|
3
|
+
from bmsdna.devtools.gh_pr import check_bucket, check_label, merge_conflict_message, protection_requires_status_checks
|
|
4
4
|
|
|
5
5
|
# Real statusCheckRollup entries captured from `gh pr view 13902 -R cli/cli --json statusCheckRollup`.
|
|
6
6
|
COMPLETED_SUCCESS_CHECK_RUN = {
|
|
@@ -60,3 +60,13 @@ def test_merge_conflict_message_conflicting() -> None:
|
|
|
60
60
|
assert msg is not None
|
|
61
61
|
assert "PR #42" in msg
|
|
62
62
|
assert "main" in msg
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_protection_requires_status_checks_true_when_configured() -> None:
|
|
66
|
+
protection = {"required_status_checks": {"strict": True, "contexts": ["build"]}}
|
|
67
|
+
assert protection_requires_status_checks(protection) is True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize("protection", [{}, {"required_status_checks": None}])
|
|
71
|
+
def test_protection_requires_status_checks_false_when_absent(protection: dict) -> None:
|
|
72
|
+
assert protection_requires_status_checks(protection) is False
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from bmsdna.devtools.pr_build import merge_conflict_message, policy_configs_include_branch
|
|
4
|
+
|
|
5
|
+
REPO_ID = "0cd3a822-389e-416e-a4fa-b73f988c2930"
|
|
6
|
+
|
|
7
|
+
# Shape captured from a real `.../_apis/policy/configurations` response.
|
|
8
|
+
BUILD_POLICY_ON_MAIN = {
|
|
9
|
+
"isEnabled": True,
|
|
10
|
+
"isDeleted": False,
|
|
11
|
+
"type": {"id": "0609b952-1397-4640-95ec-e00a01b2c241", "displayName": "Build"},
|
|
12
|
+
"settings": {
|
|
13
|
+
"scope": [{"refName": "refs/heads/main", "matchKind": "Exact", "repositoryId": REPO_ID}],
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
REVIEWER_POLICY_ON_EMERGENCY_RELEASE = {
|
|
17
|
+
"isEnabled": True,
|
|
18
|
+
"isDeleted": False,
|
|
19
|
+
"type": {"id": "fd2167ab-b0be-447a-8ec8-39368250530e", "displayName": "Minimum number of reviewers"},
|
|
20
|
+
"settings": {
|
|
21
|
+
"scope": [{"refName": "refs/heads/emergency-release", "matchKind": "Exact", "repositoryId": REPO_ID}],
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize("merge_status", ["notSet", "queued", "succeeded"])
|
|
27
|
+
def test_merge_conflict_message_none_when_mergeable(merge_status: str) -> None:
|
|
28
|
+
assert merge_conflict_message({"pullRequestId": 1, "title": "x", "mergeStatus": merge_status}) is None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_merge_conflict_message_missing_field_is_fine() -> None:
|
|
32
|
+
assert merge_conflict_message({"pullRequestId": 1, "title": "x"}) is None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_merge_conflict_message_conflicts() -> None:
|
|
36
|
+
pr = {"pullRequestId": 42, "title": "feat: widgets", "mergeStatus": "conflicts"}
|
|
37
|
+
msg = merge_conflict_message(pr)
|
|
38
|
+
assert msg is not None
|
|
39
|
+
assert "PR #42" in msg
|
|
40
|
+
assert "conflicts" in msg
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_merge_conflict_message_uses_failure_message_when_present() -> None:
|
|
44
|
+
pr = {
|
|
45
|
+
"pullRequestId": 7,
|
|
46
|
+
"title": "fix: x",
|
|
47
|
+
"mergeStatus": "failure",
|
|
48
|
+
"mergeFailureMessage": "object too large to merge",
|
|
49
|
+
}
|
|
50
|
+
msg = merge_conflict_message(pr)
|
|
51
|
+
assert msg is not None
|
|
52
|
+
assert "object too large to merge" in msg
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize("merge_status", ["conflicts", "failure", "rejectedByPolicy"])
|
|
56
|
+
def test_merge_conflict_message_covers_all_bad_statuses(merge_status: str) -> None:
|
|
57
|
+
pr = {"pullRequestId": 1, "title": "x", "mergeStatus": merge_status}
|
|
58
|
+
assert merge_conflict_message(pr) is not None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_policy_configs_include_branch_matches_build_policy_on_scoped_branch() -> None:
|
|
62
|
+
configs = [BUILD_POLICY_ON_MAIN, REVIEWER_POLICY_ON_EMERGENCY_RELEASE]
|
|
63
|
+
assert policy_configs_include_branch(configs, REPO_ID, "main", "refs/heads/main") is True
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_policy_configs_include_branch_false_for_unscoped_branch() -> None:
|
|
67
|
+
configs = [BUILD_POLICY_ON_MAIN, REVIEWER_POLICY_ON_EMERGENCY_RELEASE]
|
|
68
|
+
assert policy_configs_include_branch(configs, REPO_ID, "test", "refs/heads/main") is False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_policy_configs_include_branch_ignores_non_build_policy_types() -> None:
|
|
72
|
+
assert policy_configs_include_branch([REVIEWER_POLICY_ON_EMERGENCY_RELEASE], REPO_ID, "emergency-release", "refs/heads/main") is False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_policy_configs_include_branch_ignores_disabled_policy() -> None:
|
|
76
|
+
disabled = {**BUILD_POLICY_ON_MAIN, "isEnabled": False}
|
|
77
|
+
assert policy_configs_include_branch([disabled], REPO_ID, "main", "refs/heads/main") is False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_policy_configs_include_branch_ignores_deleted_policy() -> None:
|
|
81
|
+
deleted = {**BUILD_POLICY_ON_MAIN, "isDeleted": True}
|
|
82
|
+
assert policy_configs_include_branch([deleted], REPO_ID, "main", "refs/heads/main") is False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_policy_configs_include_branch_ignores_other_repo() -> None:
|
|
86
|
+
assert policy_configs_include_branch([BUILD_POLICY_ON_MAIN], "some-other-repo-id", "main", "refs/heads/main") is False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_policy_configs_include_branch_matches_default_branch_scope() -> None:
|
|
90
|
+
default_branch_policy = {
|
|
91
|
+
"isEnabled": True,
|
|
92
|
+
"isDeleted": False,
|
|
93
|
+
"type": {"id": "0609b952-1397-4640-95ec-e00a01b2c241"},
|
|
94
|
+
"settings": {"scope": [{"matchKind": "DefaultBranch", "repositoryId": REPO_ID}]},
|
|
95
|
+
}
|
|
96
|
+
assert policy_configs_include_branch([default_branch_policy], REPO_ID, "main", "refs/heads/main") is True
|
|
97
|
+
assert policy_configs_include_branch([default_branch_policy], REPO_ID, "test", "refs/heads/main") is False
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from bmsdna.devtools.pr_build import merge_conflict_message
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@pytest.mark.parametrize("merge_status", ["notSet", "queued", "succeeded"])
|
|
7
|
-
def test_merge_conflict_message_none_when_mergeable(merge_status: str) -> None:
|
|
8
|
-
assert merge_conflict_message({"pullRequestId": 1, "title": "x", "mergeStatus": merge_status}) is None
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def test_merge_conflict_message_missing_field_is_fine() -> None:
|
|
12
|
-
assert merge_conflict_message({"pullRequestId": 1, "title": "x"}) is None
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_merge_conflict_message_conflicts() -> None:
|
|
16
|
-
pr = {"pullRequestId": 42, "title": "feat: widgets", "mergeStatus": "conflicts"}
|
|
17
|
-
msg = merge_conflict_message(pr)
|
|
18
|
-
assert msg is not None
|
|
19
|
-
assert "PR #42" in msg
|
|
20
|
-
assert "conflicts" in msg
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_merge_conflict_message_uses_failure_message_when_present() -> None:
|
|
24
|
-
pr = {
|
|
25
|
-
"pullRequestId": 7,
|
|
26
|
-
"title": "fix: x",
|
|
27
|
-
"mergeStatus": "failure",
|
|
28
|
-
"mergeFailureMessage": "object too large to merge",
|
|
29
|
-
}
|
|
30
|
-
msg = merge_conflict_message(pr)
|
|
31
|
-
assert msg is not None
|
|
32
|
-
assert "object too large to merge" in msg
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@pytest.mark.parametrize("merge_status", ["conflicts", "failure", "rejectedByPolicy"])
|
|
36
|
-
def test_merge_conflict_message_covers_all_bad_statuses(merge_status: str) -> None:
|
|
37
|
-
pr = {"pullRequestId": 1, "title": "x", "mergeStatus": merge_status}
|
|
38
|
-
assert merge_conflict_message(pr) is not None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|