diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 4cfb4b0..1aeeaae 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -2,29 +2,33 @@ name: Test Suite on: push: - branches: [develop, main] - pull_request: branches: [develop] + workflow_run: + workflows: ["Deploy Development"] + types: [completed] + workflow_dispatch: jobs: - backend-postgres: + pytest-backend: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - - name: Backend tests against Dev Postgres (kansho_test) + - name: pytest against Dev Postgres (kansho_test) run: | set -e APP_DIR="/home/lars/docker/kansho-dev" COMPOSE_FILE="docker-compose.dev-env.yml" - echo "backend tests always use Dev Postgres (${APP_DIR})" + echo "pytest uses Dev Postgres (${APP_DIR}); event=${{ github.event_name }}" cd "$APP_DIR" for i in $(seq 1 60); do - if docker compose -f "$COMPOSE_FILE" exec -T backend true 2>/dev/null; then - echo "backend ready (attempt $i)" + if [ -f "$APP_DIR/backend/pytest.ini" ] && docker compose -f "$COMPOSE_FILE" exec -T backend true 2>/dev/null; then + echo "backend and pytest.ini ready (attempt $i)" break fi if [ "$i" -eq 60 ]; then - echo "timeout waiting for backend" + echo "timeout waiting for Dev checkout/backend" docker compose -f "$COMPOSE_FILE" ps || true + ls -la "$APP_DIR/backend" || true exit 1 fi sleep 5 @@ -51,24 +55,32 @@ jobs: backend sh -lc ' set -e unset KANSHO_DB_PATH - for test in tests/test_*.py; do - echo "=== $test ===" - python "$test" - done + pip install -q -r requirements-dev.txt + python -m pytest tests -m "not slow" -ra -vv --tb=short ' - frontend-build: + smoke-dev: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - - name: Frontend production build on deploy checkout + - name: Non-destructive smoke against the running Dev app run: | set -e - EVENT_REF="${{ github.ref_name }}" - BASE_REF="${{ github.base_ref }}" - APP_DIR="/home/lars/docker/kansho" - if [ "$EVENT_REF" = "develop" ] || [ "$BASE_REF" = "develop" ]; then - APP_DIR="/home/lars/docker/kansho-dev" - fi + echo "live Dev API (kansho_dev), read-only health" + curl -sf http://localhost:8096/api/health + echo + curl -sf http://localhost:3096/api/health + echo + echo "LAN smoke OK. Public URL after TLS: https://dev.kansho.jinkendo.de" + + frontend-build: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + steps: + - name: Frontend production build on Dev checkout + run: | + set -e + APP_DIR="/home/lars/docker/kansho-dev" cd "$APP_DIR/frontend" npm ci npm run build diff --git a/backend/pytest.ini b/backend/pytest.ini new file mode 100644 index 0000000..923916f --- /dev/null +++ b/backend/pytest.ini @@ -0,0 +1,12 @@ +[pytest] +testpaths = tests +# Default python collector is disabled; tests/conftest.py collects each +# test_*.py as one case via main(). That keeps the existing scripts until +# they are split into native pytest functions. +python_files = __pytest_default_collector_disabled__.py +python_functions = test_* +addopts = -ra --tb=short +markers = + unit: no Postgres; safe on Windows SQLite checkouts + integration: API/store tests on kansho_test (never kansho_dev) + slow: long-running; omitted in the default Gitea command diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt new file mode 100644 index 0000000..7d433de --- /dev/null +++ b/backend/requirements-dev.txt @@ -0,0 +1,3 @@ +# CI / local test runner. Install on top of requirements.txt: +# pip install -r requirements.txt -r requirements-dev.txt +pytest>=8.0 diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..9a5dbed --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,89 @@ +"""pytest runner for the existing test_*.py scripts. + +Each file stays one case (`main()`) until it is split into native pytest +functions. Integration files reset `kansho_test` only; `kansho_dev` is never +the test target. +""" +from __future__ import annotations + +import os +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from tests.harness import ( # noqa: E402 + is_postgres_test_database, + prepare_test_env, + reset_postgres_schema, +) + +UNIT_MODULES = frozenset( + { + "test_sql_compat", + "test_local_backup", + "test_provenance", + "test_model_catalog", + "test_privacy_detect_eval", + "test_journal_shape", + "test_journal_body", + "test_writing_profile", + } +) + + +def pytest_configure(config: pytest.Config) -> None: + prepare_test_env() + + +def pytest_collect_file(file_path: Path, parent: pytest.Collector): + if file_path.suffix == ".py" and file_path.name.startswith("test_"): + return KanshoScriptModule.from_parent(parent, path=file_path) + return None + + +def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None: + for item in items: + stem = Path(str(item.path)).stem + marker = pytest.mark.unit if stem in UNIT_MODULES else pytest.mark.integration + item.add_marker(marker) + + +class KanshoScriptModule(pytest.Module): + def collect(self): + module = self.obj + + def test_main() -> None: + main = getattr(module, "main", None) + if main is None or not callable(main): + pytest.fail(f"{self.path.name} has no callable main()") + main() + + yield pytest.Function.from_parent(self, name="test_main", callobj=test_main) + + +@pytest.fixture(scope="module", autouse=True) +def _module_backend(request: pytest.FixtureRequest): + stem = Path(str(request.path)).stem + if stem in UNIT_MODULES: + saved = dict(os.environ) + try: + yield + finally: + os.environ.clear() + os.environ.update(saved) + return + if not is_postgres_test_database(): + pytest.skip( + "integration requires KANSHO_DB_BACKEND=postgres and DB_NAME ending _test " + "(Gitea: kansho_test on the Dev Postgres, never kansho_dev)" + ) + reset_postgres_schema() + from db import init_db + + init_db() + yield diff --git a/backend/tests/harness.py b/backend/tests/harness.py index d406b0d..9c8a11b 100644 --- a/backend/tests/harness.py +++ b/backend/tests/harness.py @@ -29,7 +29,24 @@ def _db_name() -> str: return (os.environ.get("DB_NAME") or "").strip() +def is_postgres_test_database() -> bool: + if _backend() not in {"postgres", "postgresql", "pg"}: + return False + name = _db_name().lower() + if not name or name in LIVE_DB_NAMES: + return False + return name.endswith("_test") + + +def prepare_test_env() -> None: + os.environ.setdefault("KANSHO_PROVIDER_KEY", "") + os.environ.setdefault("KANSHO_DETECT_PROVIDER_KEY", "") + os.environ.pop("KANSHO_DB_PATH", None) + + def _require_test_database() -> None: + if is_postgres_test_database(): + return backend = _backend() if backend not in {"postgres", "postgresql", "pg"}: raise SystemExit( @@ -44,11 +61,10 @@ def _require_test_database() -> None: "DB_NAME fehlt. Tests nutzen auf der Dev-Postgres die Datenbank kansho_test, " "nicht kansho_dev." ) - if name in LIVE_DB_NAMES or not name.endswith("_test"): - raise SystemExit( - f"DB_NAME={_db_name()!r} ist für Tests gesperrt. " - "Die Suite setzt das Schema zurück und darf daher nicht kansho oder kansho_dev treffen." - ) + raise SystemExit( + f"DB_NAME={_db_name()!r} ist für Tests gesperrt. " + "Die Suite setzt das Schema zurück und darf daher nicht kansho oder kansho_dev treffen." + ) def reset_postgres_schema() -> None: @@ -71,10 +87,8 @@ def reset_postgres_schema() -> None: def configure_test_engine() -> None: - """Call after sys.path includes backend/, before importing main/db.""" - os.environ.setdefault("KANSHO_PROVIDER_KEY", "") - os.environ.setdefault("KANSHO_DETECT_PROVIDER_KEY", "") - os.environ.pop("KANSHO_DB_PATH", None) + """Reset kansho_test. pytest uses the fixture in tests/conftest.py instead.""" + prepare_test_env() _require_test_database() try: reset_postgres_schema() diff --git a/backend/tests/test_architecture_correction.py b/backend/tests/test_architecture_correction.py index c03ca12..36ecadd 100644 --- a/backend/tests/test_architecture_correction.py +++ b/backend/tests/test_architecture_correction.py @@ -11,10 +11,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_debug_persist.py b/backend/tests/test_debug_persist.py index 3b6dd17..feff974 100644 --- a/backend/tests/test_debug_persist.py +++ b/backend/tests/test_debug_persist.py @@ -11,10 +11,6 @@ ROOT = Path(__file__).resolve().parents[1] REPO = ROOT.parent sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_MEDIA_ROOT"] = str(Path(tempfile.gettempdir()) / "kansho-debug-persist-media") os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_detect_contract_retry.py b/backend/tests/test_detect_contract_retry.py index 04d503a..0ac7eda 100644 --- a/backend/tests/test_detect_contract_retry.py +++ b/backend/tests/test_detect_contract_retry.py @@ -16,10 +16,6 @@ from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_dialogue_memory.py b/backend/tests/test_dialogue_memory.py index 805934c..ca660fd 100644 --- a/backend/tests/test_dialogue_memory.py +++ b/backend/tests/test_dialogue_memory.py @@ -9,10 +9,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - from fastapi.testclient import TestClient from main import app diff --git a/backend/tests/test_frame.py b/backend/tests/test_frame.py index 3a2c6c3..758654c 100644 --- a/backend/tests/test_frame.py +++ b/backend/tests/test_frame.py @@ -10,10 +10,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_PROVIDER_KEY"] = "" os.environ["KANSHO_DETECT_PROVIDER_KEY"] = "" diff --git a/backend/tests/test_identity_registry.py b/backend/tests/test_identity_registry.py index 2c0e2a3..f22657e 100644 --- a/backend/tests/test_identity_registry.py +++ b/backend/tests/test_identity_registry.py @@ -10,10 +10,6 @@ from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_budget.py b/backend/tests/test_journal_budget.py index 9f9f99f..d8eb749 100644 --- a/backend/tests/test_journal_budget.py +++ b/backend/tests/test_journal_budget.py @@ -11,10 +11,6 @@ from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_editorial.py b/backend/tests/test_journal_editorial.py index 39c153a..527e925 100644 --- a/backend/tests/test_journal_editorial.py +++ b/backend/tests/test_journal_editorial.py @@ -10,10 +10,6 @@ from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_eval.py b/backend/tests/test_journal_eval.py index 17d51dc..0ef0323 100644 --- a/backend/tests/test_journal_eval.py +++ b/backend/tests/test_journal_eval.py @@ -9,10 +9,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_generation_policy.py b/backend/tests/test_journal_generation_policy.py index 104d807..8076f5e 100644 --- a/backend/tests/test_journal_generation_policy.py +++ b/backend/tests/test_journal_generation_policy.py @@ -12,10 +12,6 @@ ROOT = Path(__file__).resolve().parents[1] FRONTEND = ROOT.parent / "frontend" sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_narration.py b/backend/tests/test_journal_narration.py index 08a65d5..426dd21 100644 --- a/backend/tests/test_journal_narration.py +++ b/backend/tests/test_journal_narration.py @@ -11,10 +11,6 @@ from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_opening.py b/backend/tests/test_journal_opening.py index 04aa316..e642d21 100644 --- a/backend/tests/test_journal_opening.py +++ b/backend/tests/test_journal_opening.py @@ -11,10 +11,6 @@ from datetime import date ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_MEDIA_ROOT"] = str(Path(tempfile.gettempdir()) / "kansho-opening-media") os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_style_context.py b/backend/tests/test_journal_style_context.py index 5f941a0..b4ebe90 100644 --- a/backend/tests/test_journal_style_context.py +++ b/backend/tests/test_journal_style_context.py @@ -10,10 +10,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_style_legacy_immutable.py b/backend/tests/test_journal_style_legacy_immutable.py index 57728b8..caf365f 100644 --- a/backend/tests/test_journal_style_legacy_immutable.py +++ b/backend/tests/test_journal_style_legacy_immutable.py @@ -10,10 +10,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_journal_trash.py b/backend/tests/test_journal_trash.py index b8556b5..97eaa96 100644 --- a/backend/tests/test_journal_trash.py +++ b/backend/tests/test_journal_trash.py @@ -9,10 +9,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_MEDIA_ROOT"] = str(Path(tempfile.gettempdir()) / "kansho-trash-media") os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_mvp_journal.py b/backend/tests/test_mvp_journal.py index 3dcb1c0..6e269ca 100644 --- a/backend/tests/test_mvp_journal.py +++ b/backend/tests/test_mvp_journal.py @@ -9,10 +9,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_MEDIA_ROOT"] = str(Path(tempfile.gettempdir()) / "kansho-mvp-journal-media") os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_privacy_detect.py b/backend/tests/test_privacy_detect.py index 80de8f4..3038658 100644 --- a/backend/tests/test_privacy_detect.py +++ b/backend/tests/test_privacy_detect.py @@ -15,10 +15,6 @@ from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_privacy_manifest.py b/backend/tests/test_privacy_manifest.py index f30b8c3..57907b8 100644 --- a/backend/tests/test_privacy_manifest.py +++ b/backend/tests/test_privacy_manifest.py @@ -13,10 +13,6 @@ from unittest.mock import patch ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_privacy_response_integrity.py b/backend/tests/test_privacy_response_integrity.py index fc92fe2..d37d1c7 100644 --- a/backend/tests/test_privacy_response_integrity.py +++ b/backend/tests/test_privacy_response_integrity.py @@ -11,10 +11,6 @@ ROOT = Path(__file__).resolve().parents[1] REPO = ROOT.parent sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_profile_governance.py b/backend/tests/test_profile_governance.py index 43b7ede..5b996fd 100644 --- a/backend/tests/test_profile_governance.py +++ b/backend/tests/test_profile_governance.py @@ -9,10 +9,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_profile_review.py b/backend/tests/test_profile_review.py index b8d9a08..0da7536 100644 --- a/backend/tests/test_profile_review.py +++ b/backend/tests/test_profile_review.py @@ -9,10 +9,6 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/backend/tests/test_profile_review_errors.py b/backend/tests/test_profile_review_errors.py index c15ddf4..cec3181 100644 --- a/backend/tests/test_profile_review_errors.py +++ b/backend/tests/test_profile_review_errors.py @@ -16,10 +16,6 @@ import httpx ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) -from tests.harness import configure_test_engine - -configure_test_engine() - os.environ["KANSHO_FAKE_PROVIDER"] = "1" os.environ["KANSHO_FAKE_DETECT"] = "1" diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index c5fc4d1..f9b1a83 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -20,6 +20,7 @@ Datenübernahme SQLite → Postgres: `backend/sqlite_to_postgres.py` (Probe auf | **Backend-Port** | 8005 | 8096 | | **PostgreSQL** | nur Compose-Netz, DB `kansho` | nur Compose-Netz, DB `kansho_dev` | | **Domain** | kansho.jinkendo.de | dev.kansho.jinkendo.de | +| **Öffentliche URL** | https://kansho.jinkendo.de | https://dev.kansho.jinkendo.de | | **Compose** | `docker-compose.yml` | `docker-compose.dev-env.yml` | Prod-Frontend ist **3006**, nicht 3005: auf dem Pi lauscht Bookstack bereits auf `0.0.0.0:3005`. Dev 3096/8096 und Prod-API 8005 waren frei. @@ -46,7 +47,7 @@ cp .env.example .env # Prod-DB_PASSWORD und Provider-Keys setzen ``` -Host-nginx: `nginx/kansho.conf` und `nginx/kansho-dev.conf` nach `/etc/nginx/sites-available/`, dann `nginx/certbot-setup.sh`. DNS für beide Hostnamen auf den Reverse-Proxy. +Host-nginx: `nginx/kansho.conf` und `nginx/kansho-dev.conf` nach `/etc/nginx/sites-available/`, dann `nginx/certbot-setup.sh`. DNS A/AAAA für `dev.kansho.jinkendo.de` und `kansho.jinkendo.de` auf den Reverse-Proxy. Bis TLS steht, bleibt der LAN-Zugriff über die Publish-Ports. Gitea: Actions aktivieren. Derselbe Pi-Runner wie die Schwesterprodukte (`ubuntu-latest`). @@ -60,7 +61,7 @@ Watchtower bleibt aus. |----------|---------|--------| | `deploy-dev.yml` | Push `develop` | Deploy nach `/home/lars/docker/kansho-dev`, Health `localhost:8096` | | `deploy-prod.yml` | Push `main` | Deploy nach `/home/lars/docker/kansho`, Health `localhost:8005` | -| `test.yml` | Push `develop`/`main`, PR `develop` | Backend-Tests auf der bestehenden Compose-Postgres in DB `kansho_test` (nicht Live-`kansho_dev`) plus Frontend-Build. Keine Live-Provider-Keys. | +| `test.yml` | nach erfolgreichem `Deploy Development` | pytest auf `kansho_test`, nicht-schreibender Smoke gegen die laufende Dev-API, Frontend-Build. Keine Live-Provider-Keys. | Prod nur über Merge `develop` → `main`, kein direkter Prod-Schreibzugriff. diff --git a/docs/architecture/technical/data_architecture.md b/docs/architecture/technical/data_architecture.md index f579c48..20bab70 100644 --- a/docs/architecture/technical/data_architecture.md +++ b/docs/architecture/technical/data_architecture.md @@ -84,7 +84,7 @@ Entsprechen den Interview-Leitfragen Phase F1/F4 und werden hier nicht vorab bea Additiv, ohne das Fachschema vorzuziehen: - Windows-Checkout: SQLite unter `backend/data/` (gitignoriert) für die lokale App ohne Docker. -- Test-Suite und Gitea: dieselbe Dev-Postgres-Instanz, Datenbank `kansho_test` neben `kansho_dev` (keine dritte Instanz). Nicht `kansho` / `kansho_dev`, weil die Suite das Schema zurücksetzt. +- Test-Suite und Gitea: pytest auf derselben Dev-Postgres-Instanz, Datenbank `kansho_test` neben `kansho_dev` (keine dritte Instanz). Nicht `kansho` / `kansho_dev`, weil die Suite das Schema zurücksetzt. Live-Smoke trifft nur `/api/health` der laufenden Dev-App. - Docker auf dem Pi: PostgreSQL 16, eigene Instanz je Umgebung, Medien im Compose-Volume `kansho-media` / `dev-kansho-media`. - Identity-Mappings bleiben Klasse A in der Local Trusted Zone (jetzt: Pi-Postgres, nicht Provider). - Kein Shared-Schema mit Mitai. diff --git a/docs/architecture/technical/environment_handover.md b/docs/architecture/technical/environment_handover.md index 683ad60..fd8d8fc 100644 --- a/docs/architecture/technical/environment_handover.md +++ b/docs/architecture/technical/environment_handover.md @@ -164,6 +164,8 @@ Offen, vor Compose festlegen — **beantwortet 2026-09-07:** **Additiv 2026-09-07 (Test-Engine):** Zwei Postgres-Instanzen bleiben Dev und Prod. Die Gitea-Suite läuft auf der Dev-Postgres in der Datenbank `kansho_test` (neben `kansho_dev`), nicht gegen SQLite und nicht gegen das persönliche `kansho_dev`-Journal. Die Suite setzt dort das Schema zurück. SQLite bleibt die Windows-App ohne Docker und das lokale Backup-Format. +**Additiv 2026-09-07 (pytest + öffentliche URLs):** Kanonischer Runner ist `python -m pytest tests` nach Dev-Deploy. `scripts/test-mvp.ps1` wrappt pytest lokal. Produkt-URLs nach Nginx/TLS: `https://dev.kansho.jinkendo.de` und `https://kansho.jinkendo.de`. + Empfohlene Reihenfolge in Welle 2: 1. Offene Host-Fragen beantworten und in `runtime_and_deploy.md` §1/§6 eintragen. diff --git a/docs/architecture/technical/mvp_implementation.md b/docs/architecture/technical/mvp_implementation.md index 617044c..89e3f04 100644 --- a/docs/architecture/technical/mvp_implementation.md +++ b/docs/architecture/technical/mvp_implementation.md @@ -37,7 +37,7 @@ Lokal, eine Instanz, ein Profil-Nutzer nach Setup: Proxy: Vite leitet `/api` an 8018. CORS erlaubt `localhost:5188`. -**Abweichung vom technischen Zielrahmen (lokal):** `product_frame_and_stack.md` nennt PostgreSQL. Der Slice auf dem Windows-Checkout bleibt SQLite (`5188`/`8018`). **Additiv 2026-09-07:** Docker/Server nutzt PostgreSQL 16 (`KANSHO_DB_BACKEND=postgres`). Stores behalten SQLite-förmiges SQL; `sql_compat.py` übersetzt zur Laufzeit. Nummerierte Dateien ab `backend/migrations/022_*.sql`. Siehe `runtime_and_deploy.md` und `docs/DEPLOYMENT.md`. **Additiv (Test-Suite):** API- und Store-Tests setzen `tests/harness.py` auf die Dev-Postgres, Datenbank `kansho_test` neben `kansho_dev` (keine dritte Instanz). Gitea `test.yml` trifft nicht das persönliche `kansho_dev`-Journal. SQLite-Testdateien sind abgelöst; `test_local_backup.py` prüft weiter das Windows-Backup-Format. +**Abweichung vom technischen Zielrahmen (lokal):** `product_frame_and_stack.md` nennt PostgreSQL. Der Slice auf dem Windows-Checkout bleibt SQLite (`5188`/`8018`). **Additiv 2026-09-07:** Docker/Server nutzt PostgreSQL 16 (`KANSHO_DB_BACKEND=postgres`). Stores behalten SQLite-förmiges SQL; `sql_compat.py` übersetzt zur Laufzeit. Nummerierte Dateien ab `backend/migrations/022_*.sql`. Siehe `runtime_and_deploy.md` und `docs/DEPLOYMENT.md`. **Additiv (Qualitätssystem 2026-09-07):** Kanonischer Backend-Runner ist pytest im Dev-Checkout nach erfolgreichem Deploy. `python -m pytest tests -m "not slow"` gegen `kansho_test`. `scripts/test-mvp.ps1` ist nur noch der lokale Wrapper (Unit ohne Postgres, Integration nur mit `DB_NAME=kansho_test`). SQLite-Testdateien sind abgelöst; `test_local_backup.py` prüft weiter das Windows-Backup-Format. Health: `GET /api/health`. @@ -223,11 +223,11 @@ Gateway-Verfahren und offener Security Layer: `privacy_gateway.md` §9.1–9.2 u | Erster Journal-Impuls | `backend/tests/test_journal_opening.py` | | Writing-Profile-Hülle / Initial Build | `backend/tests/test_profile_review.py`, `backend/tests/test_profile_governance.py` | | Markdown-Runde | `frontend/src/journal/document.test.js` | -| Lokaler Abnahmelauf | `scripts/test-mvp.ps1` | +| Lokaler Wrapper | `scripts/test-mvp.ps1` (pytest, kein kanonischer Runner) | Tests beweisen API-Verträge und Invarianten, nicht Dialogqualität. -**Additiv 2026-09-07:** Die Backend-Suite in Gitea läuft auf der Dev-Postgres in `kansho_test` (Harness `backend/tests/harness.py`). `scripts/test-mvp.ps1` führt Store-/API-Tests lokal nur mit `KANSHO_DB_BACKEND=postgres` und `DB_NAME=kansho_test` aus; Unit-Tests ohne Datenbank laufen weiter. Kanonisch bleibt der Pi-Lauf. +**Additiv 2026-09-07:** pytest ist der Backend-Runner (`backend/pytest.ini`). Gitea führt `python -m pytest tests -m "not slow"` nach erfolgreichem Dev-Deploy auf `kansho_test` aus. `scripts/test-mvp.ps1` wrappt denselben Aufruf lokal. Unit-Marker laufen ohne Postgres; Integration nie gegen `kansho_dev`. --- @@ -279,7 +279,7 @@ Additiv zum Slice, 2026-08-25. Kein Target-Model-Vorbau. Additiv zum Slice. Fachliche Abnahme: `../functional/mvp_freeze_candidate.md`. -- Lokaler Testeinstieg `scripts/test-mvp.ps1` (UTF-8, isolierte Temp-Daten, keine Live-Keys; Fake nur in den Suites, die ihn setzen). +- Lokaler Wrapper `scripts/test-mvp.ps1` (pytest; UTF-8, isolierte Temp-Daten, keine Live-Keys; Fake nur in den Suites, die ihn setzen). Kanonisch: Gitea pytest auf `kansho_test`. - Backup: `backend/local_backup.py`, Einstieg `scripts/backup-local.ps1`. SQLite-Backup-API, Medien, Manifest, keine Secrets. Restore bestätigt, prüft Checksummen, legt Sicherheitsbackup an, bricht bei geöffneter DB ab. - Papierkorb: `GET /spaces/{id}/entries`, `GET /spaces/{id}/trash`, `POST /entries/{id}/undelete`, `POST /entries/{id}/purge`. - Erster Impuls: `journal_opening.py`, derselbe Gateway-Pfad, `{{opening_hint}}`. **Additiv 2026-09-03:** Vorhaben nur aus dem user-Dialog des vorigen Kalendertags (`calendar_date` an `space_recent_sources`); Journal-Entries und ältere Recency sind kein Opening-Mandat. Der Opening-Call setzt `include_space_recency=False`. Tests: abgeschlossener Entry mit „nächsten Tag“ und Plan von vorgestern → `local_neutral`; „Morgen wollen wir …“ nur am Folgetag → Modellpfad. **Additiv 2026-09-07:** Prefix-Recency (5×400 Entries, 3×400 Source-Conversations) bleibt im *laufenden* Dialogzug; das ist kein Opening-Mandat. Ein Nachsatz mit Morgenuhrzeit darf den Folgetag nicht über den Entry-Anfang steuern. diff --git a/docs/architecture/technical/runtime_and_deploy.md b/docs/architecture/technical/runtime_and_deploy.md index bce5f18..8ddb64b 100644 --- a/docs/architecture/technical/runtime_and_deploy.md +++ b/docs/architecture/technical/runtime_and_deploy.md @@ -77,12 +77,14 @@ Mitai-Pipeline als Vorlage: ```text push develop → deploy-dev.yml → compose build/up → Healthcheck -push/PR nach Tests → test.yml (pytest, Frontend-Build) +successful Dev deploy → test.yml (pytest on kansho_test, Live-Smoke /api/health, Frontend-Build) merge in main → deploy-prod.yml ``` **Status: entschieden.** Workflows liegen unter `.gitea/workflows/` (`deploy-dev.yml`, `deploy-prod.yml`, `test.yml`). Muster analog Kairo: `git reset --hard`, `build --no-cache`, Health `GET /api/health`. +**Additiv 2026-09-07 (Qualitätssystem):** Backend-Runner ist pytest (`backend/pytest.ini`, `backend/tests/conftest.py`). Gitea startet die Suite erst nach erfolgreichem Dev-Deploy (`workflow_run` auf `Deploy Development`), nicht parallel zum Image-Build. Destruktive Tests bleiben auf `kansho_test`. Zusätzlich ein nicht-schreibender Smoke gegen die laufende Dev-API (`kansho_dev`, nur `/api/health`). Öffentliche URLs nach Host-Nginx/TLS: `https://dev.kansho.jinkendo.de` und `https://kansho.jinkendo.de`. LAN-Ports 3096/8096 und 3006/8005 bleiben die Compose-Publish-Ziele. + Kanshō-Repo liegt auf Gitea (`Lars/Kansho`). HTTPS-Push ist eingerichtet. Der Pi-Runner (`ubuntu-latest`) ist derselbe wie bei Mitai/Shinkan/Kairo. ## 5. Was Deploy nicht übernimmt @@ -103,7 +105,8 @@ Kanshō-Repo liegt auf Gitea (`Lars/Kansho`). HTTPS-Push ist eingerichtet. Der P | Postgres | eigene Instanz je Umgebung (`kansho` / `kansho_dev`), kein Shared-Schema | entschieden 2026-09-07 | | Gitea Workflows | `.gitea/workflows/` analog Kairo | entschieden 2026-09-07 | | Auto-Rollback | nein | verworfen | -| Dual-Backend | SQLite nur Windows-App ohne Docker; Server Dev+Prod und Test-Suite PostgreSQL. Tests auf der Dev-Instanz in DB `kansho_test` neben `kansho_dev` (keine dritte Instanz) | entschieden 2026-09-07, Tests nachgezogen | +| Dual-Backend | SQLite nur Windows-App ohne Docker; Server Dev+Prod PostgreSQL. pytest auf der Dev-Instanz in DB `kansho_test` neben `kansho_dev` (keine dritte Instanz) | entschieden 2026-09-07, pytest 2026-09-07 | +| Öffentliche URLs | `https://dev.kansho.jinkendo.de` / `https://kansho.jinkendo.de` (Host-Nginx + Let's Encrypt; Einrichtung parallel) | entschieden 2026-09-07 | | Urlaubs-Laptop ohne Docker/SQLite | Phase A auf dem Heimrechner restored | dokumentiert 2026-09-07 | | Transfer vor Postgres | SQLite-Restore auf dem Heimrechner zuerst | entschieden als Reihenfolge | diff --git a/scripts/test-mvp.ps1 b/scripts/test-mvp.ps1 index 9f2c206..4c4632d 100644 --- a/scripts/test-mvp.ps1 +++ b/scripts/test-mvp.ps1 @@ -80,39 +80,29 @@ function Test-PostgresSuite { return ($backend -in @("postgres", "postgresql", "pg")) -and $name.EndsWith("_test") } -Write-Host "Kansho local MVP tests" +Write-Host "Kansho local tests (pytest wrapper)" Write-Host "Python: $py" Write-Host "Isolation: $($iso.Base)" Write-Host "No live provider/detect calls. Production DB/media untouched." -Write-Host "Fail-closed stays testable; suites that need a fake provider set it themselves." +Write-Host "Canonical backend suite: Gitea pytest on kansho_test after Dev deploy." $runBackend = Test-PostgresSuite if ($runBackend) { - Write-Host "Backend tests: Postgres $($env:DB_NAME)" + Write-Host "Backend pytest: Postgres $($env:DB_NAME)" } else { - Write-Host "Backend Postgres-Suite: SKIP (KANSHO_DB_BACKEND=postgres und DB_NAME=kansho_test auf der Dev-Postgres; kanonisch Gitea auf dem Pi). Reine Unit-Tests ohne DB laufen trotzdem." + Write-Host "Backend pytest: unit only (set KANSHO_DB_BACKEND=postgres and DB_NAME=kansho_test for integration)." } try { - $backendTests = Get-ChildItem -Path (Join-Path $root "backend\tests") -Filter "test_*.py" | Sort-Object Name - if (-not $backendTests) { - throw "no backend/tests/test_*.py files found" - } - - foreach ($test in $backendTests) { - $needsPg = $false + Invoke-Step -Name "backend pytest" -Action { + Push-Location (Join-Path $root "backend") try { - $needsPg = [bool](Select-String -Path $test.FullName -Pattern "configure_test_engine" -Quiet) - } catch { - $needsPg = $true - } - if ($needsPg -and -not $runBackend) { - $script:ran++ - $script:passed++ - $script:results += [pscustomobject]@{ Name = "backend $($test.Name)"; Status = "SKIP" } - continue - } - Invoke-Step -Name "backend $($test.Name)" -Action { - & $py $test.FullName + if ($runBackend) { + & $py -m pytest tests -m "not slow" -ra --tb=short + } else { + & $py -m pytest tests -m "unit and not slow" -ra --tb=short + } + } finally { + Pop-Location } } @@ -161,5 +151,5 @@ Write-Host ("{0} von {1} Schritten erfolgreich." -f $passed, $ran) if ($failed) { exit 1 } -Write-Host "MVP local acceptance: OK" +Write-Host "local tests: OK" exit 0