#[cfg(test)]
mod tests {
use super::super::{
APIMessage, ActionButton, ActionTypeSubscribe, ApiMessage, Ark, ArkKv, ArkObj, ArkObjKv,
DirectMessage, ETLInput, Embed, Emoji, GroupMessageParams, InputNotify, Keyboard,
KeyboardButton, KeyboardButtonAction, KeyboardButtonPermission, KeyboardButtonRenderData,
KeyboardContent, KeyboardModal, KeyboardRow, KeyboardStyle, KeyboardSubscribeData,
KeyboardTemplateId, MPTBefore, Media, MediaInfo, MentionAllUser, MentionChannel,
MentionUser, Message, MessageAttachment, MessageAudit, MessageCreateType, MessageParams,
MessageReference, MessageToCreate, MessageUser, MessagesPager, ParseCommand, Reference,
RichMediaMessage, SendType, Stream,
};
#[test]
fn test_message_helpers() {
assert_eq!(MentionUser("123"), "<@123>");
assert_eq!(MentionAllUser(), "@everyone");
assert_eq!(MentionChannel("456"), "<#456>");
assert_eq!(Emoji(1), "<emoji:1>");
assert_eq!(ETLInput("<@!123> ping value"), "ping value");
assert_eq!(
ETLInput("\u{00a0}<@!123> ping value\u{00a0}"),
"ping value"
);
assert_eq!(ETLInput("<@123> ping"), "<@123> ping");
assert_eq!(ETLInput("<@!abc> ping"), "<@!abc> ping");
let command = ParseCommand("<@!123> /ping value");
assert_eq!(command.cmd, "/ping");
assert_eq!(command.content, "value");
let command = ParseCommand("<@!123> /ping value");
assert_eq!(command.cmd, "/ping");
assert_eq!(command.content, "value");
let command = ParseCommand("/ping\tvalue");
assert_eq!(command.cmd, "/ping\tvalue");
assert_eq!(command.content, "");
}
#[test]
fn api_message_helpers_match_send_type_contract() {
let message = MessageToCreate {
event_id: Some("event-1".to_string()),
..Default::default()
};
let rich_media = RichMediaMessage {
event_id: Some("ignored".to_string()),
..Default::default()
};
let reference = Reference {
message_id: Some("message-1".to_string()),
ignore_get_message_error: Some(true),
};
assert_eq!(message.GetEventID(), "event-1");
assert_eq!(message.GetSendType(), SendType::Text);
assert_eq!(rich_media.GetEventID(), "");
assert_eq!(rich_media.GetSendType(), SendType::RichMedia);
assert_eq!(reference.GetEventID(), "message-1");
assert_eq!(reference.GetSendType(), SendType::Text);
let api_message = ApiMessage::from(message);
assert_eq!(api_message.GetEventID(), "event-1");
assert_eq!(api_message.GetSendType(), SendType::Text);
}
#[test]
fn message_params_convert_to_create_payload_shape() {
let params = MessageParams {
content: Some("hello".to_string()),
image: Some("https://example.com/image.png".to_string()),
msg_id: Some("message-1".to_string()),
file_image: Some("aW1hZ2U=".to_string()),
..Default::default()
};
assert_eq!(
serde_json::to_value(MessageToCreate::from(params)).unwrap(),
serde_json::json!({
"content": "hello",
"image": "https://example.com/image.png",
"msg_id": "message-1",
"file_image": "aW1hZ2U="
})
);
let params = GroupMessageParams {
msg_type: 7,
content: Some("media".to_string()),
media: Some(Media {
file_info: Some("file-info".to_string()),
ttl: Some(60),
}),
msg_seq: Some(42),
..Default::default()
};
assert_eq!(
serde_json::to_value(MessageToCreate::from(params)).unwrap(),
serde_json::json!({
"content": "media",
"msg_type": 7,
"media": {
"file_info": "file-info"
},
"msg_seq": 42
})
);
}
#[test]
fn message_create_omits_go_zero_values() {
let message = MessageToCreate {
content: Some(String::new()),
msg_type: Some(MessageCreateType::Text),
image: Some(String::new()),
msg_id: Some(String::new()),
event_id: Some(String::new()),
timestamp: Some(0),
msg_seq: Some(0),
subscribe_id: Some(String::new()),
feature_id: Some(0),
input_notify: Some(InputNotify {
input_type: Some(0),
input_second: Some(0),
}),
media: Some(MediaInfo {
file_info: Some(String::new()),
}),
action_button: Some(ActionButton {
template_id: Some(0),
callback_data: Some(String::new()),
feedback: Some(false),
tts: Some(false),
re_generate: Some(false),
stop_generate: Some(false),
}),
stream: Some(Stream {
state: Some(0),
id: Some(String::new()),
index: Some(0),
reset: Some(false),
}),
ark: Some(Ark {
template_id: Some(0),
kv: Some(Vec::new()),
}),
embed: Some(Embed::default()),
..Default::default()
};
let value = serde_json::to_value(&message).unwrap();
for key in [
"content",
"msg_type",
"image",
"msg_id",
"event_id",
"timestamp",
"msg_seq",
"subscribe_id",
"feature_id",
] {
assert!(value.get(key).is_none(), "{key} should be omitted");
}
assert_eq!(value["input_notify"], serde_json::json!({}));
assert_eq!(value["media"], serde_json::json!({}));
assert_eq!(value["action_button"], serde_json::json!({}));
assert_eq!(value["stream"], serde_json::json!({}));
assert_eq!(value["ark"], serde_json::json!({}));
assert_eq!(
value["embed"],
serde_json::json!({
"prompt": "",
"thumbnail": {
"url": ""
}
})
);
}
#[test]
fn message_create_keeps_non_zero_omitempty_values() {
let message = MessageToCreate {
content: Some("hello".to_string()),
msg_type: Some(MessageCreateType::Markdown),
image: Some("https://example.com/image.png".to_string()),
msg_id: Some("msg-1".to_string()),
event_id: Some("event-1".to_string()),
timestamp: Some(42),
msg_seq: Some(1),
subscribe_id: Some("sub-1".to_string()),
feature_id: Some(7),
input_notify: Some(InputNotify {
input_type: Some(1),
input_second: Some(3),
}),
media: Some(MediaInfo {
file_info: Some("file-info".to_string()),
}),
action_button: Some(ActionButton {
template_id: Some(2),
callback_data: Some("callback".to_string()),
feedback: Some(true),
tts: Some(true),
re_generate: Some(true),
stop_generate: Some(true),
}),
stream: Some(Stream {
state: Some(1),
id: Some("stream-1".to_string()),
index: Some(1),
reset: Some(true),
}),
ark: Some(Ark {
template_id: Some(23),
kv: Some(vec![ArkKv {
key: Some("key".to_string()),
value: Some("value".to_string()),
obj: Some(vec![ArkObj {
obj_kv: Some(vec![ArkObjKv {
key: Some("nested-key".to_string()),
value: Some("nested-value".to_string()),
}]),
}]),
}]),
}),
..Default::default()
};
assert_eq!(
serde_json::to_value(&message).unwrap(),
serde_json::json!({
"content": "hello",
"msg_type": 2,
"image": "https://example.com/image.png",
"msg_id": "msg-1",
"event_id": "event-1",
"timestamp": 42,
"msg_seq": 1,
"subscribe_id": "sub-1",
"input_notify": {
"input_type": 1,
"input_second": 3
},
"media": {
"file_info": "file-info"
},
"ark": {
"template_id": 23,
"kv": [{
"key": "key",
"value": "value",
"obj": [{
"obj_kv": [{
"key": "nested-key",
"value": "nested-value"
}]
}]
}]
},
"action_button": {
"template_id": 2,
"callback_data": "callback",
"feedback": true,
"tts": true,
"re_generate": true,
"stop_generate": true
},
"stream": {
"state": 1,
"id": "stream-1",
"index": 1,
"reset": true
},
"feature_id": 7
})
);
}
#[test]
fn rich_media_omits_go_zero_values() {
let message = RichMediaMessage {
event_id: Some(String::new()),
file_type: Some(0),
url: Some(String::new()),
srv_send_msg: Some(false),
content: Some(String::new()),
msg_seq: Some(0),
};
assert_eq!(
serde_json::to_value(&message).unwrap(),
serde_json::json!({})
);
let message = RichMediaMessage {
event_id: Some("event-1".to_string()),
file_type: Some(1),
url: Some("https://example.com/file.png".to_string()),
srv_send_msg: Some(true),
content: Some("caption".to_string()),
msg_seq: Some(1),
};
assert_eq!(
serde_json::to_value(&message).unwrap(),
serde_json::json!({
"event_id": "event-1",
"file_type": 1,
"url": "https://example.com/file.png",
"srv_send_msg": true,
"content": "caption",
"msg_seq": 1
})
);
}
#[test]
fn embed_keeps_required_zero_value_fields() {
let embed = Embed::default();
assert_eq!(
serde_json::to_value(&embed).unwrap(),
serde_json::json!({
"prompt": "",
"thumbnail": {
"url": ""
}
})
);
}
#[test]
fn keyboard_action_keeps_official_zero_value_shape() {
let action = KeyboardButtonAction::default();
assert_eq!(
serde_json::to_value(&action).unwrap(),
serde_json::json!({
"enter": false,
"subscribe_data": {}
})
);
}
#[test]
fn keyboard_omits_go_zero_values() {
let keyboard = Keyboard {
id: Some(String::new()),
content: Some(KeyboardContent {
rows: Some(vec![KeyboardRow {
buttons: Some(vec![KeyboardButton {
id: Some(String::new()),
render_data: Some(KeyboardButtonRenderData {
label: Some(String::new()),
visited_label: Some(String::new()),
style: Some(0),
}),
action: Some(KeyboardButtonAction {
action_type: Some(0),
click_limit: Some(0),
data: Some(String::new()),
at_bot_show_channel_list: Some(false),
permission: Some(KeyboardButtonPermission {
permission_type: Some(0),
specify_role_ids: Some(Vec::new()),
specify_user_ids: Some(Vec::new()),
}),
modal: Some(KeyboardModal {
content: Some(String::new()),
confirm_text: Some(String::new()),
cancel_text: Some(String::new()),
}),
subscribe_data: KeyboardSubscribeData {
template_ids: Some(vec![KeyboardTemplateId {
template_id: Some(0),
custom_template_id: Some(String::new()),
}]),
},
..Default::default()
}),
group_id: Some(String::new()),
}]),
}]),
style: Some(KeyboardStyle {
font_size: Some(String::new()),
}),
}),
};
assert_eq!(
serde_json::to_value(&keyboard).unwrap(),
serde_json::json!({
"content": {
"rows": [{
"buttons": [{
"render_data": {},
"action": {
"permission": {},
"enter": false,
"subscribe_data": {
"template_ids": [{}]
},
"modal": {}
}
}]
}],
"style": {}
}
})
);
}
#[test]
fn keyboard_keeps_non_zero_values() {
let action = KeyboardButtonAction {
action_type: Some(ActionTypeSubscribe),
click_limit: Some(1),
data: Some("payload".to_string()),
enter: true,
at_bot_show_channel_list: Some(true),
subscribe_data: KeyboardSubscribeData {
template_ids: Some(vec![KeyboardTemplateId {
template_id: Some(1),
custom_template_id: None,
}]),
},
..Default::default()
};
assert_eq!(
serde_json::to_value(&action).unwrap(),
serde_json::json!({
"type": 4,
"click_limit": 1,
"data": "payload",
"enter": true,
"at_bot_show_channel_list": true,
"subscribe_data": {
"template_ids": [{
"template_id": 1
}]
}
})
);
}
#[test]
fn messages_pager_query_params() {
let pager = MessagesPager::new(Some(MPTBefore), Some("msg-1"), Some(20));
let query = pager.QueryParams();
assert_eq!(query.get("before").map(String::as_str), Some("msg-1"));
assert_eq!(query.get("limit").map(String::as_str), Some("20"));
}
#[test]
fn test_message_creation() {
let message = Message::new();
assert!(message.id.is_none());
assert!(message.content.is_none());
assert!(!message.has_content());
assert!(!message.has_attachments());
assert!(!message.has_mentions());
}
#[test]
fn direct_message_is_session_dto() {
let session = DirectMessage::from_data(serde_json::json!({
"guild_id": "guild-1",
"channel_id": "channel-1",
"create_time": "2024-01-02T03:04:05+08:00",
"content": "ignored"
}));
assert_eq!(session.guild_id.as_deref(), Some("guild-1"));
assert_eq!(session.channel_id.as_deref(), Some("channel-1"));
assert_eq!(
session.create_time.as_deref(),
Some("2024-01-02T03:04:05+08:00")
);
let value = serde_json::to_value(&session).unwrap();
assert_eq!(value["guild_id"], serde_json::json!("guild-1"));
assert_eq!(value["channel_id"], serde_json::json!("channel-1"));
assert_eq!(
value["create_time"],
serde_json::json!("2024-01-02T03:04:05+08:00")
);
assert!(value.get("content").is_none());
}
#[test]
fn test_message_with_content() {
let mut message = Message::new();
message.content = Some("Hello, world!".to_string());
assert!(message.has_content());
}
#[test]
fn message_reference_keeps_ignore_error_flag() {
let reference = MessageReference::from_data(serde_json::json!({
"message_id": "message-1",
"ignore_get_message_error": true
}));
assert_eq!(reference.message_id.as_deref(), Some("message-1"));
assert_eq!(reference.ignore_get_message_error, Some(true));
let value = serde_json::to_value(&reference).unwrap();
assert_eq!(value["message_id"], serde_json::json!("message-1"));
assert_eq!(value["ignore_get_message_error"], serde_json::json!(true));
}
#[test]
fn message_audit_keeps_channel_sequence() {
let audit = MessageAudit::from_data(
crate::api::BotApi::new(crate::http::HttpClient::new(30, false).unwrap()),
"event-1".to_string(),
serde_json::json!({
"audit_id": "audit-1",
"message_id": "message-1",
"guild_id": "guild-1",
"channel_id": "channel-1",
"seq_in_channel": "42"
}),
);
assert_eq!(audit.seq_in_channel, "42");
assert_eq!(audit.audit_time, "");
assert_eq!(audit.create_time, "");
let value = serde_json::to_value(&audit).unwrap();
assert_eq!(value["seq_in_channel"], serde_json::json!("42"));
assert!(value.get("event_id").is_none());
}
#[test]
fn message_audit_uses_required_zero_value_fields() {
let audit: MessageAudit = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(audit.audit_id, "");
assert_eq!(audit.message_id, "");
assert_eq!(audit.guild_id, "");
assert_eq!(audit.channel_id, "");
assert_eq!(audit.audit_time, "");
assert_eq!(audit.create_time, "");
assert_eq!(audit.seq_in_channel, "");
let value = serde_json::to_value(&audit).unwrap();
assert_eq!(value["audit_id"], "");
assert_eq!(value["message_id"], "");
assert_eq!(value["guild_id"], "");
assert_eq!(value["channel_id"], "");
assert_eq!(value["audit_time"], "");
assert_eq!(value["create_time"], "");
assert_eq!(value["seq_in_channel"], "");
}
#[test]
fn embed_keeps_prompt_field() {
let embed = Embed {
title: Some("title".to_string()),
prompt: "summary".to_string(),
..Default::default()
};
let value = serde_json::to_value(&embed).unwrap();
assert_eq!(value["prompt"], serde_json::json!("summary"));
let parsed: Embed = serde_json::from_value(value).unwrap();
assert_eq!(parsed.prompt, "summary");
}
#[test]
fn test_message_attachment_types() {
let mut attachment = MessageAttachment {
id: Some("123".to_string()),
filename: Some("image.png".to_string()),
content_type: Some("image/png".to_string()),
size: Some(1024),
url: Some("https://example.com/image.png".to_string()),
width: Some(800),
height: Some(600),
};
assert!(attachment.is_image());
assert!(!attachment.is_video());
assert!(!attachment.is_audio());
attachment.content_type = Some("video/mp4".to_string());
assert!(!attachment.is_image());
assert!(attachment.is_video());
assert!(!attachment.is_audio());
}
#[test]
fn test_bot_detection() {
let mut message = Message::new();
message.author = Some(MessageUser {
id: Some("123".to_string()),
username: Some("Bot".to_string()),
bot: Some(true),
avatar: None,
});
assert!(message.is_from_bot());
message.author.as_mut().unwrap().bot = Some(false);
assert!(!message.is_from_bot());
}
}