Files
ctms/backend/tests/test_setup_config_project_draft.py
T
2026-05-11 11:03:06 +08:00

308 lines
9.3 KiB
Python

from datetime import date
from types import SimpleNamespace
from fastapi import HTTPException
from app.api.v1.studies import (
_apply_project_publish_snapshot_to_study,
_build_default_setup_config_from_study,
_resolve_setup_project_snapshot,
_validate_setup_data,
)
from app.schemas.study_setup_config import ProjectPublishSnapshot, StudySetupConfigData
def test_setup_config_data_keeps_project_info_draft():
draft = StudySetupConfigData.model_validate(
{
"projectInfo": {
"code": "PRJ-002",
"name": "草稿项目",
"plan_start_date": "2026-06-01",
"plan_end_date": "2026-12-31",
"planned_enrollment_count": 120,
"status": "ACTIVE",
"visit_schedule": [
{
"visit_code": "V1",
"baseline_offset_days": 7,
"window_before_days": 1,
"window_after_days": 2,
}
],
}
}
)
assert draft.projectInfo.code == "PRJ-002"
assert draft.projectInfo.name == "草稿项目"
assert draft.projectInfo.planned_enrollment_count == 120
assert draft.projectInfo.visit_schedule[0].visit_code == "V1"
def test_draft_schema_accepts_incomplete_project_snapshot_rows():
draft = StudySetupConfigData.model_validate(
{
"projectInfo": {
"code": "PRJ-002",
"name": "草稿项目",
"visit_schedule": [
{
"visit_code": "",
"baseline_offset_days": 0,
"window_before_days": 0,
"window_after_days": 0,
}
],
},
"projectMilestones": [
{
"id": "row-1",
"name": "",
"planDate": "",
"startDate": "",
"endDate": "",
"durationDays": 1,
"owner": "",
"remark": "",
"status": "",
}
],
"centerConfirm": [
{
"id": "row-1",
"siteId": "",
"siteName": "",
"confirmer": "",
"confirmStatus": "",
"confirmDate": "",
"note": "",
}
],
}
)
assert draft.projectInfo.visit_schedule[0].visit_code == ""
assert draft.projectMilestones[0].status == ""
assert draft.centerConfirm[0].confirmStatus == ""
def test_draft_save_allows_incomplete_setup_steps():
draft = StudySetupConfigData.model_validate(
{
"projectInfo": {
"code": "PRJ-002",
"name": "草稿项目",
"plan_start_date": "2026-05-01",
"plan_end_date": "2027-05-31",
},
"enrollmentPlan": {
"totalTarget": 180,
"startDate": "",
"endDate": "",
"monthlyGoalNote": "",
"stageBreakdown": "",
},
}
)
_validate_setup_data(
draft,
{},
project_plan_start=date(2026, 5, 1),
project_plan_end=date(2027, 5, 31),
strict_required=False,
)
def test_publish_validation_still_requires_complete_setup_steps():
draft = StudySetupConfigData.model_validate(
{
"projectInfo": {
"code": "PRJ-002",
"name": "草稿项目",
"plan_start_date": "2026-05-01",
"plan_end_date": "2027-05-31",
},
"enrollmentPlan": {
"totalTarget": 180,
"startDate": "",
"endDate": "",
"monthlyGoalNote": "",
"stageBreakdown": "",
},
}
)
try:
_validate_setup_data(
draft,
{},
project_plan_start=date(2026, 5, 1),
project_plan_end=date(2027, 5, 31),
)
except HTTPException as exc:
assert exc.status_code == 422
else:
raise AssertionError("publish validation should reject incomplete setup steps")
def test_project_info_is_part_of_setup_config_payload():
draft = StudySetupConfigData.model_validate(
{
"projectInfo": {
"code": "PRJ-VERSION",
"name": "版本内项目信息",
"project_full_name": "版本内项目全称",
"plan_start_date": "2026-06-01",
"plan_end_date": "2026-12-31",
"planned_site_count": 8,
"planned_enrollment_count": 120,
"status": "ACTIVE",
},
"enrollmentPlan": {
"totalTarget": 120,
"startDate": "2026-06-01",
"endDate": "2026-12-31",
"monthlyGoalNote": "",
"stageBreakdown": "",
},
}
)
payload = draft.model_dump(mode="json")
assert payload["projectInfo"]["code"] == "PRJ-VERSION"
assert payload["projectInfo"]["planned_enrollment_count"] == 120
assert payload["enrollmentPlan"]["totalTarget"] == 120
def test_default_setup_config_includes_project_info_snapshot():
study = SimpleNamespace(
code="PRJ-DEFAULT",
name="默认项目",
project_full_name="默认项目全称",
sponsor="申办方",
protocol_no="PROTO-DEFAULT",
lead_unit="组长单位",
principal_investigator="PI",
main_pm="PM",
research_analysis="III期",
research_product="研究药物",
control_product="对照药物",
indication="适应症",
research_population="研究人群",
research_design="随机双盲",
plan_start_date=date(2026, 6, 1),
plan_end_date=date(2026, 12, 31),
planned_site_count=8,
planned_enrollment_count=120,
enrollment_monthly_goal_note="",
enrollment_stage_breakdown="",
status="ACTIVE",
visit_schedule=[
{
"visit_code": "V1",
"baseline_offset_days": 7,
"window_before_days": 1,
"window_after_days": 2,
}
],
)
draft = _build_default_setup_config_from_study(study, [])
assert draft.projectInfo.code == "PRJ-DEFAULT"
assert draft.projectInfo.name == "默认项目"
assert draft.projectInfo.planned_enrollment_count == 120
assert draft.projectInfo.visit_schedule[0].visit_code == "V1"
def test_empty_project_info_does_not_fallback_to_study_snapshot():
study = SimpleNamespace(
code="PRJ-MASTER",
name="主表项目",
project_full_name="",
sponsor="",
protocol_no="",
lead_unit="",
principal_investigator="",
main_pm="",
research_analysis="",
research_product="",
control_product="",
indication="",
research_population="",
research_design="",
plan_start_date=None,
plan_end_date=None,
planned_site_count=None,
planned_enrollment_count=None,
status="DRAFT",
visit_schedule=[],
)
draft = StudySetupConfigData()
snapshot = _resolve_setup_project_snapshot(draft, study)
assert snapshot.code == ""
assert snapshot.name == ""
def test_apply_project_publish_snapshot_updates_formal_study_fields():
study = SimpleNamespace(
code="PRJ-001",
name="原项目",
project_full_name=None,
sponsor=None,
protocol_no=None,
lead_unit=None,
principal_investigator=None,
main_pm=None,
research_analysis=None,
research_product=None,
control_product=None,
indication=None,
research_population=None,
research_design=None,
plan_start_date=None,
plan_end_date=None,
planned_site_count=None,
planned_enrollment_count=10,
status="DRAFT",
visit_schedule=[],
)
snapshot = ProjectPublishSnapshot(
code="PRJ-002",
name="发布项目",
project_full_name="发布项目全称",
sponsor="申办方",
plan_start_date="2026-06-01",
plan_end_date="2026-12-31",
planned_site_count=8,
planned_enrollment_count=120,
status="ACTIVE",
visit_schedule=[
{
"visit_code": "V1",
"baseline_offset_days": 7,
"window_before_days": 1,
"window_after_days": 2,
}
],
)
changed = _apply_project_publish_snapshot_to_study(study, snapshot)
assert changed is True
assert study.code == "PRJ-002"
assert study.name == "发布项目"
assert study.project_full_name == "发布项目全称"
assert study.sponsor == "申办方"
assert study.plan_start_date == date(2026, 6, 1)
assert study.plan_end_date == date(2026, 12, 31)
assert study.planned_site_count == 8
assert study.planned_enrollment_count == 120
assert study.status == "ACTIVE"
assert study.visit_schedule[0]["visit_code"] == "V1"