Skip to content

Commit 632864e

Browse files
Tests & Fixes (#156)
- Added tests for safepull functions - Added tests for Distribution and Package model methods. - Updated ruff configuration keys and added pytest dependency.
1 parent ed09899 commit 632864e

File tree

9 files changed

+468
-72
lines changed

9 files changed

+468
-72
lines changed

.github/workflows/release.yml

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,38 @@ on:
44
tags:
55
- v*
66
jobs:
7-
build-n-publish:
8-
name: Build and Build and publish a Python Distribution.
9-
runs-on: ubuntu-latest
10-
steps:
11-
- uses: actions/checkout@v4
12-
- name: Set up Python
13-
uses: actions/setup-python@v5
14-
with:
15-
python-version: "3.x"
16-
- name: Install pypa/build
17-
run: >-
18-
python3 -m
19-
pip install
20-
build
21-
--user
22-
- name: Build a binary wheel and a source tarball
23-
run: >-
24-
python3 -m
25-
build
26-
--sdist
27-
--wheel
28-
--outdir dist/
29-
.
30-
- name: Publish distribution to PyPI
31-
if: startsWith(github.ref, 'refs/tags')
32-
uses: pypa/gh-action-pypi-publish@release/v1
33-
with:
34-
password: ${{ secrets.PYPI_API_TOKEN }}
7+
build-python-package:
8+
name: Build and publish a Python Distribution.
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- name: Set up Python
13+
uses: actions/setup-python@v5
14+
with:
15+
python-version: "3.x"
16+
- name: Install pypa/build
17+
run: >-
18+
python3 -m
19+
pip install
20+
build
21+
--user
22+
- name: Build a binary wheel and a source tarball
23+
run: >-
24+
python3 -m
25+
build
26+
--sdist
27+
--wheel
28+
--outdir dist/
29+
.
30+
pypi-publish:
31+
name: Upload release to PyPI
32+
if: startsWith(github.ref, 'refs/tags')
33+
runs-on: ubuntu-latest
34+
environment:
35+
name: pypi
36+
url: https://pypi.org/p/safepull
37+
permissions:
38+
id-token: write
39+
steps:
40+
- name: Publish package distributions to PyPI
41+
uses: pypa/gh-action-pypi-publish@release/v1

.github/workflows/tests.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Tests
2+
3+
on: pull_request
4+
5+
permissions:
6+
contents: read
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v3
13+
- uses: actions/setup-python@v3
14+
with:
15+
python-version: "3.11"
16+
- name: Install dependencies
17+
run: |
18+
sudo apt install poppler-utils tesseract-ocr
19+
python -m pip install --upgrade pip
20+
pip install poetry
21+
- name: Setup Project
22+
run: |
23+
poetry install --with dev
24+
- name: Run tests
25+
run: poetry run pytest

poetry.lock

Lines changed: 132 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ humanize = "^4.9.0"
2020
[tool.poetry.group.dev.dependencies]
2121
ruff = ">=0.5.0,<0.8.7"
2222
pre-commit = ">=3.3.3,<5.0.0"
23+
pytest = "^8.3.5"
2324

2425
[build-system]
2526
requires = ["poetry-core"]
2627
build-backend = "poetry.core.masonry.api"
2728

2829
[tool.ruff]
2930
target-version = "py311"
30-
select = ["ALL"]
31-
ignore = ["ANN101", "ANN206", "ANN102", "S202"]
31+
lint.select = ["ALL"]
32+
lint.ignore = ["ANN101", "ANN206", "ANN102", "S202"]

src/safepull/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# noqa: D104

src/safepull/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .safepull import run
1+
from .safepull import run # noqa: D100
22

33
if __name__ == "__main__":
44
run()

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# noqa: D104

tests/test_models.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"""Unit tests for the Distribution and Package models in the safepull library."""
2+
3+
from safepull.models import Distribution, Package
4+
5+
# ruff: noqa: D103, S101, PLR2004
6+
7+
8+
def test_distribution_from_dict() -> None:
9+
data = {
10+
"filename": "example.tar.gz",
11+
"packagetype": "sdist",
12+
"url": "https://example.com/example.tar.gz",
13+
"size": 1024,
14+
}
15+
distribution = Distribution.from_dict(data)
16+
assert distribution.filename == "example.tar.gz"
17+
assert distribution.packagetype == "sdist"
18+
assert distribution.url == "https://example.com/example.tar.gz"
19+
assert distribution.size == 1024
20+
21+
22+
def test_distribution_get_metadata() -> None:
23+
distribution = Distribution(
24+
filename="example.tar.gz",
25+
packagetype="sdist",
26+
url="https://example.com/example.tar.gz",
27+
size=1024,
28+
)
29+
metadata = distribution.get_metadata()
30+
assert metadata == (
31+
"Filename: example.tar.gz",
32+
"Package Type: sdist",
33+
"URL: https://example.com/example.tar.gz",
34+
"Size: 0MB",
35+
)
36+
37+
38+
def test_package_from_dict() -> None:
39+
data = {
40+
"info": {
41+
"name": "example-package",
42+
"summary": "An example package",
43+
"author": "John Doe",
44+
"version": "1.0.0",
45+
},
46+
"urls": [
47+
{
48+
"filename": "example.tar.gz",
49+
"packagetype": "sdist",
50+
"url": "https://example.com/example.tar.gz",
51+
"size": 1024,
52+
},
53+
],
54+
}
55+
package = Package.from_dict(data)
56+
assert package.name == "example-package"
57+
assert package.summary == "An example package"
58+
assert package.author == "John Doe"
59+
assert package.version == "1.0.0"
60+
assert len(package.distributions) == 1
61+
assert package.distributions[0].filename == "example.tar.gz"
62+
63+
64+
def test_package_get_metadata() -> None:
65+
package = Package(
66+
name="example-package",
67+
summary="An example package",
68+
author="John Doe",
69+
version="1.0.0",
70+
distributions=[],
71+
)
72+
metadata = package.get_metadata()
73+
assert metadata == (
74+
"example-package 1.0.0",
75+
"An example package",
76+
"Author: John Doe",
77+
)
78+
79+
80+
def test_package_get_distributions() -> None:
81+
distribution = Distribution(
82+
filename="example.tar.gz",
83+
packagetype="sdist",
84+
url="https://example.com/example.tar.gz",
85+
size=1024,
86+
)
87+
package = Package(
88+
name="example-package",
89+
summary="An example package",
90+
author="John Doe",
91+
version="1.0.0",
92+
distributions=[distribution],
93+
)
94+
distributions = package.get_distributions()
95+
assert len(distributions) == 1
96+
assert distributions[0].filename == "example.tar.gz"
97+
98+
99+
def test_package_get_sdist() -> None:
100+
sdist = Distribution(
101+
filename="example.tar.gz",
102+
packagetype="sdist",
103+
url="https://example.com/example.tar.gz",
104+
size=1024,
105+
)
106+
wheel = Distribution(
107+
filename="example.whl",
108+
packagetype="bdist_wheel",
109+
url="https://example.com/example.whl",
110+
size=2048,
111+
)
112+
package = Package(
113+
name="example-package",
114+
summary="An example package",
115+
author="John Doe",
116+
version="1.0.0",
117+
distributions=[sdist, wheel],
118+
)
119+
result = package.get_sdist()
120+
assert result == sdist
121+
122+
package_no_sdist = Package(
123+
name="example-package",
124+
summary="An example package",
125+
author="John Doe",
126+
version="1.0.0",
127+
distributions=[wheel],
128+
)
129+
result = package_no_sdist.get_sdist()
130+
assert result is None

tests/test_safepull.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""Unit tests for the safepull module."""
2+
3+
import argparse
4+
from io import BytesIO
5+
from pathlib import Path
6+
from unittest.mock import MagicMock, patch
7+
8+
import pytest
9+
from safepull.models import Package
10+
from safepull.safepull import query_package, run, unpack
11+
12+
13+
@pytest.fixture()
14+
def mock_package() -> MagicMock:
15+
"""Fixture to create a mock Package object."""
16+
mock_pkg = MagicMock(spec=Package)
17+
mock_pkg.name = "example-package"
18+
mock_pkg.version = "1.0.0"
19+
mock_pkg.get_distributions.return_value = [
20+
MagicMock(
21+
filename="example-1.0.0.tar.gz",
22+
download_package=MagicMock(
23+
return_value=(BytesIO(b"data"), "example-1.0.0.tar.gz"),
24+
),
25+
),
26+
MagicMock(
27+
filename="example-1.0.0.whl",
28+
download_package=MagicMock(
29+
return_value=(BytesIO(b"data"), "example-1.0.0.whl"),
30+
),
31+
),
32+
]
33+
mock_pkg.get_sdist.return_value = mock_pkg.get_distributions()[0]
34+
mock_pkg.get_metadata.return_value = ["Metadata line 1", "Metadata line 2"]
35+
mock_pkg.table_print.return_value = "Mocked Table"
36+
return mock_pkg
37+
38+
39+
@patch("safepull.safepull.requests.get")
40+
def test_query_package(mock_get: MagicMock, mock_package: MagicMock) -> None:
41+
"""Test the query_package function."""
42+
mock_response = MagicMock()
43+
mock_response.json.return_value = {"name": "example-package", "version": "1.0.0"}
44+
mock_response.raise_for_status = MagicMock()
45+
mock_get.return_value = mock_response
46+
47+
with patch("safepull.safepull.Package.from_dict", return_value=mock_package):
48+
result = query_package("example-package")
49+
assert result == mock_package # noqa: S101
50+
mock_get.assert_called_once_with(
51+
"https://pypi.org/pypi/example-package/json",
52+
timeout=60,
53+
)
54+
55+
56+
@patch("safepull.safepull.tarfile.open")
57+
@patch("safepull.safepull.ZipFile")
58+
def test_unpack(mock_zipfile: MagicMock, mock_tarfile: MagicMock) -> None:
59+
"""Test the unpack function."""
60+
# Test .tar.gz file
61+
mock_tar = MagicMock()
62+
mock_tarfile.return_value.__enter__.return_value = mock_tar
63+
byte_object = BytesIO(b"data")
64+
unpack(byte_object, "example-1.0.0.tar.gz")
65+
mock_tar.extractall.assert_called_once()
66+
67+
# Test .whl file
68+
mock_zip = MagicMock()
69+
mock_zipfile.return_value.__enter__.return_value = mock_zip
70+
unpack(byte_object, "example-1.0.0.whl")
71+
mock_zip.extractall.assert_called_once_with(
72+
path=Path.cwd().joinpath("example-1.0.0"),
73+
)
74+
75+
76+
@patch("safepull.safepull.query_package")
77+
@patch("safepull.safepull.unpack")
78+
@patch("safepull.safepull.Path.mkdir")
79+
@patch("safepull.safepull.chdir")
80+
@patch("safepull.safepull.Console.print")
81+
@patch("safepull.safepull.input", side_effect=["0", "y"])
82+
def test_run( # noqa: PLR0913
83+
mock_input: MagicMock, # noqa: ARG001
84+
mock_console_print: MagicMock,
85+
mock_chdir: MagicMock,
86+
mock_mkdir: MagicMock,
87+
mock_unpack: MagicMock,
88+
mock_query_package: MagicMock,
89+
mock_package: MagicMock,
90+
) -> None:
91+
"""Test the run function."""
92+
mock_query_package.return_value = mock_package
93+
94+
# Test --all flag
95+
with patch(
96+
"argparse.ArgumentParser.parse_args",
97+
return_value=argparse.Namespace(
98+
package="example-package",
99+
version=None,
100+
force=False,
101+
metadata=False,
102+
all=True,
103+
),
104+
):
105+
run()
106+
mock_mkdir.assert_called_once_with(exist_ok=True)
107+
mock_chdir.assert_called_once()
108+
mock_unpack.assert_called()
109+
110+
# Reset mock_unpack call count
111+
mock_unpack.reset_mock()
112+
113+
# Test --metadata flag
114+
with patch(
115+
"argparse.ArgumentParser.parse_args",
116+
return_value=argparse.Namespace(
117+
package="example-package",
118+
version=None,
119+
force=False,
120+
metadata=True,
121+
all=False,
122+
),
123+
):
124+
run()
125+
# Reset mock_unpack call count
126+
mock_unpack.reset_mock()
127+
# Test interactive selection
128+
with patch(
129+
"argparse.ArgumentParser.parse_args",
130+
return_value=argparse.Namespace(
131+
package="example-package",
132+
version=None,
133+
force=False,
134+
metadata=False,
135+
all=False,
136+
),
137+
):
138+
run()
139+
mock_console_print.assert_called_once_with("Mocked Table")
140+
mock_unpack.assert_called_once()

0 commit comments

Comments
 (0)