Files
kaya/packages/kaya-core/tests/test_asgi.py
T

312 lines
13 KiB
Python

import unittest
import json
import httpx
from pwo import async_test
from kaya.core import KayaApp, HttpContext, HttpMethod
from typing import Sequence, List
class AsgiTest(unittest.TestCase):
app: KayaApp
def setUp(self):
self.app = KayaApp()
@self.app.GET('/hello')
@self.app.GET('/hello2')
@self.app.route('/hello3')
@self.app.GET('/hello/*')
async def handle_request(ctx: HttpContext) -> None:
async for chunk in ctx.request_body:
print(chunk)
await ctx.send_str(200, 'Hello World!')
@self.app.route(('/foo/bar',), HttpMethod.PUT, recursive=True)
async def handle_request(ctx: HttpContext) -> None:
async for chunk in ctx.request_body:
print(chunk)
await ctx.send_str(200, ctx.path)
@self.app.route(('/foo/*',), HttpMethod.PUT, recursive=True)
async def handle_request(ctx: HttpContext, path: Sequence[str]) -> None:
async for chunk in ctx.request_body:
print(chunk)
await ctx.send_str(200, json.dumps(path))
@self.app.route(('/foo/*',), recursive=True)
async def handle_request(ctx: HttpContext, path: Sequence[str]) -> None:
await ctx.send_str(500, f"Unable to handle request for {ctx.path}")
@self.app.GET('/employee/${employee_id}')
async def handle_request(ctx: HttpContext, employee_id: str) -> None:
async for chunk in ctx.request_body:
print(chunk)
await ctx.send_str(200, employee_id)
@self.app.GET('/square/${x:int}')
async def handle_request(ctx: HttpContext, x: int) -> None:
async for chunk in ctx.request_body:
print(chunk)
await ctx.send_str(200, str(x * x))
@self.app.GET('/department/${department_id:int}/employee/${employee_id:int}')
async def handle_request(ctx: HttpContext, department_id: int, employee_id: int) -> None:
async for chunk in ctx.request_body:
print(chunk)
await ctx.send_str(200, json.dumps({
'department_id': department_id,
'employee_id': employee_id
}))
@self.app.PUT('/hello/*', recursive=True)
async def handle_request(ctx: HttpContext, _: List[str]) -> None:
await ctx.stream_body(200, (chunk async for chunk in ctx.request_body))
@self.app.GET('/client-ip')
async def handle_request(ctx: HttpContext) -> None:
host, port = ctx.client if ctx.client is not None else (None, None)
await ctx.send_str(200, json.dumps({'host': host, 'port': port}))
@async_test
async def test_hello(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.get("/hello")
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, "Hello World!")
r = await client.get("/hello2")
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, "Hello World!")
r = await client.post("/hello3")
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, "Hello World!")
r = await client.get("/hello4")
self.assertEqual(404, r.status_code)
self.assertTrue(len(r.text) == 0)
body = {'name': 'John', 'surname': 'Smith'}
r = await client.put("/hello/foo/bar", json=body)
self.assertEqual(200, r.status_code)
ans = json.loads(r.text)
self.assertTrue(body, ans)
@async_test
async def test_foo(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.put("/foo/fizz/baz")
self.assertEqual(200, r.status_code)
response = json.loads(r.text)
self.assertEqual(['fizz', 'baz'], response)
r = await client.get("/foo/not-put")
self.assertEqual(500, r.status_code)
self.assertEqual("Unable to handle request for /foo/not-put", r.text)
@async_test
async def test_method_agnostic_fallback_order_independence(self):
app = KayaApp()
@app.route(('/foo/*',), recursive=True)
async def handle_request(ctx: HttpContext, path: Sequence[str]) -> None:
await ctx.send_str(500, f"Unable to handle request for {ctx.path}")
@app.route(('/foo/*',), HttpMethod.PUT, recursive=True)
async def handle_request(ctx: HttpContext, path: Sequence[str]) -> None:
await ctx.send_str(200, json.dumps(path))
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.put("/foo/fizz/baz")
self.assertEqual(200, r.status_code)
self.assertEqual(['fizz', 'baz'], json.loads(r.text))
r = await client.get("/foo/not-put")
self.assertEqual(500, r.status_code)
self.assertEqual("Unable to handle request for /foo/not-put", r.text)
@async_test
async def test_disjoint_method_specific_matchers(self):
app = KayaApp()
@app.route(('/foo/*',), HttpMethod.PUT, recursive=True)
async def handle_request(ctx: HttpContext, path: Sequence[str]) -> None:
await ctx.send_str(200, "PUT")
@app.route(('/foo/*',), HttpMethod.GET, recursive=True)
async def handle_request(ctx: HttpContext, path: Sequence[str]) -> None:
await ctx.send_str(200, "GET")
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.put("/foo/bar")
self.assertEqual(200, r.status_code)
self.assertEqual("PUT", r.text)
r = await client.get("/foo/bar")
self.assertEqual(200, r.status_code)
self.assertEqual("GET", r.text)
@async_test
async def test_foo_bar(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.put("/foo/bar/baz")
self.assertEqual(200, r.status_code)
self.assertEqual('/foo/bar/baz', r.text)
@async_test
async def test_employee(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.get("/employee/101325")
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, '101325')
@async_test
async def test_square(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
x = 30
r = await client.get(f"/square/{x}")
self.assertEqual(200, r.status_code)
self.assertEqual(r.text, str(x * x))
@async_test
async def test_department_employee(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.get("department/189350/employee/101325")
self.assertEqual(200, r.status_code)
response = json.loads(r.text)
self.assertEqual({
'department_id': 189350,
'employee_id': 101325
}, response)
@async_test
async def test_client_ip_forwarded(self):
transport = httpx.ASGITransport(app=self.app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
# socket peer, no forwarded headers
r = await client.get("/client-ip")
socket_client = json.loads(r.text)
self.assertEqual('127.0.0.1', socket_client['host'])
# RFC 7239 Forwarded header, with port
r = await client.get("/client-ip", headers={'Forwarded': 'for=203.0.113.5:1234'})
self.assertEqual({'host': '203.0.113.5', 'port': 1234}, json.loads(r.text))
# RFC 7239 Forwarded header, bracketed IPv6 with port
r = await client.get("/client-ip", headers={'Forwarded': 'for="[2001:db8::1]:4711"'})
self.assertEqual({'host': '2001:db8::1', 'port': 4711}, json.loads(r.text))
# RFC 7239 Forwarded header without port keeps the socket port
r = await client.get("/client-ip", headers={'Forwarded': 'for=203.0.113.5'})
self.assertEqual({'host': '203.0.113.5', 'port': socket_client['port']}, json.loads(r.text))
# Forwarded with for=unknown falls through to X-Forwarded-For
r = await client.get("/client-ip", headers={
'Forwarded': 'for=unknown',
'X-Forwarded-For': '198.51.100.7',
})
self.assertEqual('198.51.100.7', json.loads(r.text)['host'])
# Forwarded takes precedence over X-Forwarded-For
r = await client.get("/client-ip", headers={
'Forwarded': 'for=203.0.113.5',
'X-Forwarded-For': '198.51.100.7',
})
self.assertEqual('203.0.113.5', json.loads(r.text)['host'])
# X-Forwarded-For: first entry of the chain, port from X-Forwarded-Port
r = await client.get("/client-ip", headers={
'X-Forwarded-For': '203.0.113.5, 70.41.3.18',
'X-Forwarded-Port': '8443',
})
self.assertEqual({'host': '203.0.113.5', 'port': 8443}, json.loads(r.text))
# X-Forwarded-Host fallback
r = await client.get("/client-ip", headers={'X-Forwarded-Host': '198.51.100.7'})
self.assertEqual('198.51.100.7', json.loads(r.text)['host'])
# invalid X-Forwarded-Port is ignored, socket port is kept
r = await client.get("/client-ip", headers={
'X-Forwarded-For': '203.0.113.5',
'X-Forwarded-Port': 'not-a-port',
})
self.assertEqual({'host': '203.0.113.5', 'port': socket_client['port']}, json.loads(r.text))
@async_test
async def test_nested_param_routes(self):
app = KayaApp()
@app.GET('/restaurants/${id}')
async def restaurant(ctx: HttpContext, id: str) -> None:
await ctx.send_str(200, f"restaurant:{id}")
@app.GET('/restaurants/${id}/menu')
async def menu(ctx: HttpContext, id: str) -> None:
await ctx.send_str(200, f"menu:{id}")
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.get("/restaurants/42")
self.assertEqual(200, r.status_code)
self.assertEqual("restaurant:42", r.text)
r = await client.get("/restaurants/42/menu")
self.assertEqual(200, r.status_code)
self.assertEqual("menu:42", r.text)
r = await client.get("/restaurants/42/unknown")
self.assertEqual(404, r.status_code)
@async_test
async def test_nested_param_routes_multiple_methods(self):
app = KayaApp()
@app.GET('/restaurants/${id}')
async def restaurant(ctx: HttpContext, id: str) -> None:
await ctx.send_str(200, f"restaurant:{id}")
@app.GET('/restaurants/${id}/menu')
async def menu_get(ctx: HttpContext, id: str) -> None:
await ctx.send_str(200, f"menu_get:{id}")
@app.POST('/restaurants/${id}/menu')
async def menu_post(ctx: HttpContext, id: str) -> None:
await ctx.send_str(200, f"menu_post:{id}")
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
r = await client.get("/restaurants/42")
self.assertEqual(200, r.status_code)
self.assertEqual("restaurant:42", r.text)
r = await client.get("/restaurants/42/menu")
self.assertEqual(200, r.status_code)
self.assertEqual("menu_get:42", r.text)
r = await client.post("/restaurants/42/menu")
self.assertEqual(200, r.status_code)
self.assertEqual("menu_post:42", r.text)
r = await client.put("/restaurants/42/menu")
self.assertEqual(404, r.status_code)