from pathlib import Path import shutil import subprocess import yaml class ComposeLoader(yaml.SafeLoader): pass def _compose_override(loader, node): if isinstance(node, yaml.MappingNode): return loader.construct_mapping(node) return loader.construct_sequence(node) ComposeLoader.add_constructor("!override", _compose_override) def test_psd_overlay_uses_generated_workspace_for_default_and_named_commands(): root = Path(__file__).resolve().parents[2] compose = yaml.load( (root / "deploy/compose.psd-local.yaml.example").read_text(), Loader=ComposeLoader, ) core = compose["services"]["core"] assert core["environment"]["THT_CONFIG"] == "/app/harness/config/tht.yaml" assert core["environment"]["THT_SECRETS_FILE"] == "/run/secrets/thothii.secrets" for name in ( "THT_DB_NAME", "THT_DWH_REST_URL", "THT_VEC_REST_URL", "THT_VEC_WRITE_REST_URL", "THT_OLLAMA_URL", "THT_PROFILE", "PI_PROVIDER", "PI_MODEL", "PI_THINKING", ): assert name in core["environment"] def target(volume): if isinstance(volume, dict): return volume["target"] parts = volume.rsplit(":", 2) return parts[-2] if parts[-1] in {"ro", "rw"} else parts[-1] targets = {target(volume) for volume in core["volumes"]} assert "/app/harness/config/tht.yaml" in targets assert "/app/harness/workspaces/psd.yaml" not in targets assert "/data/workspaces/psd/config/tht.yaml" not in targets assert "/data" in targets assert "/home/thoth/.pi" in targets assert "/home/thoth/.pi/agent/models.json" in targets assert "/home/thoth/.pi/agent/settings.json" in targets assert "/data/evidence" in targets assert "/run/secrets/thothii.secrets" in targets assert set(compose["volumes"]) == {"thoth_data", "thoth_pi_config"} assert core["networks"]["default"]["aliases"] == ["core", "thothii-core"] frontend = compose["services"]["frontend"] assert frontend["ports"] == ["127.0.0.1:8099:8080"] assert frontend["build"]["args"] == { "VITE_BASE": "/", "VITE_BACKEND_URL": "/api", } assert frontend["networks"] == { "default": {"aliases": ["frontend", "thothii-frontend"]} } assert compose["networks"]["default"] == { "external": True, "name": "thothii_default", } def test_core_image_prepares_the_writable_pi_profile_before_mounting_config_files(): root = Path(__file__).resolve().parents[2] dockerfile = (root / "docker/core.Dockerfile").read_text() assert "mkdir -p /home/thoth/.pi/agent" in dockerfile assert "chown -R thoth:thoth /home/thoth/.pi" in dockerfile assert ( "cp --remove-destination /app/harness/workspaces/local.yaml " "/app/harness/config/tht.yaml" in dockerfile ) assert "ln -sf /app/harness/workspaces/local.yaml /app/harness/config/tht.yaml" not in dockerfile assert ( "ln -sfn /app/harness/config/tht.yaml /app/harness/workspaces/psd.yaml" in dockerfile ) def test_psd_bootstrap_materializes_the_base_compose_env_file(tmp_path): source_root = Path(__file__).resolve().parents[2] root = tmp_path / "ThothII" (root / "scripts").mkdir(parents=True) (root / "deploy/workspaces").mkdir(parents=True) shutil.copy( source_root / "scripts/bootstrap-local-psd-docker-config.sh", root / "scripts/bootstrap-local-psd-docker-config.sh", ) shutil.copy( source_root / "deploy/compose.psd-local.yaml.example", root / "deploy/compose.psd-local.yaml.example", ) shutil.copy( source_root / "deploy/workspaces/psd.yaml.example", root / "deploy/workspaces/psd.yaml.example", ) source_env = tmp_path / "source.env" source_env.write_text("\n".join([ "THT_DB_NAME=postgres", "THT_DWH_REST_URL=https://dwh.invalid/", "THT_VEC_REST_URL=https://vec.invalid/read/", "THT_VEC_WRITE_REST_URL=https://vec.invalid/write/", "THT_DWH_API_KEY=dwh", "THT_VEC_API_KEY=reader", "THT_VEC_WRITE_API_KEY=writer", "", ])) workspace = tmp_path / "workspace" workspace.mkdir() auth = tmp_path / "auth.json" auth.write_text('{"zai":{"key":"model"}}') subprocess.run( [ "sh", str(root / "scripts/bootstrap-local-psd-docker-config.sh"), str(source_env), str(workspace), str(auth), ], check=True, capture_output=True, text=True, ) assert (root / "deploy/thothii.env").is_file() assert "THT_SECRETS_FILE=./deploy/secrets/thothii.secrets" in ( root / ".env" ).read_text()