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]).