Skip to content

Commit

Permalink
rework unit test to use pytest, add apt package install tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Thanhphan1147 committed Jan 5, 2024
1 parent 2ff9a8c commit 95d2560
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 57 deletions.
2 changes: 1 addition & 1 deletion src/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def install(self) -> None:
apt.update()
apt.add_package("openjdk-17-jre")
apt.add_package(AGENT_PACKAGE_NAME)
except (apt.PackageError, apt.PackageNotFoundError) as exc:
except (apt.PackageError, apt.PackageNotFoundError, apt.GPGKeyError) as exc:
raise PackageInstallError("Error installing the agent package") from exc

def restart(self) -> None:
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
"""Fixtures for jenkins-agent charm tests."""


import pytest
from ops.testing import Harness

from charm import JenkinsAgentCharm


@pytest.fixture(scope="function", name="harness")
def harness_fixture():
"""Enable ops test framework harness."""
harness = Harness(JenkinsAgentCharm)

yield harness

harness.cleanup()
110 changes: 54 additions & 56 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
# pylint: disable=protected-access
"""Test for charm hooks."""

import unittest
import unittest.mock
from unittest.mock import patch
from unittest.mock import MagicMock

import ops
import ops.testing
import pytest

import charm_state
import service
from charm import JenkinsAgentCharm


Expand All @@ -29,58 +29,56 @@ def raise_exception(exception: Exception):
raise exception


class TestCharm(unittest.TestCase):
"""Test class for unit testing."""
def test___init___invalid_state(harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch):
"""
arrange: patched State.from_charm that raises an InvalidState Error.
act: when the JenkinsAgentCharm is initialized.
assert: The agent falls into BlockedStatus.
"""
monkeypatch.setattr(
charm_state.State,
"from_charm",
MagicMock(side_effect=[charm_state.InvalidStateError("Invalid executor message")]),
)

def setUp(self):
"""Initialize the test class."""
self.harness = ops.testing.Harness(JenkinsAgentCharm)
self.addCleanup(self.harness.cleanup)
harness.begin()

@patch(
"charm_state.State.from_charm",
side_effect=charm_state.InvalidStateError("Invalid executor message"),
)
def test___init___invalid_state(self, _):
"""
arrange: patched State.from_charm that raises an InvalidState Error.
act: when the JenkinsAgentCharm is initialized.
assert: The agent falls into BlockedStatus.
"""
self.harness.begin()

jenkins_charm: JenkinsAgentCharm = self.harness.charm
assert jenkins_charm.unit.status.name == ops.BlockedStatus.name
assert jenkins_charm.unit.status.message == "Invalid executor message"

@patch("service.JenkinsAgentService.restart")
@patch("service.JenkinsAgentService.is_active", return_value=True)
@patch("ops.UpgradeCharmEvent")
def test__on_upgrade_charm(self, _service_restart, _service_is_active, _upgrade_charm_event):
"""
arrange: given a charm with patched agent service that is active.
act: when _on_upgrade_charm is called.
assert: The agent falls into waiting status with the correct message.
"""
self.harness.begin()

jenkins_charm: JenkinsAgentCharm = self.harness.charm
jenkins_charm._on_upgrade_charm(_upgrade_charm_event)

assert jenkins_charm.unit.status.message == "Waiting for relation."
assert jenkins_charm.unit.status.name == ops.BlockedStatus.name

@patch("ops.ConfigChangedEvent")
@patch("ops.Model.get_relation")
def test__on_config_changed(self, _get_relation_mock, _config_changed_event):
"""
arrange: given a charm with patched relation.
act: when _on_config_changed is called.
assert: The charm correctly updates the relation databag.
"""
self.harness.begin()
jenkins_charm: JenkinsAgentCharm = self.harness.charm
jenkins_charm._on_config_changed(_config_changed_event)

agent_relation = _get_relation_mock.return_value
assert agent_relation.data[self.harness._unit_name].update.call_count == 1
jenkins_charm: JenkinsAgentCharm = harness.charm
assert jenkins_charm.unit.status.name == ops.BlockedStatus.name
assert jenkins_charm.unit.status.message == "Invalid executor message"


def test__on_upgrade_charm(harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch):
"""
arrange: given a charm with patched agent service that is active.
act: when _on_upgrade_charm is called.
assert: The agent falls into waiting status with the correct message.
"""
monkeypatch.setattr(service.JenkinsAgentService, "is_active", MagicMock(return_value=True))
monkeypatch.setattr(service.JenkinsAgentService, "restart", MagicMock())
harness.begin()

jenkins_charm: JenkinsAgentCharm = harness.charm
upgrade_charm_event = MagicMock(spec=ops.UpgradeCharmEvent)
jenkins_charm._on_upgrade_charm(upgrade_charm_event)

assert jenkins_charm.unit.status.message == "Waiting for relation."
assert jenkins_charm.unit.status.name == ops.BlockedStatus.name


def test__on_config_changed(harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch):
"""
arrange: given a charm with patched relation.
act: when _on_config_changed is called.
assert: The charm correctly updates the relation databag.
"""
harness.begin()
config_changed_event = MagicMock(spec=ops.ConfigChangedEvent)
get_relation_mock = MagicMock()
monkeypatch.setattr(ops.Model, "get_relation", get_relation_mock)

jenkins_charm: JenkinsAgentCharm = harness.charm
jenkins_charm._on_config_changed(config_changed_event)

agent_relation = get_relation_mock.return_value
assert agent_relation.data[harness._unit_name].update.call_count == 1
40 changes: 40 additions & 0 deletions tests/unit/test_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
"""Test for service interaction."""
# pylint: disable=protected-access
from unittest.mock import MagicMock

import ops.testing
import pytest
from charms.operator_libs_linux.v0 import apt

from charm import JenkinsAgentCharm


@pytest.mark.parametrize(
"f,error_thrown",
[
("import_key", apt.GPGKeyError),
("add_package", apt.PackageError),
("add_package", apt.PackageNotFoundError),
],
)
def test_install_apt_package_gpg_key_error(
harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch, f, error_thrown
):
"""
arrange: Harness with mocked apt module.
act: run _on_install hook with methods raising different errors.
assert: The charm should be in an error state.
"""
harness.begin()
charm: JenkinsAgentCharm = harness.charm
monkeypatch.setattr(apt, "RepositoryMapping", MagicMock())
monkeypatch.setattr(apt, "import_key", MagicMock())
monkeypatch.setattr(apt, "update", MagicMock())
monkeypatch.setattr(apt, "add_package", MagicMock())

monkeypatch.setattr(apt, f, MagicMock(side_effect=[error_thrown]))

with pytest.raises(RuntimeError, match="Error installing the agent service"):
charm._on_install(MagicMock(spec=ops.InstallEvent))

0 comments on commit 95d2560

Please sign in to comment.