From f6a45771fba15e7f4ae5431972f56f93301b16d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:23:17 +0100 Subject: [PATCH] fix route parity parser for promise-all frontend calls --- .github/workflows/ci.yml | 3 ++ scripts/check_web_route_parity.py | 55 +++++++++++++++++++++---- scripts/test_check_web_route_parity.py | 57 ++++++++++++++++++++++++++ scripts/test_terminal.sh | 3 ++ 4 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 scripts/test_check_web_route_parity.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 145e88e..2a2b1d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,9 @@ jobs: with: python-version: "3.11" + - name: Test Web Route Parity Parser + run: python3 -m unittest scripts/test_check_web_route_parity.py + - name: Check Web Route Parity run: python3 scripts/check_web_route_parity.py diff --git a/scripts/check_web_route_parity.py b/scripts/check_web_route_parity.py index 05e0764..628e50f 100755 --- a/scripts/check_web_route_parity.py +++ b/scripts/check_web_route_parity.py @@ -15,10 +15,8 @@ Route = tuple[str, str] BACKEND_ROUTE_RE = re.compile( r'server_\.on\(\s*"(?P/api/[^"]+)"\s*,\s*HTTP_(?P[A-Z]+)' ) -FRONTEND_CALL_RE = re.compile( - r'requestJson\(\s*["\'](?P/api/[^"\']+)["\'](?P.*?)\);', - re.DOTALL, -) +FRONTEND_CALL_START_RE = re.compile(r"\brequestJson\(") +FRONTEND_PATH_ARG_RE = re.compile(r'^\s*(["\'])(?P/api/[^"\']+)\1') METHOD_RE = re.compile(r'method\s*:\s*["\'](?P[A-Z]+)["\']') @@ -31,11 +29,54 @@ def parse_backend_routes(source: str) -> set[Route]: return routes +def find_matching_paren(source: str, open_paren_index: int) -> int: + depth = 1 + in_string: str | None = None + escaped = False + + for index in range(open_paren_index + 1, len(source)): + char = source[index] + + if in_string is not None: + if escaped: + escaped = False + continue + if char == "\\": + escaped = True + continue + if char == in_string: + in_string = None + continue + + if char in ('"', "'", "`"): + in_string = char + continue + + if char == "(": + depth += 1 + continue + if char == ")": + depth -= 1 + if depth == 0: + return index + + return -1 + + def parse_frontend_routes(source: str) -> set[Route]: routes: set[Route] = set() - for match in FRONTEND_CALL_RE.finditer(source): - path = match.group("path") - args = match.group("args") + for match in FRONTEND_CALL_START_RE.finditer(source): + open_paren = match.end() - 1 + close_paren = find_matching_paren(source, open_paren) + if close_paren < 0: + continue + + args = source[open_paren + 1 : close_paren] + path_match = FRONTEND_PATH_ARG_RE.match(args) + if not path_match: + continue + + path = path_match.group("path") method_match = METHOD_RE.search(args) method = method_match.group("method").upper() if method_match else "GET" routes.add((method, path)) diff --git a/scripts/test_check_web_route_parity.py b/scripts/test_check_web_route_parity.py new file mode 100644 index 0000000..baf8e62 --- /dev/null +++ b/scripts/test_check_web_route_parity.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Unit tests for check_web_route_parity parser helpers.""" + +from __future__ import annotations + +import unittest + +from scripts.check_web_route_parity import parse_frontend_routes + + +class ParseFrontendRoutesTest(unittest.TestCase): + def test_detects_direct_calls(self) -> None: + source = """ + async function refresh() { + await requestJson("/api/status"); + await requestJson("/api/control", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "CALL" }), + }); + } + """ + routes = parse_frontend_routes(source) + self.assertIn(("GET", "/api/status"), routes) + self.assertIn(("POST", "/api/control"), routes) + + def test_detects_calls_inside_promise_all(self) -> None: + source = """ + const [wifi, mqtt, espnow, peers] = await Promise.all([ + requestJson("/api/network/wifi"), + requestJson("/api/network/mqtt"), + requestJson("/api/network/espnow"), + requestJson("/api/network/espnow/peer"), + ]); + """ + routes = parse_frontend_routes(source) + self.assertIn(("GET", "/api/network/wifi"), routes) + self.assertIn(("GET", "/api/network/mqtt"), routes) + self.assertIn(("GET", "/api/network/espnow"), routes) + self.assertIn(("GET", "/api/network/espnow/peer"), routes) + + def test_handles_nested_parentheses_in_payload(self) -> None: + source = """ + await requestJson("/api/network/mqtt/publish", { + method: "POST", + body: JSON.stringify({ + topic: "rtc/test", + payload: JSON.stringify({ ping: true }), + }), + }); + """ + routes = parse_frontend_routes(source) + self.assertIn(("POST", "/api/network/mqtt/publish"), routes) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/test_terminal.sh b/scripts/test_terminal.sh index 881a812..72b9072 100755 --- a/scripts/test_terminal.sh +++ b/scripts/test_terminal.sh @@ -12,6 +12,9 @@ mkdir -p .pio/host c++ -std=c++17 -Wall -Wextra -pedantic -Isrc test/host/test_dtmf_host.cpp src/telephony/DtmfDecoder.cpp -o .pio/host/test_dtmf_host .pio/host/test_dtmf_host +echo "[test_terminal] run web route parity parser tests" +python3 -m unittest scripts/test_check_web_route_parity.py + echo "[test_terminal] check web route parity" python3 scripts/check_web_route_parity.py