more tests and validation

This commit is contained in:
anhefti 2019-02-08 21:46:18 +01:00
parent c786eed28a
commit 04bbadf2e0
13 changed files with 113 additions and 99 deletions

View file

@ -138,6 +138,11 @@ public class APIMessage implements Serializable {
return ErrorMessage.FIELD_VALIDATION.of(error.toString(), args);
}
public static APIMessage fieldValidationError(final String fieldName, final String defaultMessage) {
final String[] args = StringUtils.split(defaultMessage, ":");
return ErrorMessage.FIELD_VALIDATION.of(fieldName, args);
}
public static String toHTML(final Collection<APIMessage> messages) {
final StringBuilder builder = new StringBuilder();
builder.append("<b>Messages:</b><br/><br/>");
@ -182,4 +187,15 @@ public class APIMessage implements Serializable {
}
}
public static class FieldValidationException extends RuntimeException {
private static final long serialVersionUID = 3324566460573096815L;
public final APIMessage apiMessage;
public FieldValidationException(final String fieldName, final String defaultMessage) {
this.apiMessage = APIMessage.fieldValidationError(fieldName, defaultMessage);
}
}
}

View file

@ -26,4 +26,8 @@ public class SEBServerRestEndpoints {
public static final String LIST_ENDPOINT_SUFFIX = "/list";
public static final String ENDPOINT_ACTIVE = "/active";
public static final String ENDPOINT_INACTIVE = "/inactive";
}

View file

@ -170,10 +170,12 @@ public final class Form implements FormBinding {
//@formatter:off
private FormFieldAccessor<?> createAccessor(final Label label, final Label field) {
return new FormFieldAccessor<>(label, field) {
final FormFieldAccessor<?> result = new FormFieldAccessor<>(label, field) {
@Override public String getValue() { return field.getText(); }
@Override public void setValue(final String value) { field.setText(value); }
};
return result;
}
private FormFieldAccessor<Text> createAccessor(final Label label, final Text text) {
return new FormFieldAccessor<>(label, text) {

View file

@ -32,19 +32,6 @@ public interface ActivatableEntityDAO<T extends Entity, M extends ModelIdAware>
* @return Result of Collection of Entity of the given institution and activity */
Result<Collection<T>> all(Long institutionId, Boolean active);
/** Load all active entities of concrete type for the given institution
*
* NOTE: institutionId may be null. In that case this method uses a query to get all active entities of
* concrete type from all institutions. Anyways, to not pollute the memory it is recommended to set a limit by
* using the <code>PaginationService</code> before calling this method
*
* @param institutionId the identifier of the institution.
* @return Result of Collection of Entity of the given institution */
@Override
default Result<Collection<T>> all(final Long institutionId) {
return all(institutionId, true);
}
/** Set all entities referred by the given Collection of EntityKey active / inactive
*
* @param all The Collection of EntityKeys to set active or inactive

View file

@ -53,16 +53,6 @@ public interface EntityDAO<T extends Entity, M extends ModelIdAware> {
}).flatMap(this::byPK);
}
/** Use this to get a Collection of all entities of concrete type of the given institution.
*
* NOTE: institutionId may be null. In that case this method uses a query to get all entities of
* concrete type from all institutions. Anyways, to not pollute the memory it is recommended to set a limit by
* using the <code>PaginationService</code> before calling this method
*
* @param institutionId the identifier of the institution.
* @return Result of Collection of Entity of the given institution */
Result<Collection<T>> all(Long institutionId);
Result<Collection<T>> loadEntities(Collection<EntityKey> keys);
@Transactional(readOnly = true)

View file

@ -75,27 +75,6 @@ public class IndicatorDAOImpl implements IndicatorDAO {
.flatMap(this::toDomainModel);
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Indicator>> all(final Long institutionId) {
return Result.tryCatch(() -> {
return this.indicatorRecordMapper.selectByExample()
.join(ExamRecordDynamicSqlSupport.examRecord)
.on(
ExamRecordDynamicSqlSupport.id,
SqlBuilder.equalTo(IndicatorRecordDynamicSqlSupport.examId))
.where(
ExamRecordDynamicSqlSupport.institutionId,
isEqualTo(institutionId))
.build()
.execute()
.stream()
.map(this::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.collect(Collectors.toList());
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<Indicator>> allMatching(final FilterMap filterMap, final Predicate<Indicator> predicate) {

View file

@ -26,6 +26,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityType;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
@ -131,6 +132,16 @@ public class InstitutionDAOImpl implements InstitutionDAO {
public Result<Institution> save(final String modelId, final Institution institution) {
return Result.tryCatch(() -> {
final Long count = this.institutionRecordMapper.countByExample()
.where(InstitutionRecordDynamicSqlSupport.name, isEqualTo(institution.name))
.and(InstitutionRecordDynamicSqlSupport.id, isNotEqualTo(institution.id))
.build()
.execute();
if (count != null && count.longValue() > 0) {
throw new FieldValidationException("name", "institution:name:exists");
}
final Long pk = Long.parseLong(modelId);
final InstitutionRecord newRecord = new InstitutionRecord(
pk,
@ -150,6 +161,16 @@ public class InstitutionDAOImpl implements InstitutionDAO {
@Transactional
public Result<Institution> createNew(final Institution institution) {
return Result.tryCatch(() -> {
final Long count = this.institutionRecordMapper.countByExample()
.where(InstitutionRecordDynamicSqlSupport.name, isEqualTo(institution.name))
.build()
.execute();
if (count != null && count.longValue() > 0) {
throw new FieldValidationException("name", "institution:name:exists");
}
final InstitutionRecord newRecord = new InstitutionRecord(
null,
institution.name,

View file

@ -279,40 +279,6 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<UserActivityLog>> all(final Long institutionId) {
return Result.tryCatch(() -> {
if (institutionId == null) {
return this.userLogRecordMapper
.selectByExample()
.build()
.execute()
.stream()
.map(UserActivityLogDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.collect(Collectors.toList());
} else {
return this.userLogRecordMapper
.selectByExample()
.join(UserRecordDynamicSqlSupport.userRecord)
.on(
UserRecordDynamicSqlSupport.uuid,
SqlBuilder.equalTo(UserActivityLogRecordDynamicSqlSupport.userUuid))
.where(
UserRecordDynamicSqlSupport.institutionId,
SqlBuilder.isEqualTo(institutionId))
.build()
.execute()
.stream()
.map(UserActivityLogDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logUnexpectedErrorAndSkip)
.collect(Collectors.toList());
}
});
}
@Override
@Transactional(readOnly = true)
public Result<Collection<UserActivityLog>> loadEntities(final Collection<EntityKey> keys) {

View file

@ -29,6 +29,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationException;
@ -140,4 +141,14 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(FieldValidationException.class)
public ResponseEntity<Object> handleFieldValidationException(
final FieldValidationException ex,
final WebRequest request) {
return new ResponseEntity<>(
Arrays.asList(ex.apiMessage),
HttpStatus.BAD_REQUEST);
}
}

View file

@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import ch.ethz.seb.sebserver.gbl.api.SEBServerRestEndpoints;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
@ -56,7 +57,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
}
@RequestMapping(
path = "/all/active",
path = SEBServerRestEndpoints.ENDPOINT_ACTIVE,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@ -79,7 +80,7 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
}
@RequestMapping(
path = "/all/inactive",
path = SEBServerRestEndpoints.ENDPOINT_INACTIVE,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@ -102,22 +103,22 @@ public abstract class ActivatableEntityController<T extends GrantEntity, M exten
}
@RequestMapping(
path = "/{id}/active",
path = "/{modelId}" + SEBServerRestEndpoints.ENDPOINT_ACTIVE,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public EntityProcessingReport activate(@PathVariable final String id) {
return setActive(id, true)
public EntityProcessingReport activate(@PathVariable final String modelId) {
return setActive(modelId, true)
.getOrThrow();
}
@RequestMapping(
value = "/{id}/inactive",
value = "/{modelId}" + SEBServerRestEndpoints.ENDPOINT_INACTIVE,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public EntityProcessingReport deactivate(@PathVariable final String id) {
return setActive(id, false)
public EntityProcessingReport deactivate(@PathVariable final String modelId) {
return setActive(modelId, false)
.getOrThrow();
}

View file

@ -13,8 +13,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.runner.RunWith;
@ -42,6 +44,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.SEBServer;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity;
@RunWith(SpringRunner.class)
@SpringBootTest(
@ -239,4 +242,12 @@ public abstract class AdministrationAPIIntegrationTester {
}
}
protected String getOrderedUUIDs(final Collection<? extends Entity> list) {
return list
.stream()
.map(userInfo -> userInfo.getModelId())
.collect(Collectors.toList())
.toString();
}
}

View file

@ -9,6 +9,8 @@
package ch.ethz.seb.sebserver.webservice.integration.api.admin;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Collection;
import java.util.List;
@ -16,6 +18,7 @@ import java.util.List;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;
import com.fasterxml.jackson.core.type.TypeReference;
@ -367,6 +370,37 @@ public class InstitutionAPITest extends AdministrationAPIIntegrationTester {
assertTrue(institutions.size() == 3);
}
@Test
public void testlAllActiveInactive() throws Exception {
final String sebAdminToken = getSebAdminAccess();
Page<Institution> institutions = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + SEBServerRestEndpoints.ENDPOINT_INSTITUTION + "/active")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.header("Authorization", "Bearer " + sebAdminToken))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<Page<Institution>>() {
});
assertNotNull(institutions);
assertEquals("[1]", getOrderedUUIDs(institutions.content));
// all inactive of the own institution
institutions = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + SEBServerRestEndpoints.ENDPOINT_USER_ACCOUNT + "/inactive")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.header("Authorization", "Bearer " + sebAdminToken))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(),
new TypeReference<Page<Institution>>() {
});
assertNotNull(institutions);
assertTrue(institutions.pageSize == 0);
assertEquals("[]", getOrderedUUIDs(institutions.content));
}
static void assertContainsInstitution(final String name, final Collection<Institution> institutions) {
assert institutions != null;
assert institutions.stream()

View file

@ -341,14 +341,6 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertEquals("[user7, user6, user4]", getOrderedUUIDs(userInfos.content));
}
private String getOrderedUUIDs(final Collection<UserInfo> list) {
return list
.stream()
.map(userInfo -> userInfo.uuid)
.collect(Collectors.toList())
.toString();
}
@Test
public void getAllUserInfo() throws Exception {
final String token = getSebAdminAccess();
@ -982,7 +974,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
// all active for the own institution
Page<UserInfo> usersPage = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + SEBServerRestEndpoints.ENDPOINT_USER_ACCOUNT + "/all/active")
this.mockMvc.perform(get(this.endpoint + SEBServerRestEndpoints.ENDPOINT_USER_ACCOUNT + "/active")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.header("Authorization", "Bearer " + sebAdminToken))
.andExpect(status().isOk())
@ -996,7 +988,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
// all inactive of the own institution
usersPage = this.jsonMapper.readValue(
this.mockMvc.perform(get(this.endpoint + SEBServerRestEndpoints.ENDPOINT_USER_ACCOUNT + "/all/inactive")
this.mockMvc.perform(get(this.endpoint + SEBServerRestEndpoints.ENDPOINT_USER_ACCOUNT + "/inactive")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.header("Authorization", "Bearer " + sebAdminToken))
.andExpect(status().isOk())
@ -1012,7 +1004,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
usersPage = this.jsonMapper.readValue(
this.mockMvc
.perform(get(this.endpoint + SEBServerRestEndpoints.ENDPOINT_USER_ACCOUNT
+ "/all/active?institutionId=2")
+ "/active?institutionId=2")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.header("Authorization", "Bearer " + sebAdminToken))
.andExpect(status().isOk())
@ -1028,7 +1020,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
usersPage = this.jsonMapper.readValue(
this.mockMvc
.perform(get(this.endpoint + SEBServerRestEndpoints.ENDPOINT_USER_ACCOUNT
+ "/all/inactive?institutionId=2")
+ "/inactive?institutionId=2")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.header("Authorization", "Bearer " + sebAdminToken))
.andExpect(status().isOk())