From 0e988c951e5f99d7847aed2267cc8f4c2bcbeb4c Mon Sep 17 00:00:00 2001 From: Yeongseon Choe Date: Mon, 26 Oct 2026 23:22:54 +0900 Subject: [PATCH] refactor(api): forward facade arguments explicitly by keyword --- src/azure_functions_openapi/__init__.py | 94 ++++++++++++------------- tests/test_facade_forwarding.py | 67 ++++++++++++++++++ 2 files changed, 114 insertions(+), 47 deletions(-) create mode 100644 tests/test_facade_forwarding.py diff --git a/src/azure_functions_openapi/__init__.py b/src/azure_functions_openapi/__init__.py index 3b809e3..e747071 100644 --- a/src/azure_functions_openapi/__init__.py +++ b/src/azure_functions_openapi/__init__.py @@ -122,22 +122,22 @@ def get_openapi_json( """Return JSON after optionally reconciling routes from a completed FunctionApp.""" try: spec = generate_openapi_spec( - title, - version, - openapi_version, - description, - security_schemes, - route_prefix, - strict, - registry, - hoist_flat_schemas, - infer_auth_level, - servers, - contact, - license, - external_docs, - tags, - app, + title=title, + version=version, + openapi_version=openapi_version, + description=description, + security_schemes=security_schemes, + route_prefix=route_prefix, + strict=strict, + registry=registry, + hoist_flat_schemas=hoist_flat_schemas, + infer_auth_level=infer_auth_level, + servers=servers, + contact=contact, + license=license, + external_docs=external_docs, + tags=tags, + app=app, ) return json.dumps(spec, indent=2, ensure_ascii=False) except OpenAPISpecConfigError: @@ -167,22 +167,22 @@ def get_openapi_yaml( """Return YAML after optionally reconciling routes from a completed FunctionApp.""" try: spec = generate_openapi_spec( - title, - version, - openapi_version, - description, - security_schemes, - route_prefix, - strict, - registry, - hoist_flat_schemas, - infer_auth_level, - servers, - contact, - license, - external_docs, - tags, - app, + title=title, + version=version, + openapi_version=openapi_version, + description=description, + security_schemes=security_schemes, + route_prefix=route_prefix, + strict=strict, + registry=registry, + hoist_flat_schemas=hoist_flat_schemas, + infer_auth_level=infer_auth_level, + servers=servers, + contact=contact, + license=license, + external_docs=external_docs, + tags=tags, + app=app, ) return yaml.safe_dump(spec, sort_keys=False, allow_unicode=True) except OpenAPISpecConfigError: @@ -214,21 +214,21 @@ def generate_openapi_report( registry = OpenAPIRegistry() unresolved = _prepare_generation(app, route_prefix, registry) report = _generate_openapi_report( - title, - version, - openapi_version, - description, - security_schemes, - route_prefix, - strict, - registry, - hoist_flat_schemas, - infer_auth_level, - servers, - contact, - license, - external_docs, - tags, + title=title, + version=version, + openapi_version=openapi_version, + description=description, + security_schemes=security_schemes, + route_prefix=route_prefix, + strict=strict, + registry=registry, + hoist_flat_schemas=hoist_flat_schemas, + infer_auth_level=infer_auth_level, + servers=servers, + contact=contact, + license=license, + external_docs=external_docs, + tags=tags, ) return SpecReport(spec=report.spec, warnings=(*report.warnings, *unresolved)) diff --git a/tests/test_facade_forwarding.py b/tests/test_facade_forwarding.py new file mode 100644 index 0000000..1f3a6c5 --- /dev/null +++ b/tests/test_facade_forwarding.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +from collections.abc import Callable +import inspect +from typing import Any +from unittest.mock import patch + +import pytest + +import azure_functions_openapi as api +from azure_functions_openapi.registry import OpenAPIRegistry +from azure_functions_openapi.spec import SpecReport + + +@pytest.mark.parametrize( + "facade", [api.get_openapi_json, api.get_openapi_yaml, api.generate_openapi_report] +) +def test_facade_parameters_match_spec_signature(facade: Callable[..., Any]) -> None: + # Given/When: inspect the explicit public signatures. + actual = inspect.signature(facade).parameters + expected = inspect.signature(api.generate_openapi_spec).parameters + # Then: order, defaults, annotations and positional compatibility agree. + assert actual == expected + assert all(p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD for p in actual.values()) + + +@pytest.mark.parametrize( + "facade", [api.get_openapi_json, api.get_openapi_yaml, api.generate_openapi_report] +) +@pytest.mark.parametrize("positional", [False, True]) +def test_facades_forward_every_distinct_argument( + facade: Callable[..., Any], positional: bool +) -> None: + # Given: distinct values exercise every forwarding slot, including adjacent booleans. + options = { + "title": "distinct title", + "version": "2.3", + "openapi_version": "3.2.0", + "description": "description", + "security_schemes": {"key": {"type": "apiKey"}}, + "route_prefix": "/custom", + "strict": True, + "registry": OpenAPIRegistry(), + "hoist_flat_schemas": True, + "infer_auth_level": False, + "servers": [{"url": "https://example.test"}], + "contact": {"name": "contact"}, + "license": {"name": "license"}, + "external_docs": {"url": "https://example.test/docs"}, + "tags": [{"name": "tag"}], + "app": None, + } + report_facade = facade is api.generate_openapi_report + target = "_generate_openapi_report" if report_facade else "generate_openapi_spec" + result: SpecReport | dict[str, Any] = SpecReport(spec={}, warnings=()) if report_facade else {} + # When: callers use either supported argument form. + with patch.object(api, target, return_value=result) as generate: + if positional: + facade(*options.values()) + else: + facade(**options) + # Then: binding the underlying call recovers every input without reordering or omission. + signature = inspect.signature(api.generate_openapi_spec) + bound = signature.bind(*generate.call_args.args, **generate.call_args.kwargs) + expected = {k: v for k, v in options.items() if not report_facade or k != "app"} + assert bound.arguments == expected + assert generate.call_args.args == ()