171 lines
6.6 KiB
Python
171 lines
6.6 KiB
Python
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()
|