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 01/12] 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 02/12] 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 03/12] 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 04/12] 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 05/12] 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 06/12] 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 405597ce350e4f3e0940ae63709366b0e52d9d60 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 17:27:55 +0200 Subject: [PATCH 07/12] #194: improving auth user match checking --- src/handlers/handler_topic.py | 22 ++++++++++++++++++++-- tests/unit/handlers/test_handler_topic.py | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/handlers/handler_topic.py b/src/handlers/handler_topic.py index 34b0107..5d639ca 100644 --- a/src/handlers/handler_topic.py +++ b/src/handlers/handler_topic.py @@ -168,10 +168,11 @@ def _post_topic_message(self, topic_name: str, topic_message: dict[str, Any], to return build_error_response(404, "topic", f"Topic '{topic_name}' not found") user = token.get("sub") - if topic_name not in self.access_config or user not in self.access_config[topic_name]: + authorized_user = self._resolve_authorized_user(topic_name, user) + if authorized_user is None: return build_error_response(403, "auth", "User not authorized for topic") - allowed, perm_error = self._validate_user_permissions(topic_name, user, topic_message) + allowed, perm_error = self._validate_user_permissions(topic_name, authorized_user, topic_message) if not allowed: return build_error_response(403, "permission", perm_error or "Permission denied") @@ -201,6 +202,23 @@ def _post_topic_message(self, topic_name: str, topic_message: dict[str, Any], to "body": json.dumps({"success": True, "statusCode": 202}), } + def _resolve_authorized_user(self, topic_name: str, user: str | None) -> str | None: + """Match a token user to a configured user for a topic, ignoring case. + Args: + topic_name: Target topic name. + user: User identifier from the token `sub` claim. + Returns: + The configured username (original casing) when authorized, otherwise `None`. + """ + if user is None or topic_name not in self.access_config: + return None + + for configured_user in self.access_config[topic_name]: + if configured_user.casefold() == user.casefold(): + return configured_user + + return None + def _validate_user_permissions( self, topic_name: str, diff --git a/tests/unit/handlers/test_handler_topic.py b/tests/unit/handlers/test_handler_topic.py index a8123a8..f82035f 100644 --- a/tests/unit/handlers/test_handler_topic.py +++ b/tests/unit/handlers/test_handler_topic.py @@ -257,6 +257,24 @@ def test_post_success_all_writers(event_gate_module, make_event, valid_payload): assert 202 == body["statusCode"] +def test_post_authorized_user_case_insensitive(event_gate_module, make_event, valid_payload): + with patch.object(event_gate_module.handler_token, "decode_jwt", return_value={"sub": "testuser"}): + for writer in event_gate_module.handler_topic.writers.values(): + writer.write = MagicMock(return_value=None) + + event = make_event( + "/topics/{topic_name}", + method="POST", + topic="public.cps.za.test", + body=valid_payload, + headers={"Authorization": "Bearer token"}, + ) + resp = event_gate_module.lambda_handler(event) + assert 202 == resp["statusCode"] + body = json.loads(resp["body"]) + assert body["success"] + + def test_post_passes_topic_key_to_writers(event_gate_module, make_event, valid_payload): """Configured topic key field is extracted and passed to writer.write.""" with patch.object(event_gate_module.handler_token, "decode_jwt", return_value={"sub": "TestUser"}): From 50b7589cf33bb111d9f3cea24abb9d1fbd16cc93 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 17:29:50 +0200 Subject: [PATCH 08/12] adding fully mature pythonic gitignore (source: CAL repo) --- .gitignore | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 162 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 4e0419b..53dba4e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,164 @@ -.ipynb_checkpoints +# =========================== +# OS files +# =========================== +.DS_Store +Thumbs.db +ehthumbs.db +Desktop.ini + +# =========================== +# IDE and editors +# =========================== +.vscode/ +.idea/ +*.swp +*.swo +*~ +.spyderproject +.spyproject +.ropeproject + +# =========================== +# Environment and secrets +# =========================== +.env +.env.local +.env.*.local + +# =========================== +# Python – byte-compiled / optimized +# =========================== +__pycache__/ +*.py[cod] +*$py.class +*.so +cython_debug/ + +# =========================== +# Python – packaging and distribution +# =========================== +.Python +build/ +dist/ +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# =========================== +# Python – virtual environments +# =========================== .venv -__pycache__ -/.idea/ -/dependencies -/lambda_function.zip +venv/ +env/ +ENV/ +env.bak/ +venv.bak/ +__pypackages__/ + +# =========================== +# Python – tools and type checkers +# =========================== +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ +.pytype/ +.pdm.toml +.pdm-python +.pdm-build/ +.ruff_cache/ + +# =========================== +# Python – tests and coverage +# =========================== +.coverage +.coverage.* +htmlcov/ +.tox/ +.nox/ +.cache +.pytest_cache/ +.hypothesis/ +nosetests.xml +coverage.xml +*.cover +*.py,cover +cover/ + +# =========================== +# Python – installer logs +# =========================== +pip-log.txt +pip-delete-this-directory.txt +*.manifest +*.spec + +# =========================== +# Python – misc frameworks +# =========================== +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +instance/ +.webassets-cache +.scrapy +celerybeat-schedule +celerybeat.pid +*.sage.py +*.mo +*.pot + +# =========================== +# Documentation +# =========================== +docs/_build/ +/site +.ipynb_checkpoints +profile_default/ +ipython_config.py + +# =========================== +# Terraform and Terragrunt +# =========================== +.terraform/ +*.tfstate +*.tfstate.* +crash.log +crash.*.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraformrc +terraform.rc +.terraform.lock.hcl +.terragrunt-cache/ + +# =========================== +# Corporate CA certificates +# Actual cert files must never be committed -- provide via CI/CD secrets. +# The trusted_certs/ directory is kept (README only) to document the convention. +# =========================== +trusted_certs/*.pem +trusted_certs/*.crt -*.sarif +# =========================== +# Temporary files and logs +# =========================== +tmp/ +temp/ +*.tmp +logs/ From 93519eb42f55a331a9ab4f1eb62b25cece9bfc63 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 17:31:27 +0200 Subject: [PATCH 09/12] 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 10/12] 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): From c4117b1ec273f83eecd923e9fa83a7d24d7788d7 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Fri, 10 Jul 2026 17:47:33 +0200 Subject: [PATCH 11/12] improving logging of area impacted by the PR --- src/handlers/handler_topic.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/handlers/handler_topic.py b/src/handlers/handler_topic.py index 5d639ca..7c1bc44 100644 --- a/src/handlers/handler_topic.py +++ b/src/handlers/handler_topic.py @@ -170,11 +170,16 @@ def _post_topic_message(self, topic_name: str, topic_message: dict[str, Any], to user = token.get("sub") authorized_user = self._resolve_authorized_user(topic_name, user) if authorized_user is None: - return build_error_response(403, "auth", "User not authorized for topic") + return build_error_response(403, "auth", f"User '{user}' is not authorized for topic '{topic_name}'") allowed, perm_error = self._validate_user_permissions(topic_name, authorized_user, topic_message) if not allowed: - return build_error_response(403, "permission", perm_error or "Permission denied") + return build_error_response( + 403, "permission", + perm_error or f"Permission denied for user '{authorized_user}' for POST to topic '{topic_name}'" + ) + + logger.debug("Authorized user '%s' for topic '%s'.", authorized_user, topic_name) try: validate(instance=topic_message, schema=self.topics[topic_name]) @@ -190,12 +195,19 @@ def _post_topic_message(self, topic_name: str, topic_message: dict[str, Any], to errors.append({"type": writer_name, "message": str(exc)}) if errors: + logger.error( + "POST to topic '%s' failed: %d of %d writer(s) reported errors.", + topic_name, + len(errors), + len(self.writers), + ) return { "statusCode": 500, "headers": {"Content-Type": "application/json"}, "body": json.dumps({"success": False, "statusCode": 500, "errors": errors}), } + logger.info("Message accepted for topic '%s' from user '%s'.", topic_name, authorized_user) return { "statusCode": 202, "headers": {"Content-Type": "application/json"}, From 29d8b3262cbde7d14cb44da8f57fd2368edda807 Mon Sep 17 00:00:00 2001 From: Ladislav Sulak Date: Tue, 14 Jul 2026 16:50:01 +0200 Subject: [PATCH 12/12] fixing formatting --- src/handlers/handler_topic.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/handlers/handler_topic.py b/src/handlers/handler_topic.py index 7c1bc44..09b6661 100644 --- a/src/handlers/handler_topic.py +++ b/src/handlers/handler_topic.py @@ -175,8 +175,9 @@ def _post_topic_message(self, topic_name: str, topic_message: dict[str, Any], to allowed, perm_error = self._validate_user_permissions(topic_name, authorized_user, topic_message) if not allowed: return build_error_response( - 403, "permission", - perm_error or f"Permission denied for user '{authorized_user}' for POST to topic '{topic_name}'" + 403, + "permission", + perm_error or f"Permission denied for user '{authorized_user}' for POST to topic '{topic_name}'", ) logger.debug("Authorized user '%s' for topic '%s'.", authorized_user, topic_name)