fix: clarify unavailable legacy blocker resolution
This commit is contained in:
@@ -3,7 +3,7 @@ import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from central.app.blocked_products import upsert_blocker
|
||||
from central.app.blocker_resolution import resolve_blocker_with_context, validate_resolution_input
|
||||
from central.app.blocker_resolution import BlockerResolutionUnavailable, resolve_blocker_with_context, validate_resolution_input
|
||||
from central.app.main import create_app
|
||||
from central.app.models import AdminUser, Agent, AuditEvent, BlockedProduct, FiscalContextEntry, Job, Installation, Tenant
|
||||
from central.tests.test_control_plane import seed, setup_db
|
||||
@@ -117,7 +117,7 @@ def test_legacy_blocker_without_installation_fails_closed_without_job_or_audit()
|
||||
session.commit()
|
||||
admin = session.get(AdminUser, admin_id)
|
||||
|
||||
with pytest.raises(LookupError, match="installation"):
|
||||
with pytest.raises(BlockerResolutionUnavailable, match="Blocker legado"):
|
||||
resolve_blocker_with_context(session, blocker_id, admin, {"scope": "PRODUCT", "scope_key": "P-001", "key": "product_role", "value": "RESALE", "reason": "reviewed"})
|
||||
|
||||
assert session.query(FiscalContextEntry).count() == 0
|
||||
@@ -125,6 +125,29 @@ def test_legacy_blocker_without_installation_fails_closed_without_job_or_audit()
|
||||
assert session.query(AuditEvent).count() == 0
|
||||
|
||||
|
||||
def test_resolution_rejects_company_key_over_database_limit_before_mutation():
|
||||
session_factory = setup_db()
|
||||
blocker_id, admin_id, installation_id = _seed_blocker(session_factory)
|
||||
|
||||
with session_factory() as session:
|
||||
installation = session.get(Installation, installation_id)
|
||||
installation.company_code = "C" * 65
|
||||
session.commit()
|
||||
admin = session.get(AdminUser, admin_id)
|
||||
|
||||
with pytest.raises(ValueError, match="company key.*64"):
|
||||
resolve_blocker_with_context(
|
||||
session,
|
||||
blocker_id,
|
||||
admin,
|
||||
{"scope": "PRODUCT", "scope_key": "P-001", "key": "product_role", "value": "RESALE", "reason": "reviewed"},
|
||||
)
|
||||
|
||||
assert session.query(FiscalContextEntry).count() == 0
|
||||
assert session.query(Job).count() == 0
|
||||
assert session.query(AuditEvent).count() == 0
|
||||
|
||||
|
||||
def test_cross_tenant_installation_pointer_fails_before_mutation():
|
||||
session_factory = setup_db()
|
||||
blocker_id, admin_id, _ = _seed_blocker(session_factory)
|
||||
@@ -242,3 +265,47 @@ def test_human_and_json_resolution_routes_use_the_same_validated_workflow():
|
||||
json={"scope": "PRODUCT", "scope_key": "P-001", "key": "entry_scope", "value": "INTER", "reason": "reviewed"},
|
||||
)
|
||||
assert unauthenticated.status_code == 401
|
||||
|
||||
|
||||
def test_unbound_blocker_resolution_routes_return_conflict_but_missing_stays_not_found():
|
||||
session_factory = setup_db()
|
||||
blocker_id, _, _ = _seed_blocker(session_factory)
|
||||
with session_factory() as session:
|
||||
session.get(BlockedProduct, blocker_id).installation_id = None
|
||||
session.commit()
|
||||
|
||||
client = TestClient(create_app(database_probe=lambda: True, session_factory=session_factory, bootstrap_token=None))
|
||||
client.post("/login", data={"username": "operator", "password": "a-secure-password"}, follow_redirects=False)
|
||||
payload = {"csrf_token": client.cookies.get("gti_csrf"), "scope": "PRODUCT", "scope_key": "P-001", "key": "product_role", "value": "RESALE", "reason": "reviewed"}
|
||||
|
||||
for route in (f"/blockers/{blocker_id}/resolve-context", f"/v1/blockers/{blocker_id}/resolve-human"):
|
||||
response = client.post(route, data=payload) if route.startswith("/blockers/") else client.post(route, json=payload)
|
||||
assert response.status_code == 409
|
||||
assert "sem vínculo comprovado" in response.text.lower()
|
||||
|
||||
missing = client.post("/v1/blockers/999999/resolve-human", json=payload)
|
||||
assert missing.status_code == 404
|
||||
|
||||
|
||||
def test_invalid_resolution_rerenders_submitted_values_and_persisted_context():
|
||||
session_factory = setup_db()
|
||||
blocker_id, _, _ = _seed_blocker(session_factory)
|
||||
with session_factory() as session:
|
||||
blocker = session.get(BlockedProduct, blocker_id)
|
||||
session.add(FiscalContextEntry(tenant_id=blocker.tenant_id, company_key="ACME-COMPANY", scope="PRODUCT", scope_key="P-001", key="entry_scope", value="INTER", source="OPERATOR", reason="already saved"))
|
||||
session.commit()
|
||||
|
||||
client = TestClient(create_app(database_probe=lambda: True, session_factory=session_factory, bootstrap_token=None))
|
||||
client.post("/login", data={"username": "operator", "password": "a-secure-password"}, follow_redirects=False)
|
||||
response = client.post(
|
||||
f"/blockers/{blocker_id}/resolve-context",
|
||||
data={"csrf_token": client.cookies.get("gti_csrf"), "key": "product_role", "value": "NOT_VALID", "scope": "COMPANY", "scope_key": "ACME", "reason": "submitted reason"},
|
||||
)
|
||||
|
||||
assert response.status_code == 422
|
||||
assert 'option value="product_role" selected' in response.text
|
||||
assert 'name="value" required maxlength="512" value="NOT_VALID"' in response.text
|
||||
assert 'option value="COMPANY" selected' in response.text
|
||||
assert 'name="scope_key" value="ACME"' in response.text
|
||||
assert "submitted reason" in response.text
|
||||
assert "entry_scope" in response.text and "INTER" in response.text
|
||||
|
||||
@@ -271,6 +271,28 @@ def test_agent_and_blocker_details_keep_safe_forms_and_human_event_rows():
|
||||
assert not re.search(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}", re.sub(r' title="[^"]*"', "", blocker_response.text))
|
||||
|
||||
|
||||
def test_legacy_blocker_detail_explains_unavailable_resolution_without_form():
|
||||
client, agent_id, _ = _panel_client()
|
||||
with client._session_factory() as session:
|
||||
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
|
||||
blocker = upsert_blocker(
|
||||
session,
|
||||
installation.tenant_id,
|
||||
{"company": "Acme", "product_code": "LEGACY", "blocker_type": "BLOCKED_MISSING_EVIDENCE", "field": "ST", "reason_code": "LEGACY", "input_fingerprint": "unbound-detail"},
|
||||
)
|
||||
session.commit()
|
||||
blocker_id = blocker.id
|
||||
|
||||
response = client.get(f"/blockers/{blocker_id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "blocker legado de empresa" in response.text.lower()
|
||||
assert "sem vínculo comprovado com um servidor" in response.text.lower()
|
||||
assert "não é possível oferecer o reprocessamento de contexto com segurança" in response.text.lower()
|
||||
assert "Salvar contexto e reprocessar" not in response.text
|
||||
assert f'action="/blockers/{blocker_id}/resolve-context"' not in response.text
|
||||
|
||||
|
||||
def test_agent_detail_resolves_agent_after_first_hundred_installations():
|
||||
client, _, _ = _panel_client()
|
||||
factory = client._session_factory
|
||||
|
||||
Reference in New Issue
Block a user