Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added unit tests for ClusterRequirer interface class #107

Merged
merged 7 commits into from
Dec 11, 2024
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
54 changes: 53 additions & 1 deletion tests/test_coordinated_workers/test_coordinator.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import dataclasses
import json
from unittest.mock import patch

import ops
import pytest
from ops import testing
from ops import RelationChangedEvent, testing

from cosl.coordinated_workers.interface import ClusterRemovedEvent, DataValidationError
from src.cosl.coordinated_workers.coordinator import (
ClusterRolesConfig,
Coordinator,
S3NotFoundError,
)
from src.cosl.interfaces.cluster import ClusterRequirerAppData
from tests.test_coordinated_workers.test_worker import MyCharm


@pytest.fixture
Expand Down Expand Up @@ -348,3 +351,52 @@ def test_invalid_databag_content(coordinator_charm: ops.CharmBase, event):
cluster.gather_addresses_by_role()
manager.run()
assert cluster.model.unit.status == ops.BlockedStatus("[consistency] Cluster inconsistent.")


@pytest.mark.parametrize("app", (True, False))
def test_invalid_app_or_unit_databag(
coordinator_charm: ops.CharmBase, coordinator_state, app: bool
):
# Test that when a relation changes and either the app or unit data is invalid
# the worker emits a ClusterRemovedEvent

# WHEN you define a properly configured charm
ctx = testing.Context(
MyCharm,
meta={
"name": "foo",
"requires": {"cluster": {"interface": "cluster"}},
"containers": {"foo": {"type": "oci-image"}},
},
config={"options": {"role-all": {"type": "boolean", "default": True}}},
)

# IF the relation data is invalid (forced by the patched Exception)
object_to_patch = (
"cosl.coordinated_workers.interface.ClusterProviderAppData.load"
if app
else "cosl.coordinated_workers.interface.ClusterRequirerUnitData.load"
)

with patch(object_to_patch, side_effect=DataValidationError("Mock error")):
# AND the relation changes
relation = testing.Relation("cluster")

ctx.run(
ctx.on.relation_changed(relation),
testing.State(
containers={testing.Container("foo", can_connect=True)}, relations={relation}
),
)

# NOTE: this difference should not exist, and the ClusterRemovedEvent should always
# be emitted in case of corrupted data

# THEN the charm emits a ClusterRemovedEvent
if app:
assert len(ctx.emitted_events) == 2
assert isinstance(ctx.emitted_events[0], RelationChangedEvent)
assert isinstance(ctx.emitted_events[1], ClusterRemovedEvent)
else:
assert len(ctx.emitted_events) == 1
assert isinstance(ctx.emitted_events[0], RelationChangedEvent)
129 changes: 129 additions & 0 deletions tests/test_coordinated_workers/test_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import pytest
import yaml
from ops import testing
from scenario.errors import UncaughtCharmError

from cosl.coordinated_workers.worker import (
CERT_FILE,
Expand Down Expand Up @@ -774,3 +775,131 @@ def test_worker_stop_all_services_if_not_ready(tmp_path):
assert all(svc is ops.pebble.ServiceStatus.INACTIVE for svc in service_statuses), [
stat.value for stat in service_statuses
]


@patch("socket.getfqdn")
def test_invalid_url(mock_socket_fqdn):
# Test that when socket returns an invalid url as a Fully Qualified Domain Name,
# ClusterRequirer.publish_unit_address raises a ValueError exception

# GIVEN a properly configured charm
ctx = testing.Context(
MyCharm,
meta={
"name": "foo",
"requires": {"cluster": {"interface": "cluster"}},
"containers": {"foo": {"type": "oci-image"}},
},
config={"options": {"role-all": {"type": "boolean", "default": True}}},
)

# AND ClusterRequirer is passed an invalid url as FQDN
mock_socket_fqdn.return_value = "http://www.invalid-]url.com"

# WHEN the charm executes any event
# THEN the charm raises an error with the appropriate cause
with pytest.raises(UncaughtCharmError) as exc:
ctx.run(ctx.on.update_status(), testing.State(containers={testing.Container("foo")}))
assert isinstance(exc.value.__cause__, ValueError)
PietroPasotti marked this conversation as resolved.
Show resolved Hide resolved


@pytest.mark.parametrize(
"remote_databag, expected",
(
(
{
"charm_tracing_receivers": json.dumps({"url": "test-url.com"}),
"worker_config": json.dumps("test"),
},
{"url": "test-url.com"},
),
(
{"charm_tracing_receivers": json.dumps(None), "worker_config": json.dumps("test")},
{},
),
),
)
def test_get_charm_tracing_receivers(remote_databag, expected):
# Test that when a relation changes the correct charm_tracing_receivers
# are returned by the ClusterRequirer

# GIVEN a charm with a relation
ctx = testing.Context(
MyCharm,
meta={
"name": "foo",
"requires": {"cluster": {"interface": "cluster"}},
"containers": {"foo": {"type": "oci-image"}},
},
config={"options": {"role-all": {"type": "boolean", "default": True}}},
)
container = testing.Container(
"foo",
execs={testing.Exec(("update-ca-certificates", "--fresh"))},
can_connect=True,
)

relation = testing.Relation(
"cluster",
remote_app_data=remote_databag,
)

# WHEN the relation changes
with ctx(
lorenzo-medici marked this conversation as resolved.
Show resolved Hide resolved
ctx.on.relation_changed(relation),
testing.State(containers={container}, relations={relation}),
) as mgr:
charm = mgr.charm
# THEN the charm tracing receivers are picked up correctly
assert charm.worker.cluster.get_charm_tracing_receivers() == expected


@pytest.mark.parametrize(
"remote_databag, expected",
(
(
{
"workload_tracing_receivers": json.dumps({"url": "test-url.com"}),
"worker_config": json.dumps("test"),
},
{"url": "test-url.com"},
),
(
{"workload_tracing_receivers": json.dumps(None), "worker_config": json.dumps("test")},
{},
),
),
)
def test_get_workload_tracing_receivers(remote_databag, expected):
# Test that when a relation changes the correct workload_tracing_receivers
# are returned by the ClusterRequirer

# GIVEN a charm with a relation
ctx = testing.Context(
MyCharm,
meta={
"name": "foo",
"requires": {"cluster": {"interface": "cluster"}},
"containers": {"foo": {"type": "oci-image"}},
},
config={"options": {"role-all": {"type": "boolean", "default": True}}},
)
container = testing.Container(
"foo",
execs={testing.Exec(("update-ca-certificates", "--fresh"))},
can_connect=True,
)

relation = testing.Relation(
"cluster",
remote_app_data=remote_databag,
)

# WHEN the relation changes
with ctx(
ctx.on.relation_changed(relation),
testing.State(containers={container}, relations={relation}),
) as mgr:
charm = mgr.charm
# THEN the charm tracing receivers are picked up correctly
assert charm.worker.cluster.get_workload_tracing_receivers() == expected
Loading