From 6bfe7f83f20be8aa94531268efadd4140a496c8e Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Sun, 22 Mar 2026 16:14:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85Mock=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E8=A7=84=E5=88=99=E5=91=BD=E4=B8=AD=E8=AE=A1?= =?UTF-8?q?=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lsfx-mock-server/services/file_service.py | 41 +++++++++++++++++- lsfx-mock-server/tests/test_file_service.py | 47 ++++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/lsfx-mock-server/services/file_service.py b/lsfx-mock-server/services/file_service.py index 50e3cfdd..f5be2c59 100644 --- a/lsfx-mock-server/services/file_service.py +++ b/lsfx-mock-server/services/file_service.py @@ -48,6 +48,8 @@ PHASE2_BASELINE_RULE_CODES = [ "SUPPLIER_CONCENTRATION", ] +RULE_CONFLICT_GROUPS = [] + @dataclass class FileRecord: @@ -179,8 +181,8 @@ class FileService: selected_codes = set(rng.sample(rule_codes, rng.randint(min_count, max_count))) return [rule_code for rule_code in rule_codes if rule_code in selected_codes] - def _build_rule_hit_plan(self, log_id: int) -> dict: - """基于 logId 生成稳定的规则命中计划。""" + def _build_subset_rule_hit_plan(self, log_id: int) -> dict: + """基于 logId 生成稳定的规则子集命中计划。""" rng = random.Random(f"rule-plan:{log_id}") return { "large_transaction_hit_rules": self._pick_rule_subset( @@ -195,6 +197,41 @@ class FileService: ), } + def _build_all_compatible_rule_hit_plan(self) -> dict: + """生成全部兼容规则命中计划。""" + return { + "large_transaction_hit_rules": list(LARGE_TRANSACTION_RULE_CODES), + "phase1_hit_rules": list(PHASE1_RULE_CODES), + "phase2_statement_hit_rules": list(PHASE2_STATEMENT_RULE_CODES), + "phase2_baseline_hit_rules": list(PHASE2_BASELINE_RULE_CODES), + } + + def _apply_conflict_groups(self, rule_plan: dict) -> dict: + """按显式互斥组裁剪规则计划,同组仅保留固定优先级的首个规则。""" + resolved_plan = {plan_key: list(rule_codes) for plan_key, rule_codes in rule_plan.items()} + for plan_key, rule_codes in resolved_plan.items(): + filtered_codes = list(rule_codes) + for conflict_group in RULE_CONFLICT_GROUPS: + kept_rule_code = next( + (rule_code for rule_code in conflict_group if rule_code in filtered_codes), + None, + ) + if kept_rule_code is None: + continue + filtered_codes = [ + rule_code + for rule_code in filtered_codes + if rule_code == kept_rule_code or rule_code not in conflict_group + ] + resolved_plan[plan_key] = filtered_codes + return resolved_plan + + def _build_rule_hit_plan(self, log_id: int) -> dict: + """按配置模式生成规则命中计划。""" + if settings.RULE_HIT_MODE == "all": + return self._apply_conflict_groups(self._build_all_compatible_rule_hit_plan()) + return self._build_subset_rule_hit_plan(log_id) + def _create_file_record( self, *, diff --git a/lsfx-mock-server/tests/test_file_service.py b/lsfx-mock-server/tests/test_file_service.py index 8a1c1ba4..1d87ea9c 100644 --- a/lsfx-mock-server/tests/test_file_service.py +++ b/lsfx-mock-server/tests/test_file_service.py @@ -8,7 +8,14 @@ import io from fastapi import BackgroundTasks from fastapi.datastructures import UploadFile -from services.file_service import FileRecord, FileService +from services.file_service import ( + LARGE_TRANSACTION_RULE_CODES, + PHASE1_RULE_CODES, + PHASE2_BASELINE_RULE_CODES, + PHASE2_STATEMENT_RULE_CODES, + FileRecord, + FileService, +) class FakeStaffIdentityRepository: @@ -196,6 +203,44 @@ def test_phase2_rule_hit_plan_should_be_deterministic_for_same_log_id(): assert 2 <= len(plan1["phase2_baseline_hit_rules"]) <= 4 +def test_build_rule_hit_plan_should_return_all_compatible_rules_in_all_mode(monkeypatch): + monkeypatch.setattr("services.file_service.settings.RULE_HIT_MODE", "all") + service = FileService(staff_identity_repository=FakeStaffIdentityRepository()) + + plan = service._build_rule_hit_plan(10001) + + assert plan["large_transaction_hit_rules"] == LARGE_TRANSACTION_RULE_CODES + assert plan["phase1_hit_rules"] == PHASE1_RULE_CODES + assert plan["phase2_statement_hit_rules"] == PHASE2_STATEMENT_RULE_CODES + assert plan["phase2_baseline_hit_rules"] == PHASE2_BASELINE_RULE_CODES + + +def test_build_rule_hit_plan_should_keep_subset_mode_as_default(): + service = FileService(staff_identity_repository=FakeStaffIdentityRepository()) + + plan1 = service._build_rule_hit_plan(10001) + plan2 = service._build_rule_hit_plan(10001) + + assert plan1 == plan2 + assert 2 <= len(plan1["large_transaction_hit_rules"]) <= 4 + + +def test_build_rule_hit_plan_should_drop_conflicting_rules_from_all_mode(monkeypatch): + monkeypatch.setattr("services.file_service.settings.RULE_HIT_MODE", "all") + monkeypatch.setattr( + "services.file_service.RULE_CONFLICT_GROUPS", + [["SALARY_QUICK_TRANSFER", "SALARY_UNUSED"]], + ) + service = FileService(staff_identity_repository=FakeStaffIdentityRepository()) + + plan = service._build_rule_hit_plan(10001) + + assert not ( + "SALARY_QUICK_TRANSFER" in plan["phase2_statement_hit_rules"] + and "SALARY_UNUSED" in plan["phase2_statement_hit_rules"] + ) + + def test_fetch_inner_flow_should_persist_rule_hit_plan(monkeypatch): service = FileService(staff_identity_repository=FakeStaffIdentityRepository()) monkeypatch.setattr(