Skip to content

Commit

Permalink
ISD-1514 Add unit tests, set coverage target to 95 (#17)
Browse files Browse the repository at this point in the history
Co-authored-by: arturo-seijas <[email protected]>
  • Loading branch information
Thanhphan1147 and arturo-seijas authored Jan 24, 2024
1 parent c6a788a commit 5c1fca2
Show file tree
Hide file tree
Showing 6 changed files with 422 additions and 30 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ skips = ["*/*test.py", "*/test_*.py", "*tests/*.py"]
branch = true

[tool.coverage.report]
# fail_under = 99
fail_under = 95
show_missing = true

[tool.pytest.ini_options]
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,28 @@
"""Fixtures for jenkins-agent charm tests."""


import secrets

import pytest
from ops.testing import Harness

from charm import JenkinsAgentCharm
from charm_state import AGENT_RELATION


@pytest.fixture(scope="module", name="agent_relation_data")
def agent_relation_data_fixture() -> dict:
"""Mock relation data for agent relation."""
return {"url": "http://example.com", "jenkins-agent-0_secret": secrets.token_hex(4)}


@pytest.fixture(scope="module", name="service_configuration_template")
def service_configuration_template_fixture(agent_relation_data: dict) -> str:
"""Mock service environment variables configuration for jenkins-agent."""
return f'''[Service]
Environment="JENKINS_TOKEN={agent_relation_data.get('jenkins-agent-0_secret')}"
Environment="JENKINS_URL={agent_relation_data.get('url')}"
Environment="JENKINS_AGENT=jenkins-agent-0"'''


@pytest.fixture(scope="function", name="harness")
Expand All @@ -17,3 +35,17 @@ def harness_fixture():
yield harness

harness.cleanup()


@pytest.fixture(scope="function", name="harness_with_agent_relation")
def harness_with_agent_relation_fixture(harness: Harness, agent_relation_data: dict) -> Harness:
"""Harness with agent relation to jenkins-k8s.
Args:
harness the default testing harness.
Returns:
The harness with agent relation established.
"""
harness.add_relation(AGENT_RELATION, "jenkins-k8s", unit_data=agent_relation_data)
return harness
154 changes: 154 additions & 0 deletions tests/unit/test_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.
#
# Learn more about testing at: https://juju.is/docs/sdk/testing

"""Test for agent relations."""

from unittest.mock import MagicMock, PropertyMock

import ops.testing
import pytest
from charms.operator_libs_linux.v1 import systemd

import service
from charm import JenkinsAgentCharm
from charm_state import AGENT_RELATION


def test_agent_relation_joined(harness: ops.testing.Harness, agent_relation_data: dict):
"""
arrange: initialized jenkins-agent charm.
act: add relation to the jenkins-k8s charm.
assert: The agent set the correct information in the unit's relation databag.
"""
harness.begin()
relation_id = harness.add_relation(
AGENT_RELATION, "jenkins-k8s", unit_data=agent_relation_data
)
charm: JenkinsAgentCharm = harness.charm
assert (
harness.get_relation_data(relation_id, app_or_unit="jenkins-agent/0")
== charm.state.agent_meta.as_dict()
)


def test_agent_relation_changed_service_restart(
harness_with_agent_relation: ops.testing.Harness,
monkeypatch: pytest.MonkeyPatch,
agent_relation_data: dict,
):
"""
arrange: initialized jenkins-agent charm related to jenkins-k8s charm with relation data.
act: Trigger _on_agent_relation_changed hook.
assert: The charm should be in active state.
"""
harness = harness_with_agent_relation
harness.begin()
charm: JenkinsAgentCharm = harness.charm
monkeypatch.setattr(charm.jenkins_agent_service, "restart", MagicMock())

charm.on.agent_relation_changed.emit(harness.model.get_relation(AGENT_RELATION))

assert charm.state.agent_relation_credentials
assert (
charm.state.agent_relation_credentials.secret
== agent_relation_data["jenkins-agent-0_secret"]
)
assert charm.state.agent_relation_credentials.address == agent_relation_data["url"]
assert charm.unit.status.name == ops.ActiveStatus.name


def test_agent_relation_changed_service_restart_error(
harness_with_agent_relation: ops.testing.Harness,
monkeypatch: pytest.MonkeyPatch,
agent_relation_data: dict,
):
"""
arrange: initialized jenkins-agent charm related to jenkins-k8s charm with relation data.
act: Trigger _on_agent_relation_changed hook with restart throwing an exception.
assert: The charm should be in error state with the correct error message.
"""
harness = harness_with_agent_relation
harness.begin()

charm: JenkinsAgentCharm = harness.charm
assert charm.state.agent_relation_credentials
assert (
charm.state.agent_relation_credentials.secret
== agent_relation_data["jenkins-agent-0_secret"]
)
assert charm.state.agent_relation_credentials.address == agent_relation_data["url"]

monkeypatch.setattr(
charm.jenkins_agent_service, "restart", MagicMock(side_effect=service.ServiceRestartError)
)
with pytest.raises(RuntimeError, match="Error restarting the agent service."):
harness.charm.on.agent_relation_changed.emit(harness.model.get_relation(AGENT_RELATION))


def test_agent_relation_changed_service_already_active(
harness_with_agent_relation: ops.testing.Harness,
monkeypatch: pytest.MonkeyPatch,
):
"""
arrange: initialized jenkins-agent charm related to jenkins-k8s charm with relation data.
act: Trigger _on_agent_relation_changed hook when the service is already up.
assert: The charm skips restarting the agent service.
"""
service_restart_mock = MagicMock()
service_is_active_mock = PropertyMock(return_value=True)
monkeypatch.setattr(service.JenkinsAgentService, "restart", service_restart_mock)
monkeypatch.setattr(service.JenkinsAgentService, "is_active", service_is_active_mock)
harness = harness_with_agent_relation
harness.begin()

harness.charm.on.agent_relation_changed.emit(harness.model.get_relation(AGENT_RELATION))

assert service_is_active_mock.call_count == 1
assert service_restart_mock.call_count == 0


def test_agent_relation_departed_service_stop_error(
harness_with_agent_relation: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch
):
"""
arrange: initialized jenkins-agent charm related to jenkins-k8s charm with relation data.
act: remove the relation, raising an error when stopping the agent service.
assert: The charm falls into BlockedStatus with the correct message.
"""
monkeypatch.setattr(systemd, "service_stop", MagicMock(side_effect=systemd.SystemdError))

harness = harness_with_agent_relation
harness.begin()

relation = harness.model.get_relation(AGENT_RELATION)
assert relation
harness.remove_relation(relation_id=relation.id)

charm: JenkinsAgentCharm = harness.charm
assert charm.unit.status.name == ops.BlockedStatus.name
assert charm.unit.status.message == "Error stopping the agent service"


def test_agent_relation_departed(
harness_with_agent_relation: ops.testing.Harness,
monkeypatch: pytest.MonkeyPatch,
):
"""
arrange: initialized jenkins-agent charm related to jenkins-k8s charm with relation data.
act: remove the relation.
assert: The charm falls into BlockedStatus with the correct message.
"""
monkeypatch.setattr(systemd, "service_stop", MagicMock())

harness = harness_with_agent_relation
harness.begin()

relation = harness.model.get_relation(AGENT_RELATION)
assert relation
harness.remove_relation(relation_id=relation.id)

charm: JenkinsAgentCharm = harness.charm
assert charm.unit.status.name == ops.BlockedStatus.name
assert charm.unit.status.message == "Waiting for config/relation."
97 changes: 72 additions & 25 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#
# Learn more about testing at: https://juju.is/docs/sdk/testing

# pylint: disable=protected-access
"""Test for charm hooks."""

from unittest.mock import MagicMock
Expand All @@ -17,18 +16,6 @@
from charm import JenkinsAgentCharm


def raise_exception(exception: Exception):
"""Raise exception function for monkeypatching.
Args:
exception: The exception to raise.
Raises:
exception: .
"""
raise exception


def test___init___invalid_state(harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch):
"""
arrange: patched State.from_charm that raises an InvalidState Error.
Expand All @@ -43,9 +30,9 @@ def test___init___invalid_state(harness: ops.testing.Harness, monkeypatch: pytes

harness.begin()

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


def test__on_upgrade_charm(harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch):
Expand All @@ -58,12 +45,11 @@ def test__on_upgrade_charm(harness: ops.testing.Harness, monkeypatch: pytest.Mon
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)
charm: JenkinsAgentCharm = harness.charm
charm.on.upgrade_charm.emit()

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


def test__on_config_changed(harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch):
Expand All @@ -73,12 +59,73 @@ def test__on_config_changed(harness: ops.testing.Harness, monkeypatch: pytest.Mo
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)
charm: JenkinsAgentCharm = harness.charm
charm.on.config_changed.emit()

agent_relation = get_relation_mock.return_value
assert agent_relation.data[harness._unit_name].update.call_count == 1
assert agent_relation.data[charm.unit.name].update.call_count == 1


def test_restart_agent_service(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.
"""
get_relation_mock = MagicMock()
monkeypatch.setattr(ops.Model, "get_relation", get_relation_mock)
get_credentials_mock = MagicMock()
restart_mock = MagicMock()
monkeypatch.setattr(service.JenkinsAgentService, "restart", restart_mock)
harness.begin()

charm: JenkinsAgentCharm = harness.charm
monkeypatch.setattr(charm.state, "agent_relation_credentials", get_credentials_mock)
charm.restart_agent_service()

assert restart_mock.call_count == 1
assert charm.unit.status.name == ops.ActiveStatus.name


def test_restart_agent_service_incomplete_relation_data(
harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch
):
"""
arrange: given a charm with patched relation and incomplete relation data.
act: when restart_agent_service is called.
assert: The charm skips service restart and waits for complete relation data.
"""
get_relation_mock = MagicMock()
monkeypatch.setattr(ops.Model, "get_relation", get_relation_mock)
harness.begin()

charm: JenkinsAgentCharm = harness.charm
monkeypatch.setattr(charm.state, "agent_relation_credentials", None)
charm.restart_agent_service()

assert charm.unit.status.name == ops.WaitingStatus.name


def test_restart_agent_service_service_restart_error(
harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch
):
"""
arrange: given a charm with patched relation and complete relation data.
act: when restart_agent_service is called, raising an error.
assert: The charm goes into error state with the correct error message.
"""
get_relation_mock = MagicMock()
monkeypatch.setattr(ops.Model, "get_relation", get_relation_mock)
get_credentials_mock = MagicMock()
restart_mock = MagicMock(side_effect=service.ServiceRestartError)
monkeypatch.setattr(service.JenkinsAgentService, "restart", restart_mock)

harness.begin()

charm: JenkinsAgentCharm = harness.charm
monkeypatch.setattr(charm.state, "agent_relation_credentials", get_credentials_mock)
with pytest.raises(RuntimeError, match="Error restarting the agent service"):
charm.restart_agent_service()
Loading

0 comments on commit 5c1fca2

Please sign in to comment.