在准备环境前提交次全部更改。

This commit is contained in:
Neo
2026-02-19 08:35:13 +08:00
parent ded6dfb9d8
commit 4eac07da47
1387 changed files with 6107191 additions and 33002 deletions

View File

@@ -0,0 +1,299 @@
# -*- coding: utf-8 -*-
"""任务注册表分组属性测试Property-Based Testing
Property 4: 对于 Task_Registry 中的任务集合,分组结果中每个任务应出现在
且仅出现在其所属业务域的分组中。
Validates: Requirements 2.1
测试策略:
1. 直接测试 get_tasks_grouped_by_domain 函数:
- 每个任务出现在且仅出现在其 domain 对应的分组中
- 分组中的任务总数等于全部任务数(不多不少)
- 每个分组的 key 等于该分组内所有任务的 domain
2. 通过 API 端点测试TestClient + mock auth
- 返回的 groups 中每个任务的 domain 与其所在分组 key 一致
- 所有任务都出现在结果中
3. 随机子集验证:
- 随机选取任务子集,验证分组逻辑的一致性
- 随机选取 domain验证该 domain 下的任务都正确
"""
import os
os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key-for-registry")
from hypothesis import given, settings, assume
from hypothesis import strategies as st
from app.services.task_registry import (
get_all_tasks,
get_tasks_grouped_by_domain,
TaskDefinition,
)
from fastapi.testclient import TestClient
from app.main import app
from app.auth.dependencies import get_current_user, CurrentUser
# ---------------------------------------------------------------------------
# 辅助
# ---------------------------------------------------------------------------
ALL_TASKS = get_all_tasks()
ALL_CODES = [t.code for t in ALL_TASKS]
ALL_DOMAINS = list({t.domain for t in ALL_TASKS})
def _mock_user() -> CurrentUser:
return CurrentUser(user_id=1, site_id=1)
# ---------------------------------------------------------------------------
# Property 4.1: 分组完整性 — 每个任务出现在且仅出现在其 domain 分组中
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------
@settings(max_examples=100)
@given(data=st.data())
def test_every_task_in_exactly_its_domain_group(data):
"""Property 4.1: 每个任务出现在且仅出现在其所属业务域的分组中。
从全量任务中随机选取一个任务,验证它只出现在对应 domain 的分组里,
且不出现在其他任何分组中。
"""
grouped = get_tasks_grouped_by_domain()
# 随机选取一个任务
task = data.draw(st.sampled_from(ALL_TASKS))
# 该任务必须出现在其 domain 分组中
assert task.domain in grouped, (
f"任务 {task.code} 的 domain '{task.domain}' 不在分组 keys 中"
)
domain_codes = [t.code for t in grouped[task.domain]]
assert task.code in domain_codes, (
f"任务 {task.code} 未出现在其 domain '{task.domain}' 的分组中"
)
# 该任务不应出现在其他任何分组中
for other_domain, other_tasks in grouped.items():
if other_domain == task.domain:
continue
other_codes = [t.code for t in other_tasks]
assert task.code not in other_codes, (
f"任务 {task.code}domain={task.domain})错误地出现在 "
f"domain '{other_domain}' 的分组中"
)
# ---------------------------------------------------------------------------
# Property 4.2: 分组总数守恒 — 分组中的任务总数等于全部任务数
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------
@settings(max_examples=100)
@given(data=st.data())
def test_grouped_total_equals_all_tasks(data):
"""Property 4.2: 分组中的任务总数等于全部任务数(不多不少)。
随机选取若干 domain 进行局部验证,同时验证全局总数守恒。
"""
all_tasks = get_all_tasks()
grouped = get_tasks_grouped_by_domain()
# 全局守恒:分组内任务总数 == 全量任务数
grouped_total = sum(len(tasks) for tasks in grouped.values())
assert grouped_total == len(all_tasks), (
f"分组总数 {grouped_total} != 全量任务数 {len(all_tasks)}"
)
# 随机选取一个 domain验证该 domain 下的任务数量正确
domain = data.draw(st.sampled_from(ALL_DOMAINS))
expected_count = sum(1 for t in all_tasks if t.domain == domain)
actual_count = len(grouped[domain])
assert actual_count == expected_count, (
f"domain '{domain}' 分组内任务数 {actual_count} != 预期 {expected_count}"
)
# ---------------------------------------------------------------------------
# Property 4.3: 分组 key 一致性 — 每个分组的 key 等于组内所有任务的 domain
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------
@settings(max_examples=100)
@given(data=st.data())
def test_group_key_matches_task_domains(data):
"""Property 4.3: 每个分组的 key 等于该分组内所有任务的 domain。
随机选取一个 domain 分组,验证组内每个任务的 domain 字段都等于分组 key。
"""
grouped = get_tasks_grouped_by_domain()
domain = data.draw(st.sampled_from(list(grouped.keys())))
for task in grouped[domain]:
assert task.domain == domain, (
f"分组 '{domain}' 中的任务 {task.code} 的 domain 为 "
f"'{task.domain}',与分组 key 不一致"
)
# ---------------------------------------------------------------------------
# Property 4.4: 任务 code 全局唯一 — 分组后不应出现重复 code
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------
@settings(max_examples=100)
@given(data=st.data())
def test_no_duplicate_codes_across_groups(data):
"""Property 4.4: 分组后所有任务的 code 全局唯一,无重复。
随机选取若干 domain 的任务合并,验证 code 不重复。
"""
grouped = get_tasks_grouped_by_domain()
# 收集所有分组中的 code
all_codes_in_groups = []
for tasks in grouped.values():
all_codes_in_groups.extend(t.code for t in tasks)
assert len(all_codes_in_groups) == len(set(all_codes_in_groups)), (
"分组中存在重复的任务 code"
)
# 随机选取两个不同 domain验证它们的任务 code 无交集
if len(ALL_DOMAINS) >= 2:
domains = data.draw(
st.lists(st.sampled_from(ALL_DOMAINS), min_size=2, max_size=2, unique=True)
)
codes_a = {t.code for t in grouped[domains[0]]}
codes_b = {t.code for t in grouped[domains[1]]}
overlap = codes_a & codes_b
assert not overlap, (
f"domain '{domains[0]}''{domains[1]}' 存在重叠任务 code: {overlap}"
)
# ---------------------------------------------------------------------------
# Property 4.5: 随机子集分组一致性 — 子集中的任务分组结果与全量一致
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------
@settings(max_examples=100)
@given(
indices=st.lists(
st.integers(min_value=0, max_value=len(ALL_TASKS) - 1),
min_size=1,
max_size=min(20, len(ALL_TASKS)),
unique=True,
)
)
def test_subset_grouping_consistency(indices):
"""Property 4.5: 随机选取任务子集,验证每个任务在全量分组中的归属正确。
对于随机选取的任务子集,每个任务在 get_tasks_grouped_by_domain() 的结果中
都应出现在其 domain 对应的分组里。
"""
grouped = get_tasks_grouped_by_domain()
subset = [ALL_TASKS[i] for i in indices]
for task in subset:
# 任务的 domain 必须是分组的 key 之一
assert task.domain in grouped
# 任务必须在对应分组中
group_codes = {t.code for t in grouped[task.domain]}
assert task.code in group_codes, (
f"任务 {task.code} 未出现在 domain '{task.domain}' 的分组中"
)
# ---------------------------------------------------------------------------
# Property 4.6: API 端点分组正确性 — GET /api/tasks/registry 返回一致的分组
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------
@settings(max_examples=100)
@given(data=st.data())
def test_api_registry_grouping_correctness(data):
"""Property 4.6: API 端点返回的分组中,每个任务的 domain 与分组 key 一致,
且所有任务都出现在结果中。
"""
app.dependency_overrides[get_current_user] = _mock_user
try:
client = TestClient(app)
resp = client.get("/api/tasks/registry")
assert resp.status_code == 200
body = resp.json()
groups = body["groups"]
# 收集 API 返回的所有任务 code
api_codes: set[str] = set()
for domain_key, task_list in groups.items():
for task_item in task_list:
# 每个任务的 domain 必须等于分组 key
assert task_item["domain"] == domain_key, (
f"API 返回的任务 {task_item['code']}domain={task_item['domain']}"
f"出现在分组 '{domain_key}' 中,不一致"
)
api_codes.add(task_item["code"])
# 所有任务都应出现在 API 结果中
all_codes_set = {t.code for t in get_all_tasks()}
assert api_codes == all_codes_set, (
f"API 返回的任务集合与全量任务不一致。"
f"缺失: {all_codes_set - api_codes}"
f"多余: {api_codes - all_codes_set}"
)
# 随机选取一个 domain验证该 domain 下的任务数量与服务层一致
if groups:
domain = data.draw(st.sampled_from(list(groups.keys())))
expected = get_tasks_grouped_by_domain()
assert len(groups[domain]) == len(expected[domain]), (
f"API 返回的 domain '{domain}' 任务数 {len(groups[domain])} "
f"!= 服务层 {len(expected[domain])}"
)
finally:
app.dependency_overrides.pop(get_current_user, None)
# ---------------------------------------------------------------------------
# Property 4.7: 随机 domain 过滤验证
# Validates: Requirements 2.1
# ---------------------------------------------------------------------------
@settings(max_examples=100)
@given(domain=st.sampled_from(ALL_DOMAINS))
def test_random_domain_tasks_all_correct(domain):
"""Property 4.7: 随机选取一个 domain验证该 domain 下的所有任务都正确归属。
对于选定的 domain
- 分组中的每个任务的 domain 字段都等于选定的 domain
- 全量任务中所有属于该 domain 的任务都出现在分组中
"""
grouped = get_tasks_grouped_by_domain()
all_tasks = get_all_tasks()
# 分组中该 domain 的任务
group_tasks = grouped.get(domain, [])
# 全量任务中属于该 domain 的任务
expected_tasks = [t for t in all_tasks if t.domain == domain]
# 数量一致
assert len(group_tasks) == len(expected_tasks), (
f"domain '{domain}': 分组中 {len(group_tasks)} 个任务,"
f"预期 {len(expected_tasks)}"
)
# code 集合一致
group_codes = {t.code for t in group_tasks}
expected_codes = {t.code for t in expected_tasks}
assert group_codes == expected_codes, (
f"domain '{domain}': 分组 codes {group_codes} != 预期 {expected_codes}"
)
# 每个任务的 domain 字段都正确
for task in group_tasks:
assert task.domain == domain