Skip to content

Commit

Permalink
refactor: Migrate users endpoint to tapir BasicUserInformation
Browse files Browse the repository at this point in the history
  • Loading branch information
seakayone committed Feb 13, 2024
1 parent 924f7f2 commit 73e2962
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 220 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredenti
import org.knora.webapi.routing.Authenticator
import org.knora.webapi.routing.UnsafeZioRun
import org.knora.webapi.sharedtestdata.SharedTestDataADM
import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.BasicUserInformationChangeRequest
import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.UserCreateRequest
import org.knora.webapi.slice.admin.domain.model.*
import org.knora.webapi.util.ZioScalaTestUtil.assertFailsWithA
Expand Down Expand Up @@ -225,82 +226,72 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender {
"asked to update a user" should {
"UPDATE the user's basic information" in {
/* User information is updated by the user */
appActor ! UserChangeBasicInformationRequestADM(
userIri = SharedTestDataADM.normalUser.id,
userUpdateBasicInformationPayload = UserUpdateBasicInformationPayloadADM(
givenName = Some(GivenName.unsafeFrom("Donald"))
),
requestingUser = SharedTestDataADM.normalUser,
apiRequestID = UUID.randomUUID
val response1: UserOperationResponseADM = UnsafeZioRun.runOrThrow(
UsersResponder.changeBasicUserInformationADM(
SharedTestDataADM.normalUser.userIri,
BasicUserInformationChangeRequest(givenName = Some(GivenName.unsafeFrom("Donald"))),
UUID.randomUUID
)
)

val response1 = expectMsgType[UserOperationResponseADM](timeout)
response1.user.givenName should equal("Donald")

/* User information is updated by a system admin */
appActor ! UserChangeBasicInformationRequestADM(
userIri = SharedTestDataADM.normalUser.id,
userUpdateBasicInformationPayload = UserUpdateBasicInformationPayloadADM(
familyName = Some(FamilyName.unsafeFrom("Duck"))
),
requestingUser = SharedTestDataADM.superUser,
apiRequestID = UUID.randomUUID
val response2: UserOperationResponseADM = UnsafeZioRun.runOrThrow(
UsersResponder.changeBasicUserInformationADM(
SharedTestDataADM.normalUser.userIri,
BasicUserInformationChangeRequest(familyName = Some(FamilyName.unsafeFrom("Duck"))),
UUID.randomUUID
)
)

val response2 = expectMsgType[UserOperationResponseADM](timeout)
response2.user.familyName should equal("Duck")

/* User information is updated by a system admin */
appActor ! UserChangeBasicInformationRequestADM(
userIri = SharedTestDataADM.normalUser.id,
userUpdateBasicInformationPayload = UserUpdateBasicInformationPayloadADM(
givenName = Some(GivenName.unsafeFrom(SharedTestDataADM.normalUser.givenName)),
familyName = Some(FamilyName.unsafeFrom(SharedTestDataADM.normalUser.familyName))
),
requestingUser = SharedTestDataADM.superUser,
apiRequestID = UUID.randomUUID
val response3: UserOperationResponseADM = UnsafeZioRun.runOrThrow(
UsersResponder.changeBasicUserInformationADM(
SharedTestDataADM.normalUser.userIri,
BasicUserInformationChangeRequest(
givenName = Some(GivenName.unsafeFrom(SharedTestDataADM.normalUser.givenName)),
familyName = Some(FamilyName.unsafeFrom(SharedTestDataADM.normalUser.familyName))
),
apiRequestID = UUID.randomUUID
)
)

val response3 = expectMsgType[UserOperationResponseADM](timeout)
response3.user.givenName should equal(SharedTestDataADM.normalUser.givenName)
response3.user.familyName should equal(SharedTestDataADM.normalUser.familyName)
}

"return a 'DuplicateValueException' if the supplied 'username' is not unique" in {
val duplicateUsername =
Some(Username.unsafeFrom(SharedTestDataADM.anythingUser1.username))
appActor ! UserChangeBasicInformationRequestADM(
userIri = SharedTestDataADM.normalUser.id,
userUpdateBasicInformationPayload = UserUpdateBasicInformationPayloadADM(
username = duplicateUsername
),
SharedTestDataADM.superUser,
UUID.randomUUID
)
expectMsg(
Failure(
DuplicateValueException(
s"User with the username '${SharedTestDataADM.anythingUser1.username}' already exists"
)
val exit = UnsafeZioRun.run(
UsersResponder.changeBasicUserInformationADM(
SharedTestDataADM.normalUser.userIri,
BasicUserInformationChangeRequest(username = duplicateUsername),
UUID.randomUUID
)
)
assertFailsWithA[DuplicateValueException](
exit,
s"User with the username '${SharedTestDataADM.anythingUser1.username}' already exists"
)
}

"return a 'DuplicateValueException' if the supplied 'email' is not unique" in {
val duplicateEmail = Some(Email.unsafeFrom(SharedTestDataADM.anythingUser1.email))
appActor ! UserChangeBasicInformationRequestADM(
userIri = SharedTestDataADM.normalUser.id,
userUpdateBasicInformationPayload = UserUpdateBasicInformationPayloadADM(
email = duplicateEmail
),
SharedTestDataADM.superUser,
UUID.randomUUID
)
expectMsg(
Failure(
DuplicateValueException(s"User with the email '${SharedTestDataADM.anythingUser1.email}' already exists")
val exit = UnsafeZioRun.run(
UsersResponder.changeBasicUserInformationADM(
SharedTestDataADM.normalUser.userIri,
BasicUserInformationChangeRequest(email = duplicateEmail),
UUID.randomUUID
)
)
assertFailsWithA[DuplicateValueException](
exit,
s"User with the email '${SharedTestDataADM.anythingUser1.email}' already exists"
)
}

"UPDATE the user's password (by himself)" in {
Expand Down Expand Up @@ -395,25 +386,6 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender {
}

"return a 'ForbiddenException' if the user requesting update is not the user itself or system admin" in {
/* User information is updated by other normal user */
appActor ! UserChangeBasicInformationRequestADM(
userIri = SharedTestDataADM.superUser.id,
userUpdateBasicInformationPayload = UserUpdateBasicInformationPayloadADM(
email = None,
givenName = Some(GivenName.unsafeFrom("Donald")),
familyName = None,
lang = None
),
requestingUser = SharedTestDataADM.normalUser,
UUID.randomUUID
)
expectMsg(
timeout,
Failure(
ForbiddenException("User information can only be changed by the user itself or a system administrator")
)
)

/* Password is updated by other normal user */
appActor ! UserChangePasswordRequestADM(
userIri = SharedTestDataADM.superUser.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import zio.prelude.Validation
import java.util.UUID

import dsp.errors.BadRequestException
import dsp.errors.ValidationException
import dsp.valueobjects.LanguageCode
import org.knora.webapi.*
import org.knora.webapi.core.RelayedMessage
Expand All @@ -25,7 +24,6 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectsADMJsonProtocol
import org.knora.webapi.slice.admin.domain.model.*
import org.knora.webapi.slice.common.ToValidation.validateOneWithFrom
import org.knora.webapi.slice.common.ToValidation.validateOptionWithFrom

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// API requests
Expand Down Expand Up @@ -116,21 +114,6 @@ case class UserGetByIriADM(
requestingUser: User
) extends UsersResponderRequestADM

/**
* Request updating of an existing user.
*
* @param userIri the IRI of the user to be updated.
* @param userUpdateBasicInformationPayload the [[UserUpdateBasicInformationPayloadADM]] object containing the data to be updated.
* @param requestingUser the user initiating the request.
* @param apiRequestID the ID of the API request.
*/
case class UserChangeBasicInformationRequestADM(
userIri: IRI,
userUpdateBasicInformationPayload: UserUpdateBasicInformationPayloadADM,
requestingUser: User,
apiRequestID: UUID
) extends UsersResponderRequestADM

/**
* Request updating the users password.
*
Expand Down Expand Up @@ -425,40 +408,6 @@ case class UserChangeRequestADM(
}
}

/**
* Payload used for updating basic information of an existing user.
*
* @param username the new username.
* @param email the new email address. Needs to be unique on the server.
* @param givenName the new given name.
* @param familyName the new family name.
* @param lang the new language.
*/
case class UserUpdateBasicInformationPayloadADM(
username: Option[Username] = None,
email: Option[Email] = None,
givenName: Option[GivenName] = None,
familyName: Option[FamilyName] = None,
lang: Option[LanguageCode] = None
) {
def isAtLeastOneParamSet: Boolean = Seq(username, email, givenName, familyName, lang).flatten.nonEmpty
}

object UserUpdateBasicInformationPayloadADM {

private def validateWithOptionOrNone[I, O, E](opt: Option[I], f: I => Validation[E, O]): Validation[E, Option[O]] =
opt.map(f(_).map(Some(_))).getOrElse(Validation.succeed(None))

def make(req: ChangeUserApiRequestADM): Validation[ValidationException, UserUpdateBasicInformationPayloadADM] =
Validation.validateWith(
validateOptionWithFrom(req.username, Username.from, ValidationException.apply),
validateOptionWithFrom(req.email, Email.from, ValidationException.apply),
validateOptionWithFrom(req.givenName, GivenName.from, ValidationException.apply),
validateOptionWithFrom(req.familyName, FamilyName.from, ValidationException.apply),
validateWithOptionOrNone(req.lang, LanguageCode.make)
)(UserUpdateBasicInformationPayloadADM.apply)
}

case class UserUpdatePasswordPayloadADM(requesterPassword: Password, newPassword: Password)
object UserUpdatePasswordPayloadADM {
def make(apiRequest: ChangeUserPasswordApiRequestADM): Validation[String, UserUpdatePasswordPayloadADM] = {
Expand Down
Loading

0 comments on commit 73e2962

Please sign in to comment.