def test_asgi_conductor_raised_error_skips_shutdown(): class SomeException(Exception): pass class Foo: def __init__(self): self.called_startup = False self.called_shutdown = False async def process_startup(self, scope, event): self.called_startup = True async def process_shutdown(self, scope, event): self.called_shutdown = True foo = Foo() app = App() app.add_middleware(foo) async def t(): with pytest.raises(SomeException): async with testing.ASGIConductor(app): raise SomeException() falcon.invoke_coroutine_sync(t) assert foo.called_startup assert not foo.called_shutdown
def test_at_least_one_event_method_required(): class Foo: pass app = App() with pytest.raises(TypeError): app.add_middleware(Foo())
def test_startup_only(): class Foo: async def process_startup(self, scope, event): self._called = True foo = Foo() app = App() app.add_middleware(foo) client = testing.TestClient(app) client.simulate_get() assert foo._called
def test_mw_methods_must_be_coroutines(): class MiddlewareA: def process_resource_ws(self, req, ws, resource, params): pass class MiddlewareB: def process_request_ws(self, req, ws): pass app = App() for mw in [MiddlewareA(), MiddlewareB()]: with pytest.raises(falcon.CompatibilityError): app.add_middleware(mw) with pytest.raises(falcon.CompatibilityError): App(middleware=mw)
def test_shutdown_raises(): class HandlerA: def __init__(self): self._startup_called = False async def process_startup(self, scope, event): self._startup_called = True async def process_shutdown(self, scope, event): raise Exception('testing 321') class HandlerB: def __init__(self): self._startup_called = False self._shutdown_called = False async def process_startup(self, scope, event): self._startup_called = True async def process_shutdown(self, scope, event): self._shutdown_called = True a = HandlerA() b1 = HandlerB() b2 = HandlerB() app = App() app.add_middleware(b1) app.add_middleware([a, b2]) client = testing.TestClient(app) with pytest.raises(RuntimeError) as excinfo: client.simulate_get() message = str(excinfo.value) assert message.startswith('ASGI app returned lifespan.shutdown.failed.') assert 'testing 321' in message assert a._startup_called assert b1._startup_called assert not b1._shutdown_called assert b2._startup_called assert b2._shutdown_called
def test_startup_raises(): class Foo: def __init__(self): self._shutdown_called = False async def process_startup(self, scope, event): raise Exception('testing 123') async def process_shutdown(self, scope, event): self._shutdown_called = True class Bar: def __init__(self): self._startup_called = False self._shutdown_called = False async def process_startup(self, scope, event): self._startup_called = True async def process_shutdown(self, scope, event): self._shutdown_called = True foo = Foo() bar = Bar() app = App() app.add_middleware([foo, bar]) client = testing.TestClient(app) with pytest.raises(RuntimeError) as excinfo: client.simulate_get() message = str(excinfo.value) assert message.startswith('ASGI app returned lifespan.startup.failed.') assert 'testing 123' in message assert not foo._shutdown_called assert not bar._startup_called assert not bar._shutdown_called
async def test_echo(): # noqa: C901 consumer_sleep = 0.01 producer_loop = 10 producer_sleep_factor = consumer_sleep / (producer_loop / 2) class Resource: def __init__(self): self.caught_operation_not_allowed = False async def on_websocket(self, req, ws, p1, p2, injected): # NOTE(kgriffs): Normally the receiver task is not started # until the websocket is started. But here we start it # first in order to simulate a potential race condition # that ASGIWebSocketSimulator._emit() guards against, # in case it ever arises due to the way the target ASGI # app may be implemented. ws._buffered_receiver.start() await asyncio.sleep(0) if ws.unaccepted: await ws.accept() try: await ws.accept() except falcon.OperationNotAllowed: self.caught_operation_not_allowed = True if ws.ready: await ws.send_text( f'{p1}:{p2}:{req.context.message}:{injected}') messages = deque() sink_task = falcon.create_task(self._sink(ws, messages)) while not sink_task.done(): if not messages: await asyncio.sleep(0) continue try: await ws.send_text(messages.popleft()) except falcon.WebSocketDisconnected: break sink_task.cancel() try: await sink_task except asyncio.CancelledError: pass async def _sink(self, ws, messages): while True: # NOTE(kgriffs): Throttle slightly to allow messages to # fill up the buffer. await asyncio.sleep(consumer_sleep) try: message = await ws.receive_text() except falcon.WebSocketDisconnected: break if message != 'ignore': messages.append(message) class MiddlewareA: async def process_resource_ws(self, req, ws, resource, params): assert isinstance(resource, Resource) assert isinstance(ws, falcon.asgi.WebSocket) params['injected'] = '42' class MiddlewareB: async def process_request_ws(self, req, ws): assert isinstance(ws, falcon.asgi.WebSocket) req.context.message = 'hello' resource = Resource() # NOTE(kgriffs): The two methods are split across different middleware # classes so that we can test code paths that check for the existence # of one WebSocket middleware method vs the other, and also so that # we can make sure both the kwarg and the add_middlware() paths # succeed. app = App(middleware=MiddlewareA()) app.add_middleware(MiddlewareB()) app.add_route('/{p1}/{p2}', resource) async with testing.ASGIConductor(app) as c: async with c.simulate_ws('/v1/v2', headers={}) as ws: assert (await ws.receive_text()) == 'v1:v2:hello:42' for i in range(producer_loop): message = str( i ) if i else '' # Test round-tripping the empty string as well for i in range(100): await ws.send_text('ignore') # NOTE(kgriffs): For part of the time, cause the buffer on the # server side to fill up, and for the remainder of the time # for the buffer to be empty and wait on the client for # additional messages. await asyncio.sleep(i * producer_sleep_factor) await ws.send_text(message) assert (await ws.receive_text()) == message await ws.close() assert ws.closed assert ws.close_code == CloseCode.NORMAL assert resource.caught_operation_not_allowed
def test_multiple_handlers(): counter = 0 class HandlerA: async def process_startup(self, scope, event): nonlocal counter self._called_startup = counter counter += 1 class HandlerB: async def process_startup(self, scope, event): nonlocal counter self._called_startup = counter counter += 1 async def process_shutdown(self, scope, event): nonlocal counter self._called_shutdown = counter counter += 1 class HandlerC: async def process_shutdown(self, scope, event): nonlocal counter self._called_shutdown = counter counter += 1 class HandlerD: async def process_startup(self, scope, event): nonlocal counter self._called_startup = counter counter += 1 class HandlerE: async def process_startup(self, scope, event): nonlocal counter self._called_startup = counter counter += 1 async def process_shutdown(self, scope, event): nonlocal counter self._called_shutdown = counter counter += 1 async def process_request(self, req, resp): self._called_request = True app = App() a = HandlerA() b = HandlerB() c = HandlerC() d = HandlerD() e = HandlerE() app.add_middleware([a, b, c, d, e]) client = testing.TestClient(app) client.simulate_get() assert a._called_startup == 0 assert b._called_startup == 1 assert d._called_startup == 2 assert e._called_startup == 3 assert e._called_shutdown == 4 assert c._called_shutdown == 5 assert b._called_shutdown == 6 assert e._called_request