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

Add unit and integration tests for kati-ui charm #141

Closed
Closed
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: 7 additions & 0 deletions charms/katib-ui/requirements-integration.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Pinning to <4.0 due to compatibility with the 3.1 controller version
juju<4.0
lightkube
pytest
pytest-operator
pyyaml

202 changes: 202 additions & 0 deletions charms/katib-ui/requirements-integration.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile requirements-integration.in
#
anyio==4.0.0
# via httpcore
asttokens==2.4.0
# via stack-data
backcall==0.2.0
# via ipython
bcrypt==4.0.1
# via paramiko
cachetools==5.3.1
# via google-auth
certifi==2023.7.22
# via
# httpcore
# httpx
# kubernetes
# requests
cffi==1.15.1
# via
# cryptography
# pynacl
charset-normalizer==3.2.0
# via requests
cryptography==41.0.3
# via paramiko
decorator==5.1.1
# via
# ipdb
# ipython
exceptiongroup==1.1.3
# via
# anyio
# ipython
# pytest
executing==1.2.0
# via stack-data
google-auth==2.23.0
# via kubernetes
h11==0.14.0
# via httpcore
httpcore==0.18.0
# via httpx
httpx==0.25.0
# via lightkube
hvac==1.2.1
# via juju
idna==3.4
# via
# anyio
# httpx
# requests
iniconfig==2.0.0
# via pytest
ipdb==0.13.13
# via pytest-operator
ipython==8.15.0
# via ipdb
jedi==0.19.0
# via ipython
jinja2==3.1.2
# via pytest-operator
juju==3.2.2
# via
# -r requirements-integration.in
# pytest-operator
kubernetes==27.2.0
# via juju
lightkube==0.14.0
# via -r requirements-integration.in
lightkube-models==1.28.1.4
# via lightkube
macaroonbakery==1.3.1
# via juju
markupsafe==2.1.3
# via jinja2
matplotlib-inline==0.1.6
# via ipython
mypy-extensions==1.0.0
# via typing-inspect
oauthlib==3.2.2
# via
# kubernetes
# requests-oauthlib
packaging==23.1
# via pytest
paramiko==2.12.0
# via juju
parso==0.8.3
# via jedi
pexpect==4.8.0
# via ipython
pickleshare==0.7.5
# via ipython
pluggy==1.3.0
# via pytest
prompt-toolkit==3.0.39
# via ipython
protobuf==3.20.3
# via macaroonbakery
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
# via stack-data
pyasn1==0.5.0
# via
# juju
# pyasn1-modules
# rsa
pyasn1-modules==0.3.0
# via google-auth
pycparser==2.21
# via cffi
pygments==2.16.1
# via ipython
pyhcl==0.4.5
# via hvac
pymacaroons==0.13.0
# via macaroonbakery
pynacl==1.5.0
# via
# macaroonbakery
# paramiko
# pymacaroons
pyrfc3339==1.1
# via
# juju
# macaroonbakery
pytest==7.4.2
# via
# -r requirements-integration.in
# pytest-asyncio
# pytest-operator
pytest-asyncio==0.21.1
# via pytest-operator
pytest-operator==0.29.0
# via -r requirements-integration.in
python-dateutil==2.8.2
# via kubernetes
pytz==2023.3.post1
# via pyrfc3339
pyyaml==6.0.1
# via
# -r requirements-integration.in
# juju
# kubernetes
# lightkube
# pytest-operator
requests==2.31.0
# via
# hvac
# kubernetes
# macaroonbakery
# requests-oauthlib
requests-oauthlib==1.3.1
# via kubernetes
rsa==4.9
# via google-auth
six==1.16.0
# via
# asttokens
# kubernetes
# macaroonbakery
# paramiko
# pymacaroons
# python-dateutil
sniffio==1.3.0
# via
# anyio
# httpcore
# httpx
stack-data==0.6.2
# via ipython
tomli==2.0.1
# via
# ipdb
# pytest
toposort==1.10
# via juju
traitlets==5.10.0
# via
# ipython
# matplotlib-inline
typing-extensions==4.7.1
# via typing-inspect
typing-inspect==0.9.0
# via juju
urllib3==1.26.16
# via
# google-auth
# kubernetes
# requests
wcwidth==0.2.6
# via prompt-toolkit
websocket-client==1.6.3
# via kubernetes
websockets==11.0.3
# via juju
2 changes: 2 additions & 0 deletions charms/katib-ui/requirements-unit.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pytest
pytest-mock
22 changes: 22 additions & 0 deletions charms/katib-ui/requirements-unit.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile requirements-unit.in
#
exceptiongroup==1.1.3
# via pytest
iniconfig==2.0.0
# via pytest
packaging==23.1
# via pytest
pluggy==1.3.0
# via pytest
pytest==7.4.2
# via
# -r requirements-unit.in
# pytest-mock
pytest-mock==3.11.1
# via -r requirements-unit.in
tomli==2.0.1
# via pytest
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from pathlib import Path

import json
from pathlib import Path
from typing import List
import pytest_asyncio
import requests
import pytest
import yaml
from pytest_operator.plugin import OpsTest

METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
CHARM_NAME = METADATA["name"]


@pytest_asyncio.fixture
async def ws_addresses(ops_test: OpsTest) -> List[str]:
status = await ops_test.model.get_status() # noqa: F821
addresses = []
for unit in list(status.applications[CHARM_NAME].units):
addr = status["applications"][CHARM_NAME]["units"][unit]["address"]
addresses.append(addr)
return addresses

class TestCharm:
@pytest.mark.abort_on_fail
async def test_build_and_deploy(self, ops_test: OpsTest):
"""Build and deploy the charm.

Assert on the unit status.
"""
charm_under_test = await ops_test.build_charm(".")
image_path = METADATA["resources"]["oci-image"]["upstream-source"]
resources = {"oci-image": image_path}

await ops_test.model.deploy(
charm_under_test, resources=resources, application_name=CHARM_NAME
)

await ops_test.model.wait_for_idle(
apps=[CHARM_NAME], status="active", raise_on_blocked=True, timeout=300
)



@pytest.mark.abort_on_fail
async def test_ui_server_ready(self, ws_addresses: List[str]):
"""Verify that all katib-ui units report ready."""
for addr in ws_addresses:
url = f"http://{addr}:8080/katib"
home = requests.get(url)
assert home.status_code == 200

56 changes: 56 additions & 0 deletions charms/katib-ui/tests/unit/test_katib_ui_charm_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pytest
from pytest_mock import mocker, MockerFixture
from ops.model import ActiveStatus

from ops.testing import Harness
from charm import KatibUIOperator


@pytest.fixture
def harness(mocker: MockerFixture):
mocker.patch("charm.KubernetesServicePatch")
harness = Harness(KatibUIOperator)
harness.update_config({"port": 80})
yield harness
harness.cleanup()


def test_leader(mocker: MockerFixture, harness: Harness[KatibUIOperator]):
check_container_conn = mocker.patch("charm.KatibUIOperator._check_container_connection")
deploy_k8s_resources = mocker.patch("charm.KatibUIOperator._deploy_k8s_resources")

# should do nothing if not leader
harness.set_leader(False)
harness.begin_with_initial_hooks()
check_container_conn.assert_called()
deploy_k8s_resources.assert_not_called()
harness.cleanup()
check_container_conn.reset_mock()
deploy_k8s_resources.reset_mock()

harness.set_leader(True)
check_container_conn.assert_called()
deploy_k8s_resources.assert_called()


def test_initial_plan(mocker: MockerFixture, harness: Harness[KatibUIOperator]):
# mocks unrelated HTTP calls
mocker.patch("charm.KatibUIOperator._deploy_k8s_resources")

harness.set_leader(True)
harness.begin()
harness.container_pebble_ready("katib-ui")
plan = harness.get_container_pebble_plan("katib-ui")
expected_plan = {
"services": {
"katib-ui": {
"override": "replace",
"summary": "entrypoint of the katib-ui-operator image",
"command": f"./katib-ui --port=80",
"startup": "enabled",
"environment": {"KATIB_CORE_NAMESPACE": harness.model.name},
}
}
}
assert plan.to_dict() == expected_plan
assert isinstance(harness.charm.unit.status, ActiveStatus)
30 changes: 30 additions & 0 deletions charms/katib-ui/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ envlist = fmt, lint, unit, integration
[vars]
all_path = {[vars]src_path}
src_path = {toxinidir}/src/
tests_path = {toxinidir}/tests/

[testenv]
passenv =
Expand Down Expand Up @@ -62,3 +63,32 @@ commands =
deps =
-r requirements-lint.txt
description = Check code against coding style standards

[testenv:unit]
description = Run unit tests
deps =
pytest
coverage[toml]
-r requirements.txt
-r requirements-unit.txt
commands =
coverage run --source={[vars]src_path} \
-m pytest \
--tb native \
-v \
-s \
{posargs} \
{[vars]tests_path}/unit
coverage report

[testenv:integration]
description = Run integration tests
deps =
-r {tox_root}/requirements-integration.txt
commands =
pytest -v \
-s \
--tb native \
--log-cli-level=INFO \
{posargs} \
{[vars]tests_path}/integration
Loading