1: -module(disco_and_caps_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("eunit/include/eunit.hrl").
    5: -include_lib("escalus/include/escalus_xmlns.hrl").
    6: 
    7: -import(domain_helper, [host_type/0, domain/0]).
    8: 
    9: all() ->
   10:     [{group, disco_with_caps},
   11:      {group, disco_with_extra_features}].
   12: 
   13: groups() ->
   14:     G = [{disco_with_caps, [parallel], basic_test_cases() ++ caps_test_cases()},
   15:          {disco_with_extra_features, [parallel], basic_test_cases() ++ extra_feature_test_cases()}],
   16:     ct_helper:repeat_all_until_all_ok(G).
   17: 
   18: basic_test_cases() ->
   19:     [user_cannot_query_stranger_resources,
   20:      user_cannot_query_stranger_features,
   21:      user_can_query_friend_resources,
   22:      user_can_query_friend_features,
   23:      user_cannot_query_own_resources_with_unknown_node,
   24:      user_cannot_query_friend_resources_with_unknown_node,
   25:      user_can_query_server_features].
   26: 
   27: caps_test_cases() ->
   28:     [caps_feature_is_advertised,
   29:      user_can_query_server_caps_via_disco].
   30: 
   31: extra_feature_test_cases() ->
   32:     [user_can_query_extra_domains,
   33:      user_can_query_server_info].
   34: 
   35: init_per_suite(C) ->
   36:     C.
   37: 
   38: end_per_suite(C) ->
   39:     escalus_fresh:clean(),
   40:     escalus:end_per_suite(C).
   41: 
   42: init_per_group(Name, C) ->
   43:     C2 = escalus:init_per_suite(dynamic_modules:save_modules(host_type(), C)),
   44:     dynamic_modules:ensure_modules(host_type(), required_modules(Name)),
   45:     C2.
   46: 
   47: end_per_group(_Name, C) ->
   48:     dynamic_modules:restore_modules(C).
   49: 
   50: init_per_testcase(Name, C) ->
   51:     escalus:init_per_testcase(Name, C).
   52: 
   53: end_per_testcase(Name, C) ->
   54:     escalus:end_per_testcase(Name, C).
   55: 
   56: caps_feature_is_advertised(Config) ->
   57:     Spec = escalus_users:get_userspec(Config, alice),
   58:     {ok, Connection, Features} = escalus_connection:start(Spec, [start_stream, stream_features]),
   59:     true = is_map(proplists:get_value(caps, Features)),
   60:     escalus_connection:stop(Connection).
   61: 
   62: user_can_query_server_caps_via_disco(Config) ->
   63:     NewConfig = escalus_fresh:create_users(Config, [{alice, 1}]),
   64:     Spec = escalus_users:get_userspec(NewConfig, alice),
   65:     {ok, Alice, Features} = escalus_connection:start(Spec),
   66:     #{<<"node">> := Node,
   67:       <<"ver">> := Ver} = proplists:get_value(caps, Features),
   68:     NodeVer = <<Node/binary, $#, Ver/binary>>,
   69:     Server = proplists:get_value(server, Spec),
   70:     Disco = escalus_stanza:disco_info(Server, NodeVer),
   71:     escalus:send(Alice, Disco),
   72:     DiscoResp = escalus:wait_for_stanza(Alice),
   73:     escalus:assert(is_iq_result, [Disco], DiscoResp),
   74:     Identity  = exml_query:path(DiscoResp, [{element, <<"query">>},
   75:                                             {element, <<"identity">>},
   76:                                             {attr, <<"name">>}]),
   77:     <<"MongooseIM">> = Identity,
   78:     escalus_connection:stop(Alice).
   79: 
   80: user_cannot_query_stranger_resources(Config) ->
   81:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
   82:         BobJid = escalus_client:short_jid(Bob),
   83:         Request = escalus_stanza:disco_items(BobJid),
   84:         escalus:send(Alice, Request),
   85:         Stanza = escalus:wait_for_stanza(Alice),
   86:         escalus:assert(is_iq_error, [Request], Stanza),
   87:         escalus:assert(is_error, [<<"cancel">>, <<"service-unavailable">>], Stanza),
   88:         escalus:assert(is_stanza_from, [BobJid], Stanza)
   89:     end).
   90: 
   91: user_cannot_query_stranger_features(Config) ->
   92:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
   93:         BobJid = escalus_client:short_jid(Bob),
   94:         Request = escalus_stanza:disco_info(BobJid),
   95:         escalus:send(Alice, Request),
   96:         Stanza = escalus:wait_for_stanza(Alice),
   97:         escalus:assert(is_iq_error, [Request], Stanza),
   98:         escalus:assert(is_error, [<<"cancel">>, <<"service-unavailable">>], Stanza),
   99:         escalus:assert(is_stanza_from, [BobJid], Stanza)
  100:     end).
  101: 
  102: user_can_query_friend_resources(Config) ->
  103:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  104:         escalus_story:make_all_clients_friends([Alice, Bob]),
  105:         BobJid = escalus_client:short_jid(Bob),
  106:         escalus:send(Alice, escalus_stanza:disco_items(BobJid)),
  107:         Stanza = escalus:wait_for_stanza(Alice),
  108:         Query = exml_query:subelement(Stanza, <<"query">>),
  109:         BobFullJid = escalus_client:full_jid(Bob),
  110:         BobName = escalus_client:username(Bob),
  111:         Item = exml_query:subelement_with_attr(Query, <<"jid">>, BobFullJid),
  112:         ?assertEqual(BobName, exml_query:attr(Item, <<"name">>)),
  113:         escalus:assert(is_stanza_from, [BobJid], Stanza)
  114:     end).
  115: 
  116: user_can_query_friend_features(Config) ->
  117:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  118:         escalus_story:make_all_clients_friends([Alice, Bob]),
  119:         BobJid = escalus_client:short_jid(Bob),
  120:         escalus:send(Alice, escalus_stanza:disco_info(BobJid)),
  121:         Stanza = escalus:wait_for_stanza(Alice),
  122:         escalus:assert(has_identity, [<<"account">>, <<"registered">>], Stanza),
  123:         escalus:assert(is_stanza_from, [BobJid], Stanza)
  124:     end).
  125: 
  126: user_cannot_query_own_resources_with_unknown_node(Config) ->
  127:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  128:         AliceJid = escalus_client:short_jid(Alice),
  129:         Request = escalus_stanza:disco_items(AliceJid, <<"unknown-node">>),
  130:         escalus:send(Alice, Request),
  131:         Stanza = escalus:wait_for_stanza(Alice),
  132:         escalus:assert(is_iq_error, [Request], Stanza),
  133:         escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>], Stanza),
  134:         escalus:assert(is_stanza_from, [AliceJid], Stanza)
  135:     end).
  136: 
  137: user_cannot_query_friend_resources_with_unknown_node(Config) ->
  138:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  139:         escalus_story:make_all_clients_friends([Alice, Bob]),
  140:         BobJid = escalus_client:short_jid(Bob),
  141:         Request = escalus_stanza:disco_items(BobJid, <<"unknown-node">>),
  142:         escalus:send(Alice, Request),
  143:         Stanza = escalus:wait_for_stanza(Alice),
  144:         escalus:assert(is_iq_error, [Request], Stanza),
  145:         escalus:assert(is_error, [<<"cancel">>, <<"not-allowed">>], Stanza),
  146:         escalus:assert(is_stanza_from, [BobJid], Stanza)
  147:     end).
  148: 
  149: user_can_query_extra_domains(Config) ->
  150:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  151:         Server = escalus_client:server(Alice),
  152:         escalus:send(Alice, escalus_stanza:service_discovery(Server)),
  153:         Stanza = escalus:wait_for_stanza(Alice),
  154:         escalus:assert(has_service, [extra_domain()], Stanza),
  155:         escalus:assert(is_stanza_from, [domain()], Stanza)
  156:     end).
  157: 
  158: user_can_query_server_features(Config) ->
  159:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  160:         Server = escalus_client:server(Alice),
  161:         escalus:send(Alice, escalus_stanza:disco_info(Server)),
  162:         Stanza = escalus:wait_for_stanza(Alice),
  163:         escalus:assert(has_identity, [<<"server">>, <<"im">>], Stanza),
  164:         escalus:assert(has_feature, [<<"iq">>], Stanza),
  165:         escalus:assert(has_feature, [<<"presence">>], Stanza),
  166:         escalus:assert(has_feature, [<<"presence-invisible">>], Stanza),
  167:         escalus:assert(is_stanza_from, [domain()], Stanza)
  168:     end).
  169: 
  170: %% XEP-0157: Contact Addresses for XMPP Services
  171: user_can_query_server_info(Config) ->
  172:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  173:         Server = escalus_client:server(Alice),
  174:         escalus:send(Alice, escalus_stanza:disco_info(Server)),
  175:         Stanza = escalus:wait_for_stanza(Alice),
  176:         escalus:assert(is_stanza_from, [domain()], Stanza),
  177: 
  178:         %% 'sales' is hidden for mod_disco, so only 'abuse' and 'admin' are expected
  179:         [HiddenField, AbuseField, AdminField] = get_form_fields(Stanza),
  180:         ?assertEqual(<<"FORM_TYPE">>, exml_query:attr(HiddenField, <<"var">>)),
  181:         ?assertEqual(<<"hidden">>, exml_query:attr(HiddenField, <<"type">>)),
  182:         ?assertEqual([?NS_SERVERINFO], exml_query:paths(HiddenField, [{element, <<"value">>},
  183:                                                                       cdata])),
  184:         ?assertEqual(name(abuse), exml_query:attr(AbuseField, <<"var">>)),
  185:         ?assertEqual(urls(abuse), exml_query:paths(AbuseField, [{element, <<"value">>},
  186:                                                                               cdata])),
  187:         ?assertEqual(name(admin), exml_query:attr(AdminField, <<"var">>)),
  188:         ?assertEqual(urls(admin), exml_query:paths(AdminField, [{element, <<"value">>},
  189:                                                                 cdata]))
  190:     end).
  191: 
  192: %% Helpers
  193: 
  194: required_modules(disco_with_caps) ->
  195:     [{mod_caps, []},
  196:      {mod_disco, []}];
  197: required_modules(disco_with_extra_features) ->
  198:     [{mod_disco, [{extra_domains, [extra_domain()]},
  199:                   {server_info, [server_info(abuse, []),
  200:                                  server_info(admin, [{modules, [mod_disco]}]),
  201:                                  server_info(sales, [{modules, [mod_pubsub]}])]
  202:                   }]
  203:      }].
  204: 
  205: get_form_fields(Stanza) ->
  206:      exml_query:paths(Stanza, [{element_with_ns, <<"query">>, ?NS_DISCO_INFO},
  207:                                {element_with_ns, <<"x">>, ?NS_DATA_FORMS},
  208:                                {element, <<"field">>}]).
  209: 
  210: extra_domain() ->
  211:     <<"eXtra.example.com">>.
  212: 
  213: server_info(Type, Extra) ->
  214:     [{name, name(Type)}, {urls, urls(Type)} | Extra].
  215: 
  216: name(abuse) -> <<"abuse-addresses">>;
  217: name(admin) -> <<"admin-addresses">>;
  218: name(sales) -> <<"sales-addresses">>.
  219: 
  220: urls(abuse) -> [<<"abuse@example.com">>];
  221: urls(admin) -> [<<"admin@example.com">>, <<"operations@example.com">>];
  222: urls(sales) -> [<<"sales@example.com">>].