From e51cf47b299f2e323925a7884bef4295abeb80a5 Mon Sep 17 00:00:00 2001 From: Dan Caseley Date: Sun, 22 Aug 2021 16:31:58 +0100 Subject: [PATCH] =?UTF-8?q?[sinttest]=20Additional=20tests=20for=20=C2=A7?= =?UTF-8?q?=206=20of=20XEP-0045?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modified-by: Florian Schmaus --- .../smackx/muc/MucConfigFormManager.java | 82 +++++++ .../AbstractMultiUserChatIntegrationTest.java | 30 +-- .../MultiUserChatEntityIntegrationTest.java | 200 ++++++++++++++++++ 3 files changed, 299 insertions(+), 13 deletions(-) create mode 100644 smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java index 8d941ee06..1735d0b4f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java @@ -78,6 +78,17 @@ public class MucConfigFormManager { */ public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret"; + /** + * The constant String {@value}. + */ + public static final String MUC_ROOMCONFIG_MODERATEDROOM = "muc#roomconfig_moderatedroom"; + + /** + * The constant String {@value}. + */ + public static final String MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM = "muc#roomconfig_publicroom"; + + private final MultiUserChat multiUserChat; private final FillableForm answerForm; private final List owners; @@ -151,6 +162,15 @@ public class MucConfigFormManager { return answerForm.hasField(MUC_ROOMCONFIG_MEMBERSONLY); } + /** + * Check if the room supports being moderated in the configuration. + * + * @return true if supported, false if not. + */ + public boolean supportsModeration() { + return answerForm.hasField(MUC_ROOMCONFIG_MODERATEDROOM); + } + /** * Make the room for members only. * @@ -176,6 +196,68 @@ public class MucConfigFormManager { return this; } + + /** + * Make the room moderated. + * + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager makeModerated() throws MucConfigurationNotSupportedException { + return setModerated(true); + } + + /** + * Set if the room is members only. Rooms are not members only per default. + * + * @param isModerated if the room should be moderated. + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager setModerated(boolean isModerated) throws MucConfigurationNotSupportedException { + if (!supportsModeration()) { + throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MODERATEDROOM); + } + answerForm.setAnswer(MUC_ROOMCONFIG_MODERATEDROOM, isModerated); + return this; + } + + + /** + * Make the room publicly searchable. + * + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager makePublic() throws MucConfigurationNotSupportedException { + return setPublic(true); + } + + /** + * Make the room hidden (not publicly searchable). + * + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager makeHidden() throws MucConfigurationNotSupportedException { + return setPublic(false); + } + + /** + * Set if the room is publicly searchable (i.e. visible via discovery requests to the MUC service). + * + * @param isPublic if the room should be publicly searchable. + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager setPublic(boolean isPublic) throws MucConfigurationNotSupportedException { + if (!supportsModeration()) { + throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM); + } + answerForm.setAnswer(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM, isPublic); + return this; + } + /** * Check if the room supports password protection. * diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java index d20a51f52..9fa3aa8d3 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java @@ -22,8 +22,6 @@ import java.util.List; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smackx.xdata.form.FillableForm; -import org.jivesoftware.smackx.xdata.form.Form; import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; @@ -124,16 +122,22 @@ public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrati } } - static void createModeratedMuc(MultiUserChat muc, Resourcepart resourceName) throws - SmackException.NoResponseException, XMPPException.XMPPErrorException, - InterruptedException, MultiUserChatException.MucAlreadyJoinedException, - SmackException.NotConnectedException, - MultiUserChatException.MissingMucCreationAcknowledgeException, - MultiUserChatException.NotAMucServiceException { - muc.create(resourceName); - Form configForm = muc.getConfigurationForm(); - FillableForm answerForm = configForm.getFillableForm(); - answerForm.setAnswer("muc#roomconfig_moderatedroom", true); //TODO Add this to the MucConfigFormManager? - muc.sendConfigurationForm(answerForm); + static void createModeratedMuc(MultiUserChat muc, Resourcepart resourceName) + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, + MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, + MultiUserChatException.MissingMucCreationAcknowledgeException, + MultiUserChatException.NotAMucServiceException, + MultiUserChatException.MucConfigurationNotSupportedException { + MultiUserChat.MucCreateConfigFormHandle handle = muc.create(resourceName); + handle.getConfigFormManager().makeModerated().submitConfigurationForm(); + } + + static void createHiddenMuc(MultiUserChat muc, Resourcepart resourceName) + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, + MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, + MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException, + MultiUserChatException.MucConfigurationNotSupportedException { + MultiUserChat.MucCreateConfigFormHandle handle = muc.create(resourceName); + handle.getConfigFormManager().makeHidden().submitConfigurationForm(); } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java new file mode 100644 index 000000000..3e3b2fb7e --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java @@ -0,0 +1,200 @@ +/** + * + * Copyright 2021 Dan Caseley + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.StanzaError; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; +import org.jivesoftware.smackx.disco.packet.DiscoverItems; + +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.TestNotPossibleException; +import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.jid.parts.Resourcepart; + +public class MultiUserChatEntityIntegrationTest extends AbstractMultiUserChatIntegrationTest { + + public MultiUserChatEntityIntegrationTest(SmackIntegrationTestEnvironment environment) + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, InterruptedException, TestNotPossibleException { + super(environment); + } + + /** + * Asserts that a MUC service can have its features discovered + * + *

From XEP-0045 § 6.2:

+ *
+ * An entity may wish to discover if a service implements the Multi-User Chat protocol; in order to do so, it + * sends a service discovery information ("disco#info") query to the MUC service's JID. The service MUST return + * its identity and the features it supports. + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForDiscoveringFeatures() throws Exception { + DiscoverInfo info = mucManagerOne.getMucServiceDiscoInfo(mucManagerOne.getMucServiceDomains().get(0)); + assertTrue(info.getIdentities().size() > 0); + assertTrue(info.getFeatures().size() > 0); + } + + /** + * Asserts that a MUC Service lists its public rooms. + * + *

From XEP-0045 § 6.3:

+ *
+ * The service discovery items ("disco#items") protocol enables an entity to query a service for a list of + * associated items, which in the case of a chat service would consist of the specific chat rooms hosted by the + * service. The service SHOULD return a full list of the public rooms it hosts (i.e., not return any rooms that + * are hidden). + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForDiscoveringRooms() throws Exception { + EntityBareJid mucAddressPublic = getRandomRoom("smack-inttest-publicroom"); + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddressPublic); + + EntityBareJid mucAddressHidden = getRandomRoom("smack-inttest-hiddenroom"); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddressHidden); + + createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString)); + + Map rooms; + try { + createHiddenMuc(mucAsSeenByTwo, Resourcepart.from("two-" + randomString)); + rooms = mucManagerThree.getRoomsHostedBy(mucService); + } finally { + tryDestroy(mucAsSeenByOne); + tryDestroy(mucAsSeenByTwo); + } + + assertTrue(rooms.containsKey(mucAddressPublic)); + assertFalse(rooms.containsKey(mucAddressHidden)); + } + + /** + * Asserts that a MUC Service returns disco info for a room. + * + *

From XEP-0045 § 6.4:

+ *
+ * Using the disco#info protocol, an entity may also query a specific chat room for more detailed information + * about the room....The room MUST return its identity and SHOULD return the features it supports + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForDiscoveringRoomInfo() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoinfo"); + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString)); + + DiscoverInfo discoInfo; + try { + // Use SDM because mucManagerOne.getRoomInfo(mucAddress) might not use Disco + discoInfo = ServiceDiscoveryManager.getInstanceFor(conOne).discoverInfo(mucAddress, null); + } finally { + tryDestroy(mucAsSeenByOne); + } + + assertTrue(discoInfo.getIdentities().size() > 0); + assertTrue(discoInfo.getFeatures().size() > 0); + } + + /** + * Asserts that a MUC Service returns disco info for a room's items. + * + *

From XEP-0045 § 6.5:

+ *
+ * An entity MAY also query a specific chat room for its associated items. An implementation MAY return a list + * of existing occupants if that information is publicly available, or return no list at all if this information is + * kept private. + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForDiscoveringRoomItems() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoitems"); + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString)); + + DiscoverItems roomItems; + try { + roomItems = ServiceDiscoveryManager.getInstanceFor(conTwo).discoverItems(mucAddress, null); + } finally { + tryDestroy(mucAsSeenByOne); + } + + assertEquals(1, roomItems.getItems().size()); + } + + /** + * Asserts that a non-occupant receives a Bad Request error when attempting to query an occupant by their + * occupant JID. + * + *

From XEP-0045 § 6.6:

+ *
+ * If a non-occupant attempts to send a disco request to an address of the form <room@service/nick>, a MUC service + * MUST return a <bad-request/> error + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForRejectingDiscoOnRoomOccupantByNonOccupant() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoitems"); + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); + createMuc(mucAsSeenByOne, nicknameOne); + + XMPPException.XMPPErrorException xe; + try { + xe = assertThrows(XMPPException.XMPPErrorException.class, + () -> ServiceDiscoveryManager.getInstanceFor(conTwo).discoverItems( + JidCreate.entityFullFrom(mucAddress, nicknameOne), null)); + } finally { + tryDestroy(mucAsSeenByOne); + } + + final StanzaError.Condition expectedCondition; + switch (sinttestConfiguration.compatibilityMode) { + default: + expectedCondition = StanzaError.Condition.bad_request; + break; + case ejabberd: + expectedCondition = StanzaError.Condition.not_acceptable; + break; + } + assertEquals(xe.getStanzaError().getCondition(), expectedCondition); + } +}