Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test server endpoints #483

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
63e3f41
feat: add Server::stop()
ThanGerlek Nov 17, 2024
2bfe27f
feat: add TestServerFacade (a simple client for testing Server endpoi…
ThanGerlek Nov 17, 2024
b56d696
test: add MockEndpointProvider class
ThanGerlek Nov 17, 2024
7f81ef6
test: add ServerTest
ThanGerlek Nov 17, 2024
3c1463d
standardize test names
ThanGerlek Nov 17, 2024
7521e6a
fix: add awaitInitialization()
ThanGerlek Nov 17, 2024
27308ea
use port 8080, not 0
ThanGerlek Nov 17, 2024
bd6104f
fix: return a valid JSON response from MockEndpointProvider
ThanGerlek Nov 17, 2024
d6b6e86
fix: remove irrelevant test
ThanGerlek Nov 17, 2024
4aabb81
test: verify beforeAll and afterAll are called, in the right order, f…
ThanGerlek Nov 18, 2024
2ca3594
test: verify authentication middleware is called when needed
ThanGerlek Nov 18, 2024
9a6edc1
feat: add junit-jupiter-params dependency (for parameterized tests)
ThanGerlek Nov 18, 2024
d91348b
test: change endpoint verification to use parameterized tests rather …
ThanGerlek Nov 18, 2024
076cec9
fix: remove duplicate test
ThanGerlek Nov 18, 2024
3cf5857
refactor: remove unneeded parameters in runHandler()
ThanGerlek Nov 18, 2024
f091e48
feat: add capability of verifying path parameters to MockEndpointProv…
ThanGerlek Nov 18, 2024
a39fbeb
test: verify that pathParams are receiving values
ThanGerlek Nov 18, 2024
c89b8d4
docs: clarify MockEndpointProvider should be used with spy(), not mock()
ThanGerlek Nov 18, 2024
a8786a2
Merge branch 'reduce-endpoint-dependencies' into test-server-endpoints
ThanGerlek Nov 19, 2024
095b864
Merge branch 'reduce-endpoint-dependencies' into test-server-endpoints
ThanGerlek Nov 20, 2024
b37696c
fix: add scheduleShutdown to ServerTests
ThanGerlek Nov 20, 2024
d640614
enhance: rearrange arguments so path is before name first
ThanGerlek Nov 20, 2024
6b0521c
enhance: rearrange endpoint list using paths
ThanGerlek Nov 20, 2024
9ff3cdf
fix: replace HTTP PATCH requests with POST
ThanGerlek Nov 20, 2024
b93d600
add no-longer-patch requests to ServerTest
ThanGerlek Nov 20, 2024
a7276a9
add no-longer-patch requests to ServerTest
ThanGerlek Nov 20, 2024
629352f
Merge remote-tracking branch 'origin/test-server-endpoints' into test…
ThanGerlek Nov 20, 2024
46d9eee
enhance: un-hardcode Server ports
ThanGerlek Nov 21, 2024
6886c1b
test: rmv custom ServerFacadeTest exceptions
ThanGerlek Nov 21, 2024
8d4ad38
Merge branch 'main' into test-server-endpoints
ThanGerlek Nov 25, 2024
4999c4e
fix: add updatePenalties() to ServerTest
ThanGerlek Nov 25, 2024
26aa6e3
fix: call awaitInitialization() before port()
ThanGerlek Nov 25, 2024
58b52f7
Merge branch 'main' into test-server-endpoints
ThanGerlek Dec 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>


</dependencies>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static void main(String[] args) {
throw new RuntimeException(e);
}

new Server(endpointProvider).start(8080);
new Server(endpointProvider).start();

try {
SubmissionService.reRunSubmissionsInQueue();
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/edu/byu/cs/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@
import static spark.Spark.halt;

public class UserController {
public static final Route repoPatch = (req, res) -> {
public static final Route setRepoUrl = (req, res) -> {
User user = req.session().attribute("user");
applyRepoPatch(user.netId(), null, req, res);
setRepoUrl(user.netId(), null, req, res);
return "Successfully updated repoUrl";
};

public static final Route repoPatchAdmin = (req, res) -> {
public static final Route setRepoUrlAdmin = (req, res) -> {
User admin = req.session().attribute("user");
String studentNetId = req.params(":netId");
applyRepoPatch(studentNetId, admin.netId(), req, res);
setRepoUrl(studentNetId, admin.netId(), req, res);
return "Successfully updated repoUrl for user: " + studentNetId;
};

Expand All @@ -52,7 +52,7 @@ public class UserController {
return Serializer.serialize(updates);
};

private static void applyRepoPatch(String studentNetId, String adminNetId, Request req, Response res) {
private static void setRepoUrl(String studentNetId, String adminNetId, Request req, Response res) {
JsonObject jsonObject = new Gson().fromJson(req.body(), JsonObject.class);
String repoUrl = new Gson().fromJson(jsonObject.get("repoUrl"), String.class);

Expand Down
15 changes: 13 additions & 2 deletions src/main/java/edu/byu/cs/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.byu.cs.server.endpointprovider.EndpointProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.Spark;

import static spark.Spark.*;

Expand All @@ -16,12 +17,20 @@ public Server(EndpointProvider endpointProvider) {
this.provider = endpointProvider;
}

public int start() {
return start(0);
}

public int start(int desiredPort) {
int chosenPort = setupEndpoints(desiredPort);
LOGGER.info("Server started on port {}", chosenPort);
return chosenPort;
}

public void stop() {
Spark.stop();
}

private int setupEndpoints(int port) {
port(port);

Expand All @@ -45,7 +54,7 @@ private int setupEndpoints(int port) {
if (!req.requestMethod().equals("OPTIONS")) provider.verifyAuthenticatedMiddleware().handle(req, res);
});

patch("/repo", provider.repoPatch());
post("/repo", provider.setRepoUrl());

get("/submit", provider.submitGet());
post("/submit", provider.submitPost());
Expand All @@ -64,7 +73,7 @@ private int setupEndpoints(int port) {
if (!req.requestMethod().equals("OPTIONS")) provider.verifyAdminMiddleware().handle(req, res);
});

patch("/repo/:netId", provider.repoPatchAdmin());
post("/repo/:netId", provider.setRepoUrlAdmin());

get("/repo/history", provider.repoHistoryAdminGet());

Expand Down Expand Up @@ -118,6 +127,8 @@ private int setupEndpoints(int port) {

init();

awaitInitialization();

return port();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public interface EndpointProvider {

// UserController

Route repoPatch();
Route repoPatchAdmin();
Route setRepoUrl();
Route setRepoUrlAdmin();
Route repoHistoryAdminGet();
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class EndpointProviderImpl implements EndpointProvider {
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-Methods", "GET,PUT,POST,DELETE,OPTIONS");
response.header("Access-Control-Allow-Credentials", "true");
response.header("Access-Control-Allow-Origin", ApplicationProperties.frontendUrl());
};
Expand Down Expand Up @@ -195,13 +195,13 @@ public Route submissionsReRunPost() {
// UserController

@Override
public Route repoPatch() {
return UserController.repoPatch;
public Route setRepoUrl() {
return UserController.setRepoUrl;
}

@Override
public Route repoPatchAdmin() {
return UserController.repoPatchAdmin;
public Route setRepoUrlAdmin() {
return UserController.setRepoUrlAdmin;
}

@Override
Expand Down
151 changes: 151 additions & 0 deletions src/test/java/edu/byu/cs/server/ServerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package edu.byu.cs.server;

import edu.byu.cs.server.endpointprovider.MockEndpointProvider;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InOrder;

import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;

import static org.mockito.Mockito.*;

class ServerTest {
private static TestServerFacade serverFacade;
private static Server server;

private static final MockEndpointProvider mockedMockProvider = spy(new MockEndpointProvider());

public static Stream<Arguments> getPathParamEndpoints() {
return Stream.of(
Arguments.of( "GET", "/api/submission", "submissionXGet", ":phase"),
Arguments.of( "GET", "/api/admin/analytics/commit", "commitAnalyticsGet", ":option"),
Arguments.of("POST", "/api/admin/repo", "setRepoUrlAdmin", ":netid"),
Arguments.of( "GET", "/api/admin/honorChecker/zip", "honorCheckerZipGet", ":section"),
Arguments.of( "GET", "/api/admin/submissions/latest", "latestSubmissionsGet", ":count"),
Arguments.of( "GET", "/api/admin/submissions/student", "studentSubmissionsGet", ":netid")
);
// api/admin/config/penalties
}

public static Stream<Arguments> getEndpoints() {
return Stream.of(
Arguments.of( "GET", "/auth/callback", "callbackGet"),
Arguments.of( "GET", "/auth/login", "loginGet"),
Arguments.of("POST", "/auth/logout", "logoutPost"),

Arguments.of( "GET", "/api/config", "getConfigStudent"),
Arguments.of( "GET", "/api/latest", "latestSubmissionForMeGet"),
Arguments.of( "GET", "/api/me", "meGet"),
Arguments.of("POST", "/api/repo", "setRepoUrl"),
Arguments.of( "GET", "/api/submission", "submissionXGet"),
Arguments.of( "GET", "/api/submit", "submitGet"),
Arguments.of("POST", "/api/submit", "submitPost"),

Arguments.of( "GET", "/api/admin/config", "getConfigAdmin"),
Arguments.of("POST", "/api/admin/config/banner", "updateBannerMessage"),
Arguments.of("POST", "/api/admin/config/courseIds", "updateCourseIdsPost"),
Arguments.of( "GET", "/api/admin/config/courseIds", "updateCourseIdsUsingCanvasGet"),
Arguments.of("POST", "/api/admin/config/penalties", "updatePenalties"),
Arguments.of("POST", "/api/admin/config/phases", "updateLivePhases"),
Arguments.of("POST", "/api/admin/config/phases/shutdown", "scheduleShutdown"),

Arguments.of( "GET", "/api/admin/submissions/active", "submissionsActiveGet"),
Arguments.of("POST", "/api/admin/submissions/approve", "approveSubmissionPost"),
Arguments.of( "GET", "/api/admin/submissions/latest", "latestSubmissionsGet"),
Arguments.of("POST", "/api/admin/submissions/rerun", "submissionsReRunPost"),
Arguments.of("POST", "/api/admin/submit", "adminRepoSubmitPost"),

Arguments.of( "GET", "/api/admin/analytics/commit", "commitAnalyticsGet"),
Arguments.of( "GET", "/api/admin/repo/history", "repoHistoryAdminGet"),
Arguments.of( "GET", "/api/admin/sections", "sectionsGet"),
Arguments.of( "GET", "/api/admin/test_mode", "testModeGet"),
Arguments.of( "GET", "/api/admin/users", "usersGet")
);
}

@AfterAll
static void stopServer() {
server.stop();
}

@BeforeAll
public static void init() {
server = new Server(mockedMockProvider);
int port = server.start();
System.out.println("Started test HTTP server on " + port);

serverFacade = new TestServerFacade("localhost", port);
}

@AfterEach
public void tearDown() {
reset(mockedMockProvider);
}

@ParameterizedTest
@MethodSource("getEndpoints")
public void verifyEndpointCallsItsHandlersExactlyOnceInOrder(String method, String path, String endpointName) throws IOException {
// When
serverFacade.makeRequest(method, path);

InOrder inOrder = inOrder(mockedMockProvider);

// Then
inOrder.verify(mockedMockProvider, times(1)).runHandler("beforeAll");
this.verifyInOrder_authenticationMiddleware(path, inOrder);
inOrder.verify(mockedMockProvider, times(1)).runHandler(endpointName);
inOrder.verify(mockedMockProvider, times(1)).runHandler("afterAll");
}

@ParameterizedTest
@MethodSource("getPathParamEndpoints")
public void verifyPathParameterHasAValueWhenGivenOne(String method, String path, String endpointName,
String pathParamName) throws IOException {
// Given
String fullPath = path + "/testParamValue";

// When
serverFacade.makeRequest(method, fullPath);

// Then
verify(mockedMockProvider, times(1)).hasPathParam(endpointName, pathParamName, "testParamValue");
}

private void verifyInOrder_authenticationMiddleware(String path, InOrder inOrder) {
List<String> pathNodes = Arrays.stream(path.split("/")).toList();

if (!pathNodes.contains("api")) {
return;
}

if (!pathNodes.contains("auth")) {
// Requires authentication
inOrder.verify(mockedMockProvider, times(1)).runHandler("verifyAuthenticatedMiddleware");
}

if (pathNodes.contains("admin")) {
// Requires admin
inOrder.verify(mockedMockProvider, times(1)).runHandler("verifyAdminMiddleware");
}
}

@Test
void nonexistent_GET_endpoint_calls_beforeAll_then_defaultGet_then_afterAll_exactly_once_in_order() throws IOException {
serverFacade.makeRequest("GET", "/iDoNotExist");

// Verify they ran in order
InOrder inOrder = inOrder(mockedMockProvider);
inOrder.verify(mockedMockProvider).runHandler("beforeAll");
inOrder.verify(mockedMockProvider).runHandler("defaultGet");
inOrder.verify(mockedMockProvider).runHandler("afterAll");

// Verify they only ran once
verify(mockedMockProvider, times(1)).runHandler("beforeAll");
verify(mockedMockProvider, times(1)).runHandler("defaultGet");
verify(mockedMockProvider, times(1)).runHandler("afterAll");
}
}
Loading
Loading