1: %%==============================================================================
    2: %% Copyright 2016 Erlang Solutions Ltd.
    3: %%
    4: %% Licensed under the Apache License, Version 2.0 (the "License");
    5: %% you may not use this file except in compliance with the License.
    6: %% You may obtain a copy of the License at
    7: %%
    8: %% http://www.apache.org/licenses/LICENSE-2.0
    9: %%
   10: %% Unless required by applicable law or agreed to in writing, software
   11: %% distributed under the License is distributed on an "AS IS" BASIS,
   12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13: %% See the License for the specific language governing permissions and
   14: %% limitations under the License.
   15: %%
   16: %% Author: Joseph Yiasemides <joseph.yiasemides@erlang-solutions.com>
   17: %% Description: Test HTTP Administration API for MUC Light
   18: %%==============================================================================
   19: 
   20: -module(muc_light_http_api_SUITE).
   21: -compile([export_all, nowarn_export_all]).
   22: 
   23: -include_lib("common_test/include/ct.hrl").
   24: -include_lib("eunit/include/eunit.hrl").
   25: -include_lib("escalus/include/escalus.hrl").
   26: -include_lib("escalus/include/escalus_xmlns.hrl").
   27: -include_lib("exml/include/exml.hrl").
   28: 
   29: -import(muc_light_helper, [stanza_create_room/3]).
   30: -import(distributed_helper, [subhost_pattern/1]).
   31: -import(domain_helper, [host_type/0, domain/0]).
   32: 
   33: %%--------------------------------------------------------------------
   34: %% Suite configuration
   35: %%--------------------------------------------------------------------
   36: 
   37: all() ->
   38:     [{group, positive},
   39:      {group, negative}].
   40: 
   41: groups() ->
   42:     [{positive, [parallel], success_response()},
   43:      {negative, [parallel], negative_response()}].
   44: 
   45: success_response() ->
   46:     [create_unique_room,
   47:      create_identifiable_room,
   48:      invite_to_room,
   49:      send_message_to_room,
   50:      delete_room_by_owner
   51:     ].
   52: 
   53: negative_response() ->
   54:     [delete_room_by_non_owner,
   55:      delete_non_existent_room,
   56:      delete_room_without_having_a_membership,
   57:      create_non_unique_room
   58:     ].
   59: 
   60: %%--------------------------------------------------------------------
   61: %% Init & teardown
   62: %%--------------------------------------------------------------------
   63: 
   64: init_per_suite(Config) ->
   65:     dynamic_modules:start(host_type(), mod_muc_light,
   66:         [{host, subhost_pattern(muc_light_helper:muc_host_pattern())},
   67:          {rooms_in_rosters, true},
   68:          {backend, mongoose_helper:mnesia_or_rdbms_backend()}]),
   69:     escalus:init_per_suite(Config).
   70: 
   71: end_per_suite(Config) ->
   72:     escalus_fresh:clean(),
   73:     dynamic_modules:stop(host_type(), mod_muc_light),
   74:     escalus:end_per_suite(Config).
   75: 
   76: init_per_group(_GroupName, Config) ->
   77:     escalus:create_users(Config, escalus:get_users([alice, bob, kate])).
   78: 
   79: end_per_group(_GroupName, Config) ->
   80:     escalus:delete_users(Config, escalus:get_users([alice, bob, kate])).
   81: 
   82: init_per_testcase(CaseName, Config) ->
   83:     escalus:init_per_testcase(CaseName, Config).
   84: 
   85: end_per_testcase(CaseName, Config) ->
   86:     escalus:end_per_testcase(CaseName, Config).
   87: 
   88: 
   89: %%--------------------------------------------------------------------
   90: %% Tests
   91: %%--------------------------------------------------------------------
   92: 
   93: create_unique_room(Config) ->
   94:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
   95:         Path = path([domain()]),
   96:         Name = <<"wonderland">>,
   97:         Body = #{ name => Name,
   98:                   owner => escalus_client:short_jid(Alice),
   99:                   subject => <<"Lewis Carol">>
  100:                 },
  101:         {{<<"201">>, _}, _} = rest_helper:post(admin, Path, Body),
  102:         [Item] = get_disco_rooms(Alice),
  103:         MUCLightDomain = muc_light_domain(),
  104:         true = is_room_name(Name, Item),
  105:         true = is_room_domain(MUCLightDomain, Item)
  106:     end).
  107: 
  108: create_identifiable_room(Config) ->
  109:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  110:         Path = path([domain()]),
  111:         RandBits = base16:encode(crypto:strong_rand_bytes(5)),
  112:         Name = <<"wonderland">>,
  113:         RoomID = <<"just_some_id_", RandBits/binary>>,
  114:         RoomIDescaped = escalus_utils:jid_to_lower(RoomID),
  115:         Body = #{ id => RoomID,
  116:                   name => Name,
  117:                   owner => escalus_client:short_jid(Alice),
  118:                   subject => <<"Lewis Carol">>
  119:                 },
  120:         {{<<"201">>, _}, RoomJID} = rest_helper:putt(admin, Path, Body),
  121:         [Item] = get_disco_rooms(Alice),
  122:         [RoomIDescaped, MUCLightDomain] = binary:split(RoomJID, <<"@">>),
  123:         MUCLightDomain = muc_light_domain(),
  124:         true = is_room_name(Name, Item),
  125:         true = is_room_domain(MUCLightDomain, Item),
  126:         true = is_room_id(RoomIDescaped, Item)
  127:     end).
  128: 
  129: invite_to_room(Config) ->
  130:     Name = <<"wonderland">>,
  131:     Path = path([muc_light_domain(), Name, "participants"]),
  132:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}],
  133:       fun(Alice, Bob, Kate) ->
  134:         %% XMPP: Alice creates a room.
  135:         Stt = stanza_create_room(undefined,
  136:             [{<<"roomname">>, Name}], [{Kate, member}]),
  137:         escalus:send(Alice, Stt),
  138:         %% XMPP: Alice recieves a affiliation message to herself and
  139:         %% an IQ result when creating the MUC Light room.
  140:         escalus:wait_for_stanza(Alice),
  141:         escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  142:         %% (*) HTTP: Invite Bob (change room affiliation) on Alice's behalf.
  143:         Body = #{ sender => escalus_client:short_jid(Alice),
  144:                   recipient => escalus_client:short_jid(Bob)
  145:                 },
  146:         {{<<"204">>, _}, <<"">>} = rest_helper:post(admin, Path, Body),
  147:         %% XMPP: Bob recieves his affiliation information.
  148:         member_is_affiliated(escalus:wait_for_stanza(Bob), Bob),
  149:         %% XMPP: Alice recieves Bob's affiliation infromation.
  150:         member_is_affiliated(escalus:wait_for_stanza(Alice), Bob),
  151:         %% XMPP: Alice does NOT recieve an IQ result stanza following
  152:         %% her HTTP request to invite Bob in story point (*).
  153:         escalus_assert:has_no_stanzas(Alice)
  154:       end).
  155: 
  156: send_message_to_room(Config) ->
  157:     Name = <<"wonderland">>,
  158:     Path = path([muc_light_domain(), Name, "messages"]),
  159:     Text = <<"Hello everyone!">>,
  160:     escalus:fresh_story(Config,
  161:       [{alice, 1}, {bob, 1}, {kate, 1}],
  162:       fun(Alice, Bob, Kate) ->
  163:         %% XMPP: Alice creates a room.
  164:         escalus:send(Alice, stanza_create_room(undefined,
  165:             [{<<"roomname">>, Name}], [{Bob, member}, {Kate, member}])),
  166:         %% XMPP: Alice gets her own affiliation info
  167:         escalus:wait_for_stanza(Alice),
  168:         %% XMPP: And Alice gets IQ result
  169:         CreationResult = escalus:wait_for_stanza(Alice),
  170:         escalus:assert(is_iq_result, CreationResult),
  171:         %% XMPP: Get Bob and Kate recieve their affiliation information.
  172:         [ escalus:wait_for_stanza(U) || U <- [Bob, Kate] ],
  173:         %% HTTP: Alice sends a message to the MUC room.
  174:         Body = #{ from => escalus_client:short_jid(Alice),
  175:                   body => Text
  176:                 },
  177:         {{<<"204">>, _}, <<"">>} = rest_helper:post(admin, Path, Body),
  178:         %% XMPP: Both Bob and Kate see the message.
  179:         [ see_message_from_user(U, Alice, Text) || U <- [Bob, Kate] ]
  180:     end).
  181: 
  182: delete_room_by_owner(Config) ->
  183:     RoomName = <<"wonderland">>,
  184:     escalus:fresh_story(Config,
  185:                         [{alice, 1}, {bob, 1}, {kate, 1}],
  186:                         fun(Alice, Bob, Kate)->
  187:                                 {{<<"204">>, <<"No Content">>}, <<"">>} =
  188:                                     check_delete_room(Config, RoomName, RoomName,
  189:                                                       Alice, [Bob, Kate], Alice)
  190:                         end).
  191: 
  192: delete_room_by_non_owner(Config) ->
  193:     RoomName = <<"wonderland">>,
  194:     escalus:fresh_story(Config,
  195:                         [{alice, 1}, {bob, 1}, {kate, 1}],
  196:                         fun(Alice, Bob, Kate)->
  197:                                 {{<<"403">>, <<"Forbidden">>},
  198:                                  <<"you can not delete this room">>} =
  199:                                     check_delete_room(Config, RoomName, RoomName,
  200:                                                       Alice, [Bob, Kate], Bob)
  201:                         end).
  202: 
  203: delete_non_existent_room(Config) ->
  204:     RoomName = <<"wonderland">>,
  205:     escalus:fresh_story(Config,
  206:                         [{alice, 1}, {bob, 1}, {kate, 1}],
  207:                         fun(Alice, Bob, Kate)->
  208:                                 {{<<"404">>, _}, <<"room does not exist">>} =
  209:                                     check_delete_room(Config, RoomName, <<"some_non_existent_room">>,
  210:                                                       Alice, [Bob, Kate], Alice)
  211:                         end).
  212: 
  213: delete_room_without_having_a_membership(Config) ->
  214:     RoomName = <<"wonderland">>,
  215:     escalus:fresh_story(Config,
  216:                         [{alice, 1}, {bob, 1}, {kate, 1}],
  217:                         fun(Alice, Bob, Kate)->
  218:                                 {{<<"403">>, _}, <<"given user does not occupy this room">>} =
  219:                                     check_delete_room(Config, RoomName, RoomName,
  220:                                                       Alice, [Bob], Kate)
  221:                         end).
  222: 
  223: 
  224: create_non_unique_room(Config) ->
  225:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  226:         Path = path([domain()]),
  227:         RandBits = base16:encode(crypto:strong_rand_bytes(5)),
  228:         Name = <<"wonderland">>,
  229:         RoomID = <<"just_some_id_", RandBits/binary>>,
  230:         Body = #{ id => RoomID,
  231:                   name => Name,
  232:                   owner => escalus_client:short_jid(Alice),
  233:                   subject => <<"Lewis Carol">>
  234:         },
  235:         {{<<"201">>, _}, _RoomJID} = rest_helper:putt(admin, Path, Body),
  236:         {{<<"403">>, _}, <<"Room already exists">>} = rest_helper:putt(admin, Path, Body),
  237:         ok
  238:     end).
  239: 
  240: %%--------------------------------------------------------------------
  241: %% Ancillary (borrowed and adapted from the MUC and MUC Light suites)
  242: %%--------------------------------------------------------------------
  243: 
  244: get_disco_rooms(User) ->
  245:     DiscoStanza = escalus_stanza:to(escalus_stanza:iq_get(?NS_DISCO_ITEMS, []), muc_light_domain()),
  246:     escalus:send(User, DiscoStanza),
  247:     Stanza =  escalus:wait_for_stanza(User),
  248:     XNamespaces = exml_query:paths(Stanza, [{element, <<"query">>}, {attr, <<"xmlns">>}]),
  249:     true = lists:member(?NS_DISCO_ITEMS, XNamespaces),
  250:     escalus:assert(is_stanza_from, [muc_light_domain()], Stanza),
  251:     exml_query:paths(Stanza, [{element, <<"query">>}, {element, <<"item">>}]).
  252: 
  253: is_room_name(Name, Item) ->
  254:     Name == exml_query:attr(Item, <<"name">>).
  255: 
  256: is_room_domain(Domain, Item) ->
  257:     JID = exml_query:attr(Item, <<"jid">>),
  258:     [_, Got] = binary:split(JID, <<$@>>, [global]),
  259:     Domain == Got.
  260: 
  261: is_room_id(Id, Item) ->
  262:     JID = exml_query:attr(Item, <<"jid">>),
  263:     [Got, _] = binary:split(JID, <<$@>>, [global]),
  264:     Id == Got.
  265: 
  266: see_message_from_user(User, Sender, Contents) ->
  267:     Stanza = escalus:wait_for_stanza(User),
  268:     #xmlel{ name = <<"message">> } = Stanza,
  269:     SenderJID = escalus_utils:jid_to_lower(escalus_utils:get_short_jid(Sender)),
  270:     From = exml_query:path(Stanza, [{attr, <<"from">>}]),
  271:     {_, _} = binary:match(From, SenderJID),
  272:     Contents = exml_query:path(Stanza, [{element, <<"body">>}, cdata]).
  273: 
  274: member_is_affiliated(Stanza, User) ->
  275:     MemberJID = escalus_utils:jid_to_lower(escalus_utils:get_short_jid(User)),
  276:     Data = exml_query:path(Stanza, [{element, <<"x">>}, {element, <<"user">>}, cdata]),
  277:     MemberJID == Data.
  278: 
  279: check_delete_room(_Config, RoomNameToCreate, RoomNameToDelete, RoomOwner,
  280:                   RoomMembers, UserToExecuteDelete) ->
  281:     Members = [{Member, member} || Member <- RoomMembers],
  282:     escalus:send(RoomOwner, stanza_create_room(undefined,
  283:                                            [{<<"roomname">>, RoomNameToCreate}],
  284:                                            Members)),
  285:     %% XMPP RoomOwner gets affiliation and IQ result
  286:     Affiliations = [{RoomOwner, owner} | Members],
  287:     muc_light_helper:verify_aff_bcast([{RoomOwner, owner}], Affiliations),
  288:     %% and now RoomOwner gets IQ result
  289:     CreationResult = escalus:wait_for_stanza(RoomOwner),
  290:     escalus:assert(is_iq_result, CreationResult),
  291:     muc_light_helper:verify_aff_bcast(Members, Affiliations),
  292:     ShortJID = escalus_client:short_jid(UserToExecuteDelete),
  293:     Path = path([muc_light_domain(), RoomNameToDelete, ShortJID, "management"]),
  294:     rest_helper:delete(admin, Path).
  295: 
  296: 
  297: %%--------------------------------------------------------------------
  298: %% Helpers
  299: %%--------------------------------------------------------------------
  300: 
  301: path(Items) ->
  302:     AllItems = ["muc-lights" | Items],
  303:     iolist_to_binary([[$/, Item] || Item <- AllItems]).
  304: 
  305: muc_light_domain() ->
  306:     muc_light_helper:muc_host().