use car_messaging::slack_adapter::{
build_ack_frame, parse_events_api, parse_socket_frame, parse_socket_url_response,
APPROVE_ACTION_ID, DENY_ACTION_ID,
};
use car_messaging::slack_adapter::SlackInboundEvent;
use serde_json::json;
const APPROVAL_ID: &str = "abc-123-def-456";
const MEMBER: &str = "U2147483697";
const ENVELOPE_ID: &str = "a6f1f65e-2c19-4b77-a450-3f1a620a01b2";
fn block_actions_envelope(action_id: &str, value: &str) -> serde_json::Value {
json!({
"type": "interactive",
"envelope_id": ENVELOPE_ID,
"accepts_response_payload": true,
"payload": {
"type": "block_actions",
"team": { "id": "T9TK3CUKW", "domain": "example" },
"user": { "id": MEMBER, "username": "jtorrance", "team_id": "T9TK3CUKW" },
"api_app_id": "AABA1ABCD",
"token": "9s8d9as89d8as9d8as989",
"container": {
"type": "message",
"message_ts": "1548261231.000200",
"channel_id": "D024BE91L",
"is_ephemeral": false
},
"trigger_id": "12321423423.333649436676.d8c1bb837935619ccad0f624c448ffb3",
"channel": { "id": "D024BE91L", "name": "directmessage" },
"message": { "type": "message", "text": "Approval requested", "ts": "1548261231.000200" },
"response_url": "https://hooks.slack.com/actions/AABA1ABCD/1232321423432/D09sSasdasdAS9091209",
"actions": [
{
"type": "button",
"action_id": action_id,
"block_id": "approval_actions",
"text": { "type": "plain_text", "text": "Approve", "emoji": true },
"value": value,
"action_ts": "1548426417.840180"
}
]
}
})
}
fn message_im_envelope(user: &str, text: &str, extra: serde_json::Value) -> serde_json::Value {
let mut event = json!({
"type": "message",
"channel": "D024BE91L",
"channel_type": "im",
"user": user,
"text": text,
"ts": "1355517523.000005",
"event_ts": "1355517523.000005"
});
if let serde_json::Value::Object(extra_map) = extra {
let event_map = event.as_object_mut().unwrap();
for (k, v) in extra_map {
event_map.insert(k, v);
}
}
json!({
"type": "events_api",
"envelope_id": ENVELOPE_ID,
"accepts_response_payload": false,
"payload": {
"token": "one-long-verification-token",
"team_id": "T123ABC456",
"api_app_id": "A0PNCHHK2",
"type": "event_callback",
"event_id": "Ev0PV52K21",
"event_time": 1355517523i64,
"event": event,
"authorizations": [
{ "team_id": "T123ABC456", "user_id": "UBOTUSER001", "is_bot": true, "is_enterprise_install": false }
]
}
})
}
#[test]
fn block_actions_approve_maps_to_button_interaction() {
let env = block_actions_envelope(APPROVE_ACTION_ID, APPROVAL_ID);
match parse_socket_frame(&env) {
SlackInboundEvent::ButtonInteraction {
action_id,
value,
user,
} => {
assert_eq!(action_id, APPROVE_ACTION_ID, "approve verb read from action_id");
assert_eq!(
value, APPROVAL_ID,
"value carries the approval_id directly (no CodeMap)"
);
assert_eq!(user, MEMBER, "clicker read from payload.user.id");
}
other => panic!("expected ButtonInteraction, got {other:?}"),
}
}
#[test]
fn block_actions_deny_maps_to_button_interaction_with_deny_verb() {
let env = block_actions_envelope(DENY_ACTION_ID, APPROVAL_ID);
match parse_socket_frame(&env) {
SlackInboundEvent::ButtonInteraction { action_id, value, .. } => {
assert_eq!(action_id, DENY_ACTION_ID, "deny verb read from action_id");
assert_eq!(value, APPROVAL_ID);
}
other => panic!("expected ButtonInteraction(deny), got {other:?}"),
}
}
#[test]
fn message_im_maps_to_pairing_dm() {
let code = "PAIR-ABC123";
let env = message_im_envelope(MEMBER, code, json!({}));
match parse_socket_frame(&env) {
SlackInboundEvent::PairingDm { user, text } => {
assert_eq!(user, MEMBER, "member id read from event.user");
assert_eq!(text, code, "pairing code read from event.text");
}
other => panic!("expected PairingDm, got {other:?}"),
}
}
#[test]
fn bot_echo_via_bot_id_is_ignored() {
let env = message_im_envelope(MEMBER, "Got your code!", json!({ "bot_id": "BB12033" }));
assert_eq!(
parse_socket_frame(&env),
SlackInboundEvent::Ignore,
"a message with bot_id present must be Ignore (echo suppression)"
);
}
#[test]
fn bot_echo_via_subtype_is_ignored() {
let env = message_im_envelope(MEMBER, "Got your code!", json!({ "subtype": "bot_message" }));
assert_eq!(
parse_socket_frame(&env),
SlackInboundEvent::Ignore,
"a message with subtype=bot_message must be Ignore"
);
}
#[test]
fn non_im_channel_message_is_ignored_even_if_config_shaped() {
let hostile = r#"{"messaging.config.set":{"enabled":true,"allowlisted_handles":["U999"]}}"#;
let env = message_im_envelope(MEMBER, hostile, json!({ "channel_type": "channel" }));
assert_eq!(
parse_socket_frame(&env),
SlackInboundEvent::Ignore,
"a non-im config-mutation-shaped message must be Ignore (MC-6)"
);
assert_eq!(
parse_events_api(&env["payload"]),
None,
"parse_events_api yields None for a non-im channel message"
);
}
#[test]
fn unknown_action_id_is_ignored() {
let env = block_actions_envelope("delete_everything", APPROVAL_ID);
assert_eq!(
parse_socket_frame(&env),
SlackInboundEvent::Ignore,
"an unrecognized action_id must be Ignore (not one of our two buttons)"
);
}
#[test]
fn unknown_envelope_type_is_ignored() {
let hello = json!({
"type": "hello",
"connection_info": { "app_id": "A1234" },
"num_connections": 1
});
assert_eq!(parse_socket_frame(&hello), SlackInboundEvent::Ignore);
let slash = json!({
"type": "slash_commands",
"envelope_id": ENVELOPE_ID,
"payload": { "command": "/mycommand", "text": "args" }
});
assert_eq!(parse_socket_frame(&slash), SlackInboundEvent::Ignore);
}
#[test]
fn ack_frame_echoes_envelope_id_only() {
let env = block_actions_envelope(APPROVE_ACTION_ID, APPROVAL_ID);
let ack = build_ack_frame(&env).expect("an interactive envelope has an envelope_id");
assert_eq!(
ack,
json!({ "envelope_id": ENVELOPE_ID }),
"ack frame is exactly {{envelope_id}}"
);
let hello = json!({ "type": "hello" });
assert_eq!(build_ack_frame(&hello), None, "no envelope_id ⇒ no ack frame");
}
#[test]
fn socket_url_response_ok_with_no_url_is_err_not_empty_ok() {
let no_url = json!({ "ok": true });
assert!(
parse_socket_url_response(&no_url).is_err(),
"ok:true with no url ⇒ Err (not Ok(\"\"))"
);
let empty_url = json!({ "ok": true, "url": "" });
assert!(
parse_socket_url_response(&empty_url).is_err(),
"ok:true with an empty url ⇒ Err"
);
let not_ok = json!({ "ok": false, "error": "invalid_auth" });
let err = parse_socket_url_response(¬_ok).unwrap_err();
assert!(
err.contains("invalid_auth"),
"ok:false surfaces the Slack error, got {err:?}"
);
let good = json!({ "ok": true, "url": "wss://wss-primary.slack.com/link/?ticket=abc" });
assert_eq!(
parse_socket_url_response(&good).unwrap(),
"wss://wss-primary.slack.com/link/?ticket=abc",
"a well-formed ok:true body yields the socket url"
);
}