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 1735d0b4f..80371f7c8 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
@@ -223,6 +223,15 @@ public class MucConfigFormManager {
}
+ /**
+ * Check if the room supports its visibility being controlled vioa configuration.
+ *
+ * @return true if supported, false if not.
+ */
+ public boolean supportsPublicRoom() {
+ return answerForm.hasField(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM);
+ }
+
/**
* Make the room publicly searchable.
*
@@ -251,7 +260,7 @@ public class MucConfigFormManager {
* @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
*/
public MucConfigFormManager setPublic(boolean isPublic) throws MucConfigurationNotSupportedException {
- if (!supportsModeration()) {
+ if (!supportsPublicRoom()) {
throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM);
}
answerForm.setAnswer(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM, isPublic);
@@ -299,7 +308,7 @@ public class MucConfigFormManager {
*/
public MucConfigFormManager setIsPasswordProtected(boolean isPasswordProtected)
throws MucConfigurationNotSupportedException {
- if (!supportsMembersOnly()) {
+ if (!supportsPasswordProtected()) {
throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM);
}
answerForm.setAnswer(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM, isPasswordProtected);
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/MultiResultSyncPoint.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/MultiResultSyncPoint.java
new file mode 100644
index 000000000..22e046d64
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/MultiResultSyncPoint.java
@@ -0,0 +1,59 @@
+/**
+ *
+ * Copyright 2021 Guus der Kinderen
+ *
+ * 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.igniterealtime.smack.inttest.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+import org.jivesoftware.smack.util.Objects;
+
+public class MultiResultSyncPoint {
+
+ private final List results;
+ private E exception;
+ private final int expectedResultCount;
+
+ public MultiResultSyncPoint(int expectedResultCount) {
+ this.expectedResultCount = expectedResultCount;
+ this.results = new ArrayList<>(expectedResultCount);
+ }
+
+ public synchronized List waitForResults(long timeout) throws E, InterruptedException, TimeoutException {
+ long now = System.currentTimeMillis();
+ final long deadline = now + timeout;
+ while (results.size() < expectedResultCount && exception == null && now < deadline) {
+ wait(deadline - now);
+ now = System.currentTimeMillis();
+ }
+ if (now >= deadline) throw new TimeoutException("Timeout waiting " + timeout + " millis");
+ if (exception != null) throw exception;
+ return new ArrayList<>(results);
+ }
+
+ public synchronized void signal(R result) {
+ this.results.add(Objects.requireNonNull(result));
+ if (expectedResultCount <= results.size()) {
+ notifyAll();
+ }
+ }
+
+ public synchronized void signal(E exception) {
+ this.exception = Objects.requireNonNull(exception);
+ notifyAll();
+ }
+}
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 22316ed6e..2ad9ca2d4 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,6 +22,8 @@ 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;
@@ -140,4 +142,75 @@ public abstract class AbstractMultiUserChatIntegrationTest extends AbstractSmack
.makeHidden()
.submitConfigurationForm();
}
+
+ /**
+ * Creates a non-anonymous room.
+ *
+ *
From XEP-0045 § 10.1.3:
+ *
+ * Note: The _whois configuration option specifies whether the room is non-anonymous (a value of "anyone"),
+ * semi-anonymous (a value of "moderators"), or fully anonmyous (a value of "none", not shown here).
+ *
+ * Note: The _whois configuration option specifies whether the room is non-anonymous (a value of "anyone"),
+ * semi-anonymous (a value of "moderators"), or fully anonmyous (a value of "none", not shown here).
+ *
+ */
+ static void createSemiAnonymousMuc(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_whois", "moderators");
+ muc.sendConfigurationForm(answerForm);
+ }
+
+ /**
+ * Creates a password-protected room.
+ */
+ static void createPasswordProtectedMuc(MultiUserChat muc, Resourcepart resourceName, String password) 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_passwordprotectedroom", true);
+ answerForm.setAnswer("muc#roomconfig_roomsecret", password);
+ muc.sendConfigurationForm(answerForm);
+ }
+
+ static void createLockedMuc(MultiUserChat muc, Resourcepart resourceName) throws
+ SmackException.NoResponseException, XMPPException.XMPPErrorException,
+ InterruptedException, MultiUserChatException.MucAlreadyJoinedException,
+ SmackException.NotConnectedException,
+ MultiUserChatException.MissingMucCreationAcknowledgeException,
+ MultiUserChatException.NotAMucServiceException {
+ muc.create(resourceName);
+ // Note the absence of handle.makeInstant() here. The room is still being created at this point, until a
+ // configuration is set.
+ }
+
+ static void setMaxUsers(MultiUserChat muc, int maxUsers) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, SmackException.NotConnectedException {
+ Form configForm = muc.getConfigurationForm();
+ FillableForm answerForm = configForm.getFillableForm();
+ answerForm.setAnswer("muc#roomconfig_maxusers", maxUsers);
+ muc.sendConfigurationForm(answerForm);
+ }
+
+ static void setPublicLogging(MultiUserChat muc, boolean publicLogging) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, SmackException.NotConnectedException {
+ Form configForm = muc.getConfigurationForm();
+ FillableForm answerForm = configForm.getFillableForm();
+ answerForm.setAnswer("muc#roomconfig_enablelogging", publicLogging);
+ muc.sendConfigurationForm(answerForm);
+ }
}
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
index 30b052466..0a8dcf175 100644
--- 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
@@ -22,6 +22,7 @@ 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.List;
import java.util.Map;
import org.jivesoftware.smack.SmackException;
@@ -30,11 +31,13 @@ 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.jivesoftware.smackx.muc.packet.MUCInitialPresence;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.annotations.SpecificationReference;
+
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
@@ -49,6 +52,21 @@ public class MultiUserChatEntityIntegrationTest extends AbstractMultiUserChatInt
super(environment);
}
+ /**
+ * Asserts that a MUC service can be discovered.
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest(section = "6.1", quote =
+ "An entity often discovers a MUC service by sending a Service Discovery items (\"disco#items\") request to " +
+ "its own server. The server then returns the services that are associated with it.")
+ public void mucTestForDiscoveringMuc() throws Exception {
+ // This repeats some logic from the `AbstractMultiUserChatIntegrationTest` constructor, but is preserved here
+ // as an explicit test, because that might not always be true.
+ List services = ServiceDiscoveryManager.getInstanceFor(conOne).findServices(MUCInitialPresence.NAMESPACE, true, false);
+ assertFalse(services.isEmpty(), "Expected to be able to find MUC services on the domain that '" + conOne.getUser() + "' is connecting to (but could not).");
+ }
+
/**
* Asserts that a MUC service can have its features discovered.
*
@@ -56,13 +74,12 @@ public class MultiUserChatEntityIntegrationTest extends AbstractMultiUserChatInt
*/
@SmackIntegrationTest(section = "6.2", quote =
"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.")
+ "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.")
public void mucTestForDiscoveringFeatures() throws Exception {
- final DomainBareJid mucServiceAddress = mucManagerOne.getMucServiceDomains().get(0);
- DiscoverInfo info = mucManagerOne.getMucServiceDiscoInfo(mucServiceAddress);
- assertFalse(info.getIdentities().isEmpty(), "Expected the service discovery information for service " + mucServiceAddress + " to include identities (but it did not).");
- assertFalse(info.getFeatures().isEmpty(), "Expected the service discovery information for service " + mucServiceAddress + " to include features (but it did not).");
+ DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(conOne).discoverInfo(mucService);
+ assertFalse(info.getIdentities().isEmpty(), "Expected the service discovery information for service " + mucService + " to include identities (but it did not).");
+ assertFalse(info.getFeatures().isEmpty(), "Expected the service discovery information for service " + mucService + " to include features (but it did not).");
}
/**
@@ -120,6 +137,7 @@ public class MultiUserChatEntityIntegrationTest extends AbstractMultiUserChatInt
assertFalse(discoInfo.getIdentities().isEmpty(), "Expected the service discovery information for room " + mucAddress + " to include identities (but it did not).");
assertFalse(discoInfo.getFeatures().isEmpty(), "Expected the service discovery information for room " + mucAddress + " to include features (but it did not).");
+ 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).");
}
/**
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java
index 930bbaf00..421109a00 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java
@@ -17,9 +17,7 @@
package org.jivesoftware.smackx.muc;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.concurrent.TimeoutException;
@@ -27,9 +25,6 @@ import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
-import org.jivesoftware.smack.packet.Presence;
-
-import org.jivesoftware.smackx.muc.packet.MUCUser;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
@@ -48,62 +43,6 @@ public class MultiUserChatIntegrationTest extends AbstractMultiUserChatIntegrati
super(environment);
}
- /**
- * Asserts that when a user joins a room, they are themselves included on the list of users notified (self-presence).
- *
- * @throws Exception when errors occur
- */
- @SmackIntegrationTest(section = "7.2.2", quote =
- "... the service MUST also send presence from the new participant's occupant JID to the full JIDs of all the " +
- "occupants (including the new occupant)")
- public void mucJoinTest() throws Exception {
- EntityBareJid mucAddress = getRandomRoom("smack-inttest-join");
-
- MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
- try {
- Presence reflectedJoinPresence = muc.join(Resourcepart.from("nick-one"));
-
- MUCUser mucUser = MUCUser.from(reflectedJoinPresence);
-
- assertNotNull(mucUser, "Expected, but unable, to create a MUCUser instance from reflected join presence: " + reflectedJoinPresence);
- assertTrue(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Expected the reflected join presence of " + conOne.getUser() + " of room " + mucAddress + " to include 'presence-to-self' (" + MUCUser.Status.PRESENCE_TO_SELF_110 + ") but it did not.");
- assertEquals(mucAddress + "/nick-one", reflectedJoinPresence.getFrom().toString(), "Unexpected 'from' attribute value in the reflected join presence of " + conOne.getUser() + " of room " + mucAddress);
- assertEquals(conOne.getUser().asEntityFullJidIfPossible().toString(), reflectedJoinPresence.getTo().toString(), "Unexpected 'to' attribute value in the reflected join presence of " + conOne.getUser() + " of room " + mucAddress);
- } finally {
- tryDestroy(muc);
- }
- }
-
- /**
- * Asserts that when a user leaves a room, they are themselves included on the list of users notified (self-presence).
- *
- * @throws Exception when errors occur
- */
- @SmackIntegrationTest(section = "7.14", quote =
- "The service MUST then send a presence stanzas of type \"unavailable\" from the departing user's occupant " +
- "JID to the departing occupant's full JIDs, including a status code of \"110\" to indicate that this " +
- "notification is \"self-presence\"")
- public void mucLeaveTest() throws Exception {
- EntityBareJid mucAddress = getRandomRoom("smack-inttest-leave");
-
- MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
- try {
- muc.join(Resourcepart.from("nick-one"));
-
- Presence reflectedLeavePresence = muc.leave();
-
- MUCUser mucUser = MUCUser.from(reflectedLeavePresence);
- assertNotNull(mucUser, "Expected, but unable, to create a MUCUser instance from reflected leave presence: " + reflectedLeavePresence);
-
- assertTrue(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Expected the reflected leave presence of " + conOne.getUser() + " of room " + mucAddress + " to include 'presence-to-self' (" + MUCUser.Status.PRESENCE_TO_SELF_110 + ") but it did not.");
- assertEquals(mucAddress + "/nick-one", reflectedLeavePresence.getFrom().toString(), "Unexpected 'from' attribute value in the reflected leave presence of " + conOne.getUser() + " of room " + mucAddress);
- assertEquals(conOne.getUser().asEntityFullJidIfPossible().toString(), reflectedLeavePresence.getTo().toString(), "Unexpected 'to' attribute value in the reflected leave presence of " + conOne.getUser() + " of room " + mucAddress);
- } finally {
- muc.join(Resourcepart.from("nick-one")); // We need to be in the room to destroy the room
- tryDestroy(muc);
- }
- }
-
@SmackIntegrationTest
public void mucTest() throws Exception {
EntityBareJid mucAddress = getRandomRoom("smack-inttest-message");
@@ -150,7 +89,7 @@ public class MultiUserChatIntegrationTest extends AbstractMultiUserChatIntegrati
EntityBareJid mucAddress = getRandomRoom("smack-inttest-destroy");
MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
- muc.join(Resourcepart.from("nick-one"));
+ createMuc(muc, Resourcepart.from("one-" + randomString));
final SimpleResultSyncPoint mucDestroyed = new SimpleResultSyncPoint();
@@ -165,8 +104,8 @@ public class MultiUserChatIntegrationTest extends AbstractMultiUserChatIntegrati
muc.addUserStatusListener(userStatusListener);
// These would be a test implementation bug, not assertion failure.
- if (mucManagerOne.getJoinedRooms().size() != 1) {
- throw new IllegalStateException("Expected user to have joined a room.");
+ if (mucManagerOne.getJoinedRooms().stream().noneMatch(room -> room.equals(mucAddress))) {
+ throw new IllegalStateException("Expected user to have joined a room '" + mucAddress + "' (but does not appear to have done so).");
}
try {
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatOccupantIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatOccupantIntegrationTest.java
new file mode 100644
index 000000000..1125e320d
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatOccupantIntegrationTest.java
@@ -0,0 +1,1102 @@
+/**
+ *
+ * Copyright 2015-2020 Florian Schmaus, 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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.StanzaError;
+import org.jivesoftware.smack.sm.predicates.ForEveryMessage;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smackx.muc.packet.MUCItem;
+import org.jivesoftware.smackx.muc.packet.MUCUser;
+
+import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
+import org.igniterealtime.smack.inttest.TestNotPossibleException;
+import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
+import org.igniterealtime.smack.inttest.annotations.SpecificationReference;
+import org.igniterealtime.smack.inttest.util.MultiResultSyncPoint;
+import org.igniterealtime.smack.inttest.util.ResultSyncPoint;
+import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
+import org.jxmpp.jid.EntityBareJid;
+import org.jxmpp.jid.EntityFullJid;
+import org.jxmpp.jid.impl.JidCreate;
+import org.jxmpp.jid.parts.Resourcepart;
+
+@SpecificationReference(document = "XEP-0045")
+public class MultiUserChatOccupantIntegrationTest extends AbstractMultiUserChatIntegrationTest {
+
+ public MultiUserChatOccupantIntegrationTest(SmackIntegrationTestEnvironment environment)
+ throws SmackException.NoResponseException, XMPPException.XMPPErrorException,
+ SmackException.NotConnectedException, InterruptedException, TestNotPossibleException {
+ super(environment);
+ }
+
+ /**
+ * Asserts that when a user joins a room, all events are received, and in the correct order.
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest(section = "7.1 & 7.2.2", quote = "" +
+ "§ 7.1 The order of events involved in joining a room needs to be consistent so that clients can know which events to expect when. After a client sends presence to join a room, the MUC service MUST send it events in the following order: 1. In-room presence from other occupants 2. In-room presence from the joining entity itself (so-called \"self-presence\") 3. Room history (if any) 4. The room subject [...]" +
+ "§ 7.2.2 This self-presence MUST NOT be sent to the new occupant until the room has sent the presence of all other occupants to the new occupant ... The service MUST first send the complete list of the existing occupants to the new occupant and only then send the new occupant's own presence to the new occupant")
+ public void mucJoinEventOrderingTest() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest-eventordering");
+ final String mucSubject = "Subject smack-inttest-eventordering " + randomString;
+ final String mucMessage = "Message smack-inttest-eventordering " + randomString;
+
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
+
+ final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+
+ createMuc(mucAsSeenByOne, nicknameOne);
+ mucAsSeenByOne.changeSubject(mucSubject); // Blocks until confirmed.
+
+ // Send and wait for the message to have been reflected, so that we can be sure it's part of the MUC history.
+ final SimpleResultSyncPoint messageReflectionSyncPoint = new SimpleResultSyncPoint();
+ mucAsSeenByOne.addMessageListener(message -> {
+ if (message.getBody().equals(mucMessage)) {
+ messageReflectionSyncPoint.signal();
+ }
+ });
+
+ mucAsSeenByOne.sendMessage(mucMessage);
+ messageReflectionSyncPoint.waitForResult(timeout);
+
+ final ResultSyncPoint subjectResultSyncPoint = new ResultSyncPoint<>();
+ List