Skip to content

Commit 4145e48

Browse files
Merge pull request #48 from AbsaOSS/feature/add-integration-test-for-local-run
Feature/add integration test for local run
2 parents 4cbd64f + 69d4098 commit 4145e48

18 files changed

+272
-17
lines changed

.DS_Store

6 KB
Binary file not shown.

.github/workflows/test.yml

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ on:
2121
- '**'
2222
types: [ opened, synchronize, reopened ]
2323

24+
concurrency:
25+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
26+
cancel-in-progress: true
27+
2428
jobs:
2529
static-code-analysis:
2630
runs-on: ubuntu-latest
@@ -34,7 +38,7 @@ jobs:
3438
- name: Set up Python
3539
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
3640
with:
37-
python-version: '3.11'
41+
python-version: '3.13'
3842
cache: 'pip'
3943

4044
- name: Install dependencies
@@ -68,7 +72,7 @@ jobs:
6872
- name: Set up Python
6973
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
7074
with:
71-
python-version: '3.11'
75+
python-version: '3.13'
7276
cache: 'pip'
7377

7478
- name: Install dependencies
@@ -96,18 +100,41 @@ jobs:
96100

97101
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
98102
with:
99-
python-version: '3.11'
103+
python-version: '3.13'
100104
cache: 'pip'
101105

102106
- name: Install Python dependencies
103107
run: |
104108
pip install -r requirements.txt
105109
106-
- name: Set PYTHONPATH environment variable
107-
run: echo "PYTHONPATH=${GITHUB_WORKSPACE}/version_tag_check/version_tag_check" >> $GITHUB_ENV
108-
109110
- name: Check code coverage with Pytest
110-
run: pytest --cov=. -v tests/ --cov-fail-under=80
111+
run: pytest --cov=. -v -m "not integration" tests/ --cov-fail-under=80
112+
113+
integration-test:
114+
name: Integration Tests
115+
runs-on: ubuntu-latest
116+
117+
defaults:
118+
run:
119+
shell: bash
120+
121+
steps:
122+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
123+
with:
124+
fetch-depth: 0
125+
persist-credentials: false
126+
127+
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
128+
with:
129+
python-version: '3.13'
130+
cache: 'pip'
131+
132+
- name: Install Python dependencies
133+
run: |
134+
pip install -r requirements.txt
135+
136+
- name: Run integration tests
137+
run: pytest -v -m integration tests/integration/
111138

112139
mypy-check:
113140
runs-on: ubuntu-latest
@@ -121,7 +148,7 @@ jobs:
121148
- name: Set up Python
122149
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
123150
with:
124-
python-version: '3.11'
151+
python-version: '3.13'
125152
cache: 'pip'
126153

127154
- name: Install dependencies

.pylintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ fail-under=10
4646
#from-stdin=
4747

4848
# Files or directories to be skipped. They should be base names, not paths.
49-
ignore=CVS
49+
ignore=CVS,tests
5050

5151
# Add files or directories matching the regular expressions patterns to the
5252
# ignore-list. The regex matches against paths and can be in Posix or Windows

DEVELOPER.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- [Run Black Tool Locally](#run-black-tool-locally)
77
- [Run mypy Tool Locally](#run-mypy-tool-locally)
88
- [Run Unit Tests with Pytest](#run-unit-tests-with-pytest)
9+
- [Run Integration Tests with Pytest](#run-integration-tests-with-pytest)
910
- [Code Coverage with pytest-cov](#code-coverage-with-pytest-cov)
1011
- [Releasing](#releasing)
1112
- [Development Flow with GitHub Copilot](#development-flow-with-github-copilot)
@@ -189,16 +190,42 @@ Success: no issues found in 1 source file
189190

190191
---
191192

192-
193193
## Run Unit Tests with Pytest
194194

195-
Unit tests are written using the Pytest framework. To run all the tests, use the following command:
195+
Unit tests are written using the Pytest framework and are located under `tests/unit/`.
196+
197+
To run only unit tests (excluding integration tests), use the following command:
196198
```shell
197-
pytest tests/
199+
pytest -v tests/unit/
198200
```
199201

200202
You can modify the directory to control the level of detail or granularity as per your needs.
201203

204+
---
205+
206+
## Run Integration Tests with Pytest
207+
208+
Integration tests are located under `tests/integration/` and are marked with `@pytest.mark.integration`.
209+
210+
These tests run the action entrypoint (`main.py`) in a subprocess and patch `GitHubRepository` in the child process to avoid real network calls.
211+
Inputs are provided via the same environment variables used by the action (`INPUT_GITHUB_TOKEN`, `INPUT_GITHUB_REPOSITORY`, `INPUT_VERSION_TAG`, `INPUT_SHOULD_EXIST`).
212+
213+
Prerequisites:
214+
215+
- Perform the [setup of python venv](#set-up-python-environment).
216+
217+
Run only integration tests:
218+
219+
```shell
220+
pytest -v -m integration tests/integration/
221+
```
222+
223+
Run everything except integration tests:
224+
225+
```shell
226+
pytest -v -m "not integration" tests/
227+
```
228+
202229
---
203230
## Code Coverage with pytest-cov
204231

@@ -207,7 +234,7 @@ The objective of the project is to achieve a minimum score of 80 %. We do exclud
207234

208235
To generate the coverage report, run the following command:
209236
```shell
210-
pytest --ignore=tests/integration --cov=. tests/ --cov-fail-under=80 --cov-report=html
237+
pytest --ignore=tests --cov=. tests/ --cov-fail-under=80 --cov-report=html
211238
```
212239

213240
See the coverage report on the path:

pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,23 @@ omit = ["tests/*", "main.py", "version_tag_check/github_repository.py"]
99
[tool.mypy]
1010
check_untyped_defs = true
1111
exclude = "tests"
12+
13+
[tool.pytest.ini_options]
14+
markers = [
15+
"integration: integration-style tests that run main.py as a subprocess",
16+
]
17+
18+
testpaths = [
19+
"tests",
20+
]
21+
22+
norecursedirs = [
23+
".git",
24+
".mypy_cache",
25+
".pytest_cache",
26+
".venv",
27+
"__pycache__",
28+
"build",
29+
"dist",
30+
"htmlcov",
31+
]

tests/integration/conftest.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Shared fixtures and helpers for integration tests."""
2+
3+
import os
4+
from pathlib import Path
5+
6+
import pytest
7+
8+
9+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
10+
11+
12+
@pytest.fixture
13+
def subprocess_env_with_mocked_github(tmp_path):
14+
"""Create a subprocess environment with mocked GitHubRepository.
15+
16+
This fixture prepares:
17+
- A copy of os.environ with PYTHONPATH pointing to the project root
18+
- A temporary sitecustomize.py module that patches GitHubRepository
19+
with DummyGitHubRepository before main.py runs
20+
21+
The sitecustomize.py is auto-imported by Python at startup, ensuring
22+
the mock is applied before any production code executes.
23+
24+
Returns:
25+
dict: Environment variables ready for subprocess.run(env=...)
26+
"""
27+
# Base environment with PYTHONPATH including project root
28+
env = os.environ.copy()
29+
env["PYTHONPATH"] = f"{PROJECT_ROOT}{os.pathsep}{env.get('PYTHONPATH', '')}"
30+
31+
# Create sitecustomize.py to patch GitHubRepository in the child process
32+
sitecustomize_path = tmp_path / "sitecustomize.py"
33+
sitecustomize_path.write_text(
34+
"from version_tag_check import github_repository as ghr\n"
35+
"from tests.integration.dummy_github_repository import DummyGitHubRepository\n"
36+
"ghr.GitHubRepository = DummyGitHubRepository\n",
37+
encoding="utf-8",
38+
)
39+
40+
# Prepend temp directory so Python auto-imports our sitecustomize module
41+
env["PYTHONPATH"] = f"{tmp_path}{os.pathsep}{env['PYTHONPATH']}"
42+
43+
return env
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Test-only dummy GitHubRepository used by integration tests."""
2+
3+
from __future__ import annotations
4+
5+
from version_tag_check.version import Version
6+
7+
8+
class DummyGitHubRepository:
9+
"""Simple stand-in for GitHubRepository used in integration tests."""
10+
11+
def __init__(self, owner: str, repo: str, token: str) -> None:
12+
self.owner = owner
13+
self.repo = repo
14+
self.token = token
15+
16+
def get_all_tags(self) -> list[Version]:
17+
"""Return a deterministic list of tags for integration tests."""
18+
19+
return [Version("v0.0.1"), Version("v0.0.2"), Version("v0.1.0")]
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Integration-style tests that run the action via main.py for local execution.
2+
3+
These tests execute the action entrypoint (main.py) in a subprocess to cover the
4+
real wiring (logging setup + VersionTagCheckAction).
5+
6+
To avoid real network calls, GitHubRepository is patched in the child process
7+
via a temporary ``sitecustomize.py`` module.
8+
"""
9+
10+
import subprocess
11+
import sys
12+
from pathlib import Path
13+
14+
import pytest
15+
16+
from version_tag_check.utils.constants import ERROR_TAG_ALREADY_EXISTS
17+
18+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
19+
20+
21+
@pytest.mark.integration
22+
def test_local_run_successful_new_version(subprocess_env_with_mocked_github):
23+
"""Run main.py as a subprocess and expect success for a valid new version.
24+
25+
Scenario: New tag v0.1.1 is a valid patch increment on top of existing
26+
v0.1.0.
27+
"""
28+
29+
# Use the fixture to get the environment with mocked GitHubRepository
30+
env = subprocess_env_with_mocked_github
31+
32+
# Provide action inputs via the same environment variables used in the action.yml
33+
env["INPUT_GITHUB_TOKEN"] = "fake-token"
34+
env["INPUT_GITHUB_REPOSITORY"] = "owner/repo"
35+
env["INPUT_VERSION_TAG"] = "v0.1.1"
36+
env["INPUT_SHOULD_EXIST"] = "false"
37+
38+
# Enable debug logging so we can see integration-level logs if needed
39+
env["RUNNER_DEBUG"] = "1"
40+
41+
result = subprocess.run(
42+
[sys.executable, str(PROJECT_ROOT / "main.py")],
43+
cwd=PROJECT_ROOT,
44+
env=env,
45+
capture_output=True,
46+
text=True,
47+
)
48+
49+
assert result.returncode == 0, f"stdout: {result.stdout}\nstderr: {result.stderr}"
50+
51+
52+
@pytest.mark.integration
53+
def test_local_run_fails_on_existing_tag(subprocess_env_with_mocked_github):
54+
"""Run main.py as a subprocess and expect failure when tag already exists.
55+
56+
Scenario: New tag v0.1.0 already exists in the list returned by
57+
DummyGitHubRepository, so the action should fail.
58+
"""
59+
60+
env = subprocess_env_with_mocked_github
61+
62+
env["INPUT_GITHUB_TOKEN"] = "fake-token"
63+
env["INPUT_GITHUB_REPOSITORY"] = "owner/repo"
64+
env["INPUT_VERSION_TAG"] = "v0.1.0" # already present in DummyGitHubRepository
65+
env["INPUT_SHOULD_EXIST"] = "false"
66+
67+
result = subprocess.run(
68+
[sys.executable, str(PROJECT_ROOT / "main.py")],
69+
cwd=PROJECT_ROOT,
70+
env=env,
71+
capture_output=True,
72+
text=True,
73+
)
74+
75+
assert result.returncode != 0
76+
assert ERROR_TAG_ALREADY_EXISTS in (result.stdout + result.stderr)

tests/unit/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)