diff --git a/pom.xml b/pom.xml index d05bcf09..294419cc 100644 --- a/pom.xml +++ b/pom.xml @@ -142,7 +142,7 @@ - edu.byu.cs.server.Server + Main @@ -169,7 +169,7 @@ true - edu.byu.cs.server.Server + Main diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 00000000..a533f101 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,103 @@ +import edu.byu.cs.autograder.GradingException; +import edu.byu.cs.server.endpointprovider.EndpointProvider; +import edu.byu.cs.server.endpointprovider.EndpointProviderImpl; +import edu.byu.cs.dataAccess.DaoService; +import edu.byu.cs.dataAccess.DataAccessException; +import edu.byu.cs.properties.ApplicationProperties; +import edu.byu.cs.server.Server; +import edu.byu.cs.service.SubmissionService; +import edu.byu.cs.util.ResourceUtils; +import org.apache.commons.cli.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +public class Main { + private static Logger LOGGER = LoggerFactory.getLogger(Main.class); + + private static EndpointProvider endpointProvider = new EndpointProviderImpl(); + + public static void main(String[] args) { + ResourceUtils.copyResourceFiles("phases", new File("")); + setupProperties(args); + + try { + DaoService.initializeSqlDAOs(); + } catch (DataAccessException e) { + LOGGER.error("Error setting up database", e); + throw new RuntimeException(e); + } + + new Server(endpointProvider).start(8080); + + try { + SubmissionService.reRunSubmissionsInQueue(); + } catch (IOException | DataAccessException | GradingException e) { + LOGGER.error("Error rerunning submissions already in queue", e); + } + } + + private static void setupProperties(String[] args) { + Options options = getOptions(); + + Properties properties = new Properties(); + + CommandLineParser parser = new DefaultParser(); + try { + CommandLine cmd = parser.parse(options, args); + if (cmd.hasOption("db-host")) { + properties.setProperty("db-host", cmd.getOptionValue("db-host")); + } + if (cmd.hasOption("db-port")) { + properties.setProperty("db-port", cmd.getOptionValue("db-port")); + } + if (cmd.hasOption("db-name")) { + properties.setProperty("db-name", cmd.getOptionValue("db-name")); + } + if (cmd.hasOption("db-user")) { + properties.setProperty("db-user", cmd.getOptionValue("db-user")); + } + if (cmd.hasOption("db-pass")) { + properties.setProperty("db-pass", cmd.getOptionValue("db-pass")); + } + if (cmd.hasOption("frontend-url")) { + properties.setProperty("frontend-url", cmd.getOptionValue("frontend-url")); + } + if (cmd.hasOption("cas-callback-url")) { + properties.setProperty("cas-callback-url", cmd.getOptionValue("cas-callback-url")); + } + if (cmd.hasOption("canvas-token")) { + properties.setProperty("canvas-token", cmd.getOptionValue("canvas-token")); + } + if (cmd.hasOption("use-canvas")) { + properties.setProperty("use-canvas", cmd.getOptionValue("use-canvas")); + } + if (cmd.hasOption("disable-compilation")) { + properties.setProperty("run-compilation", "false"); + } + } catch (ParseException e) { + throw new RuntimeException("Error parsing command line arguments", e); + } + + ApplicationProperties.loadProperties(properties); + } + + private static Options getOptions() { + Options options = new Options(); + options.addOption(null, "db-host", true, "Database Host"); + options.addOption(null, "db-port", true, "Database Port"); + options.addOption(null, "db-name", true, "Database Name"); + options.addOption(null, "db-user", true, "Database User"); + options.addOption(null, "db-pass", true, "Database Password"); + options.addOption(null, "frontend-url", true, "Frontend URL"); + options.addOption(null, "cas-callback-url", true, "CAS Callback URL"); + options.addOption(null, "canvas-token", true, "Canvas Token"); + options.addOption(null, "use-canvas", true, "Using Canvas"); + options.addOption(null, "disable-compilation", false, "Turn off student code compilation"); + return options; + } + +} diff --git a/src/main/java/edu/byu/cs/autograder/score/Scorer.java b/src/main/java/edu/byu/cs/autograder/score/Scorer.java index 1e390dbc..73f80c42 100644 --- a/src/main/java/edu/byu/cs/autograder/score/Scorer.java +++ b/src/main/java/edu/byu/cs/autograder/score/Scorer.java @@ -318,9 +318,11 @@ private Rubric.Results mergeResultsWithPrevious(Rubric.RubricType rubricType, Ru float score = startingScore; for (Submission previousSubmission : previousSubmissions) { - Rubric.RubricItem previousItem = previousSubmission.rubric().items().get(rubricType); - if (previousItem != null && previousItem.results().rawScore() <= results.rawScore()) { - score = Math.max(score, previousItem.results().score()); + if(previousSubmission.passed()) { + Rubric.RubricItem previousItem = previousSubmission.rubric().items().get(rubricType); + if (previousItem != null && previousItem.results().rawScore() <= results.rawScore()) { + score = Math.max(score, previousItem.results().score()); + } } } diff --git a/src/main/java/edu/byu/cs/server/Server.java b/src/main/java/edu/byu/cs/server/Server.java index 4fab4136..c2deb6be 100644 --- a/src/main/java/edu/byu/cs/server/Server.java +++ b/src/main/java/edu/byu/cs/server/Server.java @@ -1,33 +1,28 @@ package edu.byu.cs.server; -import edu.byu.cs.autograder.GradingException; import edu.byu.cs.controller.WebSocketController; -import edu.byu.cs.dataAccess.DaoService; -import edu.byu.cs.dataAccess.DataAccessException; -import edu.byu.cs.properties.ApplicationProperties; -import edu.byu.cs.service.SubmissionService; -import edu.byu.cs.util.ResourceUtils; -import org.apache.commons.cli.*; +import edu.byu.cs.server.endpointprovider.EndpointProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.util.Properties; - -import static edu.byu.cs.controller.AdminController.*; -import static edu.byu.cs.controller.AuthController.*; -import static edu.byu.cs.controller.CasController.*; -import static edu.byu.cs.controller.ConfigController.*; -import static edu.byu.cs.controller.SubmissionController.*; -import static edu.byu.cs.controller.UserController.*; import static spark.Spark.*; public class Server { private static final Logger LOGGER = LoggerFactory.getLogger(Server.class); + private final EndpointProvider provider; + + public Server(EndpointProvider endpointProvider) { + this.provider = endpointProvider; + } - public static int setupEndpoints(int port) { + public int start(int desiredPort) { + int chosenPort = setupEndpoints(desiredPort); + LOGGER.info("Server started on port {}", chosenPort); + return chosenPort; + } + + private int setupEndpoints(int port) { port(port); webSocket("/ws", WebSocketController.class); @@ -35,190 +30,94 @@ public static int setupEndpoints(int port) { staticFiles.location("/frontend/dist"); - before((request, response) -> { - response.header("Access-Control-Allow-Headers", "Authorization,Content-Type"); - response.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,PATCH,OPTIONS"); - response.header("Access-Control-Allow-Credentials", "true"); - response.header("Access-Control-Allow-Origin", ApplicationProperties.frontendUrl()); - }); + before(provider.beforeAll()); path("/auth", () -> { - get("/callback", callbackGet); - get("/login", loginGet); + get("/callback", provider.callbackGet()); + get("/login", provider.loginGet()); // all routes after this point require authentication - post("/logout", logoutPost); + post("/logout", provider.logoutPost()); }); path("/api", () -> { before("/*", (req, res) -> { - if (!req.requestMethod().equals("OPTIONS")) - verifyAuthenticatedMiddleware.handle(req, res); + if (!req.requestMethod().equals("OPTIONS")) provider.verifyAuthenticatedMiddleware().handle(req, res); }); - patch("/repo", repoPatch); + patch("/repo", provider.repoPatch()); - get("/submit", submitGet); - post("/submit", submitPost); + get("/submit", provider.submitGet()); + post("/submit", provider.submitPost()); - get("/latest", latestSubmissionForMeGet); + get("/latest", provider.latestSubmissionForMeGet()); - get("/submission", submissionXGet); - get("/submission/:phase", submissionXGet); + get("/submission", provider.submissionXGet()); + get("/submission/:phase", provider.submissionXGet()); - get("/me", meGet); + get("/me", provider.meGet()); - get("/config", getConfigStudent); + get("/config", provider.getConfigStudent()); path("/admin", () -> { before("/*", (req, res) -> { - if (!req.requestMethod().equals("OPTIONS")) - verifyAdminMiddleware.handle(req, res); + if (!req.requestMethod().equals("OPTIONS")) provider.verifyAdminMiddleware().handle(req, res); }); - patch("/repo/:netId", repoPatchAdmin); + patch("/repo/:netId", provider.repoPatchAdmin()); - get("/repo/history", repoHistoryAdminGet); + get("/repo/history", provider.repoHistoryAdminGet()); - get("/users", usersGet); + get("/users", provider.usersGet()); - post("/submit", adminRepoSubmitPost); + post("/submit", provider.adminRepoSubmitPost()); path("/submissions", () -> { - post("/approve", approveSubmissionPost); + post("/approve", provider.approveSubmissionPost()); - get("/latest", latestSubmissionsGet); + get("/latest", provider.latestSubmissionsGet()); - get("/latest/:count", latestSubmissionsGet); + get("/latest/:count", provider.latestSubmissionsGet()); - get("/active", submissionsActiveGet); + get("/active", provider.submissionsActiveGet()); - get("/student/:netId", studentSubmissionsGet); + get("/student/:netId", provider.studentSubmissionsGet()); - post("/rerun", submissionsReRunPost); + post("/rerun", provider.submissionsReRunPost()); }); - get("/test_mode", testModeGet); + get("/test_mode", provider.testModeGet()); - get("/analytics/commit", commitAnalyticsGet); + get("/analytics/commit", provider.commitAnalyticsGet()); - get("/analytics/commit/:option", commitAnalyticsGet); + get("/analytics/commit/:option", provider.commitAnalyticsGet()); - get("/honorChecker/zip/:section", honorCheckerZipGet); + get("/honorChecker/zip/:section", provider.honorCheckerZipGet()); - get("/sections", sectionsGet); + get("/sections", provider.sectionsGet()); path("/config", () -> { - get("", getConfigAdmin); + get("", provider.getConfigAdmin()); - post("/phases", updateLivePhases); - post("/phases/shutdown", scheduleShutdown); - post("/banner", updateBannerMessage); + post("/phases", provider.updateLivePhases()); + post("/phases/shutdown", provider.scheduleShutdown()); + post("/banner", provider.updateBannerMessage()); - post("/courseIds", updateCourseIdsPost); - get("/courseIds", updateCourseIdsUsingCanvasGet); + post("/courseIds", provider.updateCourseIdsPost()); + get("/courseIds", provider.updateCourseIdsUsingCanvasGet()); - post("/penalties", updatePenalties); + post("/penalties", provider.updatePenalties()); }); }); }); // spark's notFound method does not work - get("/*", (req, res) -> { - if (req.pathInfo().equals("/ws")) - return null; - - String urlParams = req.queryString(); - urlParams = urlParams == null ? "" : "?" + urlParams; - res.redirect("/" + urlParams, 302); - return null; - }); - init(); + get("/*", provider.defaultGet()); - return port(); - } + after(provider.afterAll()); - private static void setupProperties(String[] args) { - Options options = getOptions(); - - Properties properties = new Properties(); - - CommandLineParser parser = new DefaultParser(); - try { - CommandLine cmd = parser.parse(options, args); - if (cmd.hasOption("db-host")) { - properties.setProperty("db-host", cmd.getOptionValue("db-host")); - } - if (cmd.hasOption("db-port")) { - properties.setProperty("db-port", cmd.getOptionValue("db-port")); - } - if (cmd.hasOption("db-name")) { - properties.setProperty("db-name", cmd.getOptionValue("db-name")); - } - if (cmd.hasOption("db-user")) { - properties.setProperty("db-user", cmd.getOptionValue("db-user")); - } - if (cmd.hasOption("db-pass")) { - properties.setProperty("db-pass", cmd.getOptionValue("db-pass")); - } - if (cmd.hasOption("frontend-url")) { - properties.setProperty("frontend-url", cmd.getOptionValue("frontend-url")); - } - if (cmd.hasOption("cas-callback-url")) { - properties.setProperty("cas-callback-url", cmd.getOptionValue("cas-callback-url")); - } - if (cmd.hasOption("canvas-token")) { - properties.setProperty("canvas-token", cmd.getOptionValue("canvas-token")); - } - if (cmd.hasOption("use-canvas")) { - properties.setProperty("use-canvas", cmd.getOptionValue("use-canvas")); - } - if (cmd.hasOption("disable-compilation")) { - properties.setProperty("run-compilation", "false"); - } - } catch (ParseException e) { - throw new RuntimeException("Error parsing command line arguments", e); - } - - ApplicationProperties.loadProperties(properties); - } - - private static Options getOptions() { - Options options = new Options(); - options.addOption(null, "db-host", true, "Database Host"); - options.addOption(null, "db-port", true, "Database Port"); - options.addOption(null, "db-name", true, "Database Name"); - options.addOption(null, "db-user", true, "Database User"); - options.addOption(null, "db-pass", true, "Database Password"); - options.addOption(null, "frontend-url", true, "Frontend URL"); - options.addOption(null, "cas-callback-url", true, "CAS Callback URL"); - options.addOption(null, "canvas-token", true, "Canvas Token"); - options.addOption(null, "use-canvas", true, "Using Canvas"); - options.addOption(null, "disable-compilation", false, "Turn off student code compilation"); - return options; - } - - - public static void main(String[] args) { - ResourceUtils.copyResourceFiles("phases", new File("")); - setupProperties(args); - - try { - DaoService.initializeSqlDAOs(); - } catch (DataAccessException e) { - LOGGER.error("Error setting up database", e); - throw new RuntimeException(e); - } - - int port = setupEndpoints(8080); - - LOGGER.info("Server started on port {}", port); + init(); - try { - SubmissionService.reRunSubmissionsInQueue(); - } catch (IOException | DataAccessException | GradingException e) { - LOGGER.error("Error rerunning submissions already in queue", e); - } + return port(); } - } diff --git a/src/main/java/edu/byu/cs/server/endpointprovider/EndpointProvider.java b/src/main/java/edu/byu/cs/server/endpointprovider/EndpointProvider.java new file mode 100644 index 00000000..b89c1d6a --- /dev/null +++ b/src/main/java/edu/byu/cs/server/endpointprovider/EndpointProvider.java @@ -0,0 +1,64 @@ +package edu.byu.cs.server.endpointprovider; + +import spark.Filter; +import spark.Route; + +public interface EndpointProvider { + + // Wildcard endpoints + + Filter beforeAll(); + Filter afterAll(); + + Route defaultGet(); + + // AdminController + + Route usersGet(); + Route testModeGet(); + Route commitAnalyticsGet(); + Route honorCheckerZipGet(); + Route sectionsGet(); + + // AuthController + + Filter verifyAuthenticatedMiddleware(); + Filter verifyAdminMiddleware(); + Route meGet(); + + // CasController + + Route callbackGet(); + Route loginGet(); + Route logoutPost(); + + // ConfigController + + Route getConfigAdmin(); + Route getConfigStudent(); + Route updateLivePhases(); + Route scheduleShutdown(); + Route updateBannerMessage(); + Route updateCourseIdsPost(); + Route updateCourseIdsUsingCanvasGet(); + Route updatePenalties(); + + // SubmissionController + + Route submitPost(); + Route adminRepoSubmitPost(); + Route submitGet(); + Route latestSubmissionForMeGet(); + Route submissionXGet(); + Route latestSubmissionsGet(); + Route submissionsActiveGet(); + Route studentSubmissionsGet(); + Route approveSubmissionPost(); + Route submissionsReRunPost(); + + // UserController + + Route repoPatch(); + Route repoPatchAdmin(); + Route repoHistoryAdminGet(); +} diff --git a/src/main/java/edu/byu/cs/server/endpointprovider/EndpointProviderImpl.java b/src/main/java/edu/byu/cs/server/endpointprovider/EndpointProviderImpl.java new file mode 100644 index 00000000..1ba31d02 --- /dev/null +++ b/src/main/java/edu/byu/cs/server/endpointprovider/EndpointProviderImpl.java @@ -0,0 +1,211 @@ +package edu.byu.cs.server.endpointprovider; + +import edu.byu.cs.controller.*; +import edu.byu.cs.properties.ApplicationProperties; +import spark.Filter; +import spark.Route; + +public class EndpointProviderImpl implements EndpointProvider { + + // Wildcard endpoints + + @Override + public Filter beforeAll() { + return (request, response) -> { + response.header("Access-Control-Allow-Headers", "Authorization,Content-Type"); + response.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,PATCH,OPTIONS"); + response.header("Access-Control-Allow-Credentials", "true"); + response.header("Access-Control-Allow-Origin", ApplicationProperties.frontendUrl()); + }; + } + + @Override + public Filter afterAll() { + return (req, res) -> {}; + } + + @Override + public Route defaultGet() { + return (req, res) -> { + if (req.pathInfo().equals("/ws")) + return null; + + String urlParams = req.queryString(); + urlParams = urlParams == null ? "" : "?" + urlParams; + res.redirect("/" + urlParams, 302); + return null; + }; + } + + // AdminController + + @Override + public Route usersGet() { + return AdminController.usersGet; + } + + @Override + public Route testModeGet() { + return AdminController.testModeGet; + } + + @Override + public Route commitAnalyticsGet() { + return AdminController.commitAnalyticsGet; + } + + @Override + public Route honorCheckerZipGet() { + return AdminController.honorCheckerZipGet; + } + + @Override + public Route sectionsGet() { + return AdminController.sectionsGet; + } + + // AuthController + + @Override + public Filter verifyAuthenticatedMiddleware() { + return AuthController.verifyAuthenticatedMiddleware; + } + + @Override + public Filter verifyAdminMiddleware() { + return AuthController.verifyAdminMiddleware; + } + + @Override + public Route meGet() { + return AuthController.meGet; + } + + // CasController + + @Override + public Route callbackGet() { + return CasController.callbackGet; + } + + @Override + public Route loginGet() { + return CasController.loginGet; + } + + @Override + public Route logoutPost() { + return CasController.logoutPost; + } + + // ConfigController + + @Override + public Route getConfigAdmin() { + return ConfigController.getConfigAdmin; + } + + @Override + public Route getConfigStudent() { + return ConfigController.getConfigStudent; + } + + @Override + public Route updateLivePhases() { + return ConfigController.updateLivePhases; + } + + @Override + public Route scheduleShutdown() { + return ConfigController.scheduleShutdown; + } + + @Override + public Route updateBannerMessage() { + return ConfigController.updateBannerMessage; + } + + @Override + public Route updateCourseIdsPost() { + return ConfigController.updateCourseIdsPost; + } + + @Override + public Route updateCourseIdsUsingCanvasGet() { + return ConfigController.updateCourseIdsUsingCanvasGet; + } + + @Override + public Route updatePenalties() { + return ConfigController.updatePenalties; + } + + // SubmissionController + + @Override + public Route submitPost() { + return SubmissionController.submitPost; + } + + @Override + public Route adminRepoSubmitPost() { + return SubmissionController.adminRepoSubmitPost; + } + + @Override + public Route submitGet() { + return SubmissionController.submitGet; + } + + @Override + public Route latestSubmissionForMeGet() { + return SubmissionController.latestSubmissionForMeGet; + } + + @Override + public Route submissionXGet() { + return SubmissionController.submissionXGet; + } + + @Override + public Route latestSubmissionsGet() { + return SubmissionController.latestSubmissionsGet; + } + + @Override + public Route submissionsActiveGet() { + return SubmissionController.submissionsActiveGet; + } + + @Override + public Route studentSubmissionsGet() { + return SubmissionController.studentSubmissionsGet; + } + + @Override + public Route approveSubmissionPost() { + return SubmissionController.approveSubmissionPost; + } + + @Override + public Route submissionsReRunPost() { + return SubmissionController.submissionsReRunPost; + } + + // UserController + + @Override + public Route repoPatch() { + return UserController.repoPatch; + } + + @Override + public Route repoPatchAdmin() { + return UserController.repoPatchAdmin; + } + + @Override + public Route repoHistoryAdminGet() { + return UserController.repoHistoryAdminGet; + } +} diff --git a/src/test/java/edu/byu/cs/autograder/score/ScorerTest.java b/src/test/java/edu/byu/cs/autograder/score/ScorerTest.java index 2cdaece6..46b54e78 100644 --- a/src/test/java/edu/byu/cs/autograder/score/ScorerTest.java +++ b/src/test/java/edu/byu/cs/autograder/score/ScorerTest.java @@ -274,6 +274,21 @@ void score_doesNotDecrease_when_distributedHigherPriorScore() throws CanvasExcep Assertions.assertEquals(UNIT_TESTS_POSSIBLE_POINTS, rubricItems.get(Rubric.RubricType.UNIT_TESTS).results().score()); } + @Test + void score_doesDecrease_when_higherPriorScoreOfFailedSubmission() throws CanvasException, DataAccessException { + Submission lastSubmission = previousSubmissionHelper( + new Phase3SubmissionValues(0, CODE_QUALITY_POSSIBLE_POINTS, UNIT_TESTS_POSSIBLE_POINTS, -1), + new Phase3SubmissionValues(PASSOFF_POSSIBLE_POINTS, CODE_QUALITY_POSSIBLE_POINTS, UNIT_TESTS_POSSIBLE_POINTS, 30) + ); + + Assertions.assertNotNull(lastSubmission); + EnumMap rubricItems = lastSubmission.rubric().items(); + + Assertions.assertEquals(PASSOFF_POSSIBLE_POINTS / 2f, rubricItems.get(Rubric.RubricType.PASSOFF_TESTS).results().score()); + Assertions.assertEquals(CODE_QUALITY_POSSIBLE_POINTS / 2f, rubricItems.get(Rubric.RubricType.QUALITY).results().score()); + Assertions.assertEquals(UNIT_TESTS_POSSIBLE_POINTS / 2f, rubricItems.get(Rubric.RubricType.UNIT_TESTS).results().score()); + } + // Helper Methods for constructing /**