From df0a4a2be242a8bd5d318e71dcca0d90e0e1cc6a Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Sun, 11 Sep 2022 19:04:42 -0700 Subject: [PATCH] feat(rpc): add /rpc/v5/{type} openapi-compatible routes We will be modeling future RPC implementations on an OpenAPI spec. While this commit does not completely cohere to OpenAPI in terms of response data, this is a good start and will allow us to cleanly document these openapi routes in the current and future. This commit brings in the new RPC routes: - GET /rpc/v5/info/{pkgname} - GET /rpc/v5/info?arg[]=pkg1&arg[]=pkg2 - POST /rpc/v5/info with JSON data `{"arg": ["pkg1", "pkg2"]}` - GET /rpc/v5/search?arg=keywords&by=valid-by-value - POST /rpc/v5/search with JSON data `{"by": "valid-by-value", "arg": "keywords"}` Signed-off-by: Kevin Morris --- aurweb/routers/rpc.py | 104 ++++++++++++++++++++++++++++++++++++++++++ test/test_rpc.py | 95 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/aurweb/routers/rpc.py b/aurweb/routers/rpc.py index a0cf5019..9777c0a2 100644 --- a/aurweb/routers/rpc.py +++ b/aurweb/routers/rpc.py @@ -160,3 +160,107 @@ async def rpc_post( callback: Optional[str] = Form(default=None), ): return await rpc_request(request, v, type, by, arg, args, callback) + + +@router.get("/rpc/v{version}/info/{name}") +async def rpc_openapi_info(request: Request, version: int, name: str): + return await rpc_request( + request, + version, + "info", + defaults.RPC_SEARCH_BY, + name, + [], + ) + + +@router.get("/rpc/v{version}/info") +async def rpc_openapi_multiinfo( + request: Request, + version: int, + args: Optional[list[str]] = Query(default=[], alias="arg[]"), +): + arg = args.pop(0) if args else None + return await rpc_request( + request, + version, + "info", + defaults.RPC_SEARCH_BY, + arg, + args, + ) + + +@router.post("/rpc/v{version}/info") +async def rpc_openapi_multiinfo_post( + request: Request, + version: int, +): + data = await request.json() + + args = data.get("arg", []) + if not isinstance(args, list): + rpc = RPC(version, "info") + return JSONResponse( + rpc.error("the 'arg' parameter must be of array type"), + status_code=HTTPStatus.BAD_REQUEST, + ) + + arg = args.pop(0) if args else None + return await rpc_request( + request, + version, + "info", + defaults.RPC_SEARCH_BY, + arg, + args, + ) + + +@router.get("/rpc/v{version}/search") +async def rpc_openapi_search( + request: Request, + version: int, + by: Optional[str] = Query(default=defaults.RPC_SEARCH_BY), + arg: Optional[str] = Query(default=str()), +): + return await rpc_request( + request, + version, + "search", + by, + arg, + [], + ) + + +@router.post("/rpc/v{version}/search") +async def rpc_openapi_search_post( + request: Request, + version: int, +): + data = await request.json() + by = data.get("by", defaults.RPC_SEARCH_BY) + if not isinstance(by, str): + rpc = RPC(version, "search") + return JSONResponse( + rpc.error("the 'by' parameter must be of string type"), + status_code=HTTPStatus.BAD_REQUEST, + ) + + arg = data.get("arg", str()) + if not isinstance(arg, str): + rpc = RPC(version, "search") + return JSONResponse( + rpc.error("the 'arg' parameter must be of string type"), + status_code=HTTPStatus.BAD_REQUEST, + ) + + return await rpc_request( + request, + version, + "search", + by, + arg, + [], + ) diff --git a/test/test_rpc.py b/test/test_rpc.py index ed7e8894..0edd3e2e 100644 --- a/test/test_rpc.py +++ b/test/test_rpc.py @@ -933,3 +933,98 @@ def test_rpc_too_many_info_results(client: TestClient, packages: list[Package]): with client as request: resp = request.get("/rpc", params=params) assert resp.json().get("error") == "Too many package results." + + +def test_rpc_openapi_info(client: TestClient, packages: list[Package]): + pkgname = packages[0].Name + + with client as request: + endp = f"/rpc/v5/info/{pkgname}" + resp = request.get(endp) + assert resp.status_code == HTTPStatus.OK + + data = resp.json() + assert data.get("resultcount") == 1 + + +def test_rpc_openapi_multiinfo(client: TestClient, packages: list[Package]): + pkgname = packages[0].Name + + with client as request: + endp = "/rpc/v5/info" + resp = request.get(endp, params={"arg[]": [pkgname]}) + assert resp.status_code == HTTPStatus.OK + + data = resp.json() + assert data.get("resultcount") == 1 + + +def test_rpc_openapi_multiinfo_post(client: TestClient, packages: list[Package]): + pkgname = packages[0].Name + + with client as request: + endp = "/rpc/v5/info" + resp = request.post(endp, json={"arg": [pkgname]}) + assert resp.status_code == HTTPStatus.OK + + data = resp.json() + assert data.get("resultcount") == 1 + + +def test_rpc_openapi_multiinfo_post_bad_request( + client: TestClient, packages: list[Package] +): + pkgname = packages[0].Name + + with client as request: + endp = "/rpc/v5/info" + resp = request.post(endp, json={"arg": pkgname}) + assert resp.status_code == HTTPStatus.BAD_REQUEST + + data = resp.json() + expected = "the 'arg' parameter must be of array type" + assert data.get("error") == expected + + +def test_rpc_openapi_search(client: TestClient, packages: list[Package]): + pkgname = packages[0].Name + + with client as request: + endp = "/rpc/v5/search" + resp = request.get(endp, params={"arg": pkgname}) + assert resp.status_code == HTTPStatus.OK + + data = resp.json() + assert data.get("resultcount") == 1 + + +def test_rpc_openapi_search_post(client: TestClient, packages: list[Package]): + pkgname = packages[0].Name + + with client as request: + endp = "/rpc/v5/search" + resp = request.post(endp, json={"arg": pkgname}) + assert resp.status_code == HTTPStatus.OK + + data = resp.json() + assert data.get("resultcount") == 1 + + +def test_rpc_openapi_search_post_bad_request(client: TestClient): + # Test by parameter + with client as request: + endp = "/rpc/v5/search" + resp = request.post(endp, json={"by": 1}) + assert resp.status_code == HTTPStatus.BAD_REQUEST + data = resp.json() + expected = "the 'by' parameter must be of string type" + assert data.get("error") == expected + + # Test arg parameter + with client as request: + endp = "/rpc/v5/search" + resp = request.post(endp, json={"arg": ["a", "list"]}) + assert resp.status_code == HTTPStatus.BAD_REQUEST + data = resp.json() + expected = "the 'arg' parameter must be of string type" + assert data.get("error") == expected