|
16 | 16 | from conda_forge_tick.git_utils import (
|
17 | 17 | Bound,
|
18 | 18 | DryRunBackend,
|
| 19 | + DuplicatePullRequestError, |
19 | 20 | GitCli,
|
20 | 21 | GitCliError,
|
21 | 22 | GitConnectionMode,
|
@@ -1044,6 +1045,41 @@ def test_git_cli_clone_fork_and_branch_non_existing_remote_existing_target_dir(c
|
1044 | 1045 | assert "trying to reset hard" in caplog.text
|
1045 | 1046 |
|
1046 | 1047 |
|
| 1048 | +def _github_api_json_fixture(name: str) -> dict: |
| 1049 | + with Path(__file__).parent.joinpath(f"github_api/{name}.json").open() as f: |
| 1050 | + return json.load(f) |
| 1051 | + |
| 1052 | + |
| 1053 | +@pytest.fixture() |
| 1054 | +def github_response_create_issue_comment() -> dict: |
| 1055 | + return _github_api_json_fixture("create_issue_comment_pytest") |
| 1056 | + |
| 1057 | + |
| 1058 | +@pytest.fixture() |
| 1059 | +def github_response_create_pull_duplicate() -> dict: |
| 1060 | + return _github_api_json_fixture("create_pull_duplicate") |
| 1061 | + |
| 1062 | + |
| 1063 | +@pytest.fixture() |
| 1064 | +def github_response_create_pull_validation_error() -> dict: |
| 1065 | + return _github_api_json_fixture("create_pull_validation_error") |
| 1066 | + |
| 1067 | + |
| 1068 | +@pytest.fixture() |
| 1069 | +def github_response_get_pull() -> dict: |
| 1070 | + return _github_api_json_fixture("get_pull_pytest") |
| 1071 | + |
| 1072 | + |
| 1073 | +@pytest.fixture() |
| 1074 | +def github_response_get_repo() -> dict: |
| 1075 | + return _github_api_json_fixture("get_repo_pytest") |
| 1076 | + |
| 1077 | + |
| 1078 | +@pytest.fixture() |
| 1079 | +def github_response_headers() -> dict: |
| 1080 | + return _github_api_json_fixture("github_response_headers") |
| 1081 | + |
| 1082 | + |
1047 | 1083 | def test_github_backend_from_token():
|
1048 | 1084 | token = "TOKEN"
|
1049 | 1085 |
|
@@ -1305,28 +1341,23 @@ def test_github_backend_get_api_requests_left_zero_valid_reset_time(caplog):
|
1305 | 1341 |
|
1306 | 1342 |
|
1307 | 1343 | @mock.patch("requests.Session.request")
|
1308 |
| -def test_github_backend_create_pull_request_mock(request_mock: MagicMock): |
1309 |
| - with open(Path(__file__).parent / "github_api" / "get_repo_pytest.json") as f: |
1310 |
| - get_repo_response = json.load(f) |
1311 |
| - |
1312 |
| - with open( |
1313 |
| - Path(__file__).parent / "github_api" / "github_response_headers.json" |
1314 |
| - ) as f: |
1315 |
| - response_headers = json.load(f) |
1316 |
| - |
1317 |
| - with open(Path(__file__).parent / "github_api" / "get_pull_pytest.json") as f: |
1318 |
| - create_pull_response = json.load(f) |
1319 |
| - |
| 1344 | +def test_github_backend_create_pull_request_mock( |
| 1345 | + request_mock: MagicMock, |
| 1346 | + github_response_get_repo: dict, |
| 1347 | + github_response_headers: dict, |
| 1348 | + github_response_get_pull: dict, |
| 1349 | +): |
1320 | 1350 | def request_side_effect(method, _url, **_kwargs):
|
1321 | 1351 | response = requests.Response()
|
1322 | 1352 | if method == "GET":
|
1323 | 1353 | response.status_code = 200
|
1324 |
| - response.json = lambda: get_repo_response |
| 1354 | + response.json = lambda: github_response_get_repo |
1325 | 1355 | return response
|
1326 | 1356 | if method == "POST":
|
1327 | 1357 | response.status_code = 201
|
1328 |
| - response.json = lambda: create_pull_response |
1329 |
| - response.headers = CaseInsensitiveDict(response_headers) |
| 1358 | + # note that the "create pull" response body is identical to the "get pull" response body |
| 1359 | + response.json = lambda: github_response_get_pull |
| 1360 | + response.headers = CaseInsensitiveDict(github_response_headers) |
1330 | 1361 | return response
|
1331 | 1362 | assert False, f"Unexpected method: {method}"
|
1332 | 1363 |
|
@@ -1380,42 +1411,117 @@ def request_side_effect(method, _url, **_kwargs):
|
1380 | 1411 |
|
1381 | 1412 |
|
1382 | 1413 | @mock.patch("requests.Session.request")
|
1383 |
| -def test_github_backend_comment_on_pull_request_success(request_mock: MagicMock): |
1384 |
| - with open(Path(__file__).parent / "github_api" / "get_repo_pytest.json") as f: |
1385 |
| - get_repo_response = json.load(f) |
| 1414 | +def test_github_backend_create_pull_request_duplicate( |
| 1415 | + request_mock: MagicMock, |
| 1416 | + github_response_get_repo: dict, |
| 1417 | + github_response_create_pull_duplicate: dict, |
| 1418 | +): |
| 1419 | + def request_side_effect(method, _url, **_kwargs): |
| 1420 | + response = requests.Response() |
| 1421 | + if method == "GET": |
| 1422 | + response.status_code = 200 |
| 1423 | + response.json = lambda: github_response_get_repo |
| 1424 | + return response |
| 1425 | + if method == "POST": |
| 1426 | + response.status_code = 422 |
| 1427 | + # note that the "create pull" response body is identical to the "get pull" response body |
| 1428 | + response.json = lambda: github_response_create_pull_duplicate |
| 1429 | + return response |
| 1430 | + assert False, f"Unexpected method: {method}" |
| 1431 | + |
| 1432 | + request_mock.side_effect = request_side_effect |
| 1433 | + |
| 1434 | + pygithub_mock = MagicMock() |
| 1435 | + pygithub_mock.get_user.return_value.login = "CURRENT_USER" |
| 1436 | + |
| 1437 | + backend = GitHubBackend(github3.login(token="TOKEN"), pygithub_mock, "") |
| 1438 | + |
| 1439 | + with pytest.raises( |
| 1440 | + DuplicatePullRequestError, |
| 1441 | + match="Pull request from CURRENT_USER:HEAD_BRANCH to conda-forge:BASE_BRANCH already exists", |
| 1442 | + ): |
| 1443 | + backend.create_pull_request( |
| 1444 | + "conda-forge", |
| 1445 | + "pytest-feedstock", |
| 1446 | + "BASE_BRANCH", |
| 1447 | + "HEAD_BRANCH", |
| 1448 | + "TITLE", |
| 1449 | + "BODY", |
| 1450 | + ) |
| 1451 | + |
| 1452 | + |
| 1453 | +@mock.patch("requests.Session.request") |
| 1454 | +def test_github_backend_create_pull_request_validation_error( |
| 1455 | + request_mock: MagicMock, |
| 1456 | + github_response_get_repo: dict, |
| 1457 | + github_response_create_pull_validation_error: dict, |
| 1458 | +): |
| 1459 | + """ |
| 1460 | + Test that other GitHub API 422 validation errors are not caught as DuplicatePullRequestError. |
| 1461 | + """ |
1386 | 1462 |
|
1387 |
| - with open(Path(__file__).parent / "github_api" / "get_pull_pytest.json") as f: |
1388 |
| - get_pull_response = json.load(f) |
| 1463 | + def request_side_effect(method, _url, **_kwargs): |
| 1464 | + response = requests.Response() |
| 1465 | + if method == "GET": |
| 1466 | + response.status_code = 200 |
| 1467 | + response.json = lambda: github_response_get_repo |
| 1468 | + return response |
| 1469 | + if method == "POST": |
| 1470 | + response.status_code = 422 |
| 1471 | + # note that the "create pull" response body is identical to the "get pull" response body |
| 1472 | + response.json = lambda: github_response_create_pull_validation_error |
| 1473 | + return response |
| 1474 | + assert False, f"Unexpected method: {method}" |
| 1475 | + |
| 1476 | + request_mock.side_effect = request_side_effect |
| 1477 | + |
| 1478 | + pygithub_mock = MagicMock() |
| 1479 | + pygithub_mock.get_user.return_value.login = "CURRENT_USER" |
| 1480 | + |
| 1481 | + backend = GitHubBackend(github3.login(token="TOKEN"), pygithub_mock, "") |
| 1482 | + |
| 1483 | + with pytest.raises(github3.exceptions.UnprocessableEntity): |
| 1484 | + backend.create_pull_request( |
| 1485 | + "conda-forge", |
| 1486 | + "pytest-feedstock", |
| 1487 | + "BASE_BRANCH", |
| 1488 | + "HEAD_BRANCH", |
| 1489 | + "TITLE", |
| 1490 | + "BODY", |
| 1491 | + ) |
1389 | 1492 |
|
1390 |
| - with open( |
1391 |
| - Path(__file__).parent / "github_api" / "create_issue_comment_pytest.json" |
1392 |
| - ) as f: |
1393 |
| - create_comment_response = json.load(f) |
1394 | 1493 |
|
| 1494 | +@mock.patch("requests.Session.request") |
| 1495 | +def test_github_backend_comment_on_pull_request_success( |
| 1496 | + request_mock: MagicMock, |
| 1497 | + github_response_get_repo: dict, |
| 1498 | + github_response_get_pull: dict, |
| 1499 | + github_response_create_issue_comment: dict, |
| 1500 | +): |
1395 | 1501 | def request_side_effect(method, url, **_kwargs):
|
1396 | 1502 | response = requests.Response()
|
1397 | 1503 | if (
|
1398 | 1504 | method == "GET"
|
1399 | 1505 | and url == "https://api.github.com/repos/conda-forge/pytest-feedstock"
|
1400 | 1506 | ):
|
1401 | 1507 | response.status_code = 200
|
1402 |
| - response.json = lambda: get_repo_response |
| 1508 | + response.json = lambda: github_response_get_repo |
1403 | 1509 | return response
|
1404 | 1510 | if (
|
1405 | 1511 | method == "GET"
|
1406 | 1512 | and url
|
1407 | 1513 | == "https://api.github.com/repos/conda-forge/pytest-feedstock/pulls/1337"
|
1408 | 1514 | ):
|
1409 | 1515 | response.status_code = 200
|
1410 |
| - response.json = lambda: get_pull_response |
| 1516 | + response.json = lambda: github_response_get_pull |
1411 | 1517 | return response
|
1412 | 1518 | if (
|
1413 | 1519 | method == "POST"
|
1414 | 1520 | and url
|
1415 | 1521 | == "https://api.github.com/repos/conda-forge/pytest-feedstock/issues/1337/comments"
|
1416 | 1522 | ):
|
1417 | 1523 | response.status_code = 201
|
1418 |
| - response.json = lambda: create_comment_response |
| 1524 | + response.json = lambda: github_response_create_issue_comment |
1419 | 1525 | return response
|
1420 | 1526 | assert False, f"Unexpected endpoint: {method} {url}"
|
1421 | 1527 |
|
@@ -1467,18 +1573,16 @@ def request_side_effect(method, url, **_kwargs):
|
1467 | 1573 | @mock.patch("requests.Session.request")
|
1468 | 1574 | def test_github_backend_comment_on_pull_request_pull_request_not_found(
|
1469 | 1575 | request_mock: MagicMock,
|
| 1576 | + github_response_get_repo: dict, |
1470 | 1577 | ):
|
1471 |
| - with open(Path(__file__).parent / "github_api" / "get_repo_pytest.json") as f: |
1472 |
| - get_repo_response = json.load(f) |
1473 |
| - |
1474 | 1578 | def request_side_effect(method, url, **_kwargs):
|
1475 | 1579 | response = requests.Response()
|
1476 | 1580 | if (
|
1477 | 1581 | method == "GET"
|
1478 | 1582 | and url == "https://api.github.com/repos/conda-forge/pytest-feedstock"
|
1479 | 1583 | ):
|
1480 | 1584 | response.status_code = 200
|
1481 |
| - response.json = lambda: get_repo_response |
| 1585 | + response.json = lambda: github_response_get_repo |
1482 | 1586 | return response
|
1483 | 1587 | if (
|
1484 | 1588 | method == "GET"
|
@@ -1507,30 +1611,25 @@ def request_side_effect(method, url, **_kwargs):
|
1507 | 1611 | @mock.patch("requests.Session.request")
|
1508 | 1612 | def test_github_backend_comment_on_pull_request_unexpected_response(
|
1509 | 1613 | request_mock: MagicMock,
|
| 1614 | + github_response_get_repo: dict, |
| 1615 | + github_response_get_pull: dict, |
1510 | 1616 | ):
|
1511 |
| - with open(Path(__file__).parent / "github_api" / "get_repo_pytest.json") as f: |
1512 |
| - get_repo_response = json.load(f) |
1513 |
| - |
1514 |
| - with open(Path(__file__).parent / "github_api" / "get_pull_pytest.json") as f: |
1515 |
| - get_pull_response = json.load(f) |
1516 |
| - |
1517 | 1617 | def request_side_effect(method, url, **_kwargs):
|
1518 |
| - # noinspection DuplicatedCode |
1519 | 1618 | response = requests.Response()
|
1520 | 1619 | if (
|
1521 | 1620 | method == "GET"
|
1522 | 1621 | and url == "https://api.github.com/repos/conda-forge/pytest-feedstock"
|
1523 | 1622 | ):
|
1524 | 1623 | response.status_code = 200
|
1525 |
| - response.json = lambda: get_repo_response |
| 1624 | + response.json = lambda: github_response_get_repo |
1526 | 1625 | return response
|
1527 | 1626 | if (
|
1528 | 1627 | method == "GET"
|
1529 | 1628 | and url
|
1530 | 1629 | == "https://api.github.com/repos/conda-forge/pytest-feedstock/pulls/1337"
|
1531 | 1630 | ):
|
1532 | 1631 | response.status_code = 200
|
1533 |
| - response.json = lambda: get_pull_response |
| 1632 | + response.json = lambda: github_response_get_pull |
1534 | 1633 | return response
|
1535 | 1634 | if (
|
1536 | 1635 | method == "POST"
|
|
0 commit comments