opencode-config/tests/test_memory_setup_tool.py
Sergey 2b9ee23b49
feat(memory): add memory-setup tool with deterministic init (#36)
* feat(memory): add memory-setup tool and rewrite script

* fix(memory): correct OPENCODE_MEMORY_DIR in docker-compose

* test(memory): add setup-memory tests with mock remote

* docs(memory): add ADR and handoff

* docs(handoff): set PR number

* fix(docs): correct handoff and ADR filenames to PR number

* docs: update project map + handoff + ADR

* fix(memory): address code review comments

---------

Co-authored-by: opencode-agent <agent@slaid098.dev>
2026-07-24 03:26:16 +03:00

104 lines
4.1 KiB
Python

"""Tests for .opencode/tools/memory-setup.ts — the memory-setup custom tool.
Mirrors ``tests/test_pipeline_status_tool.py`` / ``test_spec_status_tool.py``:
exercises the tool's ``execute()`` function via ``tests/_ts_loader.mjs``
(a node CommonJS sandbox that strips TS-only syntax, stubs
``@opencode-ai/plugin``, and replaces ``import.meta.dir`` with the real
``.opencode/tools`` directory).
The loader is parameterized via the ``TS_FILE`` env var so the same harness
works for every TS tool wrapper. These tests set
``TS_FILE=.opencode/tools/memory-setup.ts``.
Modes used (same as the pipeline-status / spec-status tool tests):
- ``load`` — sanity-check that the tool loads and declares no args.
- ``exec_stub`` — call execute with a stubbed spawnSync to verify:
(a) success path: exit 0 → trimmed stdout returned,
(b) failure path: exit 1 → ``⚠️ memory-setup failed (exit 1): <stderr>``.
"""
import json
import os
import subprocess
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
LOADER = REPO_ROOT / "tests" / "_ts_loader.mjs"
TS_FILE = REPO_ROOT / ".opencode" / "tools" / "memory-setup.ts"
TS_FILE_REL = ".opencode/tools/memory-setup.ts"
def _run_loader(*args: str) -> dict:
"""Invoke the loader with ``TS_FILE`` env set to memory-setup.ts and parse JSON stdout."""
env = {**os.environ, "TS_FILE": TS_FILE_REL}
proc = subprocess.run(
["node", str(LOADER), *args],
capture_output=True,
text=True,
check=False,
cwd=str(REPO_ROOT),
timeout=60,
env=env,
)
if proc.returncode != 0:
raise RuntimeError(
f"_ts_loader.mjs {' '.join(args)} failed (exit {proc.returncode}):\n"
f"stdout: {proc.stdout}\nstderr: {proc.stderr}"
)
return json.loads(proc.stdout)
def test_loader_can_load_tool():
"""Sanity: memory-setup.ts loads and declares no args (toggle-style tool)."""
if not TS_FILE.exists():
pytest.skip("memory-setup.ts not present")
out = _run_loader("load")
assert "description" in out
assert out["args"] == [], f"expected no args, got: {out['args']}"
def test_success():
"""execute() with exit 0 returns trimmed stdout.
memory-setup.ts follows the tunnel.ts pattern: ``r.stdout.trim()`` on
success. Stub spawnSync to return ``" memory: ready \\n"`` and verify
the tool returns ``"memory: ready"`` (whitespace stripped).
"""
out = _run_loader("exec_stub", "_", "0", " memory: ready \n", "")
result = out["result"]
assert result == "memory: ready", f"expected trimmed stdout, got: {result!r}"
def test_failure():
"""execute() with exit 1 returns ``⚠️ memory-setup failed (exit 1): <stderr>``.
The tool prepends ``⚠️ memory-setup failed (exit <N>): `` to the stderr
(falling back to stdout if stderr is empty). Stub spawnSync to return
exit 1 + a stderr string and verify the error prefix + stderr content.
"""
out = _run_loader("exec_stub", "_", "1", "", "ERROR: OPENCODE_MEMORY_REMOTE not set")
result = out["result"]
assert "⚠️ memory-setup failed" in result, f"missing error prefix: {result!r}"
assert "exit 1" in result, f"missing exit code: {result!r}"
assert "OPENCODE_MEMORY_REMOTE not set" in result, f"missing stderr: {result!r}"
def test_execute_uses_cwd_from_context():
"""execute passes ``cwd=context.worktree`` to spawnSync (ADR-023 pattern).
Like pipeline-status.ts / spec-status.ts / tunnel.ts, the memory-setup
tool wrapper propagates ``context.worktree`` as ``cwd`` to spawnSync so
the bash script runs in the git worktree root (where .opencode/ lives),
not in the opencode process cwd.
"""
out = _run_loader("exec_stub", "_", "0", "ok", "")
calls = out["calls"]
assert len(calls) == 1, f"expected 1 spawnSync call, got {len(calls)}"
opts = calls[0]["opts"]
assert opts is not None, "spawnSync called without opts — expected cwd kwarg"
assert "cwd" in opts, f"opts missing 'cwd' key — got: {opts}"
assert opts["cwd"] == str(REPO_ROOT), (
f"cwd must equal context.worktree ({REPO_ROOT}), got: {opts['cwd']!r}"
)