def test_parser_async(body, doc): with disable_asgi_non_coroutine_wrapping(): class WrappedRespondersBodyParserAsyncResource: @falcon.before(validate_param_async, 'limit', 100) @falcon.before(parse_body_async) async def on_get(self, req, resp, doc=None): self.req = req self.resp = resp self.doc = doc app = create_app(asgi=True) resource = WrappedRespondersBodyParserAsyncResource() app.add_route('/', resource) testing.simulate_get(app, '/', body=body) assert resource.doc == doc async def test_direct(): resource = WrappedRespondersBodyParserAsyncResource() req = testing.create_asgi_req() resp = create_resp(True) await resource.on_get(req, resp, doc) assert resource.doc == doc testing.invoke_coroutine_sync(test_direct)
def test_lifespan_scope_default_version(): app = App() resource = testing.SimpleTestResourceAsync() app.add_route('/', resource) shutting_down = asyncio.Condition() req_event_emitter = testing.ASGILifespanEventEmitter(shutting_down) resp_event_collector = testing.ASGIResponseEventCollector() scope = {'type': 'lifespan'} async def t(): t = asyncio.get_event_loop().create_task( app(scope, req_event_emitter, resp_event_collector)) # NOTE(kgriffs): Yield to the lifespan task above await asyncio.sleep(0.001) async with shutting_down: shutting_down.notify() await t testing.invoke_coroutine_sync(t) assert not resource.called
def test_filelike(): s = asgi.BoundedStream(testing.ASGIRequestEventEmitter()) for __ in range(2): with pytest.raises(OSError): s.fileno() assert not s.isatty() assert s.readable() assert not s.seekable() assert not s.writable() s.close() assert s.closed # NOTE(kgriffs): Closing an already-closed stream is a noop. s.close() assert s.closed async def test_iteration(): with pytest.raises(ValueError): await s.read() with pytest.raises(ValueError): await s.readall() with pytest.raises(ValueError): await s.exhaust() with pytest.raises(ValueError): async for chunk in s: pass testing.invoke_coroutine_sync(test_iteration)
def test_asgi_request_event_emitter_hang(): # NOTE(kgriffs): This tests the ASGI server behavior that # ASGIRequestEventEmitter simulates when emit() is called # again after there are not more events available. expected_elasped_min = 1 disconnect_at = time.time() + expected_elasped_min emit = testing.ASGIRequestEventEmitter(disconnect_at=disconnect_at) async def t(): start = time.time() while True: event = await emit() if not event.get('more_body', False): break elapsed = time.time() - start assert elapsed < 0.1 start = time.time() await emit() elapsed = time.time() - start assert (elapsed + 0.1) > expected_elasped_min testing.invoke_coroutine_sync(t)
def test_ignore_extra_asgi_events(): collect = testing.ASGIResponseEventCollector() async def t(): await collect({'type': 'http.response.start', 'status': 200}) await collect({'type': 'http.response.body', 'more_body': False}) # NOTE(kgriffs): Events after more_body is False are ignored to conform # to the ASGI spec. await collect({'type': 'http.response.body'}) assert len(collect.events) == 2 testing.invoke_coroutine_sync(t)
def _call_with_scope(scope): app = App() resource = testing.SimpleTestResourceAsync() app.add_route('/', resource) req_event_emitter = testing.ASGIRequestEventEmitter() resp_event_collector = testing.ASGIResponseEventCollector() testing.invoke_coroutine_sync(app.__call__, scope, req_event_emitter, resp_event_collector) assert resource.called return resource
def test_urlencoded_form_handler_serialize(data, expected): handler = media.URLEncodedFormHandler() assert handler.serialize(data, falcon.MEDIA_URLENCODED) == expected value = testing.invoke_coroutine_sync(handler.serialize_async, data, falcon.MEDIA_URLENCODED) assert value == expected
def test_good_path(asgi, uri_prefix, uri_path, expected_path, mtype, monkeypatch): monkeypatch.setattr(io, 'open', lambda path, mode: io.BytesIO(path.encode())) sr = create_sr(asgi, uri_prefix, '/var/www/statics') req_path = uri_prefix[:-1] if uri_prefix.endswith('/') else uri_prefix req_path += uri_path req = _util.create_req(asgi, host='test.com', path=req_path, root_path='statics') resp = _util.create_resp(asgi) if asgi: async def run(): await sr(req, resp) return await resp.stream.read() body = testing.invoke_coroutine_sync(run) else: sr(req, resp) body = resp.stream.read() assert resp.content_type == mtype assert body.decode() == '/var/www/statics' + expected_path
def test_exhaust_with_disconnect(): async def t(): emitter = testing.ASGIRequestEventEmitter(b'123456798' * 1024, disconnect_at=(time.time() + 0.5)) s = asgi.BoundedStream(emitter) assert await s.read(1) == b'1' assert await s.read(2) == b'23' await asyncio.sleep(0.5) await s.exhaust() assert await s.read(1) == b'' assert await s.read(100) == b'' assert s.eof testing.invoke_coroutine_sync(t)
def test_invalid_asgi_events(): collect = testing.ASGIResponseEventCollector() def make_event(headers=None, status=200): return { 'type': 'http.response.start', 'headers': headers or [], 'status': status } async def t(): with pytest.raises(TypeError): await collect({'type': 123}) with pytest.raises(TypeError): headers = [('notbytes', b'bytes')] await collect(make_event(headers)) with pytest.raises(TypeError): headers = [(b'bytes', 'notbytes')] await collect(make_event(headers)) with pytest.raises(ValueError): headers = [ # NOTE(kgriffs): Name must be lowercase (b'Content-Type', b'application/json') ] await collect(make_event(headers)) with pytest.raises(TypeError): await collect(make_event(status='200')) with pytest.raises(TypeError): await collect(make_event(status=200.1)) with pytest.raises(TypeError): await collect({'type': 'http.response.body', 'body': 'notbytes'}) with pytest.raises(TypeError): await collect({'type': 'http.response.body', 'more_body': ''}) with pytest.raises(ValueError): # NOTE(kgriffs): Invalid type await collect({'type': 'http.response.bod'}) testing.invoke_coroutine_sync(t)
def call_method(asgi, method_name, *args): resource = ResourceAsync() if asgi else Resource() if asgi: return testing.invoke_coroutine_sync(getattr(resource, method_name), *args) return getattr(resource, method_name)(*args)
def test_bad_path(asgi, uri, monkeypatch): monkeypatch.setattr(io, 'open', lambda path, mode: io.BytesIO()) sr_type = StaticRouteAsync if asgi else StaticRoute sr = sr_type('/static', '/var/www/statics') req = _util.create_req(asgi, host='test.com', path=uri, root_path='statics') resp = _util.create_resp(asgi) with pytest.raises(falcon.HTTPNotFound): if asgi: testing.invoke_coroutine_sync(sr, req, resp) else: sr(req, resp)
def test_read_chunks(body, chunk_size): def stream(): return _stream(body) async def test_nonmixed(): s = stream() assert await s.read(0) == b'' chunks = [] while not s.eof: chunks.append(await s.read(chunk_size)) assert b''.join(chunks) == body async def test_mixed_a(): s = stream() chunks = [] chunks.append(await s.read(chunk_size)) chunks.append(await s.read(chunk_size)) chunks.append(await s.readall()) chunks.append(await s.read(chunk_size)) assert b''.join(chunks) == body async def test_mixed_b(): s = stream() chunks = [] chunks.append(await s.read(chunk_size)) chunks.append(await s.read(-1)) assert b''.join(chunks) == body for t in (test_nonmixed, test_mixed_a, test_mixed_b): testing.invoke_coroutine_sync(t) testing.invoke_coroutine_sync(t)
def test_serialization(asgi, func, body, expected): handler = media.JSONHandler(dumps=func) args = (body, b'application/javacript') if asgi: result = testing.invoke_coroutine_sync(handler.serialize_async, *args) else: result = handler.serialize(*args) # NOTE(nZac) PyPy and CPython render the final string differently. One # includes spaces and the other doesn't. This replace will normalize that. assert result.replace(b' ', b'') == expected # noqa
def test_resource_with_uri_fields_async(): app = create_app(asgi=True) resource = ClassResourceWithURIFieldsAsync() app.add_route('/{field1}/{field2}', resource) result = testing.simulate_get(app, '/a/b') assert result.status_code == 200 assert result.headers['X-Fluffiness'] == 'fluffy' assert resource.fields == ('a', 'b') async def test_direct(): resource = ClassResourceWithURIFieldsAsync() req = testing.create_asgi_req() resp = create_resp(True) await resource.on_get(req, resp, '1', '2') assert resource.fields == ('1', '2') testing.invoke_coroutine_sync(test_direct)
def test_lifespan_scope_version(spec_version, supported): app = App() shutting_down = asyncio.Condition() req_event_emitter = testing.ASGILifespanEventEmitter(shutting_down) resp_event_collector = testing.ASGIResponseEventCollector() scope = { 'type': 'lifespan', 'asgi': { 'spec_version': spec_version, 'version': '3.0' } } if not supported: with pytest.raises(UnsupportedScopeError): testing.invoke_coroutine_sync(app.__call__, scope, req_event_emitter, resp_event_collector) return async def t(): t = asyncio.get_event_loop().create_task( app(scope, req_event_emitter, resp_event_collector)) # NOTE(kgriffs): Yield to the lifespan task above await asyncio.sleep(0.001) async with shutting_down: shutting_down.notify() await t testing.invoke_coroutine_sync(t)
def test_fallback_filename(asgi, uri, default, expected, content_type, downloadable, monkeypatch): def mock_open(path, mode): if os.path.normpath(default) in path: return io.BytesIO(path.encode()) raise IOError() monkeypatch.setattr(io, 'open', mock_open) monkeypatch.setattr('os.path.isfile', lambda file: os.path.normpath(default) in file) sr = create_sr(asgi, '/static', '/var/www/statics', downloadable=downloadable, fallback_filename=default) req_path = '/static/' + uri req = _util.create_req(asgi, host='test.com', path=req_path, root_path='statics') resp = _util.create_resp(asgi) if asgi: async def run(): await sr(req, resp) return await resp.stream.read() body = testing.invoke_coroutine_sync(run) else: sr(req, resp) body = resp.stream.read() assert sr.match(req.path) assert body.decode() == os.path.normpath( os.path.join('/var/www/statics', expected)) assert resp.content_type in _MIME_ALTERNATIVE.get(content_type, (content_type, )) if downloadable: assert os.path.basename(expected) in resp.downloadable_as else: assert resp.downloadable_as is None
def test_deserialization(asgi, func, body, expected): handler = media.JSONHandler(loads=func) args = ['application/javacript', len(body)] if asgi: if not ASGI_SUPPORTED: pytest.skip('ASGI requires Python 3.6+') from falcon.asgi.stream import BoundedStream s = BoundedStream(testing.ASGIRequestEventEmitter(body)) args.insert(0, s) result = testing.invoke_coroutine_sync(handler.deserialize_async, *args) else: args.insert(0, io.BytesIO(body)) result = handler.deserialize(*args) assert result == expected
def test_read_all(body, extra_body, set_content_length): if extra_body and not set_content_length: pytest.skip( 'extra_body ignores set_content_length so we only need to test ' 'one of the parameter permutations') expected_body = body if isinstance(body, bytes) else body.encode() def stream(): stream_body = body content_length = None if extra_body: # NOTE(kgriffs): Test emitting more data than expected to the app content_length = len(expected_body) stream_body += b'\x00' if isinstance(stream_body, bytes) else '~' elif set_content_length: content_length = len(expected_body) return _stream(stream_body, content_length=content_length) async def test_iteration(): s = stream() assert b''.join([chunk async for chunk in s]) == expected_body assert await s.read() == b'' assert await s.readall() == b'' assert [chunk async for chunk in s][0] == b'' assert s.tell() == len(expected_body) assert s.eof async def test_readall_a(): s = stream() assert await s.readall() == expected_body assert await s.read() == b'' assert await s.readall() == b'' assert [chunk async for chunk in s][0] == b'' assert s.tell() == len(expected_body) assert s.eof async def test_readall_b(): s = stream() assert await s.read() == expected_body assert await s.readall() == b'' assert await s.read() == b'' assert [chunk async for chunk in s][0] == b'' assert s.tell() == len(expected_body) assert s.eof async def test_readall_c(): s = stream() body = await s.read(1) body += await s.read(None) assert body == expected_body assert s.tell() == len(expected_body) assert s.eof async def test_readall_d(): s = stream() assert not s.closed if expected_body: assert not s.eof elif set_content_length: assert s.eof else: # NOTE(kgriffs): Stream doesn't know if there is more data # coming or not until the first read. assert not s.eof assert s.tell() == 0 assert await s.read(-2) == b'' assert await s.read(-3) == b'' assert await s.read(-100) == b'' assert await s.read(-1) == expected_body assert await s.read(-1) == b'' assert await s.readall() == b'' assert await s.read() == b'' assert [chunk async for chunk in s][0] == b'' assert await s.read(-2) == b'' assert s.tell() == len(expected_body) assert s.eof assert not s.closed s.close() assert s.closed for t in (test_iteration, test_readall_a, test_readall_b, test_readall_c, test_readall_d): testing.invoke_coroutine_sync(t)