MultiUserChatIntegrationTest.java

  1. /**
  2.  *
  3.  * Copyright 2015-2024 Florian Schmaus
  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.assertNull;
  21. import static org.junit.jupiter.api.Assertions.assertThrows;

  22. import java.util.Set;
  23. import java.util.concurrent.TimeoutException;

  24. import org.jivesoftware.smack.MessageListener;
  25. import org.jivesoftware.smack.SmackException;
  26. import org.jivesoftware.smack.SmackException.NoResponseException;
  27. import org.jivesoftware.smack.SmackException.NotConnectedException;
  28. import org.jivesoftware.smack.XMPPException;
  29. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  30. import org.jivesoftware.smack.packet.Message;
  31. import org.jivesoftware.smack.packet.StanzaError;
  32. import org.jivesoftware.smackx.muc.MultiUserChatException.MissingMucCreationAcknowledgeException;
  33. import org.jivesoftware.smackx.muc.MultiUserChatException.MucAlreadyJoinedException;
  34. import org.jivesoftware.smackx.muc.MultiUserChatException.MucConfigurationNotSupportedException;
  35. import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException;

  36. import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
  37. import org.igniterealtime.smack.inttest.TestNotPossibleException;
  38. import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
  39. import org.igniterealtime.smack.inttest.annotations.SpecificationReference;
  40. import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
  41. import org.jxmpp.jid.EntityBareJid;
  42. import org.jxmpp.jid.parts.Resourcepart;
  43. import org.jxmpp.stringprep.XmppStringprepException;

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

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

  51.     @SmackIntegrationTest
  52.     public void mucTest() throws Exception {
  53.         EntityBareJid mucAddress = getRandomRoom("smack-inttest-message");

  54.         MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
  55.         MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);

  56.         final String mucMessage = "Smack Integration Test MUC Test Message " + randomString;
  57.         final SimpleResultSyncPoint resultSyncPoint = new SimpleResultSyncPoint();

  58.         mucAsSeenByTwo.addMessageListener(new MessageListener() {
  59.             @Override
  60.             public void processMessage(Message message) {
  61.                 String body = message.getBody();
  62.                 if (mucMessage.equals(body)) {
  63.                     resultSyncPoint.signal();
  64.                 }
  65.             }
  66.         });

  67.         createMuc(mucAsSeenByOne, "one-" + randomString);
  68.         mucAsSeenByTwo.join(Resourcepart.from("two-" + randomString));
  69.         mucAsSeenByOne.sendMessage(mucMessage);

  70.         try {
  71.             assertResult(resultSyncPoint, "Expected " + conTwo.getUser() + " to receive message that was sent by " + conOne.getUser() + " in room " + mucAddress + " (but it did not).");
  72.         } finally {
  73.             tryDestroy(mucAsSeenByOne);
  74.         }
  75.     }


  76.     /**
  77.      * Asserts that an owner is notified of room destruction when they destroy a room.
  78.      *
  79.      * @throws TimeoutException when roomDestroyed event doesn't get fired
  80.      * @throws Exception when other errors occur
  81.      */
  82.     @SmackIntegrationTest(section = "10.9", quote =
  83.         "A room owner MUST be able to destroy a room, especially if the room is persistent... The room removes all " +
  84.         "users from the room... and destroys the room")
  85.     public void mucDestroyOwnerTest() throws TimeoutException, Exception {

  86.         EntityBareJid mucAddress = getRandomRoom("smack-inttest-destroy-owner");

  87.         MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
  88.         createMuc(muc, Resourcepart.from("one-" + randomString));

  89.         // These would be a test implementation bug, not assertion failure.
  90.         if (!mucManagerOne.getJoinedRooms().contains(mucAddress)) {
  91.             tryDestroy(muc);
  92.             throw new IllegalStateException("Expected user to have joined a room '" + mucAddress + "' (but does not appear to have done so).");
  93.         }

  94.         final SimpleResultSyncPoint mucDestroyed = new SimpleResultSyncPoint();

  95.         UserStatusListener userStatusListener = new UserStatusListener() {
  96.             @Override
  97.             public void roomDestroyed(MultiUserChat alternateMUC, String password, String reason) {
  98.                 mucDestroyed.signal();
  99.             }
  100.         };

  101.         muc.addUserStatusListener(userStatusListener);

  102.         try {
  103.             muc.destroy("Dummy reason", null);
  104.             assertResult(mucDestroyed, "Expected " + conOne.getUser() + " to be notified of destruction of room " + mucAddress + " (but was not).");
  105.         } finally {
  106.             muc.removeUserStatusListener(userStatusListener);
  107.         }

  108.         Set<EntityBareJid> joinedRooms = mucManagerOne.getJoinedRooms();
  109.         assertFalse(muc.isJoined(), "Expected " + conOne.getUser() + " to no longer be in room " + mucAddress + " after it was destroyed, but it is still in.");
  110.         assertEquals(0, joinedRooms.size(), "Expected " + conOne.getUser() + " to no longer be in any rooms after " + mucAddress + " was destroyed. But it is still in " + joinedRooms);
  111.         assertEquals(0, muc.getOccupantsCount(), "Expected room " + mucAddress + " to no longer have any occupants after it was destroyed (but it has).");
  112.         assertNull(muc.getNickname());
  113.     }

  114.     /**
  115.      * Asserts that an occupant of a room is notified when a room is destroyed.
  116.      *
  117.      * @throws TimeoutException when roomDestroyed event doesn't get fired
  118.      * @throws Exception when other errors occur
  119.      */
  120.     @SmackIntegrationTest(section = "10.9", quote =
  121.         "A room owner MUST be able to destroy a room, especially if the room is persistent... The room removes all " +
  122.             "users from the room... and destroys the room")
  123.     public void mucDestroyTestOccupant() throws TimeoutException, Exception {

  124.         EntityBareJid mucAddress = getRandomRoom("smack-inttest-destroy-occupant");

  125.         MultiUserChat mucAsSeenByOwner = mucManagerOne.getMultiUserChat(mucAddress);
  126.         MultiUserChat mucAsSeenByParticipant = mucManagerTwo.getMultiUserChat(mucAddress);
  127.         createMuc(mucAsSeenByOwner, Resourcepart.from("one-" + randomString));

  128.         // These would be a test implementation bug, not assertion failure.
  129.         mucAsSeenByParticipant.join(Resourcepart.from("two-" + randomString));
  130.         if (!mucManagerTwo.getJoinedRooms().contains(mucAddress)) {
  131.             tryDestroy(mucAsSeenByOwner);
  132.             throw new IllegalStateException("Expected user to have joined a room '" + mucAddress + "' (but does not appear to have done so).");
  133.         }


  134.         final SimpleResultSyncPoint mucDestroyed = new SimpleResultSyncPoint();

  135.         UserStatusListener userStatusListener = new UserStatusListener() {
  136.             @Override
  137.             public void roomDestroyed(MultiUserChat alternateMUC, String password, String reason) {
  138.                 mucDestroyed.signal();
  139.             }
  140.         };

  141.         mucAsSeenByParticipant.addUserStatusListener(userStatusListener);

  142.         try {
  143.             mucAsSeenByOwner.destroy("Dummy reason", null);
  144.             assertResult(mucDestroyed, "Expected " + conTwo.getUser() + " to be notified of destruction of room " + mucAddress + " (but was not).");
  145.         } finally {
  146.             mucAsSeenByParticipant.removeUserStatusListener(userStatusListener);
  147.         }

  148.         Set<EntityBareJid> joinedRooms = mucManagerTwo.getJoinedRooms();
  149.         assertFalse(mucAsSeenByParticipant.isJoined(), "Expected " + conTwo.getUser() + " to no longer be in room " + mucAddress + " after it was destroyed, but it is still in.");
  150.         assertEquals(0, joinedRooms.size(), "Expected " + conTwo.getUser() + " to no longer be in any rooms after " + mucAddress + " was destroyed. But it is still in " + joinedRooms);
  151.         assertEquals(0, mucAsSeenByParticipant.getOccupantsCount(), "Expected room " + mucAddress + " to no longer have any occupants after it was destroyed (but it has).");
  152.         assertNull(mucAsSeenByParticipant.getNickname());
  153.     }

  154.     @SmackIntegrationTest
  155.     public void mucNameChangeTest()
  156.                     throws XmppStringprepException, MucAlreadyJoinedException, MissingMucCreationAcknowledgeException,
  157.                     NotAMucServiceException, NoResponseException, XMPPErrorException, NotConnectedException,
  158.                     InterruptedException, MucConfigurationNotSupportedException {
  159.         EntityBareJid mucAddress = getRandomRoom("smack-inttest-muc-name-change");

  160.         MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
  161.         createMuc(muc, Resourcepart.from("one-" + randomString));

  162.         final String newRoomName = "New Room Name (" + randomString + ")";

  163.         try {
  164.             muc.getConfigFormManager()
  165.                 .setRoomName(newRoomName)
  166.                 .submitConfigurationForm();

  167.             MultiUserChatManager mucManager = MultiUserChatManager.getInstanceFor(conTwo);
  168.             RoomInfo roomInfo = mucManager.getRoomInfo(muc.getRoom());
  169.             assertEquals(newRoomName, roomInfo.getName());
  170.         } finally {
  171.             tryDestroy(muc);
  172.         }
  173.     }

  174.     @SmackIntegrationTest(section = "8.1", quote = "modify the subject [...] MUST be denied if the <user@host> of the 'from' address of the request does not match "
  175.                     + "the bare JID portion of one of the moderators; in this case, the service MUST return a <forbidden/> error.")
  176.     public void mucTestVisitorNotAllowedToChangeSubject() throws XmppStringprepException, MucAlreadyJoinedException,
  177.                     MissingMucCreationAcknowledgeException, NotAMucServiceException, NoResponseException,
  178.                     XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException {
  179.         final EntityBareJid mucAddress = getRandomRoom("smack-inttest-visitor-change-subject");
  180.         final MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
  181.         final MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);

  182.         final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
  183.         final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);

  184.         createMuc(mucAsSeenByOne, nicknameOne);
  185.         try {
  186.             MucConfigFormManager configFormManager = mucAsSeenByOne.getConfigFormManager();
  187.             if (configFormManager.occupantsAreAllowedToChangeSubject()) {
  188.                 configFormManager.disallowOccupantsToChangeSubject().submitConfigurationForm();
  189.             }

  190.             mucAsSeenByTwo.join(nicknameTwo);

  191.             final XMPPException.XMPPErrorException e = assertThrows(XMPPException.XMPPErrorException.class, () -> {
  192.                 mucAsSeenByTwo.changeSubject("Test Subject Change");
  193.             }, "Expected an error after '" + conTwo.getUser()
  194.                             + "' (that is not a moderator) tried to change the subject of room '" + mucAddress
  195.                             + "' (but none occurred).");
  196.             assertEquals(StanzaError.Condition.forbidden, e.getStanzaError().getCondition(),
  197.                             "Unexpected error condition in the (expected) error that was returned to '"
  198.                                             + conTwo.getUser() + "' after it tried to change to subject of room '"
  199.                                             + mucAddress + "' while not being a moderator.");
  200.         } catch (MucConfigurationNotSupportedException e) {
  201.             throw new TestNotPossibleException(e);
  202.         } finally {
  203.             tryDestroy(mucAsSeenByOne);
  204.         }
  205.     }

  206.     @SmackIntegrationTest
  207.     public void mucTestChangeRoomName() throws XmppStringprepException, MucAlreadyJoinedException,
  208.                     MissingMucCreationAcknowledgeException, NotAMucServiceException, NoResponseException,
  209.                     XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException {
  210.         final EntityBareJid mucAddress = getRandomRoom("smack-inttest-change-room-name");
  211.         final MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
  212.         final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);

  213.         createMuc(mucAsSeenByOne, nicknameOne);
  214.         try {
  215.             String initialRoomName = "Initial Room Name";
  216.             mucAsSeenByOne.getConfigFormManager().setRoomName(initialRoomName).submitConfigurationForm();
  217.             RoomInfo roomInfo = mucManagerOne.getRoomInfo(mucAddress);
  218.             assertEquals(initialRoomName, roomInfo.getName());

  219.             String newRoomName = "New Room Name";
  220.             mucAsSeenByOne.getConfigFormManager().setRoomName(newRoomName).submitConfigurationForm();
  221.             roomInfo = mucManagerOne.getRoomInfo(mucAddress);
  222.             assertEquals(newRoomName, roomInfo.getName());
  223.         } catch (MucConfigurationNotSupportedException e) {
  224.             throw new TestNotPossibleException(e);
  225.         } finally {
  226.             tryDestroy(mucAsSeenByOne);
  227.         }
  228.     }
  229. }