Codurile ROA contin legitim '/' si '"' (ex. RCR1/4"). Rutele FastAPI
foloseau parametri de cale (/api/mappings/{sku}/{codmat}...); ASGI decodeaza
%2F -> '/' INAINTE de routing, deci o cheie cu slash devine doua segmente si
Starlette nu mai potriveste ruta -> 404 la edit/delete/restore. Cand ambii
parametri au slash, un convertor {path} nu rezolva (split ambiguu).
Solutie: sku/codmat trec din cale in query string. %2F/%22 din query se
decodeaza ca parte a valorii, fara sa atinga routing-ul de cale.
- backend: 4 rute statice + Query(...) (update/edit/delete/restore);
semnaturile mapping_service raman neschimbate
- frontend: helper mapQS(); toate cele 8 apeluri path -> query; cache-bust ?v=19
- tests: regresie ci-layer (chei-sentinel cu slash, !=404, toate 4 rutele);
test_integration update/delete convertite la query; e2e delete RCR1/4"
(slash+ghilimea) prin modalul UI, skip cand Oracle lipseste
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
3.3 KiB
Python
79 lines
3.3 KiB
Python
"""E2E regression: delete a mapping whose SKU+CODMAT contain '/' and '"'.
|
|
|
|
Covers the full real path: render → onclick="...jsAttrEsc(codmat)..." →
|
|
context menu "Sterge" → deleteMappingConfirm → modal confirm → fetch
|
|
/api/mappings/delete?sku=&codmat=. Exercises BOTH the slash (routing, query
|
|
param fix) and the quote (attribute escaping, commit a530ebf) end-to-end.
|
|
|
|
Needs a real Oracle-backed app (seed inserts into ARTICOLE_TERTI with a real
|
|
CODMAT from NOM_ARTICOLE). When run against the dummy-Oracle subprocess in the
|
|
plain e2e layer, seeding fails and the test skips — it only asserts in the
|
|
./test.sh full (or live :5003) layer.
|
|
"""
|
|
import pytest
|
|
from playwright.sync_api import Page, expect
|
|
|
|
pytestmark = pytest.mark.e2e
|
|
|
|
SLASH_SKU = 'RCR1/4"'
|
|
|
|
|
|
def _real_codmat(page: Page, app_url: str):
|
|
"""Fetch a real CODMAT from the Oracle nomenclator, or None if Oracle is down."""
|
|
for term in ["01", "PH", "CA"]:
|
|
resp = page.request.get(f"{app_url}/api/articles/search?q={term}")
|
|
if resp.ok:
|
|
results = resp.json().get("results", [])
|
|
if results:
|
|
return results[0]["codmat"]
|
|
return None
|
|
|
|
|
|
def test_delete_mapping_with_slash_and_quote(page: Page, app_url: str):
|
|
codmat = _real_codmat(page, app_url)
|
|
if not codmat:
|
|
pytest.skip("Oracle unavailable (no real CODMAT) — slash-delete e2e needs the full layer")
|
|
|
|
# Seed: create the slash+quote mapping. Restore if a prior run soft-deleted it.
|
|
resp = page.request.post(f"{app_url}/api/mappings",
|
|
data={"sku": SLASH_SKU, "codmat": codmat, "cantitate_roa": 1})
|
|
body = resp.json() if resp.status in (200, 409) else {}
|
|
if not body.get("success"):
|
|
if body.get("can_restore"):
|
|
r = page.request.post(
|
|
f"{app_url}/api/mappings/restore",
|
|
params={"sku": SLASH_SKU, "codmat": codmat})
|
|
assert r.ok and r.json().get("success"), f"restore seed failed: {r.status} {r.text()}"
|
|
elif resp.status == 503:
|
|
pytest.skip("Oracle unavailable (503 on create) — slash-delete e2e needs the full layer")
|
|
else:
|
|
pytest.fail(f"could not seed mapping: {resp.status} {resp.text()}")
|
|
|
|
# Collect any 404 on the mappings API during the UI flow.
|
|
not_found = []
|
|
page.on("response", lambda r: not_found.append(r.url)
|
|
if r.status == 404 and "/api/mappings/" in r.url else None)
|
|
|
|
page.goto(f"{app_url}/mappings")
|
|
page.wait_for_load_state("networkidle")
|
|
|
|
# Search for the slash+quote SKU.
|
|
page.fill("#searchInput", SLASH_SKU)
|
|
page.wait_for_timeout(600) # debounce + reload
|
|
|
|
# The row's context-menu trigger carries the codmat as a data attribute.
|
|
trigger = page.locator(f'.context-menu-trigger[data-sku="{SLASH_SKU}"]').first
|
|
expect(trigger).to_be_visible()
|
|
trigger.click()
|
|
|
|
# Context menu → "Sterge" → confirm modal.
|
|
page.locator(".context-menu-item", has_text="Sterge").click()
|
|
confirm = page.locator("#confirmDeleteBtn")
|
|
expect(confirm).to_be_visible()
|
|
confirm.click()
|
|
page.wait_for_timeout(600) # delete fetch + reload
|
|
|
|
# Row is gone and no 404 was hit on the mappings API.
|
|
expect(page.locator(f'.context-menu-trigger[data-sku="{SLASH_SKU}"]')).to_have_count(0)
|
|
assert not not_found, f"mappings API returned 404 during delete flow: {not_found}"
|