diff --git a/.github/workflows/linters_and_tests.yml b/.github/workflows/linters_and_tests.yml index 70b2136..2c38ff9 100644 --- a/.github/workflows/linters_and_tests.yml +++ b/.github/workflows/linters_and_tests.yml @@ -1,4 +1,4 @@ -name: linters and unit tests +name: linters and tests on: pull_request: diff --git a/pyproject.toml b/pyproject.toml index dc1234a..ed24f74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,6 +85,10 @@ select = ["I"] [tool.pytest.ini_options] asyncio_mode = "auto" +filterwarnings = [ + "ignore:'cgi' is deprecated and slated for removal in Python 3\\.13:DeprecationWarning:webob\\.compat", + "ignore:datetime\\.datetime\\.utcnow\\(\\) is deprecated and scheduled for removal in a future version\\.:DeprecationWarning:pyattest\\.testutils\\.factories\\.attestation\\.apple", +] [tool.pytest_env] APP_ATTEST_QA = "true" diff --git a/scripts/appattest_qa.py b/scripts/app_attest_qa/app_attest_qa.py similarity index 100% rename from scripts/appattest_qa.py rename to scripts/app_attest_qa/app_attest_qa.py diff --git a/scripts/generate_qa_app_attest_certificate.py b/scripts/app_attest_qa/generate_qa_app_attest_certificate.py similarity index 100% rename from scripts/generate_qa_app_attest_certificate.py rename to scripts/app_attest_qa/generate_qa_app_attest_certificate.py diff --git a/src/tests/e2e/test_app_attest_e2e.py b/src/tests/e2e/test_app_attest_e2e.py new file mode 100644 index 0000000..7eb4c15 --- /dev/null +++ b/src/tests/e2e/test_app_attest_e2e.py @@ -0,0 +1,101 @@ +import os +import shutil +import socket +import subprocess +import sys +import time +from pathlib import Path + +import httpx +import pytest + + +def _pick_free_port() -> int: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(("127.0.0.1", 0)) + return sock.getsockname()[1] + + +def _wait_for_liveness(base_url: str, timeout_s: int = 30) -> None: + deadline = time.time() + timeout_s + url = f"{base_url}/health/liveness" + while time.time() < deadline: + try: + response = httpx.get(url, timeout=2) + if response.status_code == 200: + return + except Exception: + pass + time.sleep(0.5) + raise AssertionError("MLPA did not become live in time.") + + +def test_app_attest_qa_flow_e2e(): + cert_dir = Path("src/tests/certs") + cert_dir_exists = cert_dir.exists() + existing_files = {p.name for p in cert_dir.iterdir()} if cert_dir_exists else set() + cert_dir.mkdir(parents=True, exist_ok=True) + + env = os.environ.copy() + env["APP_ATTEST_QA"] = "true" + env["APP_ATTEST_QA_CERT_DIR"] = str(cert_dir) + env["MLPA_DEBUG"] = "false" + + port = _pick_free_port() + base_url = f"http://127.0.0.1:{port}" + env["PORT"] = str(port) + + server = None + try: + subprocess.run( + [ + sys.executable, + "scripts/app_attest_qa/generate_qa_app_attest_certificate.py", + ], + check=True, + env=env, + ) + + server = subprocess.Popen( + ["mlpa"], + env=env, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + _wait_for_liveness(base_url, timeout_s=60) + + subprocess.run( + [ + sys.executable, + "scripts/app_attest_qa/app_attest_qa.py", + "register", + ], + check=True, + env=env, + ) + + subprocess.run( + [ + sys.executable, + "scripts/app_attest_qa/app_attest_qa.py", + "completion", + ], + check=True, + env=env, + ) + finally: + if server is not None: + server.terminate() + try: + server.wait(timeout=10) + except subprocess.TimeoutExpired: + server.kill() + if cert_dir.exists(): + for path in cert_dir.iterdir(): + if path.name not in existing_files: + if path.is_dir(): + shutil.rmtree(path) + else: + path.unlink() + if not cert_dir_exists: + shutil.rmtree(cert_dir)