/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.test.scratchconverter.protocol;
import android.test.AndroidTestCase;
import org.catrobat.catroid.common.Constants;
import org.catrobat.catroid.scratchconverter.Client;
import org.catrobat.catroid.scratchconverter.ClientException;
import org.catrobat.catroid.scratchconverter.protocol.Job;
import org.catrobat.catroid.scratchconverter.protocol.JobHandler;
import org.catrobat.catroid.scratchconverter.protocol.message.job.JobAlreadyRunningMessage;
import org.catrobat.catroid.scratchconverter.protocol.message.job.JobFailedMessage;
import org.catrobat.catroid.scratchconverter.protocol.message.job.JobFinishedMessage;
import org.catrobat.catroid.scratchconverter.protocol.message.job.JobOutputMessage;
import org.catrobat.catroid.scratchconverter.protocol.message.job.JobProgressMessage;
import org.catrobat.catroid.scratchconverter.protocol.message.job.JobReadyMessage;
import org.catrobat.catroid.scratchconverter.protocol.message.job.JobRunningMessage;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Date;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
public class JobHandlerTest extends AndroidTestCase {
private static final long JOB_ID_OF_JOB_HANDLER = 1;
private static final long WRONG_JOB_ID = 2;
private Client.ConvertCallback convertCallbackMock;
private Job expectedJob;
private JobHandler jobHandler;
@Override
protected void setUp() throws Exception {
super.setUp();
expectedJob = new Job(JOB_ID_OF_JOB_HANDLER, "My program", null);
convertCallbackMock = Mockito.mock(Client.ConvertCallback.class);
jobHandler = new JobHandler(expectedJob, convertCallbackMock);
}
//------------------------------------------------------------------------------------------------------------------
// Receive job-message event tests
//------------------------------------------------------------------------------------------------------------------
public void testReceivedJobMessageWithWrongJobIDShouldFail() {
expectedJob.setState(Job.State.SCHEDULED);
try {
jobHandler.onJobMessage(new JobReadyMessage(WRONG_JOB_ID));
fail("onJobMessage() should throw exception, but returned unexpectedly");
} catch (IllegalArgumentException ex) {
assertEquals("State of expectedJob changed unexpectedly", Job.State.SCHEDULED, expectedJob.getState());
verifyZeroInteractions(convertCallbackMock);
} catch (Exception ex) {
fail("Unexpected exception thrown!");
}
}
public void testReceivedJobMessageWhenJobIsNotInProgressShouldFail() {
// tests all not-in-progress states! -> see: Job.State.isInProgress()
for (Job.State givenState : new Job.State[] { Job.State.UNSCHEDULED, Job.State.FINISHED, Job.State.FAILED }) {
expectedJob.setState(givenState);
try {
jobHandler.onJobMessage(new JobReadyMessage(WRONG_JOB_ID));
fail("onJobMessage() should throw exception, but returned unexpectedly");
} catch (IllegalArgumentException ex) {
assertEquals("State of expectedJob changed unexpectedly", givenState, expectedJob.getState());
verifyZeroInteractions(convertCallbackMock);
} catch (Exception ex) {
fail("Unexpected exception thrown!");
}
}
}
public void testReceivedJobReadyMessageWhenHandlerIsInScheduledStateShouldWork() {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertNotNull("First argument of onConversionReady() call must not be null",
invocation.getArguments()[0]);
final Job job = (Job) invocation.getArguments()[0];
assertEquals("Forwarded parameter job does not equal expected job!", expectedJob, job);
return null;
}
}).when(convertCallbackMock).onConversionReady(any(Job.class));
expectedJob.setState(Job.State.SCHEDULED);
jobHandler.onJobMessage(new JobReadyMessage(JOB_ID_OF_JOB_HANDLER));
assertEquals("Expecting state to be READY after processed JobReadyMessage", Job.State.READY,
expectedJob.getState());
verify(convertCallbackMock, times(1)).onConversionReady(any(Job.class));
verifyNoMoreInteractions(convertCallbackMock);
}
public void testReceivedJobReadyMessageWhenHandlerIsNotInScheduledStateButInProgressShouldBeIgnored() {
for (Job.State givenState : new Job.State[] { Job.State.READY, Job.State.RUNNING }) {
expectedJob.setState(givenState);
jobHandler.onJobMessage(new JobReadyMessage(JOB_ID_OF_JOB_HANDLER));
assertEquals("State of expectedJob changed unexpectedly", givenState, expectedJob.getState());
}
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedJobAlreadyRunningMessageWhenHandlerIsInScheduledStateShouldWork() {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertNotNull("First argument of onConversionStart() call must not be null",
invocation.getArguments()[0]);
final Job job = (Job) invocation.getArguments()[0];
assertEquals("Forwarded parameter job does not equal expected job!", expectedJob, job);
return null;
}
}).when(convertCallbackMock).onConversionStart(any(Job.class));
expectedJob.setState(Job.State.SCHEDULED);
final String expectedProgramImageURL = "https://cdn2.scratch.mit.edu/get_image/project/11656680_480x360.png";
jobHandler.onJobMessage(new JobAlreadyRunningMessage(JOB_ID_OF_JOB_HANDLER, expectedJob.getTitle(),
expectedProgramImageURL));
assertEquals("Expecting state to be RUNNING after processed JobAlreadyRunningMessage",
Job.State.RUNNING, expectedJob.getState());
assertEquals("Image of expectedJob not set properly!",
expectedProgramImageURL, expectedJob.getImage().getUrl().toString());
verify(convertCallbackMock, times(1)).onConversionStart(any(Job.class));
verifyNoMoreInteractions(convertCallbackMock);
}
public void testReceivedJobAlreadyRunningMessageWhenHandlerIsNotInScheduledStateButInProgressShouldBeIgnored() {
for (Job.State givenState : new Job.State[] { Job.State.READY, Job.State.RUNNING }) {
expectedJob.setState(givenState);
final String expectedProgramImageURL = "https://cdn2.scratch.mit.edu/get_image/project/11656680_480x360.png";
jobHandler.onJobMessage(new JobAlreadyRunningMessage(JOB_ID_OF_JOB_HANDLER, expectedJob.getTitle(),
expectedProgramImageURL));
assertEquals("State of expectedJob changed unexpectedly", givenState, expectedJob.getState());
assertNull("Image of expectedJob not set properly!", expectedJob.getImage());
}
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedJobProgressMessageWhenHandlerIsInRunningStateShouldWork() {
final short expectedProgress = 31;
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertNotNull("First argument of onJobProgress() call must not be null",
invocation.getArguments()[0]);
assertNotNull("Second argument of onJobProgress() call must not be null",
invocation.getArguments()[1]);
final Job job = (Job) invocation.getArguments()[0];
final short progress = (short) invocation.getArguments()[1];
assertEquals("Forwarded parameter job does not equal expected job!", expectedJob, job);
assertEquals("Forwarded parameter progress does not equal expected progress value!",
expectedProgress, progress);
return null;
}
}).when(convertCallbackMock).onJobProgress(any(Job.class), any(Short.class));
expectedJob.setState(Job.State.RUNNING);
jobHandler.onJobMessage(new JobProgressMessage(JOB_ID_OF_JOB_HANDLER, expectedProgress));
assertEquals("Expecting state to be RUNNING after processed JobProgressMessage",
Job.State.RUNNING, expectedJob.getState());
assertEquals("Progress value of expectedJob not set properly!", expectedProgress, expectedJob.getProgress());
verify(convertCallbackMock, times(1)).onJobProgress(any(Job.class), any(Short.class));
verifyNoMoreInteractions(convertCallbackMock);
}
public void testReceivedJobProgressMessageWhenHandlerIsNotInRunningStateButInProgressShouldBeIgnored() {
for (Job.State givenState : new Job.State[] { Job.State.SCHEDULED, Job.State.READY }) {
final short expectedProgress = 31;
expectedJob.setState(givenState);
jobHandler.onJobMessage(new JobProgressMessage(JOB_ID_OF_JOB_HANDLER, expectedProgress));
assertEquals("State of expectedJob changed unexpectedly", givenState, expectedJob.getState());
}
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedJobOutputMessageWhenHandlerIsInRunningStateShouldWork() {
final String[] expectedLines = new String[] { "line1", "line2" };
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertNotNull("First argument of onJobOutput() call must not be null",
invocation.getArguments()[0]);
assertNotNull("Second argument of onJobOutput() call must not be null",
invocation.getArguments()[1]);
final Job job = (Job) invocation.getArguments()[0];
final String[] lines = (String[]) invocation.getArguments()[1];
assertEquals("Forwarded parameter job does not equal expected job!", expectedJob, job);
assertEquals("Forwarded parameter lines has wrong length!", expectedLines.length, lines.length);
for (int index = 0; index < expectedLines.length; index++) {
assertEquals("Lines #" + index + " does not equal expected line #" + index + "!",
expectedLines[index], lines[index]);
}
return null;
}
}).when(convertCallbackMock).onJobOutput(any(Job.class), any(String[].class));
expectedJob.setState(Job.State.RUNNING);
jobHandler.onJobMessage(new JobOutputMessage(JOB_ID_OF_JOB_HANDLER, expectedLines));
assertEquals("Expecting state to be RUNNING after processed JobOutputMessage",
Job.State.RUNNING, expectedJob.getState());
verify(convertCallbackMock, times(1)).onJobOutput(any(Job.class), any(String[].class));
verifyNoMoreInteractions(convertCallbackMock);
}
public void testReceivedJobOutputMessageWhenHandlerIsNotInRunningStateButInProgressShouldBeIgnored() {
for (Job.State givenState : new Job.State[] { Job.State.SCHEDULED, Job.State.READY }) {
final String[] expectedLines = new String[] { "line1", "line2" };
expectedJob.setState(givenState);
jobHandler.onJobMessage(new JobOutputMessage(JOB_ID_OF_JOB_HANDLER, expectedLines));
assertEquals("State of expectedJob changed unexpectedly", givenState, expectedJob.getState());
}
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedJobFinishedMessageWhenHandlerIsInScheduledOrRunningStateShouldWork() {
final String expectedDownloadURL = Constants.SCRATCH_CONVERTER_BASE_URL
+ "/download?job_id=1&client_id=1&fname=My%20program";
final Date expectedCacheDate = new Date();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertNotNull("First argument of onConversionFinished() call must not be null",
invocation.getArguments()[0]);
assertNotNull("Second argument of onConversionFinished() call must not be null",
invocation.getArguments()[1]);
assertNotNull("Third argument of onConversionFinished() call must not be null",
invocation.getArguments()[2]);
assertNotNull("Fourth argument of onConversionFinished() call must not be null",
invocation.getArguments()[3]);
final Job job = (Job) invocation.getArguments()[0];
final Client.DownloadCallback downloadCallback =
(Client.DownloadCallback) invocation.getArguments()[1];
final String downloadURL = (String) invocation.getArguments()[2];
final Date cacheDate = (Date) invocation.getArguments()[3];
assertEquals("Forwarded parameter job does not equal expected job!", expectedJob, job);
assertEquals("Forwarded parameter downloadCallback should be the job-handler!",
jobHandler, downloadCallback);
assertEquals("Forwarded parameter downloadURL does not equal expectedDownloadURL!",
expectedDownloadURL, downloadURL);
assertEquals("Forwarded parameter cacheDate does not equal exectedCacheDate!",
expectedCacheDate, cacheDate);
return null;
}
}).when(convertCallbackMock).onConversionFinished(any(Job.class), any(Client.DownloadCallback.class),
any(String.class), any(Date.class));
for (Job.State givenState : new Job.State[] { Job.State.SCHEDULED, Job.State.RUNNING }) {
expectedJob.setState(givenState);
jobHandler.onJobMessage(new JobFinishedMessage(JOB_ID_OF_JOB_HANDLER, expectedDownloadURL,
expectedCacheDate));
assertEquals("Expecting state to be FINISHED after processed JobFinishedMessage",
Job.State.FINISHED, expectedJob.getState());
assertEquals("Download URL of expectedJob not set properly!", expectedDownloadURL,
expectedJob.getDownloadURL());
}
verify(convertCallbackMock, times(2)).onConversionFinished(any(Job.class),
any(Client.DownloadCallback.class), any(String.class), any(Date.class));
verifyNoMoreInteractions(convertCallbackMock);
}
public void testReceivedJobFinishedMessageWhenHandlerIsNotInScheduledOrRunningStateButInProgressShouldBeIgnored() {
final Job.State givenState = Job.State.READY;
expectedJob.setState(givenState);
final String expectedDownloadURL = Constants.SCRATCH_CONVERTER_BASE_URL
+ "/download?job_id=1&client_id=1&fname=My%20program";
final Date expectedCacheDate = new Date();
jobHandler.onJobMessage(new JobFinishedMessage(JOB_ID_OF_JOB_HANDLER, expectedDownloadURL,
expectedCacheDate));
assertEquals("State of expectedJob changed unexpectedly", givenState, expectedJob.getState());
assertNull("Image of expectedJob not set properly!", expectedJob.getImage());
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedJobFailedMessageWhenHandlerIsInScheduledStateShouldWork() {
final String expectedErrorMessage = "Error message successfully received";
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertNotNull("First argument of onConversionFailure() call must not be null",
invocation.getArguments()[0]);
assertNotNull("Second argument of onConversionFailure() call must not be null",
invocation.getArguments()[1]);
final Job job = (Job) invocation.getArguments()[0];
final ClientException clientException = (ClientException) invocation.getArguments()[1];
assertEquals("Forwarded parameter job does not equal expected job!", expectedJob, job);
assertEquals("Forwarded parameter errorMessage does not equal expectedErrorMessage!",
"Job failed - Reason: " + expectedErrorMessage, clientException.getMessage());
return null;
}
}).when(convertCallbackMock).onConversionFailure(any(Job.class), any(ClientException.class));
for (Job.State givenState : new Job.State[] { Job.State.SCHEDULED, Job.State.RUNNING }) {
expectedJob.setState(givenState);
jobHandler.onJobMessage(new JobFailedMessage(JOB_ID_OF_JOB_HANDLER, expectedErrorMessage));
assertEquals("Expecting state to be FAILED after processed JobFailedMessage",
Job.State.FAILED, expectedJob.getState());
}
verify(convertCallbackMock, times(2)).onConversionFailure(any(Job.class), any(ClientException.class));
verifyNoMoreInteractions(convertCallbackMock);
}
public void testReceivedJobFailedMessageWhenHandlerIsNotInScheduledOrRunningStateButInProgressShouldBeIgnored() {
final Job.State givenState = Job.State.READY;
final String errorMessage = "Error message successfully received";
expectedJob.setState(givenState);
jobHandler.onJobMessage(new JobFailedMessage(JOB_ID_OF_JOB_HANDLER, errorMessage));
assertEquals("State of expectedJob changed unexpectedly", givenState, expectedJob.getState());
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedJobRunningMessageWhenHandlerIsInReadyStateShouldWork() {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertNotNull("First argument of onConversionStart() call must not be null",
invocation.getArguments()[0]);
final Job job = (Job) invocation.getArguments()[0];
assertEquals("Forwarded parameter job does not equal expected job!", expectedJob, job);
return null;
}
}).when(convertCallbackMock).onConversionStart(any(Job.class));
expectedJob.setState(Job.State.READY);
final String programImageURL = "https://cdn2.scratch.mit.edu/get_image/project/11656680_480x360.png";
jobHandler.onJobMessage(new JobRunningMessage(JOB_ID_OF_JOB_HANDLER, expectedJob.getTitle(), programImageURL));
assertEquals("Expecting state to be RUNNING after processed JobRunningMessage",
Job.State.RUNNING, expectedJob.getState());
verify(convertCallbackMock, times(1)).onConversionStart(any(Job.class));
verifyNoMoreInteractions(convertCallbackMock);
}
public void testReceivedJobRunningMessageWhenHandlerIsNotInReadyStateButInProgressShouldBeIgnored() {
for (Job.State givenState : new Job.State[] { Job.State.SCHEDULED, Job.State.RUNNING }) {
expectedJob.setState(givenState);
final String programImageURL = "https://cdn2.scratch.mit.edu/get_image/project/11656680_480x360.png";
jobHandler.onJobMessage(new JobRunningMessage(JOB_ID_OF_JOB_HANDLER, expectedJob.getTitle(),
programImageURL));
assertEquals("State of expectedJob changed unexpectedly", givenState, expectedJob.getState());
assertNull("Image of expectedJob not set properly!", expectedJob.getImage());
verifyZeroInteractions(convertCallbackMock);
}
}
//------------------------------------------------------------------------------------------------------------------
// Other event tests
//------------------------------------------------------------------------------------------------------------------
public void testReceivedOnJobScheduledEvent() {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
assertNotNull("First argument of onConversionStart() call must not be null",
invocation.getArguments()[0]);
final Job job = (Job) invocation.getArguments()[0];
assertEquals("Forwarded parameter job does not equal expected job!", expectedJob, job);
return null;
}
}).when(convertCallbackMock).onJobScheduled(any(Job.class));
expectedJob.setState(Job.State.UNSCHEDULED);
jobHandler.onJobScheduled();
assertEquals("Expecting state to be SCHEDULED after onJobScheduled() called",
Job.State.SCHEDULED, expectedJob.getState());
verify(convertCallbackMock, times(1)).onJobScheduled(any(Job.class));
verifyNoMoreInteractions(convertCallbackMock);
}
public void testReceivedOnDownloadStartedEvent() {
final String expectedDownloadURL = Constants.SCRATCH_CONVERTER_BASE_URL
+ "/download?job_id=1&client_id=1&fname=My%20program";
expectedJob.setState(Job.State.RUNNING);
expectedJob.setDownloadState(Job.DownloadState.READY);
jobHandler.onDownloadStarted(expectedDownloadURL);
assertEquals("Expecting downloadState to be DOWNLOADING after onDownloadStarted() called",
Job.DownloadState.DOWNLOADING, expectedJob.getDownloadState());
assertEquals("Expecting state to be FINISHED after onDownloadStarted() called",
Job.State.FINISHED, expectedJob.getState());
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedOnDownloadFinishedEvent() {
final String expectedDownloadURL = Constants.SCRATCH_CONVERTER_BASE_URL
+ "/download?job_id=1&client_id=1&fname=My%20program";
expectedJob.setState(Job.State.RUNNING);
expectedJob.setDownloadState(Job.DownloadState.DOWNLOADING);
jobHandler.onDownloadFinished(expectedJob.getTitle(), expectedDownloadURL);
assertEquals("Expecting downloadState to be DOWNLOADED after onDownloadFinished() called",
Job.DownloadState.DOWNLOADED, expectedJob.getDownloadState());
assertEquals("Expecting state to be FINISHED after onDownloadFinished() called",
Job.State.FINISHED, expectedJob.getState());
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedOnUserCanceledDownloadEvent() {
final String expectedDownloadURL = Constants.SCRATCH_CONVERTER_BASE_URL
+ "/download?job_id=1&client_id=1&fname=My%20program";
expectedJob.setState(Job.State.RUNNING);
expectedJob.setDownloadState(Job.DownloadState.DOWNLOADING);
jobHandler.onUserCanceledDownload(expectedDownloadURL);
assertEquals("Expecting downloadState to be NOT_DOWNLOADED after onUserCanceledDownload() called",
Job.DownloadState.CANCELED, expectedJob.getDownloadState());
assertEquals("Expecting state to be FINISHED after onUserCanceledDownload() called",
Job.State.FINISHED, expectedJob.getState());
verifyZeroInteractions(convertCallbackMock);
}
public void testReceivedOnUserCanceledConversionEvent() {
expectedJob.setState(Job.State.RUNNING);
jobHandler.onUserCanceledConversion();
assertEquals("Expecting state to be FINISHED after onUserCanceledConversion() called",
Job.State.FINISHED, expectedJob.getState());
verifyZeroInteractions(convertCallbackMock);
}
//------------------------------------------------------------------------------------------------------------------
// Wrapper method tests
//------------------------------------------------------------------------------------------------------------------
public void testIsJobInProgressWrapperCalled() {
for (Job.State givenState : Job.State.values()) {
expectedJob.setState(givenState);
boolean expectedResult = expectedJob.isInProgress();
assertTrue("Expecting wrapper to return same value as expectedJob.isInProgress() call",
expectedResult == jobHandler.isInProgress());
}
verifyZeroInteractions(convertCallbackMock);
}
public void testIsGetJobIDWrapperCalled() {
final long expectedJobID = expectedJob.getJobID();
assertEquals("Expecting wrapper to return same value as expectedJob.getJobID() call",
expectedJobID, jobHandler.getJobID());
verifyZeroInteractions(convertCallbackMock);
}
}