Files
meetingnotes/scripts/smoke_test_api.py
T
2026-09-10 00:25:09 +02:00

84 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Check the CI app's unauthenticated API without changing saved preferences."""
import json
import os
import signal
import socket
import subprocess
import sys
import tempfile
import time
import urllib.error
import urllib.request
from pathlib import Path
# Launch from the Mac's internal volume; the runner checkout is on removable storage.
cache = Path.home() / "Library/Caches/meetingnotes-ci"
cache.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="smoke-", dir=cache) as directory:
staged = Path(directory) / "Meetingnotes.app"
subprocess.run(["ditto", sys.argv[1], str(staged)], check=True)
app = staged / "Contents/MacOS/Meetingnotes"
with socket.socket() as listener:
listener.bind(("127.0.0.1", 0))
port = listener.getsockname()[1]
with tempfile.NamedTemporaryFile() as log:
process = subprocess.Popen(
["open", "-n", "-W", "-g", "-a", str(staged), "--stdout", log.name, "--stderr", log.name, "--args", "-ApplePersistenceIgnoreState", "YES", "-muteDeckAPIEnabled", "YES", "-muteDeckAPIPort", str(port), "-hasCompletedOnboarding", "YES", "-hasAcceptedTerms", "YES", "-SUEnableAutomaticChecks", "NO"],
stdout=log, stderr=subprocess.STDOUT,
)
def app_pid():
# The unique staged path identifies only this test's app instance.
listing = subprocess.check_output(["ps", "-axo", "pid=,command="], text=True)
for line in listing.splitlines():
fields = line.strip().split(None, 1)
if len(fields) == 2 and fields[1].startswith(str(app) + " "):
return int(fields[0])
return None
try:
for attempt in range(30):
if process.poll() is not None:
raise RuntimeError("CI app exited before the API became ready")
try:
with urllib.request.urlopen(f"http://127.0.0.1:{port}/api/info", timeout=2) as response:
info = json.load(response)
break
except (urllib.error.URLError, TimeoutError):
time.sleep(1)
else:
raise RuntimeError("CI API did not become ready")
assert info["name"] == "MeetingDebrief", "Unexpected API identity"
try:
urllib.request.urlopen(f"http://127.0.0.1:{port}/api/recording/status", timeout=2)
except urllib.error.HTTPError as error:
assert error.code == 401, f"Unexpected status: {error.code}"
else:
raise RuntimeError("Recording status allowed an unauthenticated request")
print("Local API readiness and authentication checks passed")
except Exception:
pid = app_pid()
if pid is not None:
sample = subprocess.run(["sample", str(pid), "1", "1"], capture_output=True, text=True, timeout=10)
print(sample.stdout[:14000], file=sys.stderr)
log.seek(0)
print(log.read().decode(errors="replace")[-8000:], file=sys.stderr)
raise
finally:
pid = app_pid()
if pid is not None:
try:
os.kill(pid, signal.SIGTERM)
except ProcessLookupError:
pass
try:
process.wait(timeout=10)
except subprocess.TimeoutExpired:
if app_pid() == pid and pid is not None:
try:
os.kill(pid, signal.SIGKILL)
except ProcessLookupError:
pass
process.terminate()
process.wait(timeout=10)