| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- from __future__ import annotations
- import uuid
- import pytest
- class FakeSemanticService:
- def __init__(self):
- self.calls = []
- self.asset = {
- "uid": str(uuid.uuid4()),
- "asset_kind": "business_term",
- "code": "CUSTOMER_LEVEL",
- "name": "客户等级",
- "owner_uid": str(uuid.uuid4()),
- "business_domain_uid": str(uuid.uuid4()),
- "status": "draft",
- "current_version": 1,
- "definition": {"definition": "客户运营分层等级", "aliases": []},
- }
- def list(self, **filters):
- self.calls.append(("list", filters))
- return [self.asset]
- def create_draft(self, kind, payload, *, actor_uid):
- self.calls.append(("create", kind, payload, actor_uid))
- return self.asset
- def submit(self, uid, *, expected_version, actor_uid):
- self.calls.append(("submit", uid, expected_version, actor_uid))
- return {**self.asset, "status": "in_review"}
- def review(self, uid, **payload):
- self.calls.append(("review", uid, payload))
- return {**self.asset, "status": "approved"}
- def publish(self, uid, *, expected_version, actor_uid):
- self.calls.append(("publish", uid, expected_version, actor_uid))
- return {**self.asset, "status": "published"}
- def rollback(self, uid, **payload):
- self.calls.append(("rollback", uid, payload))
- return {
- **self.asset,
- "status": "published",
- "current_version": 2,
- }
- def list_field_mappings(self, **filters):
- self.calls.append(("mappings", filters))
- return []
- def map_physical_field(self, payload, *, actor_uid):
- self.calls.append(("map", payload, actor_uid))
- return {"uid": str(uuid.uuid4()), **payload, "status": "published"}
- @pytest.fixture()
- def client(monkeypatch):
- from flask import request
- from app import create_app
- from app.api.data_development import semantic_routes
- from app.core.system import permissions
- service = FakeSemanticService()
- def identity():
- role = request.headers.get("Authorization", "").removeprefix("Bearer ")
- if role not in {"viewer", "editor", "admin"}:
- return None
- return {"id": f"00000000-0000-7000-8000-0000000000{role[0]}", "roles": [role]}
- monkeypatch.setattr(permissions, "authenticate_request", identity)
- monkeypatch.setattr(
- semantic_routes,
- "get_semantic_governance_service",
- lambda: service,
- )
- app = create_app()
- app.config.update(TESTING=True)
- return app.test_client(), service
- def test_viewer_lists_but_cannot_create(client):
- http, service = client
- listed = http.get(
- "/api/development/v1/semantic-assets?asset_kind=business_term",
- headers={"Authorization": "Bearer viewer"},
- )
- denied = http.post(
- "/api/development/v1/semantic-assets",
- headers={"Authorization": "Bearer viewer"},
- json={"asset_kind": "business_term"},
- )
- assert listed.status_code == 200
- assert listed.get_json()["data"][0]["code"] == "CUSTOMER_LEVEL"
- assert service.calls[0] == (
- "list",
- {"asset_kind": "business_term", "status": None},
- )
- assert denied.status_code == 403
- def test_editor_creates_submits_and_maps_but_cannot_approve(client):
- http, service = client
- headers = {"Authorization": "Bearer editor"}
- created = http.post(
- "/api/development/v1/semantic-assets",
- headers=headers,
- json={"asset_kind": "business_term", "code": "CUSTOMER_LEVEL"},
- )
- submitted = http.post(
- f"/api/development/v1/semantic-assets/{service.asset['uid']}/submit",
- headers=headers,
- json={"expected_version": 1},
- )
- mapped = http.post(
- "/api/development/v1/semantic-field-mappings",
- headers=headers,
- json={
- "asset_uid": str(uuid.uuid4()),
- "field_name": "customer_level",
- "data_element_uid": str(uuid.uuid4()),
- "owner_uid": str(uuid.uuid4()),
- },
- )
- denied = http.post(
- f"/api/development/v1/semantic-assets/{service.asset['uid']}/review",
- headers=headers,
- json={
- "expected_version": 1,
- "decision": "approve",
- "reason": "approved",
- },
- )
- assert created.status_code == 201
- assert submitted.get_json()["data"]["status"] == "in_review"
- assert mapped.status_code == 201
- assert denied.status_code == 403
- def test_admin_approves_publishes_and_rolls_back(client):
- http, service = client
- headers = {"Authorization": "Bearer admin"}
- reviewed = http.post(
- f"/api/development/v1/semantic-assets/{service.asset['uid']}/review",
- headers=headers,
- json={
- "expected_version": 1,
- "decision": "approve",
- "reason": "企业术语审核通过",
- },
- )
- published = http.post(
- f"/api/development/v1/semantic-assets/{service.asset['uid']}/publish",
- headers=headers,
- json={"expected_version": 1},
- )
- rolled_back = http.post(
- f"/api/development/v1/semantic-assets/{service.asset['uid']}/rollback",
- headers=headers,
- json={
- "expected_version": 1,
- "target_version": 1,
- "reason": "恢复验收版本",
- },
- )
- assert reviewed.get_json()["data"]["status"] == "approved"
- assert published.get_json()["data"]["status"] == "published"
- assert rolled_back.get_json()["data"]["current_version"] == 2
|