1
0
Fork 0
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:
Guus der Kinderen 2020-10-12 16:58:33 +02:00 committed by Florian Schmaus
parent e3c50aeeb7
commit 819ed87a17
3 changed files with 485 additions and 81 deletions

View file

@ -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);
}
} }
} }

View file

@ -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);
}
}
} }

View file

@ -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);
}
}
} }