Commit d59efc24 authored by Martin Lowe's avatar Martin Lowe 🇨🇦 Committed by Martin Lowe
Browse files

Add tests for the FormState request filter

Adds mockito to the project, updates auth helper to build basic tests
(DRY). The test mocks used for the persistence DAO can likely be used
elsewhere, and remove the Mock service that was created.
parent 67e8dcc5
......@@ -122,6 +122,11 @@
<artifactId>json-schema-validator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
......
......@@ -36,7 +36,7 @@ import io.undertow.httpcore.HttpMethodNames;
public class FormStateFilter implements ContainerRequestFilter {
public static final Logger LOGGER = LoggerFactory.getLogger(FormStateFilter.class);
private static final Pattern SPECIFIC_FORM_URI_PATTERN = Pattern.compile("^\\/form\\/([^\\/]+)\\/?");
private static final Pattern SPECIFIC_FORM_URI_PATTERN = Pattern.compile("^\\/form\\/([^\\/]+)\\/?.*");
@Inject
PersistenceDao dao;
......@@ -50,7 +50,8 @@ public class FormStateFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext requestContext) throws IOException {
// check if update method
String httpMethod = requestContext.getMethod();
if (httpMethod.equalsIgnoreCase(HttpMethodNames.POST) || httpMethod.equalsIgnoreCase(HttpMethodNames.PUT)) {
if (httpMethod.equalsIgnoreCase(HttpMethodNames.POST) || httpMethod.equalsIgnoreCase(HttpMethodNames.PUT)
|| httpMethod.equalsIgnoreCase(HttpMethodNames.DELETE)) {
// check if path indicates a specific form
String path = requestContext.getUriInfo().getPath();
Matcher m = SPECIFIC_FORM_URI_PATTERN.matcher(path);
......
/*
* Copyright (C) 2019 Eclipse Foundation and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipsefoundation.react.resources.mapper;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.eclipsefoundation.core.model.Error;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Martin Lowe
*/
@Provider
public class BadRequestExceptionMapper implements ExceptionMapper<BadRequestException> {
private static final Logger LOGGER = LoggerFactory.getLogger(BadRequestExceptionMapper.class);
@Override
public Response toResponse(BadRequestException exception) {
LOGGER.error(exception.getMessage(), exception);
return new Error(Status.BAD_REQUEST, "Could not process the given request: " + exception.getMessage()).asResponse();
}
}
package org.eclipsefoundation.react.request;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import javax.ws.rs.core.Response.Status;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.namespace.FormState;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.ArgumentMatcher;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectMock;
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
@QuarkusTest
class FormStateFilterTest {
static final String GENERATED_RANDOM_HEADER = "sample-header-value";
@InjectMock
PersistenceDao dao;
// these will be reset before each test, do not rely on static data here
MembershipForm inprogress;
MembershipForm completed;
MembershipForm submitted;
Contact contact;
FormOrganization org;
FormWorkingGroup wg;
/**
* Set up all of the fake data for every request. While not a perfect representation of data, is "good enough" for
* sake of testing.
*/
@BeforeEach
void setup() {
// INPROGRESS
inprogress = (MembershipForm) getForm(FormState.INPROGRESS);
Mockito.when(dao.get(ArgumentMatchers.argThat(new ArgumentMatcher<RDBMSQuery<BareNode>>() {
@Override
public boolean matches(RDBMSQuery<BareNode> argument) {
return argument != null && MembershipForm.class.equals(argument.getDocType())
&& FormState.INPROGRESS.name().equals(argument.getWrapper().getHeader(GENERATED_RANDOM_HEADER));
}
}))).thenReturn(new ArrayList<BareNode>(Arrays.asList(inprogress)));
Mockito.when(
dao.getReference(ArgumentMatchers.eq(inprogress.getId()), ArgumentMatchers.eq(MembershipForm.class)))
.thenReturn(inprogress);
// SUBMITTED
submitted = (MembershipForm) getForm(FormState.SUBMITTED);
Mockito.when(dao.get(ArgumentMatchers.argThat(new ArgumentMatcher<RDBMSQuery<BareNode>>() {
@Override
public boolean matches(RDBMSQuery<BareNode> argument) {
return argument != null && MembershipForm.class.equals(argument.getDocType())
&& FormState.SUBMITTED.name().equals(argument.getWrapper().getHeader(GENERATED_RANDOM_HEADER));
}
}))).thenReturn(new ArrayList<BareNode>(Arrays.asList(submitted)));
Mockito.when(
dao.getReference(ArgumentMatchers.eq(submitted.getId()), ArgumentMatchers.eq(MembershipForm.class)))
.thenReturn(submitted);
// COMPLETE
completed = (MembershipForm) getForm(FormState.COMPLETE);
Mockito.when(dao.get(ArgumentMatchers.argThat(new ArgumentMatcher<RDBMSQuery<BareNode>>() {
@Override
public boolean matches(RDBMSQuery<BareNode> argument) {
return argument != null && MembershipForm.class.equals(argument.getDocType())
&& FormState.COMPLETE.name().equals(argument.getWrapper().getHeader(GENERATED_RANDOM_HEADER));
}
}))).thenReturn(new ArrayList<BareNode>(Arrays.asList(completed)));
Mockito.when(
dao.getReference(ArgumentMatchers.eq(completed.getId()), ArgumentMatchers.eq(MembershipForm.class)))
.thenReturn(completed);
// contact data mocks
contact = DtoHelper.generateContact(completed, Optional.empty());
contact.setId(UUID.randomUUID().toString());
Mockito.when(dao.get(ArgumentMatchers.argThat(new ArgumentMatcher<RDBMSQuery<Contact>>() {
@Override
public boolean matches(RDBMSQuery<Contact> argument) {
return argument != null && Contact.class.equals(argument.getDocType());
}
}))).thenReturn(new ArrayList<Contact>(Arrays.asList(contact)));
Mockito.when(dao.getReference(ArgumentMatchers.eq(contact.getId()), ArgumentMatchers.eq(Contact.class)))
.thenReturn(contact);
org = DtoHelper.generateOrg(completed);
org.setId(UUID.randomUUID().toString());
Mockito.when(dao.get(ArgumentMatchers.argThat(new ArgumentMatcher<RDBMSQuery<FormOrganization>>() {
@Override
public boolean matches(RDBMSQuery<FormOrganization> argument) {
return argument != null && FormOrganization.class.equals(argument.getDocType());
}
}))).thenReturn(new ArrayList<FormOrganization>(Arrays.asList(org)));
Mockito.when(dao.getReference(ArgumentMatchers.eq(org.getId()), ArgumentMatchers.eq(FormOrganization.class)))
.thenReturn(org);
wg = DtoHelper.generateWorkingGroup(completed);
wg.setId(UUID.randomUUID().toString());
Mockito.when(dao.get(ArgumentMatchers.argThat(new ArgumentMatcher<RDBMSQuery<FormWorkingGroup>>() {
@Override
public boolean matches(RDBMSQuery<FormWorkingGroup> argument) {
return argument != null && FormWorkingGroup.class.equals(argument.getDocType());
}
}))).thenReturn(new ArrayList<FormWorkingGroup>(Arrays.asList(wg)));
Mockito.when(dao.getReference(ArgumentMatchers.eq(wg.getId()), ArgumentMatchers.eq(FormWorkingGroup.class)))
.thenReturn(wg);
// handle attempts to add by returning the list (not exact match of func, but close enough)
Mockito.when(dao.add(ArgumentMatchers.any(), ArgumentMatchers.anyList()))
.then(AdditionalAnswers.returnsSecondArg());
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = "viewer")
void filterInprogressForms_mutator() {
// attempting to modify submitted forms should pass
testMutatorsWithStatus(submitted.getId(), FormState.INPROGRESS, Status.OK.getStatusCode());
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = "viewer")
void filterSubmittedForms_mutator() {
// attempting to modify submitted forms should fail
testMutatorsAssertBadRequest(submitted.getId(), FormState.SUBMITTED);
}
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = "viewer")
void filterCompleteForms_mutator() {
// attempting to modify complete forms should fail
testMutatorsAssertBadRequest(completed.getId(), FormState.COMPLETE);
}
/**
* Checks to make sure accessors are not affected when state is {@link FormState.SUBMITTED}
*/
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = "viewer")
void filterSubmittedForms_accessor() {
testAccessors(submitted.getId(), FormState.SUBMITTED);
}
/**
* Checks to make sure accessors are not affected when state is {@link FormState.COMPLETE}
*/
@Test
@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = "viewer")
void filterCompletedForms_accessor() {
testAccessors(completed.getId(), FormState.COMPLETE);
}
private void testMutatorsAssertBadRequest(String formID, FormState state) {
testMutatorsWithStatus(formID, state, Status.BAD_REQUEST.getStatusCode());
}
private void testMutatorsWithStatus(String formID, FormState state, int status) {
String headerValue = state.name();
// get the correct form for current calls
MembershipForm form;
switch (state) {
case COMPLETE:
form = completed;
break;
case SUBMITTED:
form = submitted;
break;
default:
form = inprogress;
}
// test the PUT, POST, and DELETE calls for all form endpoints
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue)
.contentType(ContentType.JSON).accept(ContentType.JSON).body(form).when().put("/form/{id}", formID)
.then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue).when()
.delete("/form/{id}", formID).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue)
.contentType(ContentType.JSON).accept(ContentType.JSON).body(contact).when()
.post("/form/{id}/contacts", formID).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue)
.contentType(ContentType.JSON).accept(ContentType.JSON).body(contact).when()
.put("/form/{id}/contacts/{contactID}", formID, contact.getId()).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue).when()
.delete("/form/{id}/contacts/{contactID}", formID, contact.getId()).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue)
.contentType(ContentType.JSON).accept(ContentType.JSON).body(org).when()
.post("/form/{id}/organizations", formID).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue)
.contentType(ContentType.JSON).accept(ContentType.JSON).body(org).when()
.put("/form/{id}/organizations/{orgID}", formID, org.getId()).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue).when()
.delete("/form/{id}/organizations/{orgID}", formID, org.getId()).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue)
.contentType(ContentType.JSON).accept(ContentType.JSON).body(wg).when()
.post("/form/{id}/working_groups", formID).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue)
.contentType(ContentType.JSON).accept(ContentType.JSON).body(wg).when()
.put("/form/{id}/working_groups/{wgID}", formID, wg.getId()).then().statusCode(status);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue).when()
.delete("/form/{id}/working_groups/{wgID}", formID, wg.getId()).then().statusCode(status);
}
private void testAccessors(String formID, FormState state) {
String headerValue = state.name();
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue).when()
.get("/form/{id}", formID).then().statusCode(200).body("state", Matchers.equalTo(state.name()));
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue).when()
.get("/form/{id}/contacts", formID).then().statusCode(200);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue).when()
.get("/form/{id}/organizations", formID).then().statusCode(200);
AuthHelper.getAuthorizedResteasyRequest().header(GENERATED_RANDOM_HEADER, headerValue).when()
.get("/form/{id}/working_groups", formID).then().statusCode(200);
}
/**
* Generates a form with an ID and given state.
*
* @param state the state to set into the form
* @return the new mock form
*/
private BareNode getForm(FormState state) {
MembershipForm out = DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME));
out.setState(state);
out.setId(UUID.randomUUID().toString());
return out;
}
}
......@@ -2,11 +2,13 @@ package org.eclipsefoundation.react.test.helper;
import static io.restassured.RestAssured.given;
import java.util.Collections;
import java.util.Set;
import org.eclipsefoundation.core.helper.CSRFHelper;
import io.restassured.filter.session.SessionFilter;
import io.restassured.specification.RequestSpecification;
import io.smallrye.jwt.build.Jwt;
public class AuthHelper {
......@@ -40,6 +42,12 @@ public class AuthHelper {
.issuer("https://server.example.com").audience("https://service.example.com").jws().keyId("1").sign();
}
public static RequestSpecification getAuthorizedResteasyRequest() {
SessionFilter sessionFilter = new SessionFilter();
return given().filter(sessionFilter).auth().oauth2(AuthHelper.getAccessToken(Collections.emptySet()))
.header(CSRFHelper.CSRF_HEADER_NAME, AuthHelper.getCSRFValue(sessionFilter));
}
private AuthHelper() {
}
}
......@@ -59,7 +59,6 @@ public class DtoHelper {
a.setPostalCode(RandomStringUtils.randomAlphabetic(4, 10));
a.setProvinceState(RandomStringUtils.randomAlphabetic(2));
a.setStreet(RandomStringUtils.randomAlphabetic(4, 10));
a.setOrganization(o);
o.setAddress(a);
return o;
}
......@@ -68,7 +67,7 @@ public class DtoHelper {
List<Contact> out = new ArrayList<>();
for (int j = 0; j < ContactTypes.values().length; j++) {
// randomly skip contacts
if (Math.random() > 0.5) {
if (Math.random() > 0.5 && j > 0) {
continue;
}
out.add(generateContact(form, Optional.of(ContactTypes.values()[j])));
......@@ -91,6 +90,15 @@ public class DtoHelper {
List<FormWorkingGroup> wgs = new ArrayList<>();
// randomly create WG entries
while (true) {
wgs.add(generateWorkingGroup(form));
if (Math.random() > 0.5) {
break;
}
}
return wgs;
}
public static FormWorkingGroup generateWorkingGroup(MembershipForm form) {
FormWorkingGroup wg = new FormWorkingGroup();
wg.setWorkingGroupID(RandomStringUtils.randomAlphabetic(4, 10));
wg.setParticipationLevel(RandomStringUtils.randomAlphabetic(4, 10));
......@@ -99,13 +107,7 @@ public class DtoHelper {
wg.setEffectiveDate(new Date(inst.getEpochSecond()));
wg.setContact(generateContact(form, Optional.empty()));
wg.setForm(form);
wgs.add(wg);
if (Math.random() > 0.5) {
break;
}
}
return wgs;
return wg;
}
private static String generateEmail() {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment