package org.societies.integration.test.bit.userfeedback; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.societies.api.comm.xmpp.pubsub.PubsubClient; import org.societies.api.comm.xmpp.pubsub.Subscriber; import org.societies.api.identity.IIdentity; import org.societies.api.identity.IIdentityManager; import org.societies.api.internal.useragent.feedback.IUserFeedback; import org.societies.api.internal.useragent.model.ExpProposalContent; import org.societies.api.internal.useragent.model.ExpProposalType; import org.societies.api.osgi.event.EventTypes; import org.societies.api.schema.useragent.feedback.ExpFeedbackResultBean; import org.societies.api.schema.useragent.feedback.FeedbackStage; import org.societies.api.schema.useragent.feedback.ImpFeedbackResultBean; import org.societies.api.schema.useragent.feedback.UserFeedbackBean; import org.societies.useragent.api.feedback.IUserFeedbackHistoryRepository; import org.societies.useragent.api.model.UserFeedbackEventTopics; import java.sql.SQLException; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; public class Tester { private static final Logger log = LoggerFactory.getLogger(Tester.class); private class PubSubListener implements Subscriber { private static final long XMPP_TIMEOUT = 2000; public final Queue<PubSubEvent> eventQueue = new LinkedList<PubSubEvent>(); //pubsubClient event schemas private final List<String> EVENT_SCHEMA_CLASSES = Collections.unmodifiableList(Arrays.asList( "org.societies.api.schema.useragent.feedback.UserFeedbackBean", "org.societies.api.schema.useragent.feedback.ExpFeedbackResultBean", "org.societies.api.schema.useragent.feedback.ImpFeedbackResultBean", "org.societies.api.internal.schema.useragent.feedback.UserFeedbackPrivacyNegotiationEvent", "org.societies.api.internal.schema.useragent.feedback.UserFeedbackAccessControlEvent")); private final List<String> EVENT_TYPES = Collections.unmodifiableList(Arrays.asList( EventTypes.UF_PRIVACY_NEGOTIATION, EventTypes.UF_PRIVACY_NEGOTIATION_RESPONSE, EventTypes.UF_PRIVACY_NEGOTIATION_REMOVE_POPUP, EventTypes.UF_PRIVACY_ACCESS_CONTROL, EventTypes.UF_PRIVACY_ACCESS_CONTROL_RESPONSE, EventTypes.UF_PRIVACY_ACCESS_CONTROL_REMOVE_POPUP, UserFeedbackEventTopics.EXPLICIT_RESPONSE, UserFeedbackEventTopics.IMPLICIT_RESPONSE, UserFeedbackEventTopics.REQUEST)); public void registerForEvents() { if (log.isDebugEnabled()) log.debug("registerForEvents()"); if (pubsubClient == null) { log.error("PubSubClient was null, cannot register for events"); return; } try { //register schema classes pubsubClient.addSimpleClasses(EVENT_SCHEMA_CLASSES); } catch (Exception e) { log.error("Error subscribing to pubsubClient schema classes", e); } for (String eventType : EVENT_TYPES) { try { pubsubClient.subscriberSubscribe(userID, eventType, this); if (log.isDebugEnabled()) log.debug("Subscribed to " + eventType + " events"); } catch (Exception e) { log.error("Error subscribing to pubsubClient notifications (id=" + userID + " event=" + eventType, e); } } } public void unregisterForEvents() { if (log.isDebugEnabled()) log.debug("unregisterForEvents()"); if (pubsubClient == null) { log.error("PubSubClient was null, cannot unregister for events"); return; } for (String eventType : EVENT_TYPES) { try { pubsubClient.subscriberUnsubscribe(userID, eventType, this); if (log.isDebugEnabled()) log.debug("Unsubscribed from " + eventType + " events"); } catch (Exception e) { log.error("Error unsubscribing from pubsubClient notifications (id=" + userID + " event=" + eventType, e); } } } @Override public void pubsubEvent(IIdentity pubsubService, String node, String itemId, Object item) { if (log.isDebugEnabled()) { String msg = "pubsubEvent(): ID=[%s] node=[%s] item=[%s]"; log.debug(String.format(msg, itemId, node, item)); } PubSubEvent event = new PubSubEvent(node, itemId, item); synchronized (eventQueue) { eventQueue.add(event); } } public void sendExplicitResponse(String requestId, List<String> results) { if (log.isTraceEnabled()) log.trace("sendExplicitResponse()"); final ExpFeedbackResultBean resultBean = new ExpFeedbackResultBean(); resultBean.setRequestId(requestId); resultBean.setFeedback(results); try { pubsubClient.publisherPublish(userID, UserFeedbackEventTopics.EXPLICIT_RESPONSE, requestId, resultBean); if (log.isDebugEnabled()) log.debug("Sent " + UserFeedbackEventTopics.EXPLICIT_RESPONSE + " with ID " + requestId); } catch (Exception e) { log.error("Error publishing notification of completed explicit UF request", e); } } public void sendImplicitResponse(ImpFeedbackResultBean responseBean) { if (log.isTraceEnabled()) log.trace("sendImplicitResponse()"); try { pubsubClient.publisherPublish(userID, UserFeedbackEventTopics.IMPLICIT_RESPONSE, responseBean.getRequestId(), responseBean); if (log.isDebugEnabled()) log.debug("Sent " + UserFeedbackEventTopics.IMPLICIT_RESPONSE + " with ID " + responseBean.getRequestId()); } catch (Exception e) { log.error("Error publishing notification of completed implicit UF request", e); } } public void clear() { synchronized (PubSubListener.this) { eventQueue.clear(); } } private PubSubEvent waitForPubSubEvent(String requestId, String requestType) throws InterruptedException { PubSubEvent event = null; Date endDate = new Date(new Date().getTime() + XMPP_TIMEOUT); while (endDate.after(new Date())) { synchronized (eventQueue) { for (PubSubEvent queuedEvent : eventQueue) { if (!requestId.equals(queuedEvent.getItemId())) { log.trace("Queued Event ID [" + requestId + "] does not match target id [" + queuedEvent.getItemId() + "], skipping"); continue; } if (!requestType.equals(queuedEvent.getNode())) { String msg = "Found event with ID [%s] but with node [%s], expected [%s], skipping"; log.trace(String.format(msg, requestId, queuedEvent.getNode(), requestType)); continue; } event = queuedEvent; break; } log.debug("Event ID [" + requestId + "] not found in queue... retrying"); } // try again, giving time for the new event to arrive Thread.sleep(XMPP_TIMEOUT / 5L); } return event; } public void assertNotificationReceived(String requestId, int type, ExpProposalContent content) throws InterruptedException { List<String> expectedOptions = new ArrayList<String>(); Collections.addAll(expectedOptions, content.getOptions()); PubSubEvent event = waitForPubSubEvent(requestId, UserFeedbackEventTopics.REQUEST); Assert.assertNotNull("Event ID [" + requestId + "] with type [" + UserFeedbackEventTopics.REQUEST + "] not found after " + XMPP_TIMEOUT + "ms", event); UserFeedbackBean bean = (UserFeedbackBean) event.getItem(); Assert.assertEquals("Bean ID [" + requestId + "] had wrong type", type, bean.getType()); Tester.compareLists(expectedOptions, bean.getOptions()); } public void assertExpResponseReceived(String requestId, int type, List<String> expectedResults) throws InterruptedException { PubSubEvent event = waitForPubSubEvent(requestId, UserFeedbackEventTopics.EXPLICIT_RESPONSE); if (event == null) Assert.fail("Event ID [" + requestId + "] not found after " + XMPP_TIMEOUT + "ms"); ExpFeedbackResultBean bean = (ExpFeedbackResultBean) event.getItem(); Tester.compareLists(expectedResults, bean.getFeedback()); } public void assertCompletedReceived(String requestId) throws InterruptedException { waitForPubSubEvent(requestId, UserFeedbackEventTopics.COMPLETE); } } private class PubSubEvent { private final String node; private final String itemId; private final Object item; public PubSubEvent(String node, String itemId, Object item) { this.node = node; this.itemId = itemId; this.item = item; } private String getNode() { return node; } private String getItemId() { return itemId; } private Object getItem() { return item; } } private PubsubClient pubsubClient; private IIdentityManager idMgr; private IIdentity userID; private MySQLHelper mySQLHelper; private IUserFeedback userFeedback; private PubSubListener pubSubListener; private IUserFeedbackHistoryRepository userFeedbackHistoryRepository; public Tester() { log.debug("Tester constructor"); } @Before public void setUp() { log.debug("Setting up Tester"); this.pubsubClient = TestUserFeedback.getPubsub(); this.idMgr = TestUserFeedback.getIdMgr(); this.userID = this.idMgr.getThisNetworkNode(); this.mySQLHelper = TestUserFeedback.getMySQLHelper(); this.userFeedback = TestUserFeedback.getUserFeedback(); this.userFeedbackHistoryRepository = TestUserFeedback.getUserFeedbackHistoryRepository(); pubSubListener = new PubSubListener(); pubSubListener.registerForEvents(); log.debug("Finished setting up tester"); } @After public void tearDown() { pubSubListener.unregisterForEvents(); } @Test public void fullTestScript() throws SQLException, InterruptedException, ExecutionException { // NB: The following manual script was written to test full integration of UF, Android and the Webapp - only the // UF component will be tested in this automated test class //1. Empty database, clean startup // 1. No notifications displayed //2. Send 4x UF Explicit notification - AckNack, SelectOne, 2x SelectMany // 1. Notification stored in DB // 2. EXPLICIT_REQUEST PubSub event sent from server // 3. 2x notifications displayed on Android, T65 //3. Accept AckNack notification via T65 // 1. EXPLICIT_RESPONSE PubSub event sent from client // 2. Database is updated - negotiation status and results // 3. COMPLETED PubSub event is sent from server // 4. Correct notification disappears on Android, T65 //4. Accept SelectOne notification via Android // 1. EXPLICIT_RESPONSE PubSub event sent from client // 2. Database is updated - negotiation status and results // 3. COMPLETED PubSub event is sent from server // 4. Correct notification disappears on Android, T65 //5. Restart Android app // 1. 2x SelectMany notification should appear on Android (others may appear, but be marked 'completed') //6. Restart platform, restart Android app // 1. 2x SelectMany notification should appear on Android, T65 (others may appear, but be marked 'completed') //7. Accept 1x SelectMany notification via T65 (note ID) // 1. EXPLICIT_RESPONSE PubSub event sent from client // 2. Database is updated - negotiation status and results // 3. COMPLETED PubSub event is sent from server // 4. Correct notification disappears on Android, T65 //8. Accept 1x SelectMany notification via Android // 1. EXPLICIT_RESPONSE PubSub event sent from client // 2. Database is updated - negotiation status and results // 3. COMPLETED PubSub event is sent from server // 4. Correct notification disappears on Android, T65 //1. Empty database, clean startup mySQLHelper.clearTable("UserFeedbackBean_options"); mySQLHelper.clearTable("UserFeedbackBean"); mySQLHelper.clearTable("UFAccessControlEvent"); mySQLHelper.clearTable("UFPrivacyNegotiationEvent"); mySQLHelper.clearTable("UFResponsePolicy"); mySQLHelper.clearTable("UFResponseItem"); mySQLHelper.clearTable("UFRequestItem"); mySQLHelper.clearTable("UFAction"); mySQLHelper.clearTable("UFCondition"); mySQLHelper.clearTable("UFResource"); mySQLHelper.clearTable("UFRequestorBean"); log.info("Clearing UserFeedback"); userFeedback.clear(); log.info("Clearing PubSubListener"); pubSubListener.clear(); //2. Send 4x UF Explicit notification - AckNack, SelectOne, 2x SelectMany final ExpProposalContent acknackContent = new ExpProposalContent("AckNack test", new String[]{"Yes", "No"}); final Future<List<String>> acknackFB = userFeedback.getExplicitFBAsync(ExpProposalType.ACKNACK, acknackContent); // 1. Notification stored in DB final String acknackRequestId = mySQLHelper.assertNotificationStored(ExpProposalType.ACKNACK, acknackContent); // 2. EXPLICIT_REQUEST PubSub event sent from server pubSubListener.assertNotificationReceived(acknackRequestId, ExpProposalType.ACKNACK, acknackContent); final ExpProposalContent selectOneContent = new ExpProposalContent("SelectOne test", new String[]{"Kingdom", "Phylum", "Class", "Order", "Family", "Genus", "Species"}); final Future<List<String>> selectOneFB = userFeedback.getExplicitFBAsync(ExpProposalType.CHECKBOXLIST, selectOneContent); // 1. Notification stored in DB final String selectOneRequestId = mySQLHelper.assertNotificationStored(ExpProposalType.CHECKBOXLIST, selectOneContent); // 2. EXPLICIT_REQUEST PubSub event sent from server pubSubListener.assertNotificationReceived(selectOneRequestId, ExpProposalType.CHECKBOXLIST, selectOneContent); final ExpProposalContent selectManyContent1 = new ExpProposalContent("SelectMany test 1", new String[]{"red", "orange", "yellow", "green", "blue", "indigo", "violet"}); final Future<List<String>> selectManyFB1 = userFeedback.getExplicitFBAsync(ExpProposalType.RADIOLIST, selectManyContent1); // 1. Notification stored in DB final String selectManyRequestId1 = mySQLHelper.assertNotificationStored(ExpProposalType.RADIOLIST, selectManyContent1); // 2. EXPLICIT_REQUEST PubSub event sent from server pubSubListener.assertNotificationReceived(selectManyRequestId1, ExpProposalType.RADIOLIST, selectManyContent1); final ExpProposalContent selectManyContent2 = new ExpProposalContent("SelectMany test 2", new String[]{"red2", "orange2", "yellow2", "green2", "blue2", "indigo2", "violet2"}); final Future<List<String>> selectManyFB2 = userFeedback.getExplicitFBAsync(ExpProposalType.RADIOLIST, selectManyContent2); // 1. Notification stored in DB final String selectManyRequestId2 = mySQLHelper.assertNotificationStored(ExpProposalType.RADIOLIST, selectManyContent2); // 2. EXPLICIT_REQUEST PubSub event sent from server pubSubListener.assertNotificationReceived(selectManyRequestId2, ExpProposalType.RADIOLIST, selectManyContent2); //3. Accept AckNack notification Assert.assertFalse(acknackFB.isDone()); List<String> expectedAcknackResults = new ArrayList<String>(); expectedAcknackResults.add("Yes"); pubSubListener.sendExplicitResponse(acknackRequestId, expectedAcknackResults); // 1. EXPLICIT_RESPONSE PubSub event sent from client pubSubListener.assertExpResponseReceived(acknackRequestId, ExpProposalType.ACKNACK, expectedAcknackResults); // 2. Database is updated - negotiation status and results final UserFeedbackBean acknackFeedbackBean = userFeedbackHistoryRepository.getByRequestId(acknackRequestId); Assert.assertEquals(FeedbackStage.COMPLETED, acknackFeedbackBean.getStage()); // compareLists(acknackResults, acknackFeedbackBean.getOptions()); // 3. Future is updated Assert.assertTrue(acknackFB.isDone()); final List<String> acknackResult = acknackFB.get(); Tester.compareLists(expectedAcknackResults, acknackResult); // 4. COMPLETED PubSub event is sent from server pubSubListener.assertCompletedReceived(acknackRequestId); //4. Accept SelectOne notification Assert.assertFalse(selectOneFB.isDone()); final List<String> expectedSelectOneResults = new ArrayList<String>(); expectedSelectOneResults.add("Phylum"); pubSubListener.sendExplicitResponse(selectOneRequestId, expectedSelectOneResults); // 1. EXPLICIT_RESPONSE PubSub event sent from client pubSubListener.assertExpResponseReceived(selectOneRequestId, ExpProposalType.RADIOLIST, expectedSelectOneResults); // 2. Database is updated - negotiation status and results final UserFeedbackBean selectOneFeedbackBean = userFeedbackHistoryRepository.getByRequestId(selectOneRequestId); Assert.assertEquals(FeedbackStage.COMPLETED, selectOneFeedbackBean.getStage()); // compareLists(selectOneResults, selectOneFeedbackBean.getOptions()); // 3. Future is updated Assert.assertTrue(selectOneFB.isDone()); final List<String> selectOneResult = selectOneFB.get(); Tester.compareLists(expectedSelectOneResults, selectOneResult); // 4. COMPLETED PubSub event is sent from server pubSubListener.assertCompletedReceived(selectOneRequestId); } private static <T> void compareLists(T[] expected, T[] actual) { List<T> leftList = new ArrayList<T>(); Collections.addAll(leftList, expected); List<T> rightList = new ArrayList<T>(); Collections.addAll(rightList, actual); compareLists(leftList, rightList); } private static <T> void compareLists(List<T> expected, T[] actual) { List<T> rightList = new ArrayList<T>(); Collections.addAll(rightList, actual); compareLists(expected, rightList); } private static <T> void compareLists(T[] expected, List<T> actual) { List<T> leftList = new ArrayList<T>(); Collections.addAll(leftList, expected); compareLists(leftList, actual); } public static <T> void compareLists(List<T> expected, List<T> actual) { List<T> expectedClone = new ArrayList<T>(expected); for (T actualItem : actual) { if (!expected.contains(actualItem)) Assert.fail("Item [" + actualItem + "] found in actual list, but not expected"); expectedClone.remove(actualItem); } if (expectedClone.size() > 0) Assert.fail("Item [" + expectedClone.get(0) + "] was expected, but not in actual list"); } }