mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-12-22 18:48:00 +01:00
[sinttest] Wait for notification filter to propagate
The UserTuneIntegrationTest, in rapid succession: - add a listener for PEP-published usertune data - publishes a usertune - waits for a notification to arrive Implicit to adding the listener is the publication of a change in Pubsub notification filtering. This can involve a stanza handshake, as CAPS is involved. A race condition exists where the usertune data can be published before the notification filter has been properly applied. The changes in this commit add a synchronzation point that ensures that the notification filter is in place, before the usertune data is published. Co-authored-by: Paul Schaub <vanitasvitae@fsfe.org>
This commit is contained in:
parent
e3c50aeeb7
commit
819ed87a17
3 changed files with 485 additions and 81 deletions
|
@ -23,8 +23,9 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener;
|
||||||
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
import org.jivesoftware.smackx.geoloc.GeoLocationManager;
|
import org.jivesoftware.smackx.geoloc.GeoLocationManager;
|
||||||
import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
|
import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
|
||||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||||
|
@ -35,7 +36,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass;
|
||||||
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
||||||
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
|
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
|
||||||
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.jxmpp.util.XmppDateTime;
|
import org.jxmpp.util.XmppDateTime;
|
||||||
|
|
||||||
public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
|
public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
|
@ -49,10 +50,21 @@ public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
glm2 = GeoLocationManager.getInstanceFor(conTwo);
|
glm2 = GeoLocationManager.getInstanceFor(conTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
||||||
|
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that a notification is sent when a publication is received, assuming that notification filtering
|
||||||
|
* has been adjusted to allow for the notification to be delivered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
@SmackIntegrationTest
|
@SmackIntegrationTest
|
||||||
public void test() throws TimeoutException, Exception {
|
public void testNotification() throws Exception {
|
||||||
GeoLocation.Builder builder = GeoLocation.builder();
|
GeoLocation.Builder builder = GeoLocation.builder();
|
||||||
GeoLocation geoLocation1 = builder.setAccuracy(23d)
|
GeoLocation data = builder.setAccuracy(23d)
|
||||||
.setAlt(1000d)
|
.setAlt(1000d)
|
||||||
.setAltAccuracy(10d)
|
.setAltAccuracy(10d)
|
||||||
.setArea("Delhi")
|
.setArea("Delhi")
|
||||||
|
@ -77,31 +89,163 @@ public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
||||||
final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
|
|
||||||
final PepEventListener<GeoLocation> geoLocationListener = new PepEventListener<GeoLocation>() {
|
|
||||||
|
|
||||||
@Override
|
final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
|
||||||
public void onPepEvent(EntityBareJid jid, GeoLocation geoLocation, String id, Message message) {
|
|
||||||
if (geoLocation.equals(geoLocation1)) {
|
final PepEventListener<GeoLocation> geoLocationListener = (jid, geoLocation, id, message) -> {
|
||||||
geoLocationReceived.signal();
|
if (geoLocation.equals(data)) {
|
||||||
} else {
|
geoLocationReceived.signal();
|
||||||
geoLocationReceived.signalFailure("Received non matching GeoLocation");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
glm2.addGeoLocationListener(geoLocationListener);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
glm1.publishGeoLocation(geoLocation1);
|
// Register ConTwo's interest in receiving geolocation notifications, and wait for that interest to have been propagated.
|
||||||
geoLocationReceived.waitForResult(timeout);
|
registerListenerAndWait(glm2, ServiceDiscoveryManager.getInstanceFor(conTwo), geoLocationListener);
|
||||||
|
|
||||||
|
// Publish the data.
|
||||||
|
glm1.publishGeoLocation(data); // for the purpose of this test, this needs not be blocking/use publishAndWait();
|
||||||
|
|
||||||
|
// Wait for the data to be received.
|
||||||
|
try {
|
||||||
|
Object result = geoLocationReceived.waitForResult(timeout);
|
||||||
|
|
||||||
|
// Explicitly assert the success case.
|
||||||
|
Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
Assertions.fail("Expected to receive a PEP notification, but did not.");
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
glm2.removeGeoLocationListener(geoLocationListener);
|
unregisterListener(glm2, geoLocationListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
/**
|
||||||
public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
|
* Verifies that a notification for a previously sent publication is received as soon as notification filtering
|
||||||
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
|
* has been adjusted to allow for the notification to be delivered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void testNotificationAfterFilterChange() throws Exception {
|
||||||
|
GeoLocation.Builder builder = GeoLocation.builder();
|
||||||
|
GeoLocation data = builder.setAccuracy(12d)
|
||||||
|
.setAlt(999d)
|
||||||
|
.setAltAccuracy(9d)
|
||||||
|
.setArea("Amsterdam")
|
||||||
|
.setBearing(9d)
|
||||||
|
.setBuilding("Test Building")
|
||||||
|
.setCountry("Netherlands")
|
||||||
|
.setCountryCode("NL")
|
||||||
|
.setDescription("My Description")
|
||||||
|
.setFloor("middle")
|
||||||
|
.setLat(25.098345d)
|
||||||
|
.setLocality("brilliant")
|
||||||
|
.setLon(77.992034)
|
||||||
|
.setPostalcode("110085")
|
||||||
|
.setRegion("North")
|
||||||
|
.setRoom("small")
|
||||||
|
.setSpeed(250.0d)
|
||||||
|
.setStreet("Wall Street")
|
||||||
|
.setText("Unit Testing GeoLocation 2")
|
||||||
|
.setTimestamp(XmppDateTime.parseDate("2007-02-19"))
|
||||||
|
.setTzo("+5:30")
|
||||||
|
.setUri(new URI("http://xmpp.org"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
||||||
|
|
||||||
|
final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
|
||||||
|
|
||||||
|
final PepEventListener<GeoLocation> geoLocationListener = (jid, geoLocation, id, message) -> {
|
||||||
|
if (geoLocation.equals(data)) {
|
||||||
|
geoLocationReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO Ensure that pre-existing filtering notification excludes geolocation.
|
||||||
|
try {
|
||||||
|
// Publish the data
|
||||||
|
publishAndWait(glm1, ServiceDiscoveryManager.getInstanceFor(conOne), data);
|
||||||
|
|
||||||
|
// Adds listener, which implicitly publishes a disco/info filter for geolocation notification.
|
||||||
|
registerListenerAndWait(glm2, ServiceDiscoveryManager.getInstanceFor(conTwo), geoLocationListener);
|
||||||
|
|
||||||
|
// Wait for the data to be received.
|
||||||
|
try {
|
||||||
|
Object result = geoLocationReceived.waitForResult(timeout);
|
||||||
|
|
||||||
|
// Explicitly assert the success case.
|
||||||
|
Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
Assertions.fail("Expected to receive a PEP notification, but did not.");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
unregisterListener(glm2, geoLocationListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener for GeoLocation data. This implicitly publishes a CAPS update to include a notification
|
||||||
|
* filter for the geolocation node. This method blocks until the server has indicated that this update has been
|
||||||
|
* received.
|
||||||
|
*
|
||||||
|
* @param geoManager The GeoLocationManager instance for the connection that is expected to receive data.
|
||||||
|
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
|
||||||
|
* @param listener A listener instance for GeoLocation data that is to be registered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
public void registerListenerAndWait(GeoLocationManager geoManager, ServiceDiscoveryManager discoManager, PepEventListener<GeoLocation> listener) throws Exception {
|
||||||
|
final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint();
|
||||||
|
final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> {
|
||||||
|
if (info.containsFeature(GeoLocationManager.GEOLOCATION_NODE + "+notify")) {
|
||||||
|
notificationFilterReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
|
||||||
|
try {
|
||||||
|
geoManager.addGeoLocationListener(listener);
|
||||||
|
notificationFilterReceived.waitForResult(timeout);
|
||||||
|
} finally {
|
||||||
|
discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The functionally reverse of {@link #registerListenerAndWait(GeoLocationManager, ServiceDiscoveryManager, PepEventListener)}
|
||||||
|
* with the difference of not being a blocking operation.
|
||||||
|
*
|
||||||
|
* @param geoManager The GeoLocationManager instance for the connection that was expected to receive data.
|
||||||
|
* @param listener A listener instance for GeoLocation data that is to be removed.
|
||||||
|
*/
|
||||||
|
public void unregisterListener(GeoLocationManager geoManager, PepEventListener<GeoLocation> listener) {
|
||||||
|
// Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API.
|
||||||
|
geoManager.removeGeoLocationListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish data using PEP, and block until the server has echoed the publication back to the publishing user.
|
||||||
|
*
|
||||||
|
* @param geoManager The GeoLocationManager instance for the connection that is expected to publish data.
|
||||||
|
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
|
||||||
|
* @param data The data to be published.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
public void publishAndWait(GeoLocationManager geoManager, ServiceDiscoveryManager discoManager, GeoLocation data) throws Exception {
|
||||||
|
final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint();
|
||||||
|
final PepEventListener<GeoLocation> publicationEchoListener = (jid, geoLocation, id, message) -> {
|
||||||
|
if (geoLocation.equals(data)) {
|
||||||
|
publicationEchoReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
registerListenerAndWait(geoManager, discoManager, publicationEchoListener);
|
||||||
|
geoManager.addGeoLocationListener(publicationEchoListener);
|
||||||
|
geoManager.publishGeoLocation(data);
|
||||||
|
} finally {
|
||||||
|
geoManager.removeGeoLocationListener(publicationEchoListener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,13 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.mood;
|
package org.jivesoftware.smackx.mood;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener;
|
||||||
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
import org.jivesoftware.smackx.mood.element.MoodElement;
|
import org.jivesoftware.smackx.mood.element.MoodElement;
|
||||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||||
|
|
||||||
|
@ -28,6 +32,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass;
|
||||||
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
||||||
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
|
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
|
||||||
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
|
||||||
public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
|
public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
|
|
||||||
|
@ -40,32 +45,155 @@ public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
mm2 = MoodManager.getInstanceFor(conTwo);
|
mm2 = MoodManager.getInstanceFor(conTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SmackIntegrationTest
|
|
||||||
public void test() throws Exception {
|
|
||||||
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
|
||||||
|
|
||||||
final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
|
|
||||||
|
|
||||||
final PepEventListener<MoodElement> moodListener = (jid, moodElement, id, message) -> {
|
|
||||||
if (moodElement.getMood() == Mood.satisfied) {
|
|
||||||
moodReceived.signal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mm2.addMoodListener(moodListener);
|
|
||||||
|
|
||||||
try {
|
|
||||||
mm1.setMood(Mood.satisfied);
|
|
||||||
|
|
||||||
moodReceived.waitForResult(timeout);
|
|
||||||
} finally {
|
|
||||||
mm2.removeMoodListener(moodListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public void unsubscribe()
|
public void unsubscribe()
|
||||||
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
|
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
|
||||||
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
|
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
|
||||||
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
|
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that a notification is sent when a publication is received, assuming that notification filtering
|
||||||
|
* has been adjusted to allow for the notification to be delivered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void testNotification() throws Exception {
|
||||||
|
Mood data = Mood.satisfied;
|
||||||
|
|
||||||
|
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
||||||
|
|
||||||
|
final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
|
||||||
|
|
||||||
|
final PepEventListener<MoodElement> moodListener = (jid, moodElement, id, message) -> {
|
||||||
|
if (moodElement.getMood().equals(data)) {
|
||||||
|
moodReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Register ConTwo's interest in receiving mood notifications, and wait for that interest to have been propagated.
|
||||||
|
registerListenerAndWait(mm2, ServiceDiscoveryManager.getInstanceFor(conTwo), moodListener);
|
||||||
|
|
||||||
|
// Publish the data.
|
||||||
|
mm1.setMood(data); // for the purpose of this test, this needs not be blocking/use publishAndWait();
|
||||||
|
|
||||||
|
// Wait for the data to be received.
|
||||||
|
try {
|
||||||
|
moodReceived.waitForResult(timeout);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
Assertions.fail("Expected to receive a PEP notification, but did not.");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
unregisterListener(mm2, moodListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that a notification for a previously sent publication is received as soon as notification filtering
|
||||||
|
* has been adjusted to allow for the notification to be delivered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void testNotificationAfterFilterChange() throws Exception {
|
||||||
|
Mood data = Mood.cautious;
|
||||||
|
|
||||||
|
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
||||||
|
|
||||||
|
final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
|
||||||
|
|
||||||
|
final PepEventListener<MoodElement> moodListener = (jid, moodElement, id, message) -> {
|
||||||
|
if (moodElement.getMood().equals(data)) {
|
||||||
|
moodReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO Ensure that pre-existing filtering notification excludes mood.
|
||||||
|
try {
|
||||||
|
// Publish the data
|
||||||
|
publishAndWait(mm1, ServiceDiscoveryManager.getInstanceFor(conOne), data);
|
||||||
|
|
||||||
|
// Adds listener, which implicitly publishes a disco/info filter for mood notification.
|
||||||
|
registerListenerAndWait(mm2, ServiceDiscoveryManager.getInstanceFor(conTwo), moodListener);
|
||||||
|
|
||||||
|
// Wait for the data to be received.
|
||||||
|
try {
|
||||||
|
Object result = moodReceived.waitForResult(timeout);
|
||||||
|
|
||||||
|
// Explicitly assert the success case.
|
||||||
|
Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
Assertions.fail("Expected to receive a PEP notification, but did not.");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
unregisterListener(mm2, moodListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener for User Tune data. This implicitly publishes a CAPS update to include a notification
|
||||||
|
* filter for the mood node. This method blocks until the server has indicated that this update has been
|
||||||
|
* received.
|
||||||
|
*
|
||||||
|
* @param moodManager The MoodManager instance for the connection that is expected to receive data.
|
||||||
|
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
|
||||||
|
* @param listener A listener instance for Mood data that is to be registered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
public void registerListenerAndWait(MoodManager moodManager, ServiceDiscoveryManager discoManager, PepEventListener<MoodElement> listener) throws Exception {
|
||||||
|
final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint();
|
||||||
|
final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> {
|
||||||
|
if (info.containsFeature(MoodManager.MOOD_NODE + "+notify")) {
|
||||||
|
notificationFilterReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
|
||||||
|
try {
|
||||||
|
moodManager.addMoodListener(listener);
|
||||||
|
notificationFilterReceived.waitForResult(timeout);
|
||||||
|
} finally {
|
||||||
|
discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The functionally reverse of {@link #registerListenerAndWait(MoodManager, ServiceDiscoveryManager, PepEventListener)}
|
||||||
|
* with the difference of not being a blocking operation.
|
||||||
|
*
|
||||||
|
* @param moodManager The MoodManager instance for the connection that was expected to receive data.
|
||||||
|
* @param listener A listener instance for Mood data that is to be removed.
|
||||||
|
*/
|
||||||
|
public void unregisterListener(MoodManager moodManager, PepEventListener<MoodElement> listener) {
|
||||||
|
// Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API.
|
||||||
|
moodManager.removeMoodListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish data using PEP, and block until the server has echoed the publication back to the publishing user.
|
||||||
|
*
|
||||||
|
* @param moodManager The MoodManager instance for the connection that is expected to publish data.
|
||||||
|
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
|
||||||
|
* @param data The data to be published.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
public void publishAndWait(MoodManager moodManager, ServiceDiscoveryManager discoManager, Mood data) throws Exception {
|
||||||
|
final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint();
|
||||||
|
final PepEventListener<MoodElement> publicationEchoListener = (jid, moodElement, id, message) -> {
|
||||||
|
if (moodElement.getMood().equals(data)) {
|
||||||
|
publicationEchoReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
registerListenerAndWait(moodManager, discoManager, publicationEchoListener);
|
||||||
|
moodManager.addMoodListener(publicationEchoListener);
|
||||||
|
moodManager.setMood(data);
|
||||||
|
} finally {
|
||||||
|
moodManager.removeMoodListener(publicationEchoListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,14 @@
|
||||||
package org.jivesoftware.smackx.usertune;
|
package org.jivesoftware.smackx.usertune;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener;
|
||||||
|
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||||
import org.jivesoftware.smackx.usertune.element.UserTuneElement;
|
import org.jivesoftware.smackx.usertune.element.UserTuneElement;
|
||||||
|
|
||||||
|
@ -32,7 +34,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass;
|
||||||
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
||||||
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
|
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
|
||||||
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
|
||||||
public class UserTuneIntegrationTest extends AbstractSmackIntegrationTest {
|
public class UserTuneIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
|
|
||||||
|
@ -45,46 +47,176 @@ public class UserTuneIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
utm2 = UserTuneManager.getInstanceFor(conTwo);
|
utm2 = UserTuneManager.getInstanceFor(conTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SmackIntegrationTest
|
|
||||||
public void test() throws Exception {
|
|
||||||
URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#9");
|
|
||||||
UserTuneElement.Builder builder = UserTuneElement.getBuilder();
|
|
||||||
UserTuneElement userTuneElement1 = builder.setArtist("Yes")
|
|
||||||
.setLength(686)
|
|
||||||
.setRating(8)
|
|
||||||
.setSource("Yessongs")
|
|
||||||
.setTitle("Heart of the Sunrise")
|
|
||||||
.setTrack("3")
|
|
||||||
.setUri(uri)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
|
||||||
|
|
||||||
final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint();
|
|
||||||
|
|
||||||
final PepEventListener<UserTuneElement> userTuneListener = new PepEventListener<UserTuneElement>() {
|
|
||||||
@Override
|
|
||||||
public void onPepEvent(EntityBareJid jid, UserTuneElement userTuneElement, String id, Message message) {
|
|
||||||
if (userTuneElement.equals(userTuneElement1)) {
|
|
||||||
userTuneReceived.signal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
utm2.addUserTuneListener(userTuneListener);
|
|
||||||
|
|
||||||
try {
|
|
||||||
utm1.publishUserTune(userTuneElement1);
|
|
||||||
userTuneReceived.waitForResult(timeout);
|
|
||||||
} finally {
|
|
||||||
utm2.removeUserTuneListener(userTuneListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public void unsubscribe()
|
public void unsubscribe()
|
||||||
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
|
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
|
||||||
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
|
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
|
||||||
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
|
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that a notification is sent when a publication is received, assuming that notification filtering
|
||||||
|
* has been adjusted to allow for the notification to be delivered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void testNotification() throws Exception {
|
||||||
|
URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#9");
|
||||||
|
UserTuneElement.Builder builder = UserTuneElement.getBuilder();
|
||||||
|
UserTuneElement data = builder.setArtist("Yes")
|
||||||
|
.setLength(686)
|
||||||
|
.setRating(8)
|
||||||
|
.setSource("Yessongs")
|
||||||
|
.setTitle("Heart of the Sunrise")
|
||||||
|
.setTrack("3")
|
||||||
|
.setUri(uri)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
||||||
|
|
||||||
|
final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint();
|
||||||
|
|
||||||
|
final PepEventListener<UserTuneElement> userTuneListener = (jid, userTune, id, message) -> {
|
||||||
|
if (userTune.equals(data)) {
|
||||||
|
userTuneReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Register ConTwo's interest in receiving user tune notifications, and wait for that interest to have been propagated.
|
||||||
|
registerListenerAndWait(utm2, ServiceDiscoveryManager.getInstanceFor(conTwo), userTuneListener);
|
||||||
|
|
||||||
|
// Publish the data.
|
||||||
|
utm1.publishUserTune(data); // for the purpose of this test, this needs not be blocking/use publishAndWait();
|
||||||
|
|
||||||
|
// Wait for the data to be received.
|
||||||
|
try {
|
||||||
|
Object result = userTuneReceived.waitForResult(timeout);
|
||||||
|
|
||||||
|
// Explicitly assert the success case.
|
||||||
|
Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
Assertions.fail("Expected to receive a PEP notification, but did not.");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
unregisterListener(utm2, userTuneListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that a notification for a previously sent publication is received as soon as notification filtering
|
||||||
|
* has been adjusted to allow for the notification to be delivered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void testNotificationAfterFilterChange() throws Exception {
|
||||||
|
URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#8");
|
||||||
|
UserTuneElement.Builder builder = UserTuneElement.getBuilder();
|
||||||
|
UserTuneElement data = builder.setArtist("No")
|
||||||
|
.setLength(306)
|
||||||
|
.setRating(3)
|
||||||
|
.setSource("NoSongs")
|
||||||
|
.setTitle("Sunrise of the Heart")
|
||||||
|
.setTrack("2")
|
||||||
|
.setUri(uri)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
|
||||||
|
|
||||||
|
final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint();
|
||||||
|
|
||||||
|
final PepEventListener<UserTuneElement> userTuneListener = (jid, userTune, id, message) -> {
|
||||||
|
if (userTune.equals(data)) {
|
||||||
|
userTuneReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO Ensure that pre-existing filtering notification excludes userTune.
|
||||||
|
try {
|
||||||
|
// Publish the data
|
||||||
|
publishAndWait(utm1, ServiceDiscoveryManager.getInstanceFor(conOne), data);
|
||||||
|
|
||||||
|
// Adds listener, which implicitly publishes a disco/info filter for userTune notification.
|
||||||
|
registerListenerAndWait(utm2, ServiceDiscoveryManager.getInstanceFor(conTwo), userTuneListener);
|
||||||
|
|
||||||
|
// Wait for the data to be received.
|
||||||
|
try {
|
||||||
|
Object result = userTuneReceived.waitForResult(timeout);
|
||||||
|
|
||||||
|
// Explicitly assert the success case.
|
||||||
|
Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
Assertions.fail("Expected to receive a PEP notification, but did not.");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
unregisterListener(utm2, userTuneListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener for User Tune data. This implicitly publishes a CAPS update to include a notification
|
||||||
|
* filter for the usertune node. This method blocks until the server has indicated that this update has been
|
||||||
|
* received.
|
||||||
|
*
|
||||||
|
* @param userTuneManager The UserTuneManager instance for the connection that is expected to receive data.
|
||||||
|
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
|
||||||
|
* @param listener A listener instance for UserTune data that is to be registered.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
public void registerListenerAndWait(UserTuneManager userTuneManager, ServiceDiscoveryManager discoManager, PepEventListener<UserTuneElement> listener) throws Exception {
|
||||||
|
final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint();
|
||||||
|
final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> {
|
||||||
|
if (info.containsFeature(UserTuneManager.USERTUNE_NODE + "+notify")) {
|
||||||
|
notificationFilterReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
|
||||||
|
try {
|
||||||
|
userTuneManager.addUserTuneListener(listener);
|
||||||
|
notificationFilterReceived.waitForResult(timeout);
|
||||||
|
} finally {
|
||||||
|
discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The functionally reverse of {@link #registerListenerAndWait(UserTuneManager, ServiceDiscoveryManager, PepEventListener)}
|
||||||
|
* with the difference of not being a blocking operation.
|
||||||
|
*
|
||||||
|
* @param userTuneManager The UserTuneManager instance for the connection that was expected to receive data.
|
||||||
|
* @param listener A listener instance for UserTune data that is to be removed.
|
||||||
|
*/
|
||||||
|
public void unregisterListener(UserTuneManager userTuneManager, PepEventListener<UserTuneElement> listener) {
|
||||||
|
// Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API.
|
||||||
|
userTuneManager.removeUserTuneListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publish data using PEP, and block until the server has echoed the publication back to the publishing user.
|
||||||
|
*
|
||||||
|
* @param userTuneManager The UserTuneManager instance for the connection that is expected to publish data.
|
||||||
|
* @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
|
||||||
|
* @param data The data to be published.
|
||||||
|
*
|
||||||
|
* @throws Exception if the test fails
|
||||||
|
*/
|
||||||
|
public void publishAndWait(UserTuneManager userTuneManager, ServiceDiscoveryManager discoManager, UserTuneElement data) throws Exception {
|
||||||
|
final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint();
|
||||||
|
final PepEventListener<UserTuneElement> publicationEchoListener = (jid, userTune, id, message) -> {
|
||||||
|
if (userTune.equals(data)) {
|
||||||
|
publicationEchoReceived.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
registerListenerAndWait(userTuneManager, discoManager, publicationEchoListener);
|
||||||
|
userTuneManager.addUserTuneListener(publicationEchoListener);
|
||||||
|
userTuneManager.publishUserTune(data);
|
||||||
|
} finally {
|
||||||
|
userTuneManager.removeUserTuneListener(publicationEchoListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue