1: -module(event_pusher_sns_SUITE). 2: 3: -compile([export_all, nowarn_export_all]). 4: 5: -include("mongoose.hrl"). 6: -include("mod_event_pusher_events.hrl"). 7: -include_lib("exml/include/exml.hrl"). 8: -include_lib("common_test/include/ct.hrl"). 9: -include_lib("eunit/include/eunit.hrl"). 10: 11: -define(ACC_PARAMS, #{location => ?LOCATION, 12: host_type => host_type(), 13: lserver => domain(), 14: element => undefined}). 15: 16: -import(config_parser_helper, [config/2, mod_config/2]). 17: 18: all() -> 19: [ 20: handles_unicode_messages, 21: forwards_chat_messages_to_chat_topic, 22: forwards_groupchat_messages_to_groupchat_topic, 23: does_not_forward_other_messages, 24: creates_proper_sns_topic_arn, 25: forwards_online_presence_to_presence_topic, 26: forwards_offline_presence_to_presence_topic, 27: does_not_forward_messages_without_body, 28: does_not_forward_messages_when_topic_is_unset, 29: does_not_forward_presences_when_topic_is_unset, 30: calls_callback_module_to_get_user_id, 31: calls_callback_module_to_retrieve_attributes_for_presence, 32: calls_callback_module_to_retrieve_attributes_for_message 33: ]. 34: 35: %% Tests 36: 37: handles_unicode_messages(Config) -> 38: expect_message_entry(message, <<"❤☀☆☂☻♞☯☭☢€"/utf8>>), 39: send_packet_callback(Config, <<"chat">>, <<"❤☀☆☂☻♞☯☭☢€"/utf8>>). 40: 41: forwards_chat_messages_to_chat_topic(Config) -> 42: expect_topic("user_message_sent-dev-1"), 43: send_packet_callback(Config, <<"chat">>, <<"message">>). 44: 45: forwards_groupchat_messages_to_groupchat_topic(Config) -> 46: expect_topic("user_messagegroup_sent-dev-1"), 47: send_packet_callback(Config, <<"groupchat">>, <<"message">>). 48: 49: does_not_forward_other_messages(Config) -> 50: meck:expect(erlcloud_sns, publish, fun(_, _, _, _, _, _) -> ok end), 51: send_packet_callback(Config, <<"othertype">>, <<"message">>), 52: ?assertNot(meck:called(erlcloud_sns, publish, '_')). 53: 54: creates_proper_sns_topic_arn(Config) -> 55: meck:expect(erlcloud_sns, publish, fun(_, _, _, _, _, _) -> ok end), 56: ExpectedTopic = craft_arn("user_message_sent-dev-1"), 57: send_packet_callback(Config, <<"chat">>, <<"message">>), 58: ?assert(meck:called(erlcloud_sns, publish, [topic, ExpectedTopic, '_', '_', '_', '_'])). 59: 60: forwards_online_presence_to_presence_topic(Config) -> 61: expect_message_entry(present, true), 62: user_present_callback(Config), 63: ExpectedTopic = craft_arn("user_presence_updated-dev-1"), 64: ?assert(meck:called(erlcloud_sns, publish, [topic, ExpectedTopic, '_', '_', '_', '_'])). 65: 66: forwards_offline_presence_to_presence_topic(Config) -> 67: expect_message_entry(present, false), 68: user_not_present_callback(Config), 69: ExpectedTopic = craft_arn("user_presence_updated-dev-1"), 70: ?assert(meck:called(erlcloud_sns, publish, [topic, ExpectedTopic, '_', '_', '_', '_'])). 71: 72: does_not_forward_messages_without_body(Config) -> 73: meck:expect(erlcloud_sns, publish, fun(_, _, _, _, _, _) -> ok end), 74: send_packet_callback(Config, <<"chat">>, undefined), 75: ?assertNot(meck:called(erlcloud_sns, publish, '_')). 76: 77: does_not_forward_messages_when_topic_is_unset(Config) -> 78: meck:expect(erlcloud_sns, publish, fun(_, _, _, _, _, _) -> ok end), 79: send_packet_callback(Config, <<"chat">>, <<"message">>), 80: ?assertNot(meck:called(erlcloud_sns, publish, '_')). 81: 82: does_not_forward_presences_when_topic_is_unset(Config) -> 83: meck:expect(erlcloud_sns, publish, fun(_, _, _, _, _, _) -> ok end), 84: user_not_present_callback(Config), 85: ?assertNot(meck:called(erlcloud_sns, publish, '_')). 86: 87: calls_callback_module_to_get_user_id(Config) -> 88: Module = custom_callback_module("customuserid", #{}), 89: expect_message_entry(user_id, "customuserid"), 90: user_not_present_callback(Config), 91: ?assert(meck:called(Module, user_guid, '_')). 92: 93: calls_callback_module_to_retrieve_attributes_for_presence(Config) -> 94: Module = custom_callback_module("", #{"a" => 123}), 95: expect_attributes("a", 123), 96: user_not_present_callback(Config), 97: ?assert(meck:called(Module, message_attributes, ['_', '_', '_'])). 98: 99: calls_callback_module_to_retrieve_attributes_for_message(Config) -> 100: Module = custom_callback_module("", #{"b" => "abc"}), 101: expect_attributes("b", "abc"), 102: send_packet_callback(Config, <<"groupchat">>, <<"message">>), 103: ?assert(meck:called(Module, message_attributes, ['_', '_', '_', '_', '_'])). 104: 105: %% Fixtures 106: 107: init_per_suite(Config) -> 108: {ok, _} = application:ensure_all_started(jid), 109: Config. 110: 111: end_per_suite(_) -> 112: ok. 113: 114: init_per_testcase(CaseName, Config) -> 115: mongooseim_helper:start_link_loaded_hooks(), 116: meck:new(erlcloud_sns, [non_strict, passthrough]), 117: meck:new([mongoose_wpool, mongoose_metrics], [stub_all]), 118: meck:expect(erlcloud_sns, new, fun(_, _, _) -> mod_aws_sns_SUITE_erlcloud_sns_new end), 119: meck:expect(mongoose_wpool, start, fun(_, _, _, _) -> {ok, mocked} end), 120: meck:expect(mongoose_wpool, cast, fun(_, _, _, {M, F, A}) -> erlang:apply(M, F, A) end), 121: start_modules(sns_config(CaseName)), 122: [{sender, jid:from_binary(<<"sender@localhost">>)}, 123: {recipient, jid:from_binary(<<"recipient@localhost">>)} | 124: Config]. 125: 126: end_per_testcase(_, _Config) -> 127: stop_modules(), 128: meck:unload(). 129: 130: %% Wrapped callbacks 131: 132: send_packet_callback(Config, Type, Body) -> 133: Packet = message(Config, Type, Body), 134: Sender = ?config(sender, Config), 135: Recipient = ?config(recipient, Config), 136: mod_event_pusher_sns:push_event(mongoose_acc:new(?ACC_PARAMS), 137: #chat_event{type = chat, direction = in, 138: from = Sender, to = Recipient, 139: packet = Packet}). 140: 141: user_present_callback(Config) -> 142: Jid = ?config(sender, Config), 143: mod_event_pusher_sns:push_event(mongoose_acc:new(?ACC_PARAMS), 144: #user_status_event{jid = Jid, status = online}). 145: 146: user_not_present_callback(Config) -> 147: Jid = ?config(sender, Config), 148: mod_event_pusher_sns:push_event(mongoose_acc:new(?ACC_PARAMS), 149: #user_status_event{jid = Jid, status = offline}). 150: 151: %% Helpers 152: 153: custom_callback_module(UserId, Attributes) -> 154: Module = custom_callback_module(), 155: meck:new(Module, [non_strict]), 156: meck:expect(Module, user_guid, fun(_) -> UserId end), 157: meck:expect(Module, message_attributes, fun(_, _, _) -> Attributes end), 158: meck:expect(Module, message_attributes, fun(_, _, _, _, _) -> Attributes end), 159: Module. 160: 161: custom_callback_module() -> 162: mod_aws_sns_SUITE_mockcb. 163: 164: craft_arn(Topic) -> 165: #{region := Region, account_id := AccountId} = common_sns_opts(), 166: craft_arn(Region, AccountId, Topic). 167: 168: craft_arn(Region, UserId, Topic) -> 169: "arn:aws:sns:" ++ Region ++ ":" ++ UserId ++ ":" ++ Topic. 170: 171: expect_topic(ExpectedTopic) -> 172: meck:expect(erlcloud_sns, publish, 173: fun(topic, Topic, _, _, _, _) -> true = lists:suffix(ExpectedTopic, Topic) end). 174: 175: expect_message_entry(Key, Value) -> 176: meck:expect( 177: erlcloud_sns, publish, 178: fun(_, _, TupleList, _, _, _) -> 179: MessageObject = maps:from_list(TupleList), 180: Value = maps:get(Key, MessageObject) 181: end). 182: 183: expect_attributes(Key, Value) -> 184: meck:expect( 185: erlcloud_sns, publish, 186: fun(_, _, _, _, Attrs, _) -> Value = proplists:get_value(Key, Attrs) end). 187: 188: sns_config(does_not_forward_messages_when_topic_is_unset) -> 189: maps:remove(pm_messages_topic, common_sns_opts()); 190: sns_config(does_not_forward_presences_when_topic_is_unset) -> 191: maps:remove(presence_updates_topic, common_sns_opts()); 192: sns_config(CN) when CN =:= calls_callback_module_to_get_user_id; 193: CN =:= calls_callback_module_to_retrieve_attributes_for_presence; 194: CN =:= calls_callback_module_to_retrieve_attributes_for_message -> 195: (common_sns_opts())#{plugin_module => custom_callback_module()}; 196: sns_config(_) -> 197: common_sns_opts(). 198: 199: start_modules(SNSExtra) -> 200: mongoose_config:set_opts(opts(SNSExtra)), 201: mongoose_modules:start(). 202: 203: stop_modules() -> 204: mongoose_modules:stop(), 205: mongoose_config:erase_opts(). 206: 207: opts(SNSExtra) -> 208: #{hosts => [host_type()], 209: host_types => [], 210: all_metrics_are_global => false, 211: {modules, host_type()} => modules(SNSExtra)}. 212: 213: modules(SNSExtra) -> 214: gen_mod_deps:resolve_deps(host_type(), #{mod_event_pusher => module_opts(SNSExtra)}). 215: 216: module_opts(SNSExtra) -> 217: SNSOpts = config([modules, mod_event_pusher, sns], SNSExtra), 218: mod_config(mod_event_pusher, #{sns => SNSOpts}). 219: 220: common_sns_opts() -> 221: #{sns_host => "sns.eu-west-1.amazonaws.com", 222: region => "eu-west-1", 223: access_key_id => "AKIAJAZYHOIPY6A2PESA", 224: secret_access_key => "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", 225: account_id => "251423380551", 226: presence_updates_topic => "user_presence_updated-dev-1", 227: pm_messages_topic => "user_message_sent-dev-1", 228: muc_messages_topic => "user_messagegroup_sent-dev-1"}. 229: 230: message(Config, Type, Body) -> 231: message(?config(sender, Config), ?config(recipient, Config), Type, Body). 232: 233: message(From, Recipient, Type, Body) -> 234: Children = 235: case Body of 236: undefined -> []; 237: _ -> [#xmlel{name = <<"body">>, children = [#xmlcdata{content = Body}]}] 238: end, 239: #xmlel{name = <<"message">>, 240: attrs = [{<<"from">>, jid:to_binary(From)}, {<<"type">>, Type}, 241: {<<"to">>, jid:to_binary(Recipient)}], 242: children = Children}. 243: 244: host_type() -> 245: domain(). 246: 247: domain() -> 248: <<"localhost">>.