Java tutorial
/** * Copyright 2013 Michael Fitzmaurice * * 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 com.michaelfitzmaurice.clocktwerk; import static com.michaelfitzmaurice.clocktwerk.ResponseListBuilder.aResponseList; import static com.michaelfitzmaurice.clocktwerk.TweetResponder.LAST_MENTION_HANDLED_ID_KEY; import static org.easymock.EasyMock.anyObject; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.createStrictMock; import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.reportMatcher; import static org.easymock.EasyMock.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Random; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.easymock.IArgumentMatcher; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.prevayler.Prevayler; import org.prevayler.PrevaylerFactory; import org.prevayler.Query; import twitter4j.GeoLocation; import twitter4j.HashtagEntity; import twitter4j.MediaEntity; import twitter4j.Paging; import twitter4j.Place; import twitter4j.RateLimitStatus; import twitter4j.ResponseList; import twitter4j.Status; import twitter4j.StatusUpdate; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.URLEntity; import twitter4j.User; import twitter4j.UserMentionEntity; import com.michaelfitzmaurice.clocktwerk.prevayler.SetLastMentionHandledIdTransaction; public class TweetResponderTest { private File persistenceDir; private Prevayler prevayler; private Long lastSeenMentionId; private Twitter mockTwitterClient; private TweetDatabase mockTweetDb; private Paging paging; private User twitterUser; private String replyText = "We have received your query - it's in a MAHOOSIVE queue..."; private User mentioningUser; @Before public void setup() throws Exception { persistenceDir = new File(System.getProperty("java.io.tmpdir"), "test-prevayler"); FileUtils.forceMkdir(persistenceDir); FileUtils.forceDeleteOnExit(persistenceDir); lastSeenMentionId = new Long(new Random().nextInt(Integer.MAX_VALUE)); HashMap<String, Long> prevayledMap = new HashMap<String, Long>(); prevayledMap.put(LAST_MENTION_HANDLED_ID_KEY, lastSeenMentionId); prevayler = PrevaylerFactory.createPrevayler(prevayledMap, persistenceDir.getAbsolutePath()); paging = new Paging(lastSeenMentionId); mockTweetDb = createNiceMock(TweetDatabase.class); mockTweetDb.getNextTweet(); expectLastCall().andReturn(replyText).anyTimes(); replay(mockTweetDb); String twitterUserHandle = "Receiver"; twitterUser = userFrom("Mr. " + twitterUserHandle, twitterUserHandle); String mentionerHandle = "MentionU"; mentioningUser = userFrom("Miss " + mentionerHandle, mentionerHandle); mockTwitterClient = createStrictMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUserHandle); replay(mockTwitterClient); } @After public void tearDown() throws IOException { if (persistenceDir.exists()) { FileUtils.deleteDirectory(persistenceDir); } } @Test(expected = TweetException.class) public void wrapsExceptionReadingLastMentionHandledRecord() throws Exception { Exception exception = new Exception("POW!"); Prevayler mockPrevayler = createStrictMock(Prevayler.class); mockPrevayler.execute((Query) anyObject()); expectLastCall().andThrow(exception); replay(mockPrevayler); TweetResponder responder = new TweetResponder(mockPrevayler, null, mockTwitterClient); try { responder.getNewMentions(); } catch (TweetException e) { assertSame(exception, e.getCause()); throw e; } } @Test(expected = TweetException.class) public void wrapsExceptionFromTwitterClientGettingMentions() throws Exception { TwitterException twitterException = new TwitterException("POW!"); mockTwitterClient = createStrictMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andThrow(twitterException); replay(mockTwitterClient); try { new TweetResponder(prevayler, null, mockTwitterClient); } catch (TweetException e) { assertSame(twitterException, e.getCause()); throw e; } } @Test public void onlyFetchesUnseenMentions() throws Exception { mockTwitterClient = createStrictMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUser.getScreenName()); mockTwitterClient.getMentionsTimeline(paging); expectLastCall().andReturn(aResponseList().build()); replay(mockTwitterClient); TweetResponder responder = new TweetResponder(prevayler, null, mockTwitterClient); responder.getNewMentions(); verify(mockTwitterClient); } @Test public void fetchesMentionsReturnedByTwitter() throws Exception { ResponseList<Status> mentions = aResponseList().withStatus(statusFrom(randomUser(), 4l, randomTweetBody())) .withStatus(statusFrom(randomUser(), 3l, randomTweetBody())) .withStatus(statusFrom(randomUser(), 2l, randomTweetBody())) .withStatus(statusFrom(randomUser(), 1l, randomTweetBody())) .withStatus(statusFrom(randomUser(), 0l, randomTweetBody())).build(); mockTwitterClient = createStrictMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUser.getScreenName()); mockTwitterClient.getMentionsTimeline(paging); expectLastCall().andReturn(mentions); replay(mockTwitterClient); TweetResponder responder = new TweetResponder(prevayler, null, mockTwitterClient); Collection<Status> returnedMentions = responder.getNewMentions(); assertEquals(mentions, returnedMentions); } @Test(expected = TweetException.class) public void wrapsExceptionGettingNewMentions() throws Exception { TwitterException twitterException = new TwitterException("POW!"); mockTwitterClient = createStrictMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(mentioningUser.getScreenName()); mockTwitterClient.getMentionsTimeline(paging); expectLastCall().andThrow(twitterException); replay(mockTwitterClient); TweetResponder responder = new TweetResponder(prevayler, null, mockTwitterClient); try { responder.getNewMentions(); } catch (TweetException e) { assertSame(twitterException, e.getCause()); throw e; } finally { verify(mockTwitterClient); } } @Test public void queriesTweetDatabaseForReplies() throws Exception { mockTwitterClient = createNiceMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUser.getScreenName()); mockTwitterClient.updateStatus((StatusUpdate) anyObject()); expectLastCall().andReturn(null); replay(mockTwitterClient); TweetResponder responder = new TweetResponder(prevayler, mockTweetDb, mockTwitterClient); responder.respondToMentions( aResponseList().withStatus(statusFrom(randomUser(), 1l, randomTweetBody())).build()); verify(mockTweetDb); } @Test public void postsUpdatesInReplyToMentions() throws Exception { long idOfMention = lastSeenMentionId.longValue() + 1; mockTwitterClient = createStrictMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUser.getScreenName()); mockTwitterClient.updateStatus(replyUpdateFor(new User[] {}, idOfMention, replyText)); expectLastCall().andReturn(null); replay(mockTwitterClient); TweetResponder responder = new TweetResponder(prevayler, mockTweetDb, mockTwitterClient); Status mention = statusFrom(mentioningUser, idOfMention, randomTweetBody()); responder.respondToMentions(aResponseList().withStatus(mention).build()); verify(mockTwitterClient); } @Test public void copiesAllMentionedUsersIntoReply() throws Exception { long idOfMention = lastSeenMentionId.longValue() + 1; User[] mentioned = mentionedUsers(twitterUser, 3); mockTwitterClient = createStrictMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUser.getScreenName()); mockTwitterClient.updateStatus(replyUpdateFor(mentioned, idOfMention, replyText)); expectLastCall().andReturn(null); replay(mockTwitterClient); TweetResponder responder = new TweetResponder(prevayler, mockTweetDb, mockTwitterClient); Status mention = statusFrom(mentioningUser, idOfMention, tweetBodyWithMentions(mentioned)); responder.respondToMentions(aResponseList().withStatus(mention).build()); verify(mockTwitterClient); } @Test public void swallowsExceptionsReplyingToMentions() throws Exception { TwitterException exception = new TwitterException("BOOM!"); mockTwitterClient = createStrictMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUser.getScreenName()); mockTwitterClient.updateStatus((StatusUpdate) anyObject()); expectLastCall().andThrow(exception); replay(mockTwitterClient); TweetResponder responder = new TweetResponder(prevayler, mockTweetDb, mockTwitterClient); Status mention = statusFrom(randomUser(), lastSeenMentionId.longValue() + 1, randomTweetBody()); responder.respondToMentions(aResponseList().withStatus(mention).build()); verify(mockTwitterClient); } @Test public void doesNotUpdateLastMentionHandledRecordWhenNoNewMentionsAreFound() throws Exception { mockTwitterClient = createNiceMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUser.getScreenName()); replay(mockTwitterClient); Prevayler mockPrevayler = createStrictMock(Prevayler.class); replay(mockPrevayler); TweetResponder responder = new TweetResponder(mockPrevayler, mockTweetDb, mockTwitterClient); responder.respondToMentions(aResponseList().build()); verify(mockPrevayler); } @Test public void updatesLastMentionHandledRecordAfterReplyingToMentions() throws Exception { User userWhoMentioned = randomUser(); String bodyOfMentioningTweet = randomTweetBody(); long idOfMention = lastSeenMentionId.longValue() + 1; Status mention = statusFrom(userWhoMentioned, idOfMention, bodyOfMentioningTweet); ResponseList<Status> mentions = aResponseList().withStatus(mention).build(); mockTwitterClient = createNiceMock(Twitter.class); mockTwitterClient.getScreenName(); expectLastCall().andReturn(twitterUser.getScreenName()); mockTwitterClient.updateStatus((StatusUpdate) anyObject()); expectLastCall().andReturn(null); replay(mockTwitterClient); SetLastMentionHandledIdTransaction transaction = new SetLastMentionHandledIdTransaction(idOfMention); Prevayler mockPrevayler = createStrictMock(Prevayler.class); mockPrevayler.execute(eqSetLastMentionHandledIdTransaction(transaction)); replay(mockPrevayler); TweetResponder responder = new TweetResponder(mockPrevayler, mockTweetDb, mockTwitterClient); responder.respondToMentions(mentions); verify(mockPrevayler); } /////////////////////////////////////////////////////// // helper methods /////////////////////////////////////////////////////// private StatusUpdate replyUpdateFor(User[] usersMentioned, long idOfMention, String replyBody) { String fullReplyTweet = "@" + mentioningUser.getScreenName() + " "; for (User user : usersMentioned) { String userScreenName = user.getScreenName(); if (userScreenName.equalsIgnoreCase(twitterUser.getScreenName()) == false) { fullReplyTweet += "@" + userScreenName + " "; } } fullReplyTweet += replyBody; StatusUpdate replyStatus = new StatusUpdate(fullReplyTweet); replyStatus.setInReplyToStatusId(idOfMention); return replyStatus; } private String randomString() { return UUID.randomUUID().toString(); } private User randomUser() { return userFrom(randomString(), randomString().substring(0, 8)); } private User[] randomUsers(int numberOfUsers) { User[] users = new User[numberOfUsers]; for (int i = 0; i < numberOfUsers; i++) { String twitterHandle = "RandomU" + i; users[i] = userFrom("Mr. " + twitterHandle, twitterHandle); } return users; } private User[] mentionedUsers(User addressee, int numberOfRandomMentionedUsers) { User[] randoms = randomUsers(numberOfRandomMentionedUsers); User[] usersWhoWereMentioned = new User[numberOfRandomMentionedUsers + 1]; usersWhoWereMentioned[0] = addressee; System.arraycopy(randoms, 0, usersWhoWereMentioned, 1, randoms.length); return usersWhoWereMentioned; } private String randomTweetBody() { return randomString() + ", " + randomString() + " " + randomString() + " " + randomString(); } private String tweetBodyWithMentions(User[] usersMentioned) { String tweetBody = ""; for (User user : usersMentioned) { tweetBody += "@" + user.getScreenName() + " "; } return tweetBody; } @SuppressWarnings("serial") private User userFrom(final String name, final String screenName) { return new User() { @Override public RateLimitStatus getRateLimitStatus() { // TODO Auto-generated method stub return null; } @Override public int getAccessLevel() { // TODO Auto-generated method stub return 0; } @Override public int compareTo(User arg0) { // TODO Auto-generated method stub return 0; } @Override public boolean isVerified() { // TODO Auto-generated method stub return false; } @Override public boolean isTranslator() { // TODO Auto-generated method stub return false; } @Override public boolean isShowAllInlineMedia() { // TODO Auto-generated method stub return false; } @Override public boolean isProtected() { // TODO Auto-generated method stub return false; } @Override public boolean isProfileUseBackgroundImage() { // TODO Auto-generated method stub return false; } @Override public boolean isProfileBackgroundTiled() { // TODO Auto-generated method stub return false; } @Override public boolean isGeoEnabled() { // TODO Auto-generated method stub return false; } @Override public boolean isFollowRequestSent() { // TODO Auto-generated method stub return false; } @Override public boolean isContributorsEnabled() { // TODO Auto-generated method stub return false; } @Override public int getUtcOffset() { // TODO Auto-generated method stub return 0; } @Override public URLEntity getURLEntity() { // TODO Auto-generated method stub return null; } @Override public String getURL() { // TODO Auto-generated method stub return null; } @Override public String getTimeZone() { // TODO Auto-generated method stub return null; } @Override public int getStatusesCount() { // TODO Auto-generated method stub return 0; } @Override public Status getStatus() { // TODO Auto-generated method stub return null; } @Override public String getScreenName() { return screenName; } @Override public String getProfileTextColor() { // TODO Auto-generated method stub return null; } @Override public String getProfileSidebarFillColor() { // TODO Auto-generated method stub return null; } @Override public String getProfileSidebarBorderColor() { // TODO Auto-generated method stub return null; } @Override public String getProfileLinkColor() { // TODO Auto-generated method stub return null; } @Override public URL getProfileImageUrlHttps() { // TODO Auto-generated method stub return null; } @Override public String getProfileImageURLHttps() { // TODO Auto-generated method stub return null; } @Override public String getProfileImageURL() { // TODO Auto-generated method stub return null; } @Override public String getProfileBannerURL() { // TODO Auto-generated method stub return null; } @Override public String getProfileBannerRetinaURL() { // TODO Auto-generated method stub return null; } @Override public String getProfileBannerMobileURL() { // TODO Auto-generated method stub return null; } @Override public String getProfileBannerMobileRetinaURL() { // TODO Auto-generated method stub return null; } @Override public String getProfileBannerIPadURL() { // TODO Auto-generated method stub return null; } @Override public String getProfileBannerIPadRetinaURL() { // TODO Auto-generated method stub return null; } @Override public String getProfileBackgroundImageUrlHttps() { // TODO Auto-generated method stub return null; } @Override public String getProfileBackgroundImageUrl() { // TODO Auto-generated method stub return null; } @Override public String getProfileBackgroundImageURL() { // TODO Auto-generated method stub return null; } @Override public String getProfileBackgroundColor() { // TODO Auto-generated method stub return null; } @Override public String getOriginalProfileImageURLHttps() { // TODO Auto-generated method stub return null; } @Override public String getOriginalProfileImageURL() { // TODO Auto-generated method stub return null; } @Override public String getName() { return name; } @Override public String getMiniProfileImageURLHttps() { // TODO Auto-generated method stub return null; } @Override public String getMiniProfileImageURL() { // TODO Auto-generated method stub return null; } @Override public String getLocation() { // TODO Auto-generated method stub return null; } @Override public int getListedCount() { // TODO Auto-generated method stub return 0; } @Override public String getLang() { // TODO Auto-generated method stub return null; } @Override public long getId() { // TODO Auto-generated method stub return 0; } @Override public int getFriendsCount() { // TODO Auto-generated method stub return 0; } @Override public int getFollowersCount() { // TODO Auto-generated method stub return 0; } @Override public int getFavouritesCount() { // TODO Auto-generated method stub return 0; } @Override public URLEntity[] getDescriptionURLEntities() { // TODO Auto-generated method stub return null; } @Override public String getDescription() { // TODO Auto-generated method stub return null; } @Override public Date getCreatedAt() { // TODO Auto-generated method stub return null; } @Override public String getBiggerProfileImageURLHttps() { // TODO Auto-generated method stub return null; } @Override public String getBiggerProfileImageURL() { // TODO Auto-generated method stub return null; } }; } @SuppressWarnings("serial") private Status statusFrom(final User user, final long statusId, final String statusBody) { final Date createdAt = new Date(); return new Status() { @Override public UserMentionEntity[] getUserMentionEntities() { List<UserMentionEntity> userMentionEntities = new ArrayList<UserMentionEntity>(); // userMentionEntities.add( userMentionEntity( user.getScreenName() ) ); Pattern p = Pattern.compile("@\\w{8}"); Matcher m = p.matcher(statusBody); while (m.find()) { final String tweetHandle = m.group().substring(1); userMentionEntities.add(userMentionEntity(tweetHandle)); } UserMentionEntity[] mentionEntities = new UserMentionEntity[userMentionEntities.size()]; return userMentionEntities.toArray(mentionEntities); } @Override public URLEntity[] getURLEntities() { // TODO Auto-generated method stub return null; } @Override public MediaEntity[] getMediaEntities() { // TODO Auto-generated method stub return null; } @Override public HashtagEntity[] getHashtagEntities() { // TODO Auto-generated method stub return null; } @Override public RateLimitStatus getRateLimitStatus() { // TODO Auto-generated method stub return null; } @Override public int getAccessLevel() { // TODO Auto-generated method stub return 0; } @Override public int compareTo(Status o) { // TODO Auto-generated method stub return 0; } @Override public boolean isTruncated() { // TODO Auto-generated method stub return false; } @Override public boolean isRetweetedByMe() { // TODO Auto-generated method stub return false; } @Override public boolean isRetweet() { // TODO Auto-generated method stub return false; } @Override public boolean isPossiblySensitive() { // TODO Auto-generated method stub return false; } @Override public boolean isFavorited() { // TODO Auto-generated method stub return false; } @Override public User getUser() { return user; } @Override public String getText() { return statusBody; } @Override public String getSource() { // TODO Auto-generated method stub return null; } @Override public Status getRetweetedStatus() { // TODO Auto-generated method stub return null; } @Override public long getRetweetCount() { // TODO Auto-generated method stub return 0; } @Override public Place getPlace() { // TODO Auto-generated method stub return null; } @Override public long getInReplyToUserId() { // TODO Auto-generated method stub return 0; } @Override public long getInReplyToStatusId() { // TODO Auto-generated method stub return 0; } @Override public String getInReplyToScreenName() { // TODO Auto-generated method stub return null; } @Override public long getId() { return statusId; } @Override public GeoLocation getGeoLocation() { // TODO Auto-generated method stub return null; } @Override public long getCurrentUserRetweetId() { // TODO Auto-generated method stub return 0; } @Override public Date getCreatedAt() { return createdAt; } @Override public long[] getContributors() { // TODO Auto-generated method stub return null; } }; } @SuppressWarnings("serial") private UserMentionEntity userMentionEntity(final String tweetHandle) { return new UserMentionEntity() { @Override public int getStart() { // TODO Auto-generated method stub return 0; } @Override public String getScreenName() { return tweetHandle; } @Override public String getName() { // TODO Auto-generated method stub return null; } @Override public long getId() { // TODO Auto-generated method stub return 0; } @Override public int getEnd() { // TODO Auto-generated method stub return 0; } }; } // custom Matcher for command object passed to Prevayler public static SetLastMentionHandledIdTransaction eqSetLastMentionHandledIdTransaction( SetLastMentionHandledIdTransaction expected) { reportMatcher(new SetLastMentionHandledIdTransactionEquals(expected)); return expected; } static class SetLastMentionHandledIdTransactionEquals implements IArgumentMatcher { private SetLastMentionHandledIdTransaction expected; SetLastMentionHandledIdTransactionEquals(SetLastMentionHandledIdTransaction expected) { this.expected = expected; } @Override public void appendTo(StringBuffer arg0) { arg0.append("eqSetLastMentionHandledIdTransaction("); arg0.append(expected.getLastMentionHandledId()); arg0.append(")"); } @Override public boolean matches(Object arg0) { if ((arg0 instanceof SetLastMentionHandledIdTransaction) == false) { return false; } SetLastMentionHandledIdTransaction toMatch = (SetLastMentionHandledIdTransaction) arg0; boolean matches = toMatch.getLastMentionHandledId().equals(expected.getLastMentionHandledId()); return matches; } } }