Skip to content

Commit

Permalink
Merge branch 'main' into mpro7-patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
mpro7 authored Jun 18, 2024
2 parents 9c9b9a2 + 1ed1506 commit af8dc56
Show file tree
Hide file tree
Showing 19 changed files with 674 additions and 244 deletions.
3 changes: 3 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Scala Steward: Reformat with scalafmt 3.8.0
870d2491e283d5f6e329fa64ce0725f512dcadae

# Scala Steward: Reformat with scalafmt 3.8.2
318a790409a0dab067a060031cbc7c77ab2168e2
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.8.1"
version = "3.8.2"
runner.dialect = scala3
maxColumn = 120
align.preset = most
Expand Down
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("[email protected]"),
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 @@ -436,7 +436,7 @@ class OntologyResponderV2Spec extends CoreSpec with ImplicitSender {
"<http://rdfh.ch/0001/a-thing>", // rdf:type anything:Thing
"<http://rdfh.ch/0001/a-blue-thing>", // rdf:type anything:BlueThing, a subclass of anything:Thing
"<http://www.knora.org/ontology/0001/something#Something>", // a subclass of anything:Thing in another ontology
"<http://www.knora.org/ontology/0001/something#hasOtherSomething>",// a subproperty of anything:hasOtherThing in another ontology
"<http://www.knora.org/ontology/0001/something#hasOtherSomething>", // a subproperty of anything:hasOtherThing in another ontology
)

expectedSubjects.forall(s => errorMsg.contains(s)) should ===(true)
Expand Down
17 changes: 10 additions & 7 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ object Dependencies {
val PekkoActorVersion = "1.0.2"
val PekkoHttpVersion = "1.0.1"
val JenaVersion = "5.0.0"
val Rdf4jVersion = "4.3.11"
val Rdf4jVersion = "4.3.12"

val ZioConfigVersion = "4.0.2"
val ZioLoggingVersion = "2.3.0"
val ZioNioVersion = "2.0.2"
val ZioMetricsConnectorsVersion = "2.3.1"
val ZioPreludeVersion = "1.0.0-RC27"
val ZioSchemaVersion = "0.2.0"
val ZioVersion = "2.1.1"
val ZioVersion = "2.1.3"

// ZIO
val zio = "dev.zio" %% "zio" % ZioVersion
val zioConfig = "dev.zio" %% "zio-config" % ZioConfigVersion
val zioConfigMagnolia = "dev.zio" %% "zio-config-magnolia" % ZioConfigVersion
val zioConfigTypesafe = "dev.zio" %% "zio-config-typesafe" % ZioConfigVersion
val zioJson = "dev.zio" %% "zio-json" % "0.6.2"
val zioJson = "dev.zio" %% "zio-json" % "0.7.0"
val zioLogging = "dev.zio" %% "zio-logging" % ZioLoggingVersion
val zioLoggingSlf4jBridge = "dev.zio" %% "zio-logging-slf4j2-bridge" % ZioLoggingVersion
val zioNio = "dev.zio" %% "zio-nio" % ZioNioVersion
Expand All @@ -46,8 +46,8 @@ object Dependencies {

// refined
val refined = Seq(
"eu.timepit" %% "refined" % "0.11.1",
"dev.zio" %% "zio-json-interop-refined" % "0.6.2",
"eu.timepit" %% "refined" % "0.11.2",
"dev.zio" %% "zio-json-interop-refined" % "0.7.0",
)

// zio-test and friends
Expand Down Expand Up @@ -80,7 +80,10 @@ object Dependencies {
val jwtSprayJson = "com.github.jwt-scala" %% "jwt-zio-json" % "10.0.1"
// jwtSprayJson -> 9.0.2 is the latest version that's compatible with spray-json; if it wasn't for spray, this would be Scala 3 compatible
val springSecurityCore =
"org.springframework.security" % "spring-security-core" % "6.3.0" exclude ("commons-logging", "commons-logging") exclude ("org.springframework", "spring-aop")
"org.springframework.security" % "spring-security-core" % "6.3.0" exclude (
"commons-logging",
"commons-logging",
) exclude ("org.springframework", "spring-aop")
val bouncyCastle = "org.bouncycastle" % "bcprov-jdk15to18" % "1.78.1"

// caching
Expand Down Expand Up @@ -112,7 +115,7 @@ object Dependencies {
// found/added by the plugin but deleted anyway
val commonsLang3 = "org.apache.commons" % "commons-lang3" % "3.14.0"

val tapirVersion = "1.10.8"
val tapirVersion = "1.10.9"

val tapir = Seq(
"com.softwaremill.sttp.tapir" %% "tapir-pekko-http-server" % tapirVersion,
Expand Down
Loading

0 comments on commit af8dc56

Please sign in to comment.