diff --git a/src/it/java/teammates/it/ui/webapi/CreateAccountRequestActionIT.java b/src/it/java/teammates/it/ui/webapi/CreateAccountRequestActionIT.java index f73bf03f24a..85449452b5f 100644 --- a/src/it/java/teammates/it/ui/webapi/CreateAccountRequestActionIT.java +++ b/src/it/java/teammates/it/ui/webapi/CreateAccountRequestActionIT.java @@ -1,5 +1,8 @@ package teammates.it.ui.webapi; +import java.util.List; + +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -221,4 +224,16 @@ protected void testAccessControl() throws Exception { verifyAccessibleWithoutLogin(); } + @Override + @AfterMethod + protected void tearDown() { + HibernateUtil.beginTransaction(); + List accountRequests = logic.getPendingAccountRequests(); + for (AccountRequest ar : accountRequests) { + logic.deleteAccountRequest(ar.getEmail(), ar.getInstitute()); + } + accountRequests = logic.getPendingAccountRequests(); + HibernateUtil.commitTransaction(); + assert accountRequests.isEmpty(); + } } diff --git a/src/it/java/teammates/it/ui/webapi/UpdateAccountRequestActionIT.java b/src/it/java/teammates/it/ui/webapi/UpdateAccountRequestActionIT.java new file mode 100644 index 00000000000..207c042be41 --- /dev/null +++ b/src/it/java/teammates/it/ui/webapi/UpdateAccountRequestActionIT.java @@ -0,0 +1,223 @@ +package teammates.it.ui.webapi; + +import java.util.UUID; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import teammates.common.datatransfer.AccountRequestStatus; +import teammates.common.exception.EntityAlreadyExistsException; +import teammates.common.exception.InvalidParametersException; +import teammates.common.util.Const; +import teammates.common.util.FieldValidator; +import teammates.common.util.HibernateUtil; +import teammates.common.util.StringHelperExtension; +import teammates.storage.sqlentity.AccountRequest; +import teammates.storage.sqlentity.Course; +import teammates.ui.output.AccountRequestData; +import teammates.ui.request.AccountRequestUpdateRequest; +import teammates.ui.request.InvalidHttpRequestBodyException; +import teammates.ui.webapi.EntityNotFoundException; +import teammates.ui.webapi.InvalidHttpParameterException; +import teammates.ui.webapi.JsonResult; +import teammates.ui.webapi.UpdateAccountRequestAction; + +/** + * SUT: {@link UpdateAccountRequestAction}. + */ +public class UpdateAccountRequestActionIT extends BaseActionIT { + + @Override + @BeforeMethod + protected void setUp() throws Exception { + super.setUp(); + persistDataBundle(typicalBundle); + HibernateUtil.flushSession(); + } + + @Override + protected String getActionUri() { + return Const.ResourceURIs.ACCOUNT_REQUEST; + } + + @Override + protected String getRequestMethod() { + return PUT; + } + + @Override + @Test + public void testExecute() throws Exception { + ______TS("edit fields of an account request"); + AccountRequest accountRequest = typicalBundle.accountRequests.get("unregisteredInstructor1"); + accountRequest.setStatus(AccountRequestStatus.PENDING); + UUID id = accountRequest.getId(); + String name = "newName"; + String email = "newEmail@email.com"; + String institute = "newInstitute"; + String comments = "newComments"; + AccountRequestStatus status = accountRequest.getStatus(); + + AccountRequestUpdateRequest requestBody = new AccountRequestUpdateRequest(name, email, institute, status, comments); + String[] params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + UpdateAccountRequestAction action = getAction(requestBody, params); + JsonResult result = action.execute(); + + assertEquals(result.getStatusCode(), 200); + AccountRequestData data = (AccountRequestData) result.getOutput(); + + assertEquals(name, data.getName()); + assertEquals(email, data.getEmail()); + assertEquals(institute, data.getInstitute()); + assertEquals(status, data.getStatus()); + assertEquals(comments, data.getComments()); + verifyNoEmailsSent(); + + ______TS("approve a pending account request"); + accountRequest = typicalBundle.accountRequests.get("unregisteredInstructor2"); + accountRequest.setStatus(AccountRequestStatus.PENDING); + requestBody = new AccountRequestUpdateRequest(accountRequest.getName(), accountRequest.getEmail(), + accountRequest.getInstitute(), AccountRequestStatus.APPROVED, accountRequest.getComments()); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, accountRequest.getId().toString()}; + action = getAction(requestBody, params); + result = getJsonResult(action, 200); + data = (AccountRequestData) result.getOutput(); + + assertEquals(accountRequest.getName(), data.getName()); + assertEquals(accountRequest.getEmail(), data.getEmail()); + assertEquals(accountRequest.getInstitute(), data.getInstitute()); + assertEquals(AccountRequestStatus.APPROVED, data.getStatus()); + assertEquals(accountRequest.getComments(), data.getComments()); + verifyNumberOfEmailsSent(1); + + ______TS("already registered account request has no email sent when approved"); + accountRequest = typicalBundle.accountRequests.get("instructor2"); + requestBody = new AccountRequestUpdateRequest(name, email, institute, AccountRequestStatus.APPROVED, comments); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, accountRequest.getId().toString()}; + + action = getAction(requestBody, params); + result = getJsonResult(action, 200); + data = (AccountRequestData) result.getOutput(); + + assertEquals(name, data.getName()); + assertEquals(email, data.getEmail()); + assertEquals(institute, data.getInstitute()); + assertEquals(AccountRequestStatus.REGISTERED, data.getStatus()); + assertEquals(comments, data.getComments()); + verifyNumberOfEmailsSent(0); + + ______TS("non-existent but valid uuid"); + requestBody = new AccountRequestUpdateRequest("name", "email", + "institute", AccountRequestStatus.PENDING, "comments"); + String validUuid = UUID.randomUUID().toString(); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, validUuid}; + + EntityNotFoundException enfe = verifyEntityNotFound(requestBody, params); + + assertEquals(String.format("Account request with id = %s not found", validUuid), enfe.getMessage()); + + ______TS("invalid uuid"); + requestBody = new AccountRequestUpdateRequest("name", "email", + "institute", AccountRequestStatus.PENDING, "comments"); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, "invalid"}; + + InvalidHttpParameterException ihpe = verifyHttpParameterFailure(requestBody, params); + + assertEquals("Invalid UUID string: invalid", ihpe.getMessage()); + + ______TS("invalid email"); + accountRequest = typicalBundle.accountRequests.get("unregisteredInstructor1"); + id = accountRequest.getId(); + email = "newEmail"; + status = accountRequest.getStatus(); + + requestBody = new AccountRequestUpdateRequest(name, email, institute, status, comments); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + InvalidHttpRequestBodyException ihrbe = verifyHttpRequestBodyFailure(requestBody, params); + + assertEquals(getPopulatedErrorMessage(FieldValidator.EMAIL_ERROR_MESSAGE, email, + FieldValidator.EMAIL_FIELD_NAME, FieldValidator.REASON_INCORRECT_FORMAT, FieldValidator.EMAIL_MAX_LENGTH), + ihrbe.getMessage()); + + ______TS("invalid name alphanumeric"); + name = "@$@#$#@#@$#@$"; + email = "newEmail@email.com"; + + requestBody = new AccountRequestUpdateRequest(name, email, institute, status, comments); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + ihrbe = verifyHttpRequestBodyFailure(requestBody, params); + + assertEquals(getPopulatedErrorMessage(FieldValidator.INVALID_NAME_ERROR_MESSAGE, name, + FieldValidator.PERSON_NAME_FIELD_NAME, FieldValidator.REASON_START_WITH_NON_ALPHANUMERIC_CHAR), + ihrbe.getMessage()); + + ______TS("invalid name too long"); + name = StringHelperExtension.generateStringOfLength(FieldValidator.PERSON_NAME_MAX_LENGTH + 1); + + requestBody = new AccountRequestUpdateRequest(name, email, institute, status, comments); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + ihrbe = verifyHttpRequestBodyFailure(requestBody, params); + + assertEquals(getPopulatedErrorMessage(FieldValidator.SIZE_CAPPED_NON_EMPTY_STRING_ERROR_MESSAGE, name, + FieldValidator.PERSON_NAME_FIELD_NAME, FieldValidator.REASON_TOO_LONG, + FieldValidator.PERSON_NAME_MAX_LENGTH), ihrbe.getMessage()); + + ______TS("null email value"); + name = "newName"; + + requestBody = new AccountRequestUpdateRequest(name, null, institute, status, comments); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + ihrbe = verifyHttpRequestBodyFailure(requestBody, params); + + assertEquals("email cannot be null", ihrbe.getMessage()); + + ______TS("null name value"); + requestBody = new AccountRequestUpdateRequest(null, email, institute, status, comments); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + ihrbe = verifyHttpRequestBodyFailure(requestBody, params); + + assertEquals("name cannot be null", ihrbe.getMessage()); + + ______TS("null status value"); + requestBody = new AccountRequestUpdateRequest(name, email, institute, null, comments); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + ihrbe = verifyHttpRequestBodyFailure(requestBody, params); + + assertEquals("status cannot be null", ihrbe.getMessage()); + + ______TS("null institute value"); + requestBody = new AccountRequestUpdateRequest(name, email, null, status, comments); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + ihrbe = verifyHttpRequestBodyFailure(requestBody, params); + + assertEquals("institute cannot be null", ihrbe.getMessage()); + + ______TS("allow null comments in request"); + requestBody = new AccountRequestUpdateRequest(name, email, institute, status, null); + params = new String[] {Const.ParamsNames.ACCOUNT_REQUEST_ID, id.toString()}; + + action = getAction(requestBody, params); + result = getJsonResult(action, 200); + data = (AccountRequestData) result.getOutput(); + + assertEquals(name, data.getName()); + assertEquals(email, data.getEmail()); + assertEquals(institute, data.getInstitute()); + assertEquals(null, data.getComments()); + } + + @Override + @Test + protected void testAccessControl() throws InvalidParametersException, EntityAlreadyExistsException { + Course course = typicalBundle.courses.get("course1"); + verifyOnlyAdminCanAccess(course); + } +} diff --git a/src/main/java/teammates/ui/request/AccountRequestUpdateRequest.java b/src/main/java/teammates/ui/request/AccountRequestUpdateRequest.java new file mode 100644 index 00000000000..cc653d79ddb --- /dev/null +++ b/src/main/java/teammates/ui/request/AccountRequestUpdateRequest.java @@ -0,0 +1,63 @@ +package teammates.ui.request; + +import javax.annotation.Nullable; + +import teammates.common.datatransfer.AccountRequestStatus; +import teammates.common.util.SanitizationHelper; + +/** + * The create request for an account request update request. + */ +public class AccountRequestUpdateRequest extends BasicRequest { + private String name; + private String email; + private String institute; + private AccountRequestStatus status; + + @Nullable + private String comments; + + public AccountRequestUpdateRequest(String name, String email, String institute, AccountRequestStatus status, + String comments) { + this.name = SanitizationHelper.sanitizeName(name); + this.email = SanitizationHelper.sanitizeEmail(email); + this.institute = SanitizationHelper.sanitizeName(institute); + this.status = status; + if (comments != null) { + this.comments = SanitizationHelper.sanitizeTextField(comments); + } + } + + @Override + public void validate() throws InvalidHttpRequestBodyException { + assertTrue(name != null, "name cannot be null"); + assertTrue(email != null, "email cannot be null"); + assertTrue(institute != null, "institute cannot be null"); + assertTrue(status != null, "status cannot be null"); + assertTrue(status == AccountRequestStatus.APPROVED + || status == AccountRequestStatus.REJECTED + || status == AccountRequestStatus.PENDING + || status == AccountRequestStatus.REGISTERED, + "status must be one of the following: APPROVED, REJECTED, PENDING, REGISTERED"); + } + + public String getName() { + return this.name; + } + + public String getEmail() { + return this.email; + } + + public String getInstitute() { + return this.institute; + } + + public AccountRequestStatus getStatus() { + return this.status; + } + + public String getComments() { + return this.comments; + } +} diff --git a/src/main/java/teammates/ui/webapi/ActionFactory.java b/src/main/java/teammates/ui/webapi/ActionFactory.java index 72d16ad9973..169d4ae5b07 100644 --- a/src/main/java/teammates/ui/webapi/ActionFactory.java +++ b/src/main/java/teammates/ui/webapi/ActionFactory.java @@ -50,6 +50,7 @@ public final class ActionFactory { map(ResourceURIs.ACCOUNT_REQUEST, GET, GetAccountRequestAction.class); map(ResourceURIs.ACCOUNT_REQUEST, POST, CreateAccountRequestAction.class); map(ResourceURIs.ACCOUNT_REQUEST, DELETE, DeleteAccountRequestAction.class); + map(ResourceURIs.ACCOUNT_REQUEST, PUT, UpdateAccountRequestAction.class); map(ResourceURIs.ACCOUNT_REQUESTS, GET, GetAccountRequestsAction.class); map(ResourceURIs.ACCOUNT_REQUEST_RESET, PUT, ResetAccountRequestAction.class); map(ResourceURIs.ACCOUNTS, GET, GetAccountsAction.class); diff --git a/src/main/java/teammates/ui/webapi/UpdateAccountRequestAction.java b/src/main/java/teammates/ui/webapi/UpdateAccountRequestAction.java new file mode 100644 index 00000000000..761a18abd8f --- /dev/null +++ b/src/main/java/teammates/ui/webapi/UpdateAccountRequestAction.java @@ -0,0 +1,75 @@ +package teammates.ui.webapi; + +import java.util.UUID; + +import teammates.common.datatransfer.AccountRequestStatus; +import teammates.common.exception.EntityDoesNotExistException; +import teammates.common.exception.InvalidParametersException; +import teammates.common.util.Const; +import teammates.common.util.EmailWrapper; +import teammates.storage.sqlentity.AccountRequest; +import teammates.ui.output.AccountRequestData; +import teammates.ui.request.AccountRequestUpdateRequest; +import teammates.ui.request.InvalidHttpRequestBodyException; + +/** + * Updates an account request. + */ +public class UpdateAccountRequestAction extends AdminOnlyAction { + + static final String ACCOUNT_REQUEST_NOT_FOUND = "Account request with id = %s not found"; + + @Override + public JsonResult execute() throws InvalidOperationException, InvalidHttpRequestBodyException { + String id = getNonNullRequestParamValue(Const.ParamsNames.ACCOUNT_REQUEST_ID); + UUID accountRequestId; + + try { + accountRequestId = UUID.fromString(id); + } catch (IllegalArgumentException e) { + throw new InvalidHttpParameterException(e.getMessage(), e); + } + + AccountRequest accountRequest = sqlLogic.getAccountRequest(accountRequestId); + + if (accountRequest == null) { + String errorMessage = String.format(ACCOUNT_REQUEST_NOT_FOUND, accountRequestId.toString()); + throw new EntityNotFoundException(errorMessage); + } + + AccountRequestUpdateRequest accountRequestUpdateRequest = + getAndValidateRequestBody(AccountRequestUpdateRequest.class); + + if (accountRequestUpdateRequest.getStatus() == AccountRequestStatus.APPROVED + && (accountRequest.getStatus() == AccountRequestStatus.PENDING + || accountRequest.getStatus() == AccountRequestStatus.REJECTED)) { + try { + // should not need to update other fields for an approval + accountRequest.setStatus(accountRequestUpdateRequest.getStatus()); + accountRequest = sqlLogic.updateAccountRequest(accountRequest); + EmailWrapper email = sqlEmailGenerator.generateNewInstructorAccountJoinEmail( + accountRequest.getRegistrationUrl(), accountRequest.getEmail(), accountRequest.getName()); + emailSender.sendEmail(email); + } catch (InvalidParametersException e) { + throw new InvalidHttpRequestBodyException(e); + } catch (EntityDoesNotExistException e) { + throw new EntityNotFoundException(e); + } + } else { + try { + accountRequest.setName(accountRequestUpdateRequest.getName()); + accountRequest.setEmail(accountRequestUpdateRequest.getEmail()); + accountRequest.setInstitute(accountRequestUpdateRequest.getInstitute()); + accountRequest.setStatus(accountRequest.getStatus()); + accountRequest.setComments(accountRequestUpdateRequest.getComments()); + sqlLogic.updateAccountRequest(accountRequest); + } catch (InvalidParametersException e) { + throw new InvalidHttpRequestBodyException(e); + } catch (EntityDoesNotExistException e) { + throw new EntityNotFoundException(e); + } + } + + return new JsonResult(new AccountRequestData(accountRequest)); + } +} diff --git a/src/test/java/teammates/ui/webapi/GetActionClassesActionTest.java b/src/test/java/teammates/ui/webapi/GetActionClassesActionTest.java index 752a74d9101..89ace4cdf78 100644 --- a/src/test/java/teammates/ui/webapi/GetActionClassesActionTest.java +++ b/src/test/java/teammates/ui/webapi/GetActionClassesActionTest.java @@ -87,6 +87,7 @@ protected void testExecute() { GetAccountRequestAction.class, DeleteAccountRequestAction.class, GetAccountRequestsAction.class, + UpdateAccountRequestAction.class, GetAccountAction.class, GetAccountsAction.class, FeedbackSessionPublishedRemindersAction.class,