MultiUserChatEntityIntegrationTest.java

  1. /**
  2.  *
  3.  * Copyright 2021 Dan Caseley
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smackx.muc;

  18. import static org.junit.jupiter.api.Assertions.assertEquals;
  19. import static org.junit.jupiter.api.Assertions.assertFalse;
  20. import static org.junit.jupiter.api.Assertions.assertThrows;
  21. import static org.junit.jupiter.api.Assertions.assertTrue;

  22. import java.util.List;
  23. import java.util.Map;

  24. import org.jivesoftware.smack.SmackException;
  25. import org.jivesoftware.smack.XMPPException;
  26. import org.jivesoftware.smack.packet.StanzaError;
  27. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  28. import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
  29. import org.jivesoftware.smackx.disco.packet.DiscoverItems;
  30. import org.jivesoftware.smackx.muc.MultiUserChatException.MissingMucCreationAcknowledgeException;
  31. import org.jivesoftware.smackx.muc.MultiUserChatException.MucAlreadyJoinedException;
  32. import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException;
  33. import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;

  34. import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
  35. import org.igniterealtime.smack.inttest.TestNotPossibleException;
  36. import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
  37. import org.igniterealtime.smack.inttest.annotations.SpecificationReference;

  38. import org.jxmpp.jid.DomainBareJid;
  39. import org.jxmpp.jid.EntityBareJid;
  40. import org.jxmpp.jid.EntityFullJid;
  41. import org.jxmpp.jid.parts.Resourcepart;
  42. import org.jxmpp.stringprep.XmppStringprepException;

  43. @SpecificationReference(document = "XEP-0045", version = "1.34.6")
  44. public class MultiUserChatEntityIntegrationTest extends AbstractMultiUserChatIntegrationTest {

  45.     public MultiUserChatEntityIntegrationTest(SmackIntegrationTestEnvironment environment)
  46.                     throws SmackException.NoResponseException, XMPPException.XMPPErrorException,
  47.                     SmackException.NotConnectedException, InterruptedException, TestNotPossibleException, MucAlreadyJoinedException, MissingMucCreationAcknowledgeException, NotAMucServiceException, XmppStringprepException {
  48.         super(environment);
  49.     }

  50.     /**
  51.      * Asserts that a MUC service can be discovered.
  52.      *
  53.      * @throws Exception when errors occur
  54.      */
  55.     @SmackIntegrationTest(section = "6.1", quote =
  56.         "An entity often discovers a MUC service by sending a Service Discovery items (\"disco#items\") request to " +
  57.         "its own server. The server then returns the services that are associated with it.")
  58.     public void mucTestForDiscoveringMuc() throws Exception {
  59.         // This repeats some logic from the `AbstractMultiUserChatIntegrationTest` constructor, but is preserved here
  60.         // as an explicit test, because that might not always be true.
  61.         List<DomainBareJid> services = ServiceDiscoveryManager.getInstanceFor(conOne).findServices(MUCInitialPresence.NAMESPACE, true, false);
  62.         assertFalse(services.isEmpty(), "Expected to be able to find MUC services on the domain that '" + conOne.getUser() + "' is connecting to (but could not).");
  63.     }

  64.     /**
  65.      * Asserts that a MUC service can have its features discovered.
  66.      *
  67.      * @throws Exception when errors occur
  68.      */
  69.     @SmackIntegrationTest(section = "6.2", quote =
  70.         "An entity may wish to discover if a service implements the Multi-User Chat protocol; in order to do so, it " +
  71.             "sends a service discovery information (\"disco#info\") query to the MUC service's JID. The service MUST " +
  72.             "return its identity and the features it supports.")
  73.     public void mucTestForDiscoveringFeatures() throws Exception {
  74.         DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(conOne).discoverInfo(mucService);
  75.         assertFalse(info.getIdentities().isEmpty(), "Expected the service discovery information for service " + mucService + " to include identities (but it did not).");
  76.         assertFalse(info.getFeatures().isEmpty(), "Expected the service discovery information for service " + mucService + " to include features (but it did not).");
  77.     }

  78.     /**
  79.      * Asserts that a MUC Service lists its public rooms.
  80.      *
  81.      * @throws Exception when errors occur
  82.      */
  83.     @SmackIntegrationTest(section = "6.3", quote =
  84.         "The service discovery items (\"disco#items\") protocol enables an entity to query a service for a list of " +
  85.         "associated items, which in the case of a chat service would consist of the specific chat rooms hosted by the" +
  86.         "service. The service SHOULD return a full list of the public rooms it hosts (i.e., not return any rooms that" +
  87.         "are hidden).")
  88.     public void mucTestForDiscoveringRooms() throws Exception {
  89.         EntityBareJid mucAddressPublic = getRandomRoom("smack-inttest-publicroom");
  90.         MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddressPublic);

  91.         EntityBareJid mucAddressHidden = getRandomRoom("smack-inttest-hiddenroom");
  92.         MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddressHidden);

  93.         createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString));

  94.         Map<EntityBareJid, HostedRoom> rooms;
  95.         try {
  96.             createHiddenMuc(mucAsSeenByTwo, Resourcepart.from("two-" + randomString));
  97.             rooms = mucManagerThree.getRoomsHostedBy(mucService);
  98.         } finally {
  99.             tryDestroy(mucAsSeenByOne);
  100.             tryDestroy(mucAsSeenByTwo);
  101.         }

  102.         assertTrue(rooms.containsKey(mucAddressPublic), "Expected the disco response from " + mucService + " to include the public room " + mucAddressPublic + " (but it did not).");
  103.         assertFalse(rooms.containsKey(mucAddressHidden), "Expected the disco response from " + mucService + " to not include the hidden room " + mucAddressHidden + " (but it did).");
  104.     }

  105.     /**
  106.      * Asserts that a MUC Service returns disco info for a room.
  107.      *
  108.      * @throws Exception when errors occur
  109.      */
  110.     @SmackIntegrationTest(section = "6.4", quote =
  111.         "Using the disco#info protocol, an entity may also query a specific chat room for more detailed information " +
  112.         "about the room....The room MUST return its identity and SHOULD return the features it supports")
  113.     public void mucTestForDiscoveringRoomInfo() throws Exception {
  114.         EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoinfo");
  115.         MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
  116.         createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString));

  117.         DiscoverInfo discoInfo;
  118.         try {
  119.             // Use SDM because mucManagerOne.getRoomInfo(mucAddress) might not use Disco
  120.             discoInfo = ServiceDiscoveryManager.getInstanceFor(conOne).discoverInfo(mucAddress);
  121.         } finally {
  122.             tryDestroy(mucAsSeenByOne);
  123.         }

  124.         assertFalse(discoInfo.getIdentities().isEmpty(), "Expected the service discovery information for room " + mucAddress + " to include identities (but it did not).");
  125.         assertFalse(discoInfo.getFeatures().isEmpty(), "Expected the service discovery information for room " + mucAddress + " to include features (but it did not).");
  126.         assertTrue(discoInfo.getFeatures().stream().anyMatch(feature -> MultiUserChatConstants.NAMESPACE.equals(feature.getVar())), "Expected the service discovery information for room " + mucAddress + " to include the '" + MultiUserChatConstants.NAMESPACE + "' feature (but it did not).");
  127.     }

  128.     /**
  129.      * Asserts that a MUC Service returns disco info for a room's items.
  130.      *
  131.      * @throws Exception when errors occur
  132.      */
  133.     @SmackIntegrationTest(section = "6.5", quote =
  134.         "An entity MAY also query a specific chat room for its associated items. An implementation MAY return a list " +
  135.         "of existing occupants if that information is publicly available, or return no list at all if this " +
  136.         "information is kept private.")
  137.     public void mucTestForDiscoveringRoomItems() throws Exception {
  138.         EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoitems");
  139.         MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
  140.         createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString));

  141.         DiscoverItems roomItems;
  142.         try {
  143.             roomItems = ServiceDiscoveryManager.getInstanceFor(conTwo).discoverItems(mucAddress);
  144.         } finally {
  145.             tryDestroy(mucAsSeenByOne);
  146.         }

  147.         assertEquals(1, roomItems.getItems().size(), "Unexpected amount of disco items for " + mucAddress);
  148.     }

  149.     /**
  150.      * Asserts that a non-occupant receives a Bad Request error when attempting to query an occupant by their
  151.      * occupant JID.
  152.      *
  153.      * @throws Exception when errors occur
  154.      */
  155.     @SmackIntegrationTest(section = "6.6", quote =
  156.         "If a non-occupant attempts to send a disco request to an address of the form <room@service/nick>, a MUC " +
  157.         "service MUST return a <bad-request> error")
  158.     public void mucTestForRejectingDiscoOnRoomOccupantByNonOccupant() throws Exception {
  159.         EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoitems");
  160.         MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
  161.         final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
  162.         createMuc(mucAsSeenByOne, nicknameOne);
  163.         final EntityFullJid mucAsSeenByOneUserJid = mucAsSeenByOne.getMyRoomJid();
  164.         // Ensure that we do not invoke discoverItems() with null below. This should not happen, as the room JID should
  165.         // be non-null after we created and joined the room. But it can not hurt to explicitly test for it either.
  166.         if (mucAsSeenByOneUserJid == null) {
  167.             throw new AssertionError();
  168.         }

  169.         XMPPException.XMPPErrorException xe;
  170.         try {
  171.             xe = assertThrows(XMPPException.XMPPErrorException.class,
  172.                             () -> ServiceDiscoveryManager.getInstanceFor(conTwo).discoverItems(mucAsSeenByOneUserJid),
  173.                     "Expected an XMPP error when " + conTwo.getUser() + " was trying to discover items of " + mucAsSeenByOneUserJid);
  174.         } finally {
  175.             tryDestroy(mucAsSeenByOne);
  176.         }

  177.         final StanzaError.Condition expectedCondition;
  178.         switch (sinttestConfiguration.compatibilityMode) {
  179.         default:
  180.             expectedCondition = StanzaError.Condition.bad_request;
  181.             break;
  182.         case ejabberd:
  183.             expectedCondition = StanzaError.Condition.not_acceptable;
  184.             break;
  185.         }
  186.         assertEquals(expectedCondition, xe.getStanzaError().getCondition(),
  187.             "Unexpected error condition in error returned when " + conTwo.getUser() + " was trying to discover items of " + mucAsSeenByOneUserJid);
  188.     }
  189. }