Skip to content

Commit

Permalink
Unit tests for route, refactor errors
Browse files Browse the repository at this point in the history
  • Loading branch information
dantb committed Sep 27, 2023
1 parent fac9d47 commit 23266d9
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package ch.epfl.bluebrain.nexus.delta.routes

import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directive1
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{Directive1, Route}
import akka.http.scaladsl.server.Route
import cats.implicits._
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder
Expand All @@ -11,18 +12,22 @@ import ch.epfl.bluebrain.nexus.delta.routes.OrganizationsRoutes.OrganizationInpu
import ch.epfl.bluebrain.nexus.delta.sdk.OrganizationResource
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.circe.CirceUnmarshalling
import ch.epfl.bluebrain.nexus.delta.sdk.directives.AuthDirectives
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk.directives.{AuthDirectives, DeltaSchemeDirectives}
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaSchemeDirectives
import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller
import ch.epfl.bluebrain.nexus.delta.sdk.implicits._
import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.PaginationConfig
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchParams.OrganizationSearchParams
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchResults
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchResults._
import ch.epfl.bluebrain.nexus.delta.sdk.model.search.{PaginationConfig, SearchResults}
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.OrganizationDeleter
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.Organizations
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.Organization
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection._
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.{Organization, OrganizationRejection}
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions._
import io.circe.Decoder
import io.circe.generic.extras.Configuration
Expand All @@ -31,7 +36,6 @@ import kamon.instrumentation.akka.http.TracingDirectives.operationName
import monix.execution.Scheduler

import scala.annotation.nowarn
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.OrganizationDeleter

/**
* The organization routes.
Expand Down Expand Up @@ -117,21 +121,17 @@ final class OrganizationsRoutes(
},
// Deprecate organization
delete {
authorizeFor(id, orgs.write).apply {
parameter("rev".as[Int].?, "prune".?(false)) {
case (_, true) =>
authorizeFor(id, orgs.delete).apply {
emit(
orgDeleter
.deleteIfEmpty(id)
.leftMap[OrganizationRejection](_ => OrganizationRejection.OrganizationNonEmpty(id))
)
}
case (Some(rev), false) =>
parameter("rev".as[Int].?, "prune".?(false)) {
case (_, true) =>
authorizeFor(id, orgs.delete).apply {
emit(orgDeleter.deleteIfEmpty(id).leftWiden[OrganizationRejection])
}
case (Some(rev), false) =>
authorizeFor(id, orgs.write).apply {
emit(organizations.deprecate(id, rev).mapValue(_.metadata))
case (None, false) =>
complete(StatusCodes.BadRequest)
}
}
case (None, false) =>
complete(StatusCodes.BadRequest)
}
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import ch.epfl.bluebrain.nexus.delta.sdk.identities.IdentitiesDummy
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller
import ch.epfl.bluebrain.nexus.delta.sdk.implicits._
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.OrganizationsConfig
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.OrganizationDeleter
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.OrganizationsImpl
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection.OrganizationNonEmpty
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.events
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.{orgs => orgsPermissions}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.OwnerPermissionsScopeInitialization
Expand All @@ -26,9 +28,10 @@ import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Label
import ch.epfl.bluebrain.nexus.testkit.bio.IOFromMap
import io.circe.Json
import monix.bio.IO

import java.util.UUID
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.OrganizationDeleter


class OrganizationsRoutesSpec extends BaseRouteSpec with IOFromMap {

Expand All @@ -46,8 +49,10 @@ class OrganizationsRoutesSpec extends BaseRouteSpec with IOFromMap {
Set(orgsPermissions.write, orgsPermissions.read)
)

private lazy val orgs = OrganizationsImpl(Set(aopd), config, xas)
private lazy val orgDeleter = OrganizationDeleter(xas)
private lazy val orgs = OrganizationsImpl(Set(aopd), config, xas)
private lazy val orgDeleter: OrganizationDeleter = id =>
if (id == org1.label) IO.raiseError(OrganizationNonEmpty(id))
else IO.unit

private val caller = Caller(alice, Set(alice, Anonymous, Authenticated(realm), Group("group", realm)))

Expand Down Expand Up @@ -222,6 +227,33 @@ class OrganizationsRoutesSpec extends BaseRouteSpec with IOFromMap {
}
}

"fail to deprecate an organization if the revision is omitted" in {
Delete("/v1/orgs/org2") ~> addCredentials(OAuth2BearerToken("alice")) ~> routes ~> check {
status shouldEqual StatusCodes.BadRequest
}
}

"delete an organization" in {
aclChecker.append(AclAddress.fromOrg(org2.label), caller.subject -> Set(orgsPermissions.delete)).accepted
Delete("/v1/orgs/org2?prune=true") ~> addCredentials(OAuth2BearerToken("alice")) ~> routes ~> check {
status shouldEqual StatusCodes.OK
}
}

"fail when trying to delete a non-empty organization" in {
aclChecker.append(AclAddress.fromOrg(org1.label), caller.subject -> Set(orgsPermissions.delete)).accepted
Delete("/v1/orgs/org1?prune=true") ~> addCredentials(OAuth2BearerToken("alice")) ~> routes ~> check {
status shouldEqual StatusCodes.Conflict
}
}

"fail to delete an organization without organizations/delete permission" in {
aclChecker.subtract(AclAddress.fromOrg(org2.label), caller.subject -> Set(orgsPermissions.delete)).accepted
Delete("/v1/orgs/org2?prune=true") ~> addCredentials(OAuth2BearerToken("alice")) ~> routes ~> check {
status shouldEqual StatusCodes.Forbidden
}
}

"fail fetch an organization without organizations/read permission" in {
aclChecker.delete(Label.unsafe("org1")).accepted
forAll(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,41 @@
package ch.epfl.bluebrain.nexus.delta.sdk.organizations

import ch.epfl.bluebrain.nexus.delta.sdk.acls.model.AclAddress
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Label

import ch.epfl.bluebrain.nexus.delta.sdk.acls.Acls
import ch.epfl.bluebrain.nexus.delta.sourcing.model.EntityType
import cats.syntax.all._
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode
import ch.epfl.bluebrain.nexus.delta.sdk.acls.Acls
import ch.epfl.bluebrain.nexus.delta.sdk.acls.model.AclAddress
import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection.OrganizationNonEmpty
import ch.epfl.bluebrain.nexus.delta.sourcing.Transactors

import cats.syntax.all._
import ch.epfl.bluebrain.nexus.delta.sourcing.implicits._
import ch.epfl.bluebrain.nexus.delta.sourcing.model.EntityType
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Label
import com.typesafe.scalalogging.Logger
import doobie.ConnectionIO
import doobie.implicits._
import monix.bio.Task
import monix.bio.IO
import monix.bio.UIO

trait OrganizationDeleter {
def deleteIfEmpty(id: Label): Task[Unit]
def deleteIfEmpty(id: Label): IO[OrganizationNonEmpty, Unit]
}

object OrganizationDeleter {

def apply(xas: Transactors): OrganizationDeleter = new OrganizationDeleter {

def deleteIfEmpty(id: Label): Task[Unit] =
private val logger: Logger = Logger[OrganizationDeleter]

def deleteIfEmpty(id: Label): IO[OrganizationNonEmpty, Unit] =
for {
orgIsEmpty <- orgIsEmpty(id)
_ <- if (orgIsEmpty) deleteAll(id)
else Task.raiseError(new Exception(s"Cannot delete non-empty organization $id"))
_ <- if (orgIsEmpty) log(s"Deleting empty organization $id") *> deleteAll(id)
else IO.raiseError[OrganizationNonEmpty](OrganizationNonEmpty(id))
} yield ()

def deleteAll(id: Label): Task[Unit] =
List("global_events", "global_states").traverse(deleteFromTable(id, _)).transact(xas.write).void
def log(msg: String): UIO[Unit] = UIO.delay(logger.info(msg))

def deleteAll(id: Label): UIO[Unit] =
List("global_events", "global_states").traverse(deleteFromTable(id, _)).transact(xas.write).void.hideErrors

def deleteFromTable(id: Label, table: String): ConnectionIO[Unit] =
for {
Expand All @@ -41,12 +46,13 @@ object OrganizationDeleter {
def delete(id: IriOrBNode.Iri, tpe: EntityType, table: String): ConnectionIO[Unit] =
sql"""DELETE FROM $table WHERE type = $tpe AND id = $id""".update.run.void

def orgIsEmpty(id: Label): Task[Boolean] =
def orgIsEmpty(id: Label): UIO[Boolean] =
sql"""SELECT type from scoped_events WHERE org = $id LIMIT 1"""
.query[Label]
.option
.map(_.isEmpty)
.transact(xas.read)
.hideErrors
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ object OrganizationRejection {
case OrganizationRejection.OrganizationNotFound(_) => StatusCodes.NotFound
case OrganizationRejection.OrganizationAlreadyExists(_) => StatusCodes.Conflict
case OrganizationRejection.IncorrectRev(_, _) => StatusCodes.Conflict
case OrganizationRejection.OrganizationNonEmpty(_) => StatusCodes.Conflict
case OrganizationRejection.RevisionNotFound(_, _) => StatusCodes.NotFound
case _ => StatusCodes.BadRequest
}
Expand Down

0 comments on commit 23266d9

Please sign in to comment.