use super::*;
use serde_json::json;
struct StubCommand {
name: &'static str,
}
impl OutboundCommand for StubCommand {
fn name(&self) -> &'static str {
self.name
}
fn build(&self, _args: &Value) -> Result<(&'static str, Value), ImError> {
Ok(("stub/path", json!({ "ok": true })))
}
}
static STUB_A: StubCommand = StubCommand { name: "im_stub_a" };
static STUB_DUP: StubCommand = StubCommand { name: "im_stub_a" };
static STUB_MISMATCH: StubCommand = StubCommand {
name: "handler_name",
};
fn reg(name: &'static str, cmd: &'static dyn OutboundCommand) -> OutboundRegistration {
OutboundRegistration { name, command: cmd }
}
#[test]
fn build_registry_ok() {
let entries: &'static [OutboundRegistration] = Box::leak(Box::new([reg("im_stub_a", &STUB_A)]));
let map = build_registry(entries).expect("should build");
assert!(map.contains_key("im_stub_a"));
}
#[test]
fn build_registry_rejects_name_mismatch() {
let entries: &'static [OutboundRegistration] =
Box::leak(Box::new([reg("wrong_name", &STUB_MISMATCH)]));
match build_registry(entries) {
Err(ImError::InvalidWsHandlerRegistration {
registered,
handler,
}) => {
assert_eq!(registered, "wrong_name");
assert_eq!(handler, "handler_name");
}
Err(e) => panic!("expected InvalidWsHandlerRegistration, got {e:?}"),
Ok(_) => panic!("expected InvalidWsHandlerRegistration, got Ok"),
}
}
#[test]
fn build_registry_rejects_duplicate() {
let entries: &'static [OutboundRegistration] = Box::leak(Box::new([
reg("im_stub_a", &STUB_A),
reg("im_stub_a", &STUB_DUP),
]));
match build_registry(entries) {
Err(ImError::DuplicateWsAction(name)) => assert_eq!(name, "im_stub_a"),
Err(e) => panic!("expected DuplicateWsAction, got {e:?}"),
Ok(_) => panic!("expected DuplicateWsAction, got Ok"),
}
}
#[test]
fn require_str_paths() {
let args = json!({ "k": "v", "empty": "" });
assert_eq!(require_str(&args, "k", "cmd").unwrap(), "v");
assert!(require_str(&args, "missing", "cmd").is_err());
assert!(require_str(&args, "empty", "cmd").is_err());
}
#[test]
fn is_outbound_hits_registered() {
assert!(is_outbound("im_revoke"));
assert!(is_outbound("im_urgent_post"));
assert!(!is_outbound("not_a_command"));
}
#[test]
fn handle_outbound_dispatch_and_errors() {
let corr = Correlation::from_raw(1);
let payload = serde_json::to_vec(&json!({
"post_id": "p",
"req_id": "mrc-run:G-01:req-1"
}))
.unwrap();
let effects = handle_outbound(
"im_revoke",
&payload,
"http://h/api/cses",
"http://h",
Some("c1"),
corr,
)
.expect("revoke should dispatch");
match &effects[0] {
Effect::Http { req, .. } => {
assert_eq!(req.url, "http://h/api/cses/posts/revoke");
assert!(req
.headers
.iter()
.any(|(k, v)| k == "Content-Type" && v == "application/json"));
assert!(req
.headers
.iter()
.any(|(k, v)| k == "Cses-Track-Id" && v == "mrc-run:G-01:req-1"));
}
other => panic!("expected Http, got {other:?}"),
}
assert!(handle_outbound(
"bogus",
&payload,
"http://h/api/cses",
"http://h",
None,
corr
)
.is_err());
assert!(handle_outbound(
"im_revoke",
b"not-json",
"http://h/api/cses",
"http://h",
None,
corr
)
.is_err());
}
#[test]
fn vote_command_uses_default_gateway_base() {
assert!(is_outbound("im_vote_read"), "im_vote_read 应已注册");
let corr = Correlation::from_raw(7);
let payload = serde_json::to_vec(&json!({ "id": "v1" })).unwrap();
let effects = handle_outbound(
"im_vote_read",
&payload,
"http://h:8065/api/cses", "http://localhost:3399", Some("c1"),
corr,
)
.expect("vote_read should dispatch");
match &effects[0] {
Effect::Http { req, .. } => {
assert_eq!(req.url, "http://localhost:3399/vote/readVote");
assert!(
!req.url.contains("/api/cses"),
"vote 走第二网关,url 不该含 /api/cses,实际={}",
req.url
);
}
other => panic!("expected Http, got {other:?}"),
}
}
#[test]
fn default_gateway_without_java_base_fails_closed() {
let corr = Correlation::from_raw(71);
let payload = serde_json::to_vec(&json!({ "id": "v1" })).unwrap();
let error = handle_outbound(
"im_vote_read",
&payload,
"http://h:8065/api/cses",
"",
Some("c1"),
corr,
)
.expect_err("缺 Java base 时必须拒绝 default gateway");
assert!(
error.to_string().contains("default_api_base_url"),
"错误必须指出缺失的 Java base,实际={error}"
);
}
#[test]
fn existing_im_command_stays_on_im_gateway() {
let corr = Correlation::from_raw(8);
let payload = serde_json::to_vec(&json!({ "post_id": "p" })).unwrap();
let effects = handle_outbound(
"im_revoke",
&payload,
"http://h:8065/api/cses",
"http://localhost:3399", None,
corr,
)
.expect("revoke should dispatch");
match &effects[0] {
Effect::Http { req, .. } => {
assert_eq!(req.url, "http://h:8065/api/cses/posts/revoke");
assert!(
!req.url.contains("localhost:3399"),
"IM 命令不该走第二网关,实际={}",
req.url
);
}
other => panic!("expected Http, got {other:?}"),
}
}