main
py 170 lines 4.94 KB
Raw
1 import sys
2 import threading
3 from pathlib import Path
4
5 import pytest
6 from flask import Flask
7
8 PROJECT_ROOT = Path(__file__).resolve().parents[1]
9 if str(PROJECT_ROOT) not in sys.path:
10 sys.path.insert(0, str(PROJECT_ROOT))
11
12 from api.poll import Poll
13
14
15 EXPECTED_SNAPSHOT_KEYS = {
16 "deselect_chat",
17 "context",
18 "contexts",
19 "tasks",
20 "logs",
21 "log_guid",
22 "log_version",
23 "log_progress",
24 "log_progress_active",
25 "paused",
26 "notifications",
27 "notifications_guid",
28 "notifications_version",
29 }
30
31
32 @pytest.mark.asyncio
33 async def test_poll_snapshot_matches_contract_schema_key_set_null_context():
34 app = Flask("poll-snapshot-schema-test")
35 app.secret_key = "test-secret"
36 lock = threading.RLock()
37
38 poll = Poll(app, lock)
39 payload = await poll.process(
40 {
41 "context": None,
42 "log_from": 0,
43 "notifications_from": 0,
44 "timezone": "UTC",
45 },
46 None, # Poll.process does not access the flask Request object.
47 )
48
49 assert set(payload.keys()) == EXPECTED_SNAPSHOT_KEYS
50 assert payload["deselect_chat"] is False
51 assert payload["context"] == ""
52 assert payload["logs"] == []
53 assert payload["log_guid"] == ""
54 assert payload["log_version"] == 0
55 assert payload["log_progress"] == 0
56 assert payload["log_progress_active"] is False
57 assert payload["paused"] is False
58
59
60 @pytest.mark.asyncio
61 async def test_snapshot_builder_produces_contract_schema_key_set_and_defaults():
62 from helpers import state_snapshot as snapshot
63
64 payload = await snapshot.build_snapshot(
65 context=None,
66 log_from=0,
67 notifications_from=0,
68 timezone="UTC",
69 )
70
71 snapshot.validate_snapshot_schema_v1(payload)
72 assert set(payload.keys()) == EXPECTED_SNAPSHOT_KEYS
73 assert payload["deselect_chat"] is False
74 assert payload["context"] == ""
75 assert payload["logs"] == []
76 assert payload["log_guid"] == ""
77 assert payload["log_version"] == 0
78 assert payload["log_progress"] == 0
79 assert payload["log_progress_active"] is False
80 assert payload["paused"] is False
81 assert isinstance(payload["contexts"], list)
82 assert isinstance(payload["tasks"], list)
83 assert isinstance(payload["notifications"], list)
84 assert isinstance(payload["notifications_guid"], str)
85 assert isinstance(payload["notifications_version"], int)
86 assert payload["notifications_version"] >= 0
87
88
89 @pytest.mark.asyncio
90 async def test_negotiated_incremental_snapshot_uses_null_collection_sentinel():
91 from helpers import state_snapshot as snapshot
92
93 request = snapshot.StateRequestV1(
94 context=None,
95 log_from=0,
96 notifications_from=0,
97 timezone="UTC",
98 collections_delta=True,
99 )
100 payload = await snapshot.build_snapshot_from_request(
101 request=request,
102 include_collections=False,
103 )
104
105 snapshot.validate_snapshot_schema_v1(payload)
106 assert set(payload) == EXPECTED_SNAPSHOT_KEYS
107 assert payload["contexts"] is None
108 assert payload["tasks"] is None
109
110
111 def test_state_request_collection_delta_is_optional_and_type_checked():
112 from helpers import state_snapshot as snapshot
113
114 base = {
115 "context": None,
116 "log_from": 0,
117 "notifications_from": 0,
118 "timezone": "UTC",
119 }
120
121 assert snapshot.parse_state_request_payload(base).collections_delta is False
122 assert (
123 snapshot.parse_state_request_payload(
124 {**base, "collections_delta": True}
125 ).collections_delta
126 is True
127 )
128 with pytest.raises(snapshot.StateRequestValidationError) as error:
129 snapshot.parse_state_request_payload(
130 {**base, "collections_delta": "yes"}
131 )
132 assert error.value.reason == "collections_delta_type"
133
134
135 def test_snapshot_schema_rejects_unexpected_top_level_keys():
136 from helpers import state_snapshot as snapshot
137
138 payload = {
139 "deselect_chat": False,
140 "context": "",
141 "contexts": [],
142 "tasks": [],
143 "logs": [],
144 "log_guid": "",
145 "log_version": 0,
146 "log_progress": 0,
147 "log_progress_active": False,
148 "paused": False,
149 "notifications": [],
150 "notifications_guid": "guid",
151 "notifications_version": 0,
152 "api_key": "should-not-be-here",
153 }
154
155 with pytest.raises(ValueError):
156 snapshot.validate_snapshot_schema_v1(payload)
157
158
159 def test_notification_payload_and_cursor_are_captured_together():
160 from helpers.notification import NotificationManager, NotificationPriority, NotificationType
161
162 manager = NotificationManager()
163 manager.add_notification(NotificationType.INFO, NotificationPriority.HIGH, "first")
164
165 notifications, _, version = manager.output_with_state()
166 manager.add_notification(NotificationType.INFO, NotificationPriority.HIGH, "second")
167
168 assert [item["message"] for item in notifications] == ["first"]
169 assert version == 1
170 assert [item["message"] for item in manager.output(start=version)] == ["second"]