personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

test: make delete and awareness deflakes deterministic

Test-only determinism work with zero production diff. Deflakes:

- solstone/apps/transcripts/tests/test_segment_routes.py::test_cancel_delete_segment_within_window_keeps_directory
- tests/test_awareness_call_parity.py::test_log_read_fetches_all_entries_past_default_cap_and_page_boundary
- tests/test_awareness_routes.py::test_awareness_imports_post_record
- tests/test_awareness_routes.py::test_awareness_imports_post_nudge

Removes two root causes: the real threading.Timer path from the transcripts delete cluster, and 175 fsync'd append_log calls plus whole-Convey create_app startup from awareness tests. Real timer and TTL semantics remain owned by tests/test_deferred_deletes.py. The faked awareness owner functions are now covered directly by persistence tests in tests/test_awareness.py.

+216 -98
+74 -35
solstone/apps/transcripts/tests/test_segment_routes.py
··· 11 11 import subprocess 12 12 import sys 13 13 import time 14 + from collections.abc import Callable 14 15 from datetime import datetime 15 16 from pathlib import Path 16 17 17 18 import av 18 19 import pytest 19 20 21 + import solstone.apps.transcripts.routes as routes 20 22 from solstone.apps.transcripts.routes import ( 21 23 _attach_streams_to_ranges, 22 24 _segment_modality_signals, ··· 37 39 FIXTURE_DAY = "20260304" 38 40 FIXTURE_STREAM = "default" 39 41 FIXTURE_SEGMENT = "090000_300" 42 + 43 + 44 + class _FakeDeferredDeletes: 45 + def __init__(self) -> None: 46 + self.scheduled: list[tuple[str, Callable[[], None], float]] = [] 47 + self._pending: dict[str, tuple[Callable[[], None], float]] = {} 48 + 49 + def schedule_with_id( 50 + self, 51 + pending_id: str, 52 + commit_fn: Callable[[], None], 53 + ttl_seconds: float = 10.0, 54 + ) -> str: 55 + self.scheduled.append((pending_id, commit_fn, ttl_seconds)) 56 + self._pending[pending_id] = (commit_fn, ttl_seconds) 57 + return pending_id 58 + 59 + def cancel(self, pending_id: str) -> bool: 60 + if pending_id not in self._pending: 61 + return False 62 + self._pending.pop(pending_id) 63 + return True 64 + 65 + def fire(self, pending_id: str) -> None: 66 + commit_fn, _ttl_seconds = self._pending.pop(pending_id) 67 + commit_fn() 68 + 69 + 70 + @pytest.fixture 71 + def fake_deferred_deletes(monkeypatch): 72 + fake = _FakeDeferredDeletes() 73 + monkeypatch.setattr(routes, "deferred_deletes", fake) 74 + return fake 40 75 41 76 42 77 def _assert_reason(response, *, error: str, reason_code: str, detail: str) -> None: ··· 1853 1888 1854 1889 1855 1890 def test_delete_segment_happy_path_removes_segment_directory( 1856 - client, journal_copy, monkeypatch 1891 + client, journal_copy, monkeypatch, fake_deferred_deletes 1857 1892 ): 1893 + monkeypatch.setattr(routes, "is_supervisor_up", lambda: True) 1894 + emit_calls = [] 1858 1895 monkeypatch.setattr( 1859 - "solstone.apps.transcripts.routes.is_supervisor_up", lambda: True 1896 + routes, 1897 + "emit", 1898 + lambda tract, event, **payload: emit_calls.append((tract, event, payload)), 1860 1899 ) 1861 - monkeypatch.setattr("solstone.apps.transcripts.routes.SEGMENT_DELETE_TTL", 0.05) 1862 1900 segment_dir = ( 1863 1901 journal_copy / "chronicle" / FIXTURE_DAY / FIXTURE_STREAM / FIXTURE_SEGMENT 1864 1902 ) ··· 1868 1906 ) 1869 1907 1870 1908 assert response.status_code == 200 1909 + pending_id = response.get_json()["pending"] 1871 1910 assert response.get_json()["deleted"] == FIXTURE_SEGMENT 1872 - time.sleep(0.2) 1911 + fake_deferred_deletes.fire(pending_id) 1873 1912 assert not segment_dir.exists() 1913 + assert emit_calls == [ 1914 + ("supervisor", "request", {"cmd": ["journal", "indexer", "--rescan-full"]}) 1915 + ] 1874 1916 1875 1917 1876 1918 def test_delete_segment_includes_search_index_warning_when_supervisor_is_down( 1877 - client, monkeypatch 1919 + client, fake_deferred_deletes 1878 1920 ): 1879 - monkeypatch.setattr("solstone.apps.transcripts.routes.SEGMENT_DELETE_TTL", 0.05) 1880 1921 response = client.delete( 1881 1922 f"/app/transcripts/api/segment/{FIXTURE_DAY}/{FIXTURE_STREAM}/{FIXTURE_SEGMENT}" 1882 1923 ) ··· 1886 1927 assert data["success"] is True 1887 1928 assert data["deleted"] == FIXTURE_SEGMENT 1888 1929 assert data["search_index_warning"] is True 1889 - time.sleep(0.2) 1930 + assert fake_deferred_deletes.scheduled[0][0] == data["pending"] 1890 1931 1891 1932 1892 1933 def test_delete_segment_omits_search_index_warning_when_supervisor_is_up( 1893 - client, monkeypatch 1934 + client, monkeypatch, fake_deferred_deletes 1894 1935 ): 1895 - monkeypatch.setattr( 1896 - "solstone.apps.transcripts.routes.is_supervisor_up", lambda: True 1897 - ) 1898 - monkeypatch.setattr("solstone.apps.transcripts.routes.SEGMENT_DELETE_TTL", 0.05) 1936 + monkeypatch.setattr(routes, "is_supervisor_up", lambda: True) 1899 1937 1900 1938 response = client.delete( 1901 1939 f"/app/transcripts/api/segment/{FIXTURE_DAY}/{FIXTURE_STREAM}/{FIXTURE_SEGMENT}" 1902 1940 ) 1903 1941 1904 1942 assert response.status_code == 200 1905 - assert response.get_json()["deleted"] == FIXTURE_SEGMENT 1906 - time.sleep(0.2) 1943 + data = response.get_json() 1944 + assert data["deleted"] == FIXTURE_SEGMENT 1945 + assert "search_index_warning" not in data 1946 + assert fake_deferred_deletes.scheduled[0][0] == data["pending"] 1907 1947 1908 1948 1909 - def test_delete_segment_returns_pending_response_shape(client, monkeypatch): 1910 - monkeypatch.setattr( 1911 - "solstone.apps.transcripts.routes.is_supervisor_up", lambda: True 1912 - ) 1913 - monkeypatch.setattr("solstone.apps.transcripts.routes.SEGMENT_DELETE_TTL", 0.05) 1949 + def test_delete_segment_returns_pending_response_shape( 1950 + client, monkeypatch, fake_deferred_deletes 1951 + ): 1952 + monkeypatch.setattr(routes, "is_supervisor_up", lambda: True) 1914 1953 before_ms = int(time.time() * 1000) 1915 1954 1916 1955 response = client.delete( ··· 1922 1961 assert data["success"] is True 1923 1962 assert data["deleted"] == FIXTURE_SEGMENT 1924 1963 assert re.fullmatch(r"[0-9a-f]{32}", data["pending"]) 1925 - assert data["ttl_seconds"] == 0.05 1964 + assert data["ttl_seconds"] == routes.SEGMENT_DELETE_TTL 1926 1965 assert data["commit_at_ms"] >= before_ms 1927 - time.sleep(0.2) 1966 + scheduled_id, commit_fn, ttl_seconds = fake_deferred_deletes.scheduled[0] 1967 + assert scheduled_id == data["pending"] 1968 + assert callable(commit_fn) 1969 + assert ttl_seconds == routes.SEGMENT_DELETE_TTL 1928 1970 1929 1971 1930 1972 def test_cancel_delete_segment_within_window_keeps_directory( 1931 - client, journal_copy, monkeypatch 1973 + client, journal_copy, fake_deferred_deletes 1932 1974 ): 1933 - monkeypatch.setattr("solstone.apps.transcripts.routes.SEGMENT_DELETE_TTL", 5.0) 1934 1975 segment_dir = ( 1935 1976 journal_copy / "chronicle" / FIXTURE_DAY / FIXTURE_STREAM / FIXTURE_SEGMENT 1936 1977 ) ··· 1944 1985 1945 1986 assert cancel_response.status_code == 200 1946 1987 assert cancel_response.get_json() == {"cancelled": pending_id} 1947 - time.sleep(0.3) 1948 1988 assert segment_dir.exists() 1989 + assert fake_deferred_deletes.cancel(pending_id) is False 1949 1990 1950 1991 1951 - def test_cancel_delete_segment_too_late_after_commit(client, journal_copy, monkeypatch): 1952 - monkeypatch.setattr("solstone.apps.transcripts.routes.SEGMENT_DELETE_TTL", 0.05) 1992 + def test_cancel_delete_segment_too_late_after_commit( 1993 + client, journal_copy, fake_deferred_deletes 1994 + ): 1953 1995 segment_dir = ( 1954 1996 journal_copy / "chronicle" / FIXTURE_DAY / FIXTURE_STREAM / FIXTURE_SEGMENT 1955 1997 ) ··· 1959 2001 ) 1960 2002 pending_id = delete_response.get_json()["pending"] 1961 2003 1962 - time.sleep(0.2) 2004 + fake_deferred_deletes.fire(pending_id) 1963 2005 cancel_response = client.post(f"/app/transcripts/api/cancel-delete/{pending_id}") 1964 2006 1965 2007 assert cancel_response.status_code == 410 ··· 1997 2039 1998 2040 1999 2041 def test_delete_segment_writes_pending_and_committed_audit_rows( 2000 - client, journal_copy, monkeypatch 2042 + client, journal_copy, monkeypatch, fake_deferred_deletes 2001 2043 ): 2002 - monkeypatch.setattr( 2003 - "solstone.apps.transcripts.routes.is_supervisor_up", lambda: True 2004 - ) 2005 - monkeypatch.setattr("solstone.apps.transcripts.routes.SEGMENT_DELETE_TTL", 0.05) 2044 + monkeypatch.setattr(routes, "is_supervisor_up", lambda: True) 2006 2045 2007 2046 delete_response = client.delete( 2008 2047 f"/app/transcripts/api/segment/{FIXTURE_DAY}/{FIXTURE_STREAM}/{FIXTURE_SEGMENT}" ··· 2017 2056 for row in day_rows 2018 2057 ) 2019 2058 2020 - time.sleep(0.2) 2059 + fake_deferred_deletes.fire(pending_id) 2021 2060 day_rows = _action_log_rows(journal_copy, FIXTURE_DAY) 2022 2061 assert any( 2023 2062 row["action"] == "segment_delete" ··· 2028 2067 2029 2068 2030 2069 def test_cancel_delete_segment_writes_cancelled_audit_row( 2031 - client, journal_copy, monkeypatch 2070 + client, journal_copy, fake_deferred_deletes 2032 2071 ): 2033 - monkeypatch.setattr("solstone.apps.transcripts.routes.SEGMENT_DELETE_TTL", 5.0) 2034 2072 cancel_response = client.delete( 2035 2073 f"/app/transcripts/api/segment/{FIXTURE_DAY}/{FIXTURE_STREAM}/{FIXTURE_SEGMENT}" 2036 2074 ) ··· 2048 2086 and row["params"].get("phase") == "cancelled" 2049 2087 for row in cancel_rows 2050 2088 ) 2089 + assert fake_deferred_deletes.cancel(cancel_pending_id) is False
+14
tests/_awareness_harness.py
··· 1 + # SPDX-License-Identifier: AGPL-3.0-only 2 + # Copyright (c) 2026 sol pbc 3 + 4 + from flask import Flask 5 + from flask.testing import FlaskClient 6 + 7 + from solstone.apps.awareness.routes import awareness_bp 8 + 9 + 10 + def make_awareness_test_client() -> FlaskClient: 11 + app = Flask(__name__) 12 + app.config["TESTING"] = True 13 + app.register_blueprint(awareness_bp) 14 + return app.test_client()
+41
tests/test_awareness.py
··· 141 141 assert entries[0]["detail"] == "meeting detected" 142 142 143 143 144 + class TestImportTracking: 145 + def test_record_import_persists_state(self): 146 + from solstone.think.awareness import get_imports, record_import 147 + 148 + record_import("chatgpt") 149 + 150 + imports = get_imports() 151 + assert imports["has_imported"] is True 152 + assert imports["import_count"] == 1 153 + assert imports["sources_used"] == ["chatgpt"] 154 + assert imports["offer_declined"] is None 155 + assert imports["last_nudge"] is None 156 + 157 + def test_record_import_offer_declined_persists_state(self): 158 + from solstone.think.awareness import ( 159 + get_imports, 160 + record_import_offer_declined, 161 + ) 162 + 163 + record_import_offer_declined() 164 + 165 + imports = get_imports() 166 + assert imports["has_imported"] is False 167 + assert imports["import_count"] == 0 168 + assert imports["sources_used"] == [] 169 + assert re.fullmatch(r"\d{8}T\d{2}:\d{2}:\d{2}", imports["offer_declined"]) 170 + assert imports["last_nudge"] is None 171 + 172 + def test_record_import_nudge_persists_state(self): 173 + from solstone.think.awareness import get_imports, record_import_nudge 174 + 175 + record_import_nudge() 176 + 177 + imports = get_imports() 178 + assert imports["has_imported"] is False 179 + assert imports["import_count"] == 0 180 + assert imports["sources_used"] == [] 181 + assert imports["offer_declined"] is None 182 + assert re.fullmatch(r"\d{8}T\d{2}:\d{2}:\d{2}", imports["last_nudge"]) 183 + 184 + 144 185 class TestJournalState: 145 186 def test_first_daily_ready_via_update_state(self): 146 187 from solstone.think.awareness import get_current, update_state
+20 -8
tests/test_awareness_call_parity.py
··· 13 13 from solstone.convey.reasons import AWARENESS_BUSY 14 14 from solstone.think.convey_client import ConveyClient 15 15 from solstone.think.journal_io import LockTimeout 16 - from tests._baseline_harness import make_test_client, mark_setup_complete 16 + from tests._awareness_harness import make_awareness_test_client 17 17 18 18 FROZEN_MS = 1700000000000 19 19 FROZEN_ISO = "20260415T12:00:00" ··· 25 25 # Env must point at the tmp journal so BOTH the seed helpers (append_log/ 26 26 # update_state) and the in-process route handlers resolve get_journal() to it. 27 27 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 28 - mark_setup_complete(tmp_path) 29 28 return tmp_path 30 29 31 30 32 31 @pytest.fixture 33 32 def runner(journal, monkeypatch): 34 - client = ConveyClient(session=make_test_client(journal), base_url="") 33 + client = ConveyClient(session=make_awareness_test_client(), base_url="") 35 34 monkeypatch.setattr("solstone.apps.awareness.call.get_client", lambda: client) 36 35 return CliRunner() 37 36 ··· 49 48 awareness_dir = journal / "awareness" 50 49 awareness_dir.mkdir(exist_ok=True) 51 50 (awareness_dir / "current.json").write_text(json.dumps(state), encoding="utf-8") 51 + 52 + 53 + def _write_awareness_log(journal: Path, day: str, count: int) -> None: 54 + awareness_dir = journal / "awareness" 55 + awareness_dir.mkdir(exist_ok=True) 56 + entries = [ 57 + json.dumps({"ts": FROZEN_MS + i, "kind": "observation", "message": f"m{i}"}) 58 + for i in range(count) 59 + ] 60 + (awareness_dir / f"{day}.jsonl").write_text( 61 + "\n".join(entries) + "\n", 62 + encoding="utf-8", 63 + ) 52 64 53 65 54 66 def test_status_full_state_json(runner): ··· 166 178 assert result.stdout == "No entries found.\n" 167 179 168 180 169 - def test_log_read_fetches_all_entries_past_default_cap_and_page_boundary(runner): 170 - for i in range(25): 171 - awareness_mod.append_log("observation", message=f"m{i}", day="20260101") 172 - for i in range(150): 173 - awareness_mod.append_log("observation", message=f"m{i}", day="20260102") 181 + def test_log_read_fetches_all_entries_past_default_cap_and_page_boundary( 182 + runner, journal 183 + ): 184 + _write_awareness_log(journal, "20260101", 25) 185 + _write_awareness_log(journal, "20260102", 150) 174 186 175 187 capped = runner.invoke(app, ["log-read", "20260101"]) 176 188 paged = runner.invoke(app, ["log-read", "20260102"])
+67 -55
tests/test_awareness_routes.py
··· 3 3 4 4 from __future__ import annotations 5 5 6 - import json 6 + from unittest.mock import Mock 7 + 8 + import pytest 7 9 8 10 from solstone.apps import AppRegistry 9 11 from solstone.think.awareness import append_log 12 + from tests._awareness_harness import make_awareness_test_client 10 13 from tests._baseline_harness import make_test_client 11 14 12 15 PREFIX = "/app/awareness" 13 16 14 17 18 + @pytest.fixture 19 + def client(tmp_path, monkeypatch): 20 + monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 21 + return make_awareness_test_client() 22 + 23 + 15 24 def _assert_error(response, status: int) -> dict: 16 25 assert response.status_code == status 17 26 data = response.get_json() ··· 21 30 return data 22 31 23 32 33 + def _fake_import_owners(monkeypatch): 34 + sentinels = { 35 + "record": {"sentinel": "record-return", "payload": ["verbatim-record"]}, 36 + "declined": {"sentinel": "declined-return", "payload": ["verbatim-declined"]}, 37 + "nudge": {"sentinel": "nudge-return", "payload": ["verbatim-nudge"]}, 38 + } 39 + owners = { 40 + "record": Mock(return_value=sentinels["record"]), 41 + "declined": Mock(return_value=sentinels["declined"]), 42 + "nudge": Mock(return_value=sentinels["nudge"]), 43 + } 44 + monkeypatch.setattr( 45 + "solstone.apps.awareness.routes.record_import", owners["record"] 46 + ) 47 + monkeypatch.setattr( 48 + "solstone.apps.awareness.routes.record_import_offer_declined", 49 + owners["declined"], 50 + ) 51 + monkeypatch.setattr( 52 + "solstone.apps.awareness.routes.record_import_nudge", 53 + owners["nudge"], 54 + ) 55 + return owners, sentinels 56 + 57 + 24 58 def test_awareness_api_only_discovery_registers_blueprint_outside_menu(): 25 59 registry = AppRegistry() 26 60 registry.discover() ··· 37 71 assert response.status_code == 404 38 72 39 73 40 - def test_awareness_state_empty_journal_returns_empty_dict(tmp_path, monkeypatch): 41 - monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 42 - config_dir = tmp_path / "config" 43 - config_dir.mkdir(parents=True, exist_ok=True) 44 - (config_dir / "journal.json").write_text( 45 - json.dumps({"setup": {"completed_at": 1}}), 46 - encoding="utf-8", 47 - ) 48 - client = make_test_client(tmp_path) 49 - 74 + def test_awareness_state_empty_journal_returns_empty_dict(client): 50 75 response = client.get(f"{PREFIX}/api/state") 51 76 52 77 assert response.status_code == 200 ··· 54 79 55 80 56 81 def test_awareness_state_full_state_includes_journal(journal_copy): 57 - client = make_test_client(journal_copy) 82 + client = make_awareness_test_client() 58 83 59 84 response = client.get(f"{PREFIX}/api/state") 60 85 ··· 63 88 64 89 65 90 def test_awareness_state_known_section(journal_copy): 66 - client = make_test_client(journal_copy) 91 + client = make_awareness_test_client() 67 92 68 93 response = client.get(f"{PREFIX}/api/state?section=journal") 69 94 ··· 71 96 assert response.get_json()["first_daily_ready"] is True 72 97 73 98 74 - def test_awareness_state_unknown_section(journal_copy): 75 - client = make_test_client(journal_copy) 76 - 99 + def test_awareness_state_unknown_section(client): 77 100 response = client.get(f"{PREFIX}/api/state?section=nope") 78 101 79 102 data = _assert_error(response, 404) 80 103 assert data["reason_code"] == "awareness_section_not_found" 81 104 82 105 83 - def test_awareness_imports_get_defaults(journal_copy): 84 - client = make_test_client(journal_copy) 85 - 106 + def test_awareness_imports_get_defaults(client): 86 107 response = client.get(f"{PREFIX}/api/imports") 87 108 88 109 assert response.status_code == 200 89 110 assert response.get_json()["has_imported"] is False 90 111 91 112 92 - def test_awareness_imports_post_record(journal_copy): 93 - client = make_test_client(journal_copy) 113 + def test_awareness_imports_post_record(client, monkeypatch): 114 + owners, sentinels = _fake_import_owners(monkeypatch) 94 115 95 116 response = client.post(f"{PREFIX}/api/imports", json={"record": "chatgpt"}) 96 117 97 118 assert response.status_code == 200 98 - data = response.get_json() 99 - assert data["has_imported"] is True 100 - assert "chatgpt" in data["sources_used"] 119 + assert response.get_json() == sentinels["record"] 120 + owners["record"].assert_called_once_with("chatgpt") 121 + owners["declined"].assert_not_called() 122 + owners["nudge"].assert_not_called() 101 123 102 124 103 - def test_awareness_imports_post_declined(journal_copy): 104 - client = make_test_client(journal_copy) 125 + def test_awareness_imports_post_declined(client, monkeypatch): 126 + owners, sentinels = _fake_import_owners(monkeypatch) 105 127 106 128 response = client.post(f"{PREFIX}/api/imports", json={"declined": True}) 107 129 108 130 assert response.status_code == 200 109 - assert response.get_json()["offer_declined"] is not None 131 + assert response.get_json() == sentinels["declined"] 132 + owners["declined"].assert_called_once_with() 133 + owners["record"].assert_not_called() 134 + owners["nudge"].assert_not_called() 110 135 111 136 112 - def test_awareness_imports_post_nudge(journal_copy): 113 - client = make_test_client(journal_copy) 137 + def test_awareness_imports_post_nudge(client, monkeypatch): 138 + owners, sentinels = _fake_import_owners(monkeypatch) 114 139 115 140 response = client.post(f"{PREFIX}/api/imports", json={"nudge": True}) 116 141 117 142 assert response.status_code == 200 118 - assert response.get_json()["last_nudge"] is not None 143 + assert response.get_json() == sentinels["nudge"] 144 + owners["nudge"].assert_called_once_with() 145 + owners["record"].assert_not_called() 146 + owners["declined"].assert_not_called() 119 147 120 148 121 - def test_awareness_imports_post_multi_action_400(journal_copy): 122 - client = make_test_client(journal_copy) 123 - 149 + def test_awareness_imports_post_multi_action_400(client): 124 150 response = client.post( 125 151 f"{PREFIX}/api/imports", 126 152 json={"record": "x", "nudge": True}, ··· 129 155 _assert_error(response, 400) 130 156 131 157 132 - def test_awareness_imports_post_zero_action_400(journal_copy): 133 - client = make_test_client(journal_copy) 134 - 158 + def test_awareness_imports_post_zero_action_400(client): 135 159 response = client.post(f"{PREFIX}/api/imports", json={}) 136 160 137 161 _assert_error(response, 400) 138 162 139 163 140 - def test_awareness_log_collection_limit_and_kind_filter(journal_copy): 164 + def test_awareness_log_collection_limit_and_kind_filter(client): 141 165 append_log("observation", message="a") 142 166 append_log("observation", message="b") 143 167 append_log("nudge", message="c") 144 - client = make_test_client(journal_copy) 145 168 146 169 response = client.get(f"{PREFIX}/api/log?limit=2") 147 170 ··· 158 181 assert all(item["kind"] == "observation" for item in data["items"]) 159 182 160 183 161 - def test_awareness_log_day_param_uses_requested_day(journal_copy): 184 + def test_awareness_log_day_param_uses_requested_day(client): 162 185 append_log("observation", message="old", day="20260101") 163 - client = make_test_client(journal_copy) 164 186 165 187 response = client.get(f"{PREFIX}/api/log?day=20260101") 166 188 ··· 175 197 assert all(item.get("message") != "old" for item in response.get_json()["items"]) 176 198 177 199 178 - def test_awareness_log_post_creates_201(journal_copy): 179 - client = make_test_client(journal_copy) 180 - 200 + def test_awareness_log_post_creates_201(client): 181 201 response = client.post( 182 202 f"{PREFIX}/api/log", 183 203 json={"kind": "observation", "message": "hi"}, ··· 189 209 assert "ts" in data 190 210 191 211 192 - def test_awareness_log_post_missing_kind_400(journal_copy): 193 - client = make_test_client(journal_copy) 194 - 212 + def test_awareness_log_post_missing_kind_400(client): 195 213 response = client.post(f"{PREFIX}/api/log", json={}) 196 214 197 215 _assert_error(response, 400) 198 216 199 217 200 - def test_awareness_log_post_empty_kind_400(journal_copy): 201 - client = make_test_client(journal_copy) 202 - 218 + def test_awareness_log_post_empty_kind_400(client): 203 219 response = client.post(f"{PREFIX}/api/log", json={"kind": ""}) 204 220 205 221 _assert_error(response, 400) 206 222 207 223 208 - def test_awareness_post_endpoints_no_body_400(journal_copy): 209 - client = make_test_client(journal_copy) 210 - 224 + def test_awareness_post_endpoints_no_body_400(client): 211 225 imports_response = client.post(f"{PREFIX}/api/imports") 212 226 log_response = client.post(f"{PREFIX}/api/log") 213 227 ··· 217 231 assert log_data["reason_code"] == "missing_request_body" 218 232 219 233 220 - def test_awareness_post_endpoints_non_json_400(journal_copy): 221 - client = make_test_client(journal_copy) 222 - 234 + def test_awareness_post_endpoints_non_json_400(client): 223 235 imports_response = client.post( 224 236 f"{PREFIX}/api/imports", 225 237 data="not json",