feat(chat): share a chat by link — anonymous read-only /shared/<token> page, share/unshare
This commit is contained in:
@@ -17,6 +17,7 @@ import asyncio
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import uuid
|
||||
from collections.abc import AsyncIterator, Iterator
|
||||
from pathlib import Path
|
||||
|
||||
@@ -205,6 +206,7 @@ def test_html_pages_include_history() -> None:
|
||||
"/tuning.html",
|
||||
"/git-sources.html",
|
||||
"/history.html",
|
||||
"/shared.html", # phase 51: the shared page's static path
|
||||
):
|
||||
assert path in caching.HTML_PAGES, f"{path} must be in HTML_PAGES"
|
||||
|
||||
@@ -386,3 +388,67 @@ def test_read_body_drains_streaming_response() -> None:
|
||||
|
||||
resp = StreamingResponse(content=gen(), media_type="text/html")
|
||||
assert asyncio.run(caching._read_body(resp)) == b"<a></a>"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 51: the dynamic share page — the ``/shared/`` prefix contract
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _shared_page_app() -> FastAPI:
|
||||
"""A bare app with the phase-51 route pair + the middleware:
|
||||
``/shared/<token>`` (``text/html`` — the page the route serves) and
|
||||
``/api/shared/<token>`` (the JSON read), plus an unknown path."""
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/shared/{token}", response_class=HTMLResponse)
|
||||
def shared_page(token: str) -> str:
|
||||
return (
|
||||
"<html><head>"
|
||||
'<link rel="stylesheet" href="/assets/styles.css">'
|
||||
"</head><body>shared</body></html>"
|
||||
)
|
||||
|
||||
@app.get("/api/shared/{token}")
|
||||
def shared_api(token: str) -> JSONResponse:
|
||||
return JSONResponse({"title": "Shared", "messages": []})
|
||||
|
||||
@app.get("/some/unknown/path")
|
||||
def unknown() -> JSONResponse:
|
||||
return JSONResponse({"ok": True})
|
||||
|
||||
caching.configure_caching(app)
|
||||
return app
|
||||
|
||||
|
||||
def test_middleware_treats_shared_page_path_as_known_html_page() -> None:
|
||||
"""Phase 51: ``/shared/<uuid>`` joins the HTML_PAGES contract —
|
||||
``no-cache`` + ``?v=`` asset rewrite on the ``text/html`` body (the
|
||||
FileResponse body is drained by the existing ``_read_body`` path)."""
|
||||
client = TestClient(_shared_page_app())
|
||||
r = client.get(f"/shared/{uuid.uuid4()}")
|
||||
assert r.status_code == 200
|
||||
assert r.headers["cache-control"] == "no-cache"
|
||||
token = caching.asset_version()
|
||||
assert f'href="/assets/styles.css?v={token}"' in r.text
|
||||
assert 'href="/assets/styles.css">' not in r.text
|
||||
|
||||
|
||||
def test_middleware_leaves_api_shared_read_untouched() -> None:
|
||||
"""``/api/shared/<uuid>`` — the JSON read — starts with ``/api/``,
|
||||
not ``/shared/``: byte-identical pass-through, no injected headers."""
|
||||
client = TestClient(_shared_page_app())
|
||||
r = client.get(f"/api/shared/{uuid.uuid4()}")
|
||||
assert r.status_code == 200
|
||||
assert "cache-control" not in r.headers
|
||||
assert r.json() == {"title": "Shared", "messages": []}
|
||||
|
||||
|
||||
def test_middleware_leaves_unknown_paths_untouched() -> None:
|
||||
"""A path that is neither a known page, ``/shared/*``, nor
|
||||
``/assets/*`` passes through byte-identical, no headers."""
|
||||
client = TestClient(_shared_page_app())
|
||||
r = client.get("/some/unknown/path")
|
||||
assert r.status_code == 200
|
||||
assert "cache-control" not in r.headers
|
||||
assert r.json() == {"ok": True}
|
||||
|
||||
Reference in New Issue
Block a user