fix route parity parser for promise-all frontend calls
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -15,10 +15,8 @@ Route = tuple[str, str]
|
||||
BACKEND_ROUTE_RE = re.compile(
|
||||
r'server_\.on\(\s*"(?P<path>/api/[^"]+)"\s*,\s*HTTP_(?P<method>[A-Z]+)'
|
||||
)
|
||||
FRONTEND_CALL_RE = re.compile(
|
||||
r'requestJson\(\s*["\'](?P<path>/api/[^"\']+)["\'](?P<args>.*?)\);',
|
||||
re.DOTALL,
|
||||
)
|
||||
FRONTEND_CALL_START_RE = re.compile(r"\brequestJson\(")
|
||||
FRONTEND_PATH_ARG_RE = re.compile(r'^\s*(["\'])(?P<path>/api/[^"\']+)\1')
|
||||
METHOD_RE = re.compile(r'method\s*:\s*["\'](?P<method>[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))
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user