/* * Copyright (c) 2016 Google Inc. * * 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.google.samples.apps.iosched.explore; import android.app.LoaderManager; import android.content.Context; import android.content.CursorLoader; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.test.suitebuilder.annotation.SmallTest; import com.google.samples.apps.iosched.Config; import com.google.samples.apps.iosched.R; import com.google.samples.apps.iosched.explore.data.ItemGroup; import com.google.samples.apps.iosched.provider.ScheduleContract; import com.google.samples.apps.iosched.settings.SettingsUtils; import com.google.samples.apps.iosched.testutils.SettingsMockContext; import com.google.samples.apps.iosched.util.LogUtils; import com.google.samples.apps.iosched.util.SessionsHelper; import com.google.samples.apps.iosched.util.TimeUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.Iterator; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest({SettingsUtils.class, TimeUtils.class}) @SmallTest public class ExploreIOModelTest { private static final int SESSION_THEME_LIMIT = 2; private static final String FAKE_TAG_TRACK_ID = Config.Tags.CATEGORY_TRACK + Config.Tags.CATEGORY_SEP + "1"; private static final String FAKE_TAG_THEME_ID = Config.Tags.CATEGORY_THEME + Config.Tags.CATEGORY_SEP + "2"; private static final String FAKE_TAG_TRACK_ID_NOT_IN_TAGS_CURSOR = Config.Tags.CATEGORY_TRACK + Config.Tags.CATEGORY_SEP + "0"; private static final String FAKE_TAG_A = "A"; private static final String FAKE_TAG_B = "B"; /** * Fake tags data to be used for the mocked cursor. */ private static final Object[][] FAKE_TAGS_CURSOR_DATA = { new String[]{FAKE_TAG_TRACK_ID, FAKE_TAG_THEME_ID}, new String[]{FAKE_TAG_A, FAKE_TAG_B}, new Integer[]{0, 1}, new Integer[]{0, 0}}; /** * Indexes of the "column" for each video attribute in the {@link #FAKE_TAGS_CURSOR_DATA}. */ private static final int ID_COLUMN_INDEX = 0; private static final int NAME_COLUMN_INDEX = 1; private static final int NAME_ORDER_IN_CATEGORY_INDEX = 2; private static final int NAME_FAKE_INTEGER_COLUMN_COLUMN = 3; private static final String FAKE_SESSION_TITLE_1_LIVESTREAM = "TITLE_LIVESTREAM"; private static final String FAKE_SESSION_TITLE_2_KEYNOTE = "Keynote"; private static final String FAKE_SESSION_TITLE_3 = "TITLE3"; private static final String FAKE_SESSION_TITLE_4 = "TITLE4"; private static final String FAKE_SESSION_ABSTRACT = "ABSTRACT"; private static final String FAKE_SESSION_ID_1 = "ID1"; private static final String FAKE_SESSION_ID_2_KEYNOTE = "ID2"; private static final String FAKE_SESSION_ID_3 = "ID3"; private static final String FAKE_SESSION_ID_4 = "ID4"; private static final String FAKE_SESSION_URL = "URL"; private static final long FAKE_SESSION_START1 = Config.CONFERENCE_START_MILLIS; private static final long FAKE_SESSION_START2 = Config.CONFERENCE_START_MILLIS + 2 * TimeUtils.HOUR; private static final long FAKE_SESSION_START3 = Config.CONFERENCE_START_MILLIS + 3 * TimeUtils.HOUR; private static final long FAKE_SESSION_START4 = Config.CONFERENCE_START_MILLIS + 4 * TimeUtils.HOUR; private static final long FAKE_SESSION_END1 = FAKE_SESSION_START1 + 1l * TimeUtils.HOUR; private static final long FAKE_SESSION_END2 = FAKE_SESSION_START2 + 1l * TimeUtils.HOUR; private static final long FAKE_SESSION_END3 = FAKE_SESSION_START3 + 1l * TimeUtils.HOUR; private static final long FAKE_SESSION_END4 = FAKE_SESSION_START4 + 1l * TimeUtils.HOUR; private static final String FAKE_SESSION_LIVESTREAM = "fejsopfe56"; /** * Fake sessions data to be used for the mocked cursor. */ private static final Object[][] FAKE_SESSIONS_CURSOR_DATA = { new String[]{FAKE_SESSION_TITLE_1_LIVESTREAM, FAKE_SESSION_TITLE_2_KEYNOTE, FAKE_SESSION_TITLE_3, FAKE_SESSION_TITLE_4}, new String[]{FAKE_SESSION_ABSTRACT, FAKE_SESSION_ABSTRACT, FAKE_SESSION_ABSTRACT, FAKE_SESSION_ABSTRACT}, new String[]{FAKE_SESSION_ID_1, FAKE_SESSION_ID_2_KEYNOTE, FAKE_SESSION_ID_3, FAKE_SESSION_ID_4}, new String[]{FAKE_SESSION_URL, FAKE_SESSION_URL, FAKE_SESSION_URL, FAKE_SESSION_URL}, new String[]{FAKE_TAG_THEME_ID, Config.Tags.SPECIAL_KEYNOTE, FAKE_TAG_TRACK_ID, FAKE_TAG_TRACK_ID_NOT_IN_TAGS_CURSOR}, new Long[]{FAKE_SESSION_START1, FAKE_SESSION_START2, FAKE_SESSION_START3, FAKE_SESSION_START4}, new Long[]{FAKE_SESSION_END1, FAKE_SESSION_END2, FAKE_SESSION_END3, FAKE_SESSION_END4}, new String[]{FAKE_SESSION_LIVESTREAM, null, null, null}, new String[]{FAKE_SESSION_URL, FAKE_SESSION_URL, FAKE_SESSION_URL, FAKE_SESSION_URL}, new String[]{FAKE_TAG_THEME_ID + "," + FAKE_TAG_TRACK_ID, "", FAKE_TAG_TRACK_ID, FAKE_TAG_TRACK_ID + "," + FAKE_TAG_TRACK_ID_NOT_IN_TAGS_CURSOR}, new Long[]{1l, 1l, 0l, 1l} }; /** * Indexes of the "column" for each video attribute in the {@link #FAKE_SESSIONS_CURSOR_DATA}. */ private static final int TITLE_COLUMN_INDEX = 0; private static final int ABSTRACT_COLUMN_INDEX = 1; private static final int SESSION_ID_COLUMN_INDEX = 2; private static final int PHOTO_URL_COLUMN_INDEX = 3; private static final int MAIN_TAG_COLUMN_INDEX = 4; private static final int START_COLUMN_INDEX = 5; private static final int END_COLUMN_INDEX = 6; private static final int LIVESTREAM_ID_COLUMN_INDEX = 7; private static final int YOUTUBE_URL_COLUMN_INDEX = 8; private static final int TAGS_COLUMN_INDEX = 9; private static final int MY_SCHEDULE_COLUMN_INDEX = 10; /** * Cursor position in the {@link #FAKE_TAGS_CURSOR_DATA} or {@link #FAKE_SESSIONS_CURSOR_DATA} * array. */ private int mCursorIndex = -1; @Captor private ArgumentCaptor<Integer> mGetCursorValueCaptor; @Mock private Context mMockContext; @Mock private SharedPreferences mMockSharedPreferences; @Mock private Uri mMockUri; @Mock private SessionsHelper mMockSessionsHelper; @Mock private Cursor mMockSessionCursor; @Mock private Cursor mMockTagsCursor; @Mock private Cursor mMockEmptyCursor; @Mock private CursorLoader cursorLoader; @Mock private LoaderManager mMockLoaderManager; @Mock private Resources mMockResources; private ExploreIOModel mExploreIOModel; @Before public void setUp() { // Disable logging LogUtils.LOGGING_ENABLED = false; // Init mocks SettingsMockContext .initMockContextForAttendingVenueSetting(true, mMockContext); SettingsMockContext .initMockContextForCurrentTime(Config.CONFERENCE_START_MILLIS + TimeUtils.MINUTE, mMockContext); initMockThemesLimit(mMockContext, mMockResources); // Create an instance of the model. mExploreIOModel = new ExploreIOModel(mMockContext, mMockUri, mMockLoaderManager); } @Test public void readDataFromCursor_SessionsQuery_SessionsLoaded() { // Given a mock cursor with fake sessions and tags data initMockCursorWithSessionsData(mMockSessionCursor); initMockCursorWithTagsData(mMockTagsCursor); // When ran with session query boolean success = mExploreIOModel.readDataFromCursor( mMockSessionCursor, ExploreIOModel.ExploreIOQueryEnum.SESSIONS); // And tags data is available mExploreIOModel.readDataFromCursor( mMockTagsCursor, ExploreIOModel.ExploreIOQueryEnum.TAGS); // Then the model has correctly ordered session data assertThat(success, is(true)); assertThat(mExploreIOModel.getKeynoteData().getSessionName(), is(FAKE_SESSION_TITLE_2_KEYNOTE)); assertThat(mExploreIOModel.getLiveStreamData().getSessions().get(0).getSessionName(), is(FAKE_SESSION_TITLE_1_LIVESTREAM)); assertThat(mExploreIOModel.getOrderedTracks().size(), is(2)); } @Test public void orderedTracks_MissingTitle_MissingTitleTrackIsLast() { // Given a mock cursor with fake sessions and tags data initMockCursorWithSessionsData(mMockSessionCursor); initMockCursorWithTagsData(mMockTagsCursor); // When ran with session query boolean success = mExploreIOModel.readDataFromCursor( mMockSessionCursor, ExploreIOModel.ExploreIOQueryEnum.SESSIONS); // And tags data is available mExploreIOModel.readDataFromCursor( mMockTagsCursor, ExploreIOModel.ExploreIOQueryEnum.TAGS); // Then the model has correctly ordered session data assertThat(success, is(true)); assertThat(mExploreIOModel.getKeynoteData().getSessionName(), is(FAKE_SESSION_TITLE_2_KEYNOTE)); assertThat(mExploreIOModel.getLiveStreamData().getSessions().get(0).getSessionName(), is(FAKE_SESSION_TITLE_1_LIVESTREAM)); assertThat(mExploreIOModel.getOrderedTracks().size(), is(2)); Iterator<ItemGroup> groupIterator = mExploreIOModel.getOrderedTracks().iterator(); ItemGroup groupA = groupIterator.next(); ItemGroup groupB = groupIterator.next(); assertThat(groupA.getTitle(), is(FAKE_TAG_A)); assertThat(groupA.getSessions().size(), is(3)); assertNull(groupB.getTitle()); assertThat(groupB.getSessions().size(), is(1)); } @Test public void readDataFromCursor_TagsQuery_TagsLoaded() { // Given a mock cursor with fake tags data initMockCursorWithTagsData(mMockTagsCursor); // When ran with tags query boolean success = mExploreIOModel.readDataFromCursor( mMockTagsCursor, ExploreIOModel.ExploreIOQueryEnum.TAGS); // Then the model has correct tags data assertThat(success, is(true)); assertNotNull(mExploreIOModel.getTagMetadata()); assertThat(mExploreIOModel.getTagMetadata().getTag(FAKE_TAG_TRACK_ID).getName(), is(FAKE_TAG_A)); assertThat(mExploreIOModel.getTagMetadata().getTag(FAKE_TAG_THEME_ID).getName(), is(FAKE_TAG_B)); } private void initMockCursorWithSessionsData(Cursor cursor) { // Mock movements of the cursor position. Cursor position in the array is tracked using // mCursorIndex. when(cursor.moveToFirst()).then( new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { mCursorIndex = 0; return FAKE_SESSIONS_CURSOR_DATA[0].length > 0; } }); when(cursor.moveToNext()).then( new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { if (mCursorIndex < FAKE_SESSIONS_CURSOR_DATA[0].length - 1) { mCursorIndex++; return true; } else { return false; } } }); // Mock reading data from the cursor using the FAKE_VIDEO_CURSOR_DATA table values. Answer<?> getCursorValue = new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return FAKE_SESSIONS_CURSOR_DATA[mGetCursorValueCaptor.getValue()][mCursorIndex]; } }; // Map the fake data table column index. when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_TITLE)) .thenReturn(TITLE_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_ABSTRACT)) .thenReturn(ABSTRACT_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_ID)) .thenReturn(SESSION_ID_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_PHOTO_URL)) .thenReturn(PHOTO_URL_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_MAIN_TAG)) .thenReturn(MAIN_TAG_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_START)) .thenReturn(START_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_END)) .thenReturn(END_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_LIVESTREAM_ID)) .thenReturn(LIVESTREAM_ID_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_YOUTUBE_URL)) .thenReturn(YOUTUBE_URL_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_TAGS)) .thenReturn(TAGS_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Sessions.SESSION_IN_MY_SCHEDULE)) .thenReturn(MY_SCHEDULE_COLUMN_INDEX); // Returning values from the fake data table. when(cursor.getString(mGetCursorValueCaptor.capture())).then(getCursorValue); when(cursor.getLong(mGetCursorValueCaptor.capture())).then(getCursorValue); } private void initMockCursorWithTagsData(Cursor cursor) { mCursorIndex = -1; // Mock movements of the cursor position. Cursor position in the array is tracked using // mCursorIndex. when(cursor.moveToFirst()).then( new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { mCursorIndex = 0; return FAKE_TAGS_CURSOR_DATA[0].length > 0; } }); when(cursor.moveToNext()).then( new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { if (mCursorIndex < FAKE_TAGS_CURSOR_DATA[0].length - 1) { mCursorIndex++; return true; } else { return false; } } }); when(cursor.moveToPosition(mGetCursorValueCaptor.capture())).then( new Answer<Boolean>() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { if (mCursorIndex < FAKE_TAGS_CURSOR_DATA[0].length - 1) { mCursorIndex = mGetCursorValueCaptor.getValue(); return true; } else { return false; } } }); // Mock reading data from the cursor using the FAKE_VIDEO_CURSOR_DATA table values. Answer<?> getCursorValue = new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return FAKE_TAGS_CURSOR_DATA[mGetCursorValueCaptor.getValue()][mCursorIndex]; } }; // Map the fake data table column index. when(cursor.getColumnIndex(ScheduleContract.Tags.TAG_ID)) .thenReturn(ID_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Tags.TAG_NAME)) .thenReturn(NAME_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Tags.TAG_CATEGORY)) .thenReturn(NAME_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Tags.TAG_ORDER_IN_CATEGORY)) .thenReturn(NAME_ORDER_IN_CATEGORY_INDEX); when(cursor.getColumnIndex(ScheduleContract.Tags.TAG_ABSTRACT)) .thenReturn(NAME_COLUMN_INDEX); when(cursor.getColumnIndex(ScheduleContract.Tags.TAG_COLOR)) .thenReturn(NAME_FAKE_INTEGER_COLUMN_COLUMN); when(cursor.getColumnIndex(ScheduleContract.Tags.TAG_PHOTO_URL)) .thenReturn(NAME_COLUMN_INDEX); // Returning values from the fake data table. when(cursor.getString(mGetCursorValueCaptor.capture())).then(getCursorValue); when(cursor.getInt(mGetCursorValueCaptor.capture())).then(getCursorValue); when(cursor.getCount()).thenReturn(FAKE_TAGS_CURSOR_DATA.length); } private void initMockThemesLimit(Context context, Resources resources) { when(context.getResources()).thenReturn(resources); when(resources.getInteger(R.integer .explore_topic_theme_onsite_max_item_count)).thenReturn(SESSION_THEME_LIMIT); } }