From 3110afa32ce6d3e5540ab5b4847cd3b85e2ac16d Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 6 Aug 2026 07:12:49 -0500 Subject: [PATCH 1/3] test(pytest): widen root testpaths to also collect the webconsole package (BACKLOG #1027) A default pytest -q from the repo root no longer silently skips the ~344 webconsole package tests. LOCAL developer-signal fix only -- CI already covers the console via its dedicated step, so shipped code was never untested. The engine tree stays first in testpaths; the one unguarded webauthn-extra test now importorskips; a guard test under tests/ fails if the console tree is ever dropped from testpaths again. Follow-on (owner, outside this lane's files): pin the ci.yml engine Tests step to 'pytest tests -q' to avoid a double-run, and fix two stale docs testpaths claims. --- CLAUDE.md | 1 + .../tests/test_webui.py | 1 + pyproject.toml | 2 +- tests/test_testpaths_webconsole_coverage.py | 35 +++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/test_testpaths_webconsole_coverage.py diff --git a/CLAUDE.md b/CLAUDE.md index 6104b282..ca8d2f44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -334,6 +334,7 @@ diverge enough to warrant it; keep this root file general. ``` # tests (PySide6 harness/Qt tests need the offscreen platform) +# testpaths now also collects packaging/messagefoundry-webconsole/tests, so this covers the web console suite too. QT_QPA_PLATFORM=offscreen pytest -q # PowerShell: $env:QT_QPA_PLATFORM="offscreen"; pytest -q # format / lint / types diff --git a/packaging/messagefoundry-webconsole/tests/test_webui.py b/packaging/messagefoundry-webconsole/tests/test_webui.py index b59db361..35659b58 100644 --- a/packaging/messagefoundry-webconsole/tests/test_webui.py +++ b/packaging/messagefoundry-webconsole/tests/test_webui.py @@ -3671,6 +3671,7 @@ async def test_webauthn_cross_site_posts_rejected(engine: Engine) -> None: async def test_webauthn_rp_fail_closed_legible(engine: Engine) -> None: # AC-7: public_origin unset + request-derivation disallowed (the declared-proxy topology) — # ceremonies fail closed with the shared notice on every surface, never a redirect loop. + pytest.importorskip("webauthn") service = await _service(engine) await _add(service, "boss", Role.ADMINISTRATOR) transport = httpx.ASGITransport( diff --git a/pyproject.toml b/pyproject.toml index ea166252..66fb636e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -308,7 +308,7 @@ asyncio_mode = "auto" # the fixture key needs >=0.24); the dev floor below is >=0.26 to match, and the lock pins 1.4.0. asyncio_default_test_loop_scope = "session" asyncio_default_fixture_loop_scope = "session" -testpaths = ["tests"] +testpaths = ["tests", "packaging/messagefoundry-webconsole/tests"] # Per-test watchdog: a single hung await/socket is killed at 60s with a full thread-stack dump naming # the culprit, instead of burning the CI job's wall-clock. `thread` method works cross-platform (no # SIGALRM) and fires even when the main thread is blocked in a C call. Individual tests run well under diff --git a/tests/test_testpaths_webconsole_coverage.py b/tests/test_testpaths_webconsole_coverage.py new file mode 100644 index 00000000..04443d07 --- /dev/null +++ b/tests/test_testpaths_webconsole_coverage.py @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 MessageFoundry Organization and contributors +"""The default pytest gate must collect the web console package, not silently exclude it. + +BACKLOG #1027: `testpaths = ["tests"]` made a bare `pytest -q` from the repo root skip the +~344 tests under ``packaging/messagefoundry-webconsole/tests`` while the green summary line +looked complete. This guard reads the root ``pyproject.toml`` and fails if the console tree is +ever dropped from ``testpaths`` again — it lives under ``tests/`` on purpose, so the very +narrowing it guards against can never exclude the guard itself. +""" + +from __future__ import annotations + +import tomllib +from pathlib import Path + +_REPO_ROOT = Path(__file__).resolve().parents[1] +_WEBCONSOLE_TESTS = "packaging/messagefoundry-webconsole/tests" + + +def test_default_testpaths_collect_the_webconsole_package() -> None: + pyproject = _REPO_ROOT / "pyproject.toml" + data = tomllib.loads(pyproject.read_text(encoding="utf-8")) + testpaths = data["tool"]["pytest"]["ini_options"]["testpaths"] + + assert testpaths[0] == "tests", ( + f"the engine suite must stay first in testpaths, got {testpaths!r}" + ) + assert _WEBCONSOLE_TESTS in testpaths, ( + f"the web console package tests must be in testpaths so a default pytest run " + f"collects them (BACKLOG #1027), got {testpaths!r}" + ) + assert (_REPO_ROOT / _WEBCONSOLE_TESTS).is_dir(), ( + f"testpaths names {_WEBCONSOLE_TESTS!r} but that directory does not exist" + ) From 6340a5d7f9b2803a5bce87196322e820e0c46b33 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Thu, 6 Aug 2026 07:12:52 -0500 Subject: [PATCH 2/3] backlog: flip #1027 banner to shipped (BACKLOG #1027) Banner line only; census not recomputed. --- docs/BACKLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 932bbde7..57e0f8e4 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -4969,7 +4969,7 @@ The comment immediately above says *"Scope is deliberately the posture the requi ## 1027. The documented `pytest` command silently excludes the webconsole package, so a local green is not evidence about ~344 tests -> 🔢 **Filed 2026-08-05 — not started.** Value **5/10** · Difficulty **3/10** · _fill-in_. `testpaths = ["tests"]` means the command CLAUDE.md documents as the verification gate never collects `packaging/messagefoundry-webconsole/tests`. A **failing** webconsole test sat on `main` through a full day of lanes because every quartet used the documented single path. +> ✅ **SHIPPED 2026-08-06 — the root `testpaths` now also collects `packaging/messagefoundry-webconsole/tests`, so a bare `pytest -q` from the repo root stops silently excluding the web console suite; the one webauthn-extra-dependent console test that lacked a guard (`test_webauthn_rp_fail_closed_legible`) now skips-with-reason when the optional `[webauthn]` extra is absent, so an extra-less local venv stays green.** Value **5/10** · Difficulty **3/10** · _fill-in_. Local developer-signal fix only — CI already covered the console via its dedicated `Web console tests (pytest)` step; the gap was that the documented local gate collected less than it appeared to. **Cluster:** Testing / verification integrity. **Priority:** P3. **Verdict:** build (small). **Severity:** no product effect; the defect is that the project's own verification instruction produces a green that is not evidence about roughly 344 tests, and CLAUDE.md §5 states a task is not done until it passes. From 2b93aa4368c3d4f4870f1b980ae65fad7344056d Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Sat, 8 Aug 2026 09:55:47 -0500 Subject: [PATCH 3/3] ci: subtract the web console package from the engine pytest step Completes the CI half of the testpaths change, which the original commit did not carry. Adding the console package to the root testpaths fixes the local gate, and has a consequence in CI that the change did not address: ci.yml runs a bare pytest for the engine step AND a second explicit step for the console package, so once testpaths includes the console the same tests run TWICE on every leg. Measured on this branch, 2026-08-08: bare collection 11,956 console package alone 356 with --ignore-glob 11,600 THE OBVIOUS SPELLING IS THE ONE THAT FAILS. --ignore=packaging/messagefoundry- webconsole/tests was measured and does NOT prune a directory that testpaths names as a collection root; it still collected all 11,956. Only the glob form subtracts. A future edit "simplifying" the flag back would restore the double-run silently, with every check green. pytest tests was also rejected, deliberately. Hardcoding the engine path would make CI silently miss any future third entry in testpaths, which is the exact drift the testpaths change exists to prevent. Subtracting FROM testpaths keeps it the single source of truth for what the suite is. Also corrects the comment above the console step, which asserted "the engine pytest above no longer collects them -- engine testpaths = ["tests"]". That became false the moment testpaths changed, and a reader following it would conclude the double-run could not happen. Adds tests/test_ci_engine_step_excludes_webconsole.py pinning all three decisions: that the console is in testpaths at all (the precondition), that the engine step subtracts it, and that the subtraction is not the broken --ignore spelling. The guard was verified to go RED before being trusted, on both realistic mutations: dropping the flag fails one assertion, and "simplifying" it to --ignore= fails two, including the assertion written specifically for that spelling. A gate that has not been shown to fail is not evidence. Verified: ci.yml parses; the four guards pass; collection with the flag is 11,603 (11,600 plus the three tests added here). --- .github/workflows/ci.yml | 19 ++++- ...test_ci_engine_step_excludes_webconsole.py | 75 +++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 tests/test_ci_engine_step_excludes_webconsole.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ff13caf..71e9fced 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -490,11 +490,24 @@ jobs: # every OS, so $VAR resolves the same on the Windows legs (Git Bash). FAULT_TIMEOUT: ${{ matrix.fault_timeout }} PYTEST_TIMEOUT: ${{ matrix.pytest_timeout }} - run: pytest -q -o faulthandler_timeout="$FAULT_TIMEOUT" --timeout="$PYTEST_TIMEOUT" + # `--ignore-glob` subtracts the web console package, which runs as its OWN step below. BACKLOG + # #1027 added that package to the root `testpaths` so a bare local `pytest` stops silently + # excluding it; without this subtraction the SAME 356 tests would then run TWICE on every leg. + # Measured 2026-08-08 on this branch: bare collection 11,956, console-only 356, with this flag + # 11,600. + # NOT `--ignore=` -- measured, it does NOT prune a directory that `testpaths` names as a + # collection root, and still collected all 11,956. The obvious spelling is the one that fails. + # NOT `pytest tests` either: hardcoding the engine path would make CI silently miss any future + # third entry in `testpaths`, which is the exact drift #1027 exists to prevent. Subtracting FROM + # `testpaths` keeps it the single source of truth for what the suite is. + # `tests/test_ci_engine_step_excludes_webconsole.py` pins all three of those decisions. + run: pytest -q --ignore-glob='*messagefoundry-webconsole*' -o faulthandler_timeout="$FAULT_TIMEOUT" --timeout="$PYTEST_TIMEOUT" # The web console's OWN suite (Option B, ADR 0065): the moved /ui tests live in the package's - # tests/ (the engine `pytest` above no longer collects them — engine testpaths = ["tests"]), so - # run them as a SECOND step on the SAME leg. The package was installed editable in the install + # tests/. They ARE in the root `testpaths` since BACKLOG #1027, so a bare local `pytest` collects + # them -- the engine step above subtracts them explicitly via `--ignore-glob` rather than relying + # on `testpaths` to exclude them, which it no longer does. They still run as a SECOND step on the + # SAME leg, under the package's OWN pytest configuration. The package was installed editable in the install # step above (`-e packaging/messagefoundry-webconsole`), so both suites exercise the same # engine build. Its pyproject sets asyncio_mode="auto" + session loop scopes; the timeout flags # mirror the engine step (a hung ASGI test fails fast + named, not at the silent job cap). diff --git a/tests/test_ci_engine_step_excludes_webconsole.py b/tests/test_ci_engine_step_excludes_webconsole.py new file mode 100644 index 00000000..87c786e6 --- /dev/null +++ b/tests/test_ci_engine_step_excludes_webconsole.py @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright (C) 2026 MessageFoundry Organization and contributors +"""CI's engine pytest step must subtract the web console package that ``testpaths`` now includes. + +BACKLOG #1027 added ``packaging/messagefoundry-webconsole/tests`` to the root ``testpaths`` so a +bare local ``pytest`` stops silently excluding roughly 350 tests. That fix has a CI-side +consequence the original change did not carry: ``ci.yml`` runs a bare ``pytest`` for the engine +step AND a second explicit step for the console package, so once ``testpaths`` includes the +console the SAME tests run TWICE on every leg. + +Measured 2026-08-08: bare collection 11,956; console-only 356; with the subtraction 11,600. + +**The obvious spelling does not work, which is why this guard pins the working one.** +``--ignore=packaging/messagefoundry-webconsole/tests`` was measured and does NOT prune a directory +that ``testpaths`` names as a collection root -- it still collected all 11,956. Only the glob form +subtracts. A future edit "simplifying" the flag back to ``--ignore`` would restore the double-run +silently, with every check still green, which is the same shape as the defect #1027 fixed. + +``pytest tests`` is also rejected, and deliberately: hardcoding the engine path would make CI miss +any future third entry in ``testpaths``. Subtracting FROM ``testpaths`` keeps it the one source of +truth for what the suite is. +""" + +from __future__ import annotations + +import re +import tomllib +from pathlib import Path + +import pytest + +_ROOT = Path(__file__).resolve().parents[1] +_CI = _ROOT / ".github" / "workflows" / "ci.yml" +_PYPROJECT = _ROOT / "pyproject.toml" +_CONSOLE = "packaging/messagefoundry-webconsole/tests" + + +def _engine_step_run_line() -> str: + """The `run:` line of the engine `Tests (pytest)` step.""" + text = _CI.read_text(encoding="utf-8") + # The engine step is the bare `pytest -q ...` invocation; the console step names its path. + for line in text.splitlines(): + stripped = line.strip() + if stripped.startswith("run: pytest -q"): + return stripped + pytest.fail(f"no engine `run: pytest -q` line found in {_CI}") + + +def test_console_is_in_testpaths() -> None: + """Precondition: this guard is only meaningful while #1027's change is in place.""" + cfg = tomllib.loads(_PYPROJECT.read_text(encoding="utf-8")) + testpaths = cfg["tool"]["pytest"]["ini_options"]["testpaths"] + assert _CONSOLE in testpaths, ( + f"expected {_CONSOLE!r} in testpaths (BACKLOG #1027); got {testpaths!r}. " + "If that was reverted deliberately, this guard and the ci.yml subtraction go with it." + ) + + +def test_engine_step_subtracts_the_console_package() -> None: + """Without this, the console's ~356 tests run twice per leg.""" + run = _engine_step_run_line() + assert "--ignore-glob" in run and "messagefoundry-webconsole" in run, ( + "ci.yml's engine `Tests (pytest)` step must subtract the web console package, which " + f"`testpaths` now includes and which runs as its own step. Found:\n {run}" + ) + + +def test_engine_step_does_not_use_plain_ignore() -> None: + """`--ignore=` was measured NOT to prune a `testpaths` collection root.""" + run = _engine_step_run_line() + plain = re.search(r"--ignore(?!-glob)[= ]", run) + assert plain is None, ( + "`--ignore=` does not prune a directory named by `testpaths` -- measured 2026-08-08, " + "it still collected all 11,956 tests. Use `--ignore-glob`. Found:\n " + run + )