use helix_core::effect::Effect;
use helix_core::Correlation;
use serde_json::json;
use crate::outbound::registry::{handle_outbound, is_outbound};
fn dispatch(name: &str, args: serde_json::Value) -> (String, serde_json::Value) {
let corr = Correlation::from_raw(1);
let payload = serde_json::to_vec(&args).unwrap();
let effects = handle_outbound(name, &payload, "http://h/api", "http://h", Some("c1"), corr)
.unwrap_or_else(|e| panic!("{name} should dispatch: {e:?}"));
assert_eq!(effects.len(), 1, "{name} 应单条 Http effect");
match &effects[0] {
Effect::Http { req, .. } => {
let body: serde_json::Value =
serde_json::from_slice(req.body.as_ref().expect("body")).unwrap();
(req.url.clone(), body)
}
other => panic!("expected Http, got {other:?}"),
}
}
#[test]
fn display_name_endpoint_and_id_key() {
assert!(is_outbound("im_channel_change_display_name"));
let (url, body) = dispatch(
"im_channel_change_display_name",
json!({ "channel_id": "c1", "display_name": "新群名" }),
);
assert!(url.ends_with("/channel/change/displayName"), "url={url}");
assert_eq!(body["id"], "c1");
assert_eq!(body["displayName"], "新群名");
assert_eq!(body.as_object().unwrap().len(), 2);
assert!(body.get("channelId").is_none());
}
#[test]
fn orient_and_purpose_use_id_key() {
let (url, body) = dispatch(
"im_channel_change_orient",
json!({ "channel_id": "c1", "orient": "left" }),
);
assert!(url.ends_with("/channel/change/orient"), "url={url}");
assert_eq!(body["id"], "c1");
assert_eq!(body["orient"], "left");
assert_eq!(body.as_object().unwrap().len(), 2);
let (url2, body2) = dispatch(
"im_channel_change_purpose",
json!({ "channel_id": "c1", "purpose": "团队协作群" }),
);
assert!(url2.ends_with("/channel/change/purpose"), "url={url2}");
assert_eq!(body2["id"], "c1");
assert_eq!(body2["purpose"], "团队协作群");
}
#[test]
fn add_and_remove_manger_channel_id_key_and_users() {
let users = json!([{ "id": "u1", "name": "张三", "role": "admin", "teamId": "t1" }]);
let (url, body) = dispatch(
"im_channel_add_manger",
json!({ "channel_id": "c1", "users": users.clone() }),
);
assert!(url.ends_with("/channel/add/manger"), "url={url}");
assert_eq!(body["channelId"], "c1");
assert_eq!(body["users"], users);
let (url2, body2) = dispatch(
"im_channel_remove_manger",
json!({ "channel_id": "c1", "users": users.clone() }),
);
assert!(url2.ends_with("/channel/remove/manger"), "url={url2}");
assert_eq!(body2["channelId"], "c1");
assert_eq!(body2["users"], users);
}
#[test]
fn set_manger_routes_endpoint_and_role_by_set_flag() {
let (url, body) = dispatch(
"im_channel_set_manger",
json!({ "channel_id": "c1", "user_id": "u1", "team_id": "t1", "set": true }),
);
assert!(url.ends_with("/channel/add/manger"), "url={url}");
assert_eq!(body["channelId"], "c1");
assert_eq!(body["users"][0]["id"], "u1");
assert_eq!(body["users"][0]["name"], "");
assert_eq!(body["users"][0]["role"], "ADMIN");
assert_eq!(body["users"][0]["teamId"], "t1");
let (url2, body2) = dispatch(
"im_channel_set_manger",
json!({ "channel_id": "c1", "user_id": "u1", "team_id": "t1", "set": false }),
);
assert!(url2.ends_with("/channel/remove/manger"), "url={url2}");
assert_eq!(body2["users"][0]["role"], "MEMBER");
let corr = Correlation::from_raw(3);
let p = serde_json::to_vec(&json!({ "channel_id": "c1", "user_id": "u1" })).unwrap();
assert!(
handle_outbound(
"im_channel_set_manger",
&p,
"http://h/api",
"http://h",
None,
corr
)
.is_err(),
"缺 set → Err"
);
}
#[test]
fn enable_approval_bool_and_channel_id() {
let (url, body) = dispatch(
"im_channel_enable_approval",
json!({ "channel_id": "c1", "approval_status": true }),
);
assert!(url.ends_with("/channels/enableApproval"), "url={url}");
assert_eq!(body["channelId"], "c1");
assert_eq!(body["approvalStatus"], true);
}
#[test]
fn view_channels_passthrough_channels_array() {
let channels = json!([
{ "id": "chfixx0000000000000000001d", "isRoot": true },
{ "id": "chfixx0000000000000000001e", "isRoot": true }
]);
let (url, body) = dispatch("im_channels_view", json!({ "channels": channels.clone() }));
assert!(url.ends_with("/channels/view"), "url={url}");
assert_eq!(body["channels"], channels);
}
#[test]
fn view_channels_rejects_noncanonical_entries_and_duplicate_ids() {
let corr = Correlation::from_raw(4);
for channels in [
json!([{ "id": "chfixx0000000000000000001d" }]),
json!([{ "id": "chfixx0000000000000000001d", "isRoot": false }]),
json!([
{ "id": "chfixx0000000000000000001d", "isRoot": true },
{ "id": "chfixx0000000000000000001d", "isRoot": true }
]),
json!([{ "id": "chfixx0000000000000000001d", "isRoot": true, "extra": 1 }]),
] {
let payload = serde_json::to_vec(&json!({ "channels": channels })).unwrap();
assert!(
handle_outbound(
"im_channels_view",
&payload,
"http://h/api",
"http://h",
None,
corr
)
.is_err(),
"非 canonical channels 应拒绝"
);
}
}
#[test]
fn missing_required_fields_error_not_panic() {
let corr = Correlation::from_raw(2);
let p = serde_json::to_vec(&json!({ "channel_id": "c1" })).unwrap();
assert!(handle_outbound(
"im_channel_change_display_name",
&p,
"http://h/api",
"http://h",
None,
corr
)
.is_err());
let p2 = serde_json::to_vec(&json!({ "channel_id": "c1", "users": [] })).unwrap();
assert!(handle_outbound(
"im_channel_add_manger",
&p2,
"http://h/api",
"http://h",
None,
corr
)
.is_err());
let p3 = serde_json::to_vec(&json!({ "channel_id": "c1" })).unwrap();
assert!(handle_outbound(
"im_channel_enable_approval",
&p3,
"http://h/api",
"http://h",
None,
corr
)
.is_err());
let version_free =
serde_json::to_vec(&json!({ "channel_id": "c1", "display_name": "n" })).unwrap();
assert!(handle_outbound(
"im_channel_change_display_name",
&version_free,
"http://h/api",
"http://h",
None,
Correlation::from_raw(4),
)
.is_ok());
}