1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
|
"""Test the library maintainer release script."""
from __future__ import annotations
import dataclasses
import sys
from collections.abc import Iterator
from typing import Any
from unittest import mock
import pytest
from syrupy.assertion import SnapshotAssertion
from scripts import release
@dataclasses.dataclass
class Mocks:
"""Collection of all mocked objects used in the release script."""
input: mock.Mock
listdir: mock.Mock
requests: mock.Mock
subprocess: mock.Mock
@property
def mock_calls(self) -> dict[str, Any]:
"""A dict of _all_ calls to this class's mock objects."""
return {
k.name: getattr(self, k.name).mock_calls for k in dataclasses.fields(self)
}
@pytest.fixture
def mocks() -> Iterator[Mocks]:
"""Stub network and subprocesses."""
with mock.patch("builtins.input") as mock_input, mock.patch(
"os.listdir"
) as mock_listdir, mock.patch("requests.post") as mock_requests, mock.patch(
"subprocess.run"
) as mock_subprocess:
yield Mocks(
input=mock_input,
listdir=mock_listdir,
requests=mock_requests,
subprocess=mock_subprocess,
)
@pytest.mark.skipif(
sys.platform == "win32", reason="Snapshot paths are different on Windows"
)
def test_happy_path(
capsys: pytest.CaptureFixture[str],
mocks: Mocks,
monkeypatch: pytest.MonkeyPatch,
snapshot: SnapshotAssertion,
) -> None:
"""Test the release script happy path.
Simulate user input for a typical, existing release.
This one test case covers most lines of the release script, without
actually making network requests or running subprocesses. For an
infrequently used script, this coverage is useful without being too brittle
to change.
"""
monkeypatch.setenv("GITHUB_TOKEN", "fake-token")
mocks.input.side_effect = ["y", "5.0.1", "y"]
mocks.listdir.return_value = ["archive1", "archive2", "archive3"]
def mock_post(*args: Any, **kwargs: Any) -> mock.Mock:
"""Return _one_ response JSON that happens to match expectations for multiple requests."""
return mock.Mock(
json=mock.Mock(
return_value={
"body": "Body start **Full Changelog**: fake-body",
"html_url": "https://github.com/path/to/release",
}
),
)
mocks.requests.side_effect = mock_post
mocks.subprocess.return_value.stdout = ""
release.main()
out, err = capsys.readouterr()
assert mocks.mock_calls == snapshot
assert out == snapshot
assert err == snapshot
|