test: add pytest coverage for core modules
This commit is contained in:
@@ -19,4 +19,4 @@
|
|||||||
- Concise, engineering notebook tone.
|
- Concise, engineering notebook tone.
|
||||||
|
|
||||||
## How to verify (tests/commands)
|
## How to verify (tests/commands)
|
||||||
- Unknown / needs verification (no test harness yet).
|
- `pytest`
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ authors = [
|
|||||||
{name = "scawful"}
|
{name = "scawful"}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
test = [
|
||||||
|
"pytest>=7.4"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=68"]
|
requires = ["setuptools>=68"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|||||||
9
tests/conftest.py
Normal file
9
tests/conftest.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SRC = ROOT / "src"
|
||||||
|
if str(SRC) not in sys.path:
|
||||||
|
sys.path.insert(0, str(SRC))
|
||||||
44
tests/test_config.py
Normal file
44
tests/test_config.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from afs.config import load_config, load_config_model
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_config_merges_workspace_registry(tmp_path, monkeypatch) -> None:
|
||||||
|
context_root = tmp_path / "context"
|
||||||
|
context_root.mkdir()
|
||||||
|
workspace_dir = tmp_path / "workspace"
|
||||||
|
workspace_dir.mkdir()
|
||||||
|
|
||||||
|
registry_path = context_root / "workspaces.toml"
|
||||||
|
registry_path.write_text(
|
||||||
|
"[[workspaces]]\n"
|
||||||
|
f"path = \"{workspace_dir}\"\n"
|
||||||
|
"description = \"Example\"\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
config_path = tmp_path / "afs.toml"
|
||||||
|
config_path.write_text(
|
||||||
|
f"[general]\ncontext_root = \"{context_root}\"\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
data = load_config(merge_user=False)
|
||||||
|
workspaces = data["general"]["workspace_directories"]
|
||||||
|
assert workspaces
|
||||||
|
assert Path(workspaces[0]["path"]).resolve() == workspace_dir.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_config_model_uses_explicit_path(tmp_path) -> None:
|
||||||
|
context_root = tmp_path / "context"
|
||||||
|
config_path = tmp_path / "custom.toml"
|
||||||
|
config_path.write_text(
|
||||||
|
f"[general]\ncontext_root = \"{context_root}\"\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
model = load_config_model(config_path=config_path, merge_user=False)
|
||||||
|
assert model.general.context_root == context_root.resolve()
|
||||||
37
tests/test_discovery.py
Normal file
37
tests/test_discovery.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from afs.discovery import discover_contexts, get_project_stats
|
||||||
|
from afs.manager import AFSManager
|
||||||
|
from afs.schema import AFSConfig, GeneralConfig
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_contexts_ignores_names(tmp_path: Path) -> None:
|
||||||
|
root = tmp_path / "root"
|
||||||
|
root.mkdir()
|
||||||
|
|
||||||
|
alpha = root / "alpha"
|
||||||
|
alpha.mkdir()
|
||||||
|
|
||||||
|
legacy = root / "legacy"
|
||||||
|
legacy.mkdir()
|
||||||
|
beta = legacy / "beta"
|
||||||
|
beta.mkdir()
|
||||||
|
|
||||||
|
config = AFSConfig(
|
||||||
|
general=GeneralConfig(context_root=tmp_path / "context"),
|
||||||
|
)
|
||||||
|
manager = AFSManager(config=config)
|
||||||
|
|
||||||
|
manager.ensure(path=alpha)
|
||||||
|
manager.ensure(path=beta)
|
||||||
|
|
||||||
|
contexts = discover_contexts(search_paths=[root], config=config, max_depth=2)
|
||||||
|
names = [context.project_name for context in contexts]
|
||||||
|
|
||||||
|
assert "alpha" in names
|
||||||
|
assert "beta" not in names
|
||||||
|
|
||||||
|
stats = get_project_stats(contexts)
|
||||||
|
assert stats["total_projects"] == 1
|
||||||
68
tests/test_manager.py
Normal file
68
tests/test_manager.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from afs.manager import AFSManager
|
||||||
|
from afs.models import MountType
|
||||||
|
from afs.schema import AFSConfig, GeneralConfig
|
||||||
|
|
||||||
|
|
||||||
|
def _make_manager(tmp_path: Path) -> AFSManager:
|
||||||
|
context_root = tmp_path / "context"
|
||||||
|
general = GeneralConfig(
|
||||||
|
context_root=context_root,
|
||||||
|
agent_workspaces_dir=context_root / "workspaces",
|
||||||
|
)
|
||||||
|
return AFSManager(config=AFSConfig(general=general))
|
||||||
|
|
||||||
|
|
||||||
|
def test_ensure_creates_context_and_metadata(tmp_path: Path) -> None:
|
||||||
|
manager = _make_manager(tmp_path)
|
||||||
|
project_path = tmp_path / "project"
|
||||||
|
project_path.mkdir()
|
||||||
|
|
||||||
|
context = manager.ensure(path=project_path, context_root=tmp_path / "context")
|
||||||
|
|
||||||
|
assert context.path.exists()
|
||||||
|
assert (context.path / "metadata.json").exists()
|
||||||
|
assert (context.path / "memory").exists()
|
||||||
|
assert (context.path / "knowledge").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_ensure_with_link_creates_symlink(tmp_path: Path) -> None:
|
||||||
|
manager = _make_manager(tmp_path)
|
||||||
|
project_path = tmp_path / "project"
|
||||||
|
project_path.mkdir()
|
||||||
|
context_root = tmp_path / "context"
|
||||||
|
|
||||||
|
manager.ensure(path=project_path, context_root=context_root, link_context=True)
|
||||||
|
|
||||||
|
link_path = project_path / ".context"
|
||||||
|
assert link_path.is_symlink()
|
||||||
|
assert link_path.resolve() == context_root.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def test_mount_and_unmount(tmp_path: Path) -> None:
|
||||||
|
manager = _make_manager(tmp_path)
|
||||||
|
project_path = tmp_path / "project"
|
||||||
|
project_path.mkdir()
|
||||||
|
context_root = tmp_path / "context"
|
||||||
|
|
||||||
|
context = manager.ensure(path=project_path, context_root=context_root)
|
||||||
|
|
||||||
|
source_dir = tmp_path / "source"
|
||||||
|
source_dir.mkdir()
|
||||||
|
|
||||||
|
mount = manager.mount(
|
||||||
|
source_dir,
|
||||||
|
MountType.KNOWLEDGE,
|
||||||
|
context_path=context.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
mount_path = context.path / "knowledge" / mount.name
|
||||||
|
assert mount_path.is_symlink()
|
||||||
|
assert mount_path.resolve() == source_dir.resolve()
|
||||||
|
|
||||||
|
removed = manager.unmount(mount.name, MountType.KNOWLEDGE, context_path=context.path)
|
||||||
|
assert removed
|
||||||
|
assert not mount_path.exists()
|
||||||
20
tests/test_plugins.py
Normal file
20
tests/test_plugins.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from afs.plugins import discover_plugins
|
||||||
|
from afs.schema import AFSConfig, PluginsConfig
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_plugins_in_custom_dir(tmp_path: Path) -> None:
|
||||||
|
plugin_dir = tmp_path / "plugins"
|
||||||
|
plugin_dir.mkdir()
|
||||||
|
package_dir = plugin_dir / "afs_plugin_demo"
|
||||||
|
package_dir.mkdir()
|
||||||
|
(package_dir / "__init__.py").write_text("", encoding="utf-8")
|
||||||
|
|
||||||
|
plugins = PluginsConfig(plugin_dirs=[plugin_dir], auto_discover=True)
|
||||||
|
config = AFSConfig(plugins=plugins)
|
||||||
|
|
||||||
|
names = discover_plugins(config)
|
||||||
|
assert "afs_plugin_demo" in names
|
||||||
Reference in New Issue
Block a user