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

Update tests to make use of DtoHelper class, added tests

Added tests for the ConstraintViolationWrapFactory, added DtoHelper test
util that generates valid DB objects (important with new validation
logic).
parent bb075367
package org.eclipsefoundation.react.model;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import org.eclipsefoundation.persistence.dto.BareNode;
/**
* Factory for building wrapped constraint violation objects. These are to be returned in the case of requests that are
* manually validated to better inform the user of the issues with the request.
*
* @author Martin Lowe
*/
public class ConstraintViolationWrapFactory {
public <T extends BareNode> ConstraintViolationWrap build(T object, ConstraintViolation<T> inner) {
return new ConstraintViolationWrap(object.getId(), object.getClass().getSimpleName(), inner.getInvalidValue(),
inner.getPropertyPath().toString());
/**
* Builds the set of constraint violation wrapped objects from the violation object set from the validator.
*
* @param <T> the DTO type object that was validated
* @param inner the constraint violation object that should be wrapped
* @return a set of wrapped constraint violation objects representing the passed collection
*/
public <T extends BareNode> Set<ConstraintViolationWrap> build(Set<ConstraintViolation<T>> inner) {
return inner.stream().map(this::build).collect(Collectors.toSet());
}
private <T extends BareNode> ConstraintViolationWrap build(ConstraintViolation<T> inner) {
return new ConstraintViolationWrap(inner.getRootBean().getId(), inner.getRootBeanClass().getSimpleName(),
inner.getInvalidValue(), inner.getPropertyPath().toString());
}
/**
* Wrap class for the {@link ConstraintViolation} object to allow for simple JSON printing.
*
* @author Martin Lowe
*/
public class ConstraintViolationWrap {
private Object rootID;
private String type;
......
......@@ -35,6 +35,7 @@ import io.undertow.httpcore.HttpMethodNames;
@Provider
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\\/([^\\/]+)\\/?");
@Inject
......
......@@ -58,7 +58,7 @@ import io.quarkus.security.Authenticated;
*
* @author Martin Lowe
*/
//@Authenticated
@Authenticated
@Path("form")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
......@@ -95,7 +95,7 @@ public class MembershipFormResource extends AbstractRESTResource {
// check if user is allowed to modify these resources
Response r = checkAccess(formID);
if (r != null) {
//return r;
return r;
}
// create parameter map
MultivaluedMap<String, String> params = new MultivaluedMapImpl<>();
......@@ -129,7 +129,7 @@ public class MembershipFormResource extends AbstractRESTResource {
// check if user is allowed to modify these resources
Response r = checkAccess(formID);
if (r != null) {
//return r;
return r;
}
mem.setUserID(ident.getPrincipal().getName());
// need to fetch ref to use attached entity
......@@ -166,7 +166,7 @@ public class MembershipFormResource extends AbstractRESTResource {
// check if user is allowed to modify these resources
Response r = checkAccess(formID);
if (r != null) {
//return r;
return r;
}
// create parameter map
MultivaluedMap<String, String> params = new MultivaluedMapImpl<>();
......@@ -204,8 +204,8 @@ public class MembershipFormResource extends AbstractRESTResource {
}
// send the forms to the mailing service
//mailer.sendToFormAuthor(mf);
//mailer.sendToMembershipTeam(mf, !orgs.isEmpty() ? orgs.get(0) : null, wgs, contacts);
mailer.sendToFormAuthor(mf);
mailer.sendToMembershipTeam(mf, !orgs.isEmpty() ? orgs.get(0) : null, wgs, contacts);
// update the state and push the update
mf.setState(FormState.SUBMITTED);
......@@ -215,8 +215,7 @@ public class MembershipFormResource extends AbstractRESTResource {
private <T extends BareNode> Set<ConstraintViolationWrap> recordViolations(List<T> items) {
ConstraintViolationWrapFactory factory = new ConstraintViolationWrapFactory();
return items.stream()
.flatMap(item -> validator.validate(item).stream().map(violation -> factory.build(item, violation)))
return items.stream().flatMap(item -> factory.build(validator.validate(item)).stream())
.collect(Collectors.toSet());
}
}
package org.eclipsefoundation.react.model;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.model.ConstraintViolationWrapFactory.ConstraintViolationWrap;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
class ConstraintViolationWrapFactoryTest {
@Inject
Validator validator;
@Test
void testBuild_success() {
// generate an invalid validatable resource
MembershipForm f = DtoHelper.generateForm(Optional.empty());
f.setUserID(null);
f.setMembershipLevel(null);
Set<ConstraintViolation<MembershipForm>> violations = validator.validate(f);
Set<ConstraintViolationWrap> out = new ConstraintViolationWrapFactory().build(violations);
compareConstraints(out, violations, f);
}
@Test
void testBuild_acceptsEmpty() {
Set<ConstraintViolationWrap> out = new ConstraintViolationWrapFactory().build(Collections.emptySet());
Assertions.assertNotNull(out);
Assertions.assertTrue(out.isEmpty());
}
@Test
void testBuild_stateless() {
// generate an invalid validatable resource
MembershipForm f1 = DtoHelper.generateForm(Optional.empty());
f1.setUserID(null);
f1.setMembershipLevel(null);
MembershipForm f2 = DtoHelper.generateForm(Optional.empty());
f2.setPurchaseOrderRequired(null);
f2.setState(null);
ConstraintViolationWrapFactory factory = new ConstraintViolationWrapFactory();
Set<ConstraintViolation<MembershipForm>> violations1 = validator.validate(f1);
Set<ConstraintViolation<MembershipForm>> violations2 = validator.validate(f2);
// run 2 build operations with same factory
Set<ConstraintViolationWrap> out1 = factory.build(violations1);
Set<ConstraintViolationWrap> out2 = factory.build(violations2);
// check that the violations are properly mapped to the given form objects still
compareConstraints(out1, violations1, f1);
compareConstraints(out2, violations2, f2);
}
void compareConstraints(Set<ConstraintViolationWrap> out, Set<ConstraintViolation<MembershipForm>> violations,
MembershipForm f) {
// same size in and out
Assertions.assertEquals(violations.size(), out.size());
for (ConstraintViolationWrap wrap : out) {
// match on property path (most unique value)
List<ConstraintViolation<MembershipForm>> violationMatches = violations.stream()
.filter(v -> v.getPropertyPath().toString().equals(wrap.getPath())).collect(Collectors.toList());
// that they match one to one
Assertions.assertEquals(1, violationMatches.size());
// match the field values to expected values
ConstraintViolation<MembershipForm> violation = violationMatches.get(0);
Assertions.assertEquals(violation.getInvalidValue(), wrap.getValue());
Assertions.assertEquals(violation.getPropertyPath().toString(), wrap.getPath());
Assertions.assertEquals(f.getId(), wrap.getRootID());
Assertions.assertEquals(f.getClass().getSimpleName(), wrap.getType());
}
}
}
......@@ -13,6 +13,7 @@ import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.namespace.ContactTypes;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import org.eclipsefoundation.react.test.helper.SchemaNamespaceHelper;
import org.hamcrest.text.IsEmptyString;
import org.junit.jupiter.api.Assertions;
......@@ -361,13 +362,9 @@ public class ContactResourceTest {
}
private String generateSample(Optional<String> id) {
Contact out = new Contact();
Contact out = DtoHelper.generateContact(DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME)),
Optional.of(ContactTypes.ACCOUNTING));
id.ifPresent(out::setId);
out.setEmail("sample@sample.com");
out.setfName("First Name");
out.setlName("Last Name");
out.setTitle("sample title");
out.setType(ContactTypes.ACCOUNTING);
return ContactResourceTest.jsonb.toJson(out);
}
}
......@@ -10,9 +10,9 @@ import javax.json.bind.Jsonb;
import org.eclipsefoundation.core.config.JsonBConfig;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.react.dto.Address;
import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import org.eclipsefoundation.react.test.helper.SchemaNamespaceHelper;
import org.hamcrest.text.IsEmptyString;
import org.junit.jupiter.api.Assertions;
......@@ -372,17 +372,8 @@ public class FormOrganizationResourceTest {
}
private FormOrganization generateSampleRaw(Optional<String> id) {
FormOrganization out = new FormOrganization();
FormOrganization out = DtoHelper.generateOrg(DtoHelper.generateForm(Optional.empty()));
id.ifPresent(out::setId);
out.setLegalName("Sample Organization");
out.setTwitterHandle("TwitterHandle");
Address a = new Address();
a.setCity("Sample");
a.setCountry("Country");
a.setPostalCode("Postal Code");
a.setProvinceState("ON");
a.setStreet("Sample street rd");
out.setAddress(a);
return out;
}
......
......@@ -3,7 +3,6 @@ package org.eclipsefoundation.react.resources;
import static io.restassured.RestAssured.given;
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
import java.sql.Date;
import java.util.Collections;
import java.util.Optional;
......@@ -11,10 +10,9 @@ import javax.json.bind.Jsonb;
import org.eclipsefoundation.core.config.JsonBConfig;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.react.dto.Contact;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.namespace.ContactTypes;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import org.eclipsefoundation.react.test.helper.SchemaNamespaceHelper;
import org.hamcrest.text.IsEmptyString;
import org.junit.jupiter.api.Assertions;
......@@ -374,18 +372,8 @@ public class FormWorkingGroupsResourceTest {
}
private FormWorkingGroup generateSampleRaw(Optional<String> id) {
FormWorkingGroup out = new FormWorkingGroup();
out.setEffectiveDate(new Date(System.currentTimeMillis()));
out.setParticipationLevel("participant");
out.setWorkingGroupID("internet-things-iot");
FormWorkingGroup out = DtoHelper.generateWorkingGroups(DtoHelper.generateForm(Optional.empty())).get(0);
id.ifPresent(out::setId);
Contact c = new Contact();
c.setEmail("sample@sample.com");
c.setfName("First Name");
c.setlName("Last Name");
c.setTitle("sample title");
c.setType(ContactTypes.ACCOUNTING);
out.setContact(c);
return out;
}
......
......@@ -12,6 +12,7 @@ import org.eclipsefoundation.core.config.JsonBConfig;
import org.eclipsefoundation.core.helper.CSRFHelper;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import org.eclipsefoundation.react.test.helper.SchemaNamespaceHelper;
import org.hamcrest.text.IsEmptyString;
import org.junit.jupiter.api.Assertions;
......@@ -26,7 +27,7 @@ import io.restassured.http.ContentType;
@QuarkusTest
@QuarkusTestResource(OidcWiremockTestResource.class)
public class MembershipFormResourceTest {
class MembershipFormResourceTest {
public static final String SAMPLE_FORM_UUID = "form-uuid";
public static final String FORMS_BASE_URL = "/form";
public static final String FORMS_BY_ID_URL = FORMS_BASE_URL + "/{id}";
......@@ -230,7 +231,7 @@ public class MembershipFormResourceTest {
SessionFilter sessionFilter = new SessionFilter();
given().filter(sessionFilter).auth().oauth2(AuthHelper.getAccessToken(Collections.emptySet()))
.header(CSRFHelper.CSRF_HEADER_NAME, AuthHelper.getCSRFValue(sessionFilter))
.body(generateSample(Optional.empty())).contentType(ContentType.JSON).when()
.body(generateSample(Optional.of(SAMPLE_FORM_UUID))).contentType(ContentType.JSON).when()
.put(FORMS_BY_ID_URL, SAMPLE_FORM_UUID).then().statusCode(200);
}
......@@ -321,14 +322,8 @@ public class MembershipFormResourceTest {
}
private String generateSample(Optional<String> id) {
MembershipForm out = new MembershipForm();
MembershipForm out = DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME));
id.ifPresent(out::setId);
out.setMembershipLevel("sample");
out.setPurchaseOrderRequired("no");
out.setRegistrationCountry("CA");
out.setSigningAuthority(false);
out.setVatNumber("123456789");
out.setUserID("sample_user");
return MembershipFormResourceTest.jsonb.toJson(out);
}
}
......@@ -25,7 +25,7 @@ import io.restassured.filter.session.SessionFilter;
*/
@QuarkusTest
@QuarkusTestResource(OidcWiremockTestResource.class)
public class OIDCResourceTest {
class OIDCResourceTest {
@Test
void ensureCSRFEndpoint() {
......
package org.eclipsefoundation.react.service.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
......@@ -13,6 +12,7 @@ import org.eclipsefoundation.react.dto.FormOrganization;
import org.eclipsefoundation.react.dto.FormWorkingGroup;
import org.eclipsefoundation.react.dto.MembershipForm;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
......@@ -44,13 +44,13 @@ class DefaultMailerServiceTest {
mailbox.clear();
}
// Test disabled temporarily. Quarkus 2.x adds better support for mocked users and should be more easily able to test
//@Test
//@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = "viewer")
// Test disabled temporarily. Quarkus 2.x adds better support for mocked users and should be more easily able to
// test
// @Test
// @TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = "viewer")
void sendToFormAuthor_success() {
// set up form to submit through mock service
MembershipForm f = new MembershipForm();
f.setUserID(AuthHelper.TEST_USER_NAME);
MembershipForm f = DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME));
// perform the action
mailerService.sendToFormAuthor(f);
......@@ -70,8 +70,7 @@ class DefaultMailerServiceTest {
@Test
void sendToFormAuthor_anon() {
// set up form to submit through mock service
MembershipForm f = new MembershipForm();
f.setUserID(AuthHelper.TEST_USER_NAME);
MembershipForm f = DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME));
// verify that it failed to send due to state exception
Assertions.assertThrows(IllegalStateException.class, () -> {
......@@ -97,12 +96,10 @@ class DefaultMailerServiceTest {
//@TestSecurity(user = AuthHelper.TEST_USER_NAME, roles = "viewer")
void sendToMembershipTeam_success() {
// set up form to submit through mock service
MembershipForm f = new MembershipForm();
FormOrganization org = new FormOrganization();
FormWorkingGroup wg = new FormWorkingGroup();
List<FormWorkingGroup> wgs = new ArrayList<>(Arrays.asList(wg));
Contact c = new Contact();
List<Contact> contacts = new ArrayList<>(Arrays.asList(c));
MembershipForm f = DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME));
FormOrganization org = DtoHelper.generateOrg(f);
List<FormWorkingGroup> wgs = DtoHelper.generateWorkingGroups(f);
List<Contact> contacts = DtoHelper.generateContacts(f);
// perform the action
mailerService.sendToMembershipTeam(f, org, wgs, contacts);
......@@ -122,12 +119,10 @@ class DefaultMailerServiceTest {
@Test
void sendToMembershipTeam_missingData() {
// set up form to submit through mock service
MembershipForm f = new MembershipForm();
FormOrganization org = new FormOrganization();
FormWorkingGroup wg = new FormWorkingGroup();
List<FormWorkingGroup> wgs = Arrays.asList(wg);
Contact c = new Contact();
List<Contact> contacts = Arrays.asList(c);
MembershipForm f = DtoHelper.generateForm(Optional.of(AuthHelper.TEST_USER_NAME));
FormOrganization org = DtoHelper.generateOrg(f);
List<FormWorkingGroup> wgs = DtoHelper.generateWorkingGroups(f);
List<Contact> contacts = DtoHelper.generateContacts(f);
// perform the action
Assertions.assertThrows(IllegalStateException.class, () -> {
......
package org.eclipsefoundation.react.test.dao;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
......@@ -15,16 +15,15 @@ import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.eclipsefoundation.react.dto.Address;
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.ContactTypes;
import org.eclipsefoundation.react.resources.ContactResourceTest;
import org.eclipsefoundation.react.resources.FormOrganizationResourceTest;
import org.eclipsefoundation.react.resources.FormWorkingGroupsResourceTest;
import org.eclipsefoundation.react.test.helper.AuthHelper;
import org.eclipsefoundation.react.test.helper.DtoHelper;
import io.quarkus.test.Mock;
......@@ -45,49 +44,20 @@ public class MockHibernateDao implements PersistenceDao {
@PostConstruct
public void init() {
this.mockData = new HashMap<>();
MembershipForm mf = new MembershipForm();
mf.setId("form-uuid");
MembershipForm mf = DtoHelper.generateForm(Optional.of("form-uuid"));
mf.setUserID(AuthHelper.TEST_USER_NAME);
mf.setMembershipLevel("sample");
mf.setSigningAuthority(Math.random() > 0.5);
mockData.put(MembershipForm.class, Arrays.asList(mf));
FormOrganization formOrg = new FormOrganization();
FormOrganization formOrg = DtoHelper.generateOrg(mf);
formOrg.setId(FormOrganizationResourceTest.SAMPLE_ORGANIZATION_ID);
formOrg.setLegalName("Sample Organization");
formOrg.setTwitterHandle("TwitterHandle");
Address a = new Address();
a.setCity("Sample");
a.setCountry("Country");
a.setPostalCode("Postal Code");
a.setProvinceState("ON");
a.setStreet("Sample street rd");
formOrg.setAddress(a);
mockData.put(FormOrganization.class, Arrays.asList(formOrg));
Contact formContact = new Contact();
formContact.setId(ContactResourceTest.SAMPLE_CONTACT_ID);
formContact.setEmail("sample@sample.com");
formContact.setfName("First Name");
formContact.setlName("Last Name");
formContact.setTitle("sample title");
formContact.setType(ContactTypes.ACCOUNTING);
Contact formContact = DtoHelper.generateContact(mf, Optional.of(ContactTypes.ACCOUNTING));
mockData.put(Contact.class, Arrays.asList(formContact));
FormWorkingGroup wg = new FormWorkingGroup();
wg.setEffectiveDate(new Date(System.currentTimeMillis()));
wg.setParticipationLevel("participant");
wg.setWorkingGroupID("internet-things-iot");
FormWorkingGroup wg = DtoHelper.generateWorkingGroups(mf).get(0);
wg.setId(FormWorkingGroupsResourceTest.SAMPLE_WORKING_GROUPS_ID);
Contact c = new Contact();
c.setEmail("sample@sample.com");
c.setfName("First Name");
c.setlName("Last Name");
c.setTitle("sample title");
c.setType(ContactTypes.ACCOUNTING);
wg.setContact(c);
mockData.put(FormWorkingGroup.class, Arrays.asList(wg));
}
@Override
......
package org.eclipsefoundation.react.test.helper;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipsefoundation.react.dto.Address;
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.ContactTypes;
import org.eclipsefoundation.react.namespace.FormState;
/**
* Helper for creating valid random DTO objects for use in testing.
*
* @author Martin Lowe
*
*/
public class DtoHelper {
// used for random picking, not cryptographic
private static final Random r = new Random();
/**
* Generates a random valid form for use in tests.
*
* @param userID optional userID to set if needed
* @return valid membership form data
*/
public static MembershipForm generateForm(Optional<String> userID) {
MembershipForm mf = new MembershipForm();
mf.setUserID(userID.orElseGet(() -> RandomStringUtils.randomAlphabetic(10)));
mf.setMembershipLevel(RandomStringUtils.randomAlphabetic(10));
mf.setSigningAuthority(Math.random() > 0.5);
mf.setRegistrationCountry("CA");
mf.setVatNumber(RandomStringUtils.randomNumeric(10));
mf.setPurchaseOrderRequired(Math.random() > 0.5 ? "yes" : "no");
mf.setDateCreated(Math.random() > 0.5 ? System.currentTimeMillis() + r.nextInt(10000)
: System.currentTimeMillis() - r.nextInt(10000));
mf.setState(FormState.INPROGRESS);
return mf;
}
public static FormOrganization generateOrg(MembershipForm mf) {
FormOrganization o = new FormOrganization();
o.setForm(mf);
o.setLegalName(RandomStringUtils.randomAlphabetic(4, 10));