Skip to content

Commit

Permalink
Tests adjusted
Browse files Browse the repository at this point in the history
  • Loading branch information
juditnovak committed Oct 12, 2023
1 parent 27f7c1c commit 5d8fc32
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 37 deletions.
10 changes: 10 additions & 0 deletions tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,16 @@ async def test_reset_and_get_password_secret_same_as_cli(ops_test: OpsTest) -> N
# Getting back the pw programmatically
password = await get_password(ops_test, username="monitor")

#
# No way to retrieve a secet by label for now (https://bugs.launchpad.net/juju/+bug/2037104)
# Therefore we take advantage of the fact, that we only have ONE single secret a this point
# So we take the single member of the list
# NOTE: This would BREAK if for instance units had secrets at the start...
#
complete_command = "list-secrets"
_, stdout, _ = await ops_test.juju(*complete_command.split())
secret_id = stdout.split("\n")[1].split(" ")[0]

# Getting back the pw from juju CLI
complete_command = f"show-secret {secret_id} --reveal --format=json"
_, stdout, _ = await ops_test.juju(*complete_command.split())
Expand Down
257 changes: 220 additions & 37 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

import logging
import pytest
import re
import unittest
from unittest import mock
from unittest.mock import call, patch
from unittest.mock import call, patch, MagicMock

from parameterized import parameterized

from charms.operator_libs_linux.v1 import snap
from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus
Expand Down Expand Up @@ -38,6 +43,15 @@ def setUp(self, *unused):
self.harness.begin()
self.peer_rel_id = self.harness.add_relation("database-peers", "database-peers")

@pytest.fixture
def use_caplog(self, caplog):
self._caplog = caplog

def _setup_secrets(self):
self.harness.set_leader(True)
self.harness.charm._generate_secrets()
self.harness.set_leader(False)

@patch("charm.MongodbOperatorCharm.get_secret")
@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBConnection")
Expand Down Expand Up @@ -641,61 +655,230 @@ def test_start_init_user_after_second_call(self, run, config):
self.harness.charm._init_operator_user()
run.assert_called_once()

def test_get_password(self):
self._setup_secrets()
assert isinstance(self.harness.charm.get_secret("app", "monitor-password"), str)
self.harness.charm.get_secret("app", "non-existing-secret") is None

self.harness.charm.set_secret("unit", "somekey", "bla")
assert isinstance(self.harness.charm.get_secret("unit", "somekey"), str)
self.harness.charm.get_secret("unit", "non-existing-secret") is None

def test_set_reset_existing_password_app(self):
"""NOTE: currently ops.testing seems to allow for non-leader to set secrets too!"""
self._setup_secrets()

# Getting current password
self.harness.charm.set_secret("app", "monitor-password", "bla")
assert self.harness.charm.get_secret("app", "monitor-password") == "bla"

self.harness.charm.set_secret("app", "monitor-password", "blablabla")
assert self.harness.charm.get_secret("app", "monitor-password") == "blablabla"

@parameterized.expand([("app"), ("unit")])
def test_set_secret_returning_secret_id(self, scope):
secret_id = self.harness.charm.set_secret(scope, "somekey", "bla")
assert re.match(f"mongodb.{scope}", secret_id)

@parameterized.expand([("app"), ("unit")])
def test_set_reset_new_secret(self, scope):
"""NOTE: currently ops.testing seems to allow for non-leader to set secrets too!"""
# Getting current password
self.harness.charm.set_secret(scope, "new-secret", "bla")
assert self.harness.charm.get_secret(scope, "new-secret") == "bla"

# Reset new secret
self.harness.charm.set_secret(scope, "new-secret", "blablabla")
assert self.harness.charm.get_secret(scope, "new-secret") == "blablabla"

# Set another new secret
self.harness.charm.set_secret(scope, "new-secret2", "blablabla")
assert self.harness.charm.get_secret(scope, "new-secret2") == "blablabla"

@parameterized.expand([("app"), ("unit")])
def test_invalid_secret(self, scope):
with self.assertRaises(TypeError):
self.harness.charm.set_secret("unit", "somekey", 1)

self.harness.charm.set_secret("unit", "somekey", "")
assert self.harness.charm.get_secret(scope, "somekey") is None

@pytest.mark.usefixtures("use_caplog")
def test_delete_password(self):
"""NOTE: currently ops.testing seems to allow for non-leader to remove secrets too!"""
self._setup_secrets()

assert self.harness.charm.get_secret("app", "monitor-password")
self.harness.charm.remove_secret("app", "monitor-password")
assert self.harness.charm.get_secret("app", "monitor-password") is None

assert self.harness.charm.set_secret("unit", "somekey", "somesecret")
self.harness.charm.remove_secret("unit", "somekey")
assert self.harness.charm.get_secret("unit", "somekey") is None

with self._caplog.at_level(logging.ERROR):
self.harness.charm.remove_secret("app", "monitor-password")
assert (
"Non-existing secret app:monitor-password was attempted to be removed."
in self._caplog.text
)

self.harness.charm.remove_secret("unit", "somekey")
assert (
"Non-existing secret unit:somekey was attempted to be removed."
in self._caplog.text
)

self.harness.charm.remove_secret("app", "non-existing-secret")
assert (
"Non-existing secret app:non-existing-secret was attempted to be removed."
in self._caplog.text
)

self.harness.charm.remove_secret("unit", "non-existing-secret")
assert (
"Non-existing secret unit:non-existing-secret was attempted to be removed."
in self._caplog.text
)

@parameterized.expand([("app"), ("unit")])
@patch("charm.MongodbOperatorCharm._connect_mongodb_exporter")
def test_on_secret_changed(self, scope, connect_exporter):
"""NOTE: currently ops.testing seems to allow for non-leader to set secrets too!"""
secret_label = self.harness.charm.set_secret(scope, "new-secret", "bla")
secret = self.harness.charm.model.get_secret(label=secret_label)

event = mock.Mock()
event.secret = secret
secret_label = self.harness.charm._on_secret_changed(event)
connect_exporter.assert_called()

@parameterized.expand([("app"), ("unit")])
@pytest.mark.usefixtures("use_caplog")
@patch("charm.MongodbOperatorCharm._connect_mongodb_exporter")
def test_on_other_secret_changed(self, scope, connect_exporter):
"""NOTE: currently ops.testing seems to allow for non-leader to set secrets too!"""
# "Hack": creating a secret outside of the normal MongodbOperatorCharm.set_secret workflow
scope_obj = self.harness.charm._scope_obj(scope)
secret = scope_obj.add_secret({"key": "value"})

event = mock.Mock()
event.secret = secret

with self._caplog.at_level(logging.DEBUG):
self.harness.charm._on_secret_changed(event)
assert f"Secret {secret.id} changed, but it's unknown" in self._caplog.text

connect_exporter.assert_not_called()

@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBConnection")
@patch("charm.MongoDBBackups._get_pbm_status")
def test_set_password(self, pbm_status, connection):
"""Tests that a new admin password is generated and is returned to the user."""
@patch("charm.MongodbOperatorCharm._connect_mongodb_exporter")
def test_connect_to_mongo_exporter_on_set_password(self, connect_exporter, connection):
"""Test _connect_mongodb_exporter is called when the password is set for 'montior' user."""
# container = self.harness.model.unit.get_container("mongod")
# self.harness.set_can_connect(container, True)
# self.harness.charm.on.mongod_pebble_ready.emit(container)
self.harness.set_leader(True)
pbm_status.return_value = ActiveStatus("pbm")
original_password = self.harness.charm.get_secret("app", "operator-password")

action_event = mock.Mock()
action_event.params = {}
action_event.params = {"username": "monitor"}
self.harness.charm._on_set_password(action_event)
new_password = self.harness.charm.get_secret("app", "operator-password")

# verify app data is updated and results are reported to user
self.assertNotEqual(original_password, new_password)
connect_exporter.assert_called()

@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBConnection")
@patch("charm.MongoDBBackups._get_pbm_status")
def test_set_password_provided(self, pbm_status, connection):
"""Tests that a given password is set as the new mongodb password."""
@patch("charm.MongodbOperatorCharm.has_backup_service")
@patch("charm.MongoDBConnection")
@patch("charm.MongodbOperatorCharm._connect_mongodb_exporter")
def test_event_set_password_secrets(
self, connect_exporter, connection, has_backup_service, get_pbm_status
):
"""Test _connect_mongodb_exporter is called when the password is set for 'montior' user.
Furthermore: in Juju 3.x we want to use secrets
"""
pw = "bla"
has_backup_service.return_value = True
get_pbm_status.return_value = ActiveStatus()
self.harness.set_leader(True)
pbm_status.return_value = ActiveStatus("pbm")

action_event = mock.Mock()
action_event.params = {"password": "canonical123"}
action_event.set_results = MagicMock()
action_event.params = {"username": "monitor", "password": pw}
self.harness.charm._on_set_password(action_event)
new_password = self.harness.charm.get_secret("app", "operator-password")
connect_exporter.assert_called()

# verify app data is updated and results are reported to user
self.assertEqual("canonical123", new_password)
action_event.set_results.assert_called_with(
{"password": "canonical123", "secret-id": mock.ANY}
)
action_event.set_results.assert_called()
args_pw_set = action_event.set_results.call_args.args[0]
assert "secret-id" in args_pw_set

action_event.params = {"username": "monitor"}
self.harness.charm._on_get_password(action_event)
args_pw = action_event.set_results.call_args.args[0]
assert "password" in args_pw
assert args_pw["password"] == pw

@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBConnection")
@patch("charm.MongoDBBackups._get_pbm_status")
def test_set_password_failure(self, pbm_status, connection):
"""Tests failure to reset password does not update app data and failure is reported."""
@patch("charm.MongodbOperatorCharm.has_backup_service")
@patch("charm.MongoDBConnection")
@patch("charm.MongodbOperatorCharm._connect_mongodb_exporter")
def test_event_auto_reset_password_secrets_when_no_pw_value_shipped(
self, connect_exporter, connection, has_backup_service, get_pbm_status
):
"""Test _connect_mongodb_exporter is called when the password is set for 'montior' user.
Furthermore: in Juju 3.x we want to use secrets
"""
has_backup_service.return_value = True
get_pbm_status.return_value = ActiveStatus()
self._setup_secrets()
self.harness.set_leader(True)
pbm_status.return_value = ActiveStatus("pbm")
original_password = self.harness.charm.get_secret("app", "operator-password")

action_event = mock.Mock()
action_event.params = {}
action_event.set_results = MagicMock()

for exception in [PYMONGO_EXCEPTIONS, NotReadyError]:
connection.return_value.__enter__.return_value.set_user_password.side_effect = (
exception
)
self.harness.charm._on_set_password(action_event)
current_password = self.harness.charm.get_secret("app", "operator-password")
# Getting current password
action_event.params = {"username": "monitor"}
self.harness.charm._on_get_password(action_event)
args_pw = action_event.set_results.call_args.args[0]
assert "password" in args_pw
pw1 = args_pw["password"]

# verify passwords are not updated.
self.assertEqual(current_password, original_password)
action_event.fail.assert_called()
# No password value was shipped
action_event.params = {"username": "monitor"}
self.harness.charm._on_set_password(action_event)
connect_exporter.assert_called()

# New password was generated
action_event.params = {"username": "monitor"}
self.harness.charm._on_get_password(action_event)
args_pw = action_event.set_results.call_args.args[0]
assert "password" in args_pw
pw2 = args_pw["password"]

# a new password was created
assert pw1 != pw2

@patch("charm.MongoDBConnection")
@patch("charm.MongodbOperatorCharm._connect_mongodb_exporter")
def test_event_any_unit_can_get_password_secrets(self, connect_exporter, connection):
"""Test _connect_mongodb_exporter is called when the password is set for 'montior' user.
Furthermore: in Juju 3.x we want to use secrets
"""
self._setup_secrets()

action_event = mock.Mock()
action_event.set_results = MagicMock()

# Getting current password
action_event.params = {"username": "monitor"}
self.harness.charm._on_get_password(action_event)
args_pw = action_event.set_results.call_args.args[0]
assert "password" in args_pw
assert args_pw["password"]

@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBBackups._get_pbm_status")
Expand Down

0 comments on commit 5d8fc32

Please sign in to comment.