use az_device_contract_codegen::{
generate_bundle, ApiKind, ContractMethod, ContractRenderRequest, ContractService,
ContractTypedItem, ReadReturnKind, RegisterArea, TransportKind, ValueType,
};
#[test]
fn should_generate_symmetric_artifacts_for_rtu_and_tcp() -> Result<(), Box<dyn std::error::Error>> {
let request = ContractRenderRequest {
transports: vec![TransportKind::Rtu, TransportKind::Tcp],
services: vec![
ContractService {
api_kind: ApiKind::StaticRead,
interface_name: "DeviceStaticReadApi".to_string(),
interface_summary: Some("静态信息读取".to_string()),
methods: vec![ContractMethod {
summary: Some("读取设备身份".to_string()),
method_name: "getDeviceIdentity".to_string(),
read_return_kind: Some(ReadReturnKind::Dto),
read_return_type_name: Some("DeviceIdentityRegisters".to_string()),
read_area: Some(RegisterArea::HoldingRegister),
write_area: None,
read_fields: vec![
ContractTypedItem {
name: "macAddress".to_string(),
summary: Some("MAC 地址".to_string()),
value_type: ValueType::String,
},
ContractTypedItem {
name: "flashCapacityKb".to_string(),
summary: Some("Flash 容量".to_string()),
value_type: ValueType::Int,
},
],
parameters: vec![],
}],
},
ContractService {
api_kind: ApiKind::RuntimeRead,
interface_name: "DeviceReadApi".to_string(),
interface_summary: Some("运行态读取".to_string()),
methods: vec![ContractMethod {
summary: Some("读取信号灯".to_string()),
method_name: "getSignalLights".to_string(),
read_return_kind: Some(ReadReturnKind::Dto),
read_return_type_name: Some("SignalLightsRegisters".to_string()),
read_area: Some(RegisterArea::DiscreteInput),
write_area: None,
read_fields: vec![
ContractTypedItem {
name: "ch1".to_string(),
summary: Some("第一路".to_string()),
value_type: ValueType::Boolean,
},
ContractTypedItem {
name: "ch2".to_string(),
summary: Some("第二路".to_string()),
value_type: ValueType::Boolean,
},
],
parameters: vec![],
}],
},
ContractService {
api_kind: ApiKind::Write,
interface_name: "DeviceWriteApi".to_string(),
interface_summary: Some("写操作".to_string()),
methods: vec![ContractMethod {
summary: Some("设置故障灯".to_string()),
method_name: "writeIndicatorLights".to_string(),
read_return_kind: None,
read_return_type_name: None,
read_area: None,
write_area: Some(RegisterArea::Coil),
read_fields: vec![],
parameters: vec![
ContractTypedItem {
name: "faultLightOn".to_string(),
summary: None,
value_type: ValueType::Boolean,
},
ContractTypedItem {
name: "runLightOn".to_string(),
summary: None,
value_type: ValueType::Boolean,
},
],
}],
},
],
};
let bundle = generate_bundle(&request)?;
assert!(
bundle
.artifacts
.iter()
.any(|artifact| artifact.file_name == "okm_modbus_rtu_tokio_client"),
"缺少 RTU Rust 客户端产物"
);
assert!(
bundle
.artifacts
.iter()
.any(|artifact| artifact.file_name == "okm_modbus_tcp_tokio_client"),
"缺少 TCP Rust 客户端产物"
);
let markdown = bundle
.artifacts
.iter()
.find(|artifact| artifact.file_name == "modbus-rtu-contract")
.ok_or("缺少 RTU 协议文档")?;
assert!(
markdown.content.contains("getSignalLights"),
"协议文档中缺少方法映射"
);
let dispatch = bundle
.artifacts
.iter()
.find(|artifact| {
artifact.file_name == "okm_modbus_dispatch" && artifact.extension_name == "c"
})
.ok_or("缺少共享 dispatch 源文件")?;
assert!(
dispatch.content.contains("okm_bridge_get_signal_lights"),
"共享 dispatch 未引用桥接函数"
);
Ok(())
}