From 6d49b59cec3ea6903a9c2a2d30c8336c864715e7 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Thu, 1 Oct 2026 06:46:36 +0000 Subject: [PATCH] CLI db binding fix + true two-process satellite setup e2e MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cmd_satellite constructs its own Kanta bound to the global db object on the resolved path — the lifecycle module's import-time binding would follow the wrong directory if CWD changed after import. The new e2e runs 'paskia satellite ' as a real subprocess against the live upstream: own database created, link key material identical on both sides, pushed domain stored as remote. --- paskia/__main__.py | 7 ++++- tests/test_satellite_e2e.py | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/paskia/__main__.py b/paskia/__main__.py index 375904f..b9c90eb 100644 --- a/paskia/__main__.py +++ b/paskia/__main__.py @@ -9,6 +9,7 @@ from fastapi_vue import env, server, teleport from fastapi_vue.logging import setup_logging from kanta import Kanta +import paskia.db.operations as ops_db from paskia.db import legacy from paskia.db.bootstrap import bootstrap, log_reset_link from paskia.db.paths import db_file_path @@ -202,10 +203,14 @@ def cmd_satellite(args: argparse.Namespace) -> None: linked domains. """ from paskia import satellite # noqa: PLC0415 (heavy deps: fastapi, websockets) - from paskia.db.lifecycle import kanta # noqa: PLC0415 (binds the db path) db_path = db_file_path() fresh = not db_path.exists() + # A Kanta of our own, bound to the global db object so db operations + # (register_uplink's commits) write through it — like lifecycle's, but + # constructed here so the path follows the current directory. + kanta = Kanta(str(db_path), ops_db._db) + ops_db._db._store = kanta result = {} if fresh: # Satellite-only bootstrap: default config (localhost domain) and diff --git a/tests/test_satellite_e2e.py b/tests/test_satellite_e2e.py index 1391a2e..db75f8c 100644 --- a/tests/test_satellite_e2e.py +++ b/tests/test_satellite_e2e.py @@ -20,6 +20,8 @@ a mocked forward_request. import asyncio import os import socket +import subprocess +import sys import time from datetime import UTC, datetime, timedelta from types import SimpleNamespace @@ -30,10 +32,12 @@ import msgspec.structs import pytest import pytest_asyncio import uvicorn +from kanta import Kanta import paskia.db.operations as ops_db from paskia import domains, satellite from paskia.db.structs import ( + DB, Config, Credential, DomainConfig, @@ -226,6 +230,54 @@ async def test_live_registration_rejections(live_server): assert not live_server.db.satellite_links +@pytest.mark.asyncio +async def test_cli_satellite_command(live_server, tmp_path): + """The whole satellite-only setup, two processes: the CLI registers + against the live upstream (creating its own database on the way) and + stores the link and the pushed domains — 'paskia' then serves them.""" + live = live_server + ops_db.create_domain( + REMOTE_DOMAIN, + DomainConfig( + rp_name="Example", + origins={ + f"**.{REMOTE_DOMAIN}": True, + f"auth.{REMOTE_DOMAIN}": OriginEntry(auth_host=True), + }, + ), + ) + offer_url = sync.create_offer([REMOTE_DOMAIN], live.url) + proc = await asyncio.to_thread( + subprocess.run, + [sys.executable, "-m", "paskia", "satellite", offer_url], + cwd=tmp_path, + capture_output=True, + text=True, + env={k: v for k, v in os.environ.items() if not k.startswith("PASKIA_")}, + timeout=60, + ) + assert proc.returncode == 0, proc.stderr + assert REMOTE_DOMAIN in proc.stdout + + # The satellite's own database holds the uplink and the pushed domain, + # and both sides hold the same key material for the link. + sat_db_path = tmp_path / "paskia.kantadb" + kanta = Kanta(str(sat_db_path), DB()) + await kanta.open(readonly=True) + try: + sat_domains = dict(kanta.data.config.domains) + uplinks = dict(kanta.data.uplinks) + finally: + await kanta.close() + domain = sat_domains[REMOTE_DOMAIN] + assert domain.remote is not None + assert domain.rp_name == "Example" + ((link_id, uplink),) = uplinks.items() + assert uplink.url == live.url + assert live.db.satellite_links[link_id].key == uplink.key + assert live.db.satellite_links[link_id].rp_ids == [REMOTE_DOMAIN] + + # ------------------------------------------------------------------------- # Established link: sync behavior # -------------------------------------------------------------------------