diff --git a/tests/mattermost-lts/ops.py b/tests/mattermost-lts/ops.py new file mode 100644 index 0000000..1f33c85 --- /dev/null +++ b/tests/mattermost-lts/ops.py @@ -0,0 +1,46 @@ +"""mattermost-lts — pre-op seed hooks (Phase 1e HC3). The orchestrator runs these BEFORE each op; the +matching test_.py asserts post-op (assertion-only). The marker is a dedicated `ci_marker` row in +postgres (mattermost's own schema migrations don't touch it), written via psql in the `db` service +(POSTGRES_USER=mattermost, POSTGRES_DB=mattermost, password /run/secrets/postgres_password). The +recipe's `db` service is backupbot-labelled (pg_dump pre-hook + the whole PGDATA dir at +backup.path=/var/lib/postgresql/data/), so the marker rides the backup→restore cycle.""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import lifecycle # noqa: E402 + + +def _psql(domain, sql): + cmd = ( + "PGPASSWORD=$(cat /run/secrets/postgres_password) " + f'psql -U mattermost -d mattermost -tAc "{sql}"' + ) + return lifecycle.exec_in_app(domain, ["sh", "-c", cmd], service="db").strip() + + +def _seed(domain, value): + _psql( + domain, + "CREATE TABLE IF NOT EXISTS ci_marker(v text); DELETE FROM ci_marker; " + f"INSERT INTO ci_marker VALUES('{value}');", + ) + assert _psql(domain, "SELECT v FROM ci_marker;") == value + + +def pre_upgrade(domain, meta): + _seed(domain, "upgrade-survives") + + +def pre_backup(domain, meta): + _seed(domain, "original") + + +def pre_restore(domain, meta): + # drop the marker table (diverge from the backup) so a successful restore is observable + _psql(domain, "DROP TABLE ci_marker;") + assert _psql(domain, "SELECT to_regclass('public.ci_marker');") in ( + "", + "NULL", + ), "drop did not take" diff --git a/tests/mattermost-lts/test_backup.py b/tests/mattermost-lts/test_backup.py new file mode 100644 index 0000000..30b0716 --- /dev/null +++ b/tests/mattermost-lts/test_backup.py @@ -0,0 +1,23 @@ +"""mattermost-lts — BACKUP overlay (Phase 1e HC3): assertion-only + additive. +ops.pre_backup wrote "original" into postgres before the backup op (the recipe's db pre-hook dumps +the DB + archives the PGDATA dir). This overlay ADDS: the seeded row is intact at backup time.""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import lifecycle # noqa: E402 + + +def _psql(domain, sql): + cmd = ( + "PGPASSWORD=$(cat /run/secrets/postgres_password) " + f'psql -U mattermost -d mattermost -tAc "{sql}"' + ) + return lifecycle.exec_in_app(domain, ["sh", "-c", cmd], service="db").strip() + + +def test_backup_captures_state(live_app): + assert ( + _psql(live_app, "SELECT v FROM ci_marker;") == "original" + ), "the seeded postgres state was not present at backup time" diff --git a/tests/mattermost-lts/test_install.py b/tests/mattermost-lts/test_install.py new file mode 100644 index 0000000..fd73f9b --- /dev/null +++ b/tests/mattermost-lts/test_install.py @@ -0,0 +1,20 @@ +"""mattermost-lts — INSTALL overlay (Phase 1d, DG4): override + extend-by-composition. +Reuses the generic "really serving" assertion, then ADDS a recipe-specific check: mattermost's REST +liveness endpoint /api/v4/system/ping answers 200 over real HTTPS through Traefik (proves the app + +its DB-backed API are up, not just a proxy 200). Assertion-only on the shared deployment.""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import generic, lifecycle # noqa: E402 + + +def test_serving_and_api(live_app, meta): + # extend-by-composition: reuse the generic "really serving" assertion first ... + generic.assert_serving(live_app, meta) + # ... then the recipe-specific assertion: the mattermost REST liveness endpoint answers 200. + status = lifecycle.http_get(live_app, "/api/v4/system/ping") + assert status == 200, ( + f"expected 200 from {live_app}/api/v4/system/ping, got {status}" + ) diff --git a/tests/mattermost-lts/test_restore.py b/tests/mattermost-lts/test_restore.py new file mode 100644 index 0000000..c537a59 --- /dev/null +++ b/tests/mattermost-lts/test_restore.py @@ -0,0 +1,24 @@ +"""mattermost-lts — RESTORE overlay (Phase 1e HC3): data-integrity, assertion-only + additive. +ops.pre_restore dropped the marker table (diverge); the orchestrator restored once (the recipe's db +backup is the whole PGDATA dir). This overlay ADDS: the restored DB matches the pre-mutation +"original". Read via psql in the `db` service.""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import lifecycle # noqa: E402 + + +def _psql(domain, sql): + cmd = ( + "PGPASSWORD=$(cat /run/secrets/postgres_password) " + f'psql -U mattermost -d mattermost -tAc "{sql}"' + ) + return lifecycle.exec_in_app(domain, ["sh", "-c", cmd], service="db").strip() + + +def test_restore_returns_state(live_app): + assert ( + _psql(live_app, "SELECT v FROM ci_marker;") == "original" + ), "restore did not return the pre-mutation postgres state" diff --git a/tests/mattermost-lts/test_upgrade.py b/tests/mattermost-lts/test_upgrade.py new file mode 100644 index 0000000..ab26c93 --- /dev/null +++ b/tests/mattermost-lts/test_upgrade.py @@ -0,0 +1,23 @@ +"""mattermost-lts — UPGRADE overlay (Phase 1e HC3): data-continuity, assertion-only + additive. +ops.pre_upgrade wrote a postgres marker row before the upgrade; this overlay ADDS: the postgres data +survived the chaos crossover. Read via psql in the `db` service.""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import lifecycle # noqa: E402 + + +def _psql(domain, sql): + cmd = ( + "PGPASSWORD=$(cat /run/secrets/postgres_password) " + f'psql -U mattermost -d mattermost -tAc "{sql}"' + ) + return lifecycle.exec_in_app(domain, ["sh", "-c", cmd], service="db").strip() + + +def test_upgrade_preserves_data(live_app): + assert ( + _psql(live_app, "SELECT v FROM ci_marker;") == "upgrade-survives" + ), "postgres data did not survive the upgrade"