Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add integration tests for erasing a project (DEV-3681) #3283

Merged
merged 11 commits into from
Jun 17, 2024
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ lazy val root: Project = Project(id = "root", file("."))
addCommandAlias("fmt", "; all root/scalafmtSbt root/scalafmtAll; root/scalafixAll")
addCommandAlias(
"headerCreateAll",
"; all webapi/headerCreate webapi/Test/headerCreate integration/headerCreate",
"; all webapi/headerCreate webapi/Test/headerCreate integration/headerCreate integration/Test/headerCreate",
)
addCommandAlias(
"headerCheckAll",
"; all webapi/headerCheck webapi/Test/headerCheck integration/headerCheck",
"; all webapi/headerCheck webapi/Test/headerCheck integration/headerCheck integration/Test/headerCheck",
)
addCommandAlias("check", "; all root/scalafmtSbtCheck root/scalafmtCheckAll; root/scalafixAll --check; headerCheckAll")
addCommandAlias("it", "integration/test")
Expand Down
24 changes: 14 additions & 10 deletions integration/src/test/resources/application.conf
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
include "test"

app {
triplestore {
dbtype = "fuseki"
triplestore {
dbtype = "fuseki"

fuseki {
port = 3030
repository-name = "knora-test-unit"
username = "admin"
password = "test"
}

reload-on-start = false
fuseki {
port = 3030
repository-name = "knora-test-unit"
username = "admin"
password = "test"
}

reload-on-start = false
}

features {
allow-erase-projects = true
}
}
45 changes: 45 additions & 0 deletions integration/src/test/scala/org/knora/webapi/E2EZSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import org.knora.webapi.core.AppServer
import org.knora.webapi.core.LayersTest
import org.knora.webapi.core.TestStartupUtils
import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.UserIri

abstract class E2EZSpec extends ZIOSpecDefault with TestStartupUtils {

Expand Down Expand Up @@ -94,6 +96,18 @@ abstract class E2EZSpec extends ZIOSpecDefault with TestStartupUtils {
data <- response.body.asString.mapError(_.getMessage)
} yield data

def sendDeleteRequest(url: String, token: Option[String]): ZIO[env, String, Response] =
(for {
client <- ZIO.service[Client]
urlStr = s"http://localhost:3333$url"
urlFull <- ZIO.fromEither(URL.decode(urlStr))
_ <- ZIO.logDebug(s"POST ${urlFull.encode}")
bearer = token.map(Header.Authorization.Bearer.apply)
headers = Headers(List(Header.ContentType(MediaType.application.json)) ++ bearer.toList)
request = Request.delete(urlFull).addHeaders(headers)
response <- client.request(request)
} yield response).mapError(_.getMessage)

def getToken(email: String, password: String): ZIO[env, String, String] =
for {
response <-
Expand Down Expand Up @@ -122,4 +136,35 @@ abstract class E2EZSpec extends ZIOSpecDefault with TestStartupUtils {
} yield lmd.value
}

object AdminApiRestClient {
private val shortcodePlaceholder = ":shortcode"
private def replace(shortcode: Shortcode, url: String) = url.replace(shortcodePlaceholder, shortcode.value)

val projectsShortcodePath: String = s"/admin/projects/shortcode/$shortcodePlaceholder"
val projectsShortcodeErasePath: String = s"$projectsShortcodePath/erase"

private val userIriPlaceholder = ":userIri"
private def replace(userIri: UserIri, url: String) = url.replace(userIriPlaceholder, userIri.value)

val usersPath = "/admin/users"
val usersIriPath = s"$usersPath/iri/$userIriPlaceholder"
val usersIriProjectMemberships = s"$usersIriPath/project-memberships"
val usersIriProjectAdminMemberships = s"$usersIriPath/project-admin-memberships"
val usersIriGroupMemberships = s"$usersIriPath/group-memberships"

def eraseProjectAsRoot(shortcode: Shortcode): ZIO[env, String, Response] = for {
jwt <- getRootToken.map(Some.apply)
response <- sendDeleteRequest(replace(shortcode, projectsShortcodeErasePath), jwt)
} yield response

def getProjectAsRoot(shortcode: Shortcode): ZIO[env, String, Response] = for {
jwt <- getRootToken.map(Some.apply)
response <- sendGetRequest(replace(shortcode, projectsShortcodePath))
} yield response

def getUserAsRoot(userIri: UserIri): ZIO[env, IRI, Response] = for {
jwt <- getRootToken.map(Some.apply)
response <- sendGetRequest(replace(userIri, usersIriPath))
} yield response
}
}
183 changes: 183 additions & 0 deletions integration/src/test/scala/org/knora/webapi/ProjectEraseIT.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi
import zio.ZIO
import zio.http.Status
import zio.test.Spec
import zio.test.TestAspect
import zio.test.assertTrue

import dsp.valueobjects.LanguageCode
import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2
import org.knora.webapi.slice.admin.api.GroupsRequests.GroupCreateRequest
import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.UserCreateRequest
import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.ProjectCreateRequest
import org.knora.webapi.slice.admin.domain.model.Email
import org.knora.webapi.slice.admin.domain.model.FamilyName
import org.knora.webapi.slice.admin.domain.model.GivenName
import org.knora.webapi.slice.admin.domain.model.GroupDescriptions
import org.knora.webapi.slice.admin.domain.model.GroupIri
import org.knora.webapi.slice.admin.domain.model.GroupName
import org.knora.webapi.slice.admin.domain.model.GroupSelfJoin
import org.knora.webapi.slice.admin.domain.model.GroupStatus
import org.knora.webapi.slice.admin.domain.model.KnoraProject
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Description
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortname
import org.knora.webapi.slice.admin.domain.model.Password
import org.knora.webapi.slice.admin.domain.model.SystemAdmin
import org.knora.webapi.slice.admin.domain.model.UserIri
import org.knora.webapi.slice.admin.domain.model.UserStatus
import org.knora.webapi.slice.admin.domain.model.Username
import org.knora.webapi.slice.admin.domain.service.KnoraGroupService
import org.knora.webapi.slice.admin.domain.service.KnoraProjectService
import org.knora.webapi.slice.admin.domain.service.KnoraUserService
import org.knora.webapi.slice.admin.domain.service.ProjectService
import org.knora.webapi.slice.resourceinfo.domain.InternalIri
import org.knora.webapi.store.triplestore.api.TriplestoreService
import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Ask
import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update

object ProjectEraseIT extends E2EZSpec {

private val users = ZIO.serviceWithZIO[KnoraUserService]
private val projects = ZIO.serviceWithZIO[KnoraProjectService]
private val groups = ZIO.serviceWithZIO[KnoraGroupService]
private val db = ZIO.serviceWithZIO[TriplestoreService]

private def getUser(userIri: UserIri) = users(_.findById(userIri)).someOrFail(Exception(s"Must be present $userIri"))
private val shortcode = Shortcode.unsafeFrom("9999")
private val getProject = projects(_.findByShortcode(shortcode)).someOrFail(Exception(s"Must be present $shortcode"))

private val createProject = projects(
_.createProject(
ProjectCreateRequest(
None,
Shortname.unsafeFrom("TestPrj"),
Shortcode.unsafeFrom("9999"),
None,
List(Description.unsafeFrom("description", None)),
List.empty,
None,
KnoraProject.Status.Active,
KnoraProject.SelfJoin.CanJoin,
),
),
).orDie

private val createUser = users(
_.createNewUser(
UserCreateRequest(
None,
Username.unsafeFrom("donald.duck"),
Email.unsafeFrom("donald.duck@example.com"),
GivenName.unsafeFrom("Donald"),
FamilyName.unsafeFrom("Duck"),
Password.unsafeFrom("test"),
UserStatus.from(true),
LanguageCode.en,
SystemAdmin.IsNotSystemAdmin,
),
),
)

private def createGroup(project: KnoraProject) = groups(
_.createGroup(
GroupCreateRequest(
None,
GroupName.unsafeFrom("group"),
GroupDescriptions.unsafeFrom(Seq(StringLiteralV2.unsafeFrom("group description", None))),
project.id,
GroupStatus.active,
GroupSelfJoin.enabled,
),
project,
),
)

private val createUserWithMemberships = for {
user <- createUser
project <- getProject
group <- createGroup(project)
user <- users(_.addUserToGroup(user, group))
user <- users(_.addUserToProject(user, project))
user <- users(_.addUserToProjectAsAdmin(user, project))
} yield (user, group)

override def e2eSpec: Spec[ProjectEraseIT.env, Any] =
suiteAll(s"The erasing project endpoint ${AdminApiRestClient.projectsShortcodeErasePath}") {

suite("given project to delete is not present")(
test("when called as root then it responds with Not Found") {
for {
resp <- AdminApiRestClient.eraseProjectAsRoot(shortcode)
} yield assertTrue(resp.status == Status.NotFound)
},
)

suite("given project to delete is present")(
test("when called as root then it should delete the project and respond with Ok") {
for {
before <- AdminApiRestClient.getProjectAsRoot(shortcode)
erased <- AdminApiRestClient.eraseProjectAsRoot(shortcode)
after <- AdminApiRestClient.getProjectAsRoot(shortcode)
} yield assertTrue(
before.status == Status.Ok,
erased.status == Status.Ok,
after.status == Status.NotFound,
)
},
test("when called as root then it should delete user memberships and groups and respond with Ok") {
for {
// given
userAndGroup <- createUserWithMemberships
(user, group) = userAndGroup
project <- getProject

// when
erased <- AdminApiRestClient.eraseProjectAsRoot(shortcode)

// then
user <- getUser(user.id)
groupWasDeleted <- groups(_.findById(group.id)).map(_.isEmpty)
} yield assertTrue(
erased.status == Status.Ok,
!user.isInProject.contains(project.id),
!user.isInProjectAdminGroup.contains(project.id),
!user.isInGroup.contains(group),
groupWasDeleted,
)
},
test("when called as root then it should delete the project graph") {
def doesGraphExist(graphName: InternalIri) = db(_.query(Ask(s"ASK { GRAPH <${graphName.value}> {} }")))
for {
// given
project <- getProject
graphName = ProjectService.projectDataNamedGraphV2(project)
_ <- // insert something into the project graph, otherwise it does not exist
db(_.query(Update(s"""
|INSERT DATA {
| GRAPH <${graphName.value}> {
| <http://example.org/resource> <http://example.org/property> "value".
| }
|}""".stripMargin)))
graphExisted <- doesGraphExist(graphName)

// when
erased <- AdminApiRestClient.eraseProjectAsRoot(shortcode)

// then
graphDeleted <- doesGraphExist(graphName).negate
} yield assertTrue(
erased.status == Status.Ok,
graphExisted,
graphDeleted,
)
},
) @@ TestAspect.before(createProject)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.e2e.v2

import org.apache.pekko.http.scaladsl.model.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.e2ez

import zio.json.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.messages.admin.responder.listsmessages

import org.apache.pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ case class KnoraUserService(
user <- updateUser(user, UserChangeRequest(groups = Some(user.isInGroup :+ group.groupIri))).orDie
} yield user

def addUserToGroup(user: KnoraUser, group: KnoraGroup): UIO[KnoraUser] =
addUserToGroup(user, group.id)

private def addUserToGroup(user: KnoraUser, groupIri: GroupIri): UIO[KnoraUser] =
updateUser(user, UserChangeRequest(groups = Some(user.isInGroup :+ groupIri))).orDie

def removeUserFromGroup(user: User, group: Group): IO[NotGroupMember, KnoraUser] =
userRepo.findById(user.userIri).someOrFailException.orDie.flatMap(removeUserFromGroup(_, group))

Expand All @@ -156,9 +162,14 @@ case class KnoraUserService(

def addUserToProject(user: KnoraUser, project: Project): IO[IsProjectMember, KnoraUser] = for {
_ <- ZIO.fail(IsProjectMember(user.id, project.projectIri)).when(user.isInProject.contains(project.projectIri))
user <- updateUser(user, UserChangeRequest(projects = Some(user.isInProject :+ project.projectIri))).orDie
user <- addUserToProject(user, project.projectIri)
} yield user

def addUserToProject(user: KnoraUser, project: KnoraProject): UIO[KnoraUser] = addUserToProject(user, project.id)

private def addUserToProject(user: KnoraUser, projectIri: ProjectIri): UIO[KnoraUser] =
updateUser(user, UserChangeRequest(projects = Some(user.isInProject :+ projectIri))).orDie

/**
* Removes a user from a project.
* If the user is a member of the project admin group, the user is also removed from the project admin group.
Expand Down Expand Up @@ -195,14 +206,18 @@ case class KnoraUserService(
): IO[IsProjectAdminMember | NotProjectMember, KnoraUser] = {
val projectIri = project.projectIri
for {
_ <-
ZIO.fail(IsProjectAdminMember(user.id, projectIri)).when(user.isInProjectAdminGroup.contains(projectIri))
_ <- ZIO.fail(NotProjectMember(user.id, projectIri)).unless(user.isInProject.contains(projectIri))
theChange = UserChangeRequest(projectsAdmin = Some(user.isInProjectAdminGroup :+ projectIri))
user <- updateUser(user, theChange).orDie
_ <- ZIO.fail(IsProjectAdminMember(user.id, projectIri)).when(user.isInProjectAdminGroup.contains(projectIri))
_ <- ZIO.fail(NotProjectMember(user.id, projectIri)).unless(user.isInProject.contains(projectIri))
_ <- addUserToProjectAsAdmin(user, projectIri)
} yield user
}

def addUserToProjectAsAdmin(user: KnoraUser, project: KnoraProject): UIO[KnoraUser] =
addUserToProjectAsAdmin(user, project.id)

private def addUserToProjectAsAdmin(user: KnoraUser, projectIri: ProjectIri): UIO[KnoraUser] =
updateUser(user, UserChangeRequest(projectsAdmin = Some(user.isInProjectAdminGroup :+ projectIri))).orDie

/**
* Removes a user from the project admin group of a project.
* The user must already be an admin member of the project.
Expand Down
Loading