diff --git a/Makefile b/Makefile index cdab51b..c2a2dfa 100644 --- a/Makefile +++ b/Makefile @@ -116,14 +116,14 @@ cov: ensure-hatch .PHONY: e2e-local e2e-local: ensure-hatch - @echo "Running e2e tests against local Azurite (E2E_BASE_URL=http://localhost:7071)..." - @echo "E2E_EXPECTED_VERSION defaults to the locally installed candidate version (hatch version); override to certify a different deployed build." - @E2E_BASE_URL=http://localhost:7071 E2E_EXPECTED_VERSION=$${E2E_EXPECTED_VERSION:-$$($(HATCH) version)} $(HATCH) run e2e-azure + @echo "Local-only HTTP smoke; App Insights may skip. This cannot create a certification record." + @echo "E2E_EXPECTED_VERSION defaults to the locally installed candidate version (hatch version)." + @FAIL_ON_SKIP=0 E2E_BASE_URL=$${E2E_BASE_URL:-http://localhost:7071} E2E_EXPECTED_VERSION=$${E2E_EXPECTED_VERSION:-$$($(HATCH) version)} $(HATCH) run e2e-local .PHONY: e2e-azure e2e-azure: ensure-hatch @echo "Running e2e tests against Azure (E2E_BASE_URL and E2E_EXPECTED_VERSION must both be set)..." - @$(HATCH) run e2e-azure + @FAIL_ON_SKIP=1 $(HATCH) run e2e-azure .PHONY: build build: ensure-hatch diff --git a/pyproject.toml b/pyproject.toml index c90aeec..b7de4a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,6 +97,7 @@ lint = "hatch run style && hatch run typecheck" test = "pytest -v tests" cov = "pytest --cov-report=term-missing --cov-report=html --cov-report=xml tests/" e2e-azure = "pytest -v -m e2e tests/e2e/ --no-cov" +e2e-local = "pytest -v -m e2e tests/e2e/ --no-cov" docs = "mkdocs serve" precommit = "pre-commit run --all-files" precommit-install = "pre-commit install" diff --git a/tests/test_ci_skip_policy.py b/tests/test_ci_skip_policy.py index 5e3df57..8e9eeb3 100644 --- a/tests/test_ci_skip_policy.py +++ b/tests/test_ci_skip_policy.py @@ -2,7 +2,10 @@ from __future__ import annotations +import os from pathlib import Path +import subprocess +import sys from typing import Any import pytest @@ -38,3 +41,55 @@ def test_azure_certification_wires_app_insights() -> None: _, env = _lane("e2e-azure.yml", "-m e2e") assert env.get("APPINSIGHTS_NAME") assert env.get("E2E_RESOURCE_GROUP") + + +def test_missing_app_insights_fails_certification_without_network() -> None: + env = { + **os.environ, + "FAIL_ON_SKIP": "1", + "APPINSIGHTS_NAME": "", + "E2E_BASE_URL": "http://127.0.0.1:1", + } + result = subprocess.run( + [ + sys.executable, + "-m", + "pytest", + "--no-cov", + "-m", + "e2e", + "tests/e2e/test_logging_e2e.py::test_structured_log_appears_in_app_insights", + ], + env=env, + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 1, result.stdout + result.stderr + assert "APPINSIGHTS_NAME not set" in result.stdout + assert "FAIL_ON_SKIP=1" in result.stdout + + +def test_missing_upstream_module_fails_canary_instead_of_passing_skip() -> None: + script = """ +import importlib.abc +import sys +import pytest + +class MissingModule(importlib.abc.MetaPathFinder): + def find_spec(self, fullname, path=None, target=None): + if fullname == 'opentelemetry.sdk._logs': + raise ModuleNotFoundError('simulated removed upstream module', name=fullname) + +sys.meta_path.insert(0, MissingModule()) +sys.exit(pytest.main(['--no-cov', 'tests/test_otel_contract.py'])) +""" + result = subprocess.run( + [sys.executable, "-c", script], + env={**os.environ, "FAIL_ON_SKIP": "1"}, + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode != 0, result.stdout + result.stderr + assert "simulated removed upstream module" in result.stdout diff --git a/tests/test_makefile_contract.py b/tests/test_makefile_contract.py index bd9ac2b..e4ee924 100644 --- a/tests/test_makefile_contract.py +++ b/tests/test_makefile_contract.py @@ -63,3 +63,12 @@ def test_check_type_checks_once_and_never_upgrades_pip(tmp_path: Path) -> None: scripts = tomllib.loads((ROOT / "pyproject.toml").read_text())["tool"]["hatch"]["envs"] assert scripts["default"]["scripts"]["lint"].count("hatch run typecheck") == 1 assert "--upgrade" not in (ROOT / "Makefile").read_text() + + +def test_local_e2e_explicitly_uses_noncertifying_entrypoint(tmp_path: Path) -> None: + dry = _make(tmp_path, "-n", "e2e-local").stdout + assert "run e2e-local" in dry + assert "FAIL_ON_SKIP=0" in dry + scripts = tomllib.loads((ROOT / "pyproject.toml").read_text())["tool"]["hatch"]["envs"] + assert "e2e-local" in scripts["default"]["scripts"] + assert "certification.json" not in dry