package org.limewire.xmpp.client.impl;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.limewire.friend.api.Friend;
import org.limewire.friend.api.FriendConnection;
import org.limewire.friend.api.FriendConnectionConfiguration;
import org.limewire.friend.api.FriendException;
import org.limewire.friend.api.FriendRequest;
import org.limewire.friend.api.FriendRequestEvent;
import org.limewire.friend.api.RosterEvent;
import org.limewire.friend.api.FriendConnectionEvent;
import org.limewire.listener.ListenerSupport;
import org.limewire.listener.EventListener;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import com.google.inject.TypeLiteral;
import com.google.inject.Key;
/**
* Test cases pertaining to friend subscriptions
* (friend requests, and friend accept/decline/removes)
*/
public class XmppFriendSubscriptionTest extends XmppBaseTestCase {
private static final Log LOG = LogFactory.getLog(XmppFriendSubscriptionTest.class);
private static final int MAX_WAIT_FRIEND_REQUEST = 20;
static {
// get the hour (00 - 23) and modulo 4 in order to cycle thru
// gmail usernames because of server friend request limitations
Calendar cal = new GregorianCalendar();
int numUser = cal.get(Calendar.HOUR_OF_DAY) % 4;
USERNAME_A = "automatedtestfrienda" + numUser + "@gmail.com";
USERNAME_B = "automatedtestfriendb" + numUser + "@gmail.com";
}
private static final String USERNAME_A;
private static final String USERNAME_B;
private static final String PASSWORD = "automatedtestfriend";
private RosterListenerMock autoFriendARosterListener;
private RosterListenerMock autoFriendBRosterListener;
private EventListener<FriendRequestEvent> acceptFriendListener;
private XMPPFriendConnectionImpl connectionA;
private XMPPFriendConnectionImpl connectionB;
public XmppFriendSubscriptionTest(String name) {
super(name);
}
@Override
protected void setUp() throws Exception {
super.setUp();
autoFriendARosterListener = new RosterListenerMock();
autoFriendBRosterListener = new RosterListenerMock();
FriendConnectionConfiguration configFive = new FriendConnectionConfigurationMock(USERNAME_A, PASSWORD,
SERVICE, autoFriendARosterListener);
FriendConnectionConfiguration configSix = new FriendConnectionConfigurationMock(USERNAME_B, PASSWORD,
SERVICE, autoFriendBRosterListener);
connectionA = (XMPPFriendConnectionImpl)factories[0].login(configFive).get();
connectionB = (XMPPFriendConnectionImpl)factories[1].login(configSix).get();
// Allow login, roster, presence, library messages to be sent, received
Thread.sleep(SLEEP);
// Make sure that both friend5 and friend6 have empty rosters before beginning
if (autoFriendARosterListener.getRosterSize() != 0) {
removeAllUsersFromRoster(connectionA);
}
if (autoFriendARosterListener.getRosterSize() != 0) {
removeAllUsersFromRoster(connectionB);
}
}
@Override
protected void tearDown() throws Exception {
removeFriendRequestListener();
connectionA.removeFriend(USERNAME_B);
connectionB.removeFriend(USERNAME_A);
super.tearDown();
}
private EventListener<FriendRequestEvent> createFriendRequestListener(final String userName, final boolean accept) {
return new EventListener<FriendRequestEvent>() {
@Override
public void handleEvent(FriendRequestEvent event) {
LOG.debugf("handling friend request: {0}", event);
FriendRequest friendRequest = event.getData();
if (friendRequest.getFriendUsername().equals(userName)) {
friendRequest.getDecisionHandler().handleDecision(userName, accept);
}
}
};
}
private void setFriendRequestListener(String userName, boolean accept) {
acceptFriendListener = createFriendRequestListener(userName, accept);
injectors[1].getInstance(Key.get(new TypeLiteral<ListenerSupport<FriendRequestEvent>>(){})).
addListener(acceptFriendListener);
}
private void removeFriendRequestListener() {
injectors[1].getInstance(Key.get(new TypeLiteral<ListenerSupport<FriendRequestEvent>>(){})).
removeListener(acceptFriendListener);
acceptFriendListener = null;
}
/**
* Test friend request and confirmation
*/
public void testFriendRequestConfirmed() throws Exception {
setFriendRequestListener(USERNAME_A, true);
// automatedtestfrienda requests automatedtestfriendb
connectionA.addNewFriend(USERNAME_B, USERNAME_B).get();
// sleep to wait for automatedtestfriendb to confirm, friends to exchange roster packets, etc
waitForFriendRequest(true);
// check that both automatedtestfrienda and automatedtestfriendb have each other on their roster
assertEquals(1, autoFriendARosterListener.getRosterSize());
assertEquals(1, autoFriendBRosterListener.getRosterSize());
assertEquals(USERNAME_B, autoFriendARosterListener.getFirstRosterEntry());
assertEquals(USERNAME_A, autoFriendBRosterListener.getFirstRosterEntry());
}
/**
* Test rejection of friend request
*
* 1. automatedtestfrienda requests automatedtestfriendb
* 2. automatedtestfriendb rejects the friend request
* 3. both users logout and log back in
* 4. confirm that no presence packet is ever sent to either user
*/
public void testFriendRequestDenied() throws Exception {
setFriendRequestListener(USERNAME_A, false);
// automatedtestfrienda requests automatedtestfriendb
connectionA.addNewFriend(USERNAME_B, USERNAME_B).get();
// sleep to wait for friend6 to deny
waitForFriendRequest(false);
connectionA.logout().get();
connectionB.logout().get();
autoFriendARosterListener = new RosterListenerMock();
autoFriendBRosterListener = new RosterListenerMock();
FriendConnectionConfiguration configFive = new FriendConnectionConfigurationMock(USERNAME_A, PASSWORD,
SERVICE, autoFriendARosterListener);
FriendConnectionConfiguration configSix = new FriendConnectionConfigurationMock(USERNAME_B, PASSWORD,
SERVICE, autoFriendBRosterListener);
connectionA = (XMPPFriendConnectionImpl) factories[0].login(configFive).get();
connectionB = (XMPPFriendConnectionImpl) factories[1].login(configSix).get();
waitForConnection();
// check that both A and B are not aware of each other (not subscribed)
// Both users are signed in, but since they are not friends (subscribed to each others' statuses)
// neither receives a Presence available packet from the other.
assertTrue(connectionA.isLoggedIn());
assertTrue(connectionB.isLoggedIn());
assertNull(autoFriendARosterListener.getFirstPresence(USERNAME_B));
assertNull(autoFriendBRosterListener.getFirstPresence(USERNAME_A));
}
/**
* Test that friend removal works
*
* 1. automatedtestfrienda requests automatedtestfriendb successfully
* 2. automatedtestfriendb removes automatedtestfrienda as friend
* 3. confirm that upon login, automatedtestfrienda does not receive
* any automatedtestfriendb Presence available packets.
*/
public void testRemoveFriend() throws Exception {
setFriendRequestListener(USERNAME_A, true);
// automatedtestfriend5* requests automatedtestfriendb
connectionA.addNewFriend(USERNAME_B, USERNAME_B).get();
// wait for automatedtestfriends to confirm, friends to exchange roster packets, etc
waitForFriendRequest(true);
// check that both automatedtestfriends have each other on their roster
assertEquals(1, autoFriendARosterListener.getRosterSize());
assertEquals(1, autoFriendBRosterListener.getRosterSize());
assertEquals(USERNAME_B, autoFriendARosterListener.getFirstRosterEntry());
assertEquals(USERNAME_A, autoFriendBRosterListener.getFirstRosterEntry());
// test friend removal
connectionA.removeFriend(USERNAME_B).get();
waitForFriendRequest(false);
connectionA.logout().get();
connectionB.logout().get();
autoFriendARosterListener = new RosterListenerMock();
autoFriendBRosterListener = new RosterListenerMock();
FriendConnectionConfiguration configFive = new FriendConnectionConfigurationMock(USERNAME_A, PASSWORD,
SERVICE, autoFriendARosterListener);
FriendConnectionConfiguration configSix = new FriendConnectionConfigurationMock(USERNAME_B, PASSWORD,
SERVICE, autoFriendBRosterListener);
connectionA = (XMPPFriendConnectionImpl) factories[0].login(configFive).get();
connectionB = (XMPPFriendConnectionImpl) factories[1].login(configSix).get();
waitForConnection();
assertTrue(connectionA.isLoggedIn());
assertTrue(connectionB.isLoggedIn());
assertNull(autoFriendARosterListener.getFirstPresence(USERNAME_B));
assertNull(autoFriendBRosterListener.getFirstPresence(USERNAME_A));
}
private void waitForFriendRequest(boolean confirmed) throws Exception {
final CountDownLatch latch = new CountDownLatch(2);
final RosterEvent.Type expectedType = confirmed ? RosterEvent.Type.FRIENDS_ADDED :
RosterEvent.Type.FRIENDS_DELETED;
class RosterEventListener implements EventListener<RosterEvent> {
RosterEventListener(String expectedFriend) {
this.expectedFriend = expectedFriend;
}
AtomicBoolean friendInRoster = new AtomicBoolean(false);
String expectedFriend;
@Override
public void handleEvent(RosterEvent event) {
if (event.getType() == expectedType) {
assertEquals(1, event.getData().size());
if (expectedFriend.equals(event.getData().iterator().next().getId())) {
if (!friendInRoster.getAndSet(true)) {
latch.countDown();
}
}
}
}
}
injectors[1].getInstance(Key.get(new TypeLiteral<ListenerSupport<RosterEvent>>(){})).
addListener(new RosterEventListener(USERNAME_A));
injectors[0].getInstance(Key.get(new TypeLiteral<ListenerSupport<RosterEvent>>(){})).
addListener(new RosterEventListener(USERNAME_B));
latch.await(MAX_WAIT_FRIEND_REQUEST, TimeUnit.SECONDS);
}
private void waitForConnection() throws Exception {
final CountDownLatch latch = new CountDownLatch(2);
class FriendConnectionEventListener implements EventListener<FriendConnectionEvent> {
@Override
public void handleEvent(FriendConnectionEvent event) {
if (event.getType() == FriendConnectionEvent.Type.CONNECTED) {
latch.countDown();
}
}
}
injectors[1].getInstance(Key.get(new TypeLiteral<ListenerSupport<FriendConnectionEvent>>(){})).
addListener(new FriendConnectionEventListener());
injectors[0].getInstance(Key.get(new TypeLiteral<ListenerSupport<FriendConnectionEvent>>(){})).
addListener(new FriendConnectionEventListener());
assertTrue(latch.await(SLEEP, TimeUnit.SECONDS));
}
private void removeAllUsersFromRoster(FriendConnection conn) throws FriendException, ExecutionException, InterruptedException {
for (Friend friend : conn.getFriends()) {
conn.removeFriend(friend.getId()).get();
}
}
}