Files
claw/tests/scene_generator_test.rs

4190 lines
148 KiB
Rust
Raw Blame History

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
}