Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,8 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install hatch
- name: Install project with test dependencies
run: hatch env create
- name: Test with pytest and coverage
run: hatch test --cover --cover-quiet
- name: Generate coverage XML report
if: matrix.python-version == '3.12'
run: hatch run test:coverage xml
run: hatch run test-cov
- name: Upload coverage to Codecov
if: matrix.python-version == '3.12'
uses: codecov/codecov-action@v5
Expand Down
58 changes: 40 additions & 18 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,32 @@ include = ["/ibutsu_client", "/test", "/docs", "/requirements.txt", "/test-requi
[tool.hatch.build.targets.wheel]
packages = ["/ibutsu_client"]

[tool.hatch.envs.hatch-test]
extra-dependencies = ["ibutsu-client[test]"]

[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.11", "3.12", "3.13"]

[tool.hatch.envs.test]
[tool.hatch.envs.default]
dependencies = [
"pytest",
"pytest-cov",
"coverage[toml]",
]
extra-dependencies = [
"pytest-rerunfailures",
"pytest-mock",
"pytest-xdist",
"pytest-rerunfailures",
"coverage[toml]",
"pre-commit",
]

[tool.hatch.envs.default.scripts]
test = "pytest {args}"
test-cov = "pytest --cov=ibutsu_client --cov-report=xml --cov-report=term-missing --cov-report=html {args}"
cov-report = "coverage report"
cov-xml = "coverage xml"
cov-html = "coverage html"
lint = "pre-commit run --all-files"

# CI environment for testing multiple Python versions
[tool.hatch.envs.test-matrix]
template = "default"

[[tool.hatch.envs.test-matrix.matrix]]
python = ["3.11", "3.12", "3.13"]

[tool.mypy]
# Enable strict mode for maximum type safety
strict = true
Expand Down Expand Up @@ -132,6 +140,7 @@ source = ["ibutsu_client"]
branch = true
omit = [
"*/test/*",
"*/tests/*",
"*/test_*",
"*/conftest.py",
"*/__pycache__/*",
Expand All @@ -140,22 +149,35 @@ omit = [
]

[tool.coverage.report]
precision = 2
skip_empty = true
exclude_lines = [
# Pragmas and docstrings
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
# Defensive programming patterns
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"assert False",
# Type checking blocks
"if TYPE_CHECKING:",
"if typing.TYPE_CHECKING:",
# Abstract methods
"@abstractmethod",
"@abc.abstractmethod",
# Main guard
"if __name__ == .__main__.:",
# Debug-only code
"if self.debug:",
"if settings.DEBUG",
"if.*__debug__:",
"if 0:",
# Protocol definitions
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
"TYPE_CHECKING",
"@overload",
"@typing.overload",
]
show_missing = true
precision = 2
skip_covered = false

[tool.coverage.html]
directory = "htmlcov"
Expand Down
70 changes: 70 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Test configuration and fixtures for ibutsu_client tests."""

import json
from typing import Any
from unittest.mock import Mock

import pytest

from ibutsu_client.rest import RESTResponse


@pytest.fixture
def mock_api_client(mocker):
"""Create a mock ApiClient for testing API methods."""
from ibutsu_client.api_client import ApiClient

client = ApiClient()
mocker.patch.object(client, "call_api")
return client


def create_mock_response(
data: dict[str, Any] | list[Any] | None = None,
status: int = 200,
headers: dict[str, str] | None = None,
) -> RESTResponse:
"""Create a mock REST response for testing.

Args:
data: The response data (will be JSON-encoded)
status: HTTP status code
headers: Response headers

Returns:
A mock RESTResponse object
"""
if headers is None:
headers = {"Content-Type": "application/json; charset=utf-8"}

if data is None:
data = {}

response = Mock(spec=RESTResponse)
response.status = status
response.headers = headers
response.data = json.dumps(data).encode("utf-8")
response.reason = "OK" if status < 400 else "Error"

def mock_read():
"""Mock the read method to decode the response data."""
return response.data

def mock_getheader(name: str, default: str | None = None) -> str | None:
"""Mock the getheader method."""
return headers.get(name, default)

def mock_getheaders() -> dict[str, str]:
"""Mock the getheaders method."""
return headers

response.read = mock_read
response.getheader = mock_getheader
response.getheaders = mock_getheaders
return response


@pytest.fixture
def mock_rest_response():
"""Fixture that provides the create_mock_response function."""
return create_mock_response
125 changes: 89 additions & 36 deletions test/test_health.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,96 @@
Do not edit the class manually.
"""

import unittest
import json

from ibutsu_client.models.health import Health


class TestHealth(unittest.TestCase):
"""Health unit test stubs"""

def setUp(self):
pass

def tearDown(self):
pass

def make_instance(self, include_optional) -> Health:
"""Test Health
include_optional is a boolean, when False only required
params are included, when True both required and
optional params are included"""
# uncomment below to create an instance of `Health`
"""
model = Health()
if include_optional:
return Health(
status = 'Error',
message = 'Cannot connect to database'
)
else:
return Health(
)
"""

def testHealth(self):
"""Test Health"""
# inst_req_only = self.make_instance(include_optional=False)
# inst_req_and_optional = self.make_instance(include_optional=True)


if __name__ == "__main__":
unittest.main()
class TestHealth:
"""Health model tests"""

def test_health_creation_empty(self):
"""Test Health creation with no parameters"""
health = Health()
assert health.status is None
assert health.message is None

def test_health_creation_with_status(self):
"""Test Health creation with status only"""
health = Health(status="OK")
assert health.status == "OK"
assert health.message is None

def test_health_creation_full(self):
"""Test Health creation with all parameters"""
health = Health(status="Error", message="Cannot connect to database")
assert health.status == "Error"
assert health.message == "Cannot connect to database"

def test_health_to_dict(self):
"""Test Health to_dict conversion"""
health = Health(status="OK", message="Database is healthy")
health_dict = health.to_dict()

assert isinstance(health_dict, dict)
assert health_dict["status"] == "OK"
assert health_dict["message"] == "Database is healthy"

def test_health_to_json(self):
"""Test Health to_json conversion"""
health = Health(status="Error", message="Connection failed")
health_json = health.to_json()

assert isinstance(health_json, str)
parsed = json.loads(health_json)
assert parsed["status"] == "Error"
assert parsed["message"] == "Connection failed"

def test_health_from_dict(self):
"""Test Health from_dict creation"""
health_dict = {"status": "OK", "message": "All systems operational"}
health = Health.from_dict(health_dict)

assert isinstance(health, Health)
assert health.status == "OK"
assert health.message == "All systems operational"

def test_health_from_json(self):
"""Test Health from_json creation"""
health_json = '{"status": "Pending", "message": "Starting up"}'
health = Health.from_json(health_json)

assert isinstance(health, Health)
assert health.status == "Pending"
assert health.message == "Starting up"

def test_health_to_str(self):
"""Test Health to_str representation"""
health = Health(status="OK", message="Healthy")
health_str = health.to_str()

assert isinstance(health_str, str)
assert "OK" in health_str
assert "Healthy" in health_str

def test_health_none_values_excluded(self):
"""Test that None values are excluded from dict"""
health = Health(status="OK")
health_dict = health.to_dict()

assert "status" in health_dict
assert "message" not in health_dict

def test_health_from_dict_none(self):
"""Test Health.from_dict with None returns None"""
health = Health.from_dict(None)
assert health is None

def test_health_roundtrip_json(self):
"""Test Health can be serialized and deserialized"""
original = Health(status="Error", message="Test error")
json_str = original.to_json()
restored = Health.from_json(json_str)

assert restored.status == original.status
assert restored.message == original.message
Loading