refactor(harness): renaming prodotto tht (Onda -1)
Thoth (tht) è il prodotto, PSD è il cliente. Nessun riferimento al contesto
clinico nel codice.
Rinomine:
- comando+package nsp→tht (dir nsp/→tht/, 46 import, pyproject entry point)
- gate nsp-gate.js→tht-gate.js (+ rewrite token, relayIfNspFails→relayIfThtFails)
- workspace chirone.{example,test}.yaml→tht.{example,test}.yaml (generici)
- env THOTH_→THT_ (19 var) + NSP_ stragglers (NSP_HARNESS_ROOT, NSP_SESSION)
- commenti/docstring chirone/psdwp3/policlinico neutralizzati ('the reference
implementation', 'the DWH')
Aggiunto [tool.setuptools.packages.find] include=['tht*'] (necessario: l'auto-
discovery rompeva con tht/ + workspaces/ come top-level multipli).
.env operatore aggiornato in-place (prefissi THT_, valori preservati, gitignored).
Verifica: pytest 109 passed, npm test 14 pass, tht phase meta --json OK, zero
residui nsp/THOTH_/NSP_/chirone nel package.
This commit is contained in:
@@ -7,14 +7,14 @@ from dotenv import load_dotenv
|
||||
from sqlalchemy import create_engine
|
||||
from testcontainers.postgres import PostgresContainer
|
||||
|
||||
# Permetti `pytest` lanciato da qualsiasi directory di trovare il package `nsp`
|
||||
# Permetti `pytest` lanciato da qualsiasi directory di trovare il package `tht`
|
||||
# (installato in modalità editable nella venv, ma utile anche senza attivazione).
|
||||
_ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_ROOT))
|
||||
|
||||
# Directory sessions/ risolta relativamente alla root harness (per i test che creano sessioni)
|
||||
os.environ.setdefault("NSP_HARNESS_ROOT", str(_ROOT))
|
||||
os.environ.setdefault("THT_HARNESS_ROOT", str(_ROOT))
|
||||
|
||||
# L2 tests need the remote credentials from harness/.env (gitignored). Loaded once,
|
||||
# autouse, before any test. L0/L1 tests don't read these vars; missing .env only
|
||||
@@ -23,10 +23,10 @@ load_dotenv(_ROOT / ".env")
|
||||
|
||||
# L2 connection prerequisites (spec Testing Strategy). If any is missing/empty, L2
|
||||
# tests are SKIPPED (not failed) so the default run (pytest = L0+L1) stays green.
|
||||
# NOTE: THOTH_SSL_CA is NOT required -- the DWH endpoint presents a public cert
|
||||
# (*.policlinicosandonato.it signed by GoDaddy), already in the certifi bundle, so
|
||||
# NOTE: THT_SSL_CA is NOT required -- the DWH endpoint presents a public cert
|
||||
# signed by a public CA (GoDaddy), already in the certifi bundle, so
|
||||
# the clients fall back to verify=True and TLS validates without a CA file.
|
||||
REQUIRED_L2 = ["THOTH_DWH_API_KEY", "THOTH_VEC_API_KEY", "THOTH_VEC_WRITE_API_KEY"]
|
||||
REQUIRED_L2 = ["THT_DWH_API_KEY", "THT_VEC_API_KEY", "THT_VEC_WRITE_API_KEY"]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
||||
@@ -7,7 +7,7 @@ is not assumed reliable' gains real teeth for the data layer.
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from nsp.db.connection import can_create_in_schema, make_engine, ping, writable_tables
|
||||
from tht.db.connection import can_create_in_schema, make_engine, ping, writable_tables
|
||||
|
||||
pytestmark = [pytest.mark.l0]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ FKs, and indexes from a real Postgres catalog.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from nsp.db.introspect import IntrospectionError, introspect
|
||||
from tht.db.introspect import IntrospectionError, introspect
|
||||
|
||||
pytestmark = [pytest.mark.l0]
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ columns, and that wide_text / non-text columns are excluded.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from nsp.config import LshConfig
|
||||
from nsp.db.introspect import introspect
|
||||
from nsp.db.sampling import is_text_type, unique_values_for_lsh
|
||||
from tht.config import LshConfig
|
||||
from tht.db.introspect import introspect
|
||||
from tht.db.sampling import is_text_type, unique_values_for_lsh
|
||||
|
||||
pytestmark = [pytest.mark.l0]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""L2: nsp memory save-one against real pgvector (spec D11, L2).
|
||||
"""L2: tht memory save-one against real pgvector (spec D11, L2).
|
||||
|
||||
Validates D11 end-to-end: a single promoted decision is upserted to the real
|
||||
pgvector via the WRITER key (not a full resync), and a subsequent search_similar
|
||||
@@ -13,18 +13,18 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from nsp.memory import MemoryRecord, save_one_memory
|
||||
from nsp.workspace import load_workspace
|
||||
from tht.memory import MemoryRecord, save_one_memory
|
||||
from tht.workspace import load_workspace
|
||||
|
||||
pytestmark = [pytest.mark.l2]
|
||||
WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "chirone-test.yaml"
|
||||
WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "tht-test.yaml"
|
||||
|
||||
|
||||
def test_save_one_upserts_to_real_pgvector(l2_env):
|
||||
"""save_one_memory pushes one row to the real pgvector via the writer key, and
|
||||
a subsequent search_similar retrieves it. Idempotent (re-running upserts >= 0)."""
|
||||
from nsp.vectorstore.embeddings import OllamaEmbeddings
|
||||
from nsp.vectorstore.rest_client import VectorRestClient
|
||||
from tht.vectorstore.embeddings import OllamaEmbeddings
|
||||
from tht.vectorstore.rest_client import VectorRestClient
|
||||
|
||||
ws = load_workspace(WORKSPACE)
|
||||
if not ws.vector_write_rest or not ws.vector_write_rest.api_key.strip():
|
||||
|
||||
@@ -11,8 +11,8 @@ MODES (both manual, non-deterministic, pre-release -- NOT a regression gate):
|
||||
Altro/value-grounding/rollback paths so specific behaviors assert deterministically.
|
||||
|
||||
HOW TO RUN (operator, before release):
|
||||
1. Populate harness/.env (THOTH_DWH_API_KEY, THOTH_VEC_API_KEY,
|
||||
THOTH_VEC_WRITE_API_KEY, THOTH_SSL_CA, THOTH_*_REST_URL, THOTH_OLLAMA_URL).
|
||||
1. Populate harness/.env (THT_DWH_API_KEY, THT_VEC_API_KEY,
|
||||
THT_VEC_WRITE_API_KEY, THT_SSL_CA, THT_*_REST_URL, THT_OLLAMA_URL).
|
||||
2. Connect VPN. Ensure Pi is configured locally with GLM 5.2.
|
||||
3. Run: pytest -m l2 tests/l2/test_session_ablazione.py -s
|
||||
|
||||
@@ -30,13 +30,13 @@ import pytest
|
||||
|
||||
pytestmark = [pytest.mark.l2]
|
||||
|
||||
WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "chirone-test.yaml"
|
||||
WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "tht-test.yaml"
|
||||
QUESTION = "dammi la lista dei pazienti che hanno fatto un'ablazione nel 2025"
|
||||
|
||||
|
||||
def test_workspace_chirone_test_loads(l2_env):
|
||||
"""The L2 workspace YAML loads and expands ${THOTH_*} from .env."""
|
||||
from nsp.workspace import load_workspace
|
||||
"""The L2 workspace YAML loads and expands ${THT_*} from .env."""
|
||||
from tht.workspace import load_workspace
|
||||
|
||||
ws = load_workspace(WORKSPACE)
|
||||
# secrets must be expanded (not the literal ${...} token)
|
||||
@@ -68,7 +68,7 @@ def test_ablazione_session_manual(l2_env, tmp_path):
|
||||
# assertion. We surface the run instructions and assert only that the launch
|
||||
# context is ready; the operator drives the conversation and inspects the outcome.
|
||||
env_ok = all(os.environ.get(v, "").strip() for v in
|
||||
["THOTH_DWH_API_KEY", "THOTH_VEC_API_KEY", "THOTH_VEC_WRITE_API_KEY"])
|
||||
["THT_DWH_API_KEY", "THT_VEC_API_KEY", "THT_VEC_WRITE_API_KEY"])
|
||||
assert env_ok
|
||||
assert WORKSPACE.exists()
|
||||
print(f"\n[L2 manual] launch: pi --mode rpc (cwd=harness/)")
|
||||
|
||||
@@ -10,31 +10,31 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from nsp.workspace import load_workspace
|
||||
from tht.workspace import load_workspace
|
||||
|
||||
pytestmark = [pytest.mark.l2]
|
||||
WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "chirone-test.yaml"
|
||||
WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "tht-test.yaml"
|
||||
|
||||
|
||||
def test_ablazione_returns_multiple_columns(l2_env):
|
||||
"""On the real schema, 'ablazione' should ground to more than one column (e.g.
|
||||
a flag and a free-text patologia field) -- the whole point of D14a's
|
||||
non-collapsing aggregation. Requires a built LSH index (nsp lsh build)."""
|
||||
from nsp.config import LshConfig
|
||||
non-collapsing aggregation. Requires a built LSH index (tht lsh build)."""
|
||||
from tht.config import LshConfig
|
||||
try:
|
||||
from nsp.lshindex import load_index, query_index # ported with the lsh build path
|
||||
from tht.lshindex import load_index, query_index # ported with the lsh build path
|
||||
except ModuleNotFoundError:
|
||||
pytest.skip("nsp.lshindex not yet ported (deferred from B3; lands with nsp lsh build)")
|
||||
from nsp.search import aggregate_lsh_multi
|
||||
pytest.skip("tht.lshindex not yet ported (deferred from B3; lands with tht lsh build)")
|
||||
from tht.search import aggregate_lsh_multi
|
||||
|
||||
# NOTE: this test assumes the LSH index was built (nsp lsh build --workspace
|
||||
# chirone-test). If absent, build it first. The index path comes from the config.
|
||||
# NOTE: this test assumes the LSH index was built (tht lsh build --workspace
|
||||
# tht-test). If absent, build it first. The index path comes from the config.
|
||||
ws = load_workspace(WORKSPACE)
|
||||
index_dir = ws.paths.indexes
|
||||
try:
|
||||
lsh, minhashes, meta = load_index(index_dir, "datawarehouse")
|
||||
except Exception as e:
|
||||
pytest.skip(f"LSH index not built yet (run nsp lsh build): {e}")
|
||||
pytest.skip(f"LSH index not built yet (run tht lsh build): {e}")
|
||||
|
||||
hits = query_index(lsh, minhashes, "ablazione", meta, top_n=20)
|
||||
grouped = aggregate_lsh_multi(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""L1: nsp phase meta --json -- the gate reads workflow facts from here (F2).
|
||||
"""L1: tht phase meta --json -- the gate reads workflow facts from here (F2).
|
||||
Kills the JS/Python drift (no mirrored constants): workflow.yaml is the single source.
|
||||
"""
|
||||
import json
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from nsp.cli import app
|
||||
from tht.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from nsp.decisions import append_decision, list_decisions
|
||||
from tht.decisions import append_decision, list_decisions
|
||||
|
||||
|
||||
def test_decision_retracted_in_audit_but_marked(tmp_path):
|
||||
@@ -57,7 +57,7 @@ def test_empty_session_returns_empty_list(tmp_path):
|
||||
|
||||
def test_decision_type_literal_includes_retracted():
|
||||
"""decision_retracted e' un tipo valido (pydantic lo accetta)."""
|
||||
from nsp.decisions import DecisionRecord
|
||||
from tht.decisions import DecisionRecord
|
||||
from datetime import datetime, UTC
|
||||
d = DecisionRecord(
|
||||
seq=1, ts=datetime.now(UTC), type="decision_retracted",
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from nsp.evidence.formula_store import ConceptFormula, retrieve_formula, save_formula
|
||||
from tht.evidence.formula_store import ConceptFormula, retrieve_formula, save_formula
|
||||
|
||||
|
||||
def test_formula_retrieval_by_concept(tmp_path):
|
||||
@@ -72,7 +72,7 @@ def test_retrieve_empty_on_missing_dir(tmp_path):
|
||||
|
||||
def test_concept_formula_decision_types_exist():
|
||||
import typing
|
||||
from nsp.decisions import DecisionType
|
||||
from tht.decisions import DecisionType
|
||||
args = typing.get_args(DecisionType)
|
||||
assert "concept_formula_approved" in args
|
||||
assert "concept_formula_rejected" in args
|
||||
|
||||
@@ -9,7 +9,7 @@ decision rationale and survive persistence, never silently discarded.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from nsp.decisions import append_decision, list_decisions
|
||||
from tht.decisions import append_decision, list_decisions
|
||||
|
||||
|
||||
def test_altro_freetext_recorded_in_rationale(tmp_path):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""L1: nsp memory save-one -- targeted upsert via the writer key (spec D11).
|
||||
"""L1: tht memory save-one -- targeted upsert via the writer key (spec D11).
|
||||
|
||||
The D11 deviation: instead of a full vectorstore resync (nsp memory index / sync),
|
||||
The D11 deviation: instead of a full vectorstore resync (tht memory index / sync),
|
||||
a remote workstation with a writer key can push a SINGLE promoted decision to
|
||||
pgvector as a one-row upsert. This test pins the pure core of that behavior:
|
||||
- exactly one VectorRecord is built for the chosen decision_seq
|
||||
@@ -13,7 +13,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from nsp.memory import MemoryRecord, memory_vector_record_for_decision, save_one_memory
|
||||
from tht.memory import MemoryRecord, memory_vector_record_for_decision, save_one_memory
|
||||
|
||||
|
||||
def _record(seq: int = 7, **kw) -> MemoryRecord:
|
||||
|
||||
@@ -6,9 +6,9 @@ override wins over the physical classification.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from nsp.config import EligibilityConfig
|
||||
from nsp.mschema.eligibility import classify_all, classify_column, effective_eligibility
|
||||
from nsp.mschema.models import (
|
||||
from tht.config import EligibilityConfig
|
||||
from tht.mschema.eligibility import classify_all, classify_column, effective_eligibility
|
||||
from tht.mschema.models import (
|
||||
Annotations,
|
||||
ColumnAnnotation,
|
||||
ColumnPhysical,
|
||||
|
||||
@@ -5,7 +5,7 @@ ThothAI style, schema-dict for AV-SQL). Pure logic, no I/O.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
from nsp.mschema.models import (
|
||||
from tht.mschema.models import (
|
||||
Annotations,
|
||||
ColumnAnnotation,
|
||||
ColumnPhysical,
|
||||
@@ -13,7 +13,7 @@ from nsp.mschema.models import (
|
||||
PhysicalSchema,
|
||||
TablePhysical,
|
||||
)
|
||||
from nsp.mschema.render import to_markdown, to_mschema_text, to_schema_dict
|
||||
from tht.mschema.render import to_markdown, to_mschema_text, to_schema_dict
|
||||
|
||||
|
||||
def _fake_schema() -> PhysicalSchema:
|
||||
@@ -84,7 +84,7 @@ def test_to_schema_dict_avsql_shape():
|
||||
|
||||
def test_annotations_override_description_in_render():
|
||||
schema = _fake_schema()
|
||||
from nsp.mschema.models import TableAnnotation
|
||||
from tht.mschema.models import TableAnnotation
|
||||
ann = Annotations(tables={
|
||||
"dim_pazienti": TableAnnotation(columns={
|
||||
"citta": ColumnAnnotation(description="Comune di residenza"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from nsp.decisions import append_decision, DecisionRecord
|
||||
from nsp.phase import current_phase, effective_decisions
|
||||
from tht.decisions import append_decision, DecisionRecord
|
||||
from tht.phase import current_phase, effective_decisions
|
||||
|
||||
|
||||
def _d(session, dtype, subject, **kw) -> DecisionRecord:
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
Verifies the ported RestClient: each rpc carries the X-API-Key header, the right
|
||||
payload, base_url slash handling, and HTTP/network errors surface as RestError.
|
||||
Ported (renamed psdwp3->nsp) from ChironeWp3/tests/test_rest_client.py.
|
||||
Ported (renamed psdwp3->tht) from ChironeWp3/tests/test_rest_client.py.
|
||||
"""
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from nsp.config import RestConfig
|
||||
from nsp.rest.client import RestClient, RestError
|
||||
from tht.config import RestConfig
|
||||
from tht.rest.client import RestClient, RestError
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
@@ -39,7 +39,7 @@ def _capture(monkeypatch, response):
|
||||
"timeout": timeout, "verify": verify})
|
||||
return response
|
||||
|
||||
monkeypatch.setattr("nsp.rest.client.requests.post", fake_post)
|
||||
monkeypatch.setattr("tht.rest.client.requests.post", fake_post)
|
||||
return calls
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ def test_network_error_actionable(monkeypatch):
|
||||
def boom(*a, **k):
|
||||
raise requests.ConnectionError("refused")
|
||||
|
||||
monkeypatch.setattr("nsp.rest.client.requests.post", boom)
|
||||
monkeypatch.setattr("tht.rest.client.requests.post", boom)
|
||||
with pytest.raises(RestError, match="raggiungibile"):
|
||||
_client().ping()
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ effective view, teardown, taskdoc byte budget).
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from nsp.decisions import append_decision
|
||||
from nsp.phase import current_phase, effective_decisions
|
||||
from nsp.taskdoc import generate_task_doc
|
||||
from nsp.teardown import teardown_to_phase
|
||||
from nsp.workflow import load_workflow
|
||||
from tht.decisions import append_decision
|
||||
from tht.phase import current_phase, effective_decisions
|
||||
from tht.taskdoc import generate_task_doc
|
||||
from tht.teardown import teardown_to_phase
|
||||
from tht.workflow import load_workflow
|
||||
|
||||
|
||||
def _approve(session: Path, phase: int) -> None:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from pathlib import Path
|
||||
|
||||
from nsp.taskdoc import generate_task_doc
|
||||
from tht.taskdoc import generate_task_doc
|
||||
|
||||
|
||||
def test_task_doc_includes_question_and_schema_scope(tmp_path):
|
||||
@@ -52,8 +52,8 @@ def test_task_doc_carries_phase_header(tmp_path):
|
||||
def test_task_doc_excludes_stale_decisions_post_rollback(tmp_path):
|
||||
"""D15+D16: il task doc riflette lo stato effective, non quello stale.
|
||||
Dopo rollback a F4, una sql_approved:7 stale non appare nel brief delle decisioni."""
|
||||
from nsp.decisions import append_decision
|
||||
from nsp.phase import current_phase
|
||||
from tht.decisions import append_decision
|
||||
from tht.phase import current_phase
|
||||
s = tmp_path / "sess"; s.mkdir()
|
||||
(s / "question.md").write_text("q")
|
||||
# simula: lavoro fino a F7, poi rollback a F4
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from nsp.teardown import teardown_to_phase
|
||||
from tht.teardown import teardown_to_phase
|
||||
|
||||
|
||||
def test_teardown_to_phase_4_deletes_phase5plus_artifacts(tmp_path):
|
||||
|
||||
@@ -7,7 +7,7 @@ aggregate_lsh_multi exposes every column where the value appears, grouped by tab
|
||||
so the value-grounding widget can let the reviewer choose which column(s) anchor
|
||||
the value.
|
||||
"""
|
||||
from nsp.search import aggregate_lsh_multi
|
||||
from tht.search import aggregate_lsh_multi
|
||||
|
||||
|
||||
def test_value_in_multiple_columns_returns_all():
|
||||
@@ -64,7 +64,7 @@ def test_empty_hits_returns_empty():
|
||||
def test_value_grounded_decision_type_exists():
|
||||
# D14a adds the value_grounded decision type so the gate can record the
|
||||
# reviewer's choice of which column(s) anchor a cited value.
|
||||
from nsp.decisions import DecisionType
|
||||
from tht.decisions import DecisionType
|
||||
import typing
|
||||
args = typing.get_args(DecisionType)
|
||||
assert "value_grounded" in args
|
||||
|
||||
@@ -5,9 +5,9 @@ API keys against the same pgvector REST endpoint, with distinct roles
|
||||
(vector_reader / vector_writer). This test pins the dual-key construction and
|
||||
the workstation write-guard.
|
||||
"""
|
||||
from nsp.cli._guards import has_vector_write_rest, require_vector_write_allowed
|
||||
from nsp.config import Config, DatabaseConfig, RestConfig
|
||||
from nsp.vectorstore.rest_client import VectorRestClient
|
||||
from tht.cli._guards import has_vector_write_rest, require_vector_write_allowed
|
||||
from tht.config import Config, DatabaseConfig, RestConfig
|
||||
from tht.vectorstore.rest_client import VectorRestClient
|
||||
|
||||
|
||||
def _minimal_config(**kw) -> Config:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from nsp.workflow import load_workflow
|
||||
from tht.workflow import load_workflow
|
||||
|
||||
|
||||
def test_workflow_loads_8_phases():
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
from pathlib import Path
|
||||
|
||||
from nsp.workspace import load_workspace, WorkspaceError
|
||||
from tht.workspace import load_workspace, WorkspaceError
|
||||
|
||||
|
||||
def test_load_workspace_expands_env_vars(monkeypatch, tmp_path):
|
||||
# Isolate profile: load_dotenv (conftest D3) injects THOTH_PROFILE from the real
|
||||
# Isolate profile: load_dotenv (conftest D3) injects THT_PROFILE from the real
|
||||
# harness/.env into os.environ; this test asserts the *default* (server), so it
|
||||
# must del THOTH_PROFILE rather than inherit whatever the operator set.
|
||||
monkeypatch.delenv("THOTH_PROFILE", raising=False)
|
||||
monkeypatch.setenv("THOTH_VEC_API_KEY", "secret-reader")
|
||||
monkeypatch.setenv("THOTH_VEC_WRITE_API_KEY", "secret-writer")
|
||||
monkeypatch.setenv("THOTH_VEC_REST_URL", "https://example/vector/v1/")
|
||||
monkeypatch.setenv("THOTH_DWH_REST_URL", "https://example/dwh/")
|
||||
monkeypatch.setenv("THOTH_DWH_API_KEY", "dwh-key")
|
||||
monkeypatch.setenv("THOTH_DB_HOST", "h")
|
||||
monkeypatch.setenv("THOTH_DB_NAME", "db")
|
||||
monkeypatch.setenv("THOTH_DB_USER", "u")
|
||||
monkeypatch.setenv("THOTH_DB_PASSWORD", "p")
|
||||
monkeypatch.setenv("THOTH_VEC_HOST", "vh")
|
||||
monkeypatch.setenv("THOTH_VEC_USER", "vu")
|
||||
monkeypatch.setenv("THOTH_VEC_PASSWORD", "vp")
|
||||
monkeypatch.setenv("THOTH_OLLAMA_URL", "http://ollama")
|
||||
monkeypatch.setenv("THOTH_DOCS_ROOT", str(tmp_path / "docs"))
|
||||
# must del THT_PROFILE rather than inherit whatever the operator set.
|
||||
monkeypatch.delenv("THT_PROFILE", raising=False)
|
||||
monkeypatch.setenv("THT_VEC_API_KEY", "secret-reader")
|
||||
monkeypatch.setenv("THT_VEC_WRITE_API_KEY", "secret-writer")
|
||||
monkeypatch.setenv("THT_VEC_REST_URL", "https://example/vector/v1/")
|
||||
monkeypatch.setenv("THT_DWH_REST_URL", "https://example/dwh/")
|
||||
monkeypatch.setenv("THT_DWH_API_KEY", "dwh-key")
|
||||
monkeypatch.setenv("THT_DB_HOST", "h")
|
||||
monkeypatch.setenv("THT_DB_NAME", "db")
|
||||
monkeypatch.setenv("THT_DB_USER", "u")
|
||||
monkeypatch.setenv("THT_DB_PASSWORD", "p")
|
||||
monkeypatch.setenv("THT_VEC_HOST", "vh")
|
||||
monkeypatch.setenv("THT_VEC_USER", "vu")
|
||||
monkeypatch.setenv("THT_VEC_PASSWORD", "vp")
|
||||
monkeypatch.setenv("THT_OLLAMA_URL", "http://ollama")
|
||||
monkeypatch.setenv("THT_DOCS_ROOT", str(tmp_path / "docs"))
|
||||
yaml = tmp_path / "w.yaml"
|
||||
yaml.write_text(
|
||||
"database:\n"
|
||||
" host: ${THOTH_DB_HOST}\n"
|
||||
" host: ${THT_DB_HOST}\n"
|
||||
" port: 5432\n"
|
||||
" database: ${THOTH_DB_NAME}\n"
|
||||
" database: ${THT_DB_NAME}\n"
|
||||
" schema: datawarehouse\n"
|
||||
" user: ${THOTH_DB_USER}\n"
|
||||
" password: ${THOTH_DB_PASSWORD}\n"
|
||||
" user: ${THT_DB_USER}\n"
|
||||
" password: ${THT_DB_PASSWORD}\n"
|
||||
" transport: rest\n"
|
||||
"rest:\n"
|
||||
" base_url: ${THOTH_DWH_REST_URL}\n"
|
||||
" api_key: ${THOTH_DWH_API_KEY}\n"
|
||||
" base_url: ${THT_DWH_REST_URL}\n"
|
||||
" api_key: ${THT_DWH_API_KEY}\n"
|
||||
"vector_db:\n"
|
||||
" host: ${THOTH_VEC_HOST}\n"
|
||||
" host: ${THT_VEC_HOST}\n"
|
||||
" port: 5438\n"
|
||||
" database: postgres\n"
|
||||
" schema: vectors\n"
|
||||
" user: ${THOTH_VEC_USER}\n"
|
||||
" password: ${THOTH_VEC_PASSWORD}\n"
|
||||
" user: ${THT_VEC_USER}\n"
|
||||
" password: ${THT_VEC_PASSWORD}\n"
|
||||
"vector_rest:\n"
|
||||
" base_url: ${THOTH_VEC_REST_URL}\n"
|
||||
" api_key: ${THOTH_VEC_API_KEY}\n"
|
||||
" base_url: ${THT_VEC_REST_URL}\n"
|
||||
" api_key: ${THT_VEC_API_KEY}\n"
|
||||
"vector_write_rest:\n"
|
||||
" base_url: ${THOTH_VEC_REST_URL}\n"
|
||||
" api_key: ${THOTH_VEC_WRITE_API_KEY}\n"
|
||||
" base_url: ${THT_VEC_REST_URL}\n"
|
||||
" api_key: ${THT_VEC_WRITE_API_KEY}\n"
|
||||
"embeddings:\n"
|
||||
" base_url: ${THOTH_OLLAMA_URL}\n"
|
||||
" base_url: ${THT_OLLAMA_URL}\n"
|
||||
" model: nomic-embed-text-v2-moe\n"
|
||||
" dim: 768\n"
|
||||
"evidence:\n"
|
||||
" source_root: ${THOTH_DOCS_ROOT}\n"
|
||||
" source_root: ${THT_DOCS_ROOT}\n"
|
||||
)
|
||||
ws = load_workspace(yaml)
|
||||
# Dual vector key (top-level, per Config reale): reader and writer separate
|
||||
@@ -66,7 +66,7 @@ def test_load_workspace_expands_env_vars(monkeypatch, tmp_path):
|
||||
|
||||
|
||||
def test_load_workspace_missing_env_raises(monkeypatch, tmp_path):
|
||||
monkeypatch.delenv("THOTH_VEC_API_KEY", raising=False)
|
||||
monkeypatch.delenv("THT_VEC_API_KEY", raising=False)
|
||||
yaml = tmp_path / "w.yaml"
|
||||
yaml.write_text(
|
||||
"database:\n"
|
||||
@@ -74,10 +74,10 @@ def test_load_workspace_missing_env_raises(monkeypatch, tmp_path):
|
||||
" user: u\n password: p\n transport: direct\n"
|
||||
"vector_rest:\n"
|
||||
" base_url: 'https://v/'\n"
|
||||
" api_key: '${THOTH_VEC_API_KEY}'\n"
|
||||
" api_key: '${THT_VEC_API_KEY}'\n"
|
||||
)
|
||||
try:
|
||||
load_workspace(yaml)
|
||||
assert False, "should have raised"
|
||||
except WorkspaceError as e:
|
||||
assert "THOTH_VEC_API_KEY" in str(e)
|
||||
assert "THT_VEC_API_KEY" in str(e)
|
||||
|
||||
Reference in New Issue
Block a user