use std::fs; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; use sgclaw::generated_scene::analyzer::{ analyze_scene_source, analyze_scene_source_with_hint, extract_deterministic_scene_facts, G2FamilyVariant, SceneKind, ToolKind, }; use sgclaw::generated_scene::generator::{ compute_readiness_for_test, generate_scene_package, resolve_scene_ir_for_test, GenerateSceneRequest, }; use sgclaw::generated_scene::ir::{ ApiEndpointIr, ArtifactContractIr, BootstrapIr, ModeConditionIr, ModeIr, ReadinessIr, SceneIdDiagnosticsIr, SceneIr, ValidationHintsIr, WorkflowArchetype, WorkflowEvidenceIr, WorkflowStepIr, }; #[test] fn analyzer_classifies_supported_report_collection_source() { let analysis = analyze_scene_source(Path::new( "tests/fixtures/generated_scene/report_collection", )) .unwrap(); assert_eq!(analysis.scene_kind, SceneKind::ReportCollection); assert_eq!(analysis.tool_kind, ToolKind::BrowserScript); assert_eq!( analysis.bootstrap.target_url.as_deref(), Some("http://20.76.57.61:18080/gsllys") ); assert_eq!( analysis.bootstrap.app_entry_url.as_deref(), Some("http://20.76.57.61:18080/gsllys") ); assert_eq!( analysis.bootstrap.module_route_url.as_deref(), Some("http://20.76.57.61:18080/gsllys/tqLinelossStatis/tqQualifyRateMonitor") ); assert_eq!( analysis.bootstrap.target_url_kind.as_deref(), Some("runtime_context") ); assert_eq!( analysis.bootstrap.expected_domain.as_deref(), Some("20.76.57.61") ); assert_eq!( analysis.collection_entry_script.as_deref(), Some("js/report.js") ); } #[test] fn analyzer_accepts_missing_meta_with_scene_kind_hint() { let analysis = analyze_scene_source_with_hint( Path::new("tests/fixtures/generated_scene/non_report"), Some(SceneKind::ReportCollection), ) .unwrap(); assert_eq!(analysis.scene_kind, SceneKind::ReportCollection); } #[test] fn analyzer_uses_hint_when_meta_missing() { let analysis = analyze_scene_source_with_hint( Path::new("tests/fixtures/generated_scene/non_report"), Some(SceneKind::Monitoring), ) .unwrap(); assert_eq!(analysis.scene_kind, SceneKind::Monitoring); } #[test] fn analyzer_prefers_business_source_url_over_external_script_host() { let analysis = analyze_scene_source(Path::new("tests/fixtures/generated_scene/external_script")).unwrap(); assert_eq!( analysis.bootstrap.expected_domain.as_deref(), Some("yx.gs.sgcc.com.cn") ); } #[test] fn analyzer_prefers_business_source_url_over_localhost_helper_and_export() { let analysis = analyze_scene_source(Path::new( "tests/fixtures/generated_scene/bootstrap_localhost_pollution", )) .unwrap(); let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/bootstrap_localhost_pollution", )) .unwrap(); assert_eq!( analysis.bootstrap.expected_domain.as_deref(), Some("yx.gs.sgcc.com.cn") ); assert_eq!( analysis.bootstrap.target_url.as_deref(), Some("http://yx.gs.sgcc.com.cn") ); assert!(facts .endpoints .iter() .all(|endpoint| !endpoint.url.contains("localhost"))); assert!(facts .localhost_dependencies .iter() .any(|url| url.contains("localhost:13313"))); } #[test] fn deterministic_analysis_classifies_multi_mode_fixture() { let facts = extract_deterministic_scene_facts(Path::new("tests/fixtures/generated_scene/multi_mode")) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiModeRequest ); assert!(facts .branch_fields .iter() .any(|field| field == "period_mode")); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("monthReport"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("weekReport"))); let month_endpoint = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("monthReport")) .expect("expected month endpoint"); let week_endpoint = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("weekReport")) .expect("expected week endpoint"); assert_eq!( month_endpoint .request_template .as_ref() .and_then(|value| value.get("tjzq")) .and_then(|value| value.as_str()), Some("month") ); assert_eq!( week_endpoint .request_template .as_ref() .and_then(|value| value.get("tjzq")) .and_then(|value| value.as_str()), Some("week") ); } #[test] fn deterministic_analysis_prefers_g2_multi_mode_over_pagination_noise() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g2_noisy_multi_mode", )) .unwrap(); let analysis = analyze_scene_source(Path::new( "tests/fixtures/generated_scene/g2_noisy_multi_mode", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiModeRequest ); assert_eq!(facts.g2_family_variant, G2FamilyVariant::G2A); assert_eq!( analysis.bootstrap.expected_domain.as_deref(), Some("20.76.57.61:18080") ); assert_eq!( analysis.bootstrap.target_url.as_deref(), Some("http://20.76.57.61:18080/gsllys") ); assert!(facts .endpoints .iter() .any(|endpoint| { endpoint.url.contains("getYearMonWeekLinelossAnalysisList") })); assert!(facts .endpoints .iter() .any(|endpoint| { endpoint.url.contains("fourVerEightHorLinelossRateList") })); assert!(facts .endpoints .iter() .all(|endpoint| !endpoint.url.contains("github.com"))); assert!(facts .endpoints .iter() .all(|endpoint| !endpoint.url.contains("developer.mozilla.org"))); assert!(facts .endpoints .iter() .all(|endpoint| !endpoint.url.contains("stackoverflow.com"))); assert!(facts.month_column_defs.iter().any(|(key, _)| key == "YGDL")); assert!(facts .week_column_defs .iter() .any(|(key, _)| key == "LINE_LOSS_RATE")); let month_endpoint = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("fourVerEightHorLinelossRateList")) .expect("expected month endpoint"); let week_endpoint = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("getYearMonWeekLinelossAnalysisList")) .expect("expected week endpoint"); assert_eq!( month_endpoint .request_template .as_ref() .and_then(|value| value.get("yn_flag")) .and_then(|value| value.as_u64()), Some(0) ); assert_eq!( week_endpoint .request_template .as_ref() .and_then(|value| value.get("tjzq")) .and_then(|value| value.as_str()), Some("week") ); assert_eq!( week_endpoint .request_template .as_ref() .and_then(|value| value.get("weekSfdate")) .and_then(|value| value.as_str()), Some("${args.weekSfdate}") ); } #[test] fn deterministic_analysis_classifies_g2_weekly_single_mode_variant() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g2_weekly_single_mode", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiModeRequest ); assert_eq!(facts.g2_family_variant, G2FamilyVariant::G2B); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("getYearMonWeekLinelossAnalysisList"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("getTqLinelossInfoListRank"))); assert!(facts.month_column_defs.is_empty()); assert!(facts.week_column_defs.is_empty()); } #[test] fn deterministic_analysis_classifies_g2_mixed_linked_workflow_variant() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g2_mixed_linked_workflow", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiModeRequest ); assert_eq!(facts.g2_family_variant, G2FamilyVariant::G2C); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("getTqLinelossInfoListRank"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("getUserElectricList"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("syncLineLossService/workbench"))); assert!(facts.month_column_defs.is_empty()); assert!(facts.week_column_defs.is_empty()); } #[test] fn deterministic_analysis_classifies_g2_comparison_crosscheck_variant() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g2_comparison_crosscheck", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiModeRequest ); assert_eq!(facts.g2_family_variant, G2FamilyVariant::G2E); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("getTqLinelossInfoListRank"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("getUserElectricList"))); } #[test] fn deterministic_analysis_classifies_g2_diagnosis_drilldown_variant() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g2_diagnosis_drilldown", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiModeRequest ); assert_eq!(facts.g2_family_variant, G2FamilyVariant::G2F); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("tqAutoDiagnoseAnalyse/search"))); assert!(facts.endpoints.iter().any(|endpoint| endpoint .url .contains("stealElecAnalyse/getFlqdyhDetailList"))); } #[test] fn deterministic_analysis_classifies_g2_prediction_compute_variant() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g2_prediction_compute", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiModeRequest ); assert_eq!(facts.g2_family_variant, G2FamilyVariant::G2D); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.url.contains("highLineLossForecast"))); } #[test] fn deterministic_analysis_classifies_paginated_enrichment_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); assert!(facts.pagination_fields.iter().any(|field| field == "page")); assert!(facts .secondary_request_methods .iter() .any(|method| method == "getUserCharges")); assert!(facts .filter_expressions .iter() .any(|expr| expr.contains("charge !== 0"))); } #[test] fn deterministic_analysis_classifies_g1e_light_enrichment_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g1e_light_enrichment", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::SingleRequestEnrichment ); assert!(facts.branch_fields.iter().any(|field| field == "mode")); assert!(facts.branch_fields.iter().any(|field| field == "month")); assert!(facts .branch_fields .iter() .any(|field| field == "reportType")); assert!(facts .filter_expressions .iter() .any(|expr| expr.contains("status === 200"))); assert!(facts .g1e_main_endpoint .as_deref() .map(|name| name.contains("getWkorderAll")) .unwrap_or(false)); assert!(facts .g1e_enrichment_endpoints .iter() .any(|name| name.contains("queryElectCustInfo"))); assert!(facts.g1e_join_keys.iter().any(|key| key == "wkOrderNo")); assert!(facts .g1e_output_columns .iter() .any(|(field, _)| field == "countyCodeName")); } #[test] fn deterministic_analysis_classifies_g1e_light_enrichment_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g1e_light_enrichment_expansion", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::SingleRequestEnrichment ); assert!(facts .g1e_main_endpoint .as_deref() .map(|name| name.contains("getWkorderAll")) .unwrap_or(false)); assert!(facts .g1e_enrichment_endpoints .iter() .any(|name| name.contains("queryMeterInfo"))); assert!(facts.g1e_join_keys.iter().any(|key| key == "wkOrderNo")); assert!(facts .g1e_join_keys .iter() .any(|key| key == "countyCodeName")); assert!(facts .g1e_output_columns .iter() .any(|(field, _)| field == "meterCapacityThisMonth")); assert!(facts .g1e_aggregate_rules .iter() .any(|rule| rule == "group_by:countyCodeName")); } #[test] fn deterministic_analysis_classifies_g1e_light_enrichment_additional_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g1e_light_enrichment_additional", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::SingleRequestEnrichment ); assert!(facts .g1e_main_endpoint .as_deref() .map(|name| name.contains("getWkorderAll")) .unwrap_or(false)); assert!(facts .g1e_enrichment_endpoints .iter() .any(|name| name.contains("queryBusAcpt"))); assert!(facts.g1e_join_keys.iter().any(|key| key == "wkOrderNo")); assert!(facts .g1e_join_keys .iter() .any(|key| key == "countyCodeName")); assert!(facts .g1e_output_columns .iter() .any(|(field, _)| field == "batchCapacityThisMonth")); assert!(facts .g1e_aggregate_rules .iter() .any(|rule| rule == "group_by:countyCodeName")); } #[test] fn deterministic_analysis_classifies_g6_host_bridge_workflow_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g6_host_bridge_workflow", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::HostBridgeWorkflow ); assert!(facts .host_bridge_actions .iter() .any(|action| action.contains("sgBrowerserJsAjax2"))); assert!(facts .localhost_dependencies .iter() .any(|dependency| dependency.contains("localhost:13313"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.name.contains("getWorkOrderToDoList"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.name.contains("queryMeterPlanFormulateApp"))); } #[test] fn deterministic_analysis_prefers_g6_for_fixed_real_sample_mixed_with_localhost_export() { let facts = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/电能表现场检验完成率指标报表", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::HostBridgeWorkflow ); assert!(facts .host_bridge_actions .iter() .any(|action| action.contains("location.origin") || action.contains("location.href") || action.contains("yx.gs.sgcc.com.cn"))); assert!(facts .localhost_dependencies .iter() .any(|dependency| dependency.contains("localhost:13313"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.name.contains("getWorkOrderToDoList"))); assert!(facts .endpoints .iter() .any(|endpoint| endpoint.name.contains("queryMeterPlanFormulateApp"))); } #[test] fn deterministic_analysis_classifies_g7_multi_endpoint_inventory_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g7_multi_endpoint_inventory", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiEndpointInventory ); assert!(facts.inventory_endpoint_names.len() >= 5); assert!(facts .inventory_endpoint_names .iter() .any(|name| name.contains("assetStatsQueryMeter"))); assert!(facts .inventory_endpoint_names .iter() .any(|name| name.contains("assetStatsQueryJlGnModule"))); } #[test] fn deterministic_analysis_prefers_g7_for_mixed_g7_host_bridge_real_sample() { let facts = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/计量资产库存统计", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::MultiEndpointInventory ); assert!(facts.inventory_endpoint_names.len() >= 5); assert!(!facts.host_bridge_actions.is_empty()); assert!(facts .localhost_dependencies .iter() .any(|dependency| dependency.contains("localhost:13313"))); } #[test] fn deterministic_analysis_classifies_g8_local_doc_pipeline_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g8_local_doc_pipeline", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::LocalDocPipeline ); assert!(facts .localhost_dependencies .iter() .any(|dependency| dependency.contains("localhost:13313"))); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action == "definedSqlQuery")); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action == "docExport")); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action.contains("selectData"))); } #[test] fn deterministic_analysis_prefers_g3_for_mixed_g3_g8_boundary_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/g3_g8_mixed_boundary", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); assert!(facts .g3_business_endpoint_names .iter() .any(|name| name.contains("queryHisS95598WkstGrid") || name.contains("newQueryS95598WkstGrid"))); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action == "selectData")); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action == "docExport")); assert!(facts .pagination_fields .iter() .any(|field| field == "PAGEPOLITCURRENTPAGEINDEX_grid")); assert!(facts.response_paths.iter().any(|field| field == "rows")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); assert!(facts.endpoints.len() >= 2); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("ticketRiskDetail")) .expect("expected secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn real_g3_sample_deterministic_analysis_stays_in_paginated_enrichment() { let facts = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/95598工单明细表", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); assert!(facts .g3_business_endpoint_names .iter() .any(|name| name.contains("queryHisS95598WkstGrid") || name.contains("newQueryS95598WkstGrid"))); assert!(facts .host_bridge_actions .iter() .any(|action| action.contains("sgBrowerserJsAjax2"))); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action == "selectData")); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action == "docExport")); assert!(facts .pagination_fields .iter() .any(|field| field == "PAGEPOLITCURRENTPAGEINDEX_grid")); } #[test] fn real_g3_sample_runtime_scope_gate_allows_subordinate_localhost_dependencies() { let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string( "examples/g3_real_sample_validation/skills/g3-95598-ticket-detail-real/references/generation-report.json", ) .unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(generated_report .runtime_dependencies .iter() .all(|item| item.subordinate_to_business_chain)); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g3_runtime_scope_compatible" && gate.passed)); assert_ne!(generated_report.readiness.level, "C"); } #[test] fn real_g3_sample_output_contract_is_narrower_than_generic_g3_shape() { let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string( "examples/g3_real_sample_validation/skills/g3-95598-ticket-detail-real/references/generation-report.json", ) .unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert_eq!( generated_report .main_request .as_ref() .and_then(|request| request.api_endpoint.as_ref()) .map(|endpoint| endpoint.name.as_str()), Some("queryHisS95598WkstGrid") ); assert_eq!( generated_report .main_request .as_ref() .map(|request| request.response_path.as_str()), Some("rows") ); assert!(generated_report .enrichment_requests .iter() .all(|item| item.name != "login.jsp" && item.name != "main1.jsp")); assert!(generated_report.join_keys.iter().all(|key| matches!( key.as_str(), "appNo" | "custNo" | "ticketNo" | "workOrderNo" | "orderNo" ))); assert!(generated_report .merge_or_dedupe_rules .iter() .all(|rule| rule.contains("appNo") || rule.contains("custNo") || rule.contains("ticketNo") || rule.contains("workOrderNo") || rule.contains("orderNo"))); } #[test] fn real_g2_sample_contract_matches_closed_multi_mode_shape() { let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string( "examples/real_scene_batch_round1/skills/real-tq-lineloss-report-r4/references/generation-report.json", ) .unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::MultiModeRequest ); assert_eq!( generated_report.bootstrap.expected_domain, "20.76.57.61:18080" ); assert_eq!( generated_report.bootstrap.target_url, "http://20.76.57.61:18080/gsllys" ); assert_eq!(generated_report.readiness.level, "A"); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g2_bootstrap_resolved" && gate.passed)); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g2_request_contract_complete" && gate.passed)); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g2_response_contract_complete" && gate.passed)); assert_eq!(generated_report.modes.len(), 2); assert_eq!(generated_report.default_mode.as_deref(), Some("month")); assert_eq!( generated_report.mode_switch_field.as_deref(), Some("period_mode") ); let month_mode = generated_report .modes .iter() .find(|mode| mode.name == "month") .expect("expected month mode"); assert_eq!( month_mode .api_endpoint .as_ref() .map(|endpoint| endpoint.name.as_str()), Some("fourVerEightHorLinelossRateList") ); assert_eq!(month_mode.response_path, "content"); assert!(month_mode.request_template.as_object().is_some()); assert!(month_mode .request_template .as_object() .and_then(|template| template.get("orgno")) .is_some()); assert!(month_mode .request_template .as_object() .and_then(|template| template.get("fdate")) .is_some()); assert!(!month_mode.column_defs.is_empty()); let week_mode = generated_report .modes .iter() .find(|mode| mode.name == "week") .expect("expected week mode"); assert_eq!( week_mode .api_endpoint .as_ref() .map(|endpoint| endpoint.name.as_str()), Some("getYearMonWeekLinelossAnalysisList") ); assert_eq!(week_mode.response_path, "content"); assert!(week_mode.request_template.as_object().is_some()); assert!(week_mode .request_template .as_object() .and_then(|template| template.get("orgno")) .is_some()); assert!(week_mode .request_template .as_object() .and_then(|template| template.get("weekSfdate")) .is_some()); assert!(week_mode .request_template .as_object() .and_then(|template| template.get("weekEfdate")) .is_some()); assert!(!week_mode.column_defs.is_empty()); assert_eq!(generated_report.response_path, "content"); } #[test] fn generator_derives_reusable_request_field_mappings_for_real_g2_fixture() { let source_dir = PathBuf::from("tests/fixtures/generated_scene/multi_mode"); let analysis = analyze_scene_source_with_hint(&source_dir, Some(SceneKind::ReportCollection)).unwrap(); let facts = extract_deterministic_scene_facts(&source_dir).unwrap(); let generated_report = resolve_scene_ir_for_test( &GenerateSceneRequest { source_dir: source_dir.clone(), scene_id: "resolver-request-mapping-g2".to_string(), scene_name: "Resolver Request Mapping G2".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: temp_workspace("sgclaw-resolver-request-mapping-g2"), lessons_path: None, scene_info_json: None, scene_ir_json: Some(build_multi_mode_scene_ir()), }, &analysis, &facts, ); let month_mode = generated_report .modes .iter() .find(|mode| mode.name == "month") .expect("expected month mode"); assert!(month_mode.request_field_mappings.iter().any(|mapping| { mapping.source_field == "org_code" && mapping.target_field == "orgno" && mapping.mode.as_deref() == Some("month") })); assert!(month_mode.request_field_mappings.iter().any(|mapping| { mapping.source_field == "period_payload.fdate" && mapping.target_field == "fdate" && mapping.mode.as_deref() == Some("month") })); let week_mode = generated_report .modes .iter() .find(|mode| mode.name == "week") .expect("expected week mode"); assert!(week_mode.request_field_mappings.iter().any(|mapping| { mapping.source_field == "org_code" && mapping.target_field == "orgno" && mapping.mode.as_deref() == Some("week") })); assert!(week_mode.request_field_mappings.iter().any(|mapping| { mapping.source_field == "period_payload.weekSfdate" && mapping.target_field == "weekSfdate" && mapping.mode.as_deref() == Some("week") })); assert!(week_mode.request_field_mappings.iter().any(|mapping| { mapping.source_field == "period_payload.weekEfdate" && mapping.target_field == "weekEfdate" && mapping.mode.as_deref() == Some("week") })); } #[test] fn analyzer_extracts_embedded_org_dictionary_from_sweep_030_source() { let facts = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/台区线损大数据-月_周累计线损率统计分析", )) .unwrap(); assert!( facts.org_dictionary_entries.len() >= 10, "expected source-driven org dictionary entries, got {}", facts.org_dictionary_entries.len() ); assert!(facts .org_dictionary_entries .iter() .any(|entry| entry.code == "62401")); assert!(facts .org_dictionary_entries .iter() .any(|entry| entry.code == "62408")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_workorder_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_workorder", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("workOrderSourceSummary")) .expect("expected workorder secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for workorder secondary endpoint"); assert!(template.contains_key("workOrderNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_orderno_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_orderno", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("serviceOrderTrend")) .expect("expected orderno secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for orderno secondary endpoint"); assert!(template.contains_key("orderNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_source_distribution_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_source_distribution", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("ticketSourceSummary")) .expect("expected source distribution secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for source distribution secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_service_risk_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_service_risk", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("serviceRiskDetail")) .expect("expected service risk secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for service risk secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_timeout_warning_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_timeout_warning", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("ticketTimeoutWarningDetail")) .expect("expected timeout warning secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for timeout warning secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_device_monitor_weekly_expansion_fixture( ) { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_device_monitor_weekly", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("deviceMonitorWeeklyDetail")) .expect("expected device monitor weekly secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for device monitor weekly secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_customer_satisfaction_expansion_fixture( ) { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_customer_satisfaction", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("customerSatisfactionDetail")) .expect("expected customer satisfaction secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for customer satisfaction secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_repair_return_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_repair_return", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("repairReturnDetail")) .expect("expected repair return secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for repair return secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_repair_daily_control_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_repair_daily_control", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("repairDailyControlDetail")) .expect("expected repair daily control secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for repair daily control secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn deterministic_analysis_extracts_request_templates_for_g3_business_stats_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_business_stats", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let secondary = facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("businessStatsDetail")) .expect("expected business stats secondary endpoint"); let template = secondary .request_template .as_ref() .and_then(serde_json::Value::as_object) .expect("expected parsed request template for business stats secondary endpoint"); assert!(template.contains_key("ticketNo")); } #[test] fn generator_blocks_incomplete_multi_mode_contract() { let output_root = temp_workspace("sgclaw-scene-generator-g2-incomplete"); let mut scene_ir = build_multi_mode_scene_ir(); scene_ir.modes[0].request_template = serde_json::Value::Null; scene_ir.modes[0].column_defs.clear(); let error = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/multi_mode"), scene_id: "g2-incomplete-scene".to_string(), scene_name: "G2涓嶅畬鏁村満鏅�".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .expect_err("expected incomplete G2 contract to be blocked"); assert!(error .to_string() .contains("workflow evidence is incomplete for archetype multi_mode_request")); } #[test] fn generator_writes_multi_mode_package_with_generation_report() { let output_root = temp_workspace("sgclaw-scene-generator-multi-mode"); let scene_ir = build_multi_mode_scene_ir(); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/multi_mode"), scene_id: "multi-mode-scene".to_string(), scene_name: "多模式场景".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .unwrap(); let skill_root = output_root.join("skills/multi-mode-scene"); let generated_script = fs::read_to_string(skill_root.join("scripts/collect_multi_mode_scene.js")).unwrap(); let generated_manifest = fs::read_to_string(skill_root.join("scene.toml")).unwrap(); let generation_report = fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(); assert!(generated_script.contains("const MODES =")); assert!(generated_script.contains("function applyRequestMappings")); assert!(generated_script.contains("period_payload: normalizePayload")); assert!(generated_manifest.contains("resolver = \"dictionary_entity\"")); assert!(generated_manifest.contains("resolver = \"month_week_period\"")); assert!(generated_manifest.contains("[[request_mappings]]")); assert!(generated_manifest.contains("source_field = \"org_code\"")); assert!(generated_manifest.contains("target_field = \"orgno\"")); assert!(generated_manifest.contains("app_entry_url = \"http://20.76.57.61:18080/gsllys\"")); assert!(generated_manifest .contains("module_route_url = \"http://20.76.57.61:18080/gsllys/monthReport\"")); assert!(generated_manifest.contains("target_url_kind = \"runtime_context\"")); assert!(generation_report.contains("\"workflowArchetype\": \"multi_mode_request\"")); assert!(generation_report.contains("\"tjzq\": \"month\"")); assert!(generation_report.contains("\"tjzq\": \"week\"")); assert!(generation_report.contains("\"requestFieldMappings\"")); assert!(generation_report.contains("\"sourceField\": \"org_code\"")); assert!(generation_report.contains("\"sourceField\": \"period_payload.weekSfdate\"")); assert!(generation_report.contains("\"appEntryUrl\": \"http://20.76.57.61:18080/gsllys\"")); assert!(generation_report .contains("\"moduleRouteUrl\": \"http://20.76.57.61:18080/gsllys/monthReport\"")); assert!(generation_report.contains("\"targetUrlKind\": \"runtime_context\"")); assert!(generation_report.contains("\"request_contract_complete\"")); assert!(generation_report.contains("\"response_contract_complete\"")); assert!(generation_report.contains("\"workflow_contract_complete\"")); assert!(generation_report.contains("\"g2_modes_present\"")); assert!(generation_report.contains("\"g2_request_contract_complete\"")); assert!(generation_report.contains("\"g2_response_contract_complete\"")); assert!(generation_report.contains("\"evidenceType\": \"bootstrap_candidate\"")); } #[test] fn generator_writes_multi_mode_package_from_deterministic_analysis() { let facts = extract_deterministic_scene_facts(Path::new("tests/fixtures/generated_scene/multi_mode")) .unwrap(); assert!(facts .endpoints .iter() .all(|endpoint| endpoint.request_template.is_some())); let output_root = temp_workspace("sgclaw-scene-generator-multi-mode-auto"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/multi_mode"), scene_id: "multi-mode-auto".to_string(), scene_name: "多模式自动场景".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generation_report = fs::read_to_string( output_root.join("skills/multi-mode-auto/references/generation-report.json"), ) .unwrap(); let org_dictionary = fs::read_to_string( output_root.join("skills/multi-mode-auto/references/org-dictionary.json"), ) .unwrap(); assert!(generation_report.contains("\"workflowArchetype\": \"multi_mode_request\"")); assert!(generation_report.contains("\"name\": \"month\"")); assert!(generation_report.contains("\"name\": \"week\"")); assert!(generation_report.contains("\"g2_modes_present\"")); assert!(generation_report.contains("\"g2_request_contract_complete\"")); assert!(generation_report.contains("\"g2_response_contract_complete\"")); assert_eq!(org_dictionary.trim(), "[]"); } #[test] fn generator_writes_real_sweep_030_org_dictionary_from_embedded_source() { let output_root = temp_workspace("sgclaw-scene-generator-sweep-030-dictionary"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "D:/desk/智能体资料/全量业务场景/一平台场景/台区线损大数据-月_周累计线损率统计分析", ), scene_id: "sweep-030-scene".to_string(), scene_name: "台区线损大数据-月_周累计线损率统计分析".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let org_dictionary = fs::read_to_string( output_root.join("skills/sweep-030-scene/references/org-dictionary.json"), ) .unwrap(); let scene_manifest = fs::read_to_string(output_root.join("skills/sweep-030-scene/scene.toml")).unwrap(); assert!(org_dictionary.contains("\"code\": \"62401\"")); assert!(org_dictionary.contains("\"code\": \"62408\"")); assert!(scene_manifest.contains("suffix = \"。。。\"")); assert!(scene_manifest.contains("default_strategy = \"lineloss_page_semantics\"")); assert!(scene_manifest.contains( "\u{7ebf}\u{635f}\u{5927}\u{6570}\u{636e} \u{6708}\u{7d2f}\u{8ba1}\u{7ebf}\u{635f}\u{7edf}\u{8ba1}\u{5206}\u{6790}" )); assert!(scene_manifest.contains( "\u{7ebf}\u{635f}\u{5927}\u{6570}\u{636e} \u{5468}\u{7d2f}\u{8ba1}\u{7ebf}\u{635f}\u{7edf}\u{8ba1}\u{5206}\u{6790}" )); assert!(scene_manifest.contains("\u{53f0}\u{533a}\u{7ebf}\u{635f}")); } #[test] fn generator_escapes_request_mapping_fields_for_valid_toml() { let output_root = temp_workspace("sgclaw-scene-generator-sweep-078-valid-toml"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "D:/desk/智能体资料/全量业务场景/一平台场景/线损同期差异报表", ), scene_id: "sweep-078-scene".to_string(), scene_name: "线损同期差异报表".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let scene_manifest = fs::read_to_string(output_root.join("skills/sweep-078-scene/scene.toml")).unwrap(); assert!(scene_manifest.contains("suffix = \"。。。\"")); toml::from_str::(&scene_manifest).unwrap(); } #[test] fn analyzer_recovers_local_doc_residual_export_workflow_evidence() { let cases = [ ( "D:/desk/智能体资料/全量业务场景/一平台场景/力禾动环系统巡视记录", "docExport", ), ( "D:/desk/智能体资料/全量业务场景/一平台场景/基于全量报表的出口版本", "docTemplateTransform", ), ( "D:/desk/智能体资料/全量业务场景/一平台场景/安全督查周通报", "docExport", ), ( "D:/desk/智能体资料/全量业务场景/一平台场景/巡视计划完成情况自动检索", "docExport", ), ( "D:/desk/智能体资料/全量业务场景/一平台场景/消防工作分析报告", "docExport", ), ( "D:/desk/智能体资料/全量业务场景/一平台场景/输变电设备运行分析报告", "reportLogSet", ), ]; for (source_dir, expected_action) in cases { let facts = extract_deterministic_scene_facts(Path::new(source_dir)).unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::LocalDocPipeline, "source_dir={source_dir}" ); assert!( facts .local_doc_pipeline_actions .iter() .any(|action| action == expected_action), "source_dir={source_dir}; expected_action={expected_action}; actions={:?}", facts.local_doc_pipeline_actions ); assert!( !facts.local_doc_pipeline_actions.is_empty(), "source_dir={source_dir}" ); } } #[test] fn generator_recovers_local_doc_residual_packages_from_source_evidence() { let cases = [ ( "sweep-025-scene", "力禾动环系统巡视记录", "D:/desk/智能体资料/全量业务场景/一平台场景/力禾动环系统巡视记录", ), ( "sweep-047-scene", "基于全量报表的出口版本", "D:/desk/智能体资料/全量业务场景/一平台场景/基于全量报表的出口版本", ), ( "sweep-050-scene", "安全督查周通报", "D:/desk/智能体资料/全量业务场景/一平台场景/安全督查周通报", ), ( "sweep-052-scene", "巡视计划完成情况自动检索", "D:/desk/智能体资料/全量业务场景/一平台场景/巡视计划完成情况自动检索", ), ( "sweep-062-scene", "消防工作分析报告", "D:/desk/智能体资料/全量业务场景/一平台场景/消防工作分析报告", ), ( "sweep-087-scene", "输变电设备运行分析报告", "D:/desk/智能体资料/全量业务场景/一平台场景/输变电设备运行分析报告", ), ]; for (scene_id, scene_name, source_dir) in cases { let source_dir = PathBuf::from(source_dir); if !source_dir.exists() { eprintln!("skipping {scene_id} local-doc residual regression: source dir not found"); continue; } let skill_root = generate_scene_package(GenerateSceneRequest { source_dir, scene_id: scene_id.to_string(), scene_name: scene_name.to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: temp_workspace(&format!("sgclaw-{scene_id}-local-doc-residual")), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap_or_else(|err| panic!("{scene_id} should generate after local-doc residual closure: {err}")); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::LocalDocPipeline, "scene_id={scene_id}" ); assert!( generated_report .workflow_steps .iter() .any(|step| step.step_type == "doc_export"), "scene_id={scene_id}; steps={:?}", generated_report.workflow_steps ); assert!(skill_root.join("SKILL.toml").exists(), "scene_id={scene_id}"); assert!(skill_root.join("scene.toml").exists(), "scene_id={scene_id}"); } } #[test] fn analyzer_recovers_lineloss_period_default_strategy_from_source() { let facts = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/台区线损大数据-月_周累计线损率统计分析", )) .unwrap(); assert_eq!( facts.period_default_strategy.as_deref(), Some("lineloss_page_semantics") ); } #[test] fn generator_fills_empty_g2_mode_request_templates() { let output_root = temp_workspace("sgclaw-scene-generator-g2-empty-template-fill"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g2_noisy_multi_mode"), scene_id: "g2-empty-template-fill".to_string(), scene_name: "G2 empty request template fill".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string( output_root.join("skills/g2-empty-template-fill/references/generation-report.json"), ) .unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::MultiModeRequest ); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g2_request_contract_complete" && gate.passed)); assert!(generated_report.modes.iter().all(|mode| mode .request_template .as_object() .is_some_and(|template| !template.is_empty()))); assert!(generated_report .modes .iter() .filter(|mode| mode.name == "month" || mode.name == "week") .all(|mode| mode .request_template .as_object() .and_then(|template| template.get("tjzq")) .and_then(serde_json::Value::as_str) == Some(mode.name.as_str()))); } #[test] fn generator_writes_paginated_enrichment_without_forced_org_period_defaults() { let output_root = temp_workspace("sgclaw-scene-generator-paginated"); let scene_ir = build_paginated_scene_ir(); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/paginated_enrichment"), scene_id: "paginated-scene".to_string(), scene_name: "分页补数场景".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .unwrap(); let skill_root = output_root.join("skills/paginated-scene"); let generated_script = fs::read_to_string(skill_root.join("scripts/collect_paginated_scene.js")).unwrap(); let generated_manifest = fs::read_to_string(skill_root.join("scene.toml")).unwrap(); let generation_report = fs::read_to_string(skill_root.join("references/generation-report.md")).unwrap(); assert!(generated_script.contains("async function queryPrimaryPage")); assert!(generated_script.contains("async function querySecondary")); assert!(generated_script.contains("function applyFilter")); assert!(!generated_manifest.contains("resolver = \"dictionary_entity\"")); assert!(!generated_manifest.contains("resolver = \"month_week_period\"")); assert!(generation_report.contains("Readiness")); let generation_report_json = fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(); assert!(generation_report_json.contains("\"mainRequest\"")); assert!(generation_report_json.contains("\"paginationPlan\"")); assert!(generation_report_json.contains("\"enrichmentRequests\"")); assert!(generation_report_json.contains("\"joinKeys\"")); assert!(generation_report_json.contains("\"mergeOrDedupeRules\"")); assert!(generation_report_json.contains("\"exportPlan\"")); assert!(generation_report_json.contains("\"g3_main_request_resolved\"")); assert!(generation_report_json.contains("\"g3_pagination_contract_complete\"")); assert!(generation_report_json.contains("\"g3_enrichment_contract_complete\"")); assert!(generation_report_json.contains("\"g3_join_key_resolved\"")); assert!(generation_report_json.contains("\"g3_export_path_identified\"")); assert!(generation_report_json.contains("\"g3_runtime_scope_compatible\"")); } #[test] fn paginated_enrichment_p0_regression_checklist_is_actionable() { let output_root = temp_workspace("sgclaw-g3-p0-regression"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/paginated_enrichment"), scene_id: "g3-p0-regression".to_string(), scene_name: "G3 P0 Regression".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert_eq!(generated_report.readiness.level, "A"); assert_eq!( generated_report .main_request .as_ref() .map(|request| request.response_path.as_str()), Some("rows") ); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("page") ); assert_eq!(generated_report.join_keys, vec!["custNo".to_string()]); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:charge")); assert_eq!( generated_report .export_plan .as_ref() .and_then(|plan| plan.entry.as_deref()), Some("exportExcel") ); assert!(generated_report .evidence .iter() .all(|item| item.evidence_type != "localhost_dependency_candidate")); assert!(generated_report .readiness .gates .iter() .any(|gate| { gate.name == "g3_join_key_resolved" && gate.passed })); } #[test] fn generator_writes_paginated_enrichment_family_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); assert!(facts.endpoints.len() >= 2); assert!(facts .endpoints .iter() .find(|endpoint| endpoint.url.contains("ticketRiskDetail")) .and_then(|endpoint| endpoint.request_template.as_ref()) .and_then(serde_json::Value::as_object) .map(|template| template.contains_key("ticketNo")) .unwrap_or(false)); let output_root = temp_workspace("sgclaw-g3-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/paginated_enrichment_expansion"), scene_id: "g3-family-expansion".to_string(), scene_name: "G3 Family Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!( "g3 expansion fixture should reuse paginated enrichment contract; error={err}; pagination={:?}; secondary={:?}; filters={:?}; exports={:?}; response_paths={:?}; join_keys={:?}", facts.pagination_fields, facts.secondary_request_methods, facts.filter_expressions, facts.export_methods, facts.response_paths, facts.g1e_join_keys, ); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("pageNum") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:riskLevel")); assert_eq!( generated_report .export_plan .as_ref() .and_then(|plan| plan.entry.as_deref()), Some("exportExcel") ); assert!(generated_report .readiness .gates .iter() .any(|gate| { gate.name == "g3_main_request_resolved" && gate.passed })); assert!(generated_report .readiness .gates .iter() .any(|gate| { gate.name == "g3_join_key_resolved" && gate.passed })); } #[test] fn generator_writes_paginated_enrichment_workorder_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_workorder", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-workorder-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_workorder", ), scene_id: "g3-family-expansion-workorder".to_string(), scene_name: "G3 WorkOrder Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!( "g3 workorder expansion fixture should reuse paginated enrichment contract; error={err}; pagination={:?}; secondary={:?}; filters={:?}; exports={:?}; response_paths={:?}; join_keys={:?}", facts.pagination_fields, facts.secondary_request_methods, facts.filter_expressions, facts.export_methods, facts.response_paths, facts.g1e_join_keys, ); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("pageNo") ); assert!(generated_report .join_keys .iter() .any(|key| key == "workOrderNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:sourceType")); } #[test] fn generator_writes_paginated_enrichment_orderno_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_orderno", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-orderno-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_orderno", ), scene_id: "g3-family-expansion-orderno".to_string(), scene_name: "G3 OrderNo Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!( "g3 orderno expansion fixture should reuse paginated enrichment contract; error={err}; pagination={:?}; secondary={:?}; filters={:?}; exports={:?}; response_paths={:?}; join_keys={:?}", facts.pagination_fields, facts.secondary_request_methods, facts.filter_expressions, facts.export_methods, facts.response_paths, facts.g1e_join_keys, ); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("page") ); assert!(generated_report .join_keys .iter() .any(|key| key == "orderNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:sourceType")); } #[test] fn generator_writes_paginated_enrichment_source_distribution_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_source_distribution", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-source-distribution-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_source_distribution", ), scene_id: "g3-family-expansion-source-distribution".to_string(), scene_name: "G3 Source Distribution Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!( "g3 source distribution expansion fixture should reuse paginated enrichment contract; error={err}; pagination={:?}; secondary={:?}; filters={:?}; exports={:?}; response_paths={:?}; join_keys={:?}", facts.pagination_fields, facts.secondary_request_methods, facts.filter_expressions, facts.export_methods, facts.response_paths, facts.g1e_join_keys, ); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("pageNum") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:sourceType")); } #[test] fn generator_writes_paginated_enrichment_service_risk_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_service_risk", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-service-risk-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_service_risk", ), scene_id: "g3-family-expansion-service-risk".to_string(), scene_name: "G3 Service Risk Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!( "g3 service risk expansion fixture should reuse paginated enrichment contract; error={err}; pagination={:?}; secondary={:?}; filters={:?}; exports={:?}; response_paths={:?}; join_keys={:?}", facts.pagination_fields, facts.secondary_request_methods, facts.filter_expressions, facts.export_methods, facts.response_paths, facts.g1e_join_keys, ); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("pageNo") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:riskLevel")); } #[test] fn generator_writes_paginated_enrichment_timeout_warning_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_timeout_warning", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-timeout-warning-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_timeout_warning", ), scene_id: "g3-family-expansion-timeout-warning".to_string(), scene_name: "G3 Timeout Warning Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!( "g3 timeout warning expansion fixture should reuse paginated enrichment contract; error={err}; pagination={:?}; secondary={:?}; filters={:?}; exports={:?}; response_paths={:?}; join_keys={:?}", facts.pagination_fields, facts.secondary_request_methods, facts.filter_expressions, facts.export_methods, facts.response_paths, facts.g1e_join_keys, ); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("pageNum") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:riskLevel")); } #[test] fn generator_recovers_g3_enrichment_requests_from_secondary_request_signals() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); assert!(!facts.secondary_request_methods.is_empty()); let output_root = temp_workspace("sgclaw-g3-enrichment-request-recovery"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/paginated_enrichment_expansion"), scene_id: "g3-enrichment-request-recovery".to_string(), scene_name: "G3 Enrichment Request Recovery".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(!generated_report.enrichment_requests.is_empty()); assert!(generated_report .enrichment_requests .iter() .all(|item| item.api_endpoint.is_some())); assert!( generated_report .workflow_evidence .secondary_request_entries .len() >= 1 ); assert!(generated_report .readiness .gates .iter() .any(|gate| { gate.name == "g3_enrichment_contract_complete" && gate.passed })); } #[test] fn analyzer_collects_word_export_as_g3_export_signal() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_business_stats", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); assert!(facts .export_methods .iter() .any(|item| item.to_ascii_lowercase().contains("export") || item.to_ascii_lowercase().contains("word"))); } #[test] fn generator_writes_paginated_enrichment_device_monitor_weekly_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_device_monitor_weekly", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-device-monitor-weekly-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_device_monitor_weekly", ), scene_id: "g3-family-expansion-device-monitor-weekly".to_string(), scene_name: "G3 Device Monitor Weekly Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!("g3 device monitor weekly expansion fixture should reuse paginated enrichment contract; error={err}"); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("pageNo") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:sourceType")); } #[test] fn generator_writes_paginated_enrichment_customer_satisfaction_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_customer_satisfaction", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-customer-satisfaction-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_customer_satisfaction", ), scene_id: "g3-family-expansion-customer-satisfaction".to_string(), scene_name: "G3 Customer Satisfaction Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!("g3 customer satisfaction expansion fixture should reuse paginated enrichment contract; error={err}"); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("page") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:sourceType")); } #[test] fn generator_writes_paginated_enrichment_repair_return_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_repair_return", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-repair-return-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_repair_return", ), scene_id: "g3-family-expansion-repair-return".to_string(), scene_name: "G3 Repair Return Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!("g3 repair return expansion fixture should reuse paginated enrichment contract; error={err}"); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("pageNum") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:riskLevel")); } #[test] fn generator_writes_paginated_enrichment_repair_daily_control_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_repair_daily_control", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-repair-daily-control-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_repair_daily_control", ), scene_id: "g3-family-expansion-repair-daily-control".to_string(), scene_name: "G3 Repair Daily Control Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!("g3 repair daily control expansion fixture should reuse paginated enrichment contract; error={err}"); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("pageNo") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:riskLevel")); } #[test] fn generator_writes_paginated_enrichment_business_stats_expansion_fixture() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment_expansion_business_stats", )) .unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::PaginatedEnrichment ); let output_root = temp_workspace("sgclaw-g3-business-stats-expansion-fixture"); let generation = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "tests/fixtures/generated_scene/paginated_enrichment_expansion_business_stats", ), scene_id: "g3-family-expansion-business-stats".to_string(), scene_name: "G3 Business Stats Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }); if let Err(err) = &generation { panic!("g3 business stats expansion fixture should reuse paginated enrichment contract; error={err}"); } let skill_root = generation.unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert_eq!( generated_report .pagination_plan .as_ref() .map(|plan| plan.page_field.as_str()), Some("page") ); assert!(generated_report .join_keys .iter() .any(|key| key == "ticketNo")); assert!(generated_report .merge_or_dedupe_rules .iter() .any(|rule| rule == "aggregate:sourceType")); } #[test] fn generator_writes_g1e_light_enrichment_package() { let output_root = temp_workspace("sgclaw-g1e-package"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g1e_light_enrichment"), scene_id: "g1e-light-enrichment".to_string(), scene_name: "高低压新增报装容量月度统计表".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generation_report = fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(); let browser_script = fs::read_to_string(skill_root.join("scripts/collect_g1e_light_enrichment.js")).unwrap(); assert!(generation_report.contains("\"workflowArchetype\": \"single_request_enrichment\"")); assert!(generation_report.contains("\"mainRequest\"")); assert!(generation_report.contains("\"enrichmentRequests\"")); assert!(generation_report.contains("\"mergePlan\"")); assert!(generation_report.contains("getWkorderAll")); assert!(generation_report.contains("queryElectCustInfo")); assert!(generation_report.contains("countyCodeName")); assert!(browser_script.contains("const MAIN_REQUEST =")); assert!(browser_script.contains("const ENRICHMENT_REQUESTS =")); assert!(browser_script.contains("const MERGE_PLAN =")); } #[test] fn generator_writes_g1e_light_enrichment_expansion_package() { let output_root = temp_workspace("sgclaw-g1e-expansion-package"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g1e_light_enrichment_expansion"), scene_id: "g1e-light-enrichment-expansion".to_string(), scene_name: "G1-E Light Enrichment Expansion".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::SingleRequestEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert!(generated_report .main_request .as_ref() .and_then(|request| request.api_endpoint.as_ref()) .map(|endpoint| endpoint.name.contains("getWkorderAll")) .unwrap_or(false)); assert!(generated_report .enrichment_requests .iter() .any(|request| request.name.contains("queryMeterInfo"))); let merge_plan = generated_report .merge_plan .as_ref() .expect("expected g1e merge plan"); assert!(merge_plan.join_keys.iter().any(|key| key == "wkOrderNo")); assert!(merge_plan .aggregate_rules .iter() .any(|rule| rule == "group_by:countyCodeName")); assert!(merge_plan .output_columns .iter() .any(|(field, _)| field == "meterCapacityThisMonth")); assert!(generated_report .readiness .gates .iter() .any(|gate| { gate.name == "g1e_scope_compatible" && gate.passed })); } #[test] fn generator_writes_g1e_light_enrichment_additional_package() { let output_root = temp_workspace("sgclaw-g1e-additional-package"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g1e_light_enrichment_additional"), scene_id: "g1e-light-enrichment-additional".to_string(), scene_name: "G1-E Light Enrichment Additional".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::SingleRequestEnrichment ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert!(generated_report .main_request .as_ref() .and_then(|request| request.api_endpoint.as_ref()) .map(|endpoint| endpoint.name.contains("getWkorderAll")) .unwrap_or(false)); assert!(generated_report .enrichment_requests .iter() .any(|request| request.name.contains("queryBusAcpt"))); let merge_plan = generated_report .merge_plan .as_ref() .expect("expected g1e additional merge plan"); assert!(merge_plan.join_keys.iter().any(|key| key == "wkOrderNo")); assert!(merge_plan .aggregate_rules .iter() .any(|rule| rule == "group_by:countyCodeName")); assert!(merge_plan .output_columns .iter() .any(|(field, _)| field == "batchCapacityThisMonth")); assert!(generated_report .readiness .gates .iter() .any(|gate| { gate.name == "g1e_scope_compatible" && gate.passed })); } #[test] fn analyzer_recovers_g1e_output_columns_from_cols_push_and_wide_title_list() { let quality_facts = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/业扩报装质量评价体系", )) .unwrap(); assert_eq!( quality_facts.workflow_archetype, WorkflowArchetype::SingleRequestEnrichment ); assert!(quality_facts .g1e_output_columns .iter() .any(|(field, _)| field == "dyfjmZs")); assert!(quality_facts .g1e_output_columns .iter() .any(|(field, _)| field == "gdfndfZs")); let install_facts = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/用电报装信息统计列表", )) .unwrap(); assert_eq!( install_facts.workflow_archetype, WorkflowArchetype::SingleRequestEnrichment ); assert!(install_facts .g1e_output_columns .iter() .any(|(field, _)| field == "wkOrderNo")); assert!(install_facts .g1e_output_columns .iter() .any(|(field, _)| field == "appCtrtCap")); } #[test] fn generator_writes_g2_weekly_single_mode_package() { let output_root = temp_workspace("sgclaw-scene-generator-g2-weekly-single"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g2_weekly_single_mode"), scene_id: "g2-weekly-single".to_string(), scene_name: "G2 Weekly Single".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generation_report = fs::read_to_string( output_root.join("skills/g2-weekly-single/references/generation-report.json"), ) .unwrap(); assert!(generation_report.contains("\"workflowArchetype\": \"multi_mode_request\"")); assert!(generation_report.contains("\"name\": \"week\"")); assert!(generation_report.contains("\"LINE_LOSS_RATE\"")); assert!(generation_report.contains("\"g2_request_contract_complete\"")); assert!(generation_report.contains("\"g2_response_contract_complete\"")); } #[test] fn generator_writes_g2_mixed_linked_workflow_package() { let output_root = temp_workspace("sgclaw-scene-generator-g2-mixed-linked"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g2_mixed_linked_workflow"), scene_id: "g2-mixed-linked".to_string(), scene_name: "G2 Mixed Linked".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generation_report = fs::read_to_string( output_root.join("skills/g2-mixed-linked/references/generation-report.json"), ) .unwrap(); assert!(generation_report.contains("\"workflowArchetype\": \"multi_mode_request\"")); assert!(generation_report.contains("\"name\": \"primary\"")); assert!(generation_report.contains("\"TG_NO\"")); assert!(generation_report.contains("\"g2_request_contract_complete\"")); assert!(generation_report.contains("\"g2_response_contract_complete\"")); } #[test] fn generator_writes_g2_comparison_crosscheck_package() { let output_root = temp_workspace("sgclaw-scene-generator-g2-comparison"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g2_comparison_crosscheck"), scene_id: "g2-comparison".to_string(), scene_name: "G2 Comparison".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generation_report = fs::read_to_string( output_root.join("skills/g2-comparison/references/generation-report.json"), ) .unwrap(); assert!(generation_report.contains("\"workflowArchetype\": \"multi_mode_request\"")); assert!(generation_report.contains("\"name\": \"comparison\"")); assert!(generation_report.contains("\"TG_NO\"")); assert!(generation_report.contains("\"consno\"")); assert!(generation_report.contains("\"g2_request_contract_complete\"")); assert!(generation_report.contains("\"g2_response_contract_complete\"")); } #[test] fn generator_writes_g2_diagnosis_drilldown_package() { let output_root = temp_workspace("sgclaw-scene-generator-g2-diagnosis"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g2_diagnosis_drilldown"), scene_id: "g2-diagnosis".to_string(), scene_name: "G2 Diagnosis".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generation_report = fs::read_to_string( output_root.join("skills/g2-diagnosis/references/generation-report.json"), ) .unwrap(); assert!(generation_report.contains("\"workflowArchetype\": \"multi_mode_request\"")); assert!(generation_report.contains("\"name\": \"diagnosis\"")); assert!(generation_report.contains("\"LOSS_PQ\"")); assert!(generation_report.contains("\"remark\"")); assert!(generation_report.contains("\"g2_request_contract_complete\"")); assert!(generation_report.contains("\"g2_response_contract_complete\"")); } #[test] fn generator_writes_g2_prediction_compute_package() { let output_root = temp_workspace("sgclaw-scene-generator-g2-prediction"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g2_prediction_compute"), scene_id: "g2-prediction".to_string(), scene_name: "G2 Prediction".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generation_report = fs::read_to_string( output_root.join("skills/g2-prediction/references/generation-report.json"), ) .unwrap(); assert!(generation_report.contains("\"workflowArchetype\": \"multi_mode_request\"")); assert!(generation_report.contains("\"name\": \"prediction\"")); assert!(generation_report.contains("\"lineId\"")); assert!(generation_report.contains("\"lineLossRate\"")); assert!(generation_report.contains("\"powerLoss\"")); assert!(generation_report.contains("\"g2_request_contract_complete\"")); assert!(generation_report.contains("\"g2_response_contract_complete\"")); } #[test] fn generator_blocks_incomplete_paginated_enrichment_workflow() { let output_root = temp_workspace("sgclaw-scene-generator-paginated-incomplete"); let mut scene_ir = build_paginated_scene_ir(); scene_ir .workflow_steps .retain(|step| step.step_type != "filter" && step.step_type != "export"); let error = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/paginated_enrichment"), scene_id: "paginated-scene-incomplete".to_string(), scene_name: "分页补数场景-不完整".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .expect_err("expected incomplete paginated workflow to be blocked"); assert!(error .to_string() .contains("workflow evidence is incomplete for archetype paginated_enrichment")); let report_path = output_root.join("skills/paginated-scene-incomplete/references/generation-report.json"); let report: serde_json::Value = serde_json::from_str(&fs::read_to_string(report_path).unwrap()).unwrap(); assert_eq!(report["generationStatus"], "fail-closed"); assert_eq!(report["failureStage"], "readiness-before-report"); assert_eq!( report["contractSnapshot"]["workflowArchetype"], "paginated_enrichment" ); assert!(report["contractSnapshot"]["mainRequest"].is_object()); assert!(report["contractSnapshot"]["paginationPlan"].is_object()); assert!(report["contractSnapshot"]["enrichmentRequests"].is_array()); assert!(report["contractSnapshot"]["joinKeys"].is_array()); } #[test] fn generator_blocks_incomplete_paginated_response_contract() { let output_root = temp_workspace("sgclaw-scene-generator-paginated-response-incomplete"); let mut scene_ir = build_paginated_scene_ir(); scene_ir.response_path = String::new(); let error = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/paginated_enrichment"), scene_id: "paginated-scene-response-incomplete".to_string(), scene_name: "分页补数场景-响应不完整".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .expect_err("expected incomplete paginated response contract to be blocked"); assert!(error .to_string() .contains("workflow evidence is incomplete for archetype paginated_enrichment")); } #[test] fn generator_blocks_paginated_enrichment_without_join_keys() { let output_root = temp_workspace("sgclaw-scene-generator-paginated-join-missing"); let mut scene_ir = build_paginated_scene_ir(); scene_ir.join_keys.clear(); let error = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/paginated_enrichment"), scene_id: "paginated-scene-join-missing".to_string(), scene_name: "鍒嗛〉琛ユ暟鍦烘櫙-鍏宠仈閿己澶?".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .expect_err("expected paginated enrichment without join keys to be blocked"); assert!(error .to_string() .contains("workflow evidence is incomplete for archetype paginated_enrichment")); } #[test] fn generator_writes_g6_host_bridge_workflow_package() { let output_root = temp_workspace("sgclaw-g6-host-bridge"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g6_host_bridge_workflow"), scene_id: "g6-host-bridge".to_string(), scene_name: "G6 Host Bridge Workflow".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); let browser_script = fs::read_to_string(skill_root.join("scripts/collect_g6_host_bridge.js")).unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::HostBridgeWorkflow ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g6_host_bridge_detected" && gate.passed)); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g6_fail_closed" && gate.passed)); assert!(browser_script.contains("const WORKFLOW_STEPS =")); assert!(browser_script.contains("host_bridge_workflow")); assert!(browser_script.contains("invokeHostBridge")); assert!(browser_script.contains("queryCallbackEndpoint")); } #[test] fn generator_writes_fixed_real_g6_sample_as_host_bridge_workflow() { let output_root = temp_workspace("sgclaw-g6-real-host-bridge"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from( "D:/desk/智能体资料/全量业务场景/一平台场景/电能表现场检验完成率指标报表", ), scene_id: "g6-real-meter-inspection-completion-test".to_string(), scene_name: "电能表现场检验完成率指标报表".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::HostBridgeWorkflow ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g6_host_bridge_detected" && gate.passed)); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g6_fail_closed" && gate.passed)); } #[test] fn generator_blocks_incomplete_g6_host_bridge_contract() { let output_root = temp_workspace("sgclaw-g6-host-bridge-incomplete"); let mut scene_ir = SceneIr { scene_id: "g6-host-bridge-incomplete".to_string(), scene_id_diagnostics: SceneIdDiagnosticsIr::default(), scene_name: "G6 Host Bridge Incomplete".to_string(), scene_kind: "report_collection".to_string(), workflow_archetype: Some(WorkflowArchetype::HostBridgeWorkflow), bootstrap: BootstrapIr { expected_domain: "yx.gscc.com.cn".to_string(), target_url: "http://yx.gscc.com.cn".to_string(), app_entry_url: "http://yx.gscc.com.cn".to_string(), module_route_url: String::new(), target_url_kind: Some("runtime_context".to_string()), requires_target_page: true, page_title_keywords: vec!["G6".to_string()], source: Some("test".to_string()), }, params: Vec::new(), modes: Vec::new(), default_mode: None, mode_switch_field: None, workflow_steps: Vec::new(), workflow_evidence: WorkflowEvidenceIr::default(), main_request: None, pagination_plan: None, enrichment_requests: Vec::new(), join_keys: Vec::new(), merge_or_dedupe_rules: Vec::new(), export_plan: None, merge_plan: None, request_template: serde_json::Value::Null, response_path: String::new(), normalize_rules: None, artifact_contract: ArtifactContractIr::default(), validation_hints: ValidationHintsIr::default(), evidence: Vec::new(), readiness: ReadinessIr::default(), api_endpoints: Vec::new(), runtime_dependencies: Vec::new(), static_params: Default::default(), column_defs: Vec::new(), confidence: 0.7, uncertainties: Vec::new(), }; scene_ir.workflow_steps.push(WorkflowStepIr { step_type: "host_bridge".to_string(), entry: Some("sgBrowerserJsAjax2".to_string()), ..WorkflowStepIr::default() }); let error = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g6_host_bridge_workflow"), scene_id: "g6-host-bridge-incomplete".to_string(), scene_name: "G6 Host Bridge Incomplete".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .expect_err("expected incomplete G6 host bridge workflow to fail closed"); assert!(error .to_string() .contains("workflow evidence is incomplete for archetype host_bridge_workflow")); } #[test] fn generator_writes_g7_multi_endpoint_inventory_package() { let output_root = temp_workspace("sgclaw-g7-inventory"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g7_multi_endpoint_inventory"), scene_id: "g7-inventory".to_string(), scene_name: "G7 Multi Endpoint Inventory".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); let browser_script = fs::read_to_string(skill_root.join("scripts/collect_g7_inventory.js")).unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::MultiEndpointInventory ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g7_inventory_endpoints_detected" && gate.passed)); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g7_fail_closed" && gate.passed)); assert!(browser_script.contains("multi_endpoint_inventory")); assert!(browser_script.contains("inventoryEndpoints")); assert!(browser_script.contains("aggregateEntry")); } #[test] fn generator_blocks_incomplete_g7_inventory_contract() { let output_root = temp_workspace("sgclaw-g7-inventory-incomplete"); let scene_ir = SceneIr { scene_id: "g7-inventory-incomplete".to_string(), scene_id_diagnostics: SceneIdDiagnosticsIr::default(), scene_name: "G7 Inventory Incomplete".to_string(), scene_kind: "report_collection".to_string(), workflow_archetype: Some(WorkflowArchetype::MultiEndpointInventory), bootstrap: BootstrapIr { expected_domain: "yx.gscc.com.cn".to_string(), target_url: "http://yx.gscc.com.cn/asset".to_string(), app_entry_url: "http://yx.gscc.com.cn/asset".to_string(), module_route_url: String::new(), target_url_kind: Some("runtime_context".to_string()), requires_target_page: true, page_title_keywords: vec!["G7".to_string()], source: Some("test".to_string()), }, params: Vec::new(), modes: Vec::new(), default_mode: None, mode_switch_field: None, workflow_steps: vec![ WorkflowStepIr { step_type: "inventory_request".to_string(), endpoint: Some("assetStatsQueryMeter".to_string()), ..WorkflowStepIr::default() }, WorkflowStepIr { step_type: "transform".to_string(), entry: Some("aggregateInventory".to_string()), ..WorkflowStepIr::default() }, ], workflow_evidence: WorkflowEvidenceIr::default(), main_request: None, pagination_plan: None, enrichment_requests: Vec::new(), join_keys: Vec::new(), merge_or_dedupe_rules: Vec::new(), export_plan: None, merge_plan: None, request_template: serde_json::Value::Null, response_path: String::new(), normalize_rules: None, artifact_contract: ArtifactContractIr::default(), validation_hints: ValidationHintsIr::default(), evidence: Vec::new(), readiness: ReadinessIr::default(), api_endpoints: vec![ApiEndpointIr { name: "assetStatsQueryMeter".to_string(), url: "http://yx.gscc.com.cn/asset/assetStatsQueryMeter".to_string(), method: "POST".to_string(), content_type: Some("application/json".to_string()), description: None, }], runtime_dependencies: Vec::new(), static_params: Default::default(), column_defs: Vec::new(), confidence: 0.7, uncertainties: Vec::new(), }; let error = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g7_multi_endpoint_inventory"), scene_id: "g7-inventory-incomplete".to_string(), scene_name: "G7 Inventory Incomplete".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .expect_err("expected incomplete G7 inventory workflow to fail closed"); assert!(error .to_string() .contains("workflow evidence is incomplete for archetype multi_endpoint_inventory")); } #[test] fn generator_writes_g8_local_doc_pipeline_package() { let output_root = temp_workspace("sgclaw-g8-doc-pipeline"); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g8_local_doc_pipeline"), scene_id: "g8-doc-pipeline".to_string(), scene_name: "G8 Local Document Pipeline".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); let browser_script = fs::read_to_string(skill_root.join("scripts/collect_g8_doc_pipeline.js")).unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::LocalDocPipeline ); assert!(matches!( generated_report.readiness.level.as_str(), "A" | "B" )); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g8_local_doc_pipeline_detected" && gate.passed)); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g8_fail_closed" && gate.passed)); assert!(browser_script.contains("local_doc_pipeline")); assert!(browser_script.contains("localDataEndpoints")); assert!(browser_script.contains("docExportEntry")); } #[test] fn generator_recovers_sweep_012_report_log_doc_pipeline_package() { let source_dir = PathBuf::from("D:/desk/智能体资料/全量业务场景/一平台场景/业扩报装管理制度"); if !source_dir.exists() { eprintln!("skipping sweep-012 real source regression: source dir not found"); return; } let facts = extract_deterministic_scene_facts(source_dir.as_path()).unwrap(); assert_eq!( facts.workflow_archetype, WorkflowArchetype::LocalDocPipeline ); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action == "reportLogQuery")); assert!(facts .local_doc_pipeline_actions .iter() .any(|action| action == "docExport")); let skill_root = generate_scene_package(GenerateSceneRequest { source_dir, scene_id: "sweep-012-scene".to_string(), scene_name: "业扩报装管理制度".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: temp_workspace("sgclaw-sweep-012-recovery"), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::LocalDocPipeline ); assert_eq!(generated_report.readiness.level, "A"); assert!(skill_root.join("SKILL.toml").exists()); assert!(skill_root.join("scene.toml").exists()); } #[test] fn generator_writes_g3_g8_mixed_boundary_fixture_as_paginated_enrichment() { let request = GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g3_g8_mixed_boundary"), scene_id: "g3-g8-mixed-boundary".to_string(), scene_name: "G3 G8 Mixed Boundary".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: temp_workspace("sgclaw-g3-g8-mixed-boundary"), lessons_path: None, scene_info_json: None, scene_ir_json: None, }; let analysis = analyze_scene_source(request.source_dir.as_path()).unwrap(); let facts = extract_deterministic_scene_facts(request.source_dir.as_path()).unwrap(); let scene_ir = resolve_scene_ir_for_test(&request, &analysis, &facts); let readiness = compute_readiness_for_test(&scene_ir, &facts); assert_eq!( scene_ir.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert_eq!(readiness.level, "A"); let skill_root = generate_scene_package(request).unwrap(); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::PaginatedEnrichment ); assert!(generated_report .main_request .as_ref() .and_then(|request| request.api_endpoint.as_ref()) .map(|endpoint| endpoint.name.contains("queryHisS95598WkstGrid") || endpoint.name.contains("newQueryS95598WkstGrid")) .unwrap_or(false)); assert!(generated_report .workflow_steps .iter() .any(|step| step.step_type == "host_bridge")); } #[test] fn generator_blocks_incomplete_g8_local_doc_pipeline_contract() { let output_root = temp_workspace("sgclaw-g8-doc-pipeline-incomplete"); let scene_ir = SceneIr { scene_id: "g8-doc-pipeline-incomplete".to_string(), scene_id_diagnostics: SceneIdDiagnosticsIr::default(), scene_name: "G8 Doc Pipeline Incomplete".to_string(), scene_kind: "report_collection".to_string(), workflow_archetype: Some(WorkflowArchetype::LocalDocPipeline), bootstrap: BootstrapIr { expected_domain: "south.95598.sgcc.com.cn".to_string(), target_url: "http://south.95598.sgcc.com.cn/report".to_string(), app_entry_url: "http://south.95598.sgcc.com.cn/report".to_string(), module_route_url: String::new(), target_url_kind: Some("runtime_context".to_string()), requires_target_page: true, page_title_keywords: vec!["G8".to_string()], source: Some("test".to_string()), }, params: Vec::new(), modes: Vec::new(), default_mode: None, mode_switch_field: None, workflow_steps: vec![ WorkflowStepIr { step_type: "local_doc_pipeline".to_string(), entry: Some("selectData".to_string()), ..WorkflowStepIr::default() }, WorkflowStepIr { step_type: "sql_query".to_string(), entry: Some("definedSqlQuery".to_string()), ..WorkflowStepIr::default() }, ], workflow_evidence: WorkflowEvidenceIr::default(), main_request: None, pagination_plan: None, enrichment_requests: Vec::new(), join_keys: Vec::new(), merge_or_dedupe_rules: Vec::new(), export_plan: None, merge_plan: None, request_template: serde_json::Value::Null, response_path: String::new(), normalize_rules: None, artifact_contract: ArtifactContractIr::default(), validation_hints: ValidationHintsIr::default(), evidence: Vec::new(), readiness: ReadinessIr::default(), api_endpoints: vec![ApiEndpointIr { name: "selectData".to_string(), url: "http://localhost:13313/configServices/selectData".to_string(), method: "POST".to_string(), content_type: Some("application/json".to_string()), description: None, }], runtime_dependencies: Vec::new(), static_params: Default::default(), column_defs: Vec::new(), confidence: 0.7, uncertainties: Vec::new(), }; let error = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g8_local_doc_pipeline"), scene_id: "g8-doc-pipeline-incomplete".to_string(), scene_name: "G8 Doc Pipeline Incomplete".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .expect_err("expected incomplete G8 local document pipeline to fail closed"); assert!(error .to_string() .contains("workflow evidence is incomplete for archetype local_doc_pipeline")); } #[test] fn generator_accepts_g8_local_doc_select_data_contract() { let output_root = temp_workspace("sgclaw-g8-doc-pipeline-select-data"); let scene_ir = SceneIr { scene_id: "g8-doc-pipeline-select-data".to_string(), scene_id_diagnostics: SceneIdDiagnosticsIr::default(), scene_name: "G8 Doc Pipeline Select Data".to_string(), scene_kind: "report_collection".to_string(), workflow_archetype: Some(WorkflowArchetype::LocalDocPipeline), bootstrap: BootstrapIr { expected_domain: "localhost:13313".to_string(), target_url: "http://localhost:13313/report".to_string(), app_entry_url: "http://localhost:13313/report".to_string(), module_route_url: String::new(), target_url_kind: Some("runtime_context".to_string()), requires_target_page: true, page_title_keywords: vec!["G8".to_string()], source: Some("test".to_string()), }, params: Vec::new(), modes: Vec::new(), default_mode: None, mode_switch_field: None, workflow_steps: vec![ WorkflowStepIr { step_type: "doc_export".to_string(), entry: Some("docExport".to_string()), ..WorkflowStepIr::default() }, WorkflowStepIr { step_type: "local_doc_pipeline".to_string(), entry: Some("configServices/selectData".to_string()), ..WorkflowStepIr::default() }, ], workflow_evidence: WorkflowEvidenceIr::default(), main_request: None, pagination_plan: None, enrichment_requests: Vec::new(), join_keys: Vec::new(), merge_or_dedupe_rules: Vec::new(), export_plan: None, merge_plan: None, request_template: serde_json::Value::Null, response_path: "content".to_string(), normalize_rules: None, artifact_contract: ArtifactContractIr::default(), validation_hints: ValidationHintsIr::default(), evidence: Vec::new(), readiness: ReadinessIr::default(), api_endpoints: vec![ApiEndpointIr { name: "selectData".to_string(), url: "http://localhost:13313/configServices/selectData".to_string(), method: "POST".to_string(), content_type: Some("application/json".to_string()), description: None, }], runtime_dependencies: Vec::new(), static_params: Default::default(), column_defs: Vec::new(), confidence: 0.7, uncertainties: Vec::new(), }; let skill_root = generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/g8_local_doc_pipeline"), scene_id: "g8-doc-pipeline-select-data".to_string(), scene_name: "G8 Doc Pipeline Select Data".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root, lessons_path: None, scene_info_json: None, scene_ir_json: Some(scene_ir), }) .expect("selectData local-doc contract should compile"); let generated_report: SceneIr = serde_json::from_str( &fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(), ) .unwrap(); assert_eq!( generated_report.workflow_archetype(), WorkflowArchetype::LocalDocPipeline ); assert_eq!(generated_report.readiness.level, "A"); assert!(generated_report .readiness .gates .iter() .any(|gate| gate.name == "g8_fail_closed" && gate.passed)); } #[test] fn paginated_enrichment_readiness_marks_join_key_missing_taxonomy() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment", )) .unwrap(); let mut scene_ir = build_paginated_scene_ir(); scene_ir.join_keys.clear(); let readiness = compute_readiness_for_test(&scene_ir, &facts); assert_eq!(readiness.level, "C"); assert!(readiness .missing_pieces .iter() .any(|item| item == "g3_join_keys")); assert!(readiness .risks .iter() .any(|item| item == "g3_join_keys_missing")); assert!(readiness.gates.iter().any(|gate| { gate.name == "g3_join_key_resolved" && !gate.passed && gate.reason.as_deref() == Some("g3_join_keys") })); } #[test] fn paginated_enrichment_readiness_marks_pagination_incomplete_taxonomy() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment", )) .unwrap(); let mut scene_ir = build_paginated_scene_ir(); scene_ir .pagination_plan .as_mut() .unwrap() .termination_rule .clear(); let readiness = compute_readiness_for_test(&scene_ir, &facts); assert_eq!(readiness.level, "C"); assert!(readiness .missing_pieces .iter() .any(|item| item == "g3_pagination_contract")); assert!(readiness .risks .iter() .any(|item| item == "g3_pagination_contract_incomplete")); assert!(readiness.gates.iter().any(|gate| { gate.name == "g3_pagination_contract_complete" && !gate.passed && gate.reason.as_deref() == Some("g3_pagination_contract") })); } #[test] fn paginated_enrichment_readiness_marks_export_only_without_business_chain_taxonomy() { let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/paginated_enrichment", )) .unwrap(); let mut scene_ir = build_paginated_scene_ir(); scene_ir.main_request = None; scene_ir.enrichment_requests.clear(); scene_ir.workflow_steps = vec![WorkflowStepIr { step_type: "export".to_string(), entry: Some("exportExcel".to_string()), ..WorkflowStepIr::default() }]; scene_ir.workflow_evidence.request_entries.clear(); scene_ir.workflow_evidence.secondary_request_entries.clear(); let readiness = compute_readiness_for_test(&scene_ir, &facts); assert_eq!(readiness.level, "C"); assert!(readiness .missing_pieces .iter() .any(|item| item == "g3_main_request")); assert!(readiness .missing_pieces .iter() .any(|item| item == "g3_enrichment_contract")); assert!(readiness.gates.iter().any(|gate| { gate.name == "g3_main_request_resolved" && !gate.passed && gate.reason.as_deref() == Some("g3_main_request") })); assert!(readiness.gates.iter().any(|gate| { gate.name == "g3_enrichment_contract_complete" && !gate.passed && gate.reason.as_deref() == Some("g3_enrichment_contract") })); } #[test] fn analyzer_recovers_g3_residual_export_fn_and_operational_join_keys() { let g3_monthly = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/95598供电服务月报", )) .unwrap(); assert!( g3_monthly .export_methods .iter() .any(|method| method == "excelExportFn"), "expected export method with Fn suffix to be detected; exports={:?}", g3_monthly.export_methods ); let g3_repair = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/故障报修工单信息统计表", )) .unwrap(); assert!( g3_repair .g3_join_key_hints .iter() .any(|key| key.eq_ignore_ascii_case("userId") || key.eq_ignore_ascii_case("reportNo")), "expected operational join key hints to be detected; hints={:?}", g3_repair.g3_join_key_hints ); let g3_patrol = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/巡视计划完成情况自动检索", )) .unwrap(); assert!( g3_patrol .g3_join_key_hints .iter() .any(|key| key.eq_ignore_ascii_case("orgId") || key.eq_ignore_ascii_case("userID")), "expected org/user join key hints to be detected; hints={:?}", g3_patrol.g3_join_key_hints ); let g3_transformer = extract_deterministic_scene_facts(Path::new( "D:/desk/智能体资料/全量业务场景/一平台场景/输变电设备运行分析报告", )) .unwrap(); assert!( g3_transformer .export_methods .iter() .any(|method| method == "aSaveExcelFile"), "expected ExcelFile export method to be detected; exports={:?}", g3_transformer.export_methods ); } #[test] fn generator_emits_monitoring_template() { let output_root = temp_workspace("sgclaw-monitoring-generator"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/monitoring"), scene_id: "sample-monitor-scene".to_string(), scene_name: "示例监测场景".to_string(), scene_kind: Some(SceneKind::Monitoring), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let skill_root = output_root.join("skills/sample-monitor-scene"); let generated_manifest = fs::read_to_string(skill_root.join("scene.toml")).unwrap(); assert!(generated_manifest.contains("category = \"monitoring\"")); } #[test] fn generator_preserves_localhost_dependency_as_host_runtime_evidence() { let analysis = analyze_scene_source(Path::new( "tests/fixtures/generated_scene/bootstrap_localhost_pollution", )) .unwrap(); let facts = extract_deterministic_scene_facts(Path::new( "tests/fixtures/generated_scene/bootstrap_localhost_pollution", )) .unwrap(); assert_eq!( analysis.bootstrap.expected_domain.as_deref(), Some("yx.gs.sgcc.com.cn") ); assert!(facts .localhost_dependencies .iter() .any(|item| item.contains("localhost:13313"))); assert_ne!( facts.workflow_archetype, WorkflowArchetype::LocalDocPipeline ); return; let output_root = temp_workspace("sgclaw-scene-generator-localhost-evidence"); generate_scene_package(GenerateSceneRequest { source_dir: PathBuf::from("tests/fixtures/generated_scene/bootstrap_localhost_pollution"), scene_id: "localhost-evidence-scene".to_string(), scene_name: "宿主依赖证据场景".to_string(), scene_kind: Some(SceneKind::ReportCollection), target_url: None, output_root: output_root.clone(), lessons_path: None, scene_info_json: None, scene_ir_json: None, }) .unwrap(); let skill_root = output_root.join("skills/localhost-evidence-scene"); let generation_report = fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(); assert!(generation_report.contains("\"localhost_dependency_candidate\"")); assert!(generation_report.contains("localhost:13313")); assert!(generation_report.contains("\"expectedDomain\": \"yx.gs.sgcc.com.cn\"")); } fn build_multi_mode_scene_ir() -> SceneIr { SceneIr { scene_id: "multi-mode-scene".to_string(), scene_id_diagnostics: SceneIdDiagnosticsIr::default(), scene_name: "多模式场景".to_string(), scene_kind: "report_collection".to_string(), workflow_archetype: Some(WorkflowArchetype::MultiModeRequest), bootstrap: BootstrapIr { expected_domain: "20.76.57.61".to_string(), target_url: "http://20.76.57.61:18080/gsllys".to_string(), app_entry_url: "http://20.76.57.61:18080/gsllys".to_string(), module_route_url: "http://20.76.57.61:18080/gsllys/monthReport".to_string(), target_url_kind: Some("runtime_context".to_string()), requires_target_page: true, page_title_keywords: vec!["多模式".to_string()], source: Some("test".to_string()), }, params: Vec::new(), modes: vec![ ModeIr { name: "month".to_string(), label: Some("month".to_string()), condition: Some(ModeConditionIr { field: "period_mode".to_string(), operator: "equals".to_string(), value: serde_json::Value::String("month".to_string()), }), api_endpoint: Some(ApiEndpointIr { name: "monthReport".to_string(), url: "http://20.76.57.61:18080/gsllys/monthReport".to_string(), method: "POST".to_string(), content_type: Some("application/x-www-form-urlencoded".to_string()), description: None, }), column_defs: vec![("ORG_NAME".to_string(), "供电单位".to_string())], request_template: serde_json::json!({ "orgno":"${args.org_code}", "fdate":"${args.fdate}", "tjzq":"month" }), request_field_mappings: Vec::new(), normalize_rules: None, response_path: "content".to_string(), }, ModeIr { name: "week".to_string(), label: Some("week".to_string()), condition: Some(ModeConditionIr { field: "period_mode".to_string(), operator: "equals".to_string(), value: serde_json::Value::String("week".to_string()), }), api_endpoint: Some(ApiEndpointIr { name: "weekReport".to_string(), url: "http://20.76.57.61:18080/gsllys/weekReport".to_string(), method: "POST".to_string(), content_type: Some("application/x-www-form-urlencoded".to_string()), description: None, }), column_defs: vec![("ORG_NAME".to_string(), "供电单位".to_string())], request_template: serde_json::json!({ "orgno":"${args.org_code}", "weekSfdate":"${args.weekSfdate}", "weekEfdate":"${args.weekEfdate}", "tjzq":"week" }), request_field_mappings: Vec::new(), normalize_rules: None, response_path: "content".to_string(), }, ], default_mode: Some("month".to_string()), mode_switch_field: Some("period_mode".to_string()), workflow_steps: vec![ WorkflowStepIr { step_type: "request".to_string(), description: Some("select mode".to_string()), ..WorkflowStepIr::default() }, WorkflowStepIr { step_type: "transform".to_string(), description: Some("normalize rows".to_string()), ..WorkflowStepIr::default() }, ], workflow_evidence: WorkflowEvidenceIr::default(), main_request: None, pagination_plan: None, enrichment_requests: Vec::new(), join_keys: Vec::new(), merge_or_dedupe_rules: Vec::new(), export_plan: None, merge_plan: None, request_template: serde_json::Value::Null, response_path: "content".to_string(), normalize_rules: None, artifact_contract: ArtifactContractIr::default(), validation_hints: ValidationHintsIr::default(), evidence: Vec::new(), readiness: ReadinessIr::default(), api_endpoints: Vec::new(), runtime_dependencies: Vec::new(), static_params: Default::default(), column_defs: Vec::new(), confidence: 0.9, uncertainties: Vec::new(), } } fn build_paginated_scene_ir() -> SceneIr { SceneIr { scene_id: "paginated-scene".to_string(), scene_id_diagnostics: SceneIdDiagnosticsIr::default(), scene_name: "分页补数场景".to_string(), scene_kind: "report_collection".to_string(), workflow_archetype: Some(WorkflowArchetype::PaginatedEnrichment), bootstrap: BootstrapIr { expected_domain: "yx.gs.sgcc.com.cn".to_string(), target_url: "http://yx.gs.sgcc.com.cn".to_string(), app_entry_url: "http://yx.gs.sgcc.com.cn".to_string(), module_route_url: String::new(), target_url_kind: Some("runtime_context".to_string()), requires_target_page: true, page_title_keywords: vec!["零度户".to_string()], source: Some("test".to_string()), }, params: Vec::new(), modes: Vec::new(), default_mode: None, mode_switch_field: None, workflow_steps: vec![ WorkflowStepIr { step_type: "request".to_string(), entry: Some("getUserList".to_string()), endpoint: Some("userList".to_string()), ..WorkflowStepIr::default() }, WorkflowStepIr { step_type: "paginate".to_string(), entry: Some("getUserList".to_string()), ..WorkflowStepIr::default() }, WorkflowStepIr { step_type: "secondary_request".to_string(), entry: Some("getUserCharges".to_string()), ..WorkflowStepIr::default() }, WorkflowStepIr { step_type: "filter".to_string(), expr: Some("row.charge !== 0".to_string()), ..WorkflowStepIr::default() }, WorkflowStepIr { step_type: "export".to_string(), entry: Some("exportExcel".to_string()), ..WorkflowStepIr::default() }, ], workflow_evidence: WorkflowEvidenceIr { request_entries: vec!["getUserList".to_string()], pagination_fields: vec!["page".to_string(), "pageSize".to_string()], secondary_request_entries: vec!["getUserCharges".to_string()], post_process_steps: vec!["filter".to_string(), "export".to_string()], }, main_request: Some(sgclaw::generated_scene::ir::MainRequestIr { api_endpoint: Some(ApiEndpointIr { name: "userList".to_string(), url: "http://yx.gs.sgcc.com.cn/marketing/userList".to_string(), method: "POST".to_string(), content_type: Some("application/json".to_string()), description: Some("g3_main_request".to_string()), }), request_template: serde_json::json!({"page":"${args.page}","pageSize":"${args.page_size}"}), response_path: "rows".to_string(), column_defs: Vec::new(), }), pagination_plan: Some(sgclaw::generated_scene::ir::PaginationPlanIr { page_field: "page".to_string(), page_size_field: Some("pageSize".to_string()), start_page: Some(1), termination_rule: "stop_when_page_rows_empty".to_string(), }), enrichment_requests: vec![sgclaw::generated_scene::ir::EnrichmentRequestIr { name: "userCharges".to_string(), api_endpoint: Some(ApiEndpointIr { name: "userCharges".to_string(), url: "http://yx.gs.sgcc.com.cn/marketing/userCharges".to_string(), method: "POST".to_string(), content_type: Some("application/json".to_string()), description: Some("g3_enrichment_request".to_string()), }), param_bindings: serde_json::Map::new(), response_path: "rows".to_string(), consumed_fields: vec!["custNo".to_string()], }], join_keys: vec!["custNo".to_string()], merge_or_dedupe_rules: vec!["dedupe:custNo".to_string(), "aggregate:charge".to_string()], export_plan: Some(sgclaw::generated_scene::ir::ExportPlanIr { entry: Some("exportExcel".to_string()), artifact_type: Some("report-artifact".to_string()), depends_on_host_bridge: false, }), merge_plan: None, request_template: serde_json::Value::Null, response_path: "rows".to_string(), normalize_rules: None, artifact_contract: ArtifactContractIr::default(), validation_hints: ValidationHintsIr::default(), evidence: Vec::new(), readiness: ReadinessIr::default(), api_endpoints: vec![ ApiEndpointIr { name: "userList".to_string(), url: "http://yx.gs.sgcc.com.cn/marketing/userList".to_string(), method: "POST".to_string(), content_type: Some("application/json".to_string()), description: None, }, ApiEndpointIr { name: "userCharges".to_string(), url: "http://yx.gs.sgcc.com.cn/marketing/userCharges".to_string(), method: "POST".to_string(), content_type: Some("application/json".to_string()), description: None, }, ], runtime_dependencies: Vec::new(), static_params: Default::default(), column_defs: Vec::new(), confidence: 0.9, uncertainties: Vec::new(), } } fn temp_workspace(prefix: &str) -> PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); let path = std::env::temp_dir().join(format!("{prefix}-{nanos}")); fs::create_dir_all(&path).unwrap(); path }