/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.streams.youtube.provider; import org.apache.streams.core.StreamsDatum; import org.apache.streams.google.gplus.configuration.UserInfo; import org.apache.streams.local.queues.ThroughputQueue; import org.apache.streams.util.api.requests.backoff.impl.ExponentialBackOffStrategy; import org.apache.streams.youtube.YoutubeConfiguration; import com.google.api.services.youtube.YouTube; import com.google.api.services.youtube.model.Activity; import com.google.api.services.youtube.model.ActivityContentDetails; import com.google.api.services.youtube.model.ActivityContentDetailsUpload; import com.google.api.services.youtube.model.ActivityListResponse; import com.google.api.services.youtube.model.Video; import com.google.api.services.youtube.model.VideoListResponse; import com.google.api.services.youtube.model.VideoSnippet; import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.BlockingQueue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Test for YoutubeUserActivityCollector. */ public class YoutubeUserActivityCollectorTest { private static final String USER_ID = "fake_user_id"; private static final String IN_RANGE_IDENTIFIER = "data in range"; private YoutubeConfiguration config; private static VideoListResponse createMockVideoListResponse(int numBefore, int numAfter, int numInRange, DateTime after, DateTime before, boolean page) { VideoListResponse feed = new VideoListResponse(); List<Video> list = new LinkedList<>(); for (int i = 0; i < numAfter; ++i) { com.google.api.client.util.DateTime published = new com.google.api.client.util.DateTime(after.getMillis() + 1000000); Video video = new Video(); video.setSnippet(new VideoSnippet()); video.getSnippet().setPublishedAt(published); list.add(video); } for (int i = 0; i < numInRange; ++i) { DateTime published; if ((before == null && after == null) || before == null) { published = DateTime.now(); // no date range or end time date range so just make the time now. } else if (after == null) { published = before.minusMillis(100000); //no beginning to range } else { // has to be in range long range = before.getMillis() - after.getMillis(); published = after.plus(range / 2); //in the middle } com.google.api.client.util.DateTime ytPublished = new com.google.api.client.util.DateTime(published.getMillis()); Video video = new Video(); video.setSnippet(new VideoSnippet()); video.getSnippet().setPublishedAt(ytPublished); video.getSnippet().setTitle(IN_RANGE_IDENTIFIER); list.add(video); } for (int i = 0; i < numBefore; ++i) { com.google.api.client.util.DateTime published = new com.google.api.client.util.DateTime((after.minusMillis(100000)).getMillis()); Video video = new Video(); video.setSnippet(new VideoSnippet()); video.getSnippet().setPublishedAt(published); list.add(video); } if (page) { feed.setNextPageToken("A"); } else { feed.setNextPageToken(null); } feed.setItems(list); return feed; } @Before public void setup() { this.config = new YoutubeConfiguration(); this.config.setApiKey("API_KEY"); } @Test public void testGetVideos() throws IOException { DateTime now = new DateTime(System.currentTimeMillis()); YouTube youtube = buildYouTube(0, 1, 0, now, now.minus(10000)); BlockingQueue<StreamsDatum> datumQueue = new ThroughputQueue<>(); YoutubeUserActivityCollector collector = new YoutubeUserActivityCollector(youtube, datumQueue, new ExponentialBackOffStrategy(2), new UserInfo().withUserId(USER_ID), this.config); List<Video> video = collector.getVideoList("fake_video_id"); assertNotNull(video.get(0)); } @Test public void testGetVideosNull() throws IOException { DateTime now = new DateTime(System.currentTimeMillis()); YouTube youtube = buildYouTube(0, 0, 0, now.plus(10000), now.minus(10000)); BlockingQueue<StreamsDatum> datumQueue = new ThroughputQueue<>(); YoutubeUserActivityCollector collector = new YoutubeUserActivityCollector(youtube, datumQueue, new ExponentialBackOffStrategy(2), new UserInfo().withUserId(USER_ID), this.config); List<Video> video = collector.getVideoList("fake_video_id"); assertEquals(video.size(), 0); } @Test public void testProcessActivityFeed() throws IOException, InterruptedException { DateTime now = new DateTime(System.currentTimeMillis()); YouTube youtube = buildYouTube(0, 0, 5, now.plus(3000000), now.minus(1000000)); BlockingQueue<StreamsDatum> datumQueue = new ThroughputQueue<>(); YoutubeUserActivityCollector collector = new YoutubeUserActivityCollector(youtube, datumQueue, new ExponentialBackOffStrategy(2), new UserInfo().withUserId(USER_ID), this.config); ActivityListResponse feed = buildActivityListResponse(1); collector.processActivityFeed(feed, new DateTime(System.currentTimeMillis()), null); assertEquals(collector.getDatumQueue().size(), 5); } @Test public void testProcessActivityFeedBefore() throws IOException, InterruptedException { DateTime now = new DateTime(System.currentTimeMillis()); YouTube youtube = buildYouTube(5, 0, 0, now, now); BlockingQueue<StreamsDatum> datumQueue = new ThroughputQueue<>(); YoutubeUserActivityCollector collector = new YoutubeUserActivityCollector(youtube, datumQueue, new ExponentialBackOffStrategy(2), new UserInfo().withUserId(USER_ID), this.config); ActivityListResponse feed = buildActivityListResponse(1); collector.processActivityFeed(feed, new DateTime(System.currentTimeMillis()), null); assertEquals(collector.getDatumQueue().size(), 0); } @Test public void testProcessActivityFeedAfter() throws IOException, InterruptedException { DateTime now = new DateTime(System.currentTimeMillis()); YouTube youtube = buildYouTube(0, 5, 0, now, now); BlockingQueue<StreamsDatum> datumQueue = new ThroughputQueue<>(); YoutubeUserActivityCollector collector = new YoutubeUserActivityCollector(youtube, datumQueue, new ExponentialBackOffStrategy(2), new UserInfo().withUserId(USER_ID), this.config); ActivityListResponse feed = buildActivityListResponse(1); collector.processActivityFeed(feed, new DateTime(now.getMillis() - 100000), null); assertEquals(collector.getDatumQueue().size(), 5); } @Test public void testProcessActivityFeedMismatchCount() throws IOException, InterruptedException { DateTime now = new DateTime(System.currentTimeMillis()); YouTube youtube = buildYouTube(5, 5, 5, now, now.minus(100000)); BlockingQueue<StreamsDatum> datumQueue = new ThroughputQueue<>(); YoutubeUserActivityCollector collector = new YoutubeUserActivityCollector(youtube, datumQueue, new ExponentialBackOffStrategy(2), new UserInfo().withUserId(USER_ID), this.config); ActivityListResponse feed = buildActivityListResponse(1); collector.processActivityFeed(feed, new DateTime(now), null); assertEquals(collector.getDatumQueue().size(), 5); } @Test public void testProcessActivityFeedMismatchCountInRange() throws IOException, InterruptedException { DateTime now = new DateTime(System.currentTimeMillis()); YouTube youtube = buildYouTube(5, 5, 5, now, now.minus(100000)); BlockingQueue<StreamsDatum> datumQueue = new ThroughputQueue<>(); YoutubeUserActivityCollector collector = new YoutubeUserActivityCollector(youtube, datumQueue, new ExponentialBackOffStrategy(2), new UserInfo().withUserId(USER_ID), this.config); ActivityListResponse feed = buildActivityListResponse(1); collector.processActivityFeed(feed, new DateTime(now), new DateTime(now).minus(100000)); assertEquals(collector.getDatumQueue().size(), 5); } private ActivityListResponse buildActivityListResponse(int num) { ActivityListResponse activityListResponse = new ActivityListResponse(); List<Activity> items = new ArrayList<>(); for ( int x = 0; x < num; x++ ) { Activity activity = new Activity(); ActivityContentDetails contentDetails = new ActivityContentDetails(); ActivityContentDetailsUpload upload = new ActivityContentDetailsUpload(); upload.setVideoId("video_id_" + x); contentDetails.setUpload(upload); activity.setId("id_" + x); activity.setContentDetails(contentDetails); items.add(activity); } activityListResponse.setItems(items); return activityListResponse; } private YouTube buildYouTube(int numBeforeRange, int numAfterRange, int numInRange, DateTime afterDate, DateTime beforeDate) { return createYoutubeMock(numBeforeRange, numAfterRange, numInRange, afterDate, beforeDate); } private YouTube createYoutubeMock(int numBefore, int numAfter, int numInRange, DateTime after, DateTime before) { YouTube youtube = mock(YouTube.class); final YouTube.Videos videos = createMockVideos(numBefore, numAfter, numInRange, after, before); doAnswer(invocationOnMock -> videos).when(youtube).videos(); return youtube; } private YouTube.Videos createMockVideos(int numBefore, int numAfter, int numInRange, DateTime after, DateTime before) { YouTube.Videos videos = mock(YouTube.Videos.class); try { YouTube.Videos.List list = createMockVideosList(numBefore, numAfter, numInRange, after, before); when(videos.list(anyString())).thenReturn(list); } catch (IOException ex) { fail("Exception thrown while creating mock"); } return videos; } private YouTube.Videos.List createMockVideosList(int numBefore, int numAfter, int numInRange, DateTime after, DateTime before) { YouTube.Videos.List list = mock(YouTube.Videos.List.class); when(list.setMaxResults(anyLong())).thenReturn(list); when(list.setPageToken(anyString())).thenReturn(list); when(list.setId(anyString())).thenReturn(list); when(list.setKey(anyString())).thenReturn(list); VideoListResponseAnswer answer = new VideoListResponseAnswer(numBefore, numAfter, numInRange, after, before); try { doAnswer(answer).when(list).execute(); } catch (IOException ioe) { fail("Should not have thrown exception while creating mock. : " + ioe.getMessage()); } return list; } private static class VideoListResponseAnswer implements Answer<VideoListResponse> { private int afterCount = 0; private int beforeCount = 0; private int inCount = 0; private int maxBatch = 100; private int numAfter; private int numInRange; private int numBefore; private DateTime after; private DateTime before; private VideoListResponseAnswer(int numBefore, int numAfter, int numInRange, DateTime after, DateTime before) { this.numBefore = numBefore; this.numAfter = numAfter; this.numInRange = numInRange; this.after = after; this.before = before; } @Override public VideoListResponse answer(InvocationOnMock invocationOnMock) throws Throwable { int totalCount = 0; int batchAfter = 0; int batchBefore = 0; int batchIn = 0; inCount = 0; afterCount = 0; beforeCount = 0; if (afterCount != numAfter) { if (numAfter - afterCount >= maxBatch) { afterCount += maxBatch; batchAfter += maxBatch; totalCount += batchAfter; } else { batchAfter += numAfter - afterCount; totalCount += numAfter - afterCount; afterCount = numAfter; } } if (totalCount < maxBatch && inCount != numInRange) { if (numInRange - inCount >= maxBatch - totalCount) { inCount += maxBatch - totalCount; batchIn += maxBatch - totalCount; totalCount += batchIn; } else { batchIn += numInRange - inCount; totalCount += numInRange - inCount; inCount = numInRange; } } if (totalCount < maxBatch && beforeCount != numBefore) { if (numBefore - batchBefore >= maxBatch - totalCount) { batchBefore += maxBatch - totalCount; totalCount = maxBatch; beforeCount += batchBefore; } else { batchBefore += numBefore - beforeCount; totalCount += numBefore - beforeCount; beforeCount = numBefore; } } return createMockVideoListResponse(batchBefore, batchAfter, batchIn, after, before, numAfter != afterCount || inCount != numInRange || beforeCount != numBefore); } } }