ek-ui 0.9.21

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
Documentation
//! F9 自检闸(0.6.3 常驻)——**纯订阅观察件形态挂钩**(skill §14.1 挂钩表):
//! - `_shape`:0.4.3 后零服务面件合法(请求线自报豁免),期望**零 finding**;
//! - 订阅矩阵:逐线驱动 `on_mail` 直投(零服务面 = 没人 ask,矩阵就是它的
//!   deploy 等档)——字段 getters 断言 `UiShared` 每支订阅线有暗主;
//! - 坏载荷显形:非契约载荷进不了账本但**必须计数**(§10 纪律);
//! - 跨 crate 寻址常量钉定(单源哨;ui 改引 ek-ctp/ek-oms 的 0.3.19 导出)。
use std::sync::Arc;

use ek_core::audit::shape_audit;
use ek_core::plugin::Plugin;
use ek_ctp::EVENT_CONTRACT_FINISH;
use ek_data::constant::{Asset, Exchange, InstrumentType};
use ek_data::contract::ContractData;
use ek_data::event::{EVENT_BAR, EVENT_CONTRACT, EVENT_LOG, EVENT_TICK};
use ek_data::log::LogData;
use ek_data::tick::TickData;
use ek_event::Event;
use ek_oms::wire::{EVENT_ORDER as W_ORDER, EVENT_POSITION as W_POSITION, EVENT_TRADE as W_TRADE};
use ek_ui::plugin::UiPlugin;
use ek_ui::state::UiShared;

fn ui() -> (Arc<UiShared>, UiPlugin) {
    let shared = Arc::new(UiShared::new());
    let plugin = UiPlugin::new(Arc::clone(&shared));
    (shared, plugin)
}

/// ek-ui 的交易四表线名本就是 ek-oms 单源的引用式(plugin.rs:98-101),
/// 测试同走该正源(上方 import 别名)。

#[test]
fn f9_shape_audit_is_ok_for_pure_subscriber() {
    let (_, _) = ui();
    let p = UiPlugin::new(Arc::new(UiShared::new()));
    // shape 只读插件形态;故意不走 on_init(装配之外的结构判定)。
    let rep = shape_audit(&p, env!("CARGO_PKG_VERSION"));
    assert!(rep.is_ok(), "纯订阅件应零 finding: {:?}", rep.findings);
}

#[test]
fn subscription_matrix_every_route_lands_state() {
    let (shared, plg) = ui();
    let count_before = shared.logs_len();

    // ① eLog:日志窗落账(INFO 级)。
    let log = LogData::info("gateway_test", "链上一拍");
    plg.on_mail(&Event::with_data(EVENT_LOG, Arc::new(log)));
    assert!(shared.logs_len() > count_before, "eLog 必须进日志窗");

    // ② eTick:行情盯盘的逐笔快照。
    let mut tick = TickData {
        gateway_name: "gateway_test".to_string(),
        ..TickData::default()
    };
    tick.symbol = "AG2601".to_string();
    tick.last_price = 8000.0;
    plg.on_mail(&Event::with_data(EVENT_TICK, Arc::new(tick)));
    assert_eq!(shared.tick_count(), 1, "eTick 必须入行情快照");

    // ③ eContract:合约表(全字段构造,无 Default 形)。
    let contract = ContractData {
        gateway_name: "gateway_test".to_string(),
        symbol: "AG2601".to_string(),
        exchange: Exchange::SHFE,
        name: "白银2601".to_string(),
        asset: Asset::FUTURES,
        instrument_type: Some(InstrumentType::FUTURES_CONTRACT),
        size: 15.0,
        price_tick: 1.0,
        product_id: Some("AG".to_string()),
        max_limit_order_volume: None,
        min_limit_order_volume: None,
        max_market_order_volume: None,
        min_market_order_volume: None,
        delivery_year: Some(2026),
        delivery_month: Some(1),
        open_date: None,
        pre_close: None,
        up_stop_price: None,
        down_stop_price: None,
        float_volume: None,
        total_volume: None,
        instrument_status: None,
        is_trading: Some(true),
    };
    plg.on_mail(&Event::with_data(EVENT_CONTRACT, Arc::new(contract)));
    assert!(shared.contract_count() >= 1, "eContract 必须入合约表");

    // ④ eContractFinish(全局线,常量单源自 ek-ctp 0.10.5):日志显形一次。
    let before = shared.logs_len();
    plg.on_mail(&Event::with_data(
        EVENT_CONTRACT_FINISH,
        Arc::new("gateway_test".to_string()),
    ));
    assert!(shared.logs_len() > before, "eContractFinish 必须显形日志");

    // ⑤ 交易四表坏载荷:静默丢 = 纪律零容忍,必须计数(首次还会显形日志)。
    let drop_before = shared.bad_payload_count(W_ORDER);
    plg.on_mail(&Event::with_data(W_ORDER, Arc::new(String::from("bad"))));
    assert_eq!(
        shared.bad_payload_count(W_ORDER),
        drop_before + 1,
        "W_ORDER 坏载荷必须计数"
    );
    plg.on_mail(&Event::with_data(W_TRADE, Arc::new(String::from("bad"))));
    plg.on_mail(&Event::with_data(W_POSITION, Arc::new(String::from("bad"))));
    assert_eq!(shared.bad_payload_count(W_TRADE), 1);
    assert_eq!(shared.bad_payload_count(W_POSITION), 1);

    // ⑥ eBar 裸线:数据型 downcast miss 必须静默(有主裁决 = 只读不崩);
    // 推送面板由 eBarUpdate/私有快照线驱动,这一条只钉"线有暗主不 panic"。
    plg.on_mail(&Event::with_data(
        EVENT_BAR,
        Arc::new(String::from("nonsense")),
    ));
}

#[test]
fn cross_crate_addressing_is_constant_sourced() {
    // 单源哨(0.6.3):ui 不再造寻址字面 —— 值全部向 ek-ctp/ek-oms 收口。
    assert_eq!(ek_oms::NAME, "oms");
    assert_eq!(ek_oms::SVC_PIPELINE_STATUS, "oms.pipeline_status");
    assert_eq!(EVENT_CONTRACT_FINISH, "eContractFinish");
}