151 lines
7.0 KiB
Python
151 lines
7.0 KiB
Python
"""Tests for the dependency ordering that replaces the special casing of authentik."""
|
|
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
import click
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import alakazam
|
|
from alakazam import get_apps, group_apps_by_dependency, sort_apps_by_dependency
|
|
|
|
|
|
def instance(**apps):
|
|
"""Builds an instance configuration, values are the 'dependency' lists."""
|
|
return {name: {"app_domain": f"{name}.a.org", "dependency": list(dependencies)} for name, dependencies in apps.items()}
|
|
|
|
|
|
class TestSortAppsByDependency:
|
|
def test_a_dependency_comes_first(self):
|
|
config = instance(nextcloud=["authentik"], authentik=[])
|
|
assert sort_apps_by_dependency(["nextcloud", "authentik"], config) == ["authentik", "nextcloud"]
|
|
|
|
def test_order_is_independent_of_the_input_order(self):
|
|
config = instance(nextcloud=["authentik"], authentik=[])
|
|
assert sort_apps_by_dependency(["authentik", "nextcloud"], config) == ["authentik", "nextcloud"]
|
|
|
|
def test_several_apps_share_one_dependency(self):
|
|
config = instance(nextcloud=["authentik"], wekan=["authentik"], authentik=[])
|
|
ordered = sort_apps_by_dependency(["nextcloud", "wekan", "authentik"], config)
|
|
assert ordered.index("authentik") == 0
|
|
|
|
def test_transitive_dependencies(self):
|
|
config = instance(c=["b"], b=["a"], a=[])
|
|
assert sort_apps_by_dependency(["c", "b", "a"], config) == ["a", "b", "c"]
|
|
|
|
def test_unrelated_apps_keep_their_configuration_order(self):
|
|
config = instance(traefik=[], authentik=[], nextcloud=[])
|
|
apps = ["traefik", "authentik", "nextcloud"]
|
|
assert sort_apps_by_dependency(apps, config) == apps
|
|
|
|
def test_apps_without_a_dependency_key(self):
|
|
config = {"authentik": {"app_domain": "login.a.org"}, "nextcloud": {"app_domain": "cloud.a.org"}}
|
|
assert sort_apps_by_dependency(["nextcloud", "authentik"], config) == ["nextcloud", "authentik"]
|
|
|
|
def test_every_app_is_returned_exactly_once(self):
|
|
config = instance(d=["a", "b"], c=["a"], b=["a"], a=[])
|
|
ordered = sort_apps_by_dependency(["d", "c", "b", "a"], config)
|
|
assert sorted(ordered) == ["a", "b", "c", "d"]
|
|
assert len(ordered) == 4
|
|
|
|
def test_empty_selection(self):
|
|
assert sort_apps_by_dependency([], instance(a=[])) == []
|
|
|
|
|
|
class TestGroupAppsByDependency:
|
|
"""Levels express what a flat order cannot: which apps may proceed without waiting."""
|
|
|
|
def test_apps_sharing_one_dependency_end_up_in_one_level(self):
|
|
config = instance(zammad=["authentik"], kimai=["authentik"], hedgedoc=["authentik"],
|
|
authentik=[], traefik=[])
|
|
levels = group_apps_by_dependency(["zammad", "kimai", "hedgedoc", "authentik", "traefik"], config)
|
|
assert levels == [["authentik", "traefik"], ["zammad", "kimai", "hedgedoc"]]
|
|
|
|
def test_unrelated_apps_are_all_in_the_first_level(self):
|
|
config = instance(traefik=[], authentik=[], nextcloud=[])
|
|
assert group_apps_by_dependency(["traefik", "authentik", "nextcloud"], config) == [
|
|
["traefik", "authentik", "nextcloud"]
|
|
]
|
|
|
|
def test_a_transitive_chain_yields_one_level_each(self):
|
|
config = instance(c=["b"], b=["a"], a=[])
|
|
assert group_apps_by_dependency(["c", "b", "a"], config) == [["a"], ["b"], ["c"]]
|
|
|
|
def test_the_deepest_dependency_decides_the_level(self):
|
|
"""An app waits for all of its dependencies, so it lands behind the latest of them."""
|
|
config = instance(d=["a", "c"], c=["b"], b=["a"], a=[])
|
|
assert group_apps_by_dependency(["d", "c", "b", "a"], config) == [["a"], ["b"], ["c"], ["d"]]
|
|
|
|
def test_configuration_order_is_kept_within_a_level(self):
|
|
config = instance(wekan=["authentik"], nextcloud=["authentik"], authentik=[])
|
|
assert group_apps_by_dependency(["wekan", "nextcloud", "authentik"], config)[1] == ["wekan", "nextcloud"]
|
|
|
|
def test_a_dependency_outside_the_selection_does_not_add_a_level(self):
|
|
config = instance(nextcloud=["authentik"], authentik=[])
|
|
assert group_apps_by_dependency(["nextcloud"], config) == [["nextcloud"]]
|
|
|
|
def test_empty_selection(self):
|
|
assert group_apps_by_dependency([], instance(a=[])) == []
|
|
|
|
def test_every_app_appears_exactly_once(self):
|
|
config = instance(d=["a", "b"], c=["a"], b=["a"], a=[])
|
|
levels = group_apps_by_dependency(["d", "c", "b", "a"], config)
|
|
assert sorted(app for level in levels for app in level) == ["a", "b", "c", "d"]
|
|
|
|
def test_a_cycle_is_rejected(self):
|
|
config = instance(nextcloud=["authentik"], authentik=["nextcloud"])
|
|
with pytest.raises(click.ClickException):
|
|
group_apps_by_dependency(["nextcloud", "authentik"], config)
|
|
|
|
|
|
class TestCycles:
|
|
def test_a_cycle_is_rejected(self):
|
|
config = instance(nextcloud=["authentik"], authentik=["nextcloud"])
|
|
with pytest.raises(click.ClickException) as excinfo:
|
|
sort_apps_by_dependency(["nextcloud", "authentik"], config)
|
|
assert excinfo.value.exit_code != 0
|
|
|
|
def test_the_cycle_is_named(self):
|
|
config = instance(nextcloud=["authentik"], authentik=["nextcloud"])
|
|
with pytest.raises(click.ClickException) as excinfo:
|
|
sort_apps_by_dependency(["nextcloud", "authentik"], config)
|
|
assert "nextcloud -> authentik -> nextcloud" in excinfo.value.message
|
|
|
|
def test_a_longer_cycle_is_named(self):
|
|
config = instance(a=["c"], b=["a"], c=["b"])
|
|
with pytest.raises(click.ClickException) as excinfo:
|
|
sort_apps_by_dependency(["a", "b", "c"], config)
|
|
assert "a -> c -> b -> a" in excinfo.value.message
|
|
|
|
def test_an_app_depending_on_itself(self):
|
|
config = instance(a=["a"])
|
|
with pytest.raises(click.ClickException) as excinfo:
|
|
sort_apps_by_dependency(["a"], config)
|
|
assert "a -> a" in excinfo.value.message
|
|
|
|
|
|
class TestDependenciesOutsideTheSelection:
|
|
def test_a_dependency_not_selected_is_skipped(self, caplog):
|
|
"""Filtering a run down to a few recipes must not pull in anything else."""
|
|
config = instance(nextcloud=["authentik"], authentik=[])
|
|
with caplog.at_level(logging.WARNING):
|
|
assert sort_apps_by_dependency(["nextcloud"], config) == ["nextcloud"]
|
|
assert caplog.text == ""
|
|
|
|
class TestGetAppsOrdering:
|
|
"""The order has to reach the commands, not just the helper."""
|
|
|
|
def test_get_apps_returns_dependencies_first(self, monkeypatch):
|
|
config = instance(nextcloud=["authentik"], authentik=[])
|
|
monkeypatch.setattr(alakazam, "INSTANCE_CONFIGS", {"a.org": config})
|
|
assert [app for app, _ in get_apps()["a.org"]] == ["authentik", "nextcloud"]
|
|
|
|
def test_an_explicit_recipe_filter_is_ordered_too(self, monkeypatch):
|
|
config = instance(nextcloud=["authentik"], authentik=[])
|
|
monkeypatch.setattr(alakazam, "INSTANCE_CONFIGS", {"a.org": config})
|
|
apps = get_apps(("nextcloud", "authentik"))["a.org"]
|
|
assert [app for app, _ in apps] == ["authentik", "nextcloud"]
|