Conversation
…(RFC-761 PR-2)
Add HTTP-layer tests for the Settings Hooks sync surface (Phase D),
satisfying ADR-0001 §5 c2 (round-trip) and §5 c4 (conflict path) at
the FastAPI-route layer:
- test_sync_route_round_trip_unidirectional — write canonical →
POST /api/context/settings/sync → GET /api/context/settings asserts
in_sync, AND target file on disk preserves user-authored hooks +
non-hook keys (permissions, env). The unidirectional shape per the
§5 c2 wording: Settings has no reverse-import API by design because
additive merge cannot distinguish canonical-authored from
user-authored entries.
- test_resolve_route_returns_soft_abort_on_stale_mtime — pins the
POST /api/context/settings/resolve response contract: HTTP 200 +
{"status": "aborted", "reason": <... modified by another process ...>}
when target mtime changes between the route's capture and
recheck. Mirrors the helper-level pattern from
TestClaudeSettingsMergeConcurrent.test_aborts_on_mtime_change but
exercises the route handler.
Helper-level merge / concurrent / atomic-write behavior is already
covered by the 21 existing tests in this file; these two new tests
specifically pin the FastAPI surface that production UI depends on,
so a future refactor of the route layer can't silently regress the
in-sync detection or soft-abort response shape.
No i18n JSON changes — existing settings.hooks.* keys (en/ko both)
already cover the prod overview card; the original plan's i18n step
was based on a wrong assumption (verified via inventory).
PR-3 (settings_sync router move from _DEV_ONLY_ROUTERS to
_PROD_ROUTERS + STATE.uiMode === 'dev' gate removal in
context-gateway.js) ships separately. These tests use mode="dev" so
they pass against current main and remain correct after PR-3 lands
(the router is mounted in either mode).
Refs: #761
Co-Authored-By: Claude <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
packages/memtomem/tests/test_context_settings.pypinning the FastAPI route contract for the Settings Hooks sync surface
(Phase D), satisfying ADR-0001 §5 c2 (round-trip) and §5 c4 (conflict
path).
Why
PR-2 of RFC #761 — gap-fill against the §5 readiness contract that
landed in #769. The existing
test_context_settings.pyhas 21 testscovering merge / mtime / atomic-write at the Python helper layer, but
zero HTTP-layer tests against
/api/context/settings/*. ProductionUI depends on those routes, so a future refactor of
settings_sync.pycould silently regress in-sync detection or the soft-abort response
shape without these tests catching it.
Tests added
test_sync_route_round_trip_unidirectional(§5 c2, unidirectional shape).memtomem/settings.jsonwith one(PostToolUse, "Write|Edit")hook.~/.claude/settings.jsonwith a user-authored(PreToolUse, "Bash")hook + non-hook keys (permissions,env).POST /api/context/settings/sync {"allow_host_writes": true}→assert HTTP 200 +
claude_settings.status == "ok".GET /api/context/settings→ assertstatus == "in_sync",canonical hook surfaces under
hooks.synced.(PreToolUse, "Bash")hook +permissions+envkeys allsurvive verbatim (additive merge does not clobber).
Settings has no reverse-import API by design (additive merge cannot
distinguish canonical-authored from user-authored entries), so the
round-trip is shaped per §5 c2's unidirectional clause: write
canonical → apply via sync route → re-read via diff route + assert
target-side merge invariants.
test_resolve_route_returns_soft_abort_on_stale_mtime(§5 c4, soft-abort)(PostToolUse, "Write")rules.
_safe_load_jsonso reading the target as a sideeffect bumps its mtime — simulates a concurrent writer landing
between the route's mtime capture (line 239) and recheck
(line 262).
POST /api/context/settings/resolve→ assert HTTP 200 + body{"status": "aborted", "reason": <... modified by another process ...>}.Mirrors the helper-level pattern from
TestClaudeSettingsMergeConcurrent.test_aborts_on_mtime_change(line 243), but pins the route response contract — the helper test
covers the merge function's behavior, this test covers the FastAPI
surface that wires it up.
Scope notes
settings.hooks.*keys (10 keys, en + ko both, lines 338–347 inweb/static/locales/*.json) already cover the Settings overviewcard and section. PR-3's gate flip will use them as-is. The
original RFC-761 plan's "add
settings.ctx.settings_title" stepwas based on a wrong assumption — verified by re-reading
context-gateway.js:88-95(the card label usest('settings.hooks.title'), not a separatectx_settings.*key).§5 c3 (i18n parity, auto-discovered by
test_i18n.py) is alreadysatisfied.
mode="dev"(currently required —settings_synclivesin
_DEV_ONLY_ROUTERS). They remain correct after PR-3 moves therouter to
_PROD_ROUTERS(the router is mounted in either mode).Out of scope
STATE.uiMode === 'dev'gate removal in
context-gateway.js+ CHANGELOG entry. Shipsafter this PR lands.
prospective; gaps in already-shipped phases are tracked as hygiene
follow-ups, not regressions.
Test plan
uv run pytest packages/memtomem/tests/test_context_settings.py -v—37 tests pass (21 existing + 2 new + 14 unaffected).
uv run pytest packages/memtomem/tests/test_i18n.py -v—16 tests pass; no JSON drift.
uv run ruff check packages/memtomem/tests/test_context_settings.py—clean.
uv run ruff format --check packages/memtomem/tests/test_context_settings.py—clean.
Refs: #761
Follows: #769
🤖 Generated with Claude Code