# retoor import json import shutil from datetime import datetime, timezone import pytest from starlette.websockets import WebSocketDisconnect from devplacepy.config import DBAPI_DIR from devplacepy.services.manager import service_manager def test_query_requires_auth(client): assert client.post("/dbapi/query", json={"sql": "SELECT 1"}).status_code == 403 def test_select_runs(client, auth): response = client.post("/dbapi/query", json={"sql": "SELECT 1 AS n"}, headers=auth) assert response.status_code == 200 body = response.json() assert body["valid"] is True assert body["rows"] == [{"n": 1}] def test_non_select_is_409(client, auth): response = client.post( "/dbapi/query", json={"sql": "DELETE FROM posts"}, headers=auth ) assert response.status_code == 409 assert response.json()["statement_type"] == "delete" def test_invalid_sql_is_400(client, auth): response = client.post( "/dbapi/query", json={"sql": "SELECT * FROM no_such_table_zzz"}, headers=auth ) assert response.status_code == 400 def test_suspicious_select_is_flagged(client, auth): response = client.post( "/dbapi/query", json={"sql": "SELECT * FROM users"}, headers=auth ) assert response.status_code == 200 assert response.json()["suspicious"] def test_async_enqueue_and_status(client, auth): enqueued = client.post( "/dbapi/query/async", json={"sql": "SELECT uid FROM users LIMIT 3"}, headers=auth ) assert enqueued.status_code == 200 uid = enqueued.json()["uid"] assert enqueued.json()["ws_url"].endswith("/ws") assert client.get(f"/dbapi/query/{uid}", headers=auth).status_code == 200 def test_async_non_select_is_409(client, auth): response = client.post( "/dbapi/query/async", json={"sql": "UPDATE users SET role = 1"}, headers=auth ) assert response.status_code == 409 def test_ws_unknown_job_is_closed(client, auth): with client.websocket_connect("/dbapi/query/not-a-real-uid/ws") as ws: with pytest.raises(WebSocketDisconnect) as info: ws.receive_json() assert info.value.code == 1008 def test_ws_non_owner_retries(client, auth): service_manager.set_lock_owner(False) try: with client.websocket_connect("/dbapi/query/anything/ws") as ws: with pytest.raises(WebSocketDisconnect) as info: ws.receive_json() assert info.value.code == 4013 finally: service_manager.set_lock_owner(True) def test_result_route_serves_the_persisted_rows(client, auth): enqueued = client.post( "/dbapi/query/async", json={"sql": "SELECT 1 AS n"}, headers=auth ) uid = enqueued.json()["uid"] output_dir = DBAPI_DIR / uid output_dir.mkdir(parents=True, exist_ok=True) result = { "sql": "SELECT 1 AS n", "row_count": 1, "truncated": False, "suspicious": [], "rows": [{"n": 1}], } (output_dir / "result.json").write_text(json.dumps(result), encoding="utf-8") try: response = client.get(f"/dbapi/query/{uid}/result", headers=auth) assert response.status_code == 200, response.text assert response.json()["rows"] == [{"n": 1}] finally: shutil.rmtree(output_dir, ignore_errors=True) def test_result_route_404s_without_a_written_result(client, auth): enqueued = client.post( "/dbapi/query/async", json={"sql": "SELECT 1 AS n"}, headers=auth ) uid = enqueued.json()["uid"] response = client.get(f"/dbapi/query/{uid}/result", headers=auth) assert response.status_code == 404 def test_result_route_rejects_a_traversal_uid(client, auth): from devplacepy.database import get_table evil_uid = ".." now = datetime.now(timezone.utc).isoformat() get_table("jobs").insert( { "uid": evil_uid, "kind": "dbquery", "status": "done", "owner_kind": "user", "owner_id": "traversal-owner", "preferred_name": "", "payload": "{}", "result": "{}", "error": "", "retry_count": 0, "created_at": now, "started_at": now, "completed_at": now, "updated_at": now, "duration_ms": 0, "last_accessed_at": "", "expires_at": "", "bytes_in": 0, "bytes_out": 0, "item_count": 0, } ) escape_target = DBAPI_DIR.parent / "result.json" try: response = client.get(f"/dbapi/query/{evil_uid}/result", headers=auth) assert response.status_code in (400, 404), response.text assert not escape_target.exists() finally: get_table("jobs").delete(uid=evil_uid)