from datetime import date from types import SimpleNamespace from fastapi import HTTPException from app.api.v1.studies import _apply_project_publish_snapshot_to_study, _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_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"