from __future__ import annotations import os import re import time from typing import Any import pytest from .helpers import ReleaseApiClient def _select_rule_set(client: ReleaseApiClient) -> dict[str, Any]: preferred_rule_type = os.getenv("LEAUDIT_TEST_RULE_TYPE", "contract.entrust").strip() rule_sets = ReleaseApiClient.json_data(client.get("/api/rule-sets")) assert isinstance(rule_sets, list) assert rule_sets, "当前环境没有规则集,无法测试版本管理" for item in rule_sets: if str(item.get("ruleType") or "") == preferred_rule_type and item.get("currentVersionId"): return item for item in rule_sets: if item.get("currentVersionId"): return item pytest.skip("当前环境没有带 currentVersionId 的规则集,无法测试发布/回滚闭环") def _set_metadata_version(yaml_text: str, version_no: str) -> str: lines = yaml_text.splitlines() metadata_index: int | None = None version_pattern = re.compile(r"^(\s*)version\s*:") for index, line in enumerate(lines): if line.strip() == "metadata:": metadata_index = index continue if metadata_index is None: continue if line and not line.startswith((" ", "\t")): break match = version_pattern.match(line) if match: lines[index] = f"{match.group(1)}version: '{version_no}'" return "\n".join(lines) + ("\n" if yaml_text.endswith("\n") else "") if metadata_index is None: return f"metadata:\n version: '{version_no}'\n{yaml_text}" lines.insert(metadata_index + 1, f" version: '{version_no}'") return "\n".join(lines) + ("\n" if yaml_text.endswith("\n") else "") def _rule_set_by_type(client: ReleaseApiClient, rule_type: str) -> dict[str, Any]: rule_sets = ReleaseApiClient.json_data(client.get("/api/rule-sets")) for item in rule_sets: if str(item.get("ruleType") or "") == rule_type: return item raise AssertionError(f"规则集不存在: {rule_type}") @pytest.mark.release def test_g6_rule_detail_version_management_save_publish_and_rollback(admin_client: ReleaseApiClient) -> None: """覆盖规则配置详情页的版本管理闭环。 该用例会创建一个真实历史版本并短暂发布,最后回滚到执行前的 currentVersionId。 """ target_rule_set = _select_rule_set(admin_client) rule_type = str(target_rule_set["ruleType"]) original_rule_set_id = int(target_rule_set["id"]) original_current_version_id = int(target_rule_set["currentVersionId"]) original_tenant_code = str(target_rule_set.get("effectiveTenantCode") or "") versions_before = ReleaseApiClient.json_data(admin_client.get(f"/api/rule-sets/{rule_type}/versions")) assert isinstance(versions_before, list) assert {int(item["ruleSetId"]) for item in versions_before} == {original_rule_set_id} assert original_current_version_id in {int(item["id"]) for item in versions_before} content = ReleaseApiClient.json_data(admin_client.get(f"/api/rule-sets/versions/{original_current_version_id}/content")) assert int(content["ruleSetId"]) == original_rule_set_id assert str(content["ruleType"]) == rule_type assert str(content["yamlText"]).strip() new_version_no = f"pytest-vm-{int(time.time())}" yaml_text = _set_metadata_version(str(content["yamlText"]), new_version_no) created_version_id: int | None = None try: created = ReleaseApiClient.json_data( admin_client.post( f"/api/rule-sets/{rule_type}/versions", json={ "yamlText": yaml_text, "changeNote": f"pytest rule detail version management smoke {new_version_no}", }, expected_status=200, ) ) created_version_id = int(created["id"]) assert int(created["ruleSetId"]) == original_rule_set_id assert str(created["versionNo"]) == new_version_no assert str(created["status"]) == "draft" versions_after_create = ReleaseApiClient.json_data(admin_client.get(f"/api/rule-sets/{rule_type}/versions")) assert created_version_id in {int(item["id"]) for item in versions_after_create} assert {int(item["ruleSetId"]) for item in versions_after_create} == {original_rule_set_id} published = ReleaseApiClient.json_data( admin_client.post( f"/api/rule-sets/{rule_type}/publish", json={"versionId": created_version_id}, expected_status=200, ) ) assert int(published["id"]) == created_version_id assert int(published["ruleSetId"]) == original_rule_set_id assert str(published["status"]) == "published" current_after_publish = _rule_set_by_type(admin_client, rule_type) assert int(current_after_publish["id"]) == original_rule_set_id assert int(current_after_publish["currentVersionId"]) == created_version_id assert str(current_after_publish.get("effectiveTenantCode") or "") == original_tenant_code finally: if created_version_id: admin_client.post( f"/api/rule-sets/{rule_type}/rollback", json={"versionId": original_current_version_id}, expected_status=200, ) restored = _rule_set_by_type(admin_client, rule_type) assert int(restored["id"]) == original_rule_set_id assert int(restored["currentVersionId"]) == original_current_version_id assert str(restored.get("effectiveTenantCode") or "") == original_tenant_code @pytest.mark.release def test_g6_rule_detail_version_management_rejects_cross_rule_set_publish(admin_client: ReleaseApiClient) -> None: """发布接口必须拒绝把其他规则集的版本发布到当前规则类型。""" rule_sets = ReleaseApiClient.json_data(admin_client.get("/api/rule-sets")) candidates = [item for item in rule_sets if item.get("currentVersionId")] if len(candidates) < 2: pytest.skip("当前环境少于两个带 currentVersionId 的规则集,无法测试跨规则集发布拦截") left = candidates[0] right = next((item for item in candidates[1:] if str(item["ruleType"]) != str(left["ruleType"])), None) if right is None: pytest.skip("当前环境没有可用于跨规则类型发布拦截的第二个规则集") response = admin_client.post( f"/api/rule-sets/{left['ruleType']}/publish", json={"versionId": int(right["currentVersionId"])}, expected_status=403, ) assert "当前租户不能发布或回滚其他租户的规则版本" in response.text