Refactor to composable KayaMixin architecture
Replace wrapper-based SessionMiddleware/OIDCApp with KayaMixin subclasses applied via KayaApp(mixins=[...]). Mixins hook into handle_request and handle_websocket via before/after hooks, so both ASGI and RSGI keep working. Mixin dependencies are applied automatically and deduplicated.
This commit is contained in:
@@ -6,7 +6,7 @@ import httpx
|
||||
from pwo import async_test
|
||||
from kaya.core import KayaApp, HttpContext
|
||||
|
||||
from kaya.session import InMemorySessionStore, Session, SessionMiddleware, SessionStore
|
||||
from kaya.session import InMemorySessionStore, Session, SessionMixin, SessionStore
|
||||
from kaya.session._cookie import format_set_cookie, parse_cookie_value
|
||||
|
||||
|
||||
@@ -24,42 +24,40 @@ class FakeClock:
|
||||
class SessionTest(unittest.TestCase):
|
||||
app: KayaApp
|
||||
store: InMemorySessionStore
|
||||
session_app: SessionMiddleware
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.app = KayaApp()
|
||||
self.store = InMemorySessionStore()
|
||||
self.session_app = SessionMiddleware(self.app, self.store)
|
||||
self.app = KayaApp(mixins=[SessionMixin(self.store)])
|
||||
|
||||
@self.session_app.GET('/')
|
||||
@self.app.GET('/')
|
||||
async def home(ctx: HttpContext) -> None:
|
||||
n = ctx.session.get('visits', 0) + 1
|
||||
ctx.session['visits'] = n
|
||||
await ctx.send_str(200, f'visits: {n}')
|
||||
|
||||
@self.session_app.GET('/read')
|
||||
@self.app.GET('/read')
|
||||
async def read(ctx: HttpContext) -> None:
|
||||
n = ctx.session.get('visits', 0)
|
||||
await ctx.send_str(200, f'visits: {n}')
|
||||
|
||||
@self.session_app.GET('/write')
|
||||
@self.app.GET('/write')
|
||||
async def write(ctx: HttpContext) -> None:
|
||||
ctx.session['foo'] = 'bar'
|
||||
await ctx.send_str(200, 'ok')
|
||||
|
||||
@self.session_app.GET('/clear')
|
||||
@self.app.GET('/clear')
|
||||
async def clear(ctx: HttpContext) -> None:
|
||||
ctx.session.invalidate()
|
||||
await ctx.send_str(200, 'cleared')
|
||||
|
||||
@self.session_app.GET('/rotate')
|
||||
@self.app.GET('/rotate')
|
||||
async def rotate(ctx: HttpContext) -> None:
|
||||
ctx.session.regenerate_id()
|
||||
await ctx.send_str(200, 'rotated')
|
||||
|
||||
@async_test
|
||||
async def test_session_persists_across_requests(self) -> None:
|
||||
transport = httpx.ASGITransport(app=self.session_app)
|
||||
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('/')
|
||||
self.assertEqual(200, r.status_code)
|
||||
@@ -72,7 +70,7 @@ class SessionTest(unittest.TestCase):
|
||||
|
||||
@async_test
|
||||
async def test_no_cookie_when_session_not_modified(self) -> None:
|
||||
transport = httpx.ASGITransport(app=self.session_app)
|
||||
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('/read')
|
||||
self.assertEqual(200, r.status_code)
|
||||
@@ -81,7 +79,7 @@ class SessionTest(unittest.TestCase):
|
||||
|
||||
@async_test
|
||||
async def test_existing_session_refreshes_cookie(self) -> None:
|
||||
transport = httpx.ASGITransport(app=self.session_app)
|
||||
transport = httpx.ASGITransport(app=self.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url='http://127.0.0.1:80') as client:
|
||||
await client.get('/')
|
||||
r = await client.get('/read')
|
||||
@@ -91,7 +89,7 @@ class SessionTest(unittest.TestCase):
|
||||
|
||||
@async_test
|
||||
async def test_cookie_attributes(self) -> None:
|
||||
transport = httpx.ASGITransport(app=self.session_app)
|
||||
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('/')
|
||||
set_cookie = r.headers['Set-Cookie']
|
||||
@@ -102,7 +100,7 @@ class SessionTest(unittest.TestCase):
|
||||
|
||||
@async_test
|
||||
async def test_sessions_are_isolated(self) -> None:
|
||||
transport = httpx.ASGITransport(app=self.session_app)
|
||||
transport = httpx.ASGITransport(app=self.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url='http://127.0.0.1:80') as client:
|
||||
r1 = await client.get('/')
|
||||
r2 = await client.get('/')
|
||||
@@ -115,7 +113,7 @@ class SessionTest(unittest.TestCase):
|
||||
|
||||
@async_test
|
||||
async def test_invalidate(self) -> None:
|
||||
transport = httpx.ASGITransport(app=self.session_app)
|
||||
transport = httpx.ASGITransport(app=self.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url='http://127.0.0.1:80') as client:
|
||||
await client.get('/')
|
||||
r = await client.get('/clear')
|
||||
@@ -129,7 +127,7 @@ class SessionTest(unittest.TestCase):
|
||||
|
||||
@async_test
|
||||
async def test_regenerate_id(self) -> None:
|
||||
transport = httpx.ASGITransport(app=self.session_app)
|
||||
transport = httpx.ASGITransport(app=self.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url='http://127.0.0.1:80') as client:
|
||||
await client.get('/')
|
||||
cookies_before = {c.name: c.value for c in client.cookies.jar}
|
||||
@@ -153,7 +151,7 @@ class SessionTest(unittest.TestCase):
|
||||
|
||||
@async_test
|
||||
async def test_invalid_cookie_creates_fresh_session(self) -> None:
|
||||
transport = httpx.ASGITransport(app=self.session_app)
|
||||
transport = httpx.ASGITransport(app=self.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url='http://127.0.0.1:80') as client:
|
||||
client.cookies.set('session_id', 'not-a-real-id')
|
||||
r = await client.get('/')
|
||||
@@ -164,19 +162,18 @@ class SessionTest(unittest.TestCase):
|
||||
async def test_stale_cookie_cannot_access_old_data(self) -> None:
|
||||
clock = FakeClock()
|
||||
store = InMemorySessionStore(clock=clock)
|
||||
app = KayaApp()
|
||||
session_app = SessionMiddleware(app, store, max_age=60)
|
||||
app = KayaApp(mixins=[SessionMixin(store, max_age=60)])
|
||||
|
||||
@session_app.GET('/')
|
||||
@app.GET('/')
|
||||
async def home(ctx: HttpContext) -> None:
|
||||
ctx.session['secret'] = 'super-sensitive'
|
||||
await ctx.send_str(200, 'ok')
|
||||
|
||||
@session_app.GET('/read')
|
||||
@app.GET('/read')
|
||||
async def read(ctx: HttpContext) -> None:
|
||||
await ctx.send_str(200, ctx.session.get('secret', 'none'))
|
||||
|
||||
transport = httpx.ASGITransport(app=session_app)
|
||||
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('/')
|
||||
self.assertEqual('ok', r.text)
|
||||
|
||||
Reference in New Issue
Block a user