nanobot: 2026-09-10 12:33:37

This commit is contained in:
lachtan
2026-09-10 12:33:37 +02:00
parent a65d082b27
commit 52161b1cd3
95 changed files with 4904 additions and 6041 deletions

View File

@@ -0,0 +1,165 @@
"""Scope precedence (paths -> include -> exclude) and config validation."""
import sys
from pathlib import Path
import pytest
SCRIPTS = Path(__file__).parent.parent / "scripts"
sys.path.insert(0, str(SCRIPTS))
import wiki_config
from wiki_config import ConfigError, SourceConfig, load_config, source_root
WORKSPACE_YAML = """
embedding:
endpoint: http://nvidia.hell:11434/
model: qwen3-embedding:0.6b
dims: 1024
batch: 32
keep_alive: -1
query_prefix: "Instruct: task\\nQuery: "
sources:
index:
kind: git
url: git@git.fnet.cz:lachtan/index.git
paths: ["**"]
include: ["*.md"]
exclude:
- "**/node_modules/**"
- "**/vendor/**"
workspace:
kind: workspace
paths:
- "notes/**"
- "develop/**"
include: ["*.md"]
exclude:
- "**/inbox/**"
- "develop/history.md"
"""
def _write(tmp_path, text):
path = tmp_path / "config.yaml"
path.write_text(text, encoding="utf-8")
return path
def _source(
source_id: str = "ws",
kind: str = "workspace",
url: str | None = None,
paths: tuple[str, ...] = ("**",),
include: tuple[str, ...] = ("*.md",),
exclude: tuple[str, ...] = (),
) -> SourceConfig:
return SourceConfig(source_id=source_id, kind=kind, url=url, paths=paths, include=include, exclude=exclude)
def test_loads_embedding_and_sources(tmp_path):
config = load_config(_write(tmp_path, WORKSPACE_YAML))
assert config.embedding.endpoint == "http://nvidia.hell:11434" # trailing slash trimmed
assert config.embedding.model == "qwen3-embedding:0.6b"
assert config.embedding.dims == 1024
assert config.embedding.keep_alive == -1
assert config.embedding.query_prefix.endswith("Query: ")
assert [s.source_id for s in config.sources] == ["index", "workspace"]
index = config.source("index")
assert index is not None and index.kind == "git"
assert config.source("nope") is None
def test_string_keep_alive_is_rejected():
"""Ollama answers HTTP 400 to a string "-1" — catch it in config, not at runtime."""
with pytest.raises(ConfigError, match="keep_alive"):
wiki_config._parse_embedding({"endpoint": "http://x", "model": "m", "dims": 1024, "keep_alive": "-1"})
def test_missing_config_file(tmp_path):
with pytest.raises(ConfigError, match="missing config"):
load_config(tmp_path / "nope.yaml")
def test_git_source_needs_url(tmp_path):
yaml_text = WORKSPACE_YAML.replace(" url: git@git.fnet.cz:lachtan/index.git\n", "")
with pytest.raises(ConfigError, match="need a url"):
load_config(_write(tmp_path, yaml_text))
def test_unknown_kind_is_rejected(tmp_path):
yaml_text = WORKSPACE_YAML.replace(" kind: workspace", " kind: mirror")
with pytest.raises(ConfigError, match="kind must be one of"):
load_config(_write(tmp_path, yaml_text))
def test_paths_is_required(tmp_path):
yaml_text = WORKSPACE_YAML.replace(' paths: ["**"]\n', "")
with pytest.raises(ConfigError, match="`paths` is required"):
load_config(_write(tmp_path, yaml_text))
def test_paths_whitelist_gates_everything():
source = _source(paths=("notes/**", "develop/**"))
assert source.covers("notes/a.md")
assert source.covers("notes/deep/nested/a.md")
assert source.covers("develop/knowledge.md")
# Not on the whitelist -> does not exist for the index.
assert not source.covers("tmp/a.md")
assert not source.covers("skills/wiki/SKILL.md")
assert not source.covers("AGENTS.md")
def test_include_filters_extensions():
source = _source(paths=("**",))
assert source.covers("notes/a.md")
assert not source.covers("notes/main.py")
assert not source.covers("memory/history.jsonl")
assert not source.covers("assets/photo.png")
def test_exclude_wins_over_paths_and_include():
source = _source(paths=("notes/**", "develop/**"), exclude=("**/inbox/**", "develop/history.md"))
assert not source.covers("notes/inbox/raw.md")
assert not source.covers("develop/history.md")
assert source.covers("develop/knowledge.md")
assert source.covers("notes/notes.md")
def test_double_star_matches_whole_repo():
source = _source(paths=("**",), exclude=("**/node_modules/**", "**/vendor/**"))
assert source.covers("README.md")
assert source.covers("japan/tokyo/metro.md")
assert not source.covers("node_modules/pkg/README.md")
assert not source.covers("web/vendor/lib/CHANGELOG.md")
def test_glob_star_does_not_cross_a_slash():
source = _source(paths=("notes/*",))
assert source.covers("notes/a.md")
assert not source.covers("notes/deep/a.md")
def test_covers_dir_prunes_the_walk():
source = _source(paths=("notes/**", "develop/**"), exclude=("**/inbox/**",))
assert source.covers_dir("")
assert source.covers_dir("notes")
assert source.covers_dir("notes/deep")
assert source.covers_dir("develop")
assert not source.covers_dir("tmp")
assert not source.covers_dir("notes/inbox")
def test_covers_dir_never_prunes_a_double_star_source():
source = _source(paths=("**",), exclude=("**/node_modules/**",))
assert source.covers_dir("anything/deep")
assert not source.covers_dir("app/node_modules")
def test_source_root_derives_clone_path_from_id():
assert source_root(_source(source_id="travel", kind="git", url="git@x:y.git")) == (
wiki_config.REMOTE_DIR / "travel"
)
assert source_root(_source(kind="workspace")) == wiki_config.WORKSPACE