1: -module(mam_misc_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("proper/include/proper.hrl").
    5: -include_lib("eunit/include/eunit.hrl").
    6: -include_lib("exml/include/exml.hrl").
    7: -include("mongoose_ns.hrl").
    8: 
    9: -import(mod_mam_utils,
   10:         [
   11:          is_archivable_message/4,
   12:          is_valid_message/4
   13:         ]).
   14: 
   15: all() ->
   16:     [
   17:      test_encode_decode_functionality,
   18:      {group, should_archive}
   19:     ].
   20: 
   21: groups() ->
   22:     [
   23:      {should_archive, [parallel],
   24:       [
   25:        non_messages_are_always_false,
   26:        messages_type_error_false,
   27:        other_message_types_return_false,
   28:        must_be_rejected,
   29:        must_be_accepted
   30:       ]}
   31:     ].
   32: 
   33: init_per_suite(Config) ->
   34:     {ok, _} = application:ensure_all_started(jid),
   35:     Config.
   36: 
   37: end_per_suite(Config) ->
   38:     Config.
   39: 
   40: test_encode_decode_functionality(_Config) ->
   41:     PossibleDomainNames = [<<"a">>, <<"b">>, <<"c">>, <<"d">>, <<"e">>, <<"f">>],
   42:     PossibleUserNames = [<<"">> | PossibleDomainNames],
   43:     PossibleResourceNames = [<<"just:some@random/text here">> | PossibleUserNames],
   44:     PossibleJIDs = [{U, D, R, jid:make(U, D, R)} || U <- PossibleUserNames,
   45:                                                     D <- PossibleDomainNames,
   46:                                                     R <- PossibleResourceNames],
   47:     FailedJIDs = [[{U1, D1, R1}, {U2, D2, R2}, EncodedJID, DecodedJID]
   48:                   || {U1, D1, R1, JID1} <- PossibleJIDs,
   49:                      {U2, D2, R2, JID2} <- PossibleJIDs,
   50:                      EncodedJID <- [catch mam_jid_mini:encode(JID1, JID2)],
   51:                      DecodedJID <- [catch mam_jid_mini:decode(JID1, EncodedJID)],
   52:                      DecodedJID =/= JID2],
   53:     case lists:sublist(FailedJIDs, 100) of
   54:         [] -> ok;
   55:         First100FailedJIDs ->
   56:             [ct:log("~nJID encoding/decoding failed:~n"
   57:                     "\tbase JID      - ~p~n"
   58:                     "\tJID to encode - ~p~n"
   59:                     "\tencoded JID   - ~p~n"
   60:                     "\tdecoded JID   - ~p~n", Params) || Params <- First100FailedJIDs],
   61:             ct:fail("Failed to encode/decode some of the JIDs,"
   62:                     " see test suite logs for more details", [])
   63:     end.
   64: 
   65: non_messages_are_always_false(_) ->
   66:     NonMsgPacket = ?LET({T, Attrs, Children},
   67:                         {oneof([<<"iq">>, <<"presence">>]), gen_attrs(all), gen_children()},
   68:                         packet(T, Attrs, Children)),
   69:     Prop = ?FORALL({Mod, Dir, Packet, Acm},
   70:                    {gen_modules(), gen_directions(), NonMsgPacket, gen_arc_markers()},
   71:                    not is_archivable_message(Mod, Dir, Packet, Acm)),
   72:     run_prop(?FUNCTION_NAME, Prop).
   73: 
   74: messages_type_error_false(_) ->
   75:     ErrorMsgDef = ?LET({Attrs, Children},
   76:                        {gen_attrs(<<"error">>), gen_children()},
   77:                        packet(<<"message">>, Attrs, Children)),
   78:     Prop = ?FORALL({Mod, Dir, ErrorMessage, Acm},
   79:                    {gen_modules(), gen_directions(), ErrorMsgDef, gen_arc_markers()},
   80:                    not is_archivable_message(Mod, Dir, ErrorMessage, Acm)),
   81:     run_prop(?FUNCTION_NAME, Prop).
   82: 
   83: other_message_types_return_false(_) ->
   84:     StrangeTypeDef = ?LET({Attrs, Children},
   85:                           {gen_attrs(strange), gen_children()},
   86:                           packet(<<"message">>, Attrs, Children)),
   87:     Prop = ?FORALL({Mod, Dir, StrangeType, Acm},
   88:                    {gen_modules(), gen_directions(), StrangeTypeDef, gen_arc_markers()},
   89:                    not is_archivable_message(Mod, Dir, StrangeType, Acm)),
   90:     run_prop(?FUNCTION_NAME, Prop).
   91: 
   92: must_be_rejected(_) ->
   93:     MustContain = oneof([no_store, delay, result]),
   94:     MsgDef = ?LET({Attrs, Children},
   95:                   {gen_attrs(good), gen_children(MustContain, [])},
   96:                   packet(<<"message">>, Attrs, Children)),
   97:     Prop = ?FORALL({Mod, Dir, Msg, Acm},
   98:                    {gen_modules(), gen_directions(), MsgDef, gen_arc_markers()},
   99:                    not is_valid_message(Mod, Dir, Msg, Acm)),
  100:     run_prop(?FUNCTION_NAME, Prop).
  101: 
  102: must_be_accepted(_) ->
  103:     MustContain = oneof([body, store, retraction]),
  104:     MustNotContain = [no_store, delay, result],
  105:     MsgDef = ?LET({Attrs, Children},
  106:                   {gen_attrs(good), gen_children(MustContain, MustNotContain)},
  107:                   packet(<<"message">>, Attrs, Children)),
  108:     Prop = ?FORALL({Mod, Dir, Msg, Acm},
  109:                    {gen_modules(), gen_directions(), MsgDef, gen_arc_markers()},
  110:                    is_valid_message(Mod, Dir, Msg, Acm)),
  111:     run_prop(?FUNCTION_NAME, Prop).
  112: 
  113: %% Generators
  114: gen_modules() ->
  115:     oneof([mod_mam_pm, mod_inbox]).
  116: 
  117: gen_directions() ->
  118:     oneof([outgoing, incoming]).
  119: 
  120: gen_arc_markers() ->
  121:     boolean().
  122: 
  123: gen_attrs(Bin) when is_binary(Bin) ->
  124:     ?LET({From, To}, {gen_jid(), gen_jid()}, attrs(From, To, Bin));
  125: gen_attrs(Type) when is_atom(Type) ->
  126:     ?LET({From, To, MsgType}, {gen_jid(), gen_jid(), gen_msg_type(Type)}, attrs(From, To, MsgType)).
  127: 
  128: gen_msg_type(all) ->
  129:     oneof([<<"normal">>, <<"chat">>, <<"groupchat">>, <<"error">>]);
  130: gen_msg_type(good) ->
  131:     oneof([<<"normal">>, <<"chat">>, <<"groupchat">>]);
  132: gen_msg_type(strange) ->
  133:     ?SUCHTHAT(B, non_empty(binary()),
  134:               not lists:member(B, [<<"normal">>, <<"chat">>, <<"groupchat">>, <<"error">>])).
  135: 
  136: gen_jid() ->
  137:     oneof([alice(), bob(), room()]).
  138: 
  139: gen_children() ->
  140:     do_gen_children([undefined], []).
  141: 
  142: gen_children([], F) ->
  143:     do_gen_children([undefined], F);
  144: gen_children(F1, F2) ->
  145:     do_gen_children(F1, F2).
  146: 
  147: do_gen_children(ForceOneYes, MustNotContain) ->
  148:     ?LET({B1, B2, B3, B4, B5, B6, B7, OneYes},
  149:          {boolean(), boolean(), boolean(), boolean(), boolean(), boolean(), boolean(), ForceOneYes},
  150:          begin
  151:              Elems = [{body, body(), B1},
  152:                       {store, store(), B2},
  153:                       {marker, chat_marker(), B3},
  154:                       {retraction, retraction(), B4},
  155:                       {result, mam_result(), B5},
  156:                       {delay, offline_delay(), B6},
  157:                       {no_store, no_store(), B7}],
  158:              Children = [ maybe_get(Val, OneYes, MustNotContain) || Val <- Elems ],
  159:              lists:filter(fun(El) -> El =/= false end, Children)
  160:          end).
  161: 
  162: maybe_get({Elem, XmlElem, _}, Elem, _) ->
  163:     XmlElem;
  164: maybe_get({Elem, XmlElem, Maybe}, _, MustNotContain) ->
  165:     Maybe andalso not lists:member(Elem, MustNotContain) andalso XmlElem.
  166: 
  167: %% Possible XML elements
  168: attrs(From, To, Type) ->
  169:     [{<<"from">>, From}, {<<"to">>, To}, {<<"type">>, Type}].
  170: body() ->
  171:     #xmlel{name = <<"body">>, children = [#xmlcdata{content = bin()}]}.
  172: chat_marker() ->
  173:     #xmlel{name = <<"displayed">>, attrs = [{<<"xmlmn">>, ?NS_CHAT_MARKERS}, {<<"id">>, bin()}]}.
  174: retraction() ->
  175:     #xmlel{name = <<"apply-to">>,
  176:            attrs = [{<<"id">>, bin()}, {<<"xmlns">>, ?NS_FASTEN}],
  177:            children = [#xmlel{name = <<"retract">>, attrs = [{<<"xmlns">>, ?NS_RETRACT}]}]}.
  178: mam_result() ->
  179:     #xmlel{name = <<"result">>,
  180:            attrs = [{<<"id">>, bin()}, {<<"queryid">>, bin()}, {<<"xmlns">>, ?NS_MAM_06}]}.
  181: offline_delay() ->
  182:     #xmlel{name = <<"delay">>, attrs = [{<<"stamp">>, bin()}, {<<"xmlns">>, ?NS_DELAY}]}.
  183: no_store() ->
  184:     #xmlel{name = <<"no-store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]}.
  185: store() ->
  186:     #xmlel{name = <<"store">>, attrs = [{<<"xmlns">>, ?NS_HINTS}]}.
  187: packet(Name, Attrs, Children) ->
  188:     #xmlel{name = Name, attrs = Attrs, children = Children}.
  189: 
  190: %% Helpers
  191: bin() ->
  192:     base16:encode(crypto:strong_rand_bytes(8)).
  193: alice() ->
  194:     <<"alice@localhost">>.
  195: bob() ->
  196:     <<"bob@localhost">>.
  197: room() ->
  198:     <<"room@muclight.localhost">>.
  199: 
  200: run_prop(PropName, Property) ->
  201:     Opts = [quiet, long_result, {start_size, 2}, {numtests, 1000},
  202:             {numworkers, erlang:system_info(schedulers_online)}],
  203:     case proper:quickcheck(proper:conjunction([{PropName, Property}]), Opts) of
  204:         true -> ok;
  205:         Res -> ct:fail(Res)
  206:     end.