#!/usr/bin/env python3 from __future__ import annotations import json import re from pathlib import Path ROOT = Path(__file__).resolve().parents[1] errors: list[str] = [] checks: list[str] = [] def require(condition: bool, message: str) -> None: (checks if condition else errors).append(message) app_gradle = (ROOT / "app/build.gradle.kts").read_text() manifest = (ROOT / "app/src/main/AndroidManifest.xml").read_text() network = (ROOT / "app/src/main/res/xml/network_security_config.xml").read_text() source = "\n".join(p.read_text(errors="replace") for p in (ROOT / "app/src/main").rglob("*.*") if p.is_file()) api = (ROOT / "app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt").read_text() require('applicationId = "de.drazz.boehmitools.training"' in app_gradle, "Application ID stimmt") require('minSdk = 26' in app_gradle, "Minimum SDK 26") require('compileSdk = 35' in app_gradle, "Compile SDK 35") require('android:usesCleartextTraffic="false"' in manifest, "Release-Manifest verbietet Klartext") require('cleartextTrafficPermitted="false"' in network, "Release Network Security verbietet Klartext") require('https://tools.d-razz.de/' in source, "Standardserver ist gesetzt") require('RipMe' in (ROOT / 'app/src/main/res/values/strings.xml').read_text(), "Sichtbarer App-Name RipMe") require('Planeditor' not in source and 'EditorScreen' not in source, "Planeditor aus App entfernt") require('WebView' not in source, "Keine WebView") require('api.openai.com' not in source.lower(), "Keine direkte OpenAI-URL") require('sk-' not in source, "Kein erkennbarer OpenAI-Key") require('Credentials.basic' in api, "Basic Auth über OkHttp") for route in ['tracker/session', '/analysis', '/analysis/status']: require(route in api, f"API-Route vorhanden: {route}") require('trainingsplan/api' not in api, "Keine Planeditor-API im Android-Client") tracker_screen = (ROOT / 'app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt').read_text() require('ScrollableTabRow' not in tracker_screen, "Hauptnavigation ist nicht horizontal scrollbar") require(tracker_screen.count('horizontalScroll(') == 2, "Nur Wochen und Tage scrollen horizontal") require('record.obj("result")' in tracker_screen, "Analyseergebnis liest Serverfeld result") require('last_seen_version_code' in source, "WhatsNew-Version wird gespeichert") require('BuildConfig.VERSION_NAME' in source, "Versionsanzeige ist vorhanden") fixtures = ROOT / "app/src/test/resources/samples" for name in ["phase-1", "calihoss-newbie-8-wochen"]: wrapper = json.loads((fixtures / f"plans/{name}.json").read_text()) config = wrapper["config"] require(config.get("schema_version") == 3, f"{name}: schema_version 3") require(config.get("contract_version") == 2, f"{name}: contract_version 2") groups = {"days": [], "rotations": [], "placements": [], "steps": []} for day in config.get("days", []): groups["days"].append(day.get("id")) for rotation in day.get("rotations", []): groups["rotations"].append(rotation.get("id")) for exercise in rotation.get("exercises", []): groups["placements"].append(exercise.get("id")) require(bool(re.fullmatch(r"d\d+-r\d+-e\d+", exercise.get("legacy_id", ""))), f"{name}: Legacy-ID gültig") require(bool(exercise.get("result_schema")), f"{name}: Übungsschema vorhanden") for stage in config.get("stages", {}).values(): for step in stage.get("steps", []): groups["steps"].append(step.get("id")) require(bool(step.get("result_schema")), f"{name}: Stufenschema vorhanden") for label, ids in groups.items(): require(all(ids), f"{name}: {label} vollständig") require(len(ids) == len(set(ids)), f"{name}: {label} eindeutig") for name in ["phase-1", "calihoss-newbie-8-wochen"]: tracker = json.loads((fixtures / f"sessions/{name}.json").read_text()) require(1 <= int(tracker.get("version", 0)) <= 7, f"{name}: Legacy-Tracker unterstützt") require(bool(tracker.get("sessions")), f"{name}: Sessions vorhanden") print(f"PASS: {len(checks)} Prüfungen") if errors: print(f"FAIL: {len(errors)} Prüfungen") for error in errors: print(f"- {error}") raise SystemExit(1)