"""AI layout awareness and path expansion.""" from __future__ import annotations from pathlib import Path import pytest import thicket.layout as layout from thicket.pipeline_core import IngestConfig @pytest.fixture def fake_ai(tmp_path: Path, monkeypatch): (tmp_path / "corpus" / "cold").mkdir(parents=True) (tmp_path / "corpus" / "hot").mkdir(parents=True) (tmp_path / "corpus" / "books").mkdir(parents=True) monkeypatch.setattr(layout, "AI_ROOT", tmp_path) return tmp_path def test_layout_detected_with_corpus_taxonomy(fake_ai): profile = layout.detect_layout() assert profile is not None assert profile.corpus_cold == fake_ai / "corpus" / "cold" assert profile.corpus_hot == fake_ai / "corpus" / "hot" assert profile.books_present is True assert profile.archive == fake_ai / "corpus" / "archive" def test_layout_absent_without_corpus(tmp_path, monkeypatch): monkeypatch.setattr(layout, "AI_ROOT", tmp_path) assert layout.detect_layout() is None def test_defaults_follow_the_layout(fake_ai): assert layout.default_input() == fake_ai / "corpus" / "cold" assert layout.default_vault() == fake_ai / "corpus" / "hot" assert layout.default_archive(Path("/anywhere/in")) == fake_ai / "corpus" / "archive" def test_defaults_fall_home_without_layout(tmp_path, monkeypatch): monkeypatch.setattr(layout, "AI_ROOT", tmp_path) assert layout.default_input() == Path.home() / "Downloads" / "Raw_Books_And_Papers" assert layout.default_vault() == Path.home() / "Documents" / "ObsidianVault" assert layout.default_archive(Path("/data/in")) == Path("/data/ingested-archive") def test_archive_falls_back_to_sibling_without_layout(tmp_path, monkeypatch): monkeypatch.setattr(layout, "AI_ROOT", tmp_path) assert layout.default_archive(Path("/data/in")) == Path("/data/ingested-archive") def test_expand_path_tilde_and_env(monkeypatch): monkeypatch.setenv("THICKET_TEST_DIR", "/tmp/thicket-expanded") assert layout.expand_path("~/books") == Path.home() / "books" assert layout.expand_path("$THICKET_TEST_DIR/x") == Path("/tmp/thicket-expanded/x") assert layout.expand_path("/plain/path") == Path("/plain/path") def test_ingest_config_expands_paths(monkeypatch): monkeypatch.setenv("THICKET_TEST_DIR", "/tmp/thicket-expanded") config = IngestConfig(input_dir="~/in", vault_dir="$THICKET_TEST_DIR/vault", archive_dir="$THICKET_TEST_DIR/archive", use_qdrant=False) assert config.input_dir == Path.home() / "in" assert config.vault_dir == Path("/tmp/thicket-expanded/vault") assert config.archive_dir == Path("/tmp/thicket-expanded/archive") def test_backend_profiles_fill_gaps_env_wins(tmp_path, monkeypatch): import os import thicket.layout as layout backends = tmp_path / "backends" backends.mkdir() (backends / "thicket.env").write_text( "# profile\n" "PGHOST=db.local\n" "PGUSER='fileuser'\n" "MINIO_ENDPOINT=\"127.0.0.1:9000\"\n" "NOT_A_LINE\n", encoding="utf-8") monkeypatch.setattr(layout, "AI_ROOT", tmp_path) for key in ("PGHOST", "PGUSER", "MINIO_ENDPOINT"): monkeypatch.delenv(key, raising=False) applied = layout.apply_backend_profiles() assert applied == {"PGHOST": "db.local", "PGUSER": "fileuser", "MINIO_ENDPOINT": "127.0.0.1:9000"} assert os.environ["PGUSER"] == "fileuser" monkeypatch.setenv("PGUSER", "shelluser") monkeypatch.delenv("PGHOST", raising=False) applied = layout.apply_backend_profiles() assert "PGUSER" not in applied # environment wins assert applied["PGHOST"] == "db.local" # gaps still filled assert os.environ["PGUSER"] == "shelluser" def test_backend_profiles_absent_file_is_noop(tmp_path, monkeypatch): import thicket.layout as layout monkeypatch.setattr(layout, "AI_ROOT", tmp_path) assert layout.apply_backend_profiles() == {}