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