md-first memory extraction framework for AI agents. Markdown is the single source of truth; SQLite holds state and LanceDB provides the rebuildable vector + BM25 + scalar index. The codebase follows a single-direction DDD layering (entrypoints -> service -> memory -> infra, with component / core / config cross-cutting) enforced by import-linter. Engineering surface: - Coding conventions in .claude/rules/ (path-scoped) and workflows in .claude/skills/ (/commit, /new-branch, /pr). - GitHub Actions CI runs make lint + test + integration; pre-commit mirrors the gates locally (ruff, hygiene hooks, gitlint commit-msg). - Commit messages follow Conventional Commits, enforced by gitlint. - make lint also enforces datetime two-zone discipline and OpenAPI drift.
214 lines
7.2 KiB
Python
214 lines
7.2 KiB
Python
"""``everos init`` — CLI behavior + edge cases.
|
|
|
|
Covers:
|
|
|
|
- default ``./.env`` path, written with 0600 permissions
|
|
- ``--to <path>`` creates parent dirs
|
|
- ``--force`` overwrites; without it the command refuses with exit 1
|
|
- ``--print`` writes to stdout, NOT to disk
|
|
- ``--xdg`` and ``--to`` are mutually exclusive (exit 2)
|
|
- ``--xdg`` honors ``XDG_CONFIG_HOME``
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import stat
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from typer.testing import CliRunner
|
|
|
|
from everos.entrypoints.cli.main import app
|
|
|
|
|
|
@pytest.fixture
|
|
def runner() -> CliRunner:
|
|
return CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def in_tmp(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
"""Run from a fresh tmp cwd so default ``./.env`` lands in tmp_path."""
|
|
monkeypatch.chdir(tmp_path)
|
|
return tmp_path
|
|
|
|
|
|
def test_default_writes_dotenv_in_cwd(runner: CliRunner, in_tmp: Path) -> None:
|
|
result = runner.invoke(app, ["init"])
|
|
assert result.exit_code == 0, result.output
|
|
written = in_tmp / ".env"
|
|
assert written.exists()
|
|
assert written.stat().st_size > 0
|
|
assert "EVEROS_LLM__API_KEY" in written.read_text()
|
|
|
|
|
|
def test_default_file_permissions_are_0600(runner: CliRunner, in_tmp: Path) -> None:
|
|
"""The generated .env holds API keys — must not be world-readable."""
|
|
result = runner.invoke(app, ["init"])
|
|
assert result.exit_code == 0
|
|
mode = stat.S_IMODE((in_tmp / ".env").stat().st_mode)
|
|
assert mode == 0o600, f"expected 0o600, got {oct(mode)}"
|
|
|
|
|
|
def test_refuses_overwrite_without_force(runner: CliRunner, in_tmp: Path) -> None:
|
|
(in_tmp / ".env").write_text("PREEXISTING=1\n")
|
|
result = runner.invoke(app, ["init"])
|
|
assert result.exit_code == 1
|
|
assert "already exists" in (result.output + (result.stderr or ""))
|
|
# Original content must be preserved.
|
|
assert (in_tmp / ".env").read_text() == "PREEXISTING=1\n"
|
|
|
|
|
|
def test_force_overwrites(runner: CliRunner, in_tmp: Path) -> None:
|
|
(in_tmp / ".env").write_text("PREEXISTING=1\n")
|
|
result = runner.invoke(app, ["init", "--force"])
|
|
assert result.exit_code == 0
|
|
body = (in_tmp / ".env").read_text()
|
|
assert "PREEXISTING=1" not in body
|
|
assert "EVEROS_LLM__API_KEY" in body
|
|
|
|
|
|
def test_to_creates_parent_dirs(runner: CliRunner, in_tmp: Path) -> None:
|
|
target = in_tmp / "nested" / "subdir" / ".env"
|
|
result = runner.invoke(app, ["init", "--to", str(target)])
|
|
assert result.exit_code == 0
|
|
assert target.exists()
|
|
assert "EVEROS_LLM__API_KEY" in target.read_text()
|
|
|
|
|
|
def test_print_writes_stdout_not_disk(runner: CliRunner, in_tmp: Path) -> None:
|
|
result = runner.invoke(app, ["init", "--print"])
|
|
assert result.exit_code == 0
|
|
assert "EVEROS_LLM__API_KEY" in result.output
|
|
# No disk side-effect.
|
|
assert not (in_tmp / ".env").exists()
|
|
|
|
|
|
def test_xdg_writes_to_xdg_config_home(
|
|
runner: CliRunner, in_tmp: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
xdg_root = in_tmp / "xdg"
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_root))
|
|
result = runner.invoke(app, ["init", "--xdg"])
|
|
assert result.exit_code == 0
|
|
target = xdg_root / "everos" / ".env"
|
|
assert target.exists()
|
|
|
|
|
|
def test_xdg_falls_back_to_dot_config(
|
|
runner: CliRunner, in_tmp: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""No ``XDG_CONFIG_HOME`` → default ``~/.config``.
|
|
|
|
We sandbox ``$HOME`` to ``in_tmp`` so the test does not touch a real
|
|
user's ``~/.config``.
|
|
"""
|
|
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
|
|
monkeypatch.setenv("HOME", str(in_tmp))
|
|
result = runner.invoke(app, ["init", "--xdg"])
|
|
assert result.exit_code == 0
|
|
target = in_tmp / ".config" / "everos" / ".env"
|
|
assert target.exists()
|
|
|
|
|
|
def test_xdg_and_to_are_mutually_exclusive(runner: CliRunner, in_tmp: Path) -> None:
|
|
result = runner.invoke(app, ["init", "--xdg", "--to", str(in_tmp / "other.env")])
|
|
assert result.exit_code == 2
|
|
assert "mutually exclusive" in (result.output + (result.stderr or ""))
|
|
|
|
|
|
def test_template_resource_is_packaged_under_everos_templates() -> None:
|
|
"""The packaged resource must remain at the canonical location.
|
|
|
|
Guards the wheel/sdist layout: ``init_cmd`` reads
|
|
``everos.templates.env.template`` via ``importlib.resources``; if
|
|
someone moves the file without updating ``_TEMPLATE_PACKAGE``, this
|
|
test fails immediately.
|
|
"""
|
|
from importlib import resources
|
|
|
|
res = resources.files("everos.templates").joinpath("env.template")
|
|
assert res.is_file()
|
|
body = res.read_text(encoding="utf-8")
|
|
assert "EVEROS_LLM__API_KEY" in body
|
|
|
|
|
|
# ── 4-layer .env resolution for ``server start`` ────────────────────────
|
|
|
|
|
|
def test_resolve_env_file_explicit_wins(in_tmp: Path) -> None:
|
|
"""``--env-file <path>`` beats cwd / XDG / ~/.everos fallbacks."""
|
|
from everos.entrypoints.cli.commands.server import _resolve_env_file
|
|
|
|
explicit = in_tmp / "explicit.env"
|
|
explicit.write_text("X=1\n")
|
|
# Also seed cwd .env so we can prove the explicit wins.
|
|
(in_tmp / ".env").write_text("CWD=1\n")
|
|
resolved = _resolve_env_file(str(explicit))
|
|
assert resolved == explicit
|
|
|
|
|
|
def test_resolve_env_file_cwd_wins_over_xdg(
|
|
in_tmp: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
from everos.entrypoints.cli.commands.server import _resolve_env_file
|
|
|
|
xdg_root = in_tmp / "xdg"
|
|
(xdg_root / "everos").mkdir(parents=True)
|
|
(xdg_root / "everos" / ".env").write_text("XDG=1\n")
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_root))
|
|
cwd_env = in_tmp / ".env"
|
|
cwd_env.write_text("CWD=1\n")
|
|
resolved = _resolve_env_file(None)
|
|
assert resolved == cwd_env
|
|
|
|
|
|
def test_resolve_env_file_xdg_when_no_cwd(
|
|
in_tmp: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
from everos.entrypoints.cli.commands.server import _resolve_env_file
|
|
|
|
xdg_root = in_tmp / "xdg"
|
|
(xdg_root / "everos").mkdir(parents=True)
|
|
target = xdg_root / "everos" / ".env"
|
|
target.write_text("XDG=1\n")
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(xdg_root))
|
|
# No cwd/.env.
|
|
resolved = _resolve_env_file(None)
|
|
assert resolved == target
|
|
|
|
|
|
def test_resolve_env_file_everos_home_fallback(
|
|
in_tmp: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""``~/.everos/.env`` is the last fallback when nothing else exists."""
|
|
from everos.entrypoints.cli.commands.server import _resolve_env_file
|
|
|
|
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
|
|
monkeypatch.setenv("HOME", str(in_tmp))
|
|
target = in_tmp / ".everos" / ".env"
|
|
target.parent.mkdir(parents=True)
|
|
target.write_text("EVEROS_ROOT=1\n")
|
|
resolved = _resolve_env_file(None)
|
|
assert resolved == target
|
|
|
|
|
|
def test_resolve_env_file_none_when_no_layer_matches(
|
|
in_tmp: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""All four layers absent → ``None`` (the server then falls back to
|
|
inherited process env, which is the documented CI/container path)."""
|
|
from everos.entrypoints.cli.commands.server import _resolve_env_file
|
|
|
|
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
|
|
monkeypatch.setenv("HOME", str(in_tmp))
|
|
# Nothing in cwd, no XDG path, no ~/.everos/.
|
|
assert not (in_tmp / ".env").exists()
|
|
assert _resolve_env_file(None) is None
|
|
|
|
|
|
# ``os`` imported above just to keep ruff from complaining; remove if Ruff
|
|
# F401 hits.
|
|
_ = os
|