agnes-the-ai-analyst/tests/test_init_resume_sentinel.py
ZdenekSrotyr 12db59127b
release: 0.53.0 — close Tier B trackers (#259-#261) + admin UI fix (#265) (#267)
* release: 0.53.0 — Tier B trackers + admin UI bugfix

Closes #259 (init resume sentinel), #260 (startup parquet-lock sweep),
#261 (materialized schema uses local parquet, not BQ), #265 (admin
tables apostrophe → HTML-entity escape).

Tracker notes: #262 closed as obsolete (pre-empted by 0.51.0 changes),
#266 left open pending UX clarification.

* fix(init): move resume sentinel from .agnes/ to .claude/

The clean-install integration test (test_clean_install_integration.py)
forbids creating .agnes/ in the workspace root via its
forbidden_unconditional list — that path is reserved for ~/.agnes/ in
the user's HOME (marketplace clone, CA bundle).

.claude/ is already created by agnes init for settings.json + hooks,
so dropping init-complete next to those keeps the resume sentinel
consistent with the rest of Claude Code's workspace surface and lets
the clean-install assertions pass.

Issue #259.

* docs(changelog): point #259 entry at new .claude/init-complete path

Follows the sentinel move from .agnes/ → .claude/ to keep the changelog
in sync with what 0.53.0 actually ships.
2026-05-12 16:28:41 +02:00

46 lines
2.2 KiB
Python

"""`agnes init` after an interrupted run resumes without `--force`.
Regression coverage for issue #259: pre-0.53 every killed `agnes init`
left `CLAUDE.md` on disk but no completion marker; the next attempt
errored with `partial_state` and forced a full re-download.
"""
from pathlib import Path
def test_init_complete_constant_points_at_dotfile():
"""The sentinel lives under `.claude/` (already created by init for
settings.json + hooks) so it doesn't pollute the workspace root and
doesn't trip the `forbidden_unconditional` check in
test_clean_install_integration.py (which bans `.agnes/`)."""
from cli.commands.init import _INIT_COMPLETE_FILE
assert _INIT_COMPLETE_FILE.startswith(".claude/")
assert _INIT_COMPLETE_FILE.endswith("init-complete")
def test_workspace_without_sentinel_is_treated_as_resumable(tmp_path: Path):
"""A workspace with CLAUDE.md but no completion sentinel must NOT
raise `partial_state` — it's a resume."""
# We exercise the gate logic directly by checking what the
# path-existence check sees.
(tmp_path / "CLAUDE.md").write_text("# Acme — AI Data Analyst\n", encoding="utf-8")
# Sentinel absent.
from cli.commands.init import _INIT_COMPLETE_FILE, _INIT_MARKER
assert _INIT_MARKER in (tmp_path / "CLAUDE.md").read_text()
assert not (tmp_path / _INIT_COMPLETE_FILE).exists()
# If both conditions hold (marker present, sentinel absent), the
# init flow's early-out should NOT fire. We can't easily run the
# full init command in a unit test, but the boolean condition is
# testable.
is_resumable = (tmp_path / "CLAUDE.md").exists() and not (tmp_path / _INIT_COMPLETE_FILE).exists()
assert is_resumable
def test_workspace_with_sentinel_blocks_without_force(tmp_path: Path):
"""Both CLAUDE.md AND sentinel present → require --force (old behavior)."""
(tmp_path / "CLAUDE.md").write_text("# Acme — AI Data Analyst\n", encoding="utf-8")
sentinel = tmp_path / ".claude" / "init-complete"
sentinel.parent.mkdir(parents=True, exist_ok=True)
sentinel.write_text("completed_at: 2026-05-12T10:00:00+00:00\n", encoding="utf-8")
is_blocked = (tmp_path / "CLAUDE.md").exists() and sentinel.exists()
assert is_blocked