From 8ba70ab315bc272dd5d59d4b6d93d47a6c6ecaa5 Mon Sep 17 00:00:00 2001 From: Luiz Oliveira Date: Fri, 23 May 2025 10:08:49 -0300 Subject: [PATCH 1/4] replace /proxy route with something more secure Signed-off-by: Luiz Oliveira --- controller.py | 11 ++- server.py | 153 +++++++++++++++++---------------------- tests/test_controller.py | 10 +-- tests/test_server.py | 96 +++++++++++------------- 4 files changed, 120 insertions(+), 150 deletions(-) diff --git a/controller.py b/controller.py index 1cb297d..c4d1926 100644 --- a/controller.py +++ b/controller.py @@ -39,10 +39,10 @@ def service_event(event, memo: kopf.Memo, logger, **kwargs): if event['type'] == 'DELETED': - if f"{namespace}/{application_name}" in memo.apps: - del memo.apps[f"{namespace}/{application_name}"] + if f"{namespace}.{application_name}" in memo.apps: + del memo.apps[f"{namespace}.{application_name}"] else: - memo.apps.update({f"{namespace}/{application_name}": { + memo.apps.update({f"{namespace}.{application_name}": { 'url': urlunparse(application_url), 'name': application_name, 'header': annotations.get(name_header, ""), @@ -54,4 +54,7 @@ def service_event(event, memo: kopf.Memo, logger, **kwargs): with file_lock: with open('static/openapi/urls.json', 'w') as file: json.dump(urls, file, indent=2) - logger.info("URLs written to file.") \ No newline at end of file + logger.info("URLs written to file.") + with open('static/openapi/services.json', 'w') as file: + json.dump(memo.apps, file, indent=2) + logger.info("Services written to file.") \ No newline at end of file diff --git a/server.py b/server.py index e263b13..87c8178 100644 --- a/server.py +++ b/server.py @@ -6,7 +6,6 @@ import os import requests import logging -from urllib.parse import quote, unquote from starlette.responses import RedirectResponse from authlib.integrations.starlette_client import OAuth from starlette.config import Config @@ -64,29 +63,29 @@ def require_login(request: Request): request.session['user'] = "anonymous" return request.session['user'] - -@app.get("/proxy", include_in_schema=False) -async def proxy(url: str, headers: str = None, user=Depends(require_login)): - """ - Proxy endpoint to fetch the OpenAPI document from a given URL (JSON or YAML). - """ - try: - if headers: - resp = requests.get(url, headers=json.loads(unquote(headers)), timeout=int(os.environ.get("PROXY_TIMEOUT", 10))) - else: - resp = requests.get(url, timeout=int(os.environ.get("PROXY_TIMEOUT", 10))) - content_type = resp.headers.get("content-type", "") - # Se for JSON, repasse como application/json - if "json" in content_type: - return Response(content=resp.content, media_type="application/json") - # Se for YAML, repasse como text/yaml - elif "yaml" in content_type or "yml" in content_type: - return Response(content=resp.content, media_type="text/yaml") - else: - raise HTTPException(status_code=400, detail="Unsupported content type") - except requests.RequestException as e: - logger.error(f"Error fetching OpenAPI document: {e}") - raise HTTPException(status_code=500, detail={"error": "Failed to fetch OpenAPI document", "details": str(e)}) +@app.get("/services/{name}", response_class=HTMLResponse, include_in_schema=False) +async def services(request: Request, name: str, user=Depends(require_login)): + with open('static/openapi/services.json', 'r') as f: + services = json.load(f) + logger.info(f"Loaded {len(services)} services.") + if name not in services: + logger.error(f"Service {name} not found.") + raise HTTPException(status_code=404, detail="Service not found") + service = services[name] + if not service: + logger.error(f"Service {name} not found.") + raise HTTPException(status_code=404, detail="Service not found") + resp = requests.get(service['url'], timeout=int(os.environ.get("PROXY_TIMEOUT", 10)), headers=parse_headers(service.get("header"))) + content_type = resp.headers.get("content-type", "") + # Se for JSON, repasse como application/json + if "json" in content_type: + return Response(content=resp.content, media_type="application/json") + # Se for YAML, repasse como text/yaml + elif "yaml" in content_type or "yml" in content_type: + return Response(content=resp.content, media_type="text/yaml") + else: + logger.error(f"Unsupported content type: {content_type}") + raise HTTPException(status_code=400, detail="Unsupported content type") def parse_headers(header_string: str) -> dict: headers = {} @@ -100,72 +99,50 @@ def parse_headers(header_string: str) -> dict: return headers -def apply_proxy_to_openapi(openapi_url: str, header: str = None) -> str: - """ - Apply the proxy to the OpenAPI URL. - """ - if openapi_url.startswith("http"): - new_url = f"/proxy?url={openapi_url}" - if header: - header = quote(json.dumps(header)) - new_url += f"&headers={header}" - return new_url - return openapi_url - - @app.get("/", response_class=HTMLResponse) -async def docs(request: Request, template:str=None, user=Depends(require_login)): - """ - Main documentation page. - """ +async def index(request: Request, template:str='swagger-ui', user=Depends(require_login)): + if template.lower() not in ["redoc", "swagger-ui"]: + raise HTTPException(status_code=400, detail="Invalid template. Use 'redoc' or 'swagger-ui'.") + try: - with open('static/openapi/urls.json', 'r') as f: - swaggers = json.load(f) - logger.info(f"Loaded {len(swaggers)} URLs.") - except FileNotFoundError: - logger.error("File not found: static/openapi/urls.json") - request.session['error'] = "File not found: static/openapi/urls.json" - swaggers = [ - { - "url": "/openapi.json", - "name": "Swagger Aggregator", - "header": "", - } - ] - except json.JSONDecodeError: - logger.error("Error decoding JSON from static/openapi/urls.json") - request.session['error'] = "Error decoding JSON from static/openapi/urls.json" - swaggers = [ - { - "url": "/openapi.json", - "name": "Swagger Aggregator", - "header": "", - } - ] - - for swagger in swaggers: - swagger["url"] = apply_proxy_to_openapi(swagger.get("url"), parse_headers(swagger.get("header"))) - - if template and template.lower() in ["redoc", "swagger-ui"]: - return templates.TemplateResponse( - f"{template.lower()}.html", - { - "request": request, - "urls": swaggers, - "title": os.environ.get("TITLE", "API Documentation"), - } - ) - interface = os.environ.get("INTERFACE", "swagger-ui").lower() - if interface not in ["swagger-ui", "redoc"]: - interface = "swagger-ui" - return templates.TemplateResponse( - f"{interface}.html", - { - "request": request, - "urls": swaggers, - "title": os.environ.get("TITLE", "API Documentation"), - } - ) + with open('static/openapi/services.json', 'r') as f: + services = json.load(f) + logger.info(f"Loaded {len(services)} services.") + except Exception as e: + logger.error(f"Error loading services file: {e}") + raise HTTPException(status_code=500, detail="Error loading services file.") + + urls = [] + for service_name, service in services.items(): + urls.append({ + "url": f"/services/{service_name}", + "name": service['name'], + "header": service.get("header", ""), + }) + + match template.lower(): + case "redoc": + return templates.TemplateResponse( + "redoc.html", + { + "request": request, + "urls": urls, + "title": os.environ.get("TITLE", "API Documentation"), + } + ) + case "swagger-ui": + return templates.TemplateResponse( + "swagger-ui.html", + { + "request": request, + "urls": urls, + "title": os.environ.get("TITLE", "API Documentation"), + } + ) + case _: + logger.error(f"Invalid template: {template}") + raise HTTPException(status_code=400, detail="Invalid template. Use 'redoc' or 'swagger-ui'.") + @app.get("/config", response_class=HTMLResponse, include_in_schema=False) async def config(request: Request, user=Depends(require_login)): diff --git a/tests/test_controller.py b/tests/test_controller.py index 6eb971e..94ef476 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -32,7 +32,7 @@ def test_path_without_slash(fake_memo): logger = MagicMock() with patch("builtins.open"), patch("json.dump"): service_event(event, fake_memo, logger) - key = "default/my-app" + key = "default.my-app" assert key in fake_memo.apps # The final path should contain the original path assert fake_memo.apps[key]['url'].endswith("/openapi.json") @@ -43,7 +43,7 @@ def test_path_with_slash(fake_memo): logger = MagicMock() with patch("builtins.open"), patch("json.dump"): service_event(event, fake_memo, logger) - key = "default/my-app" + key = "default.my-app" assert key in fake_memo.apps assert fake_memo.apps[key]['url'].endswith("/openapi.json") @@ -53,7 +53,7 @@ def test_path_with_host(fake_memo): logger = MagicMock() with patch("builtins.open"), patch("json.dump"): service_event(event, fake_memo, logger) - key = "default/my-app" + key = "default.my-app" assert key in fake_memo.apps # The host should be preserved assert fake_memo.apps[key]['url'].startswith("http://myhost") @@ -72,7 +72,7 @@ def test_missing_annotation(fake_memo): def test_service_event_deleted(fake_memo): # Pre-add an app to memo - fake_memo.apps["default/my-app"] = { + fake_memo.apps["default.my-app"] = { "url": "http://dummy", "name": "my-app", "header": "X-API-KEY" @@ -96,4 +96,4 @@ def test_service_event_deleted(fake_memo): with patch("builtins.open"), patch("json.dump"): service_event(event, fake_memo, logger) # Should remove the app from memo - assert "default/my-app" not in fake_memo.apps \ No newline at end of file + assert "default.my-app" not in fake_memo.apps \ No newline at end of file diff --git a/tests/test_server.py b/tests/test_server.py index 585c846..e51a178 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -4,44 +4,34 @@ from server import app, apply_proxy_to_openapi from urllib.parse import unquote +import pytest + client = TestClient(app) -def test_proxy_invalid_url(): - # Tests error when passing an invalid URL - response = client.get("/proxy", params={"url": "http://invalid-url"}) - assert response.status_code == 500 - assert "Failed to fetch OpenAPI document" in response.text +@pytest.fixture(autouse=True) +def set_proxy_timeout(monkeypatch): + monkeypatch.setenv("PROXY_TIMEOUT", "1") -def test_docs_returns_html(): - # Tests if the main route returns HTML - response = client.get("/") - assert response.status_code == 200 - assert "text/html" in response.headers["content-type"] - -def test_proxy_with_headers(): - url = "http://example.com/openapi.json" - headers_dict = {"Authorization": "Bearer token123"} - headers_encoded = json.dumps(headers_dict) # The endpoint expects headers already in JSON +def test_services_invalid_name(): + # Testa erro ao passar um nome inválido + response = client.get("/services/inexistente") + assert response.status_code == 404 + assert "Service not found" in response.text +def test_services_json_response(): + # Testa se retorna JSON corretamente mock_response = MagicMock() mock_response.content = b'{"openapi": "3.0.0"}' - mock_response.text = '{"openapi": "3.0.0"}' mock_response.headers = {"content-type": "application/json"} - with patch("server.requests.get", return_value=mock_response) as mock_get: - response = client.get("/proxy", params={"url": url, "headers": headers_encoded}) + with patch("server.requests.get", return_value=mock_response): + response = client.get("/services/default.nginx") assert response.status_code == 200 + assert response.headers["content-type"].startswith("application/json") assert response.json() == {"openapi": "3.0.0"} - mock_get.assert_called_once() - # Checks if the headers were passed correctly - called_headers = mock_get.call_args[1]["headers"] - assert called_headers == headers_dict - -def test_proxy_yaml_response(): - url = "http://example.com/openapi.yaml" - headers_dict = {"Authorization": "Bearer token123"} - headers_json = json.dumps(headers_dict) +def test_services_yaml_response(): + # Testa se retorna YAML corretamente yaml_content = """ openapi: 3.0.0 info: @@ -51,37 +41,49 @@ def test_proxy_yaml_response(): """ mock_response = MagicMock() mock_response.content = yaml_content.encode("utf-8") - mock_response.text = yaml_content mock_response.headers = {"content-type": "application/yaml"} - with patch("server.requests.get", return_value=mock_response) as mock_get: - response = client.get("/proxy", params={"url": url, "headers": headers_json}) + with patch("server.requests.get", return_value=mock_response): + response = client.get("/services/default.timeout") assert response.status_code == 200 assert response.headers["content-type"].startswith("text/yaml") assert "openapi: 3.0.0" in response.text - mock_get.assert_called_once() - called_headers = mock_get.call_args[1]["headers"] - assert called_headers == headers_dict + +def test_services_unsupported_content_type(): + # Testa erro para content-type não suportado + mock_response = MagicMock() + mock_response.content = b"not supported" + mock_response.headers = {"content-type": "text/plain"} + + with patch("server.requests.get", return_value=mock_response): + response = client.get("/services/default.nginx") + assert response.status_code == 400 + assert "Unsupported content type" in response.text + +def test_docs_returns_html(): + # Testa se a rota principal retorna HTML + response = client.get("/") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] def test_docs_file_not_found(monkeypatch): - # Simulates FileNotFoundError when opening the file + # Simula FileNotFoundError ao abrir o arquivo with patch("builtins.open", side_effect=FileNotFoundError): response = client.get("/") - assert response.status_code == 200 - # Should contain the default aggregator name - assert "Swagger Aggregator" in response.text + assert response.status_code == 500 + assert "Error loading services file" in response.text def test_docs_json_decode_error(monkeypatch): - # Simulates invalid JSON error when opening the file + # Simula erro de JSON inválido ao abrir o arquivo m = mock_open(read_data="not a json") with patch("builtins.open", m): with patch("json.load", side_effect=json.JSONDecodeError("msg", "doc", 0)): response = client.get("/") - assert response.status_code == 200 - assert "Swagger Aggregator" in response.text + assert response.status_code == 500 + assert "Error loading services file" in response.text def test_config_json_decode_error(): - # Simulates invalid JSON error when opening the file + # Simula erro de JSON inválido ao abrir o arquivo de config m = mock_open(read_data="not a json") with patch("builtins.open", m): with patch("json.load", side_effect=Exception("invalid json")): @@ -95,17 +97,6 @@ def test_apply_proxy_to_openapi_with_http(): assert result.startswith("/proxy?url=http://example.com/openapi.json") assert "headers=" in result headers_param = result.split("headers=")[1] - decoded = json.loads(unquote(headers_param)) # Fixed here! - assert decoded == header - -def test_apply_proxy_to_openapi_with_http_and_headers(): - url = "http://example.com/openapi.json" - header = {"Authorization": "Bearer token"} - result = apply_proxy_to_openapi(url, header) - assert result.startswith("/proxy?url=http://example.com/openapi.json") - assert "headers=" in result - # Decodes and checks the header - headers_param = result.split("headers=")[1] decoded = json.loads(unquote(headers_param)) assert decoded == header @@ -122,7 +113,6 @@ def test_apply_proxy_to_openapi_with_non_http_url(): def test_apply_proxy_to_openapi_with_empty_header(): url = "http://example.com/openapi.json" result = apply_proxy_to_openapi(url, {}) - # Should not add headers if the dict is empty assert result == f"/proxy?url={url}" def test_apply_proxy_to_openapi_with_special_characters_in_header(): From 4857865710a9386b0d3956829e3c6a7adc28a8bd Mon Sep 17 00:00:00 2001 From: Luiz Oliveira Date: Fri, 23 May 2025 10:18:39 -0300 Subject: [PATCH 2/4] fix tests Signed-off-by: Luiz Oliveira --- tests/test_server.py | 54 ++++++++------------------------------------ 1 file changed, 10 insertions(+), 44 deletions(-) diff --git a/tests/test_server.py b/tests/test_server.py index e51a178..286060e 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,8 +1,7 @@ import json from unittest.mock import patch, mock_open, MagicMock from fastapi.testclient import TestClient -from server import app, apply_proxy_to_openapi -from urllib.parse import unquote +from server import app import pytest @@ -13,13 +12,13 @@ def set_proxy_timeout(monkeypatch): monkeypatch.setenv("PROXY_TIMEOUT", "1") def test_services_invalid_name(): - # Testa erro ao passar um nome inválido - response = client.get("/services/inexistente") + # Tests error when passing an invalid service name + response = client.get("/services/i-dont-exist") assert response.status_code == 404 assert "Service not found" in response.text def test_services_json_response(): - # Testa se retorna JSON corretamente + # Tests if JSON is returned correctly mock_response = MagicMock() mock_response.content = b'{"openapi": "3.0.0"}' mock_response.headers = {"content-type": "application/json"} @@ -31,7 +30,7 @@ def test_services_json_response(): assert response.json() == {"openapi": "3.0.0"} def test_services_yaml_response(): - # Testa se retorna YAML corretamente + # Tests if YAML is returned correctly yaml_content = """ openapi: 3.0.0 info: @@ -50,7 +49,7 @@ def test_services_yaml_response(): assert "openapi: 3.0.0" in response.text def test_services_unsupported_content_type(): - # Testa erro para content-type não suportado + # Tests error for unsupported content-type mock_response = MagicMock() mock_response.content = b"not supported" mock_response.headers = {"content-type": "text/plain"} @@ -61,20 +60,20 @@ def test_services_unsupported_content_type(): assert "Unsupported content type" in response.text def test_docs_returns_html(): - # Testa se a rota principal retorna HTML + # Tests if the main route returns HTML response = client.get("/") assert response.status_code == 200 assert "text/html" in response.headers["content-type"] def test_docs_file_not_found(monkeypatch): - # Simula FileNotFoundError ao abrir o arquivo + # Simulates FileNotFoundError when opening the file with patch("builtins.open", side_effect=FileNotFoundError): response = client.get("/") assert response.status_code == 500 assert "Error loading services file" in response.text def test_docs_json_decode_error(monkeypatch): - # Simula erro de JSON inválido ao abrir o arquivo + # Simulates invalid JSON error when opening the file m = mock_open(read_data="not a json") with patch("builtins.open", m): with patch("json.load", side_effect=json.JSONDecodeError("msg", "doc", 0)): @@ -83,42 +82,9 @@ def test_docs_json_decode_error(monkeypatch): assert "Error loading services file" in response.text def test_config_json_decode_error(): - # Simula erro de JSON inválido ao abrir o arquivo de config + # Simulates invalid JSON error when opening the config file m = mock_open(read_data="not a json") with patch("builtins.open", m): with patch("json.load", side_effect=Exception("invalid json")): response = client.get("/config") assert response.status_code == 200 - -def test_apply_proxy_to_openapi_with_http(): - url = "http://example.com/openapi.json" - header = {"Authorization": "Bearer token"} - result = apply_proxy_to_openapi(url, header) - assert result.startswith("/proxy?url=http://example.com/openapi.json") - assert "headers=" in result - headers_param = result.split("headers=")[1] - decoded = json.loads(unquote(headers_param)) - assert decoded == header - -def test_apply_proxy_to_openapi_with_http_no_headers(): - url = "http://example.com/openapi.json" - result = apply_proxy_to_openapi(url) - assert result == f"/proxy?url={url}" - -def test_apply_proxy_to_openapi_with_non_http_url(): - url = "/local/openapi.json" - result = apply_proxy_to_openapi(url) - assert result == url - -def test_apply_proxy_to_openapi_with_empty_header(): - url = "http://example.com/openapi.json" - result = apply_proxy_to_openapi(url, {}) - assert result == f"/proxy?url={url}" - -def test_apply_proxy_to_openapi_with_special_characters_in_header(): - url = "http://example.com/openapi.json" - header = {"X-Test": "çãõ@#%&"} - result = apply_proxy_to_openapi(url, header) - headers_param = result.split("headers=")[1] - decoded = json.loads(unquote(headers_param)) - assert decoded == header From 8ad80a0e0a0930a8a43b6df5d98aed64d10bc82f Mon Sep 17 00:00:00 2001 From: Luiz Oliveira Date: Fri, 23 May 2025 10:34:19 -0300 Subject: [PATCH 3/4] improving tests Signed-off-by: Luiz Oliveira --- tests/test_server.py | 119 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 98 insertions(+), 21 deletions(-) diff --git a/tests/test_server.py b/tests/test_server.py index 286060e..62b4d04 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,7 +1,7 @@ import json from unittest.mock import patch, mock_open, MagicMock from fastapi.testclient import TestClient -from server import app +from server import app, parse_headers import pytest @@ -13,9 +13,18 @@ def set_proxy_timeout(monkeypatch): def test_services_invalid_name(): # Tests error when passing an invalid service name - response = client.get("/services/i-dont-exist") - assert response.status_code == 404 - assert "Service not found" in response.text + services_dict = { + "default.nginx": { + "url": "http://nginx", + "name": "nginx", + "header": "" + } + } + m = mock_open(read_data=json.dumps(services_dict)) + with patch("builtins.open", m): + response = client.get("/services/i-dont-exist") + assert response.status_code == 404 + assert "Service not found" in response.text def test_services_json_response(): # Tests if JSON is returned correctly @@ -23,11 +32,20 @@ def test_services_json_response(): mock_response.content = b'{"openapi": "3.0.0"}' mock_response.headers = {"content-type": "application/json"} - with patch("server.requests.get", return_value=mock_response): - response = client.get("/services/default.nginx") - assert response.status_code == 200 - assert response.headers["content-type"].startswith("application/json") - assert response.json() == {"openapi": "3.0.0"} + services_dict = { + "default.nginx": { + "url": "http://nginx", + "name": "nginx", + "header": "" + } + } + m = mock_open(read_data=json.dumps(services_dict)) + with patch("builtins.open", m): + with patch("server.requests.get", return_value=mock_response): + response = client.get("/services/default.nginx") + assert response.status_code == 200 + assert response.headers["content-type"].startswith("application/json") + assert response.json() == {"openapi": "3.0.0"} def test_services_yaml_response(): # Tests if YAML is returned correctly @@ -42,11 +60,20 @@ def test_services_yaml_response(): mock_response.content = yaml_content.encode("utf-8") mock_response.headers = {"content-type": "application/yaml"} - with patch("server.requests.get", return_value=mock_response): - response = client.get("/services/default.timeout") - assert response.status_code == 200 - assert response.headers["content-type"].startswith("text/yaml") - assert "openapi: 3.0.0" in response.text + services_dict = { + "default.timeout": { + "url": "http://timeout", + "name": "timeout", + "header": "" + } + } + m = mock_open(read_data=json.dumps(services_dict)) + with patch("builtins.open", m): + with patch("server.requests.get", return_value=mock_response): + response = client.get("/services/default.timeout") + assert response.status_code == 200 + assert response.headers["content-type"].startswith("text/yaml") + assert "openapi: 3.0.0" in response.text def test_services_unsupported_content_type(): # Tests error for unsupported content-type @@ -54,16 +81,34 @@ def test_services_unsupported_content_type(): mock_response.content = b"not supported" mock_response.headers = {"content-type": "text/plain"} - with patch("server.requests.get", return_value=mock_response): - response = client.get("/services/default.nginx") - assert response.status_code == 400 - assert "Unsupported content type" in response.text + services_dict = { + "default.nginx": { + "url": "http://nginx", + "name": "nginx", + "header": "" + } + } + m = mock_open(read_data=json.dumps(services_dict)) + with patch("builtins.open", m): + with patch("server.requests.get", return_value=mock_response): + response = client.get("/services/default.nginx") + assert response.status_code == 400 + assert "Unsupported content type" in response.text def test_docs_returns_html(): # Tests if the main route returns HTML - response = client.get("/") - assert response.status_code == 200 - assert "text/html" in response.headers["content-type"] + services_dict = { + "default.nginx": { + "url": "http://nginx", + "name": "nginx", + "header": "" + } + } + m = mock_open(read_data=json.dumps(services_dict)) + with patch("builtins.open", m): + response = client.get("/") + assert response.status_code == 200 + assert "text/html" in response.headers["content-type"] def test_docs_file_not_found(monkeypatch): # Simulates FileNotFoundError when opening the file @@ -88,3 +133,35 @@ def test_config_json_decode_error(): with patch("json.load", side_effect=Exception("invalid json")): response = client.get("/config") assert response.status_code == 200 + +def test_parse_headers_empty(): + # Tests parse_headers with empty input + assert parse_headers("") == {} + assert parse_headers(None) == {} + +def test_parse_headers_valid(): + # Tests parse_headers with valid headers string + header_string = "Authorization: Bearer token\nX-Test: value" + expected = {"Authorization": "Bearer token", "X-Test": "value"} + assert parse_headers(header_string) == expected + +def test_parse_headers_invalid_lines(): + # Tests parse_headers with lines without colon + header_string = "InvalidLine\nKey: Value" + expected = {"Key": "Value"} + assert parse_headers(header_string) == expected + +def test_index_invalid_template(): + # Tests if the main route returns 400 for invalid template + services_dict = { + "default.nginx": { + "url": "http://nginx", + "name": "nginx", + "header": "" + } + } + m = mock_open(read_data=json.dumps(services_dict)) + with patch("builtins.open", m): + response = client.get("/?template=invalid") + assert response.status_code == 400 + assert "Invalid template" in response.text From cd59252b52a3cb00d023a9a48f0e07097313812a Mon Sep 17 00:00:00 2001 From: Luiz Oliveira Date: Fri, 23 May 2025 11:02:25 -0300 Subject: [PATCH 4/4] complete remove use of urls.json file Signed-off-by: Luiz Oliveira --- server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 87c8178..512c33c 100644 --- a/server.py +++ b/server.py @@ -150,8 +150,8 @@ async def config(request: Request, user=Depends(require_login)): Configuration page for the OpenAPI URLs. """ try: - with open('static/openapi/urls.json') as f: - swaggers = json.load(f) + with open('static/openapi/services.json') as f: + swaggers = json.load(f).values() except Exception as e: logger.error(f"Error loading configuration file: {e}") swaggers = []