diff --git a/pom.xml b/pom.xml index 8c02cb3c..7ea9584c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ fi.helsinki.cs.tmc core - 0.9.12-SNAPSHOT + 0.10.0-SNAPSHOT jar tmc-core http://testmycode.net @@ -402,6 +402,11 @@ tmc-junit-runner 0.2.5 + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.client + 1.0.2 + diff --git a/src/main/java/fi/helsinki/cs/tmc/core/TmcCore.java b/src/main/java/fi/helsinki/cs/tmc/core/TmcCore.java index 9173f73c..a328759e 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/TmcCore.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/TmcCore.java @@ -1,9 +1,12 @@ package fi.helsinki.cs.tmc.core; +import fi.helsinki.cs.tmc.core.commands.AuthenticateUser; +import fi.helsinki.cs.tmc.core.commands.DownloadAdaptiveExercise; import fi.helsinki.cs.tmc.core.commands.DownloadCompletedExercises; import fi.helsinki.cs.tmc.core.commands.DownloadModelSolution; import fi.helsinki.cs.tmc.core.commands.DownloadOrUpdateExercises; import fi.helsinki.cs.tmc.core.commands.GetCourseDetails; +import fi.helsinki.cs.tmc.core.commands.GetOrganizations; import fi.helsinki.cs.tmc.core.commands.GetUnreadReviews; import fi.helsinki.cs.tmc.core.commands.GetUpdatableExercises; import fi.helsinki.cs.tmc.core.commands.ListCourses; @@ -16,17 +19,22 @@ import fi.helsinki.cs.tmc.core.commands.SendFeedback; import fi.helsinki.cs.tmc.core.commands.SendSpywareEvents; import fi.helsinki.cs.tmc.core.commands.Submit; +import fi.helsinki.cs.tmc.core.commands.SubmitAdaptiveExerciseToSkillifier; import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth; import fi.helsinki.cs.tmc.core.configuration.TmcSettings; import fi.helsinki.cs.tmc.core.domain.Course; import fi.helsinki.cs.tmc.core.domain.Exercise; +import fi.helsinki.cs.tmc.core.domain.Organization; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.domain.Review; +import fi.helsinki.cs.tmc.core.domain.Theme; import fi.helsinki.cs.tmc.core.domain.submission.FeedbackAnswer; import fi.helsinki.cs.tmc.core.domain.submission.SubmissionResult; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; import fi.helsinki.cs.tmc.core.utilities.ExceptionTrackingCallable; +import fi.helsinki.cs.tmc.core.utilities.TmcServerAddressNormalizer; import fi.helsinki.cs.tmc.langs.abstraction.ValidationResult; import fi.helsinki.cs.tmc.langs.domain.RunResult; import fi.helsinki.cs.tmc.langs.util.TaskExecutor; @@ -72,6 +80,17 @@ public TmcCore() {} public TmcCore(TmcSettings settings, TaskExecutor tmcLangs) { TmcSettingsHolder.set(settings); TmcLangsHolder.set(tmcLangs); + TmcServerAddressNormalizer.normalize(); + } + + public Callable> getOrganizations(ProgressObserver observer) { + logger.info("Creating new GetOrganizations command"); + return new GetOrganizations(observer); + } + + public Callable authenticate(ProgressObserver observer, String password) { + logger.info("Creating new AuthenticateUser command"); + return new AuthenticateUser(observer, password, Oauth.getInstance()); } public Callable sendDiagnostics( @@ -125,11 +144,16 @@ public Callable sendSpywareEvents( return new ExceptionTrackingCallable<>(new SendSpywareEvents(observer, currentCourse, events)); } - + public Callable submit(ProgressObserver observer, Exercise exercise) { logger.info("Creating new Submit command"); return new ExceptionTrackingCallable<>(new Submit(observer, exercise)); } + + public Callable submitAdaptiveExercise(ProgressObserver observer, Exercise exercise) { + logger.info("Creating new submit adaptiveExercise command"); + return new ExceptionTrackingCallable<>(new SubmitAdaptiveExerciseToSkillifier(observer, exercise)); + } public Callable getExerciseUpdates( ProgressObserver observer, Course course) { @@ -158,6 +182,11 @@ public Callable downloadModelSolution(ProgressObserver observer, Exerc return new ExceptionTrackingCallable<>(new DownloadModelSolution(observer, exercise)); } + public Callable downloadAdaptiveExercise(ProgressObserver observer, Theme theme, Course course) { + logger.info("Creating new DownloadAdaptiveExercise command"); + return new ExceptionTrackingCallable<>(new DownloadAdaptiveExercise(observer, theme, course)); + } + /** * NOT IMPLEMENTED! * diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/AbstractSubmissionCommand.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/AbstractSubmissionCommand.java index af2125bc..e64090e0 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/commands/AbstractSubmissionCommand.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/AbstractSubmissionCommand.java @@ -3,12 +3,17 @@ import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; import fi.helsinki.cs.tmc.core.domain.Exercise; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.domain.submission.AdaptiveSubmissionResult; +import fi.helsinki.cs.tmc.core.domain.submission.SubmissionResult; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; import fi.helsinki.cs.tmc.core.exceptions.TmcCoreException; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; import fi.helsinki.cs.tmc.langs.domain.NoLanguagePluginFoundException; import com.google.common.annotations.VisibleForTesting; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,8 +41,6 @@ abstract class AbstractSubmissionCommand extends Command { TmcServerCommunicationTaskFactory.SubmissionResponse submitToServer( Exercise exercise, Map extraParams) throws TmcCoreException { - byte[] zippedProject; - informObserver(0, "Zipping project"); Path tmcRoot = TmcSettingsHolder.get().getTmcProjectDirectory(); @@ -46,13 +49,7 @@ TmcServerCommunicationTaskFactory.SubmissionResponse submitToServer( checkInterrupt(); logger.info("Submitting project from path {}", projectPath); - try { - zippedProject = TmcLangsHolder.get().compressProject(projectPath); - } catch (IOException | NoLanguagePluginFoundException ex) { - informObserver(1, "Failed to compress project"); - logger.warn("Failed to compress project", ex); - throw new TmcCoreException("Failed to compress project", ex); - } + byte[] zippedProject = compressProject(projectPath); extraParams.put("error_msg_locale", TmcSettingsHolder.get().getLocale().toString()); @@ -71,9 +68,71 @@ TmcServerCommunicationTaskFactory.SubmissionResponse submitToServer( return response; } catch (Exception ex) { + if (ex instanceof NotLoggedInException) { + throw (NotLoggedInException)ex; + } informObserver(1, "Failed to submit exercise"); logger.warn("Failed to submit exercise", ex); throw new TmcCoreException("Failed to submit exercise", ex); } } + + private byte[] compressProject(Path projectPath) throws TmcCoreException { + byte[] zippedProject; + try { + zippedProject = TmcLangsHolder.get().compressProject(projectPath); + } catch (IOException | NoLanguagePluginFoundException ex) { + informObserver(1, "Failed to compress project"); + logger.warn("Failed to compress project", ex); + throw new TmcCoreException("Failed to compress project", ex); + } + return zippedProject; + } + + SubmissionResult submitToSkillifier( + Exercise exercise, Map extraParams) throws TmcCoreException { + + informObserver(0, "Zipping project"); + + Path tmcRoot = TmcSettingsHolder.get().getTmcProjectDirectory(); + Path projectPath = exercise.getExerciseDirectory(tmcRoot); + + checkInterrupt(); + logger.info("Submitting project to skillifier from path {}", projectPath); + + byte[] zippedProject = compressProject(projectPath); + + extraParams.put("error_msg_locale", TmcSettingsHolder.get().getLocale().toString()); + + checkInterrupt(); + informObserver(0.5, "Submitting project to skillifier"); + logger.info("Submitting project to skillifier"); + + try { + String resultJson + = tmcServerCommunicationTaskFactory + .getSubmittingExerciseToSkillifierTask(exercise, zippedProject, extraParams) + .call(); + + informObserver(1, "Submission to skillifier successfully completed"); + logger.info("Submission to skillifier successfully completed"); + + try { + Gson gson = new GsonBuilder().create(); + SubmissionResult result = gson.fromJson(resultJson, AdaptiveSubmissionResult.class).toSubmissionResult(); + return result; + } catch (Exception e) { + logger.warn("Failed to parse submission result from skillifier", e); + } + return null; + + } catch (Exception ex) { + if (ex instanceof NotLoggedInException) { + throw (NotLoggedInException)ex; + } + informObserver(1, "Failed to submit exercise to skillifier"); + logger.warn("Failed to submit exercise to skillifier", ex); + throw new TmcCoreException("Failed to submit exercise to skillifier", ex); + } + } } diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/AuthenticateUser.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/AuthenticateUser.java new file mode 100644 index 00000000..88b1d4fe --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/AuthenticateUser.java @@ -0,0 +1,46 @@ +package fi.helsinki.cs.tmc.core.commands; + +import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth; +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.exceptions.AuthenticationFailedException; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; + +import com.google.common.annotations.VisibleForTesting; + +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; + +import java.io.IOException; + +public class AuthenticateUser extends Command { + private String password; + private final Oauth oauth; + + public AuthenticateUser(ProgressObserver observer, String password, Oauth oauth) { + super(observer); + this.password = password; + this.oauth = oauth; + } + + @VisibleForTesting + AuthenticateUser(ProgressObserver observer, String password, Oauth oauth, + TmcServerCommunicationTaskFactory tmcServerCommunicationTaskFactory) { + super(observer, tmcServerCommunicationTaskFactory); + this.password = password; + this.oauth = oauth; + } + + @Override + public Void call() throws AuthenticationFailedException { + TmcSettings tmcSettings = TmcSettingsHolder.get(); + try { + tmcServerCommunicationTaskFactory.getOauthCredentialsTask(); + oauth.fetchNewToken(password); + } catch (OAuthSystemException | OAuthProblemException | IOException e) { + throw new AuthenticationFailedException(e); + } + return null; + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/DownloadAdaptiveExercise.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/DownloadAdaptiveExercise.java new file mode 100644 index 00000000..7c21a03f --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/DownloadAdaptiveExercise.java @@ -0,0 +1,54 @@ +package fi.helsinki.cs.tmc.core.commands; + +import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.domain.Course; +import fi.helsinki.cs.tmc.core.domain.Exercise; +import fi.helsinki.cs.tmc.core.domain.Progress; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.domain.Theme; + +import com.google.common.annotations.VisibleForTesting; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by markovai on 2.6.2017. + */ +public class DownloadAdaptiveExercise extends ExerciseDownloadingCommand { + + private static final Logger logger = LoggerFactory.getLogger(DownloadAdaptiveExercise.class); + + private Theme theme; + private Course course; + + public DownloadAdaptiveExercise(ProgressObserver observer, Theme theme, Course course) { + super(observer); + this.theme = theme; + this.course = course; + } + + @VisibleForTesting + DownloadAdaptiveExercise( + ProgressObserver observer, + TmcServerCommunicationTaskFactory tmcServerCommunicationTaskFactory, + Theme theme, Course course) { + super(observer, tmcServerCommunicationTaskFactory); + this.theme = theme; + this.course = course; + } + + @Override + public Exercise call() throws Exception { + logger.info("Checking adaptive exercises availability by theme"); + Exercise exercise = tmcServerCommunicationTaskFactory.getAdaptiveExercise(theme, course).call(); + if (exercise == null) { + return null; + } + byte[] zipb = tmcServerCommunicationTaskFactory.getDownloadingAdaptiveExerciseZipTask(exercise).call(); + checkInterrupt(); + Progress progress = new Progress(3); + extractProject(zipb, exercise, progress); + return exercise; + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/DownloadOrUpdateExercises.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/DownloadOrUpdateExercises.java index 4cf29ab8..54fcf367 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/commands/DownloadOrUpdateExercises.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/DownloadOrUpdateExercises.java @@ -5,7 +5,7 @@ import fi.helsinki.cs.tmc.core.domain.Progress; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.exceptions.ExerciseDownloadFailedException; -import fi.helsinki.cs.tmc.core.exceptions.ExtractingExericeFailedException; +import fi.helsinki.cs.tmc.core.exceptions.ExtractingExerciseFailedException; import fi.helsinki.cs.tmc.core.exceptions.TmcCoreException; import fi.helsinki.cs.tmc.core.exceptions.TmcInterruptionException; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/ExerciseDownloadingCommand.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/ExerciseDownloadingCommand.java index 0fcfeddc..967527ca 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/commands/ExerciseDownloadingCommand.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/ExerciseDownloadingCommand.java @@ -8,7 +8,7 @@ import fi.helsinki.cs.tmc.core.domain.Progress; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.exceptions.ExerciseDownloadFailedException; -import fi.helsinki.cs.tmc.core.exceptions.ExtractingExericeFailedException; +import fi.helsinki.cs.tmc.core.exceptions.ExtractingExerciseFailedException; import fi.helsinki.cs.tmc.core.exceptions.TmcCoreException; import fi.helsinki.cs.tmc.core.exceptions.TmcInterruptionException; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; @@ -75,7 +75,7 @@ protected void extractSolution(byte[] zip, Exercise exercise, Progress progress) + " to " + target, ex); - throw new ExtractingExericeFailedException(exercise, ex); + throw new ExtractingExerciseFailedException(exercise, ex); } finally { cleanUp(exerciseZipTemporaryPath); } @@ -117,7 +117,7 @@ protected void extractProject(byte[] zip, Exercise exercise, Progress progress) + " to " + target, ex); - throw new ExtractingExericeFailedException(exercise, ex); + throw new ExtractingExerciseFailedException(exercise, ex); } finally { cleanUp(exerciseZipTemporaryPath); } diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/GetCourseDetails.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/GetCourseDetails.java index 2da3908f..40bf7e98 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/commands/GetCourseDetails.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/GetCourseDetails.java @@ -3,6 +3,7 @@ import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; import fi.helsinki.cs.tmc.core.domain.Course; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; import fi.helsinki.cs.tmc.core.exceptions.TmcCoreException; import com.google.common.annotations.VisibleForTesting; @@ -46,6 +47,9 @@ public Course call() throws TmcCoreException, URISyntaxException { informObserver(1, "Course refresh completed successfully"); return result; } catch (Exception ex) { + if (ex instanceof NotLoggedInException) { + throw (NotLoggedInException)ex; + } logger.warn("Failed to get course details for course " + course.getName(), ex); informObserver(1, "Failed to refresh course"); throw new TmcCoreException("Failed to get course details", ex); diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/GetOrganizations.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/GetOrganizations.java new file mode 100644 index 00000000..1172e889 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/GetOrganizations.java @@ -0,0 +1,26 @@ +package fi.helsinki.cs.tmc.core.commands; + +import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.domain.Organization; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.List; + +public class GetOrganizations extends Command> { + + public GetOrganizations(ProgressObserver observer) { + super(observer); + } + + @VisibleForTesting + GetOrganizations(ProgressObserver observer, TmcServerCommunicationTaskFactory tmcServerCommunicationTaskFactory) { + super(observer, tmcServerCommunicationTaskFactory); + } + + @Override + public List call() throws Exception { + return tmcServerCommunicationTaskFactory.getOrganizationListTask(); + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/ListCourses.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/ListCourses.java index 32cf5fa3..5a8803c1 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/commands/ListCourses.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/ListCourses.java @@ -3,6 +3,7 @@ import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; import fi.helsinki.cs.tmc.core.domain.Course; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; import fi.helsinki.cs.tmc.core.exceptions.TmcCoreException; import fi.helsinki.cs.tmc.core.utilities.ServerErrorHelper; @@ -43,6 +44,9 @@ public List call() throws TmcCoreException { logger.debug("Successfully fetched course list"); return result; } catch (Exception ex) { + if (ex instanceof NotLoggedInException) { + throw (NotLoggedInException)ex; + } logger.info("Failed to fetch courses from the server", ex); informObserver(1, "Failed to fetch courses from the server"); throw new TmcCoreException("Failed to fetch courses from the server. \n" diff --git a/src/main/java/fi/helsinki/cs/tmc/core/commands/SubmitAdaptiveExerciseToSkillifier.java b/src/main/java/fi/helsinki/cs/tmc/core/commands/SubmitAdaptiveExerciseToSkillifier.java new file mode 100644 index 00000000..191616d9 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/commands/SubmitAdaptiveExerciseToSkillifier.java @@ -0,0 +1,101 @@ +package fi.helsinki.cs.tmc.core.commands; + +import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.domain.Exercise; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.domain.submission.AdaptiveSubmissionResult; +import fi.helsinki.cs.tmc.core.domain.submission.SubmissionResult; +import fi.helsinki.cs.tmc.core.exceptions.TmcCoreException; + +import com.google.common.annotations.VisibleForTesting; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.concurrent.Callable; + + +public class SubmitAdaptiveExerciseToSkillifier extends AbstractSubmissionCommand { + private static final Logger logger = LoggerFactory.getLogger(AbstractSubmissionCommand.class); + private static final int DEFAULT_POLL_INTERVAL = 1000 * 2; + + private Exercise exercise; + + public SubmitAdaptiveExerciseToSkillifier(ProgressObserver observer, Exercise exercise) { + super(observer); + this.exercise = exercise; + } + + @VisibleForTesting + SubmitAdaptiveExerciseToSkillifier( + ProgressObserver observer, + Exercise exercise, + TmcServerCommunicationTaskFactory tmcServerCommunicationTaskFactory) { + super(observer, tmcServerCommunicationTaskFactory); + this.exercise = exercise; + } + + + @Override + public SubmissionResult call() throws TmcCoreException { + logger.info("Submitting exercise {}", exercise.getName()); + informObserver(0, "Submitting exercise to server"); + + //Submit zipped project to skillifier + SubmissionResult submissionResult = + submitToSkillifier(exercise, new HashMap()); + + return submissionResult; + + //TODO? + //Wait for skillifier and sandbox to process the submission, fetch submission result after + /* + while (true) { + checkInterrupt(); + try { + Thread.sleep(DEFAULT_POLL_INTERVAL); + } catch (InterruptedException ex) { + logger.debug("Interrupted while sleeping", ex); + } + try { + logger.debug("Checking if skillifier is done processing submission"); + Callable submissionResultFetcher = + tmcServerCommunicationTaskFactory.getSubmissionFetchTask( + submissionResponse.submissionUrl); + + String submissionStatus = submissionResultFetcher.call(); + JsonElement submission = new JsonParser().parse(submissionStatus); + if (isProcessing(submission)) { + logger.debug("Skillifier not done, sleeping for {}", DEFAULT_POLL_INTERVAL); + informObserver(0.3, "Waiting for response from skillifier"); + Thread.sleep(DEFAULT_POLL_INTERVAL); + } else { + logger.debug("Skillifier done, parsing results"); + informObserver(0.6, "Reading submission result"); + + Gson gson = new GsonBuilder().create(); + SubmissionResult result = gson.fromJson(submissionStatus, AdaptiveSubmissionResult.class).toSubmissionResult(); + + logger.debug("Done parsing server response"); + informObserver(1, "Successfully read submission results"); + + return result; + } + } catch (Exception ex) { + informObserver(1, "Error while waiting for response from server"); + logger.warn("Error while updating submission status from server, continuing", ex); + } + } + */ + } + + + private boolean isProcessing(JsonElement submissionStatus) { + return submissionStatus.getAsJsonObject().get("status").getAsString().equals("processing"); + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/TmcServerCommunicationTaskFactory.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/TmcServerCommunicationTaskFactory.java index 02d9fcd7..c9658928 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/communication/TmcServerCommunicationTaskFactory.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/TmcServerCommunicationTaskFactory.java @@ -2,16 +2,25 @@ import fi.helsinki.cs.tmc.core.communication.http.HttpTasks; import fi.helsinki.cs.tmc.core.communication.http.UriUtils; +import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth; +import fi.helsinki.cs.tmc.core.communication.serialization.AdaptiveExerciseParser; import fi.helsinki.cs.tmc.core.communication.serialization.ByteArrayGsonSerializer; import fi.helsinki.cs.tmc.core.communication.serialization.CourseInfoParser; import fi.helsinki.cs.tmc.core.communication.serialization.CourseListParser; +import fi.helsinki.cs.tmc.core.communication.serialization.ExerciseListParser; import fi.helsinki.cs.tmc.core.communication.serialization.ReviewListParser; +import fi.helsinki.cs.tmc.core.communication.serialization.SkillListParser; import fi.helsinki.cs.tmc.core.configuration.TmcSettings; import fi.helsinki.cs.tmc.core.domain.Course; import fi.helsinki.cs.tmc.core.domain.Exercise; +import fi.helsinki.cs.tmc.core.domain.OauthCredentials; +import fi.helsinki.cs.tmc.core.domain.Organization; import fi.helsinki.cs.tmc.core.domain.Review; +import fi.helsinki.cs.tmc.core.domain.Skill; +import fi.helsinki.cs.tmc.core.domain.Theme; import fi.helsinki.cs.tmc.core.domain.submission.FeedbackAnswer; import fi.helsinki.cs.tmc.core.exceptions.FailedHttpResponseException; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; import fi.helsinki.cs.tmc.core.exceptions.ObsoleteClientException; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; import fi.helsinki.cs.tmc.core.utilities.JsonMaker; @@ -24,6 +33,10 @@ import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; +import org.apache.commons.io.IOUtils; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; @@ -35,6 +48,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.zip.GZIPOutputStream; /** @@ -42,32 +57,45 @@ */ public class TmcServerCommunicationTaskFactory { - public static final int API_VERSION = 7; + private static final Logger LOG = Logger.getLogger( + TmcServerCommunicationTaskFactory.class.getName()); + public static final int API_VERSION = 8; private TmcSettings settings; + private Oauth oauth; + private AdaptiveExerciseParser adaptiveExerciseParser; private CourseListParser courseListParser; private CourseInfoParser courseInfoParser; + private ExerciseListParser exerciseListParser; private ReviewListParser reviewListParser; private String clientVersion; + private SkillListParser skillListParser; public TmcServerCommunicationTaskFactory() { - this(TmcSettingsHolder.get()); + this(TmcSettingsHolder.get(), Oauth.getInstance()); } - public TmcServerCommunicationTaskFactory(TmcSettings settings) { - this(settings, new CourseListParser(), new CourseInfoParser(), new ReviewListParser()); + public TmcServerCommunicationTaskFactory(TmcSettings settings, Oauth oauth) { + this(settings, oauth, new AdaptiveExerciseParser(), new CourseListParser(), + new CourseInfoParser(), new ReviewListParser()); } public TmcServerCommunicationTaskFactory( - TmcSettings settings, - CourseListParser courseListParser, - CourseInfoParser courseInfoParser, - ReviewListParser reviewListParser) { + TmcSettings settings, + Oauth oauth, + AdaptiveExerciseParser adaptiveExerciseParser, + CourseListParser courseListParser, + CourseInfoParser courseInfoParser, + ReviewListParser reviewListParser) { this.settings = settings; + this.oauth = oauth; + this.adaptiveExerciseParser = adaptiveExerciseParser; this.courseListParser = courseListParser; this.courseInfoParser = courseInfoParser; this.reviewListParser = reviewListParser; this.clientVersion = getClientVersion(); + this.exerciseListParser = new ExerciseListParser(); + this.skillListParser = new SkillListParser(); } private static String getClientVersion() { @@ -78,46 +106,81 @@ public void setSettings(TmcSettings settings) { this.settings = settings; } - private URI getCourseListUrl() { + /** + * Returns a Callable that calls the given Callable. + *

+ * If the call fails once, the oauth token is refreshed and the call is done again.

+ * @param return type of the callable + * @param callable Callable to be wrapped + * @return The given Callable wrapped in another Callable + */ + private Callable wrapWithNotLoggedInException(final Callable callable) { + return new Callable() { + @Override + public T call() throws Exception { + try { + return callable.call(); + } catch (FailedHttpResponseException e) { + LOG.log(Level.WARNING, + "Communication with the server failed!"); + throw new NotLoggedInException(); + } + } + }; + } + + private URI getCourseListUrl() + throws OAuthSystemException, OAuthProblemException, NotLoggedInException { String serverAddress = settings.getServerAddress(); String url; + // "/core/org/" => "/core/org" + String urlLastPart = "api/v" + API_VERSION + "/core/org" + settings.getOrganization() + "/courses.json"; if (serverAddress.endsWith("/")) { - url = settings.getServerAddress() + "courses.json"; + url = serverAddress + urlLastPart; } else { - url = settings.getServerAddress() + "/courses.json"; + url = serverAddress + "/" + urlLastPart; } return addApiCallQueryParameters(URI.create(url)); } - private URI addApiCallQueryParameters(URI url) { - url = UriUtils.withQueryParam(url, "api_version", "" + API_VERSION); + private URI addApiCallQueryParameters(URI url) throws NotLoggedInException { url = UriUtils.withQueryParam(url, "client", settings.clientName()); url = UriUtils.withQueryParam(url, "client_version", clientVersion); + url = UriUtils.withQueryParam(url, "access_token", oauth.getToken()); return url; } - private HttpTasks createHttpTasks() { - return new HttpTasks().setCredentials(settings.getUsername(), settings.getPassword()); - } - - public boolean hasEnoughSettings() { - return !settings.getUsername().isEmpty() - && !settings.getPassword().isEmpty() - && !settings.getServerAddress().isEmpty(); + public URI getSkillifierUrl(String addition) { + if (!addition.isEmpty()) { + return URI.create("http://localhost:3000/" + addition); + } + return URI.create("http://localhost:3000/"); } - public boolean needsOnlyPassword() { - return !settings.getUsername().isEmpty() - && settings.getPassword().isEmpty() - && !settings.getServerAddress().isEmpty(); + public Callable getAdaptiveExercise(final Theme theme, final Course course) + throws OAuthSystemException, OAuthProblemException, NotLoggedInException { + return wrapWithNotLoggedInException(new Callable() { + @Override + public Exercise call() throws Exception { + try { + Callable download = new HttpTasks() + .getForText(getSkillifierUrl(course.getName() + "/" + theme.getName() + "/next.json?token=" + oauth.getToken())); + String json = download.call(); + return adaptiveExerciseParser.parseFromJson(json); + } catch (Exception ex) { + LOG.log(Level.WARNING, "Downloading and parsing adaptive exercise URL failed."); + return null; + } + } + }); } public Callable> getDownloadingCourseListTask() { - final Callable download = createHttpTasks().getForText(getCourseListUrl()); - return new Callable>() { + return wrapWithNotLoggedInException(new Callable>() { @Override public List call() throws Exception { try { + Callable download = new HttpTasks().getForText(getCourseListUrl()); String text = download.call(); return courseListParser.parseFromJson(text); } catch (FailedHttpResponseException ex) { @@ -125,35 +188,65 @@ public List call() throws Exception { } //TODO: Cancellable? } - }; + }); } - public Callable getFullCourseInfoTask(Course courseStub) { - URI url = addApiCallQueryParameters(courseStub.getDetailsUrl()); - final Callable download = createHttpTasks().getForText(url); - return new Callable() { + public Callable getFullCourseInfoTask(final Course courseStub) { + return wrapWithNotLoggedInException(new Callable() { @Override public Course call() throws Exception { try { - String text = download.call(); - return courseInfoParser.parseFromJson(text); + URI serverUrl = addApiCallQueryParameters(courseStub.getDetailsUrl()); + Course returnedFromServer = getCourseInfo(serverUrl); + returnedFromServer.generateThemes(); + try { + URI skillifierExercisesUrl = getSkillifierUrl("courses/" + courseStub.getName() + "/uexercises?token=" + oauth.getToken()); + List returnedFromSkillifier = getExerciseList(skillifierExercisesUrl); + returnedFromServer.getExercises().addAll(returnedFromSkillifier); + + URI skillifierSkillsUrl = getSkillifierUrl("courses/" + courseStub.getName() + "/skills?token=" + oauth.getToken()); + List skillsFromSkillifier = getSkillList(skillifierSkillsUrl); + returnedFromServer.addSkillsToThemes(skillsFromSkillifier); + } catch (Exception e) { + LOG.log(Level.WARNING, "Downloading adaptive exercise info from skillifier failed."); + } + + return returnedFromServer; } catch (FailedHttpResponseException ex) { return checkForObsoleteClient(ex); } } //TODO: Cancellable? - }; + }); + } + + private List getSkillList(URI uri) throws Exception { + final Callable downloadFromServer = new HttpTasks().getForText(uri); + String jsonFromServer = downloadFromServer.call(); + return skillListParser.parseFromJson(jsonFromServer); + } + + private List getExerciseList(URI uri) throws Exception { + final Callable downloadFromServer = new HttpTasks().getForText(uri); + String jsonFromServer = downloadFromServer.call(); + return exerciseListParser.parseFromJson(jsonFromServer); } - public Callable getUnlockingTask(Course course) { - Map params = Collections.emptyMap(); - final Callable download = - createHttpTasks().postForText(getUnlockUrl(course), params); - return new Callable() { + private Course getCourseInfo(URI uri) throws Exception { + final Callable downloadFromServer = new HttpTasks().getForText(uri); + String jsonFromServer = downloadFromServer.call(); + return courseInfoParser.parseFromJson(jsonFromServer); + } + + public Callable getUnlockingTask(final Course course) { + final Map params = Collections.emptyMap(); + return wrapWithNotLoggedInException(new Callable() { @Override public Void call() throws Exception { try { + final Callable download = new HttpTasks() + .postForText(getUnlockUrl(course), params); download.call(); return null; } catch (FailedHttpResponseException ex) { @@ -162,67 +255,104 @@ public Void call() throws Exception { } //TODO: Cancellable? - }; + }); } - private URI getUnlockUrl(Course course) { + private URI getUnlockUrl(Course course) throws NotLoggedInException { return addApiCallQueryParameters(course.getUnlockUrl()); } + public Callable getDownloadingAdaptiveExerciseZipTask(Exercise exercise) throws NotLoggedInException { + exercise.setZipUrl(URI.create(exercise.getZipUrl() + "?token=" + oauth.getToken())); + exercise.setDownloadUrl(exercise.getZipUrl()); + return getDownloadingExerciseZipTask(exercise); + } + public Callable getDownloadingExerciseZipTask(Exercise exercise) { URI zipUrl = exercise.getDownloadUrl(); - return createHttpTasks().getForBinary(zipUrl); + return new HttpTasks().getForBinary(zipUrl); } public Callable getDownloadingExerciseSolutionZipTask(Exercise exercise) { URI zipUrl = exercise.getSolutionDownloadUrl(); - return createHttpTasks().getForBinary(zipUrl); + return new HttpTasks().getForBinary(zipUrl); } + public Callable getSubmittingExerciseToSkillifierTask( + final Exercise exercise, final byte[] byteToSend, Map extraParams) { + + final Map params = new LinkedHashMap<>(); + params.put("client_time", "" + (System.currentTimeMillis() / 1000L)); + params.put("client_nanotime", "" + System.nanoTime()); + params.putAll(extraParams); + + return wrapWithNotLoggedInException(new Callable() { + @Override + public String call() throws Exception { + String response; + try { + final URI submitUrl = getSkillifierUrl("/submitZip?token=" + oauth.getToken()); + final Callable upload = new HttpTasks() + .uploadRawDataForTextDownload(submitUrl, params, + byteToSend); + response = upload.call(); + } catch (FailedHttpResponseException ex) { + return checkForObsoleteClient(ex); + } + return response; + } + + //TODO: Cancellable? + }); + } + + public Callable getSubmittingExerciseTask( final Exercise exercise, final byte[] sourceZip, Map extraParams) { - final URI submitUrl = addApiCallQueryParameters(exercise.getReturnUrl()); - Map params = new LinkedHashMap<>(); + final Map params = new LinkedHashMap<>(); params.put("client_time", "" + (System.currentTimeMillis() / 1000L)); params.put("client_nanotime", "" + System.nanoTime()); params.putAll(extraParams); - final Callable upload = - createHttpTasks() - .uploadFileForTextDownload( - submitUrl, params, "submission[file]", sourceZip); - - return new Callable() { + return wrapWithNotLoggedInException(new Callable() { @Override public SubmissionResponse call() throws Exception { String response; try { + final URI submitUrl = addApiCallQueryParameters(exercise.getReturnUrl()); + final Callable upload = new HttpTasks() + .uploadFileForTextDownload(submitUrl, params, + "submission[file]", sourceZip); response = upload.call(); } catch (FailedHttpResponseException ex) { return checkForObsoleteClient(ex); } JsonObject respJson = new JsonParser().parse(response).getAsJsonObject(); - if (respJson.get("error") != null) { - throw new RuntimeException( - "Server responded with error: " + respJson.get("error")); - } else if (respJson.get("submission_url") != null) { - try { - URI submissionUrl = new URI(respJson.get("submission_url").getAsString()); - URI pasteUrl = new URI(respJson.get("paste_url").getAsString()); - return new SubmissionResponse(submissionUrl, pasteUrl); - } catch (Exception e) { - throw new RuntimeException( - "Server responded with malformed " + "submission url"); - } - } else { - throw new RuntimeException("Server returned unknown response"); - } + return getSubmissionResponse(respJson); } //TODO: Cancellable? - }; + }); + } + + private SubmissionResponse getSubmissionResponse(JsonObject respJson) { + if (respJson.get("error") != null) { + throw new RuntimeException( + "Server responded with error: " + respJson.get("error")); + } else if (respJson.get("submission_url") != null) { + try { + URI submissionUrl = new URI(respJson.get("submission_url").getAsString()); + URI pasteUrl = new URI(respJson.get("paste_url").getAsString()); + return new SubmissionResponse(submissionUrl, pasteUrl); + } catch (Exception e) { + throw new RuntimeException( + "Server responded with malformed " + "submission url"); + } + } else { + throw new RuntimeException("Server returned unknown response"); + } } public static class SubmissionResponse { @@ -236,17 +366,17 @@ public SubmissionResponse(URI submissionUrl, URI pasteUrl) { } } - public Callable getSubmissionFetchTask(URI submissionUrl) { - return createHttpTasks().getForText(submissionUrl); + public Callable getSubmissionFetchTask(URI submissionUrl) throws NotLoggedInException { + return new HttpTasks().getForText(addApiCallQueryParameters(submissionUrl)); } - public Callable> getDownloadingReviewListTask(Course course) { - URI url = addApiCallQueryParameters(course.getReviewsUrl()); - final Callable download = createHttpTasks().getForText(url); - return new Callable>() { + public Callable> getDownloadingReviewListTask(final Course course) { + return wrapWithNotLoggedInException(new Callable>() { @Override public List call() throws Exception { try { + URI url = addApiCallQueryParameters(course.getReviewsUrl()); + final Callable download = new HttpTasks().getForText(url); String text = download.call(); return reviewListParser.parseFromJson(text); } catch (FailedHttpResponseException ex) { @@ -255,12 +385,11 @@ public List call() throws Exception { } //TODO: Cancellable? - }; + }); } - public Callable getMarkingReviewAsReadTask(Review review, boolean read) { - URI url = addApiCallQueryParameters(URI.create(review.getUpdateUrl() + ".json")); - Map params = new HashMap<>(); + public Callable getMarkingReviewAsReadTask(final Review review, boolean read) { + final Map params = new HashMap<>(); params.put("_method", "put"); if (read) { params.put("mark_as_read", "1"); @@ -268,22 +397,22 @@ public Callable getMarkingReviewAsReadTask(Review review, boolean read) { params.put("mark_as_unread", "1"); } - final Callable task = createHttpTasks().postForText(url, params); - return new Callable() { + return wrapWithNotLoggedInException(new Callable() { @Override public Void call() throws Exception { + URI url = addApiCallQueryParameters(URI.create(review.getUpdateUrl() + ".json")); + final Callable task = new HttpTasks().postForText(url, params); task.call(); return null; } //TODO: Cancellable? - }; + }); } - public Callable getFeedbackAnsweringJob(URI answerUrl, List answers) { - final URI submitUrl = addApiCallQueryParameters(answerUrl); - - Map params = new HashMap<>(); + public Callable getFeedbackAnsweringJob(final URI answerUrl, + List answers) { + final Map params = new HashMap<>(); for (int i = 0; i < answers.size(); ++i) { String keyPrefix = "answers[" + i + "]"; FeedbackAnswer answer = answers.get(i); @@ -291,12 +420,13 @@ public Callable getFeedbackAnsweringJob(URI answerUrl, List upload = createHttpTasks().postForText(submitUrl, params); - - return new Callable() { + return wrapWithNotLoggedInException(new Callable() { @Override public String call() throws Exception { try { + final URI submitUrl = addApiCallQueryParameters(answerUrl); + final Callable upload = new HttpTasks() + .postForText(submitUrl, params); return upload.call(); } catch (FailedHttpResponseException ex) { return checkForObsoleteClient(ex); @@ -304,35 +434,67 @@ public String call() throws Exception { } //TODO: Cancellable? - }; + }); } - public Callable getSendEventLogJob(URI spywareServerUrl, List events) { - - Map extraHeaders = new LinkedHashMap<>(); + public Callable getSendEventLogJob(final URI spywareServerUrl, + List events) throws NotLoggedInException { + final Map extraHeaders = new LinkedHashMap<>(); extraHeaders.put("X-Tmc-Version", "1"); extraHeaders.put("X-Tmc-Username", settings.getUsername()); - extraHeaders.put("X-Tmc-Password", settings.getPassword()); + extraHeaders.put("X-Tmc-SESSION-ID", oauth.getToken()); - byte[] data; + final byte[] data; try { data = eventListToPostBody(events); } catch (IOException e) { throw new RuntimeException(e); } - URI url = addApiCallQueryParameters(spywareServerUrl); - final Callable upload = createHttpTasks().rawPostForText(url, data, extraHeaders); - - return new Callable() { + return wrapWithNotLoggedInException(new Callable() { @Override public Object call() throws Exception { + URI url = addApiCallQueryParameters(spywareServerUrl); + final Callable upload = new HttpTasks() + .rawPostForText(url, data, extraHeaders); upload.call(); return null; } //TODO: Cancellable? - }; + }); + } + + public Callable getOauthCredentialsTask() throws IOException { + URI credentialsUrl; + if (settings.getServerAddress().endsWith("/")) { + credentialsUrl = URI.create( + settings.getServerAddress() + "api/v" + API_VERSION + + "/application/" + settings.clientName() + "/credentials"); + } else { + credentialsUrl = URI.create( + settings.getServerAddress() + "/api/v" + API_VERSION + + "/application/" + settings.clientName() + "/credentials"); + } + OauthCredentials credentials = + new Gson().fromJson( + IOUtils.toString(credentialsUrl.toURL()), OauthCredentials.class); + settings.setOauthCredentials(credentials); + return null; + } + + public List getOrganizationListTask() throws IOException { + String url; + String serverAddress = settings.getServerAddress(); + String urlLastPart = "api/v" + API_VERSION + "/org"; + if (serverAddress.endsWith("/")) { + url = settings.getServerAddress() + urlLastPart; + } else { + url = serverAddress + "/" + urlLastPart; + } + URI organizationUrl = URI.create(url); + List organizations = new Gson().fromJson(IOUtils.toString(organizationUrl.toURL()), new TypeToken>(){}.getType()); + return organizations; } private byte[] eventListToPostBody(List events) throws IOException { diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/http/HttpTasks.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/http/HttpTasks.java index c80adc0a..a6c71ce8 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/communication/http/HttpTasks.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/http/HttpTasks.java @@ -5,7 +5,6 @@ import com.google.gson.Gson; import org.apache.http.NameValuePair; -import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; @@ -36,19 +35,12 @@ public class HttpTasks { ContentType.create("text/plain", "utf-8"); private static final Gson gson = new Gson(); - private UsernamePasswordCredentials credentials = null; - - public HttpTasks setCredentials(String username, String password) { - this.credentials = new UsernamePasswordCredentials(username, password); - return this; - } - private HttpRequestExecutor createExecutor(URI url) { - return new HttpRequestExecutor(url).setCredentials(credentials); + return new HttpRequestExecutor(url); } private HttpRequestExecutor createExecutor(HttpPost request) { - return new HttpRequestExecutor(request).setCredentials(credentials); + return new HttpRequestExecutor(request); } /** @@ -99,6 +91,12 @@ public Callable uploadFileForTextDownload( return downloadToText(createExecutor(request)); } + public Callable uploadRawDataForTextDownload( + URI url, Map params, byte[] data) { + HttpPost request = makeRawPostRequest(url, data, params); + return downloadToText(createExecutor(request)); + } + private Callable downloadToBinary(final HttpRequestExecutor download) { return new Callable() { @Override diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/oauth2/Oauth.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/oauth2/Oauth.java new file mode 100644 index 00000000..3c7cffc3 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/oauth2/Oauth.java @@ -0,0 +1,99 @@ +package fi.helsinki.cs.tmc.core.communication.oauth2; + +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.OauthCredentials; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; +import fi.helsinki.cs.tmc.core.exceptions.TmcCoreException; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; + +import com.google.common.base.Optional; +import org.apache.oltu.oauth2.client.OAuthClient; +import org.apache.oltu.oauth2.client.URLConnectionClient; +import org.apache.oltu.oauth2.client.request.OAuthClientRequest; +import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.message.types.GrantType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Oauth { + + private static Oauth oauth; + private static final Logger log = LoggerFactory.getLogger(Oauth.class); + + private TmcSettings settings; + /** + * Returns the Oauth instance. + * + * @return single oauth instance + */ + public static synchronized Oauth getInstance() { + if (oauth == null) { + oauth = new Oauth(); + } + return oauth; + } + + protected Oauth() { + settings = TmcSettingsHolder.get(); + } + + /** + * Returns the oauth token. + * + *

+ * Gets the token from cache or uses the known flow to fetch the it.

+ * + * @return oauth token + * @throws TmcCoreException when an oauth token hasn't been fetched yet + */ + public String getToken() throws NotLoggedInException { + if (!hasToken()) { + throw new NotLoggedInException(); + } + return settings.getToken().get(); + } + + /** + * Returns true if the token is already known. + * + * @return has token + */ + public boolean hasToken() { + return settings.getToken().isPresent(); + } + + /** + * Fetches a new oauth token from server using settings' parameters in request. + * @param password for fetching correct token + * @throws OAuthSystemException an error occurred with getting token + * @throws OAuthProblemException an error occurred with getting token + */ + public void fetchNewToken(String password) throws OAuthSystemException, OAuthProblemException { + log.info("Fetching new oauth token from server"); + + String oauthTokenUrl; + if (settings.getServerAddress().endsWith("/")) { + oauthTokenUrl = settings.getServerAddress() + "oauth/token"; + } else { + oauthTokenUrl = settings.getServerAddress() + "/oauth/token"; + } + + OauthCredentials credentials = settings.getOauthCredentials(); + OAuthClientRequest request = OAuthClientRequest + .tokenLocation(oauthTokenUrl) + .setGrantType(GrantType.PASSWORD) + .setClientId(credentials.getOauthApplicationId()) + .setClientSecret(credentials.getOauthSecret()) + .setUsername(settings.getUsername()) + .setPassword(password) + .setRedirectURI("urn:ietf:wg:oauth:2.0:oob") + .buildQueryMessage(); + OAuthClient client = new OAuthClient(new URLConnectionClient()); + String token = client.accessToken(request, OAuthJSONAccessTokenResponse.class) + .getAccessToken(); + settings.setToken(Optional.of(token)); + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/AdaptiveExerciseParser.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/AdaptiveExerciseParser.java new file mode 100644 index 00000000..2fffa673 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/AdaptiveExerciseParser.java @@ -0,0 +1,46 @@ +package fi.helsinki.cs.tmc.core.communication.serialization; + +import fi.helsinki.cs.tmc.core.domain.Exercise; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; + +public class AdaptiveExerciseParser { + + private static final Logger logger = LoggerFactory.getLogger(AdaptiveExerciseParser.class); + + public Exercise parseFromJson(String json) { + if (json == null) { + throw new NullPointerException("Json string is null"); + } + if (json.trim().isEmpty()) { + throw new IllegalArgumentException("Empty input"); + } + if (json.contains("\"available\":\"false\"")) { + logger.info("The exercise is not available."); + return null; + } + try { + Gson gson = new GsonBuilder().create(); + Exercise adaptive = gson.fromJson(json, Exercise.class); + adaptive.setAdaptive(true); + if (adaptive.isAvailable()) { + String parsedUrl = adaptive.getZipUrl().toString(); + adaptive.setZipUrl(URI.create("http://" + parsedUrl)); + return adaptive; + } + logger.info("The gson parsed adaptive exercise is not available."); + return null; + } catch (RuntimeException ex) { + logger.warn("Failed to parse an adaptive course from URL", ex); + throw new RuntimeException("Failed to parse an adaptive course from URL: " + + ex.getMessage(), ex); + } + } + +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/CourseListParser.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/CourseListParser.java index fca7cd9b..e5d92a80 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/CourseListParser.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/CourseListParser.java @@ -15,11 +15,6 @@ public class CourseListParser { - private static class CourseListContainer { - public int apiVersion; - public Course[] courses; - } - private static final Logger logger = LoggerFactory.getLogger(CourseListParser.class); public List parseFromJson(String json) { @@ -35,7 +30,7 @@ public List parseFromJson(String json) { .registerTypeAdapter(Date.class, new CustomDateDeserializer()) .create(); - Course[] courses = gson.fromJson(json, CourseListContainer.class).courses; + Course[] courses = gson.fromJson(json, Course[].class); List courseList = new ArrayList<>(); for (Course course : courses) { diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/ExerciseListParser.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/ExerciseListParser.java new file mode 100644 index 00000000..ada00ad4 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/ExerciseListParser.java @@ -0,0 +1,45 @@ +package fi.helsinki.cs.tmc.core.communication.serialization; + +import fi.helsinki.cs.tmc.core.domain.Exercise; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +public class ExerciseListParser { + + private static final Logger logger = LoggerFactory.getLogger(CourseListParser.class); + + public List parseFromJson(String json) { + if (json == null) { + throw new NullPointerException("Json string is null"); + } + if (json.trim().isEmpty()) { + throw new IllegalArgumentException("Empty input"); + } + try { + Gson gson = + new GsonBuilder() + .registerTypeAdapter(Date.class, new CustomDateDeserializer()) + .create(); + + Exercise[] exercises = gson.fromJson(json, Exercise[].class); + List exerciseList = new ArrayList<>(); + exerciseList.addAll(Arrays.asList(exercises)); + for (Exercise exercise : exerciseList) { + exercise.setAdaptive(true); + } + return exerciseList; + } catch (RuntimeException ex) { + logger.warn("Failed to parse exercises info", ex); + throw new RuntimeException("Failed to parse adaptive exercise list: " + ex.getMessage(), ex); + } + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/ReviewListParser.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/ReviewListParser.java index ec4cf97a..0293c032 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/ReviewListParser.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/ReviewListParser.java @@ -14,11 +14,6 @@ public class ReviewListParser { - private static class ReviewListContainer { - public int apiVersion; - public Review[] reviews; - } - private static final Logger logger = LoggerFactory.getLogger(ReviewListParser.class); public List parseFromJson(String json) { @@ -36,7 +31,7 @@ public List parseFromJson(String json) { .registerTypeAdapter(Date.class, new CustomDateDeserializer()) .create(); - Review[] reviews = gson.fromJson(json, ReviewListContainer.class).reviews; + Review[] reviews = gson.fromJson(json, Review[].class); return Arrays.asList(reviews); } catch (RuntimeException ex) { logger.warn("Failed to parse review list", ex); diff --git a/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/SkillListParser.java b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/SkillListParser.java new file mode 100644 index 00000000..94a365d4 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/communication/serialization/SkillListParser.java @@ -0,0 +1,41 @@ +package fi.helsinki.cs.tmc.core.communication.serialization; + +import fi.helsinki.cs.tmc.core.domain.Skill; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * Created by markovai on 16.6.2017. + */ +public class SkillListParser { + private static final Logger logger = LoggerFactory.getLogger(SkillListParser.class); + + public List parseFromJson(String json) { + if (json == null) { + throw new NullPointerException("Json string is null"); + } + if (json.trim().isEmpty()) { + throw new IllegalArgumentException("Empty input"); + } + try { + Gson gson = + new GsonBuilder() + .registerTypeAdapter(Date.class, new CustomDateDeserializer()) + .create(); + + Skill[] skills = gson.fromJson(json, Skill[].class); + return Arrays.asList(skills); + } catch (RuntimeException ex) { + logger.warn("Failed to parse themes info", ex); + throw new RuntimeException("Failed to parse adaptive theme list: " + ex.getMessage(), ex); + } + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/configuration/TmcSettings.java b/src/main/java/fi/helsinki/cs/tmc/core/configuration/TmcSettings.java index fc688973..e6b22332 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/configuration/TmcSettings.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/configuration/TmcSettings.java @@ -1,6 +1,7 @@ package fi.helsinki.cs.tmc.core.configuration; import fi.helsinki.cs.tmc.core.domain.Course; +import fi.helsinki.cs.tmc.core.domain.OauthCredentials; import com.google.common.annotations.Beta; import com.google.common.base.Optional; @@ -14,7 +15,14 @@ public interface TmcSettings { String getServerAddress(); - String getPassword(); + void setServerAddress(String address); + + /** + * Used for old login credentials, new ones use oauth. + */ + Optional getPassword(); + + void setPassword(Optional password); String getUsername(); @@ -25,8 +33,6 @@ public interface TmcSettings { Optional getCurrentCourse(); - String apiVersion(); - String clientName(); String clientVersion(); @@ -58,4 +64,16 @@ public interface TmcSettings { String hostProgramVersion(); boolean getSendDiagnostics(); + + OauthCredentials getOauthCredentials(); + + void setOauthCredentials(OauthCredentials credentials); + + void setToken(Optional token); + + Optional getToken(); + + String getOrganization(); + + void setOrganization(String organization); } diff --git a/src/main/java/fi/helsinki/cs/tmc/core/domain/Course.java b/src/main/java/fi/helsinki/cs/tmc/core/domain/Course.java index 29074b9f..6ad58ae8 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/domain/Course.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/domain/Course.java @@ -4,15 +4,21 @@ import java.net.URI; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class Course { private int id; private String name; + private String title; + private String description; private List exercises; + private List themes; + @SerializedName("details_url") private URI detailsUrl; @@ -65,6 +71,63 @@ public void setExercises(List exercises) { this.exercises = exercises; } + public List getThemes() { + return themes; + } + + public List getSkillsByTheme(String themeName) { + for (Theme theme : themes) { + if (theme.getName().equals(themeName)) { + return theme.getSkills(); + } + } + return new ArrayList<>(); + } + + /* + public void setThemes(List themes) { + this.themes = themes; + for (final Theme theme : themes) { + //exercises.stream().filter(theme::shouldContain).collect(Collectors.toList()); + List l = new ArrayList<>(); + for (Exercise ex : exercises) { + if (theme.shouldContain(ex)) { + l.add(ex); + } + } + theme.setExercises(l); + } + } + */ + + public void generateThemes() { + themes = new ArrayList<>(); + Map themeMap = new HashMap<>(); + for (Exercise ex : exercises) { + addExerciseToTheme(themeMap, ex); + } + } + + private void addExerciseToTheme(Map themeMap, Exercise ex) { + String themeName = ex.getName().split("-")[0]; + Theme theme = themeMap.get(themeName); + if (theme == null) { + theme = new Theme(themeName); + themeMap.put(themeName, theme); + themes.add(theme); + } + theme.addExercise(ex); + } + + public List getExercisesByTheme(String themeName) { + for (Theme theme : themes) { + if (theme.getName().equals(themeName)) { + return theme.getExercises(); + } + } + return new ArrayList<>(); + } + public int getId() { return id; } @@ -81,6 +144,22 @@ public void setName(String name) { this.name = name; } + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + public URI getDetailsUrl() { return detailsUrl; } @@ -150,4 +229,25 @@ not use toString() to present Course objects */ return name; } + + public void setThemes(List themes) { + this.themes = themes; + } + + public void addSkillsToThemes(List skillsFromSkillifier) { + for (Skill skill : skillsFromSkillifier) { + String themeName = skill.getThemeName(); + Theme skillTheme = null; + for (Theme theme : themes) { + if (theme.getName().equals(themeName)) { + skillTheme = theme; + break; + } + } + if (skillTheme == null) { + themes.add(skillTheme = new Theme(themeName)); + } + skillTheme.addSkill(skill); + } + } } diff --git a/src/main/java/fi/helsinki/cs/tmc/core/domain/Exercise.java b/src/main/java/fi/helsinki/cs/tmc/core/domain/Exercise.java index 6dc0513b..633902ee 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/domain/Exercise.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/domain/Exercise.java @@ -25,6 +25,7 @@ public class Exercise implements Serializable { private int id; private String name; private boolean locked; + private boolean isAdaptive = false; @SerializedName("deadline_description") private String deadlineDescription; @@ -76,6 +77,8 @@ public class Exercise implements Serializable { @SerializedName("exercise_submissions_url") private URI exerciseSubmissionsUrl; + private boolean available; + public Exercise() {} public Exercise(String name) { @@ -258,6 +261,22 @@ public void setExerciseSubmissionsUrl(URI exerciseSubmissionsUrl) { this.exerciseSubmissionsUrl = exerciseSubmissionsUrl; } + public boolean isAdaptive() { + return isAdaptive; + } + + public void setAdaptive(boolean adaptive) { + isAdaptive = adaptive; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + public boolean isAvailable() { + return available; + } + private String courseName; // Zip contains folder for the exercise. diff --git a/src/main/java/fi/helsinki/cs/tmc/core/domain/OauthCredentials.java b/src/main/java/fi/helsinki/cs/tmc/core/domain/OauthCredentials.java new file mode 100644 index 00000000..530429f7 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/domain/OauthCredentials.java @@ -0,0 +1,39 @@ +package fi.helsinki.cs.tmc.core.domain; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +public class OauthCredentials implements Serializable { + + @SerializedName("application_id") + private String oauthApplicationId; + + @SerializedName("secret") + private String oauthSecret; + + public OauthCredentials() { + + } + + public OauthCredentials(String oauthApplicationId, String oauthSecret) { + this.oauthApplicationId = oauthApplicationId; + this.oauthSecret = oauthSecret; + } + + public void setOauthApplicationId(String oauthApplicationId) { + this.oauthApplicationId = oauthApplicationId; + } + + public String getOauthApplicationId() { + return oauthApplicationId; + } + + public void setOauthSecret(String oauthSecret) { + this.oauthSecret = oauthSecret; + } + + public String getOauthSecret() { + return oauthSecret; + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/domain/Organization.java b/src/main/java/fi/helsinki/cs/tmc/core/domain/Organization.java new file mode 100644 index 00000000..5b988d0c --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/domain/Organization.java @@ -0,0 +1,49 @@ +package fi.helsinki.cs.tmc.core.domain; + +import com.google.gson.annotations.SerializedName; + +public class Organization { + + @SerializedName("name") + private String name; + + @SerializedName("information") + private String information; + + @SerializedName("slug") + private String slug; + + @SerializedName("logo_path") + private String logoPath; + + @SerializedName("pinned") + private boolean pinned; + + public Organization(String name, String information, String slug, String logoPath, boolean pinned) { + this.name = name; + this.information = information; + this.slug = slug; + this.logoPath = logoPath; + this.pinned = pinned; + } + + public String getName() { + return name; + } + + public String getInformation() { + return information; + } + + public String getSlug() { + return slug; + } + + public String getLogoPath() { + return logoPath; + } + + public boolean isPinned() { + return pinned; + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/domain/Skill.java b/src/main/java/fi/helsinki/cs/tmc/core/domain/Skill.java new file mode 100644 index 00000000..bcf43bae --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/domain/Skill.java @@ -0,0 +1,42 @@ +package fi.helsinki.cs.tmc.core.domain; + +import java.io.Serializable; +import java.util.List; + +public class Skill implements Serializable{ + private List exercises; + private String name; + private double percentage; + public double mastery; + private String themeName; + + public Skill(String name) { + this.name = name; + percentage = 0.0; + mastery = 90.0; + } + + public void setExercises(List exercises) { + this.exercises = exercises; + } + + public String getName() { + return name; + } + + public double getPercentage() { + return percentage; + } + + public String getThemeName() { + return themeName; + } + + public boolean isMastered() { + return percentage >= mastery; + } + + public void setThemeName(String themeName) { + this.themeName = themeName; + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/domain/Theme.java b/src/main/java/fi/helsinki/cs/tmc/core/domain/Theme.java new file mode 100644 index 00000000..83dbaaa4 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/domain/Theme.java @@ -0,0 +1,62 @@ +package fi.helsinki.cs.tmc.core.domain; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class Theme implements Serializable{ + private List exercises; + private boolean unlocked = false; + private String name; + private List skills; + + public Theme(String name) { + this.name = name; + this.exercises = new ArrayList<>(); + this.skills = new ArrayList<>(); + } + + public List getExercises() { + return exercises; + } + + public List getSkills() { + return skills; + } + + public void setSkills(List skills) { + this.skills = skills; + } + + public void setExercises(List exercises) { + this.exercises = exercises; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setUnlocked(boolean unlocked) { + this.unlocked = unlocked; + } + + public boolean isUnlocked() { + return unlocked; + } + + public boolean shouldContain(Exercise exercise) { + return this.name.equals(exercise.getName().split("-")[0]); + } + + public void addExercise(Exercise exercise) { + exercises.add(exercise); + } + + public void addSkill(Skill skill) { + skills.add(skill); + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/domain/submission/AdaptiveSubmissionResult.java b/src/main/java/fi/helsinki/cs/tmc/core/domain/submission/AdaptiveSubmissionResult.java new file mode 100644 index 00000000..7258bc3d --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/domain/submission/AdaptiveSubmissionResult.java @@ -0,0 +1,36 @@ +package fi.helsinki.cs.tmc.core.domain.submission; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; + +/** + * Created by markovai on 24.5.2017. + */ +public class AdaptiveSubmissionResult { + public AdaptiveSubmissionResult() { + status = SubmissionResult.Status.ERROR; + error = null; + points = 0; + } + + + @SerializedName("error") + private final String error; // e.g. compile error + + @SerializedName("status") + private final SubmissionResult.Status status; + + @SerializedName("points") + private final int points; + + public SubmissionResult toSubmissionResult() { + SubmissionResult submissionResult = new SubmissionResult(); + submissionResult.setError(error); + submissionResult.setStatus(status); + ArrayList submissionPoints = new ArrayList<>(); + submissionPoints.add(String.valueOf(points)); + submissionResult.setPoints(submissionPoints); + return submissionResult; + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/exceptions/AuthenticationFailedException.java b/src/main/java/fi/helsinki/cs/tmc/core/exceptions/AuthenticationFailedException.java new file mode 100644 index 00000000..928b14b7 --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/exceptions/AuthenticationFailedException.java @@ -0,0 +1,8 @@ +package fi.helsinki.cs.tmc.core.exceptions; + +public class AuthenticationFailedException extends TmcCoreException { + + public AuthenticationFailedException(Exception ex) { + super("Authentication failed!", ex); + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExerciseDownloadFailedException.java b/src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExerciseDownloadFailedException.java index 691b1c8c..915988d1 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExerciseDownloadFailedException.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExerciseDownloadFailedException.java @@ -2,7 +2,7 @@ import fi.helsinki.cs.tmc.core.domain.Exercise; -import java.io.IOException; +import java.net.URI; public class ExerciseDownloadFailedException extends TmcCoreException { diff --git a/src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExtractingExericeFailedException.java b/src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExtractingExerciseFailedException.java similarity index 53% rename from src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExtractingExericeFailedException.java rename to src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExtractingExerciseFailedException.java index 88dc5af5..0d4b5ec3 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExtractingExericeFailedException.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/exceptions/ExtractingExerciseFailedException.java @@ -2,8 +2,8 @@ import fi.helsinki.cs.tmc.core.domain.Exercise; -public class ExtractingExericeFailedException extends TmcCoreException { - public ExtractingExericeFailedException(Exercise exercise, Exception ex) { +public class ExtractingExerciseFailedException extends TmcCoreException { + public ExtractingExerciseFailedException(Exercise exercise, Exception ex) { super("Extracting zip for " + exercise.getName() + " failed", ex); } } diff --git a/src/main/java/fi/helsinki/cs/tmc/core/exceptions/NotLoggedInException.java b/src/main/java/fi/helsinki/cs/tmc/core/exceptions/NotLoggedInException.java new file mode 100644 index 00000000..e82b9a0e --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/exceptions/NotLoggedInException.java @@ -0,0 +1,8 @@ +package fi.helsinki.cs.tmc.core.exceptions; + +public class NotLoggedInException extends TmcCoreException { + + public NotLoggedInException() { + super("Not logged in!"); + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/core/holders/TmcSettingsHolder.java b/src/main/java/fi/helsinki/cs/tmc/core/holders/TmcSettingsHolder.java index ddf01629..d646f6d1 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/holders/TmcSettingsHolder.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/holders/TmcSettingsHolder.java @@ -1,12 +1,23 @@ package fi.helsinki.cs.tmc.core.holders; +import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth; import fi.helsinki.cs.tmc.core.configuration.TmcSettings; import fi.helsinki.cs.tmc.core.exceptions.UninitializedHolderException; +import com.google.common.base.Optional; + +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public final class TmcSettingsHolder { private static TmcSettings settings; + private static final Logger logger = LoggerFactory.getLogger(TmcSettingsHolder.class); + private TmcSettingsHolder() {} public static synchronized TmcSettings get() { @@ -18,5 +29,20 @@ public static synchronized TmcSettings get() { public static synchronized void set(TmcSettings settings) { TmcSettingsHolder.settings = settings; + if (settings != null) { + migrateOldSettings(); + } + } + // TODO delete at some point + private static void migrateOldSettings() { + try { + Optional password = settings.getPassword(); + if (password.isPresent()) { + Oauth.getInstance().fetchNewToken(password.get()); + settings.setPassword(Optional.absent()); + } + } catch (OAuthSystemException | OAuthProblemException e) { + logger.warn("Settings migration failed."); + } } } diff --git a/src/main/java/fi/helsinki/cs/tmc/core/utilities/ExceptionTrackingCallable.java b/src/main/java/fi/helsinki/cs/tmc/core/utilities/ExceptionTrackingCallable.java index d5f9541b..364bdda4 100644 --- a/src/main/java/fi/helsinki/cs/tmc/core/utilities/ExceptionTrackingCallable.java +++ b/src/main/java/fi/helsinki/cs/tmc/core/utilities/ExceptionTrackingCallable.java @@ -3,6 +3,7 @@ import fi.helsinki.cs.tmc.core.communication.TmcBandicootCommunicationTaskFactory; import fi.helsinki.cs.tmc.core.configuration.TmcSettings; import fi.helsinki.cs.tmc.core.domain.bandicoot.Crash; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; import com.google.common.annotations.VisibleForTesting; @@ -33,7 +34,7 @@ public T call() throws Exception { try { return command.call(); } catch (Exception ex) { - if (settings.getSendDiagnostics()) { + if (settings.getSendDiagnostics() && !(ex instanceof NotLoggedInException)) { tmcBandicootCommunicationTaskFactory.sendCrash(new Crash(ex)).call(); } throw ex; diff --git a/src/main/java/fi/helsinki/cs/tmc/core/utilities/TmcServerAddressNormalizer.java b/src/main/java/fi/helsinki/cs/tmc/core/utilities/TmcServerAddressNormalizer.java new file mode 100644 index 00000000..99f16d2f --- /dev/null +++ b/src/main/java/fi/helsinki/cs/tmc/core/utilities/TmcServerAddressNormalizer.java @@ -0,0 +1,31 @@ +package fi.helsinki.cs.tmc.core.utilities; + +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.Organization; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; + +public class TmcServerAddressNormalizer { + + public static void normalize() { + TmcSettings tmcSettings = TmcSettingsHolder.get(); + String address = tmcSettings.getServerAddress(); + + if (!address.contains("/org/") && address.contains("/hy")) { + int last = address.lastIndexOf("/"); + tmcSettings.setServerAddress(address.substring(0, last)); + tmcSettings.setOrganization(address.substring(last + 1, address.length())); + } else if (!address.contains("/org/")) { + return; + } else { + String[] split = address.split("/org/"); + + tmcSettings.setServerAddress(split[0]); + + String organization = split[1]; + if (organization.charAt(organization.length() - 1) == '/') { + organization = organization.substring(0, organization.length()); + } + tmcSettings.setOrganization(organization); + } + } +} diff --git a/src/main/java/fi/helsinki/cs/tmc/spyware/EventSendBuffer.java b/src/main/java/fi/helsinki/cs/tmc/spyware/EventSendBuffer.java index b08e3a36..ba8b7f37 100644 --- a/src/main/java/fi/helsinki/cs/tmc/spyware/EventSendBuffer.java +++ b/src/main/java/fi/helsinki/cs/tmc/spyware/EventSendBuffer.java @@ -220,9 +220,9 @@ public void run() { "Sending {0} events to {1}", new Object[] {eventsToSend.size(), url}); - if (!tryToSend(eventsToSend, url)) { - shouldSendMore = false; - } + if (!tryToSend(eventsToSend, url)) { + shouldSendMore = false; + } } while (shouldSendMore); } @@ -258,11 +258,10 @@ private URI pickDestinationUrl() { } private boolean tryToSend(final ArrayList eventsToSend, final URI url) { - Callable task = serverAccess.getSendEventLogJob(url, eventsToSend); - // TODO: Should we still wrap this into bg task (future) try { + Callable task = serverAccess.getSendEventLogJob(url, eventsToSend); task.call(); } catch (Exception ex) { log.log(Level.INFO, "Sending failed", ex); diff --git a/src/test/java/fi/helsinki/cs/tmc/core/TmcCoreTest.java b/src/test/java/fi/helsinki/cs/tmc/core/TmcCoreTest.java new file mode 100644 index 00000000..40ffcae0 --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/TmcCoreTest.java @@ -0,0 +1,122 @@ +package fi.helsinki.cs.tmc.core; + +import static org.mockito.Mockito.doReturn; + +import fi.helsinki.cs.tmc.core.commands.GetCourseDetails; +import fi.helsinki.cs.tmc.core.commands.GetUnreadReviews; +import fi.helsinki.cs.tmc.core.commands.ListCourses; +import fi.helsinki.cs.tmc.core.commands.MarkReviewAsRead; +import fi.helsinki.cs.tmc.core.commands.SendFeedback; +import fi.helsinki.cs.tmc.core.commands.SendSpywareEvents; +import fi.helsinki.cs.tmc.core.commands.Submit; +import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth; +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.Course; +import fi.helsinki.cs.tmc.core.domain.Exercise; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.domain.Review; +import fi.helsinki.cs.tmc.core.domain.submission.FeedbackAnswer; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; +import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; +import fi.helsinki.cs.tmc.langs.util.TaskExecutor; +import fi.helsinki.cs.tmc.spyware.LoggableEvent; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import java.lang.reflect.Field; +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class TmcCoreTest { + + @Mock + ProgressObserver observer; + + @Spy + Course course = new Course(); + + @Mock + Review review; + + @Mock + Exercise exercise; + + @Spy + TmcSettings settings = new MockSettings(); + + @Mock + TaskExecutor tmcLangs; + + @Mock + Path path; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + TmcSettingsHolder.set(settings); + TmcLangsHolder.set(tmcLangs); + Locale locale = new Locale("a", "b", "c"); + doReturn(URI.create("testUrl")).when(course).getReviewsUrl(); + doReturn(URI.create("testUrl")).when(course).getDetailsUrl(); + doReturn(path).when(settings).getTmcProjectDirectory(); + doReturn(locale).when(settings).getLocale(); + doReturn(URI.create("testUrl")).when(exercise).getReturnUrl(); + } + + @After + public void tearDown() throws NoSuchFieldException, IllegalAccessException { + Field oauth = Oauth.class.getDeclaredField("oauth"); + oauth.setAccessible(true); + oauth.set(null, null); + } + + @Test (expected = NotLoggedInException.class) + public void commandListCoursesThrowsNotLoggedInExceptionWhenHasNoToken() throws Exception { + new ListCourses(observer).call(); + } + + @Test (expected = NotLoggedInException.class) + public void commandGetUnreadReviewsThrowsNotLoggedInExceptionWhenHasNoToken() throws Exception { + new GetUnreadReviews(observer, course).call(); + } + + @Test (expected = NotLoggedInException.class) + public void commandSendFeedbackThrowsNotLoggedInExceptionWhenHasNoToken() throws Exception { + new SendFeedback(observer, new ArrayList(), new URI("test")).call(); + } + + @Test (expected = NotLoggedInException.class) + public void commandGetCourseDetailsThrowsNotLoggedInExceptionWhenHasNoToken() throws Exception { + new GetCourseDetails(observer, course).call(); + } + + @Test (expected = NotLoggedInException.class) + public void commandMarkReviewsAsReadThrowsNotLoggedInExceptionWhenHasNoToken() + throws Exception { + new MarkReviewAsRead(observer, review).call(); + } + + @Test (expected = NotLoggedInException.class) + public void commandSendSpywareEventsThrowsNotLoggedInExceptionWhenHasNoToken() + throws Exception { + List spywareUrls = new ArrayList<>(); + spywareUrls.add(URI.create("test")); + course.setSpywareUrls(spywareUrls); + new SendSpywareEvents(observer, course, new ArrayList()).call(); + } + + @Test (expected = NotLoggedInException.class) + public void commandSubmitThrowsNotLoggedInExceptionWhenHasNoToken() throws Exception { + new Submit(observer, exercise).call(); + } +} diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/AuthenticateUserTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/AuthenticateUserTest.java new file mode 100644 index 00000000..a16c58f8 --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/AuthenticateUserTest.java @@ -0,0 +1,89 @@ +package fi.helsinki.cs.tmc.core.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth; +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.exceptions.AuthenticationFailedException; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; + +import com.google.common.base.Optional; + +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; + +import org.junit.Before; +import org.junit.Test; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.IOException; + +public class AuthenticateUserTest { + @Mock + ProgressObserver mockObserver; + + TmcSettings settings; + @Mock + Oauth oauth; + @Mock + TmcServerCommunicationTaskFactory tmcServerCommunicationTaskFactory; + + private Command command; + + @Before + public void setUp() throws OAuthProblemException, OAuthSystemException, IOException { + MockitoAnnotations.initMocks(this); + settings = new MockSettings(); + TmcSettingsHolder.set(settings); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocationOnMock) throws Throwable { + settings.setToken(Optional.of("testToken")); + return null; + } + }).when(oauth).fetchNewToken("password"); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocationOnMock) throws Throwable { + throw new OAuthSystemException(); + } + }).when(oauth).fetchNewToken("wrongPassword"); + when(tmcServerCommunicationTaskFactory.getOauthCredentialsTask()).thenReturn(null); + } + + @Test + public void testCallSucceeds() throws Exception { + command = new AuthenticateUser(mockObserver, "password", + oauth, tmcServerCommunicationTaskFactory); + command.call(); + assertTrue(settings.getToken().isPresent()); + assertEquals(settings.getToken().get(), "testToken"); + } + + @Test(expected = AuthenticationFailedException.class) + public void testCallFails() throws Exception { + command = new AuthenticateUser(mockObserver, "wrongPassword", + oauth, tmcServerCommunicationTaskFactory); + command.call(); + } + + @Test + public void callsTmcServerCommunicationTaskFactorysGetOauthCredentialsTask() throws Exception { + command = new AuthenticateUser(mockObserver, "password", + oauth, tmcServerCommunicationTaskFactory); + command.call(); + verify(tmcServerCommunicationTaskFactory, times(1)).getOauthCredentialsTask(); + } +} diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/DownloadAdaptiveExerciseTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/DownloadAdaptiveExerciseTest.java new file mode 100644 index 00000000..8c10c753 --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/DownloadAdaptiveExerciseTest.java @@ -0,0 +1,127 @@ +package fi.helsinki.cs.tmc.core.commands; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth; +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.Course; +import fi.helsinki.cs.tmc.core.domain.Exercise; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.domain.Theme; +import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; +import fi.helsinki.cs.tmc.core.utils.TestUtils; +import fi.helsinki.cs.tmc.langs.util.TaskExecutor; +import fi.helsinki.cs.tmc.langs.util.TaskExecutorImpl; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.Callable; + +public class DownloadAdaptiveExerciseTest { + + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + @Mock + ProgressObserver mockObserver; + @Spy + TmcSettings settings = new MockSettings(); + @Mock + TmcServerCommunicationTaskFactory factory; + @Mock + Course mockCourse; + @Mock + Exercise mockExerciseOne; + @Mock + Callable mockGetAdaptiveExercise; + @Mock + Oauth oauth; + @Mock + Theme mockTheme; + + private Command command; + TaskExecutor langs; + Path arithFuncsTempDir; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + langs = spy(new TaskExecutorImpl()); + TmcSettingsHolder.set(settings); + TmcLangsHolder.set(langs); + arithFuncsTempDir = testFolder.getRoot().toPath().resolve("arith_funcs"); + command = new DownloadAdaptiveExercise(mockObserver, factory, mockTheme, mockCourse); + + doCallRealMethod().when(langs).extractProject(any(Path.class), any(Path.class)); + mockExerciseOne.setName("ex1"); + mockExerciseOne.setCourseName("course1"); + } + + @Test + public void testDownloadAndExtractSuccess() throws Exception { + setUpMocks(); + + Exercise exercise = command.call(); + + verify(factory).getAdaptiveExercise(mockTheme, mockCourse); + verify(factory).getDownloadingAdaptiveExerciseZipTask(mockExerciseOne); + + verifyNoMoreInteractions(factory); + + assertTrue(Files.exists(arithFuncsTempDir)); + } + + @Test + public void testDownloadAndExtractFailure() throws Exception { + setUpMocks(); + when(mockGetAdaptiveExercise.call()).thenReturn(null); + + Exercise exercise = command.call(); + + verify(factory).getAdaptiveExercise(mockTheme, mockCourse); + + verifyNoMoreInteractions(factory); + + } + + private void setUpMocks() throws Exception { + verifyZeroInteractions(langs); + + when(mockTheme.getName()).thenReturn("testTheme"); + when(factory.getAdaptiveExercise(any(Theme.class), any(Course.class))).thenReturn(mockGetAdaptiveExercise); + when(mockGetAdaptiveExercise.call()).thenReturn(mockExerciseOne); + + when(mockExerciseOne.getExtractionTarget(any(Path.class))).thenReturn(arithFuncsTempDir); + when(settings.getTmcProjectDirectory()).thenReturn(testFolder.getRoot().toPath()); + when(oauth.getToken()).thenReturn("testToken"); + + when(factory.getDownloadingAdaptiveExerciseZipTask(mockExerciseOne)) + .thenReturn( + new Callable() { + @Override + public byte[] call() throws Exception { + return Files.readAllBytes( + TestUtils.getZip(this.getClass(), "arith_funcs.zip")); + } + }); + } + +} diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/DownloadOrUpdateExercisesTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/DownloadOrUpdateExercisesTest.java index af246ed8..cbe6c043 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/DownloadOrUpdateExercisesTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/DownloadOrUpdateExercisesTest.java @@ -18,6 +18,7 @@ import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import fi.helsinki.cs.tmc.core.utils.TestUtils; import fi.helsinki.cs.tmc.langs.util.TaskExecutor; import fi.helsinki.cs.tmc.langs.util.TaskExecutorImpl; @@ -31,6 +32,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.nio.file.Files; import java.nio.file.Path; @@ -42,7 +44,7 @@ public class DownloadOrUpdateExercisesTest { @Rule public TemporaryFolder testFolder = new TemporaryFolder(); @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock TmcServerCommunicationTaskFactory factory; @Mock Course mockCourse; @Mock Exercise mockExerciseOne; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/GetCourseDetailsTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/GetCourseDetailsTest.java index ba215e7e..af5b4230 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/GetCourseDetailsTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/GetCourseDetailsTest.java @@ -9,19 +9,21 @@ import fi.helsinki.cs.tmc.core.domain.Course; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.util.concurrent.Callable; public class GetCourseDetailsTest { @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock TmcServerCommunicationTaskFactory factory; @Mock Course mockCourse; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/GetOrganizationsTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/GetOrganizationsTest.java new file mode 100644 index 00000000..b25fec23 --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/GetOrganizationsTest.java @@ -0,0 +1,49 @@ +package fi.helsinki.cs.tmc.core.commands; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.Organization; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; + +import org.junit.Before; +import org.junit.Test; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class GetOrganizationsTest { + @Mock + ProgressObserver mockObserver; + @Mock + TmcServerCommunicationTaskFactory tmcServerCommunicationTaskFactory; + + TmcSettings settings; + private Command> command; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + settings = new MockSettings(); + TmcSettingsHolder.set(settings); + List organizations = new ArrayList(); + organizations.add(new Organization("test", "test", "test", "test", false)); + when(tmcServerCommunicationTaskFactory.getOrganizationListTask()).thenReturn(organizations); + } + + @Test + public void callsTmcServerCommunicationTaskFactoryGetOrganizationsList() throws Exception { + command = new GetOrganizations(mockObserver, tmcServerCommunicationTaskFactory); + command.call(); + verify(tmcServerCommunicationTaskFactory, times(1)).getOrganizationListTask(); + } +} diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/GetUpdatableExercisesTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/GetUpdatableExercisesTest.java index c5c0c669..b9858c73 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/GetUpdatableExercisesTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/GetUpdatableExercisesTest.java @@ -15,6 +15,7 @@ import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import fi.helsinki.cs.tmc.langs.util.TaskExecutor; import fi.helsinki.cs.tmc.langs.util.TaskExecutorImpl; @@ -27,6 +28,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.nio.file.Path; import java.util.concurrent.Callable; @@ -36,7 +38,7 @@ public class GetUpdatableExercisesTest { @Rule public TemporaryFolder testFolder = new TemporaryFolder(); @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock TmcServerCommunicationTaskFactory factory; @Mock Course mockCurrentCourse; @Mock Course mockRefreshedCourse; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/ListCoursesTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/ListCoursesTest.java index a0518c9e..392f29a9 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/ListCoursesTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/ListCoursesTest.java @@ -9,13 +9,16 @@ import fi.helsinki.cs.tmc.core.domain.Course; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Test; + import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.util.List; import java.util.concurrent.Callable; @@ -23,7 +26,7 @@ public class ListCoursesTest { @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock TmcServerCommunicationTaskFactory factory; @Mock Course courseOne; @Mock Course courseTwo; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/PasteWithCommentTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/PasteWithCommentTest.java index 2fddeeb2..1c863044 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/PasteWithCommentTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/PasteWithCommentTest.java @@ -14,6 +14,7 @@ import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import fi.helsinki.cs.tmc.core.utils.TestUtils; import fi.helsinki.cs.tmc.langs.util.TaskExecutor; import fi.helsinki.cs.tmc.langs.util.TaskExecutorImpl; @@ -25,6 +26,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.net.URI; import java.nio.file.Path; @@ -37,7 +39,7 @@ public class PasteWithCommentTest { @Rule public TemporaryFolder testFolder = new TemporaryFolder(); @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock TmcServerCommunicationTaskFactory factory; @Mock Course mockCourse; @Mock Exercise mockExercise; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/RunCheckstyleTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/RunCheckstyleTest.java index d60bf695..a6e63195 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/RunCheckstyleTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/RunCheckstyleTest.java @@ -12,6 +12,7 @@ import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import fi.helsinki.cs.tmc.core.utils.TestUtils; import fi.helsinki.cs.tmc.langs.abstraction.ValidationResult; import fi.helsinki.cs.tmc.langs.util.TaskExecutor; @@ -24,6 +25,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.nio.file.Path; import java.util.Locale; @@ -33,7 +35,7 @@ public class RunCheckstyleTest { @Rule public TemporaryFolder testFolder = new TemporaryFolder(); @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock Course mockCourse; @Mock Exercise mockExercise; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/RunTestsTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/RunTestsTest.java index d2de1915..41eb925f 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/RunTestsTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/RunTestsTest.java @@ -12,6 +12,7 @@ import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import fi.helsinki.cs.tmc.core.utils.TestUtils; import fi.helsinki.cs.tmc.langs.domain.RunResult.Status; import fi.helsinki.cs.tmc.langs.domain.RunResult; @@ -25,6 +26,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.nio.file.Path; @@ -34,7 +36,7 @@ public class RunTestsTest { Path project; @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock Course mockCourse; @Mock Exercise mockExercise; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/SendDiagnosticsTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/SendDiagnosticsTest.java index 96b2d38f..0933f02c 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/SendDiagnosticsTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/SendDiagnosticsTest.java @@ -4,13 +4,13 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import fi.helsinki.cs.tmc.core.communication.TmcBandicootCommunicationTaskFactory; import fi.helsinki.cs.tmc.core.configuration.TmcSettings; import fi.helsinki.cs.tmc.core.domain.ProgressObserver; import fi.helsinki.cs.tmc.core.domain.bandicoot.Diagnostics; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import org.junit.Before; import org.junit.Test; @@ -20,12 +20,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Locale; import java.util.concurrent.Callable; public class SendDiagnosticsTest { - @Mock TmcSettings settings; @Mock @@ -40,11 +38,8 @@ public class SendDiagnosticsTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + this.settings = new MockSettings(); TmcSettingsHolder.set(settings); - when(settings.getServerAddress()).thenReturn("testAddress"); - when(settings.clientName()).thenReturn("testClient"); - when(settings.clientVersion()).thenReturn("testVersion"); - when(settings.getLocale()).thenReturn(new Locale("en")); doReturn(new Callable() { @Override public Object call() throws Exception { diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/SendFeedbackTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/SendFeedbackTest.java index 9ac26f43..46874ed8 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/SendFeedbackTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/SendFeedbackTest.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -15,6 +14,7 @@ import fi.helsinki.cs.tmc.core.domain.submission.FeedbackAnswer; import fi.helsinki.cs.tmc.core.domain.submission.FeedbackQuestion; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import com.google.common.collect.ImmutableList; @@ -25,6 +25,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.net.URI; import java.util.List; @@ -35,7 +36,7 @@ public class SendFeedbackTest { @Rule public TemporaryFolder testFolder = new TemporaryFolder(); @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock TmcServerCommunicationTaskFactory factory; @Mock Course mockCourse; @Mock Exercise mockExercise; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/SubmitAdaptiveExerciseToSkillifierTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/SubmitAdaptiveExerciseToSkillifierTest.java new file mode 100644 index 00000000..7a72adb9 --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/SubmitAdaptiveExerciseToSkillifierTest.java @@ -0,0 +1,126 @@ +package fi.helsinki.cs.tmc.core.commands; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; +import fi.helsinki.cs.tmc.core.communication.oauth2.Oauth; +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.Course; +import fi.helsinki.cs.tmc.core.domain.Exercise; +import fi.helsinki.cs.tmc.core.domain.ProgressObserver; +import fi.helsinki.cs.tmc.core.domain.submission.SubmissionResult; +import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; +import fi.helsinki.cs.tmc.core.utils.TestUtils; +import fi.helsinki.cs.tmc.langs.util.TaskExecutor; +import fi.helsinki.cs.tmc.langs.util.TaskExecutorImpl; + +import com.google.common.base.Optional; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import java.net.URI; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.Callable; + + +public class SubmitAdaptiveExerciseToSkillifierTest { + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + + @Mock + ProgressObserver mockObserver; + @Spy + TmcSettings settings = new MockSettings(); + @Mock + TmcServerCommunicationTaskFactory factory; + @Mock + Course mockCourse; + @Mock + Exercise mockExercise; + + private static final String STUB_PROSESSING_ERRORED_RESPONSE = "{status : \"ERROR\", error: \"failed to submit the exercise\"}"; + private static final String STUB_PROSESSING_DONE_RESPONSE = "{status: \"OK\"}"; + + private Command command; + private Path arithFuncsTempDir; + private TaskExecutor langs; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + TmcSettingsHolder.set(settings); + + langs = spy(new TaskExecutorImpl()); + TmcLangsHolder.set(langs); + TmcSettingsHolder.set(settings); + Exercise ex = new Exercise("Osa02_01.WilliamLovelace", "Example"); + command = new SubmitAdaptiveExerciseToSkillifier(mockObserver, mockExercise, factory); + + arithFuncsTempDir = TestUtils.getProject(this.getClass(), "arith_funcs"); + when(mockExercise.getExerciseDirectory(any(Path.class))).thenReturn(arithFuncsTempDir); + when(settings.getLocale()).thenReturn(new Locale("FI")); + Optional mockToken = Optional.of("testToken"); + settings.setToken(mockToken); + + when(factory.getSkillifierUrl(anyString())).thenReturn(URI.create("www.someurl.com")); + } + + @Test(timeout = 10000) + public void testSuccessfulSubmit() throws Exception { + + verifyZeroInteractions(mockObserver); + doReturn(new byte[0]).when(langs).compressProject(any(Path.class)); + when( + factory.getSubmittingExerciseToSkillifierTask( + any(Exercise.class), any(byte[].class), any(Map.class))) + .thenReturn( + new Callable() { + @Override + public String call() throws Exception { + return STUB_PROSESSING_DONE_RESPONSE; + } + }); + + SubmissionResult result = command.call(); + + assertEquals(SubmissionResult.Status.OK, result.getStatus()); + } + + @Test(timeout = 10000) + public void testUnsuccessfulSubmit() throws Exception { + + verifyZeroInteractions(mockObserver); + doReturn(new byte[0]).when(langs).compressProject(any(Path.class)); + when( + factory.getSubmittingExerciseToSkillifierTask( + any(Exercise.class), any(byte[].class), any(Map.class))) + .thenReturn( + new Callable() { + @Override + public String call() throws Exception { + return STUB_PROSESSING_ERRORED_RESPONSE; + } + }); + + SubmissionResult result = command.call(); + + assertEquals(SubmissionResult.Status.ERROR, result.getStatus()); + assertEquals("failed to submit the exercise", result.getError()); + } +} diff --git a/src/test/java/fi/helsinki/cs/tmc/core/commands/SubmitTest.java b/src/test/java/fi/helsinki/cs/tmc/core/commands/SubmitTest.java index d202c7e3..9042ab77 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/commands/SubmitTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/commands/SubmitTest.java @@ -2,7 +2,6 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verifyZeroInteractions; @@ -17,6 +16,7 @@ import fi.helsinki.cs.tmc.core.domain.submission.SubmissionResult; import fi.helsinki.cs.tmc.core.holders.TmcLangsHolder; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import fi.helsinki.cs.tmc.core.utils.TestUtils; import fi.helsinki.cs.tmc.langs.util.TaskExecutor; import fi.helsinki.cs.tmc.langs.util.TaskExecutorImpl; @@ -28,6 +28,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.net.URI; import java.nio.file.Path; @@ -40,7 +41,7 @@ public class SubmitTest { @Rule public TemporaryFolder testFolder = new TemporaryFolder(); @Mock ProgressObserver mockObserver; - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Mock TmcServerCommunicationTaskFactory factory; @Mock Course mockCourse; @Mock Exercise mockExercise; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/communication/http/HttpRequestExecutorTest.java b/src/test/java/fi/helsinki/cs/tmc/core/communication/http/HttpRequestExecutorTest.java index ac03d277..d6d3b886 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/communication/http/HttpRequestExecutorTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/communication/http/HttpRequestExecutorTest.java @@ -6,10 +6,10 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.verifyNoMoreInteractions; import fi.helsinki.cs.tmc.core.configuration.TmcSettings; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; @@ -23,15 +23,15 @@ import org.junit.Rule; import org.junit.Test; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.net.URI; import java.nio.charset.Charset; public class HttpRequestExecutorTest { - @Mock TmcSettings settings; + @Spy TmcSettings settings = new MockSettings(); @Rule public WireMockRule wireMockRule = new WireMockRule(0); @@ -39,7 +39,8 @@ public class HttpRequestExecutorTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); TmcSettingsHolder.set(settings); - verifyNoMoreInteractions(settings); +// TODO uncomment this after removing migration from TmcSettingsHolder +// verifyNoMoreInteractions(settings); wireMockRule.start(); } diff --git a/src/test/java/fi/helsinki/cs/tmc/core/communication/http/serialization/AdaptiveExerciseParserTest.java b/src/test/java/fi/helsinki/cs/tmc/core/communication/http/serialization/AdaptiveExerciseParserTest.java new file mode 100644 index 00000000..0a12c17b --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/communication/http/serialization/AdaptiveExerciseParserTest.java @@ -0,0 +1,43 @@ +package fi.helsinki.cs.tmc.core.communication.http.serialization; + +import static org.junit.Assert.assertEquals; + +import fi.helsinki.cs.tmc.core.communication.serialization.AdaptiveExerciseParser; +import fi.helsinki.cs.tmc.core.domain.Exercise; + +import org.junit.Before; +import org.junit.Test; + +import java.net.URI; + +public class AdaptiveExerciseParserTest { + + private AdaptiveExerciseParser adaptiveParser; + + @Before + public void setUp() { + this.adaptiveParser = new AdaptiveExerciseParser(); + } + + @Test(expected = NullPointerException.class) + public void jsonEmptyException() { + adaptiveParser.parseFromJson(null); + } + + @Test(expected = IllegalArgumentException.class) + public void jsonIllegalException() { + adaptiveParser.parseFromJson(" "); + } + + @Test + public void exerciseNotAvailable() { + Exercise exercise = adaptiveParser.parseFromJson("{ available: false, zip_url: additionToString }"); + assertEquals(exercise, null); + } + + @Test + public void exersiceAvailableTest() { + Exercise exercise = adaptiveParser.parseFromJson("{available: true, zip_url: additionToString }"); + assertEquals(URI.create("http://additionToString"), exercise.getZipUrl()); + } +} diff --git a/src/test/java/fi/helsinki/cs/tmc/core/communication/http/serialization/ExerciseListParserTest.java b/src/test/java/fi/helsinki/cs/tmc/core/communication/http/serialization/ExerciseListParserTest.java new file mode 100644 index 00000000..1226b49e --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/communication/http/serialization/ExerciseListParserTest.java @@ -0,0 +1,89 @@ +package fi.helsinki.cs.tmc.core.communication.http.serialization; + +import static org.junit.Assert.assertEquals; + +import fi.helsinki.cs.tmc.core.communication.serialization.ExerciseListParser; +import fi.helsinki.cs.tmc.core.domain.Exercise; + +import org.junit.Before; +import org.junit.Test; + +import java.util.List; + +public class ExerciseListParserTest { + + private ExerciseListParser exerciseListParser; + private List exerciseList; + + @Before + public void setUp() { + this.exerciseListParser = new ExerciseListParser(); + exerciseList = null; + } + + @Test(expected = NullPointerException.class) + public void jsonEmptyException() { + exerciseListParser.parseFromJson(null); + } + + @Test(expected = IllegalArgumentException.class) + public void jsonIllegalException() { + exerciseListParser.parseFromJson(" "); + } + + @Test + public void singleExcerciseIsParsedAndSetToList() { + String json = "[\n" + + " {\n" + + " \"id\": 1,\n" + + " \"name\": \"Exercise name\",\n" + + " \"disabled\": false,\n" + + " \"available_points\": [\n" + + " {\n" + + " \"id\": 1,\n" + + " \"exercise_id\": 1,\n" + + " \"name\": \"Point name\",\n" + + " \"require_review\": false\n" + + " }\n" + + " ]\n" + + " }\n" + + "]"; + exerciseList = exerciseListParser.parseFromJson(json); + assertEquals(1, exerciseList.size()); + } + + @Test + public void setOfExcercisesIsParsedAndSetToList() { + String json = "[\n" + + " {\n" + + " \"id\": 1,\n" + + " \"name\": \"Exercise name\",\n" + + " \"disabled\": false,\n" + + " \"available_points\": [\n" + + " {\n" + + " \"id\": 1,\n" + + " \"exercise_id\": 1,\n" + + " \"name\": \"Point name\",\n" + + " \"require_review\": false\n" + + " }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"id\": 2,\n" + + " \"name\": \"Exercise name2\",\n" + + " \"disabled\": false,\n" + + " \"available_points\": [\n" + + " {\n" + + " \"id\": 2,\n" + + " \"exercise_id\": 2,\n" + + " \"name\": \"Point name\",\n" + + " \"require_review\": false\n" + + " }\n" + + " ]\n" + + " }\n" + + "]"; + exerciseList = exerciseListParser.parseFromJson(json); + assertEquals(2, exerciseList.size()); + } + +} diff --git a/src/test/java/fi/helsinki/cs/tmc/core/communication/oauth2/OauthTest.java b/src/test/java/fi/helsinki/cs/tmc/core/communication/oauth2/OauthTest.java new file mode 100644 index 00000000..5fff0a64 --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/communication/oauth2/OauthTest.java @@ -0,0 +1,83 @@ +package fi.helsinki.cs.tmc.core.communication.oauth2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; +import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; + +import com.google.common.base.Optional; + +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.lang.reflect.Field; + +public class OauthTest { + + private TmcSettings settings; + + private Oauth oauth; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + settings = spy(new MockSettings()); + TmcSettingsHolder.set(settings); + oauth = spy(new Oauth()); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocationOnMock) throws Throwable { + settings.setToken(Optional.of("testToken")); + return null; + } + }).when(oauth).fetchNewToken(anyString()); + } + + @After + public void tearDown() throws NoSuchFieldException, IllegalAccessException { + Field oauth = Oauth.class.getDeclaredField("oauth"); + oauth.setAccessible(true); + oauth.set(null, null); + } + + @Test + public void hasTokenWhenFetched() throws NotLoggedInException { + try { + oauth.fetchNewToken("password"); + assertTrue(oauth.hasToken()); + assertEquals("testToken", oauth.getToken()); + } catch (OAuthSystemException | OAuthProblemException ex) { + fail("Got exception: " + ex.toString()); + } + } + + @Test + public void hasNoTokenWhenInitialized() throws Exception { + assertFalse(oauth.hasToken()); + verify(oauth, times(0)).fetchNewToken(anyString()); + } + + @Test + public void setsTokenToSettings() throws OAuthProblemException, OAuthSystemException { + oauth.fetchNewToken("password"); + verify(settings, times(1)).setToken(Optional.of(anyString())); + } +} diff --git a/src/test/java/fi/helsinki/cs/tmc/core/domain/CourseTest.java b/src/test/java/fi/helsinki/cs/tmc/core/domain/CourseTest.java index c965d27c..6734c743 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/domain/CourseTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/domain/CourseTest.java @@ -103,4 +103,29 @@ public void hashCodeGeneratedFromId() { c2.setId(1); assertTrue(courses.contains(c2)); } + + @Test + public void courseExercisesAreDividedIntoThemesCorrectly() { + exercises = new ArrayList<>(); + exercises.add(new Exercise("viikko1-testi")); + exercises.add(new Exercise("viikko2-testi")); + exercises.add(new Exercise("viikko3-testi")); + course.setExercises(exercises); + course.generateThemes(); + assertEquals(3,course.getExercises().size()); + } + + @Test + public void returnExercisesByThemeTest() { + exercises = new ArrayList<>(); + exercises.add(new Exercise("viikko1-testi")); + exercises.add(new Exercise("viikko1-testi")); + exercises.add(new Exercise("viikko3-testi")); + course.setExercises(exercises); + course.generateThemes(); + assertEquals(2, course.getExercisesByTheme("viikko1").size()); + assertEquals(1, course.getExercisesByTheme("viikko3").size()); + } + + } diff --git a/src/test/java/fi/helsinki/cs/tmc/core/holders/TmcSettingsHolderTest.java b/src/test/java/fi/helsinki/cs/tmc/core/holders/TmcSettingsHolderTest.java index 3830c6ad..7f650cc3 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/holders/TmcSettingsHolderTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/holders/TmcSettingsHolderTest.java @@ -4,18 +4,19 @@ import fi.helsinki.cs.tmc.core.configuration.TmcSettings; import fi.helsinki.cs.tmc.core.exceptions.UninitializedHolderException; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; public class TmcSettingsHolderTest { private TmcSettingsHolder holder; - @Mock private TmcSettings settings; + @Spy private TmcSettings settings = new MockSettings(); @Before public void setUp() { diff --git a/src/test/java/fi/helsinki/cs/tmc/core/spyware/EventSendBufferTest.java b/src/test/java/fi/helsinki/cs/tmc/core/spyware/EventSendBufferTest.java index 594543a7..5263551e 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/spyware/EventSendBufferTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/spyware/EventSendBufferTest.java @@ -13,7 +13,9 @@ import fi.helsinki.cs.tmc.core.communication.TmcServerCommunicationTaskFactory; import fi.helsinki.cs.tmc.core.configuration.TmcSettings; import fi.helsinki.cs.tmc.core.domain.Course; +import fi.helsinki.cs.tmc.core.exceptions.NotLoggedInException; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import fi.helsinki.cs.tmc.spyware.EventSendBuffer; import fi.helsinki.cs.tmc.spyware.EventStore; import fi.helsinki.cs.tmc.spyware.LoggableEvent; @@ -29,6 +31,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.io.IOException; import java.net.URI; @@ -45,7 +48,7 @@ public class EventSendBufferTest { private Course mockCourse; - @Mock TmcSettings tmcSettings; + @Spy TmcSettings tmcSettings = new MockSettings(); @Mock TmcServerCommunicationTaskFactory factory; @Mock private SpywareSettings settings; @@ -65,7 +68,7 @@ public class EventSendBufferTest { private EventSendBuffer sender; @Before - public void setUp() throws IOException { + public void setUp() throws IOException, NotLoggedInException { MockitoAnnotations.initMocks(this); TmcSettingsHolder.set(tmcSettings); diff --git a/src/test/java/fi/helsinki/cs/tmc/core/utilities/ExceptionTrackingCallableTest.java b/src/test/java/fi/helsinki/cs/tmc/core/utilities/ExceptionTrackingCallableTest.java index e91d7e88..da9532ed 100644 --- a/src/test/java/fi/helsinki/cs/tmc/core/utilities/ExceptionTrackingCallableTest.java +++ b/src/test/java/fi/helsinki/cs/tmc/core/utilities/ExceptionTrackingCallableTest.java @@ -8,19 +8,17 @@ import fi.helsinki.cs.tmc.core.domain.bandicoot.Crash; import fi.helsinki.cs.tmc.core.exceptions.TmcCoreException; import fi.helsinki.cs.tmc.core.holders.TmcSettingsHolder; +import fi.helsinki.cs.tmc.core.utils.MockSettings; import org.junit.Before; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Locale; import java.util.concurrent.Callable; public class ExceptionTrackingCallableTest { - @Mock TmcSettings settings; @Mock TmcBandicootCommunicationTaskFactory factory; @@ -28,14 +26,8 @@ public class ExceptionTrackingCallableTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + this.settings = new MockSettings(); TmcSettingsHolder.set(settings); - when(settings.getServerAddress()).thenReturn("testAddress"); - when(settings.clientName()).thenReturn("testClient"); - when(settings.clientVersion()).thenReturn("testVersion"); - when(settings.hostProgramName()).thenReturn("testHostProgram"); - when(settings.hostProgramVersion()).thenReturn("testHostProgramVersion"); - when(settings.getLocale()).thenReturn(new Locale("en")); - when(settings.getSendDiagnostics()).thenReturn(true); when(factory.sendCrash(any(Crash.class))).thenReturn(new Callable() { @Override public Void call() throws Exception { return null; diff --git a/src/test/java/fi/helsinki/cs/tmc/core/utils/MockSettings.java b/src/test/java/fi/helsinki/cs/tmc/core/utils/MockSettings.java new file mode 100644 index 00000000..2b668cb3 --- /dev/null +++ b/src/test/java/fi/helsinki/cs/tmc/core/utils/MockSettings.java @@ -0,0 +1,150 @@ +package fi.helsinki.cs.tmc.core.utils; + +import fi.helsinki.cs.tmc.core.configuration.TmcSettings; +import fi.helsinki.cs.tmc.core.domain.Course; +import fi.helsinki.cs.tmc.core.domain.OauthCredentials; +import fi.helsinki.cs.tmc.core.domain.Organization; + +import com.google.common.base.Optional; + +import org.apache.http.impl.conn.SystemDefaultRoutePlanner; + +import java.lang.UnsupportedOperationException; +import java.nio.file.Path; +import java.util.Locale; + +public class MockSettings implements TmcSettings { + + private Optional token; + + public MockSettings() { + token = Optional.absent(); + } + + @Override + public String getServerAddress() { + return "testAddress"; + } + + @Override + public void setServerAddress(String address) { + + } + + @Override + public Optional getPassword() { + return Optional.absent(); + } + + @Override + public void setPassword(Optional password) { + + } + + @Override + public String getUsername() { + return "testUsername"; + } + + @Override + public boolean userDataExists() { + return false; + } + + @Override + public Optional getCurrentCourse() { + return null; + } + + @Override + public String clientName() { + return "testClient"; + } + + @Override + public String clientVersion() { + return "testVersion"; + } + + @Override + public String getFormattedUserData() { + throw new UnsupportedOperationException(); + } + + @Override + public Path getTmcProjectDirectory() { + return null; + } + + @Override + public Locale getLocale() { + return new Locale("en"); + } + + @Override + public SystemDefaultRoutePlanner proxy() { + return null; + } + + @Override + public void setCourse(Course theCourse) { + + } + + @Override + public void setConfigRoot(Path configRoot) { + + } + + @Override + public Path getConfigRoot() { + throw new UnsupportedOperationException(); + } + + @Override + public String hostProgramName() { + return "testHostProgram"; + } + + @Override + public String hostProgramVersion() { + return "testHostProgramVersion"; + } + + @Override + public boolean getSendDiagnostics() { + return true; + } + + @Override + public OauthCredentials getOauthCredentials() { + OauthCredentials credentials = new OauthCredentials(); + credentials.setOauthApplicationId("testOauthApplicationId"); + credentials.setOauthSecret("testOauthSecret"); + return credentials; + } + + @Override + public void setOauthCredentials(OauthCredentials credentials) { + + } + + @Override + public void setToken(Optional token) { + this.token = token; + } + + @Override + public Optional getToken() { + return token; + } + + @Override + public String getOrganization() { + return "testOrganization"; + } + + @Override + public void setOrganization(String organization) { + } +} diff --git a/src/test/resources/__files/test.zip b/src/test/resources/__files/test.zip new file mode 100644 index 00000000..c23c97ca Binary files /dev/null and b/src/test/resources/__files/test.zip differ diff --git a/tmc-core.iml b/tmc-core.iml deleted file mode 100644 index 85a232b4..00000000 --- a/tmc-core.iml +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file