From f66b709d82d8a30fca431b90d26cf763b1cdee24 Mon Sep 17 00:00:00 2001 From: Jer Miller Date: Mon, 13 Jul 2026 14:56:40 -0600 Subject: [PATCH] test(oura): move real-process lock probes to the integration tier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 91f8fd9f3 on origin/main established that tests using real external processes belong in operator validation, not unit CI, adding the `integration` marker and `PYTEST_UNIT_ARGS := -m "not integration and not performance"` to `make test` / `test-cov` / `coverage`; 2cf9e9585 then pointed `make ci` at `make test`. The health-lane branch predates that policy, so its two cross-process Oura lock proofs arrived unmarked and would have run inside the unit gate — precisely the class of probe the policy exiles. 704d53bb9 shows the repo actively rewriting tests away from real-process mechanics, so leaving them unmarked would regress a live policy. Marking them `integration` alone, however, would leave `make ci` with no exclusion proof at all, while the scope requires exclusion be demonstrated with distinct processes *or file descriptors* — not only threads. So both subprocess probes are marked `integration` (they still run under `make test-integration`), and the exclusion proof is retained inside the unit gate by two in-process tests that use distinct file descriptors. This is sound because hold_lock uses fcntl.flock(LOCK_EX | LOCK_NB), which is per open-file description: two separate open() calls genuinely exclude one another within a single process, so this is a real exclusion proof and not a thread simulation. test_private_import_lock_excludes_distinct_fds_in_one_process pins that per-open-file-description semantics directly, and fails loudly if the primitive is ever swapped for POSIX per-process record locking, under which a second descriptor would silently succeed and invalidate the proof. test_oura_sync_lock_timeout_with_same_process_distinct_fd drives save-mode sync against a held lock and asserts the structured OuraSyncLockError names the journal, lock path, and timeout, leaks no token, request URL, or health payload, performs no fetch, and writes no bundle, raw, normalized, dedupe, manifest, or cursor state. --- tests/test_oura_importer.py | 81 +++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/test_oura_importer.py b/tests/test_oura_importer.py index c9b32b0f2..c5fb858ad 100644 --- a/tests/test_oura_importer.py +++ b/tests/test_oura_importer.py @@ -51,6 +51,10 @@ from solstone.think.importers.pre_save_gate import ( approval_path_for_journal, oura_sync_approval_path_for_journal, ) +from solstone.think.importers.shared import ( + ImportLockTimeout, + hold_private_import_lock, +) from solstone.think.importers.sync import SYNCABLE_REGISTRY, get_syncable_backends FIXTURE_ROOT = ( @@ -1817,6 +1821,82 @@ def test_oura_sync_missing_auth_repairs_imports_dir_before_lock( ) +def test_private_import_lock_excludes_distinct_fds_in_one_process( + tmp_path: Path, +) -> None: + lock_path = tmp_path / "journal" / "imports" / "oura.json" + + with hold_private_import_lock(lock_path, timeout=1.0, poll_interval=0.0): + # This pins flock's per-open-file-description behavior. If this lock ever + # becomes POSIX per-process locking, the second acquire would succeed. + with pytest.raises(ImportLockTimeout) as exc_info: + with hold_private_import_lock( + lock_path, + timeout=0.0, + poll_interval=0.0, + ): + pass + + assert exc_info.value.path == lock_path + assert exc_info.value.timeout == 0.0 + + +def test_oura_sync_lock_timeout_with_same_process_distinct_fd( + tmp_path: Path, + monkeypatch, +) -> None: + journal = _use_journal(tmp_path, monkeypatch) + _write_sync_artifact(journal, _sync_artifact(journal)) + lock_path = journal / "imports" / "oura.json" + transport = _fixture_transport() + monkeypatch.setattr(oura, "OURA_SYNC_LOCK_TIMEOUT", 0.2) + + with hold_private_import_lock(lock_path, timeout=1.0, poll_interval=0.0): + with pytest.raises(oura.OuraSyncLockError) as exc_info: + oura.backend.sync( + journal, + dry_run=False, + confirm_health_save=True, + client=_canned_client(transport), + today=dt.date(2026, 1, 10), + ) + + payload = exc_info.value.to_dict() + text = ( + f"{exc_info.value!s}\n" + f"{exc_info.value.format_text()}\n" + f"{exc_info.value.to_dict()!r}" + ) + assert payload["error"] == "oura_sync_lock_timeout" + assert payload["journal_root"] == str(journal) + assert payload["lock_path"] == str(lock_path) + assert payload["timeout_seconds"] == 0.2 + assert str(journal) in text + assert str(lock_path) in text + for forbidden in ( + "Bearer", + "synthetic-access", + "synthetic-refresh", + "access_token", + "refresh_token", + "api.ouraring", + "daily_readiness", + "synthetic-readiness", + ): + assert forbidden not in text + + assert transport.calls == [] + contents = _imports_contents(journal) + assert "imports/oura.json.lock" in contents + assert "imports/oura.json" not in contents + assert "imports/health-dedupe.sqlite" not in contents + assert not any(entry.startswith("imports/2026") for entry in contents) + assert not any(entry.endswith("manifest.json") for entry in contents) + assert "imports/content_manifest.jsonl" not in contents + assert not any("/raw/" in entry or "/normalized/" in entry for entry in contents) + + +@pytest.mark.integration def test_overlapping_save_sync_loser_gets_structured_lock_timeout( tmp_path: Path, monkeypatch, @@ -1894,6 +1974,7 @@ oura.backend.sync( assert child.returncode == 0, stdout + stderr +@pytest.mark.integration def test_in_lock_gate_rechecks_artifact_before_fetch_after_wait( tmp_path: Path, monkeypatch, -- 2.51.2