@@ -69,6 +69,68 @@ def _make_app(send_stream=None) -> Starlette:
6969}
7070
7171
72+ # Deck PR #7910 (CardCreatedEvent etc.) emits ``{"card": Card::jsonSerialize()}``
73+ # — see ~/Software/deck/lib/Event/ACardEvent.php. Card::jsonSerialize() includes
74+ # id and stackId but not boardId (the processor falls back to iteration for
75+ # that). BoardUpdatedEvent emits only ``{"boardId": int}``.
76+ _DECK_CARD_CREATED = {
77+ "user" : {"uid" : "admin" },
78+ "time" : 1762900000 ,
79+ "event" : {
80+ "class" : "OCA\\ Deck\\ Event\\ CardCreatedEvent" ,
81+ "card" : {
82+ "id" : 4242 ,
83+ "title" : "Webhook smoke test" ,
84+ "stackId" : 16 ,
85+ },
86+ },
87+ }
88+
89+
90+ _DECK_CARD_DELETED = {
91+ "user" : {"uid" : "alice" },
92+ "time" : 1762900100 ,
93+ "event" : {
94+ "class" : "OCA\\ Deck\\ Event\\ CardDeletedEvent" ,
95+ "card" : {
96+ "id" : 4242 ,
97+ "title" : "Webhook smoke test" ,
98+ "stackId" : 16 ,
99+ },
100+ },
101+ }
102+
103+
104+ _DECK_BOARD_UPDATED = {
105+ "user" : {"uid" : "admin" },
106+ "time" : 1762900200 ,
107+ "event" : {
108+ "class" : "OCA\\ Deck\\ Event\\ BoardUpdatedEvent" ,
109+ "boardId" : 5 ,
110+ },
111+ }
112+
113+
114+ _NOTE_CREATED_MISSING_ID = {
115+ "user" : {"uid" : "admin" },
116+ "time" : 1762850300 ,
117+ "event" : {
118+ "class" : "OCP\\ Files\\ Events\\ Node\\ NodeCreatedEvent" ,
119+ "node" : {"path" : "/admin/files/Notes/no-id.md" },
120+ },
121+ }
122+
123+
124+ _DECK_CARD_CREATED_MISSING_ID = {
125+ "user" : {"uid" : "admin" },
126+ "time" : 1762900300 ,
127+ "event" : {
128+ "class" : "OCA\\ Deck\\ Event\\ CardCreatedEvent" ,
129+ "card" : {"title" : "Card without id" , "stackId" : 16 },
130+ },
131+ }
132+
133+
72134def test_index_event_queues_task_and_returns_200 ():
73135 send_stream , receive_stream = anyio .create_memory_object_stream (max_buffer_size = 4 )
74136 app = _make_app (send_stream = send_stream )
@@ -127,6 +189,93 @@ def test_unsupported_event_is_ignored():
127189 receive_stream .receive_nowait ()
128190
129191
192+ def test_deck_card_created_queues_index_task ():
193+ send_stream , receive_stream = anyio .create_memory_object_stream (max_buffer_size = 4 )
194+ app = _make_app (send_stream = send_stream )
195+
196+ with TestClient (app ) as client :
197+ response = client .post ("/webhooks/nextcloud" , json = _DECK_CARD_CREATED )
198+
199+ assert response .status_code == 200
200+ assert response .json ()["status" ] == "queued"
201+ assert response .json ()["operation" ] == "index"
202+ assert response .json ()["doc_id" ] == "4242"
203+
204+ task = receive_stream .receive_nowait ()
205+ assert task .user_id == "admin"
206+ assert task .doc_id == "4242"
207+ assert task .doc_type == "deck_card"
208+ assert task .operation == "index"
209+ assert task .metadata == {"stack_id" : 16 }
210+
211+
212+ def test_deck_card_deleted_queues_delete_task ():
213+ send_stream , receive_stream = anyio .create_memory_object_stream (max_buffer_size = 4 )
214+ app = _make_app (send_stream = send_stream )
215+
216+ with TestClient (app ) as client :
217+ response = client .post ("/webhooks/nextcloud" , json = _DECK_CARD_DELETED )
218+
219+ assert response .status_code == 200
220+ assert response .json ()["operation" ] == "delete"
221+
222+ task = receive_stream .receive_nowait ()
223+ assert task .doc_type == "deck_card"
224+ assert task .operation == "delete"
225+ assert task .doc_id == "4242"
226+ assert task .user_id == "alice"
227+
228+
229+ def test_deck_board_updated_is_ignored ():
230+ """BoardUpdatedEvent carries no card id, so the parser logs delivery and
231+ returns None — the polling scanner picks up the actual card changes."""
232+ send_stream , receive_stream = anyio .create_memory_object_stream (max_buffer_size = 4 )
233+ app = _make_app (send_stream = send_stream )
234+
235+ with TestClient (app ) as client :
236+ response = client .post ("/webhooks/nextcloud" , json = _DECK_BOARD_UPDATED )
237+
238+ assert response .status_code == 200
239+ assert response .json ()["status" ] == "ignored"
240+
241+ with pytest .raises (anyio .WouldBlock ):
242+ receive_stream .receive_nowait ()
243+
244+
245+ def test_deck_card_missing_id_is_ignored ():
246+ """A card event without ``card.id`` can't address Qdrant points, so the
247+ parser logs a warning and returns None — the polling scanner reconciles."""
248+ send_stream , receive_stream = anyio .create_memory_object_stream (max_buffer_size = 4 )
249+ app = _make_app (send_stream = send_stream )
250+
251+ with TestClient (app ) as client :
252+ response = client .post (
253+ "/webhooks/nextcloud" , json = _DECK_CARD_CREATED_MISSING_ID
254+ )
255+
256+ assert response .status_code == 200
257+ assert response .json ()["status" ] == "ignored"
258+
259+ with pytest .raises (anyio .WouldBlock ):
260+ receive_stream .receive_nowait ()
261+
262+
263+ def test_note_missing_node_id_is_ignored ():
264+ """Symmetric coverage for the file-event fail-open branch: a notes path
265+ without ``node.id`` falls back to the polling scanner."""
266+ send_stream , receive_stream = anyio .create_memory_object_stream (max_buffer_size = 4 )
267+ app = _make_app (send_stream = send_stream )
268+
269+ with TestClient (app ) as client :
270+ response = client .post ("/webhooks/nextcloud" , json = _NOTE_CREATED_MISSING_ID )
271+
272+ assert response .status_code == 200
273+ assert response .json ()["status" ] == "ignored"
274+
275+ with pytest .raises (anyio .WouldBlock ):
276+ receive_stream .receive_nowait ()
277+
278+
130279def test_invalid_json_returns_400 ():
131280 app = _make_app (send_stream = None )
132281
0 commit comments