1: -module(mam_proper_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: -include_lib("proper/include/proper.hrl"). 4: -include_lib("eunit/include/eunit.hrl"). 5: 6: -import(distributed_helper, [mim/0, 7: require_rpc_nodes/1, 8: rpc/4]). 9: -import(domain_helper, [host_type/0]). 10: 11: %% Common Test init/teardown functions 12: suite() -> 13: require_rpc_nodes([mim]) ++ escalus:suite(). 14: 15: all() -> 16: [{group, async_writer}]. 17: 18: groups() -> 19: [{async_writer, [], [async_writer_store]}]. 20: 21: init_per_suite(C) -> 22: application:ensure_all_started(jid), 23: C1 = dynamic_modules:save_modules(host_type(), C), 24: escalus:init_per_suite(C1). 25: 26: end_per_suite(C) -> 27: dynamic_modules:restore_modules(C), 28: escalus:end_per_suite(C). 29: 30: init_per_group(G, _C) -> 31: case mongoose_helper:is_rdbms_enabled(domain_helper:host_type()) of 32: true -> dynamic_modules:ensure_modules(host_type(), required_modules(G)); 33: false -> {skip, "rdbms not enabled"} 34: end. 35: 36: required_modules(_G) -> 37: [{mod_mam, mam_helper:config_opts(#{pm => #{}})}]. 38: 39: end_per_group(_G, C) -> 40: C. 41: 42: init_per_testcase(Name, C) -> 43: escalus:init_per_testcase(Name, C). 44: 45: end_per_testcase(Name, C) -> 46: escalus:end_per_testcase(Name, C). 47: 48: %% Common Test Cases 49: async_writer_store(C) -> 50: run_prop(async_writer_store, async_writer_store_prop(C)). 51: 52: %% Proper Test Cases 53: async_writer_store_prop(_C) -> 54: HostType = domain_helper:host_type(), 55: ?FORALL(ParamsList, params_list(), async_writer_store_check(HostType, ParamsList)). 56: 57: async_writer_store_check(HostType, ParamsList) -> 58: clean_db(HostType), 59: Name = prepare_insert(length(ParamsList)), 60: Rows = [prepare_message(HostType, Params) || Params <- ParamsList], 61: case execute(HostType, Name, lists:append(Rows)) of 62: {updated, _} -> true; 63: Other -> 64: ct:pal("ParamsList ~p~nOther ~p", [ParamsList, Other]), 65: false 66: end. 67: 68: %% Proper Type Generators 69: usernames() -> 70: [<<"alice">>, <<"bob">>, <<"bob.1">>, <<"bob44">>, <<"a">>, <<"xd">>, 71: <<"admin">>]. 72: 73: resources() -> 74: [<<"res1">>, <<"PICKLE">>, <<"1">>, <<"xdxd">>]. 75: 76: origin_id() -> 77: oneof([none, <<"orig_id">>, 78: proper_types:non_empty(strip_nulls(proper_unicode:utf8(5)))]). 79: 80: strip_nulls(Gen) -> 81: ?LET(X, Gen, do_strip_nulls(X)). 82: 83: do_strip_nulls(B) -> 84: %% PostgreSQL doesn't support storing NULL (\0x00) characters in text fields 85: List = unicode:characters_to_list(B), 86: List2 = [X || X <- List, X =/= 0], 87: unicode:characters_to_binary(List2). 88: 89: username() -> 90: ?SUCHTHAT(U, proper_types:non_empty(oneof(usernames())), 91: jid:nodeprep(U) =/= error). 92: 93: hostname() -> 94: proper_types:oneof([<<"localhost">>, <<"otherhost">>, <<"example.com">>]). 95: 96: resource() -> 97: ?SUCHTHAT(R, proper_types:non_empty(oneof(resources())), 98: jid:resourceprep(R) =/= error). 99: 100: jid() -> 101: ?SUCHTHAT(Jid, maybe_invalid_jid(), Jid =/= error). 102: 103: maybe_invalid_jid() -> 104: ?LET({U, S, R}, 105: {username(), hostname(), resource()}, 106: make_jid(U, S, R)). 107: 108: make_jid(U, S, R) when is_binary(U), is_binary(S), is_binary(R) -> 109: mongoose_helper:make_jid(U, S, R). 110: 111: direction() -> 112: proper_types:oneof([incoming, outgoing]). 113: 114: body() -> 115: strip_nulls(proper_unicode:utf8(1000)). 116: 117: packet(To, Body) -> 118: escalus_stanza:chat_to(jid:to_binary(To), Body). 119: 120: %% Generates mod_mam:archive_message_params() 121: params() -> 122: ?LET({MessId, ArcId, LocalJid, RemoteJid, 123: OriginId, Dir, Body, SenderId, IsGroupChat}, 124: {non_neg_integer(), non_neg_integer(), jid(), jid(), 125: origin_id(), direction(), body(), non_neg_integer(), boolean()}, 126: #{message_id => MessId, archive_id => ArcId, 127: local_jid => LocalJid, remote_jid => RemoteJid, 128: source_jid => choose_source_jid(Dir, LocalJid, RemoteJid), 129: origin_id => OriginId, direction => Dir, 130: packet => packet(choose_dest_jid(Dir, LocalJid, RemoteJid), Body), 131: sender_id => SenderId, is_groupchat => IsGroupChat}). 132: 133: choose_source_jid(incoming, _LocalJid, RemoteJid) -> RemoteJid; 134: choose_source_jid(outgoing, LocalJid, _RemoteJid) -> LocalJid. 135: 136: choose_dest_jid(outgoing, _LocalJid, RemoteJid) -> RemoteJid; 137: choose_dest_jid(incoming, LocalJid, _RemoteJid) -> LocalJid. 138: 139: params_list() -> 140: ?LET(ParamsList, proper_types:non_empty(proper_types:list(params())), 141: with_unique_message_id(ParamsList)). 142: 143: with_unique_message_id(ParamsList) -> 144: Pairs = [{MessId, Params} || #{message_id := MessId} = Params <- ParamsList], 145: %% Removes duplicates 146: maps:values(maps:from_list(Pairs)). 147: 148: %% Proper Helper Functions 149: run_prop(PropName, Property) -> 150: Opts = [verbose, long_result, {numtests, 30}], 151: ?assertEqual(true, quickcheck(PropName, Property, Opts)). 152: 153: quickcheck(PropName, Property, Opts) -> 154: proper:quickcheck(proper:conjunction([{PropName, Property}]), Opts). 155: 156: %% Helper Functions 157: prepare_insert(Len) -> 158: Name = multi_name(insert_mam_message_prop, Len), 159: rpc(mim(), mod_mam_rdbms_arch, prepare_insert, [Name, Len]), 160: Name. 161: 162: multi_name(Name, Times) -> 163: list_to_atom(atom_to_list(Name) ++ integer_to_list(Times)). 164: 165: prepare_message(HostType, Params) -> 166: rpc(mim(), mod_mam_rdbms_arch, prepare_message, [HostType, Params]). 167: 168: execute(HostType, BatchName, Args) -> 169: rpc(mim(), mongoose_rdbms, execute, [HostType, BatchName, Args]). 170: 171: clean_db(HostType) -> 172: Q = <<"DELETE FROM mam_message">>, 173: rpc(mim(), mongoose_rdbms, sql_query, [HostType, Q]).