1: %%============================================================================== 2: %% Copyright 2013 Erlang Solutions Ltd. 3: %% 4: %% Test the mod_vcard running on the server. 5: %% 6: %% 7: %% Licensed under the Apache License, Version 2.0 (the "License"); 8: %% you may not use this file except in compliance with the License. 9: %% You may obtain a copy of the License at 10: %% 11: %% http://www.apache.org/licenses/LICENSE-2.0 12: %% 13: %% Unless required by applicable law or agreed to in writing, software 14: %% distributed under the License is distributed on an "AS IS" BASIS, 15: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16: %% See the License for the specific language governing permissions and 17: %% limitations under the License. 18: %%============================================================================== 19: 20: -module(vcard_simple_SUITE). 21: -compile([export_all, nowarn_export_all]). 22: 23: -include_lib("escalus/include/escalus_xmlns.hrl"). 24: -include_lib("escalus/include/escalus.hrl"). 25: -include_lib("common_test/include/ct.hrl"). 26: -include_lib("exml/include/exml.hrl"). 27: 28: %% Element CData 29: -define(EL(Element, Name), exml_query:path(Element, [{element, Name}])). 30: -define(EL_CD(Element, Name), exml_query:path(Element, [{element, Name}, cdata])). 31: 32: -import(vcard_helper, [is_vcard_ldap/0]). 33: 34: -import(distributed_helper, [mim/0, 35: require_rpc_nodes/1, 36: subhost_pattern/1, 37: rpc/4]). 38: -import(domain_helper, [host_type/0, 39: host_types/0, 40: domain/0]). 41: 42: %%-------------------------------------------------------------------- 43: %% Suite configuration 44: %%-------------------------------------------------------------------- 45: 46: all() -> 47: [{group, all} 48: ]. 49: 50: groups() -> 51: %% setting test data before tests is proving awkward so might as well use the 52: %% data set in the update tests to test the rest. 53: G = [{all, [sequence], all_tests()} 54: ], 55: ct_helper:repeat_all_until_all_ok(G). 56: 57: all_tests() -> 58: [update_own_card, 59: retrieve_own_card, 60: user_doesnt_exist, 61: update_other_card, 62: retrieve_others_card, 63: request_search_fields, 64: search_empty, 65: search_some, 66: search_wildcard]. 67: 68: suite() -> 69: require_rpc_nodes([mim]) ++ escalus:suite(). 70: 71: %%-------------------------------------------------------------------- 72: %% Init & teardown 73: %%-------------------------------------------------------------------- 74: 75: init_per_suite(Config) -> 76: Config1 = prepare_vcard_module(escalus:init_per_suite(Config)), 77: configure_mod_vcard(Config1), 78: escalus:create_users(Config1, escalus:get_users([alice, bob])). 79: 80: end_per_suite(Config) -> 81: NewConfig = escalus:delete_users(Config, escalus:get_users([alice, bob])), 82: restore_vcard_module(NewConfig), 83: escalus:end_per_suite(NewConfig). 84: 85: init_per_group(_GN, Config) -> 86: Config. 87: 88: end_per_group(_, Config) -> 89: Config. 90: 91: init_per_testcase(CaseName, Config) -> 92: escalus:init_per_testcase(CaseName, Config). 93: 94: end_per_testcase(CaseName, Config) -> 95: escalus:end_per_testcase(CaseName, Config). 96: 97: 98: %%-------------------------------------------------------------------- 99: %% XEP-0054: vcard-temp Test cases 100: %%-------------------------------------------------------------------- 101: 102: update_own_card(Config) -> 103: case is_vcard_ldap() of 104: true -> 105: {skip,ldap_vcard_is_readonly}; 106: _ -> 107: escalus:story( 108: Config, [{alice, 1}], 109: fun(Client1) -> 110: %% set some initial value different from the actual test data 111: %% so we know it really got updated and wasn't just old data 112: FN = get_FN(Config), 113: Client1Fields = [{<<"FN">>, FN}], 114: Client1SetResultStanza 115: = escalus:send_and_wait(Client1, 116: escalus_stanza:vcard_update(Client1Fields)), 117: escalus:assert(is_iq_result, Client1SetResultStanza), 118: escalus_stanza:vcard_request(), 119: Client1GetResultStanza 120: = escalus:send_and_wait(Client1, escalus_stanza:vcard_request()), 121: FN 122: = stanza_get_vcard_field_cdata(Client1GetResultStanza, <<"FN">>) 123: end) 124: end. 125: 126: retrieve_own_card(Config) -> 127: escalus:story( 128: Config, [{alice, 1}], 129: fun(Client) -> 130: Res = escalus:send_and_wait(Client, 131: escalus_stanza:vcard_request()), 132: ClientVCardTups = [{<<"FN">>, get_FN(Config)}], 133: check_vcard(ClientVCardTups, Res) 134: end). 135: 136: 137: 138: %% If no vCard exists, the server MUST return a stanza error 139: %% (which SHOULD be <item-not-found/>) or an IQ-result 140: %% containing an empty <vCard/> element. 141: %% We return <item-not-found/> 142: user_doesnt_exist(Config) -> 143: escalus:story( 144: Config, [{alice, 1}], 145: fun(Client) -> 146: Domain = domain(), 147: BadJID = <<"nonexistent@", Domain/binary>>, 148: Res = escalus:send_and_wait(Client, 149: escalus_stanza:vcard_request(BadJID)), 150: case 151: escalus_pred:is_error(<<"cancel">>, 152: <<"item-not-found">>, 153: Res) of 154: true -> 155: ok; 156: _ -> 157: [] = Res#xmlel.children, 158: ct:comment("empty result instead of error") 159: end 160: end). 161: 162: update_other_card(Config) -> 163: escalus:story( 164: Config, [{alice, 1}, {bob, 1}], 165: fun(Client, OtherClient) -> 166: JID = escalus_client:short_jid(Client), 167: Fields = [{<<"FN">>, <<"New name">>}], 168: Res = escalus:send_and_wait(OtherClient, 169: escalus_stanza:vcard_update(JID, Fields)), 170: 171: %% check that nothing was changed 172: Res2 = escalus:send_and_wait(Client, 173: escalus_stanza:vcard_request()), 174: ClientVCardTups = [{<<"FN">>, get_FN(Config)}], 175: check_vcard(ClientVCardTups, Res2), 176: 177: case escalus_pred:is_error(<<"cancel">>, 178: <<"not-allowed">>, Res) of 179: true -> 180: ok; 181: _ -> 182: ct:comment("no error returned") 183: end 184: end). 185: 186: retrieve_others_card(Config) -> 187: escalus:story( 188: Config, [{alice, 1}, {bob, 1}], 189: fun(Client, OtherClient) -> 190: JID = escalus_client:short_jid(Client), 191: Res = escalus:send_and_wait(OtherClient, 192: escalus_stanza:vcard_request(JID)), 193: OtherClientVCardTups = [{<<"FN">>, get_FN(Config)}], 194: check_vcard(OtherClientVCardTups, Res), 195: 196: %% In accordance with XMPP Core [5], a compliant server MUST 197: %% respond on behalf of the requestor and not forward the IQ to 198: %% the requestee's connected resource. 199: 200: Res2 = (catch escalus:wait_for_stanza(Client)), 201: escalus:assert(stanza_timeout, Res2) 202: end). 203: 204: %%-------------------------------------------------------------------- 205: %% XEP-0055 jabber:iq:search User Directory service Test cases 206: %% 207: %%-------------------------------------------------------------------- 208: 209: %% all.search.domain 210: 211: request_search_fields(Config) -> 212: escalus:story( 213: Config, [{alice, 1}], 214: fun(Client) -> 215: Domain = domain(), 216: DirJID = <<"vjud.", Domain/binary>>, 217: Res = escalus:send_and_wait(Client, 218: escalus_stanza:search_fields_iq(DirJID)), 219: escalus:assert(is_iq_result, Res), 220: Result = ?EL(Res, <<"query">>), 221: XData = ?EL(Result, <<"x">>), 222: #xmlel{ children = XChildren } = XData, 223: FieldTups = field_tuples(XChildren), 224: true = lists:member({<<"text-single">>, 225: get_field_name(user), <<"User">>}, 226: FieldTups), 227: true = lists:member({<<"text-single">>, 228: get_field_name(fn), 229: <<"Full Name">>}, 230: FieldTups) 231: 232: end). 233: 234: search_empty(Config) -> 235: escalus:story( 236: Config, [{alice, 1}], 237: fun(Client) -> 238: Domain = domain(), 239: DirJID = <<"vjud.", Domain/binary>>, 240: Fields = [{get_field_name(fn), <<"nobody">>}], 241: Res = escalus:send_and_wait(Client, 242: escalus_stanza:search_iq(DirJID, 243: escalus_stanza:search_fields(Fields))), 244: escalus:assert(is_iq_result, Res), 245: [] = search_result_item_tuples(Res) 246: end). 247: 248: search_some(Config) -> 249: escalus:story( 250: Config, [{bob, 1}], 251: fun(Client) -> 252: Domain = domain(), 253: DirJID = <<"vjud.", Domain/binary>>, 254: Fields = [{get_field_name(fn), get_FN(Config)}], 255: Res = escalus:send_and_wait(Client, 256: escalus_stanza:search_iq(DirJID, 257: escalus_stanza:search_fields(Fields))), 258: escalus:assert(is_iq_result, Res), 259: 260: %% Basically test that the right values exist 261: %% and map to the right column headings 262: ItemTups = search_result_item_tuples(Res), 263: 1 = length(ItemTups) 264: end). 265: 266: search_wildcard(Config) -> 267: escalus:story( 268: Config, [{bob, 1}], 269: fun(Client) -> 270: Domain = domain(), 271: DirJID = <<"vjud.", Domain/binary>>, 272: Fields = [{get_field_name(fn), get_FN_wildcard()}], 273: Res = escalus:send_and_wait(Client, 274: escalus_stanza:search_iq(DirJID, 275: escalus_stanza:search_fields(Fields))), 276: escalus:assert(is_iq_result, Res), 277: ItemTups = search_result_item_tuples(Res), 278: 1 = length(ItemTups) 279: end). 280: 281: %%-------------------------------------------------------------------- 282: %% Helper functions 283: %%-------------------------------------------------------------------- 284: 285: expected_search_results(Key, Config) -> 286: {_, ExpectedResults} = 287: lists:keyfind(expected_results, 1, 288: escalus_config:get_config(search_data, Config)), 289: lists:keyfind(Key, 1, ExpectedResults). 290: 291: %%---------------------- 292: %% xmlel shortcuts 293: stanza_get_vcard_field(Stanza, FieldName) -> 294: VCard = ?EL(Stanza, <<"vCard">>), 295: ?EL(VCard, FieldName). 296: 297: stanza_get_vcard_field_cdata(Stanza, FieldName) -> 298: VCard = ?EL(Stanza, <<"vCard">>), 299: ?EL_CD(VCard, FieldName). 300: 301: %%--------------------- 302: %% test helpers 303: 304: %% 305: %% -> [{Type, Var, Label}] 306: %% 307: field_tuples([]) -> 308: []; 309: field_tuples([#xmlel{name = <<"field">>, 310: attrs=Attrs, 311: children=_Children} = El| Rest]) -> 312: {<<"type">>,Type} = lists:keyfind(<<"type">>, 1, Attrs), 313: {<<"var">>,Var} = lists:keyfind(<<"var">>, 1, Attrs), 314: {<<"label">>,Label} = lists:keyfind(<<"label">>, 1, Attrs), 315: case ?EL_CD(El, <<"value">>) of 316: undefined -> 317: [{Type, Var, Label}|field_tuples(Rest)]; 318: ValCData -> 319: [{Type, Var, Label, ValCData}|field_tuples(Rest)] 320: end; 321: field_tuples([_SomeOtherEl|Rest]) -> 322: field_tuples(Rest). 323: 324: 325: %% 326: %% -> [{Type, Var, Label, ValueCData}] 327: %% 328: %% This is naiive and expensive LOL! 329: item_field_tuples(_, []) -> 330: []; 331: item_field_tuples(ReportedFieldTups, 332: [#xmlel{name = <<"field">>, 333: attrs=Attrs, 334: children=_Children} = El| Rest]) -> 335: {<<"var">>,Var} = lists:keyfind(<<"var">>, 1, Attrs), 336: {Type, Var, Label} = lists:keyfind(Var, 2, ReportedFieldTups), 337: [{Type, Var, Label, ?EL_CD(El, <<"value">>)} 338: | item_field_tuples(ReportedFieldTups, Rest)]; 339: 340: item_field_tuples(ReportedFieldTups, [_SomeOtherEl|Rest]) -> 341: item_field_tuples(ReportedFieldTups, Rest). 342: 343: 344: %% 345: %% -> [{JID, [ItemFieldTups]}] 346: %% 347: %% Finds the JID and maps fields to their labels and types 348: %% 349: item_tuples(_, []) -> 350: []; 351: item_tuples(ReportedFieldTups, [#xmlel{name = <<"item">>, 352: children = Children} | Rest]) -> 353: ItemFieldTups = item_field_tuples(ReportedFieldTups, Children), 354: {_,_,_,JID} = lists:keyfind(<<"jid">>, 2, ItemFieldTups), 355: [{JID, ItemFieldTups}|item_tuples(ReportedFieldTups, Rest)]; 356: item_tuples(ReportedFieldTypes, [_SomeOtherChild | Rest]) -> 357: item_tuples(ReportedFieldTypes, Rest). 358: 359: 360: %% This tests that at least the values in the ExpectedVCardTups are in the 361: %% VCardUnderTest. 362: %% Any extra values in the vcard are ignored by this function and should be 363: %% checked or rejected elsewhere. 364: %% crash means fail, return means success. 365: check_vcard(ExpectedVCardTups, Stanza) -> 366: escalus_pred:is_iq(<<"result">>, Stanza), 367: VCardUnderTest = ?EL(Stanza, <<"vCard">>), 368: check_xml_element(ExpectedVCardTups, VCardUnderTest). 369: 370: 371: check_xml_element([], _ElUnderTest) -> 372: ok; %% just return true to be consistent with other clauses. 373: check_xml_element([{ExpdFieldName, ExpdChildren}|Rest], ElUnderTest) 374: when is_list(ExpdChildren) -> 375: check_xml_element(ExpdChildren, ?EL(ElUnderTest, ExpdFieldName)), 376: check_xml_element(Rest, ElUnderTest); 377: check_xml_element([{ExpdFieldName, ExpdCData}|Rest], ElUnderTest) -> 378: case ?EL_CD(ElUnderTest, ExpdFieldName) of 379: ExpdCData -> 380: check_xml_element(Rest, ElUnderTest); 381: Else -> 382: ct:fail("Expected ~p got ~p~n", [ExpdCData, Else]) 383: end. 384: 385: %% Checks that the elements of two lists with matching keys are equal 386: %% while the order of the elements does not matter. 387: %% Returning means success. Crashing via ct:fail means failure. 388: %% Prints the lists in the ct:fail Result term. 389: list_unordered_key_match(Keypos, Expected, Actual) -> 390: case length(Actual) of 391: ActualLength when ActualLength == length(Expected) -> 392: list_unordered_key_match2(Keypos, Expected, Actual); 393: ActualLength -> 394: ct:fail("Expected size ~p, actual size ~p~nExpected: ~p~nActual: ~p", 395: [length(Expected), ActualLength, Expected, Actual]) 396: end. 397: 398: list_unordered_key_match2(_, [], _) -> 399: ok; 400: list_unordered_key_match2(Keypos, [ExpctdTup|Rest], ActualTuples) -> 401: Key = element(Keypos, ExpctdTup), 402: ActualTup = lists:keyfind(Key, Keypos, ActualTuples), 403: case ActualTup of 404: ExpctdTup -> 405: list_unordered_key_match2(Keypos, Rest, ActualTuples); 406: _ -> 407: ct:fail("~nExpected ~p~nGot ~p", [ExpctdTup, ActualTup]) 408: end. 409: 410: search_result_item_tuples(Stanza) -> 411: Result = ?EL(Stanza, <<"query">>), 412: XData = ?EL(Result, <<"x">>), 413: #xmlel{ attrs = _XAttrs, 414: children = XChildren } = XData, 415: Reported = ?EL(XData, <<"reported">>), 416: ReportedFieldTups = field_tuples(Reported#xmlel.children), 417: _ItemTups = item_tuples(ReportedFieldTups, XChildren). 418: 419: get_field_name(fn)-> 420: case is_vcard_ldap() of 421: true -> <<"cn">>; 422: false -> <<"fn">> 423: end; 424: get_field_name(user)-> 425: case is_vcard_ldap() of 426: true -> <<"uid">>; 427: false -> <<"user">> 428: end. 429: 430: get_FN_wildcard() -> 431: case is_vcard_ldap() of 432: true -> <<"*li*e">>; 433: false -> <<"old*">> 434: end. 435: get_FN(Config) -> 436: case is_vcard_ldap() of 437: true -> 438: escalus_utils:jid_to_lower(escalus_users:get_username(Config, alice)); 439: false -> 440: <<"Old Name">> 441: end. 442: 443: configure_mod_vcard(Config) -> 444: HostType = ct:get_config({hosts, mim, host_type}), 445: case is_vcard_ldap() of 446: true -> 447: ensure_started(HostType, ldap_opts()); 448: _ -> 449: ensure_started(HostType, ?config(mod_vcard_opts, Config)) 450: end. 451: 452: ldap_opts() -> 453: LDAPOpts = #{filter => <<"(objectClass=inetOrgPerson)">>, 454: base => <<"ou=Users,dc=esl,dc=com">>, 455: search_fields => [{<<"Full Name">>, <<"cn">>}, {<<"User">>, <<"uid">>}], 456: vcard_map => [{<<"FN">>, <<"%s">>, [<<"cn">>]}]}, 457: LDAPOptsWithDefaults = config_parser_helper:config([modules, mod_vcard, ldap], LDAPOpts), 458: config_parser_helper:mod_config(mod_vcard, #{backend => ldap, ldap => LDAPOptsWithDefaults}). 459: 460: ensure_started(HostType, Opts) -> 461: dynamic_modules:stop(HostType, mod_vcard), 462: dynamic_modules:start(HostType, mod_vcard, Opts). 463: 464: prepare_vcard_module(Config) -> 465: %% Keep the old config, so we can undo our changes, once finished testing 466: Config1 = dynamic_modules:save_modules(host_types(), Config), 467: %% Get a list of options, we can use as a prototype to start new modules 468: Backend = mongoose_helper:mnesia_or_rdbms_backend(), 469: VCardOpts = config_parser_helper:mod_config(mod_vcard, #{backend => Backend}), 470: [{mod_vcard_opts, VCardOpts} | Config1]. 471: 472: restore_vcard_module(Config) -> 473: dynamic_modules:restore_modules(Config).