Files
ss-tools/backend/tests/api/conftest.py
busya a32ca0631b feat(037): capture, verification lifecycle, inheritance + close 036 stabilization
- Authoritative candidate capture with server-issued artifacts and raw-byte
  immutability hashing (source_response_hash server-owned)
- Closed-period lifecycle: request-hash bound approvals, persisted closure
  immutability violations, byte-for-byte catalog stability on reclosure
- Verification runs: persisted VerificationRun model + FK migration,
  publish gate (block_publish), scheduled observability runs (02:00 UTC)
- FR-013 baseline inheritance: prior_release_id migration, plan_inheritance/
  execute_inheritance classification and re-extraction, API endpoints
- Visual executor bound to release-deployment environment; caller mismatch
  rejected; visual SSIM/reconciliation modules
- Query execution decomposed: envelope/model/executor split, no direct SQL
- AgentRun approvals extracted to submodule; evidence adapter; _utils
- Dashboard testing service decomposed into 30+ modules (all <400 LOC)
- Five Feature-037 agent tools with permission guards (tools_037.py)
- API readiness endpoint; Alembic env/migrations; test fixture repos
- Specs 036/037 contracts, openapi.yaml, schema.json, tasks/traceability
  updated; semantic index rebuilt with 0 parse warnings
- Fix ADR-0003 parser ambiguity: remove [DEF🆔ADR] prose example
- Add axiom-mcp-agent-feedback.md: agent findings for MCP rework plan
- Tests: 298 service + 1464 API + 45 agent passing; ruff clean
2026-07-31 11:28:50 +03:00

191 lines
7.1 KiB
Python

# #region Test.Api.DashboardTesting.Fixtures [C:3] [TYPE Module] [SEMANTICS testing,api,dashboard-testing,fixtures,auth]
# @defgroup Shared deterministic fixtures and builders for dashboard-testing API tests.
# @LAYER Test
# @RELATION BINDS_TO -> [Api.DashboardTesting]
from __future__ import annotations
from itertools import count
import pytest
from uuid import uuid4
from fastapi.testclient import TestClient
from src.app import app
from src.dependencies import get_current_user
from src.models.agent_run import AgentRun
from src.models.auth import Role, User
from src.models.git import GitRepository, GitServerConfig
_VERIFICATION_REPOSITORY_DASHBOARD_IDS = count(9001)
# #region Test.Api.DashboardTesting.Fixtures.MakeAdminUser [C:1] [TYPE Function] [SEMANTICS test,api,dashboard-testing,auth]
def make_dashboard_testing_admin_user() -> User:
admin_role = Role(id="admin-role-test-001", name="Admin", is_admin=True)
user = User(id="test-user-lifecycle", username="tester", email="tester@test.com")
user.roles = [admin_role]
return user
# #endregion Test.Api.DashboardTesting.Fixtures.MakeAdminUser
# #region Test.Api.DashboardTesting.Fixtures.CreateCaptureArtifact [C:2] [TYPE Function] [SEMANTICS test,api,dashboard-testing,capture,artifact]
# @BRIEF Create a server-issued capture execution DraftArtifact in the database with deterministic hash.
# @POST Returns (artifact_id, sha256) tuple. The artifact is persisted to session.
# @INVARIANT The artifact has kind="capture_execution" and valid sha256 matching mock raw bytes.
def create_dashboard_testing_capture_artifact(session, run_id: str, result_key: str = "count") -> tuple[str, str]:
"""Create a capture execution DraftArtifact for lifecycle test fixtures.
Returns (artifact_id, sha256) with deterministic hash computed from mock raw bytes.
"""
import hashlib
from src.models.agent_run import DraftArtifact
mock_raw = f'{{"result": "{result_key}", "value": 100}}'.encode()
sha256 = hashlib.sha256(mock_raw).hexdigest()
artifact = DraftArtifact(
run_id=run_id,
kind="capture_execution",
name=f"capture:{result_key}:dev",
intended_path="",
content_ref=f"draft:{run_id}:{sha256}",
sha256=sha256,
validation_status="valid",
capture_meta={
"kind": "capture_execution",
"environment_id": "dev",
"dashboard_id": 42,
"chart_id": 1,
"dataset_id": None,
"result_key": result_key,
"source_response_hash": sha256,
"repo_key": "test-repo",
"dash_key": "test-dash",
"normalized_value": {
"kind": "integer",
"canonical_value": "100",
},
},
)
session.add(artifact)
session.flush()
return artifact.id, sha256
# #endregion Test.Api.DashboardTesting.Fixtures.CreateCaptureArtifact
# #region Test.Api.DashboardTesting.Fixtures.MakeCandidatePayload [C:2] [TYPE Function] [SEMANTICS test,api,dashboard-testing,candidate]
# @BRIEF Build a CandidateRequest dict for testing, optionally with a server-issued capture artifact ref.
# @INVARIANT For kind=metric, caller MUST provide capture_artifact_ref to satisfy server-side validation.
# source_response_hash must match the capture artifact's sha256.
def make_dashboard_testing_candidate_payload(
agent_run_id: str,
capture_artifact_ref: str | None = None,
source_response_hash: str | None = None,
) -> dict:
payload: dict = {
"environment_id": "dev",
"dashboard_id": 42,
"repository_key": "test-repo",
"dashboard_key": "test-dash",
"chart_id": 1,
"result_key": "count",
"label": "test-candidate",
"normalized_filters": {
"filters": [],
"filters_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
"candidate_value": {
"kind": "integer",
"raw_value": 100,
"canonical_value": "100",
"display_value": "100",
},
"source_response_hash": source_response_hash or ("a" * 64),
"comparison_policy": {
"type": "exact",
},
"provenance": {
"environment": "dev",
"actor": "tester",
"agent_run_id": agent_run_id,
},
"agent_run_id": agent_run_id,
}
if capture_artifact_ref:
payload["capture_artifact_ref"] = capture_artifact_ref
return payload
# #endregion Test.Api.DashboardTesting.Fixtures.MakeCandidatePayload
# #region Test.Api.DashboardTesting.Fixtures.CreateAgentRun [C:1] [TYPE Function] [SEMANTICS test,api,dashboard-testing,agent-run]
def create_dashboard_testing_agent_run(
session, user_id: str = "test-user-lifecycle"
) -> AgentRun:
run = AgentRun(
user_id=user_id,
dashboard_id="42",
environment_id="dev",
context_snapshot={},
status="CREATED",
)
session.add(run)
session.flush()
return run
# #endregion Test.Api.DashboardTesting.Fixtures.CreateAgentRun
# #region Test.Api.DashboardTesting.Fixtures.VerificationRepository [C:2] [TYPE Function] [SEMANTICS test,api,dashboard-testing,verification,repository]
# @BRIEF Create a real minimal GitServerConfig/GitRepository pair in the API database.
@pytest.fixture
def dashboard_testing_verification_repository_id() -> str:
from src.core.database import SessionLocal
session = SessionLocal()
try:
server = GitServerConfig(
id=str(uuid4()),
name="verification-api-server",
provider="GITHUB",
url="https://git.example.test",
pat="verification-api-token",
)
session.add(server)
session.flush()
repository = GitRepository(
id=str(uuid4()),
dashboard_id=next(_VERIFICATION_REPOSITORY_DASHBOARD_IDS),
config_id=server.id,
remote_url="https://git.example.test/org/verification-api.git",
local_path="/tmp/verification-api-repository",
)
session.add(repository)
session.commit()
return repository.id
finally:
session.close()
# #endregion Test.Api.DashboardTesting.Fixtures.VerificationRepository
# #region Test.Api.DashboardTesting.Fixtures.MockUser [C:2] [TYPE Function] [SEMANTICS test,api,dashboard-testing,fixture]
# @BRIEF Provide an admin user whose permission check passes through the admin bypass.
@pytest.fixture
def dashboard_testing_mock_user() -> User:
return make_dashboard_testing_admin_user()
# #endregion Test.Api.DashboardTesting.Fixtures.MockUser
# #region Test.Api.DashboardTesting.Fixtures.Client [C:2] [TYPE Function] [SEMANTICS test,api,dashboard-testing,fixture]
# @BRIEF Provide a TestClient with only the current-user dependency overridden.
@pytest.fixture
def dashboard_testing_client(dashboard_testing_mock_user: User):
app.dependency_overrides[get_current_user] = lambda: dashboard_testing_mock_user
try:
yield TestClient(app)
finally:
app.dependency_overrides.pop(get_current_user, None)
# #endregion Test.Api.DashboardTesting.Fixtures.Client
# #endregion Test.Api.DashboardTesting.Fixtures