Add swagger-drogon generator
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from swagger_drogon.contract import load_contract
|
||||
from swagger_drogon.generator import generate
|
||||
from swagger_drogon.infer import infer_operation, load_sources
|
||||
from swagger_drogon.parser import parse_routes
|
||||
|
||||
|
||||
class SwaggerDrogonTest(unittest.TestCase):
|
||||
def test_parse_route_metadata(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
controllers = root / "controllers"
|
||||
controllers.mkdir()
|
||||
(controllers / "SampleController.h").write_text(
|
||||
"""
|
||||
class SampleController {
|
||||
METHOD_LIST_BEGIN
|
||||
/*
|
||||
* openapi:
|
||||
* tags: Sample
|
||||
* summary: List samples
|
||||
* description: Lists samples.
|
||||
* auth: public
|
||||
* body: none
|
||||
* response: Sample list.
|
||||
*/
|
||||
ADD_METHOD_TO(SampleController::list, "/api/v1/samples", drogon::Get);
|
||||
METHOD_LIST_END
|
||||
};
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
routes = parse_routes(controllers)
|
||||
self.assertEqual(len(routes), 1)
|
||||
self.assertEqual(routes[0].operation_id, "SampleController.list")
|
||||
self.assertEqual(routes[0].metadata["summary"], "List samples")
|
||||
|
||||
def test_infer_query_and_json_fields(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
controllers = root / "controllers"
|
||||
services = root / "services"
|
||||
controllers.mkdir()
|
||||
services.mkdir()
|
||||
header = controllers / "SampleController.h"
|
||||
header.write_text(
|
||||
"""
|
||||
class SampleController {
|
||||
METHOD_LIST_BEGIN
|
||||
/*
|
||||
* openapi:
|
||||
* tags: Sample
|
||||
* summary: Create sample
|
||||
* description: Creates a sample.
|
||||
* auth: bearer
|
||||
* body: json
|
||||
* response: Created sample.
|
||||
*/
|
||||
ADD_METHOD_TO(SampleController::create, "/api/v1/samples", drogon::Post);
|
||||
METHOD_LIST_END
|
||||
};
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(controllers / "SampleController.cc").write_text(
|
||||
"""
|
||||
void SampleController::create(const drogon::HttpRequestPtr &req) {
|
||||
auto status = queryValue(req, "status");
|
||||
SampleService service;
|
||||
service.create(body);
|
||||
}
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(services / "SampleService.cc").write_text(
|
||||
"""
|
||||
Json::Value SampleService::create(const Json::Value &body) {
|
||||
const auto missing = validation::missingStrings(body, {"name"});
|
||||
if (!validation::hasNumber(body, "category_id")) {}
|
||||
auto response = success();
|
||||
response["sample_id"] = Json::Int64(1);
|
||||
return response;
|
||||
}
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
route = parse_routes(controllers)[0]
|
||||
inferred = infer_operation(route, load_sources([controllers, services]))
|
||||
self.assertEqual(inferred.query_parameters[0]["name"], "status")
|
||||
self.assertIn("name", inferred.request_schema["required"])
|
||||
self.assertIn("category_id", inferred.request_schema["required"])
|
||||
self.assertIn("sample_id", inferred.response_schema["properties"])
|
||||
|
||||
def test_strict_validation_rejects_generic_contract(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
controllers = root / "controllers"
|
||||
controllers.mkdir()
|
||||
(controllers / "SampleController.h").write_text(
|
||||
"""
|
||||
class SampleController {
|
||||
METHOD_LIST_BEGIN
|
||||
/*
|
||||
* openapi:
|
||||
* tags: Sample
|
||||
* summary: Create sample
|
||||
* description: Creates a sample.
|
||||
* auth: public
|
||||
* body: json
|
||||
* response: Created sample.
|
||||
*/
|
||||
ADD_METHOD_TO(SampleController::create, "/api/v1/samples", drogon::Post);
|
||||
METHOD_LIST_END
|
||||
};
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "generic object"):
|
||||
generate(
|
||||
controllers_dir=controllers,
|
||||
source_dirs=[],
|
||||
contract={
|
||||
"operations": {
|
||||
"SampleController.create": {
|
||||
"request": {"type": "object"},
|
||||
"response": {"type": "object"},
|
||||
}
|
||||
}
|
||||
},
|
||||
title="Sample",
|
||||
version="0.1.0",
|
||||
server="http://localhost",
|
||||
)
|
||||
|
||||
def test_visione_contract_generates_concrete_media_path(self) -> None:
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
spec = generate(
|
||||
controllers_dir=repo / "controllers",
|
||||
source_dirs=[repo / "controllers", repo / "services"],
|
||||
contract=load_contract(repo / "docs/openapi/visione.yaml"),
|
||||
title="Visione API",
|
||||
version="0.1.0",
|
||||
server="http://localhost:5555",
|
||||
)
|
||||
self.assertIn("/api/v1/reports/{id}/media", spec["paths"])
|
||||
self.assertFalse(any("/photos" in path for path in spec["paths"]))
|
||||
for path, path_item in spec["paths"].items():
|
||||
for method, operation in path_item.items():
|
||||
if method == "get":
|
||||
self.assertNotIn("requestBody", operation, path)
|
||||
success = next(
|
||||
response
|
||||
for status, response in operation["responses"].items()
|
||||
if str(status).startswith("2")
|
||||
)
|
||||
self.assertTrue(success.get("content"), operation["operationId"])
|
||||
json.dumps(spec)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user