From 30cd0752f5ea021f30b430088e50153357424e67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:01:11 +0000 Subject: [PATCH 1/8] Initial plan From f676d3b2578436ea3db2360080e9337121c88d07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:06:11 +0000 Subject: [PATCH 2/8] feat: add GET /docs endpoint serving Swagger UI for OpenAPI spec --- .coverage | Bin 0 -> 53248 bytes README.md | 1 + api.yaml | 13 +++++++++ src/event_gate_lambda.py | 1 + src/handlers/handler_api.py | 34 +++++++++++++++++++++++- tests/integration/conftest.py | 4 +++ tests/integration/test_api_endpoint.py | 26 ++++++++++++++++++ tests/unit/handlers/test_handler_api.py | 30 +++++++++++++++++++++ tests/unit/test_event_gate_lambda.py | 11 ++++++++ 9 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 .coverage diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..89085e7c3b7b780fd7111a4b73d480adf0977108 GIT binary patch literal 53248 zcmeI4eQez19mnnS#}M_2lm{hr6? zdub9Br;|{_&&u6nKYpICf4|T3=HFd>TW{TLIJz=yT3O9eKE>5>JkPCH6prIWc*Wr5 zZDH66dIym575fYAid@&k*JI>%P6~aABX`FxA)V5Q^lo&Iv|9XP)ChNoU2p<7B!C2v z01{X>1PZrG;bbbs@7&>NgBjhiw6t!y@39+h>+9Xtr)=xpu(?lh^ORK)1-8ykrB|`c zNo8EOlwl*ID@Ja}NNbLf8&RB5-7B59^&z@uK_0AHv|tyjPxn+~2x>X{2uO@uMpmd;Vb( z7-mk@4QLHehX&l5#cNg2DOaASX0dp&@-P^eGd-;3iuBS@ znc1AnX9ofKSM%9%+Bi_dy|800RCA%aj#+ee(QPwNTbBK;YEBP1ZBe&YK4)yt>!rr; zP|7tsm>W5>L!nMU4_FZmC9jY39P~j+t%`hGx-$ zrkKToVChHN+o-W%gEE*g2dn!U>oK+t0|e#*uh?)tm!?C-raq+23Ek30^seGrcUt(U zI}&uA9x;^cYh9@8l)8Z0Q`(IbQlW6NrG?)qvTjsttzr_c>Ox*i&NGdAr?R?c=Php{ z`LIdSt5KdMy)jZe1MYN&L&@Hjh1@BYQ+tX#wS{mnoJ=J6f;%GUyhL9$RZh^H@(?(0 zAy~Zmo1N+mu1m%;;LqT^pdrCL6A4*b1{@?y9t|tYJ@6jgg5qW|OzL z&}n`f3_5Ro!A>@-<%Wvu64_H4(izLs?-~at zR5hy&xx<|{N`>O&s6I!|U5z$sj_w#)U7aRj@QlWN1AKvwg7EglA{de(U- z%bn~CFiXQNQf23&z$+NiL-z`FMs%Bf&_(B!D&8PP+G&8#lhYjh;f4f|01`j~NB{{S z0VIF~kN^@u0!RP}EI$GQAK=3@{tu8Dj=T+TxFG=~fCP{L5sF4y^-4@QSsN}4yb|~5xO0!5&epc zfO=V@KM5L%#cNDj@S_Em-R-98xM@2hmTrSJeNCW5maH_U4UbW$Zcczq%aUans6opZ zf@k>0r#Ci&SZwiPdB@1u-RxIB)QmYY0>8XLJ&XJE%vCAYS2JKzh638u05VCx#@O1h zuG+d~Xc^-k_$3oFNByc_ieK-ELYrs(WieDZF@7Jf#X_ZVZFO zg>(`&?NKZte^{#LDiG+c@#l;uP~l8}5F~t%ozvq-buGijPM-iuKIY3gO4_&qK5Ynq zjE_a2BvawkYF^+b7vIJeHR<>NDe-!a{7(Fucs+TLtd-xBS4f*AF824>_hWZN_eZB< ziO5muaI`b>xb%p;PtHZQMkMjj$7;DpXORFBKmter2_OL^z;PRcg($!HTdsNI|I`h^ zg6LbMI{t6o6f7isXn428bL0Qy=Ys{ZbR{qTZ`vFzv@BhwI{r`e1q(4BV)MuUjXphF zApVcv6f7it8#8bG->@XV&X51=dxM1<-v(92|8@PrLfnT!DflSvl;VGK;X-cwFRvAL z_%NtC{*NsQ>-q7&v{bC6_&>TnSP*=BQ5lx`9MxXbJ~#dsHv|h|AEtOp6~@$FXxn=6 zf7mCM>bVMH<&aNjJb?;lYJ8HN)8k9=f6(WwGDpdc{{^3mKuM;;slaMs(#J;#6*ckq z|L|r82_OL^fCP{L5u|!pT($j{9ZjKax!|Kb}3Q4jho0AoG$C$Nk_@O=u1!;7G3F8yX>93E}5GD-NzZ z$vu64e4tf|(~^xGcWC9o&(+p7z|j>Aa3s$BkUP2UlZ|2pstwDn zq3xuMG(w*A@HxACtf)mZm5Brl>!vY6#laRm>A|}_)rk?detrp0DA9qA2t1`G2yNBd5txa)i7}OfpLTNS-B6 zktfKbIr!WIHza@rkN^@u z0!RP}AOR$R1dsp{Kmtf$`4XVt|6}~WeA^d2Ljp(u2_OL^fCP{L5 dict[str, Any]: "headers": {"Content-Type": "application/yaml"}, "body": self.api_spec, } + + def get_docs(self) -> dict[str, Any]: + """Return a Swagger UI HTML page pointed at the /api spec. + Returns: + API Gateway response with Swagger UI HTML page. + """ + logger.debug("Handling GET docs.") + html = ( + "\n" + '\n' + "\n" + ' \n' + " EventGate API Docs\n" + ' \n' + "\n" + "\n" + '
\n' + ' \n' + " \n" + "\n" + "" + ) + return { + "statusCode": 200, + "headers": {"Content-Type": "text/html"}, + "body": html, + } diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index debd6dc..28bbb35 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -415,6 +415,10 @@ def get_api(self) -> Dict[str, Any]: """Get OpenAPI specification.""" return self.invoke("/api", "GET") + def get_docs(self) -> Dict[str, Any]: + """Get Swagger UI documentation page.""" + return self.invoke("/docs", "GET") + def get_token(self) -> Dict[str, Any]: """Get token provider info.""" return self.invoke("/token", "GET") diff --git a/tests/integration/test_api_endpoint.py b/tests/integration/test_api_endpoint.py index 8e8c259..4be9353 100644 --- a/tests/integration/test_api_endpoint.py +++ b/tests/integration/test_api_endpoint.py @@ -42,3 +42,29 @@ def test_get_api_body_contains_openapi_spec(self, eventgate_client: EventGateTes assert "openapi:" in body assert "paths:" in body assert "/topics" in body + + +class TestDocsEndpoint: + """Tests for the /docs endpoint.""" + + def test_get_docs_returns_200(self, eventgate_client: EventGateTestClient) -> None: + """Test GET /docs returns successful response.""" + response = eventgate_client.get_docs() + + assert 200 == response["statusCode"] + + def test_get_docs_returns_html_content_type(self, eventgate_client: EventGateTestClient) -> None: + """Test GET /docs returns text/html content type.""" + response = eventgate_client.get_docs() + + assert "Content-Type" in response["headers"] + assert "text/html" in response["headers"]["Content-Type"] + + def test_get_docs_body_contains_swagger_ui(self, eventgate_client: EventGateTestClient) -> None: + """Test GET /docs body contains Swagger UI markers and references ./api.""" + response = eventgate_client.get_docs() + + body = response["body"] + assert "swagger" in body.lower() + assert "SwaggerUIBundle" in body + assert "./api" in body diff --git a/tests/unit/handlers/test_handler_api.py b/tests/unit/handlers/test_handler_api.py index 0139ba6..8827eb3 100644 --- a/tests/unit/handlers/test_handler_api.py +++ b/tests/unit/handlers/test_handler_api.py @@ -54,3 +54,33 @@ def test_get_api_returns_correct_response(mocker): assert 200 == response["statusCode"] assert "application/yaml" == response["headers"]["Content-Type"] assert mock_content == response["body"] + + +def test_get_docs_returns_200(mocker): + """Test get_docs returns status code 200.""" + mocker.patch("builtins.open", mock_open(read_data="openapi: 3.0.0")) + handler = HandlerApi().with_api_definition_loaded() + response = handler.get_docs() + + assert 200 == response["statusCode"] + + +def test_get_docs_returns_html_content_type(mocker): + """Test get_docs returns text/html content type.""" + mocker.patch("builtins.open", mock_open(read_data="openapi: 3.0.0")) + handler = HandlerApi().with_api_definition_loaded() + response = handler.get_docs() + + assert "text/html" == response["headers"]["Content-Type"] + + +def test_get_docs_body_contains_swagger_ui_markers(mocker): + """Test get_docs body contains Swagger UI markers and references ./api.""" + mocker.patch("builtins.open", mock_open(read_data="openapi: 3.0.0")) + handler = HandlerApi().with_api_definition_loaded() + response = handler.get_docs() + + body = response["body"] + assert "swagger-ui" in body + assert "SwaggerUIBundle" in body + assert "./api" in body diff --git a/tests/unit/test_event_gate_lambda.py b/tests/unit/test_event_gate_lambda.py index ab62b1e..142f18f 100644 --- a/tests/unit/test_event_gate_lambda.py +++ b/tests/unit/test_event_gate_lambda.py @@ -33,6 +33,17 @@ def test_get_api_endpoint(event_gate_module, make_event): assert "openapi" in resp["body"].lower() +def test_get_docs_endpoint(event_gate_module, make_event): + event = make_event("/docs") + resp = event_gate_module.lambda_handler(event) + assert 200 == resp["statusCode"] + assert "swagger" in resp["body"].lower() + + +def test_docs_route_in_route_map(event_gate_module): + assert "/docs" in event_gate_module.ROUTE_MAP + + def test_internal_error_path(event_gate_module, make_event): with patch.object(event_gate_module.handler_topic, "get_topics_list", side_effect=RuntimeError("boom")): event = make_event("/topics") From ffb153bcdaa3bd2f6003d6cd55cbb3e57d37fcfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:07:47 +0000 Subject: [PATCH 3/8] fix: tighten get_docs() docstring summary per coding guidelines --- src/handlers/handler_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/handler_api.py b/src/handlers/handler_api.py index 00c3c2f..beda89a 100644 --- a/src/handlers/handler_api.py +++ b/src/handlers/handler_api.py @@ -65,7 +65,7 @@ def get_api(self) -> dict[str, Any]: } def get_docs(self) -> dict[str, Any]: - """Return a Swagger UI HTML page pointed at the /api spec. + """Return a Swagger UI HTML page for browsing the API spec. Returns: API Gateway response with Swagger UI HTML page. """ From 502df7fac16a1d9f835730bdfb937ab9da001136 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 16:43:56 +0200 Subject: [PATCH 4/8] post-review comment --- api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.yaml b/api.yaml index 7c111dc..067c4eb 100644 --- a/api.yaml +++ b/api.yaml @@ -1,6 +1,6 @@ openapi: 3.0.0 info: - title: Event Gate + title: EventGate version: 0.0.0 description: This API provides topic management for an event bus. From b34f3cd8f691249562c832efc50a0e7aaf57ac9a Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 16:55:53 +0200 Subject: [PATCH 5/8] forgotten exception raising - DB-write-unsupported topics should not cause the failure --- src/writers/writer_postgres.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/writers/writer_postgres.py b/src/writers/writer_postgres.py index 06df7c1..5eae61d 100644 --- a/src/writers/writer_postgres.py +++ b/src/writers/writer_postgres.py @@ -192,7 +192,7 @@ def write(self, topic_name: str, message: dict[str, Any], message_key: str = "") if topic_name not in POSTGRES_WRITE_TOPICS: msg = f"Unknown topic for Postgres/{topic_name}" logger.debug(msg) - raise WriteError(msg) + return # no need to pollute the logs and no write should happen for these try: self._execute_with_retry(lambda conn: self._write_topic(conn, topic_name, message), retry=False) From bc324c0556242a0d6539a3777ee7ab86c6f3eef4 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 17:02:56 +0200 Subject: [PATCH 6/8] removing servers - I do not find them useful --- api.yaml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/api.yaml b/api.yaml index 067c4eb..460c56e 100644 --- a/api.yaml +++ b/api.yaml @@ -4,19 +4,6 @@ info: version: 0.0.0 description: This API provides topic management for an event bus. -servers: - - url: https://{id}-vpce-{vpce}.execute-api.{region}.amazonaws.com/DEV - variables: - id: - default: 01234567ab - description: API Gateway ID - vpce: - default: '01234567abcdef012' - description: VPC endpoint - region: - default: 'af-south-1' - description: AWS Region - paths: /api: get: From 93519eb42f55a331a9ab4f1eb62b25cece9bfc63 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 17:31:27 +0200 Subject: [PATCH 7/8] improvement --- api.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api.yaml b/api.yaml index 460c56e..2bcb1b3 100644 --- a/api.yaml +++ b/api.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: - title: EventGate - version: 0.0.0 + title: EventGate API + version: 1.0.0 description: This API provides topic management for an event bus. paths: From 5915141581d37e79142b56c1ba45bd057a8a7d32 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 17:34:04 +0200 Subject: [PATCH 8/8] unit test fix --- tests/unit/writers/test_writer_postgres.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/writers/test_writer_postgres.py b/tests/unit/writers/test_writer_postgres.py index 0777499..c7ae19d 100644 --- a/tests/unit/writers/test_writer_postgres.py +++ b/tests/unit/writers/test_writer_postgres.py @@ -215,7 +215,7 @@ def test_write_skips_when_psycopg2_missing(reset_env, monkeypatch): writer.write("public.cps.za.test", {}) -def test_write_unknown_topic_raises(reset_env, monkeypatch): +def test_write_unknown_topic_skips_silently(reset_env, monkeypatch): store = [] monkeypatch.setattr(pb, "psycopg2", DummyPsycopg(store)) writer = WriterPostgres({}) @@ -224,8 +224,8 @@ def test_write_unknown_topic_raises(reset_env, monkeypatch): "_pg_config", property(lambda self: {"database": "db", "host": "h", "user": "u", "password": "p", "port": 5432}), ) - with pytest.raises(WriteError, match="Unknown topic"): - writer.write("public.cps.za.unknown", {}) + writer.write("public.cps.za.unknown", {}) + assert 0 == len(store) def test_write_success_known_topic(reset_env, monkeypatch):