/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.slick.protocol.icq;
import java.util.*;
import junit.framework.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.icqconstants.*;
import net.java.sip.communicator.util.*;
/**
* Test icq/aim specific behaviour for OSCAR (AIM/ICQ) implementations of the
* protocol provider service. The class would basically test that registration
* succeeds, that the provider registration state is updated accordingly and
* that the corrsponding registration change events are generated and dispatched.
*
* In the case of a registration failure we try to remain consistent and do
* assertions accordingly.
*
* It is important to note that it is critical to execute tests in this class
* in a particular order because of protocol limitations (we cannot login and
* logoff to most of the existing protocol services as often as we'd like to).
* This is why we have overridden the suite() method which kind of solves the
* problem but adds the limitation of making extending developers manually add
* the tests they write to the suite method.
*
* @author Emil Ivov
*/
public class TestProtocolProviderServiceIcqImpl extends TestCase
{
private static final Logger logger =
Logger.getLogger(TestProtocolProviderServiceIcqImpl.class);
private IcqSlickFixture fixture = new IcqSlickFixture();
/**
* The lock that we wait on until registration is finalized.
*/
private Object registrationLock = new Object();
/**
* An event adapter that would collec registation state change events
*/
public RegistrationEventCollector regEvtCollector
= new RegistrationEventCollector();
/**
* Creates a test encapsulator for the method with the specified name.
* @param name the name of the method this test should run.
*/
public TestProtocolProviderServiceIcqImpl(String name)
{
super(name);
}
/**
* Initializes the fixture.
* @throws Exception if super.setUp() throws one.
*/
@Override
protected void setUp() throws Exception
{
super.setUp();
fixture.setUp();
}
/**
* Tears the fixture down.
* @throws Exception if fixture.tearDown() fails.
*/
@Override
protected void tearDown() throws Exception
{
fixture.tearDown();
super.tearDown();
}
// -------------------------- IMPORTANT ----------------------
/**
* Since we cannot afford to log on and off to the icq service as many
* times as we want we're obliged to do our testing in a predfined order.
* That's why we explicitely difine a suite with the order that suits us ;).
* @return a TestSuite with the tests of this class ordered for execution
* the way we want them to be.
*/
public static Test suite()
{
TestSuite suite = new TestSuite();
if(!IcqSlickFixture.onlineTestingDisabled)
{
suite.addTest(
new TestProtocolProviderServiceIcqImpl("testRegister"));
suite.addTest(
new TestProtocolProviderServiceIcqImpl("testIsRegistered"));
suite.addTest(
new TestProtocolProviderServiceIcqImpl("testGetRegistrationState"));
suite.addTest(
new TestProtocolProviderServiceIcqImpl("testOperationSetTypes"));
}
return suite;
}
/**
* Makes sure that the instance of the ICQ protocol provider that we're
* going to use for testing is properly initialized and signed on ICQ. This
* method only makes sense if called before any other icq testing.
*
* The method also verifies that a registration event is fired upond
* succesful registration and collected by our event collector.
*
* @throws OperationFailedException if provider.register() fails.
*/
public void testRegister()
throws OperationFailedException
{
//add an event collector that will collect all events during the
//registration and allows us to later inspect them and make sure
//they were properly dispatched.
fixture.provider.addRegistrationStateChangeListener(regEvtCollector);
fixture.provider.register(new SecurityAuthorityImpl());
//give it enough time to register. We won't really have to wait all this
//time since the registration event collector would notify us the moment
//we get signed on.
try{
synchronized(registrationLock){
logger.debug("Waiting for registration to complete ...");
registrationLock.wait(40000);
logger.debug("Registration was completed or we lost patience.");
}
}
catch (InterruptedException ex){
logger.debug("Interrupted while waiting for registration", ex);
}
catch(Throwable t)
{
logger.debug("We got thrown out while waiting for registration", t);
}
// Here is registered the listener which will receive the first message
// This message is supposed to be offline message and as one is tested
// in TestOperationSetBasicInstantMessaging.testReceiveOfflineMessages()
OperationSetBasicInstantMessaging opSetBasicIM =
fixture.provider.getOperationSet(OperationSetBasicInstantMessaging.class);
IcqSlickFixture.offlineMsgCollector.register(opSetBasicIM);
//give time for the AIM server to notify everyone of our arrival
//simply waitinf is really not a reliable way of doing things but I
//can't think of anything better
Object lock = new Object();
synchronized(lock)
{
try
{
logger.debug("Giving the aim server time to notify for our arrival!");
lock.wait(5000);
}
catch (Exception ex)
{}
}
//make sure the provider is on-line
assertTrue(
"The tested ICQ implementation on-line status was OFFLINE",
!IcqStatusEnum.OFFLINE.equals(
IcqSlickFixture.testerAgent.getBuddyStatus(fixture.ourUserID))
);
//make sure that the registration process trigerred the corresponding
//events.
assertTrue(
"No events were dispatched during the registration process."
,regEvtCollector.collectedNewStates.size() > 0);
assertTrue(
"No registration event notifying of registration was dispatched. "
+"All events were: " + regEvtCollector.collectedNewStates
,regEvtCollector.collectedNewStates
.contains(RegistrationState.REGISTERED));
fixture.provider.removeRegistrationStateChangeListener(regEvtCollector);
}
/**
* Verifies whether the isRegistered method returns whatever it has to.
*/
public void testIsRegistered()
{
if (!IcqStatusEnum.OFFLINE.equals(
IcqSlickFixture.testerAgent.getBuddyStatus(fixture.ourUserID)))
assertTrue(
"provider.isRegistered() returned false while registered"
,fixture.provider.isRegistered());
else
//in case registration failed - the provider needs to know that.:
assertFalse(
"provider.isRegistered() returned true while unregistered"
,fixture.provider.isRegistered());
}
/**
* Tests that getRegistrationState returns properly after registration
* has completed (or failed)
*/
public void testGetRegistrationState()
{
if (!IcqStatusEnum.OFFLINE.equals(
IcqSlickFixture.testerAgent.getBuddyStatus(fixture.ourUserID)))
assertEquals(
"a provider was not in a REGISTERED state while registered."
,RegistrationState.REGISTERED
,fixture.provider.getRegistrationState());
else
assertFalse(
"a provider had a REGISTERED reg state while unregistered."
,fixture
.provider.getRegistrationState()
.equals(RegistrationState.REGISTERED));
}
/**
* Verifies that all operation sets have the type they are declarded to
* have.
*
* @throws java.lang.Exception if a class indicated in one of the keys
* could not be forName()ed.
*/
public void testOperationSetTypes() throws Exception
{
Map<String, OperationSet> supportedOperationSets =
fixture.provider.getSupportedOperationSets();
// make sure that keys (which are supposed to be class names) correspond
// what the class of the values recorded against them.
for (Map.Entry<String, OperationSet> entry : supportedOperationSets
.entrySet())
{
String setName = entry.getKey();
Object opSet = entry.getValue();
assertTrue(opSet + " was not an instance of " + setName
+ " as declared", Class.forName(setName).isInstance(opSet));
}
}
/**
* A class that would plugin as a registration listener to a protocol
* provider and simply record all events that it sees and notify the
* registrationLock if it sees an event that notifies us of a completed
* registration.
* @author Emil Ivov
*/
public class RegistrationEventCollector implements RegistrationStateChangeListener
{
public List<RegistrationState> collectedNewStates = new LinkedList<RegistrationState>();
/**
* The method would simply register all received events so that they
* could be available for later inspection by the unit tests. In the
* case where a registraiton event notifying us of a completed
* registration is seen, the method would call notifyAll() on the
* registrationLock.
*
* @param evt ProviderStatusChangeEvent the event describing the status
* change.
*/
public void registrationStateChanged(RegistrationStateChangeEvent evt)
{
logger.debug("Received a RegistrationStateChangeEvent: " + evt);
collectedNewStates.add(evt.getNewState());
if(evt.getNewState().equals( RegistrationState.REGISTERED))
{
logger.debug("We're registered and will notify those who wait");
synchronized(registrationLock){
registrationLock.notifyAll();
}
}
}
}
/**
* A very simple straightforward implementation of a security authority
* that would authentify our tested implementation if necessary, by
* retrieving its password through the system properties.
*/
public class SecurityAuthorityImpl implements SecurityAuthority
{
private boolean isUserNameEditable = false;
/**
* Creates an instance of this class that would would authentify our
* tested implementation if necessary, by retrieving its password
* through the system properties.
*/
public SecurityAuthorityImpl()
{}
/**
* Returns a Credentials object containing the password for our
* tested implementation.
* <p>
* @param realm The realm that the credentials are needed for.
* @param defaultValues the values to propose the user by default
* @param reasonCode the reason for which we're obtaining the
* credentials.
* @return The credentials associated with the specified realm or null if
* none could be obtained.
*/
public UserCredentials obtainCredentials(String realm,
UserCredentials defaultValues,
int reasonCode)
{
return obtainCredentials(realm, defaultValues);
}
/**
* Returns a Credentials object containing the password for our
* tested implementation.
* <p>
* @param realm The realm that the credentials are needed for.
* @param defaultValues the values to propose the user by default
* @return The credentials associated with the specified realm or null if
* none could be obtained.
*/
public UserCredentials obtainCredentials(String realm,
UserCredentials defaultValues)
{
String passwd = System.getProperty( IcqProtocolProviderSlick
.TESTED_IMPL_PWD_PROP_NAME, null );
defaultValues.setPassword(passwd.toCharArray());
return defaultValues;
}
/**
* Sets the userNameEditable property, which should indicate if the
* user name could be changed by user or not.
*
* @param isUserNameEditable indicates if the user name could be changed
*/
public void setUserNameEditable(boolean isUserNameEditable)
{
this.isUserNameEditable = isUserNameEditable;
}
/**
* Indicates if the user name is currently editable, i.e. could be changed
* by user or not.
*
* @return <code>true</code> if the user name could be changed,
* <code>false</code> - otherwise.
*/
public boolean isUserNameEditable()
{
return isUserNameEditable;
}
}
}