Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ API version 1.2.0 corrects conflation and semantic-type responses and rejects in
flags. It retains the multi-Babel selectors, response headers and status health checks added in 1.1.0.

The API is described by `src/nodenorm/webapp/openapi.json`, which is written by hand and served
verbatim at `/webapp/openapi.json` (with `/openapi.json` redirecting there); the Swagger UI at `/`
renders it. `/status` reports its `info.version` as the API version, so bump that when the API
verbatim at `/openapi.json`, as NodeNorm Redis and most Translator services serve theirs, and also
at the older `/webapp/openapi.json`; the Swagger UI at `/` renders it. `/status` reports its `info.version` as the API version, so bump that when the API
changes. `tests/test_openapi.py` checks that the document validates and that its paths, methods,
query parameters, POST body fields and the `SetIDResponse` schema match the handlers, so a handler
change that isn't reflected in the JSON fails the test suite. The parameter and body checks find
Expand Down
6 changes: 4 additions & 2 deletions src/nodenorm/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ def build_handlers() -> dict[str, tuple[str, Callable]]:
favicon_handler = (r"/favicon\.ico", tornado.web.RedirectHandler, {"url": "/webapp/swaggerui/favicon-32x32.png"})
handler_collection.append(favicon_handler)

# NodeNorm Redis (FastAPI) serves its OpenAPI document at the root; keep that address working
openapi_handler = (r"/openapi\.json", tornado.web.RedirectHandler, {"url": "/webapp/openapi.json"})
# The OpenAPI document is served at the root, where NodeNorm Redis (FastAPI) and most Translator
# services serve theirs. The asset handler above also serves it at /webapp/openapi.json, the
# address Babel Validation's targets and the Translator diagram are configured with.
openapi_handler = (r"/(openapi\.json)", tornado.web.StaticFileHandler, {"path": str(webapp_directory)})
handler_collection.append(openapi_handler)

handlers = {handler[0]: handler for handler in handler_collection}
Expand Down
2 changes: 1 addition & 1 deletion src/nodenorm/webapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// Initialize Swagger UI
window.onload = function() {
const ui = SwaggerUIBundle({
url: "/webapp/openapi.json",
url: "/openapi.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
Expand Down
2 changes: 1 addition & 1 deletion tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def test_root_serves_swagger_ui(self):
assert response.code == 200
assert response.headers["Content-Type"].startswith("text/html")
assert b"SwaggerUIBundle" in response.body
assert b"/webapp/openapi.json" in response.body
assert b'url: "/openapi.json"' in response.body

def test_webapp_serves_openapi_spec(self):
response = self.fetch("/webapp/openapi.json")
Expand Down
16 changes: 9 additions & 7 deletions tests/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,19 @@ def test_setid_response_schema_matches_the_dataclass():
assert documented == {field.name for field in dataclasses.fields(SetIDResponse)}


class TestOpenAPIRedirect(AsyncHTTPTestCase):
"""/openapi.json is where NodeNorm Redis, SmartAPI and Babel Validation look for the document."""
class TestOpenAPIAddresses(AsyncHTTPTestCase):
"""/openapi.json is where NodeNorm Redis serves its document; /webapp/openapi.json is the older address."""

def get_app(self) -> tornado.web.Application:
return tornado.web.Application(list(build_handlers().values()))

def test_openapi_json_redirects_to_the_document(self):
response = self.fetch("/openapi.json", follow_redirects=False)
def test_the_document_is_served_at_both_addresses(self):
for path in ("/openapi.json", "/webapp/openapi.json"):
response = self.fetch(path, follow_redirects=False)

assert response.code == 301
assert response.headers["Location"] == "/webapp/openapi.json"
assert response.code == 200, path
assert response.headers["Content-Type"].startswith("application/json"), path
assert json.loads(response.body) == SPEC, path

def test_served_document_is_valid_openapi(self):
# test_document_is_valid_openapi() reads the file; this checks what the static route serves.
Expand All @@ -191,7 +193,7 @@ def test_served_document_is_valid_openapi(self):
assert response.headers["Content-Type"].startswith("application/json")
validate(json.loads(response.body))

def test_only_the_exact_path_redirects(self):
def test_only_the_exact_path_matches(self):
assert self.fetch("/openapiXjson", follow_redirects=False).code == 404

def test_api_paths_are_matched_exactly(self):
Expand Down