SEBSERV-10 more integration tests and fixes

This commit is contained in:
anhefti 2020-07-09 15:32:13 +02:00
parent e38ba52715
commit 896db25eca
10 changed files with 274 additions and 25 deletions

View file

@ -2,7 +2,8 @@ package ch.ethz.seb.sebserver.gbl.api;
import javax.annotation.Generated;
@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2020-07-06T10:44:00.903+02:00")
@Generated(value = "org.mybatis.generator.api.MyBatisGenerator",
comments = "ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin", date = "2020-07-06T10:44:00.903+02:00")
public enum EntityType {
CONFIGURATION_ATTRIBUTE,
CONFIGURATION_VALUE,

View file

@ -31,9 +31,9 @@ public class EntityProcessingReport {
@JsonProperty(value = "source", required = true)
public final Set<EntityKey> source;
/** A set of entity-keys for all entities that has been detected as dependency to one or more of the source entities
* during the process */
@JsonProperty(value = "dependencies", required = true)
public final Set<EntityKey> dependencies;
* during the process */
@JsonProperty(value = "results", required = true)
public final Set<EntityKey> results;
/** A set of error entries that defines an error if happened. */
@JsonProperty(value = "errors", required = true)
public final Set<ErrorEntry> errors;
@ -41,11 +41,11 @@ public class EntityProcessingReport {
@JsonCreator
public EntityProcessingReport(
@JsonProperty(value = "source", required = true) final Collection<EntityKey> source,
@JsonProperty(value = "dependencies", required = true) final Collection<EntityKey> dependencies,
@JsonProperty(value = "results", required = true) final Collection<EntityKey> results,
@JsonProperty(value = "errors", required = true) final Collection<ErrorEntry> errors) {
this.source = Utils.immutableSetOf(source);
this.dependencies = Utils.immutableSetOf(dependencies);
this.results = Utils.immutableSetOf(results);
this.errors = Utils.immutableSetOf(errors);
}
@ -62,22 +62,25 @@ public class EntityProcessingReport {
return this.source;
}
public Set<EntityKey> getDependencies() {
return this.dependencies;
public Set<EntityKey> getResults() {
return this.results;
}
public Set<ErrorEntry> getErrors() {
return this.errors;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public static final class ErrorEntry {
@JsonProperty(value = "entity_key", required = false)
public final EntityKey entityKey;
@JsonProperty(value = "error_message", required = true)
public final APIMessage errorMessage;
@JsonCreator
public ErrorEntry(
@JsonProperty(value = "entity_key", required = true) final EntityKey entityKey,
@JsonProperty(value = "entity_key", required = false) final EntityKey entityKey,
@JsonProperty(value = "error_message", required = true) final APIMessage errorMessage) {
this.entityKey = entityKey;
@ -98,8 +101,8 @@ public class EntityProcessingReport {
final StringBuilder builder = new StringBuilder();
builder.append("EntityProcessingReport [source=");
builder.append(this.source);
builder.append(", dependencies=");
builder.append(this.dependencies);
builder.append(", results=");
builder.append(this.results);
builder.append(", errors=");
builder.append(this.errors);
builder.append("]");

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class DeleteUserAccount extends RestCall<EntityProcessingReport> {
public DeleteUserAccount() {
super(new TypeKey<>(
CallType.ACTIVATION_DEACTIVATE,
EntityType.USER,
new TypeReference<EntityProcessingReport>() {
}),
HttpMethod.DELETE,
MediaType.APPLICATION_FORM_URLENCODED,
API.USER_ACCOUNT_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT);
}
}

View file

@ -40,7 +40,8 @@ public interface BulkActionSupportDAO<T extends Entity> {
* and the type of this BulkActionSupportDAO.
*
* @param bulkAction the BulkAction to get keys of dependencies for the concrete type of this BulkActionSupportDAO
* @return Collection of Result. Each Result refers to the EntityKey of processed entity or to an error if happened */
* @return Collection of Result. Each Result refers to the EntityKey of processed entity or to an error if
* happened */
Set<EntityKey> getDependencies(BulkAction bulkAction);
/** This processed a given BulkAction for all entities of the concrete type of this BulkActionSupportDAO
@ -97,7 +98,7 @@ public interface BulkActionSupportDAO<T extends Entity> {
* @return List of Result refer to a given error for all given EntityKey instances */
static List<Result<EntityKey>> handleBulkActionError(final Exception error, final Set<EntityKey> all) {
return all.stream()
.map(key -> Result.<EntityKey> ofError(new BulkActionEntityException(key)))
.map(key -> Result.<EntityKey> ofError(new BulkActionEntityException(key, error)))
.collect(Collectors.toList());
}

View file

@ -19,18 +19,23 @@ import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport.ErrorEntry;
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionEntityException;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
@ -39,6 +44,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
@WebServiceProfile
public class BulkActionServiceImpl implements BulkActionService {
private static final Logger log = LoggerFactory.getLogger(BulkActionServiceImpl.class);
private final EnumMap<EntityType, EnumSet<EntityType>> directDependancyMap =
new EnumMap<>(EntityType.class);
@ -134,15 +141,42 @@ public class BulkActionServiceImpl implements BulkActionService {
private Result<EntityProcessingReport> createFullReport(final BulkAction action) {
return Result.tryCatch(() -> {
// TODO
return new EntityProcessingReport(
action.sources,
Collections.emptyList(),
Collections.emptyList());
action.result.stream()
.map(result -> result.getOr(null))
.filter(Objects::nonNull)
.collect(Collectors.toList()),
action.result.stream()
.filter(Result::hasError)
.map(this::createErrorEntry)
.collect(Collectors.toList()));
});
}
private ErrorEntry createErrorEntry(final Result<EntityKey> bulkActionSingleResult) {
if (!bulkActionSingleResult.hasError()) {
return null;
}
final Exception error = bulkActionSingleResult.getError();
log.error(
"Unexpected error on bulk action processing. This error is reported to the caller: {}",
error.getMessage());
if (error instanceof BulkActionEntityException) {
return new ErrorEntry(
((BulkActionEntityException) error).key,
APIMessage.ErrorMessage.UNEXPECTED.of(error, error.getMessage()));
} else {
return new ErrorEntry(
null,
APIMessage.ErrorMessage.UNEXPECTED.of(error, error.getMessage()));
}
}
private void processUserActivityLog(final BulkAction action) {
final UserLogActivityType activityType = action.getActivityType();

View file

@ -334,6 +334,13 @@ public class UserDAOImpl implements UserDAO {
final List<Long> ids = extractListOfPKs(all);
// first delete assigned user roles
this.roleRecordMapper.deleteByExample()
.where(RoleRecordDynamicSqlSupport.userId, isIn(ids))
.build()
.execute();
// then delete the user account
this.userRecordMapper.deleteByExample()
.where(UserRecordDynamicSqlSupport.id, isIn(ids))
.build()

View file

@ -316,9 +316,9 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
.getOrThrow();
}
// ******************
// * DELETE (delete)
// ******************
// ************************
// * DELETE (hard-delete)
// ************************
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT,

View file

@ -20,6 +20,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -51,6 +52,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_CLIENT_CONFIGURATION;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport.ErrorEntry;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
@ -96,6 +98,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamCon
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ExportExamConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMapping;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames;
@ -166,6 +169,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetRunnin
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.PropagateInstruction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.ActivateUserAccount;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.ChangePassword;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.DeleteUserAccount;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccount;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserDependency;
@ -2325,7 +2329,166 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
+ "EntityKey [modelId=1, entityType=INDICATOR], "
+ "EntityKey [modelId=2, entityType=INDICATOR]]",
dependencies.toString());
}
@Test
@Order(19)
// *************************************
// Use Case 19: Login as examAdmin2 and delete the user examAdmin2 and all its relational data
// - First login as examAdmin2 and check that it is not possible to delete the own account
public void testUsecase19_DeleteUserDependencies() throws IOException {
final RestServiceImpl restService = createRestServiceForUser(
"examAdmin2",
"examAdmin2",
new GetUserAccountNames(),
new DeleteUserAccount(),
new GetUserAccount(),
new GetExam(),
new GetExamConfigMapping(),
new GetExamConfigNode());
final EntityName user = restService.getBuilder(GetUserAccountNames.class)
.call()
.getOrThrow()
.stream()
.filter(name -> name.name.startsWith("examAdmin2"))
.findFirst()
.get();
// try to delete the own user account should result in error message
try {
restService.getBuilder(DeleteUserAccount.class)
.withURIVariable(API.PARAM_MODEL_ID, user.getModelId())
.call()
.getOrThrow();
fail("Forbidden error message expected here");
} catch (final RestCallError e) {
final APIMessage apiMessage = e.getErrorMessages().get(0);
assertEquals("1001", apiMessage.messageCode);
assertEquals("FORBIDDEN", apiMessage.systemMessage);
}
// Now login as institutional admin and delete the user account with all dependencies (exam configs and exams)
final RestServiceImpl restServiceAdmin = createRestServiceForUser(
"TestInstAdmin",
"987654321",
new GetUserAccountNames(),
new DeleteUserAccount(),
new GetUserAccount(),
new GetExam(),
new GetExamConfigMapping(),
new GetExamConfigNode());
final EntityProcessingReport report = restServiceAdmin.getBuilder(DeleteUserAccount.class)
.withURIVariable(API.PARAM_MODEL_ID, user.getModelId())
.withQueryParam(API.PARAM_BULK_ACTION_INCLUDES, EntityType.EXAM.name())
.withQueryParam(API.PARAM_BULK_ACTION_INCLUDES, EntityType.CONFIGURATION_NODE.name())
.call()
.getOrThrow();
assertNotNull(report);
final List<EntityKey> dependencies = report.getResults()
.stream()
.sorted()
.collect(Collectors.toList());
assertEquals(
"[EntityKey [modelId=1, entityType=CLIENT_CONNECTION], "
+ "EntityKey [modelId=2, entityType=CLIENT_CONNECTION], "
+ "EntityKey [modelId=3, entityType=CLIENT_CONNECTION], "
+ "EntityKey [modelId=4, entityType=CLIENT_CONNECTION], "
+ "EntityKey [modelId=2, entityType=CONFIGURATION_NODE], "
+ "EntityKey [modelId=3, entityType=CONFIGURATION_NODE], "
+ "EntityKey [modelId=4, entityType=CONFIGURATION_NODE], "
+ "EntityKey [modelId=5, entityType=CONFIGURATION_NODE], "
+ "EntityKey [modelId=1, entityType=EXAM], "
+ "EntityKey [modelId=3, entityType=EXAM_CONFIGURATION_MAP], "
+ "EntityKey [modelId=1, entityType=INDICATOR], "
+ "EntityKey [modelId=2, entityType=INDICATOR], "
+ "EntityKey [modelId=9, entityType=USER]]",
dependencies.toString());
final Set<ErrorEntry> errors = report.getErrors();
assertNotNull(errors);
assertTrue(errors.isEmpty());
// test deletion
try {
restServiceAdmin.getBuilder(GetUserAccount.class)
.withURIVariable(API.PARAM_MODEL_ID, user.getModelId())
.call()
.getOrThrow();
fail("no resource found exception expected here");
} catch (final RestCallError e) {
final APIMessage apiMessage = e.getErrorMessages().get(0);
assertEquals("1002", apiMessage.getMessageCode());
assertEquals("resource not found", apiMessage.getSystemMessage());
}
try {
restServiceAdmin.getBuilder(GetExam.class)
.withURIVariable(API.PARAM_MODEL_ID, "1")
.call()
.getOrThrow();
fail("no resource found exception expected here");
} catch (final RestCallError e) {
final APIMessage apiMessage = e.getErrorMessages().get(0);
assertEquals("1002", apiMessage.getMessageCode());
assertEquals("resource not found", apiMessage.getSystemMessage());
}
try {
restServiceAdmin.getBuilder(GetExamConfigMapping.class)
.withURIVariable(API.PARAM_MODEL_ID, "3")
.call()
.getOrThrow();
fail("no resource found exception expected here");
} catch (final RestCallError e) {
final APIMessage apiMessage = e.getErrorMessages().get(0);
assertEquals("1002", apiMessage.getMessageCode());
assertEquals("resource not found", apiMessage.getSystemMessage());
}
try {
restServiceAdmin.getBuilder(GetExamConfigNode.class)
.withURIVariable(API.PARAM_MODEL_ID, "2")
.call()
.getOrThrow();
fail("no resource found exception expected here");
} catch (final RestCallError e) {
final APIMessage apiMessage = e.getErrorMessages().get(0);
assertEquals("1002", apiMessage.getMessageCode());
assertEquals("resource not found", apiMessage.getSystemMessage());
}
try {
restServiceAdmin.getBuilder(GetExamConfigNode.class)
.withURIVariable(API.PARAM_MODEL_ID, "3")
.call()
.getOrThrow();
fail("no resource found exception expected here");
} catch (final RestCallError e) {
final APIMessage apiMessage = e.getErrorMessages().get(0);
assertEquals("1002", apiMessage.getMessageCode());
assertEquals("resource not found", apiMessage.getSystemMessage());
}
try {
restServiceAdmin.getBuilder(GetExamConfigNode.class)
.withURIVariable(API.PARAM_MODEL_ID, "4")
.call()
.getOrThrow();
fail("no resource found exception expected here");
} catch (final RestCallError e) {
final APIMessage apiMessage = e.getErrorMessages().get(0);
assertEquals("1002", apiMessage.getMessageCode());
assertEquals("resource not found", apiMessage.getSystemMessage());
}
try {
restServiceAdmin.getBuilder(GetExamConfigNode.class)
.withURIVariable(API.PARAM_MODEL_ID, "5")
.call()
.getOrThrow();
fail("no resource found exception expected here");
} catch (final RestCallError e) {
final APIMessage apiMessage = e.getErrorMessages().get(0);
assertEquals("1002", apiMessage.getMessageCode());
assertEquals("resource not found", apiMessage.getSystemMessage());
}
}
}

View file

@ -103,7 +103,7 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester {
assertNotNull(report.source);
assertTrue(report.source.size() == 1);
assertEquals(String.valueOf(lmsSetup.id), report.source.iterator().next().modelId);
assertEquals("[]", report.dependencies.toString());
assertEquals("[]", report.results.toString());
assertEquals("[]", report.errors.toString());
// get
@ -133,7 +133,7 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester {
assertNotNull(report.source);
assertTrue(report.source.size() == 1);
assertEquals(String.valueOf(lmsSetup.id), report.source.iterator().next().modelId);
assertEquals("[]", report.dependencies.toString());
assertEquals("[]", report.results.toString());
assertEquals("[]", report.errors.toString());
lmsSetup = new RestAPITestHelper()
@ -162,7 +162,7 @@ public class LmsSetupAPITest extends AdministrationAPIIntegrationTester {
assertNotNull(report.source);
assertTrue(report.source.size() == 1);
assertEquals(String.valueOf(lmsSetup.id), report.source.iterator().next().modelId);
assertEquals("[]", report.dependencies.toString());
assertEquals("[]", report.results.toString());
assertEquals("[]", report.errors.toString());
// get

View file

@ -930,7 +930,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertNotNull(report);
assertNotNull(report.source);
assertTrue(report.dependencies.isEmpty()); // TODO
assertTrue(report.results.isEmpty()); // TODO
assertTrue(report.errors.isEmpty());
assertTrue(report.source.size() == 1);
@ -993,7 +993,7 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
assertNotNull(report);
assertNotNull(report.source);
assertTrue(report.dependencies.isEmpty()); // TODO
assertTrue(report.results.isEmpty()); // TODO
assertTrue(report.errors.isEmpty());
assertTrue(report.source.size() == 1);