You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
orquestrador/tests/test_admin_panel_tools_web.py

294 lines
13 KiB
Python

import unittest
from datetime import datetime, timezone
from fastapi.testclient import TestClient
from admin_app.api.dependencies import (
get_current_panel_staff_principal,
get_tool_management_service,
)
from admin_app.app_factory import create_app
from admin_app.core import AdminSettings, AuthenticatedStaffPrincipal
from admin_app.db.models import ToolDraft, ToolVersion
from admin_app.services import ToolManagementService
from shared.contracts import StaffRole, ToolLifecycleStatus
class _FakeToolDraftRepository:
def __init__(self):
self.drafts: list[ToolDraft] = []
self.next_id = 1
def list_drafts(self, *, statuses=None) -> list[ToolDraft]:
drafts = sorted(
self.drafts,
key=lambda draft: draft.updated_at or draft.created_at or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
)
if statuses:
allowed = set(statuses)
drafts = [draft for draft in drafts if draft.status in allowed]
return drafts
def get_by_tool_name(self, tool_name: str) -> ToolDraft | None:
normalized = str(tool_name or "").strip().lower()
for draft in self.drafts:
if draft.tool_name == normalized:
return draft
return None
def create(self, **kwargs) -> ToolDraft:
now = datetime(2026, 3, 31, 17, 0, tzinfo=timezone.utc)
draft = ToolDraft(
id=self.next_id,
draft_id=f"draft_panel_{self.next_id}",
created_at=now,
updated_at=now,
status=ToolLifecycleStatus.DRAFT,
**kwargs,
)
self.next_id += 1
self.drafts.append(draft)
return draft
def update_submission(self, draft: ToolDraft, **kwargs) -> ToolDraft:
draft.display_name = kwargs["display_name"]
draft.domain = kwargs["domain"]
draft.description = kwargs["description"]
draft.business_goal = kwargs["business_goal"]
draft.summary = kwargs["summary"]
draft.parameters_json = kwargs["parameters_json"]
draft.required_parameter_count = kwargs["required_parameter_count"]
draft.current_version_number = kwargs["current_version_number"]
draft.version_count = kwargs["version_count"]
draft.requires_director_approval = kwargs["requires_director_approval"]
draft.owner_staff_account_id = kwargs["owner_staff_account_id"]
draft.owner_display_name = kwargs["owner_display_name"]
draft.updated_at = datetime(2026, 3, 31, 17, draft.current_version_number, tzinfo=timezone.utc)
return draft
class _FakeToolVersionRepository:
def __init__(self):
self.versions: list[ToolVersion] = []
self.next_id = 1
def list_versions(self, *, tool_name=None, draft_id=None, statuses=None) -> list[ToolVersion]:
versions = sorted(
self.versions,
key=lambda version: (version.version_number, version.updated_at or version.created_at or datetime.min.replace(tzinfo=timezone.utc)),
reverse=True,
)
if tool_name:
normalized = str(tool_name).strip().lower()
versions = [version for version in versions if version.tool_name == normalized]
if draft_id is not None:
versions = [version for version in versions if version.draft_id == draft_id]
if statuses:
allowed = set(statuses)
versions = [version for version in versions if version.status in allowed]
return versions
def get_next_version_number(self, tool_name: str) -> int:
versions = self.list_versions(tool_name=tool_name)
return (versions[0].version_number if versions else 0) + 1
def create(self, **kwargs) -> ToolVersion:
version_number = kwargs["version_number"]
now = datetime(2026, 3, 31, 18, version_number, tzinfo=timezone.utc)
version = ToolVersion(
id=self.next_id,
version_id=self.build_version_id(kwargs["tool_name"], version_number),
created_at=now,
updated_at=now,
**kwargs,
)
self.next_id += 1
self.versions.append(version)
return version
@staticmethod
def build_version_id(tool_name: str, version_number: int) -> str:
normalized = str(tool_name or "").strip().lower()
return f"tool_version::{normalized}::v{int(version_number)}"
class AdminPanelToolsWebTests(unittest.TestCase):
def _build_client_with_role(
self,
role: StaffRole,
settings: AdminSettings | None = None,
) -> tuple[TestClient, object, _FakeToolDraftRepository, _FakeToolVersionRepository]:
app = create_app(
settings
or AdminSettings(
admin_auth_token_secret="test-secret",
admin_api_prefix="/admin",
)
)
draft_repository = _FakeToolDraftRepository()
version_repository = _FakeToolVersionRepository()
service = ToolManagementService(
settings=app.state.admin_settings,
draft_repository=draft_repository,
version_repository=version_repository,
)
app.dependency_overrides[get_current_panel_staff_principal] = lambda: AuthenticatedStaffPrincipal(
id=21,
email="colaborador@empresa.com" if role == StaffRole.COLABORADOR else "diretor@empresa.com",
display_name="Equipe Web",
role=role,
is_active=True,
)
app.dependency_overrides[get_tool_management_service] = lambda: service
return TestClient(app), app, draft_repository, version_repository
def test_panel_tools_overview_is_available_for_colaborador_session(self):
client, app, _, _ = self._build_client_with_role(StaffRole.COLABORADOR)
try:
response = client.get("/admin/panel/tools/overview")
finally:
app.dependency_overrides.clear()
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload["mode"], "admin_tool_draft_governance")
self.assertIn("persisted_versions", [item["key"] for item in payload["metrics"]])
self.assertIn("/admin/panel/tools/contracts", [item["href"] for item in payload["actions"]])
self.assertIn("/admin/panel/tools/drafts/intake", [item["href"] for item in payload["actions"]])
def test_panel_tool_intake_persists_draft_with_version_metadata_for_colaborador(self):
client, app, draft_repository, version_repository = self._build_client_with_role(StaffRole.COLABORADOR)
try:
response = client.post(
"/admin/panel/tools/drafts/intake",
json={
"domain": "vendas",
"tool_name": "consultar_vendas_periodo",
"display_name": "Consultar vendas por periodo",
"description": "Consulta vendas consolidadas por periodo informado no painel.",
"business_goal": "Ajudar o time interno a acompanhar o desempenho comercial com mais agilidade.",
"parameters": [
{
"name": "periodo_inicio",
"parameter_type": "string",
"description": "Data inicial usada no filtro.",
"required": True,
},
{
"name": "periodo_fim",
"parameter_type": "string",
"description": "Data final usada no filtro.",
"required": True,
},
],
},
)
finally:
app.dependency_overrides.clear()
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload["storage_status"], "admin_database")
self.assertEqual(payload["draft_preview"]["status"], "draft")
self.assertEqual(payload["draft_preview"]["tool_name"], "consultar_vendas_periodo")
self.assertEqual(payload["draft_preview"]["version_id"], "tool_version::consultar_vendas_periodo::v1")
self.assertEqual(payload["draft_preview"]["version_number"], 1)
self.assertEqual(payload["draft_preview"]["version_count"], 1)
self.assertTrue(payload["draft_preview"]["requires_director_approval"])
self.assertEqual(len(payload["draft_preview"]["parameters"]), 2)
self.assertEqual(len(draft_repository.drafts), 1)
self.assertEqual(len(version_repository.versions), 1)
def test_panel_drafts_list_returns_single_root_item_after_new_version(self):
client, app, draft_repository, version_repository = self._build_client_with_role(StaffRole.COLABORADOR)
try:
client.post(
"/admin/panel/tools/drafts/intake",
json={
"domain": "revisao",
"tool_name": "consultar_agenda_revisao",
"display_name": "Consultar agenda de revisao",
"description": "Consulta disponibilidade e contexto da agenda de revisao para o time interno.",
"business_goal": "Ajudar a equipe a responder mais rapido sobre slots e horarios disponiveis.",
"parameters": [],
},
)
client.post(
"/admin/panel/tools/drafts/intake",
json={
"domain": "revisao",
"tool_name": "consultar_agenda_revisao",
"display_name": "Consultar agenda de revisao",
"description": "Consulta disponibilidade, contexto e observacoes da agenda de revisao para o time interno.",
"business_goal": "Ajudar a equipe a responder mais rapido sobre slots, horarios e observacoes relevantes.",
"parameters": [],
},
)
response = client.get("/admin/panel/tools/drafts")
finally:
app.dependency_overrides.clear()
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload["storage_status"], "admin_database")
self.assertEqual(len(payload["drafts"]), 1)
self.assertEqual(payload["drafts"][0]["tool_name"], "consultar_agenda_revisao")
self.assertEqual(payload["drafts"][0]["current_version_number"], 2)
self.assertEqual(payload["drafts"][0]["version_count"], 2)
self.assertEqual(len(draft_repository.drafts), 1)
self.assertEqual(len(version_repository.versions), 2)
def test_panel_tools_review_queue_requires_director_session(self):
client, app, _, _ = self._build_client_with_role(StaffRole.COLABORADOR)
try:
response = client.get("/admin/panel/tools/review-queue")
finally:
app.dependency_overrides.clear()
self.assertEqual(response.status_code, 403)
self.assertEqual(
response.json()["detail"],
"Permissao administrativa insuficiente: 'review_tool_generations'.",
)
def test_panel_tools_review_queue_is_available_for_diretor_session(self):
client, app, _, _ = self._build_client_with_role(StaffRole.DIRETOR)
try:
response = client.get("/admin/panel/tools/review-queue")
finally:
app.dependency_overrides.clear()
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json()["queue_mode"], "bootstrap_empty_state")
def test_panel_tools_publications_require_director_publication_permission(self):
client, app, _, _ = self._build_client_with_role(StaffRole.COLABORADOR)
try:
response = client.get("/admin/panel/tools/publications")
finally:
app.dependency_overrides.clear()
self.assertEqual(response.status_code, 403)
self.assertEqual(
response.json()["detail"],
"Permissao administrativa insuficiente: 'publish_tools'.",
)
def test_panel_tools_publications_return_catalog_for_diretor_session(self):
client, app, _, _ = self._build_client_with_role(StaffRole.DIRETOR)
try:
response = client.get("/admin/panel/tools/publications")
finally:
app.dependency_overrides.clear()
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload["target_service"], "product")
self.assertGreaterEqual(len(payload["publications"]), 10)
self.assertIn("consultar_estoque", [item["tool_name"] for item in payload["publications"]])
if __name__ == "__main__":
unittest.main()