174 lines
5.9 KiB
Rust
174 lines
5.9 KiB
Rust
//! Mattermost's JSON: events as the server sends them (v11.11.0 shapes), the typing request, and
|
|
//! the `posts?since` list, whose edited, deleted and root-only posts must not be answered (M4a spec,
|
|
//! sections 7 and 9). Do not edit.
|
|
|
|
use gatewayd::mm::{Event, MmError, SINCE_LIMIT, parse_event, since_list, typing};
|
|
use serde_json::json;
|
|
|
|
const KYLE: &str = "k0000000000000000000000000";
|
|
const DM: &str = "d0000000000000000000000000";
|
|
|
|
fn post(id: &str, create_at: i64) -> serde_json::Value {
|
|
json!({
|
|
"id": id, "create_at": create_at, "update_at": create_at, "edit_at": 0, "delete_at": 0,
|
|
"is_pinned": false, "user_id": KYLE, "channel_id": DM, "root_id": "", "original_id": "",
|
|
"message": "hello", "type": "", "props": {"from_bot": "true"}, "hashtags": "",
|
|
"pending_post_id": "", "reply_count": 0, "metadata": {}
|
|
})
|
|
}
|
|
|
|
fn id(n: u32) -> String {
|
|
format!("p{n:025}")
|
|
}
|
|
|
|
fn posted(post: &serde_json::Value, channel_type: &str) -> String {
|
|
json!({
|
|
"event": "posted",
|
|
"data": {
|
|
"channel_display_name": "@kyle", "channel_name": "x__y", "channel_type": channel_type,
|
|
"post": post.to_string(), "sender_name": "@kyle", "set_online": true, "team_id": ""
|
|
},
|
|
"broadcast": {"omit_users": null, "user_id": "", "channel_id": DM, "team_id": ""},
|
|
"seq": 3
|
|
})
|
|
.to_string()
|
|
}
|
|
|
|
#[test]
|
|
fn hello_replies_and_other_events() {
|
|
let hello =
|
|
json!({"event": "hello", "data": {"server_version": "11.11.0"}, "broadcast": {}, "seq": 0});
|
|
assert_eq!(parse_event(&hello.to_string()).unwrap(), Event::Hello);
|
|
assert_eq!(
|
|
parse_event(r#"{"status":"OK","seq_reply":1}"#).unwrap(),
|
|
Event::Other(String::new())
|
|
);
|
|
let typing = json!({"event": "typing", "data": {"parent_id": ""}, "broadcast": {}, "seq": 4});
|
|
assert_eq!(
|
|
parse_event(&typing.to_string()).unwrap(),
|
|
Event::Other("typing".to_string())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_posted_event_carries_the_post_as_a_string() {
|
|
let mut p = post(&id(1), 1_758_650_000_123);
|
|
p["root_id"] = json!(id(0));
|
|
p["message"] = json!("line one\nline two");
|
|
let Event::Posted { post, channel_type } = parse_event(&posted(&p, "D")).unwrap() else {
|
|
panic!("not a post")
|
|
};
|
|
assert_eq!(channel_type, "D");
|
|
assert_eq!(
|
|
(post.id.as_str(), post.root_id.as_str()),
|
|
(id(1).as_str(), id(0).as_str())
|
|
);
|
|
assert_eq!(
|
|
(post.user_id.as_str(), post.channel_id.as_str()),
|
|
(KYLE, DM)
|
|
);
|
|
assert_eq!(
|
|
(post.message.as_str(), post.create_at, post.kind.as_str()),
|
|
("line one\nline two", 1_758_650_000_123, "")
|
|
);
|
|
let mut s = self::post(&id(2), 5);
|
|
s["type"] = json!("system_join_channel");
|
|
let Event::Posted { post, .. } = parse_event(&posted(&s, "O")).unwrap() else {
|
|
panic!()
|
|
};
|
|
assert_eq!(post.kind, "system_join_channel");
|
|
}
|
|
|
|
#[test]
|
|
fn bad_posts_are_errors_not_panics() {
|
|
let p = post(&id(1), 5);
|
|
let as_object =
|
|
json!({"event": "posted", "data": {"post": p, "channel_type": "D"}}).to_string();
|
|
let mut cases = vec![
|
|
as_object,
|
|
"not json".to_string(),
|
|
"[1,2]".to_string(),
|
|
"{\"event\":5}".to_string(),
|
|
];
|
|
for (field, value) in [
|
|
("id", json!("../../etc")),
|
|
("user_id", json!("")),
|
|
("channel_id", json!("A0000000000000000000000000")),
|
|
("root_id", json!("short")),
|
|
("create_at", json!("soon")),
|
|
] {
|
|
let mut bad = post(&id(1), 5);
|
|
bad[field] = value;
|
|
cases.push(posted(&bad, "D"));
|
|
}
|
|
let mut missing = post(&id(1), 5);
|
|
missing.as_object_mut().unwrap().remove("create_at");
|
|
cases.push(posted(&missing, "D"));
|
|
for case in cases {
|
|
assert!(
|
|
matches!(parse_event(&case), Err(MmError::Json(_))),
|
|
"{case}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn the_typing_request() {
|
|
let got: serde_json::Value = serde_json::from_str(&typing(7, DM, &id(0))).unwrap();
|
|
assert_eq!(
|
|
got,
|
|
json!({"action": "user_typing", "seq": 7, "data": {"channel_id": DM, "parent_id": id(0)}})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn since_keeps_new_posts_in_order_and_nothing_else() {
|
|
let since = 1000;
|
|
let mut edited = post(&id(1), 900);
|
|
edited["update_at"] = json!(1500);
|
|
let mut deleted = post(&id(2), 1100);
|
|
deleted["delete_at"] = json!(1200);
|
|
let root_only = post(&id(3), 10);
|
|
let later = post(&id(4), 1300);
|
|
let earlier = post(&id(5), 1200);
|
|
let at_since = post(&id(6), 1000);
|
|
let mut posts = serde_json::Map::new();
|
|
for p in [&edited, &deleted, &root_only, &later, &earlier, &at_since] {
|
|
posts.insert(p["id"].as_str().unwrap().to_string(), p.clone());
|
|
}
|
|
let order = json!([id(4), id(2), id(1), id(5), id(9), id(6)]);
|
|
let body = json!({"order": order, "posts": posts, "next_post_id": "", "prev_post_id": "", "has_next": false});
|
|
let got = since_list(body.to_string().as_bytes(), since).unwrap();
|
|
let ids: Vec<&str> = got.posts.iter().map(|p| p.id.as_str()).collect();
|
|
assert_eq!(ids, [id(5), id(4)]);
|
|
assert!(!got.full);
|
|
}
|
|
|
|
#[test]
|
|
fn a_full_since_answer_says_so() {
|
|
let mut posts = serde_json::Map::new();
|
|
let mut order = Vec::new();
|
|
for n in 0..SINCE_LIMIT {
|
|
let n = u32::try_from(n).unwrap();
|
|
posts.insert(id(n), post(&id(n), 2000 + i64::from(n)));
|
|
order.push(id(n));
|
|
}
|
|
let body = json!({"order": order, "posts": posts});
|
|
let got = since_list(body.to_string().as_bytes(), 1000).unwrap();
|
|
assert!(got.full);
|
|
assert_eq!(got.posts.len(), SINCE_LIMIT);
|
|
let empty = since_list(br#"{"order":[],"posts":{}}"#, 0).unwrap();
|
|
assert!(empty.posts.is_empty() && !empty.full);
|
|
}
|
|
|
|
#[test]
|
|
fn a_server_error_body_is_quoted_in_messages() {
|
|
let e = MmError::Status(500, "boom\ngatewayd: a forged line".to_string());
|
|
let text = e.to_string();
|
|
assert!(!text.contains('\n'), "{text}");
|
|
assert_eq!(
|
|
MmError::Auth(401).to_string(),
|
|
"Mattermost refused the token (401)"
|
|
);
|
|
}
|