4190 lines
148 KiB
Rust
4190 lines
148 KiB
Rust
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_monitoring_action_detect_preview_package,
|
||
generate_scene_package, generate_scheduled_monitoring_action_skill_package,
|
||
resolve_scene_ir_for_test, GenerateMonitoringActionPreviewRequest,
|
||
GenerateScheduledMonitoringActionSkillRequest, 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涓嶅畬鏁村満鏅<EFBFBD>".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::<toml::Value>(&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(),
|
||
monitoring_action_workflow: None,
|
||
};
|
||
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(),
|
||
monitoring_action_workflow: None,
|
||
};
|
||
|
||
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(),
|
||
monitoring_action_workflow: None,
|
||
};
|
||
|
||
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(),
|
||
monitoring_action_workflow: None,
|
||
};
|
||
|
||
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_emits_monitoring_action_detect_preview_anchor_package() {
|
||
let output_root = temp_workspace("sgclaw-monitoring-action-detect-preview");
|
||
let skill_root =
|
||
generate_monitoring_action_detect_preview_package(GenerateMonitoringActionPreviewRequest {
|
||
scene_id: "command-center-fee-control-monitor".to_string(),
|
||
scene_name: "指挥中心费控异常监测与处置预览".to_string(),
|
||
output_root: output_root.clone(),
|
||
source_evidence_json: PathBuf::from(
|
||
"tests/fixtures/generated_scene/monitoring_action_source_evidence_extraction_2026-04-21.json",
|
||
),
|
||
ir_contract_json: PathBuf::from(
|
||
"tests/fixtures/generated_scene/monitoring_action_ir_contract_2026-04-21.json",
|
||
),
|
||
})
|
||
.unwrap();
|
||
|
||
let scene_toml = fs::read_to_string(skill_root.join("scene.toml")).unwrap();
|
||
assert!(scene_toml.contains("category = \"monitoring\""));
|
||
assert!(scene_toml.contains("suffix = \""));
|
||
assert!(scene_toml.contains("[safety]"));
|
||
assert!(scene_toml.contains("dry_run_default = true"));
|
||
assert!(scene_toml.contains("action_modes_enabled = false"));
|
||
assert!(scene_toml.contains("mode = \"detect_preview\""));
|
||
assert!(scene_toml.contains("repetCtrlSend"));
|
||
|
||
let generation_report =
|
||
fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap();
|
||
let generated_report: SceneIr = serde_json::from_str(&generation_report).unwrap();
|
||
assert_eq!(
|
||
generated_report.workflow_archetype(),
|
||
WorkflowArchetype::MonitoringActionWorkflow
|
||
);
|
||
let monitoring = generated_report
|
||
.monitoring_action_workflow
|
||
.as_ref()
|
||
.expect("expected monitoring action workflow metadata");
|
||
assert_eq!(monitoring.default_mode, "detect_preview");
|
||
assert!(monitoring.side_effect_policy.dry_run_default);
|
||
assert!(monitoring
|
||
.side_effect_policy
|
||
.blocked_call_signatures
|
||
.iter()
|
||
.any(|item| item == "repetCtrlSend"));
|
||
|
||
let script = fs::read_to_string(skill_root.join("scripts/detect_preview.js")).unwrap();
|
||
assert!(script.contains("monitoring_action_workflow"));
|
||
assert!(script.contains("detect_preview"));
|
||
assert!(!script.contains("repetCtrlSend("));
|
||
assert!(!script.contains("mac.sendMessages("));
|
||
assert!(!script.contains("mac.callOutLogin("));
|
||
assert!(!script.contains("mac.audioPlay("));
|
||
assert!(!script.contains("mac.exeTQueue("));
|
||
|
||
let blocked =
|
||
fs::read_to_string(skill_root.join("references/blocked-side-effects.json")).unwrap();
|
||
assert!(blocked.contains("repetCtrlSend"));
|
||
|
||
let test_status = std::process::Command::new("node")
|
||
.arg("detect_preview.test.js")
|
||
.current_dir(skill_root.join("scripts"))
|
||
.status()
|
||
.unwrap();
|
||
assert!(test_status.success());
|
||
}
|
||
|
||
#[test]
|
||
fn generator_emits_scheduled_monitoring_action_skill_package() {
|
||
let output_root = temp_workspace("sgclaw-scheduled-monitoring-action-skill");
|
||
let skill_root = generate_scheduled_monitoring_action_skill_package(
|
||
GenerateScheduledMonitoringActionSkillRequest {
|
||
scene_id: "command-center-fee-control-monitor".to_string(),
|
||
scene_name: "指挥中心费控异常监测".to_string(),
|
||
output_root: output_root.clone(),
|
||
source_evidence_json: PathBuf::from(
|
||
"tests/fixtures/generated_scene/monitoring_action_source_evidence_extraction_2026-04-21.json",
|
||
),
|
||
ir_contract_json: PathBuf::from(
|
||
"tests/fixtures/generated_scene/scheduled_monitoring_action_ir_contract_2026-04-22.json",
|
||
),
|
||
trigger_contract_json: PathBuf::from(
|
||
"tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json",
|
||
),
|
||
},
|
||
)
|
||
.unwrap();
|
||
|
||
for relative in [
|
||
"SKILL.toml",
|
||
"SKILL.md",
|
||
"scene.toml",
|
||
"scripts/detect.js",
|
||
"scripts/decide.js",
|
||
"scripts/action_plan.js",
|
||
"scripts/detect.test.js",
|
||
"references/source-evidence.json",
|
||
"references/workflow-ir.json",
|
||
"references/trigger-contract.json",
|
||
"references/platform-dependencies.json",
|
||
"references/side-effect-policy.json",
|
||
"references/audit-policy.json",
|
||
"references/idempotency-policy.json",
|
||
"references/generation-report.json",
|
||
] {
|
||
assert!(
|
||
skill_root.join(relative).exists(),
|
||
"expected scheduled skill package file {relative}"
|
||
);
|
||
}
|
||
|
||
let scene_toml = fs::read_to_string(skill_root.join("scene.toml")).unwrap();
|
||
toml::from_str::<toml::Value>(&scene_toml).unwrap();
|
||
assert!(scene_toml.contains("kind = \"scheduled_monitoring_action_workflow\""));
|
||
assert!(scene_toml.contains("natural_language_primary = false"));
|
||
assert!(scene_toml.contains("enabled = [\"dry_run\", \"monitor_only\"]"));
|
||
assert!(scene_toml.contains("disabled = [\"active\", \"queue_process\"]"));
|
||
assert!(scene_toml.contains("active_enabled = false"));
|
||
assert!(scene_toml.contains("queue_process_enabled = false"));
|
||
assert!(!scene_toml.contains("[deterministic]"));
|
||
|
||
let skill_toml = fs::read_to_string(skill_root.join("SKILL.toml")).unwrap();
|
||
toml::from_str::<toml::Value>(&skill_toml).unwrap();
|
||
assert!(skill_toml.contains("name = \"detect\""));
|
||
assert!(skill_toml.contains("name = \"decide\""));
|
||
assert!(skill_toml.contains("name = \"action_plan\""));
|
||
assert!(skill_toml.contains("kind = \"scheduled_monitoring_action_workflow\""));
|
||
|
||
let generation_report =
|
||
fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap();
|
||
let report: serde_json::Value = serde_json::from_str(&generation_report).unwrap();
|
||
assert_eq!(report["family"], "scheduled_monitoring_action_workflow");
|
||
assert_eq!(report["naturalLanguagePrimary"], false);
|
||
assert_eq!(report["safety"]["activeEnabled"], false);
|
||
assert_eq!(report["safety"]["queueProcessEnabled"], false);
|
||
|
||
let side_effect_policy =
|
||
fs::read_to_string(skill_root.join("references/side-effect-policy.json")).unwrap();
|
||
let side_effect_policy: serde_json::Value = serde_json::from_str(&side_effect_policy).unwrap();
|
||
assert!(side_effect_policy["blockedCallSignatures"]
|
||
.as_array()
|
||
.unwrap()
|
||
.iter()
|
||
.any(|item| item == "repetCtrlSend"));
|
||
|
||
let enabled_scripts = [
|
||
fs::read_to_string(skill_root.join("scripts/detect.js")).unwrap(),
|
||
fs::read_to_string(skill_root.join("scripts/decide.js")).unwrap(),
|
||
fs::read_to_string(skill_root.join("scripts/action_plan.js")).unwrap(),
|
||
];
|
||
assert!(enabled_scripts[0].contains("platformServiceBaseUrl"));
|
||
assert!(enabled_scripts[0].contains("postViaPageAxios"));
|
||
assert!(enabled_scripts[0].contains("getViaPageAxios"));
|
||
assert!(enabled_scripts[0].contains("EmssLib.dataEncrypt_PUB"));
|
||
assert!(enabled_scripts[0].contains("readStepTraces"));
|
||
assert!(enabled_scripts[0].contains("read_step_timeout_ms"));
|
||
assert!(enabled_scripts[0].contains("scheduled monitoring read step"));
|
||
for script in enabled_scripts {
|
||
for forbidden in [
|
||
"repetCtrlSend(",
|
||
"mac.sendMessages(",
|
||
"mac.callOutLogin(",
|
||
"mac.audioPlay(",
|
||
"mac.exeTQueue(",
|
||
"_this.autoTask(",
|
||
"_this.processQueue(",
|
||
"setDisposeLog(",
|
||
"setMonitorData(",
|
||
"setMonitorLog(",
|
||
"setSendMessageLog(",
|
||
"setAudioPlayLog(",
|
||
] {
|
||
assert!(
|
||
!script.contains(forbidden),
|
||
"enabled scheduled script must not contain forbidden executable call {forbidden}"
|
||
);
|
||
}
|
||
}
|
||
|
||
let test_status = std::process::Command::new("node")
|
||
.arg("detect.test.js")
|
||
.current_dir(skill_root.join("scripts"))
|
||
.status()
|
||
.unwrap();
|
||
assert!(test_status.success());
|
||
}
|
||
|
||
#[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(),
|
||
monitoring_action_workflow: None,
|
||
}
|
||
}
|
||
|
||
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(),
|
||
monitoring_action_workflow: None,
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|