feat(auth): single-admin password login (signed cookie) — gate tuning + Sources catalog, keep chat and document viewer public
This commit is contained in:
@@ -63,8 +63,8 @@ def _turn_log_lines(caplog: pytest.LogCaptureFixture) -> list[str]:
|
||||
# ---------- CRUD ----------
|
||||
|
||||
|
||||
def test_create_note_returns_201_and_stores_trimmed(client: TestClient, db) -> None:
|
||||
r = client.post("/api/steering", json={"note": f" {NOTE} "})
|
||||
def test_create_note_returns_201_and_stores_trimmed(admin_client: TestClient, db) -> None:
|
||||
r = admin_client.post("/api/steering", json={"note": f" {NOTE} "})
|
||||
assert r.status_code == 201
|
||||
body = r.json()
|
||||
assert body["note"] == NOTE # trimmed before storage
|
||||
@@ -74,13 +74,13 @@ def test_create_note_returns_201_and_stores_trimmed(client: TestClient, db) -> N
|
||||
assert [row.note for row in rows] == [NOTE]
|
||||
|
||||
|
||||
def test_list_notes_empty(client: TestClient) -> None:
|
||||
r = client.get("/api/steering")
|
||||
def test_list_notes_empty(admin_client: TestClient) -> None:
|
||||
r = admin_client.get("/api/steering")
|
||||
assert r.status_code == 200
|
||||
assert r.json() == {"notes": []}
|
||||
|
||||
|
||||
def test_list_notes_newest_first(client: TestClient, db) -> None:
|
||||
def test_list_notes_newest_first(admin_client: TestClient, db) -> None:
|
||||
base = datetime.now(UTC)
|
||||
db.add_all(
|
||||
[
|
||||
@@ -91,7 +91,7 @@ def test_list_notes_newest_first(client: TestClient, db) -> None:
|
||||
)
|
||||
db.commit()
|
||||
|
||||
r = client.get("/api/steering")
|
||||
r = admin_client.get("/api/steering")
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert [n["note"] for n in body["notes"]] == ["newest", "middle", "oldest"]
|
||||
@@ -100,33 +100,33 @@ def test_list_notes_newest_first(client: TestClient, db) -> None:
|
||||
uuid.UUID(n["id"])
|
||||
|
||||
|
||||
def test_delete_note_returns_204_and_removes(client: TestClient, db) -> None:
|
||||
created = client.post("/api/steering", json={"note": NOTE}).json()
|
||||
def test_delete_note_returns_204_and_removes(admin_client: TestClient, db) -> None:
|
||||
created = admin_client.post("/api/steering", json={"note": NOTE}).json()
|
||||
|
||||
assert client.delete(f"/api/steering/{created['id']}").status_code == 204
|
||||
assert client.get("/api/steering").json() == {"notes": []}
|
||||
assert admin_client.delete(f"/api/steering/{created['id']}").status_code == 204
|
||||
assert admin_client.get("/api/steering").json() == {"notes": []}
|
||||
assert db.scalars(select(SteeringNote)).all() == []
|
||||
|
||||
|
||||
def test_delete_unknown_note_returns_404(client: TestClient) -> None:
|
||||
r = client.delete(f"/api/steering/{uuid.uuid4()}")
|
||||
def test_delete_unknown_note_returns_404(admin_client: TestClient) -> None:
|
||||
r = admin_client.delete(f"/api/steering/{uuid.uuid4()}")
|
||||
assert r.status_code == 404
|
||||
assert "not found" in r.json()["detail"]
|
||||
|
||||
|
||||
def test_delete_invalid_id_returns_422(client: TestClient) -> None:
|
||||
assert client.delete("/api/steering/not-a-uuid").status_code == 422
|
||||
def test_delete_invalid_id_returns_422(admin_client: TestClient) -> None:
|
||||
assert admin_client.delete("/api/steering/not-a-uuid").status_code == 422
|
||||
|
||||
|
||||
def test_create_rejects_empty_and_blank_notes(client: TestClient) -> None:
|
||||
assert client.post("/api/steering", json={"note": ""}).status_code == 422
|
||||
assert client.post("/api/steering", json={"note": " \t\n "}).status_code == 422
|
||||
assert client.get("/api/steering").json() == {"notes": []}
|
||||
def test_create_rejects_empty_and_blank_notes(admin_client: TestClient) -> None:
|
||||
assert admin_client.post("/api/steering", json={"note": ""}).status_code == 422
|
||||
assert admin_client.post("/api/steering", json={"note": " \t\n "}).status_code == 422
|
||||
assert admin_client.get("/api/steering").json() == {"notes": []}
|
||||
|
||||
|
||||
def test_create_enforces_2000_char_limit(client: TestClient) -> None:
|
||||
assert client.post("/api/steering", json={"note": "x" * 2001}).status_code == 422
|
||||
r = client.post("/api/steering", json={"note": "x" * 2000})
|
||||
def test_create_enforces_2000_char_limit(admin_client: TestClient) -> None:
|
||||
assert admin_client.post("/api/steering", json={"note": "x" * 2001}).status_code == 422
|
||||
r = admin_client.post("/api/steering", json={"note": "x" * 2000})
|
||||
assert r.status_code == 201
|
||||
assert len(r.json()["note"]) == 2000
|
||||
|
||||
@@ -135,14 +135,14 @@ def test_create_enforces_2000_char_limit(client: TestClient) -> None:
|
||||
|
||||
|
||||
def test_chat_turn_high_mode_receives_note_in_system_prompt(
|
||||
client: TestClient, seeded_kb: FakeRagLLM, caplog: pytest.LogCaptureFixture
|
||||
admin_client: TestClient, seeded_kb: FakeRagLLM, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
client.post("/api/steering", json={"note": NOTE})
|
||||
admin_client.post("/api/steering", json={"note": NOTE})
|
||||
caplog.set_level(logging.INFO, logger="app.chat")
|
||||
|
||||
fastapi_app.dependency_overrides[chat_api.get_llm] = lambda: seeded_kb
|
||||
try:
|
||||
_, _, frames = _stream_chat(client, QUESTION)
|
||||
_, _, frames = _stream_chat(admin_client, QUESTION)
|
||||
finally:
|
||||
fastapi_app.dependency_overrides.clear()
|
||||
|
||||
@@ -170,14 +170,14 @@ def test_chat_turn_high_mode_receives_note_in_system_prompt(
|
||||
|
||||
|
||||
def test_chat_turn_low_mode_receives_note_in_system_prompt(
|
||||
client: TestClient, seeded_kb: FakeRagLLM, caplog: pytest.LogCaptureFixture
|
||||
admin_client: TestClient, seeded_kb: FakeRagLLM, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
client.post("/api/steering", json={"note": NOTE})
|
||||
admin_client.post("/api/steering", json={"note": NOTE})
|
||||
caplog.set_level(logging.INFO, logger="app.chat")
|
||||
|
||||
fastapi_app.dependency_overrides[chat_api.get_llm] = lambda: seeded_kb
|
||||
try:
|
||||
_, _, frames = _stream_chat(client, OFF_TOPIC)
|
||||
_, _, frames = _stream_chat(admin_client, OFF_TOPIC)
|
||||
finally:
|
||||
fastapi_app.dependency_overrides.clear()
|
||||
|
||||
@@ -192,13 +192,13 @@ def test_chat_turn_low_mode_receives_note_in_system_prompt(
|
||||
|
||||
|
||||
def test_chat_turn_without_notes_has_no_tuning_section(
|
||||
client: TestClient, seeded_kb: FakeRagLLM, caplog: pytest.LogCaptureFixture
|
||||
admin_client: TestClient, seeded_kb: FakeRagLLM, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
caplog.set_level(logging.INFO, logger="app.chat")
|
||||
|
||||
fastapi_app.dependency_overrides[chat_api.get_llm] = lambda: seeded_kb
|
||||
try:
|
||||
_stream_chat(client, QUESTION)
|
||||
_stream_chat(admin_client, QUESTION)
|
||||
finally:
|
||||
fastapi_app.dependency_overrides.clear()
|
||||
|
||||
@@ -208,7 +208,7 @@ def test_chat_turn_without_notes_has_no_tuning_section(
|
||||
assert lines and "tuning=0" in lines[-1]
|
||||
|
||||
|
||||
def test_chat_turn_numbers_notes_oldest_first(client: TestClient, db, seeded_kb) -> None:
|
||||
def test_chat_turn_numbers_notes_oldest_first(admin_client: TestClient, db, seeded_kb) -> None:
|
||||
base = datetime.now(UTC)
|
||||
db.add_all(
|
||||
[
|
||||
@@ -220,7 +220,7 @@ def test_chat_turn_numbers_notes_oldest_first(client: TestClient, db, seeded_kb)
|
||||
|
||||
fastapi_app.dependency_overrides[chat_api.get_llm] = lambda: seeded_kb
|
||||
try:
|
||||
_stream_chat(client, QUESTION)
|
||||
_stream_chat(admin_client, QUESTION)
|
||||
finally:
|
||||
fastapi_app.dependency_overrides.clear()
|
||||
|
||||
@@ -231,22 +231,22 @@ def test_chat_turn_numbers_notes_oldest_first(client: TestClient, db, seeded_kb)
|
||||
assert system["content"].index("1. older note") < system["content"].index("2. newer note")
|
||||
|
||||
|
||||
def test_multiple_turns_keep_reading_notes(client: TestClient, seeded_kb) -> None:
|
||||
def test_multiple_turns_keep_reading_notes(admin_client: TestClient, seeded_kb) -> None:
|
||||
"""The note steers EVERY subsequent turn, not just the next one."""
|
||||
client.post("/api/steering", json={"note": NOTE})
|
||||
admin_client.post("/api/steering", json={"note": NOTE})
|
||||
|
||||
fastapi_app.dependency_overrides[chat_api.get_llm] = lambda: seeded_kb
|
||||
try:
|
||||
_stream_chat(client, QUESTION)
|
||||
_stream_chat(client, QUESTION)
|
||||
_stream_chat(admin_client, QUESTION)
|
||||
_stream_chat(admin_client, QUESTION)
|
||||
assert len(seeded_kb.seen_messages) == 2
|
||||
for messages in seeded_kb.seen_messages:
|
||||
assert f"1. {NOTE}" in messages[0]["content"]
|
||||
|
||||
# Delete → the following turn is clean again.
|
||||
note_id = client.get("/api/steering").json()["notes"][0]["id"]
|
||||
assert client.delete(f"/api/steering/{note_id}").status_code == 204
|
||||
_stream_chat(client, QUESTION)
|
||||
note_id = admin_client.get("/api/steering").json()["notes"][0]["id"]
|
||||
assert admin_client.delete(f"/api/steering/{note_id}").status_code == 204
|
||||
_stream_chat(admin_client, QUESTION)
|
||||
assert len(seeded_kb.seen_messages) == 3
|
||||
assert "<tuning>" not in seeded_kb.seen_messages[-1][0]["content"]
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user