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: 250: escalus:story( 251: Config, [{bob, 1}], 252: fun(Client) -> 253: Domain = domain(), 254: DirJID = <<"vjud.", Domain/binary>>, 255: Fields = [{get_field_name(fn), get_FN(Config)}], 256: timer:sleep(timer:seconds(1)), %% this is required by Riak 2.0 257: %% Search to be sure the vcard is 258: %% indexed 259: Res = escalus:send_and_wait(Client, 260: escalus_stanza:search_iq(DirJID, 261: escalus_stanza:search_fields(Fields))), 262: escalus:assert(is_iq_result, Res), 263: 264: %% Basically test that the right values exist 265: %% and map to the right column headings 266: ItemTups = search_result_item_tuples(Res), 267: 1 = length(ItemTups) 268: end). 269: 270: search_wildcard(Config) -> 271: escalus:story( 272: Config, [{bob, 1}], 273: fun(Client) -> 274: Domain = domain(), 275: DirJID = <<"vjud.", Domain/binary>>, 276: Fields = [{get_field_name(fn), get_FN_wildcard()}], 277: Res = escalus:send_and_wait(Client, 278: escalus_stanza:search_iq(DirJID, 279: escalus_stanza:search_fields(Fields))), 280: escalus:assert(is_iq_result, Res), 281: ItemTups = search_result_item_tuples(Res), 282: 1 = length(ItemTups) 283: end). 284: 285: %%-------------------------------------------------------------------- 286: %% Helper functions 287: %%-------------------------------------------------------------------- 288: 289: expected_search_results(Key, Config) -> 290: {_, ExpectedResults} = 291: lists:keyfind(expected_results, 1, 292: escalus_config:get_config(search_data, Config)), 293: lists:keyfind(Key, 1, ExpectedResults). 294: 295: %%---------------------- 296: %% xmlel shortcuts 297: stanza_get_vcard_field(Stanza, FieldName) -> 298: VCard = ?EL(Stanza, <<"vCard">>), 299: ?EL(VCard, FieldName). 300: 301: stanza_get_vcard_field_cdata(Stanza, FieldName) -> 302: VCard = ?EL(Stanza, <<"vCard">>), 303: ?EL_CD(VCard, FieldName). 304: 305: %%--------------------- 306: %% test helpers 307: 308: %% 309: %% -> [{Type, Var, Label}] 310: %% 311: field_tuples([]) -> 312: []; 313: field_tuples([#xmlel{name = <<"field">>, 314: attrs=Attrs, 315: children=_Children} = El| Rest]) -> 316: {<<"type">>,Type} = lists:keyfind(<<"type">>, 1, Attrs), 317: {<<"var">>,Var} = lists:keyfind(<<"var">>, 1, Attrs), 318: {<<"label">>,Label} = lists:keyfind(<<"label">>, 1, Attrs), 319: case ?EL_CD(El, <<"value">>) of 320: undefined -> 321: [{Type, Var, Label}|field_tuples(Rest)]; 322: ValCData -> 323: [{Type, Var, Label, ValCData}|field_tuples(Rest)] 324: end; 325: field_tuples([_SomeOtherEl|Rest]) -> 326: field_tuples(Rest). 327: 328: 329: %% 330: %% -> [{Type, Var, Label, ValueCData}] 331: %% 332: %% This is naiive and expensive LOL! 333: item_field_tuples(_, []) -> 334: []; 335: item_field_tuples(ReportedFieldTups, 336: [#xmlel{name = <<"field">>, 337: attrs=Attrs, 338: children=_Children} = El| Rest]) -> 339: {<<"var">>,Var} = lists:keyfind(<<"var">>, 1, Attrs), 340: {Type, Var, Label} = lists:keyfind(Var, 2, ReportedFieldTups), 341: [{Type, Var, Label, ?EL_CD(El, <<"value">>)} 342: | item_field_tuples(ReportedFieldTups, Rest)]; 343: 344: item_field_tuples(ReportedFieldTups, [_SomeOtherEl|Rest]) -> 345: item_field_tuples(ReportedFieldTups, Rest). 346: 347: 348: %% 349: %% -> [{JID, [ItemFieldTups]}] 350: %% 351: %% Finds the JID and maps fields to their labels and types 352: %% 353: item_tuples(_, []) -> 354: []; 355: item_tuples(ReportedFieldTups, [#xmlel{name = <<"item">>, 356: children = Children} | Rest]) -> 357: ItemFieldTups = item_field_tuples(ReportedFieldTups, Children), 358: {_,_,_,JID} = lists:keyfind(<<"jid">>, 2, ItemFieldTups), 359: [{JID, ItemFieldTups}|item_tuples(ReportedFieldTups, Rest)]; 360: item_tuples(ReportedFieldTypes, [_SomeOtherChild | Rest]) -> 361: item_tuples(ReportedFieldTypes, Rest). 362: 363: 364: %% This tests that at least the values in the ExpectedVCardTups are in the 365: %% VCardUnderTest. 366: %% Any extra values in the vcard are ignored by this function and should be 367: %% checked or rejected elsewhere. 368: %% crash means fail, return means success. 369: check_vcard(ExpectedVCardTups, Stanza) -> 370: escalus_pred:is_iq(<<"result">>, Stanza), 371: VCardUnderTest = ?EL(Stanza, <<"vCard">>), 372: check_xml_element(ExpectedVCardTups, VCardUnderTest). 373: 374: 375: check_xml_element([], _ElUnderTest) -> 376: ok; %% just return true to be consistent with other clauses. 377: check_xml_element([{ExpdFieldName, ExpdChildren}|Rest], ElUnderTest) 378: when is_list(ExpdChildren) -> 379: check_xml_element(ExpdChildren, ?EL(ElUnderTest, ExpdFieldName)), 380: check_xml_element(Rest, ElUnderTest); 381: check_xml_element([{ExpdFieldName, ExpdCData}|Rest], ElUnderTest) -> 382: case ?EL_CD(ElUnderTest, ExpdFieldName) of 383: ExpdCData -> 384: check_xml_element(Rest, ElUnderTest); 385: Else -> 386: ct:fail("Expected ~p got ~p~n", [ExpdCData, Else]) 387: end. 388: 389: %% Checks that the elements of two lists with matching keys are equal 390: %% while the order of the elements does not matter. 391: %% Returning means success. Crashing via ct:fail means failure. 392: %% Prints the lists in the ct:fail Result term. 393: list_unordered_key_match(Keypos, Expected, Actual) -> 394: case length(Actual) of 395: ActualLength when ActualLength == length(Expected) -> 396: list_unordered_key_match2(Keypos, Expected, Actual); 397: ActualLength -> 398: ct:fail("Expected size ~p, actual size ~p~nExpected: ~p~nActual: ~p", 399: [length(Expected), ActualLength, Expected, Actual]) 400: end. 401: 402: list_unordered_key_match2(_, [], _) -> 403: ok; 404: list_unordered_key_match2(Keypos, [ExpctdTup|Rest], ActualTuples) -> 405: Key = element(Keypos, ExpctdTup), 406: ActualTup = lists:keyfind(Key, Keypos, ActualTuples), 407: case ActualTup of 408: ExpctdTup -> 409: list_unordered_key_match2(Keypos, Rest, ActualTuples); 410: _ -> 411: ct:fail("~nExpected ~p~nGot ~p", [ExpctdTup, ActualTup]) 412: end. 413: 414: search_result_item_tuples(Stanza) -> 415: Result = ?EL(Stanza, <<"query">>), 416: XData = ?EL(Result, <<"x">>), 417: #xmlel{ attrs = _XAttrs, 418: children = XChildren } = XData, 419: Reported = ?EL(XData, <<"reported">>), 420: ReportedFieldTups = field_tuples(Reported#xmlel.children), 421: _ItemTups = item_tuples(ReportedFieldTups, XChildren). 422: 423: get_field_name(fn)-> 424: case is_vcard_ldap() of 425: true -> <<"cn">>; 426: false -> <<"fn">> 427: end; 428: get_field_name(user)-> 429: case is_vcard_ldap() of 430: true -> <<"uid">>; 431: false -> <<"user">> 432: end. 433: 434: get_FN_wildcard() -> 435: case is_vcard_ldap() of 436: true -> <<"*li*e">>; 437: false -> <<"old*">> 438: end. 439: get_FN(Config) -> 440: case is_vcard_ldap() of 441: true -> 442: escalus_utils:jid_to_lower(escalus_users:get_username(Config, alice)); 443: false -> 444: <<"Old Name">> 445: end. 446: 447: configure_mod_vcard(Config) -> 448: HostType = ct:get_config({hosts, mim, host_type}), 449: case is_vcard_ldap() of 450: true -> 451: ensure_started(HostType, ldap_opts()); 452: _ -> 453: ensure_started(HostType, ?config(mod_vcard_opts, Config)) 454: end. 455: 456: ldap_opts() -> 457: VCardOpts = #{backend => ldap, 458: host => subhost_pattern("vjud.@HOST@"), 459: ldap_uids => [{<<"uid">>}], %% equivalent to {<<"uid">>, <<"%u">>} 460: ldap_filter => <<"(objectClass=inetOrgPerson)">>, 461: ldap_base => "ou=Users,dc=esl,dc=com", 462: ldap_search_fields => [{"Full Name", "cn"}, {"User", "uid"}], 463: ldap_vcard_map => [{"FN", "%s", ["cn"]}]}, 464: config_parser_helper:mod_config(mod_vcard, VCardOpts). 465: 466: ensure_started(HostType, Opts) -> 467: dynamic_modules:stop(HostType, mod_vcard), 468: dynamic_modules:start(HostType, mod_vcard, Opts). 469: 470: prepare_vcard_module(Config) -> 471: %% Keep the old config, so we can undo our changes, once finished testing 472: Config1 = dynamic_modules:save_modules(host_types(), Config), 473: %% Get a list of options, we can use as a prototype to start new modules 474: VCardOpts = config_parser_helper:default_mod_config(mod_vcard), 475: [{mod_vcard_opts, VCardOpts} | Config1]. 476: 477: restore_vcard_module(Config) -> 478: dynamic_modules:restore_modules(Config).