1212from structlog .testing import capture_logs
1313from yarl import URL
1414
15- from socketshark import (
16- SocketShark ,
17- config_defaults ,
18- constants as c ,
19- setup_logging ,
20- )
15+ from socketshark import SocketShark , config_defaults , setup_logging
2116from socketshark .session import Session
2217
2318LOCAL_REDIS_HOST = os .environ .get ('LOCAL_REDIS_HOST' )
@@ -193,39 +188,39 @@ async def test_invalid_message(self):
193188 await session .on_client_event (None )
194189 assert client .log .pop () == {
195190 'status' : 'error' ,
196- 'error' : c . ERR_INVALID_EVENT ,
191+ 'error' : 'Messages must be JSON and contain an event field.' ,
197192 }
198193
199194 await session .on_client_event ('hello' )
200195 assert client .log .pop () == {
201196 'status' : 'error' ,
202- 'error' : c . ERR_INVALID_EVENT ,
197+ 'error' : 'Messages must be JSON and contain an event field.' ,
203198 }
204199
205200 await session .on_client_event ({})
206201 assert client .log .pop () == {
207202 'status' : 'error' ,
208- 'error' : c . ERR_INVALID_EVENT ,
203+ 'error' : 'Messages must be JSON and contain an event field.' ,
209204 }
210205
211206 await session .on_client_event ({'event' : None })
212207 assert client .log .pop () == {
213208 'status' : 'error' ,
214- 'error' : c . ERR_INVALID_EVENT ,
209+ 'error' : 'Messages must be JSON and contain an event field.' ,
215210 }
216211
217212 await session .on_client_event ({'event' : '' })
218213 assert client .log .pop () == {
219214 'status' : 'error' ,
220215 'event' : '' ,
221- 'error' : c . ERR_EVENT_NOT_FOUND ,
216+ 'error' : 'Event not found.' ,
222217 }
223218
224219 await session .on_client_event ({'event' : 'hello' })
225220 assert client .log .pop () == {
226221 'status' : 'error' ,
227222 'event' : 'hello' ,
228- 'error' : c . ERR_EVENT_NOT_FOUND ,
223+ 'error' : 'Event not found.' ,
229224 }
230225
231226 assert not client .log
@@ -243,7 +238,7 @@ async def test_auth_invalid(self):
243238 assert client .log .pop () == {
244239 'status' : 'error' ,
245240 'event' : 'auth' ,
246- 'error' : c . ERR_AUTH_UNSUPPORTED ,
241+ 'error' : 'Authentication method unsupported.' ,
247242 }
248243
249244 no_auth_config = TEST_CONFIG .copy ()
@@ -255,7 +250,7 @@ async def test_auth_invalid(self):
255250 assert client .log .pop () == {
256251 'status' : 'error' ,
257252 'event' : 'auth' ,
258- 'error' : c . ERR_AUTH_UNSUPPORTED ,
253+ 'error' : 'Authentication method unsupported.' ,
259254 }
260255 assert not client .log
261256
@@ -286,6 +281,77 @@ async def test_auth_invalid_emits_structlog_logs(self):
286281 },
287282 ]
288283
284+ @pytest .mark .asyncio
285+ async def test_invalid_event_emits_structlog_logs (self ):
286+ shark = SocketShark (TEST_CONFIG )
287+ session = MockClient (shark ).session
288+ with capture_logs () as structlog_logs :
289+ await session .on_service_event ({'invalid' : 'event' })
290+ assert structlog_logs == [
291+ {
292+ 'log_level' : 'warning' ,
293+ 'event' : 'invalid service event' ,
294+ 'data' : {'invalid' : 'event' },
295+ 'pid' : mock .ANY ,
296+ 'session' : mock .ANY ,
297+ },
298+ ]
299+
300+ @pytest .mark .asyncio
301+ async def test_invalid_published_at_is_handled_gracefully (self ):
302+ shark = SocketShark (TEST_CONFIG )
303+ await shark .prepare ()
304+ client = MockClient (shark )
305+ session = client .session
306+
307+ await session .on_client_event (
308+ {
309+ 'event' : 'subscribe' ,
310+ 'subscription' : 'simple.topic' ,
311+ }
312+ )
313+ assert client .log .pop () == {
314+ 'event' : 'subscribe' ,
315+ 'subscription' : 'simple.topic' ,
316+ 'status' : 'ok' ,
317+ }
318+
319+ with capture_logs () as structlog_logs :
320+ await session .on_service_event (
321+ {
322+ 'subscription' : 'simple.topic' ,
323+ 'data' : {'foo' : 'bar' },
324+ 'published_at' : 'not-a-date' ,
325+ }
326+ )
327+
328+ assert structlog_logs == [
329+ {
330+ 'log_level' : 'warning' ,
331+ 'event' : 'invalid published_at format' ,
332+ 'published_at' : 'not-a-date' ,
333+ 'session' : mock .ANY ,
334+ 'pid' : mock .ANY ,
335+ },
336+ # The message is still expected to be sent. An invalid
337+ # `published_at` should not break the entire end-to-end messaging
338+ # flow .
339+ {
340+ 'log_level' : 'debug' ,
341+ 'event' : 'client send' ,
342+ 'data' : {
343+ 'subscription' : 'simple.topic' ,
344+ 'event' : 'message' ,
345+ 'data' : {'foo' : 'bar' },
346+ 'sent_at' : mock .ANY ,
347+ },
348+ 'pid' : mock .ANY ,
349+ 'session' : mock .ANY ,
350+ },
351+ ]
352+
353+ await shark .shutdown ()
354+
289355 @pytest .mark .asyncio
290356 async def test_auth_ticket (self ):
291357 """
@@ -299,14 +365,14 @@ async def test_auth_ticket(self):
299365 assert client .log .pop () == {
300366 'status' : 'error' ,
301367 'event' : 'auth' ,
302- 'error' : c . ERR_NEEDS_TICKET ,
368+ 'error' : 'Must specify ticket.' ,
303369 }
304370
305371 await session .on_client_event ({'event' : 'auth' , 'method' : 'ticket' })
306372 assert client .log .pop () == {
307373 'status' : 'error' ,
308374 'event' : 'auth' ,
309- 'error' : c . ERR_NEEDS_TICKET ,
375+ 'error' : 'Must specify ticket.' ,
310376 }
311377
312378 with aioresponses () as mock_responses :
@@ -321,7 +387,7 @@ async def test_auth_ticket(self):
321387 assert client .log .pop () == {
322388 'status' : 'error' ,
323389 'event' : 'auth' ,
324- 'error' : c . ERR_SERVICE_UNAVAILABLE ,
390+ 'error' : 'Service unavailable.' ,
325391 }
326392
327393 with aioresponses () as mock_responses :
@@ -359,7 +425,7 @@ async def test_auth_ticket(self):
359425 assert client .log .pop () == {
360426 'status' : 'error' ,
361427 'event' : 'auth' ,
362- 'error' : c . ERR_AUTH_FAILED ,
428+ 'error' : 'Authentication failed.' ,
363429 }
364430
365431 assert session .auth_info == {}
@@ -412,7 +478,7 @@ async def test_subscription_invalid(self):
412478 assert client .log .pop () == {
413479 'event' : 'subscribe' ,
414480 'status' : 'error' ,
415- 'error' : c . ERR_INVALID_SUBSCRIPTION_FORMAT ,
481+ 'error' : 'Invalid subscription format.' ,
416482 }
417483
418484 await session .on_client_event (
@@ -422,7 +488,7 @@ async def test_subscription_invalid(self):
422488 'event' : 'subscribe' ,
423489 'subscription' : 'invalid' ,
424490 'status' : 'error' ,
425- 'error' : c . ERR_INVALID_SUBSCRIPTION_FORMAT ,
491+ 'error' : 'Invalid subscription format.' ,
426492 }
427493
428494 await session .on_client_event (
@@ -432,7 +498,7 @@ async def test_subscription_invalid(self):
432498 'event' : 'subscribe' ,
433499 'subscription' : 'invalid.topic' ,
434500 'status' : 'error' ,
435- 'error' : c . ERR_INVALID_SERVICE ,
501+ 'error' : 'Invalid service.' ,
436502 }
437503
438504 assert not client .log
@@ -456,7 +522,7 @@ async def test_subscription_needs_auth(self):
456522 'event' : 'subscribe' ,
457523 'subscription' : 'empty.topic' ,
458524 'status' : 'error' ,
459- 'error' : c . ERR_AUTH_REQUIRED ,
525+ 'error' : 'Authentication required.' ,
460526 }
461527
462528 assert not client .log
@@ -490,7 +556,7 @@ async def test_subscription_validation(self):
490556 'event' : 'subscribe' ,
491557 'subscription' : 'simple.topic' ,
492558 'status' : 'error' ,
493- 'error' : c . ERR_ALREADY_SUBSCRIBED ,
559+ 'error' : 'Already subscribed.' ,
494560 }
495561
496562 await session .on_client_event (
@@ -504,7 +570,7 @@ async def test_subscription_validation(self):
504570 'status' : 'error' ,
505571 'subscription' : 'simple.invalid' ,
506572 'event' : 'message' ,
507- 'error' : c . ERR_SUBSCRIPTION_NOT_FOUND ,
573+ 'error' : 'Subscription does not exist.' ,
508574 }
509575
510576 await session .on_client_event (
@@ -517,7 +583,7 @@ async def test_subscription_validation(self):
517583 'event' : 'unsubscribe' ,
518584 'subscription' : 'simple.invalid' ,
519585 'status' : 'error' ,
520- 'error' : c . ERR_SUBSCRIPTION_NOT_FOUND ,
586+ 'error' : 'Subscription does not exist.' ,
521587 }
522588
523589 await shark .shutdown ()
@@ -679,7 +745,7 @@ async def test_subscription_auth(self):
679745 'event' : 'subscribe' ,
680746 'subscription' : 'simple_auth.topic' ,
681747 'status' : 'error' ,
682- 'error' : c . ERR_AUTH_REQUIRED ,
748+ 'error' : 'Authentication required.' ,
683749 }
684750
685751 await self ._auth_session (session )
@@ -775,7 +841,7 @@ async def test_subscription_authorizer(self):
775841 'event' : 'subscribe' ,
776842 'subscription' : 'authorizer.topic' ,
777843 'status' : 'error' ,
778- 'error' : c . ERR_AUTH_REQUIRED ,
844+ 'error' : 'Authentication required.' ,
779845 }
780846
781847 await self ._auth_session (session )
@@ -792,7 +858,7 @@ async def test_subscription_authorizer(self):
792858 'event' : 'subscribe' ,
793859 'subscription' : 'authorizer.topic' ,
794860 'status' : 'error' ,
795- 'error' : c . ERR_SERVICE_UNAVAILABLE ,
861+ 'error' : 'Service unavailable.' ,
796862 }
797863
798864 with aioresponses () as mock_responses :
@@ -831,7 +897,7 @@ async def test_subscription_authorizer(self):
831897 'event' : 'subscribe' ,
832898 'subscription' : 'authorizer.topic' ,
833899 'status' : 'error' ,
834- 'error' : c . ERR_UNAUTHORIZED ,
900+ 'error' : 'Unauthorized.' ,
835901 }
836902
837903 await session .on_client_event (
@@ -1023,7 +1089,7 @@ async def test_subscription_periodic_authorizer(self):
10231089 assert client .log .pop (0 ) == {
10241090 'event' : 'unsubscribe' ,
10251091 'subscription' : 'periodic_authorizer.topic' ,
1026- 'error' : c . ERR_UNAUTHORIZED ,
1092+ 'error' : 'Unauthorized.' ,
10271093 }
10281094
10291095 assert client .log .pop (0 ) == {
@@ -1182,7 +1248,7 @@ async def test_subscription_complex(self):
11821248 'event' : 'subscribe' ,
11831249 'subscription' : 'complex.topic' ,
11841250 'status' : 'error' ,
1185- 'error' : c . ERR_UNHANDLED_EXCEPTION ,
1251+ 'error' : 'Unhandled exception.' ,
11861252 }
11871253
11881254 mock_responses .post (conf ['authorizer' ], payload = {'status' : 'ok' })
@@ -1433,7 +1499,7 @@ async def test_subscription_complex(self):
14331499 'subscription' : 'complex.topic' ,
14341500 'status' : 'error' ,
14351501 'extra' : 'hello' ,
1436- 'error' : c . ERR_UNHANDLED_EXCEPTION ,
1502+ 'error' : 'Unhandled exception.' ,
14371503 }
14381504
14391505 await session .on_client_event (
0 commit comments