diff --git a/src/main/scala/org/broadinstitute/dsde/firecloud/FireCloudApiService.scala b/src/main/scala/org/broadinstitute/dsde/firecloud/FireCloudApiService.scala index a2d3afc36..9747bfaa3 100644 --- a/src/main/scala/org/broadinstitute/dsde/firecloud/FireCloudApiService.scala +++ b/src/main/scala/org/broadinstitute/dsde/firecloud/FireCloudApiService.scala @@ -70,7 +70,6 @@ trait FireCloudApiService with OauthApiService with RegisterApiService with WorkspaceApiService - with WorkspaceV2ApiService with MethodConfigurationApiService with SubmissionApiService with StatusApiService @@ -213,7 +212,6 @@ trait FireCloudApiService userServiceRoutes ~ managedGroupServiceRoutes ~ workspaceRoutes ~ - workspaceV2Routes ~ statusRoutes ~ pathPrefix("api") { apiRoutes diff --git a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/EntityApiService.scala b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/EntityApiService.scala index 05c6686b0..c9e8eae89 100644 --- a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/EntityApiService.scala +++ b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/EntityApiService.scala @@ -1,9 +1,6 @@ package org.broadinstitute.dsde.firecloud.webservice -import akka.http.scaladsl.model.HttpMethods import akka.http.scaladsl.server.Route -import com.google.common.net.UrlEscapers -import org.broadinstitute.dsde.firecloud.FireCloudConfig.Rawls.entityQueryPathFromWorkspace import org.broadinstitute.dsde.firecloud.model.ModelJsonProtocol._ import org.broadinstitute.dsde.firecloud.model._ import org.broadinstitute.dsde.firecloud.service.{FireCloudDirectives, FireCloudRequestBuilding} @@ -45,75 +42,27 @@ trait EntityApiService } } ~ pathPrefix("entities") { - pathEnd { - requireUserInfo() { _ => - passthrough(encodeUri(baseRawlsEntitiesUrl), HttpMethods.GET) - } - } ~ - path("copy") { - post { - requireUserInfo() { userInfo => - parameter(Symbol("linkExistingEntities").?) { linkExistingEntities => - entity(as[EntityCopyWithoutDestinationDefinition]) { copyRequest => - val linkExistingEntitiesBool = - Try(linkExistingEntities.getOrElse("false").toBoolean).getOrElse(false) - val copyMethodConfig = new EntityCopyDefinition( - sourceWorkspace = copyRequest.sourceWorkspace, - destinationWorkspace = WorkspaceName(workspaceNamespace, workspaceName), - entityType = copyRequest.entityType, - entityNames = copyRequest.entityNames - ) - val extReq = Post(FireCloudConfig.Rawls.workspacesEntitiesCopyUrl(linkExistingEntitiesBool), - copyMethodConfig - ) + path("copy") { + post { + requireUserInfo() { userInfo => + parameter(Symbol("linkExistingEntities").?) { linkExistingEntities => + entity(as[EntityCopyWithoutDestinationDefinition]) { copyRequest => + val linkExistingEntitiesBool = + Try(linkExistingEntities.getOrElse("false").toBoolean).getOrElse(false) + val copyMethodConfig = new EntityCopyDefinition( + sourceWorkspace = copyRequest.sourceWorkspace, + destinationWorkspace = WorkspaceName(workspaceNamespace, workspaceName), + entityType = copyRequest.entityType, + entityNames = copyRequest.entityNames + ) + val extReq = Post(FireCloudConfig.Rawls.workspacesEntitiesCopyUrl(linkExistingEntitiesBool), + copyMethodConfig + ) - complete(userAuthedRequest(extReq)(userInfo)) - } + complete(userAuthedRequest(extReq)(userInfo)) } } } - } ~ - path("delete") { - post { - passthrough(encodeUri(baseRawlsEntitiesUrl + "/delete"), HttpMethods.POST) - } - } ~ - pathPrefix(Segment) { entityType => - streamingPassthrough( - FireCloudConfig.Rawls.entityPathFromWorkspace(escapePathSegment(workspaceNamespace), - escapePathSegment(workspaceName) - ) + "/" + entityType - ) - } - } ~ - pathPrefix("entityQuery") { - streamingPassthrough( - entityQueryPathFromWorkspace(escapePathSegment(workspaceNamespace), escapePathSegment(workspaceName)) - ) - } ~ - pathPrefix("entityTypes") { - extractRequest { req => - pathPrefix(Segment) { _ => // entityType - // all passthroughs under entityTypes use the same path in Orch as they do in Rawls, - // so we can just grab the path from the request object - val passthroughTarget = encodeUri(FireCloudConfig.Rawls.baseUrl + req.uri.path.toString) - pathEnd { - patch { - passthrough(passthroughTarget, HttpMethods.PATCH) - } ~ - delete { - passthrough(passthroughTarget, HttpMethods.DELETE) - } - } ~ - pathPrefix("attributes") { - path(Segment) { _ => // attributeName - pathEnd { - patch { - passthrough(passthroughTarget, HttpMethods.PATCH) - } - } - } - } } } } diff --git a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/PassthroughApiService.scala b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/PassthroughApiService.scala index 069f3d013..271978894 100644 --- a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/PassthroughApiService.scala +++ b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/PassthroughApiService.scala @@ -12,7 +12,8 @@ trait PassthroughApiService extends Directives with StreamingPassthrough { val passthroughRoutes: Route = concat( pathPrefix("ga4gh")(streamingPassthrough(s"$agora/ga4gh")), pathPrefix("api" / "billing")(streamingPassthrough(s"$rawls/api/billing")), - pathPrefix("api" / "notifications")(streamingPassthrough(s"$rawls/api/notifications")) + pathPrefix("api" / "notifications")(streamingPassthrough(s"$rawls/api/notifications")), + pathPrefix("api" / "workspaces")(streamingPassthrough(s"$rawls/api/workspaces")) ) } diff --git a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/SubmissionApiService.scala b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/SubmissionApiService.scala index 328f45a4a..7edebd89f 100644 --- a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/SubmissionApiService.scala +++ b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/SubmissionApiService.scala @@ -1,7 +1,7 @@ package org.broadinstitute.dsde.firecloud.webservice import akka.http.scaladsl.server.Route -import org.broadinstitute.dsde.firecloud.FireCloudConfig.Rawls.{submissionQueueStatusUrl, workspacesUrl} +import org.broadinstitute.dsde.firecloud.FireCloudConfig.Rawls.submissionQueueStatusUrl import org.broadinstitute.dsde.firecloud.service.FireCloudDirectives import org.broadinstitute.dsde.firecloud.utils.StreamingPassthrough @@ -9,9 +9,5 @@ trait SubmissionApiService extends FireCloudDirectives with StreamingPassthrough val submissionServiceRoutes: Route = pathPrefix("submissions" / "queueStatus") { streamingPassthrough(submissionQueueStatusUrl) - } ~ - pathPrefix("workspaces" / Segment / Segment / "submissions") { (namespace, name) => - // N.B. streamingPassthrough to ".../submissions" also handles ".../submissionsCount" - streamingPassthrough(s"$workspacesUrl/${escapePathSegment(namespace)}/${escapePathSegment(name)}/submissions") - } + } } diff --git a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceApiService.scala b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceApiService.scala index aa2993a9f..bf46ba4aa 100644 --- a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceApiService.scala +++ b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceApiService.scala @@ -61,405 +61,332 @@ trait WorkspaceApiService extends FireCloudRequestBuilding with FireCloudDirecti } ~ pathPrefix("api") { pathPrefix("workspaces") { - pathEnd { - requireUserInfo() { _ => - extract(_.request.uri.query()) { query => - passthrough(Uri(rawlsWorkspacesRoot).withQuery(query), HttpMethods.GET, HttpMethods.POST) - } - } - } ~ - pathPrefix("tags") { - pathEnd { - requireUserInfo() { _ => - parameter(Symbol("q").?) { queryString => - val baseUri = Uri(rawlsWorkspacesRoot + "/tags") - val uri = queryString match { - case Some(query) => baseUri.withQuery(Query(("q", query))) - case None => baseUri - } - passthrough(uri.toString, HttpMethods.GET) + pathPrefix(Segment / Segment) { (workspaceNamespace, workspaceName) => + val workspacePath = encodeUri(rawlsWorkspacesRoot + "/%s/%s".format(workspaceNamespace, workspaceName)) + pathEnd { + delete { + requireUserInfo() { userInfo => + complete { + workspaceServiceConstructor(userInfo).deleteWorkspace(workspaceNamespace, workspaceName) } } } } ~ - pathPrefix(Segment / Segment) { (workspaceNamespace, workspaceName) => - val workspacePath = encodeUri(rawlsWorkspacesRoot + "/%s/%s".format(workspaceNamespace, workspaceName)) - pathEnd { - get { - requireUserInfo() { _ => - extract(_.request.uri.query()) { query => - passthrough(Uri(workspacePath).withQuery(query), HttpMethods.GET) - } - } - } ~ - delete { - requireUserInfo() { userInfo => - complete { - workspaceServiceConstructor(userInfo).deleteWorkspace(workspaceNamespace, workspaceName) - } - } - } - } ~ - path("methodconfigs") { - get { - extract(_.request.uri.query()) { query => - requireUserInfo() { _ => - passthrough(Uri(workspacePath + "/methodconfigs").withQuery(query), HttpMethods.GET) - } - } - } ~ - post { - requireUserInfo() { userInfo => - entity(as[MethodConfiguration]) { methodConfig => - if ( - !methodConfig.outputs.exists { param => - param._2.value - .startsWith("this.library:") || param._2.value.startsWith("workspace.library:") - } - ) { - val passthroughReq = Post(workspacePath + "/methodconfigs", methodConfig) - complete(userAuthedRequest(passthroughReq)(userInfo)) - } else { - complete( - StatusCodes.Forbidden, - ErrorReport("Methods and configurations can not create or modify library attributes") - ) - } - } - } - } - } ~ - path("flexibleImportEntities") { - post { - requireUserInfo() { userInfo => - parameter("async" ? "false") { asyncStr => - parameter("deleteEmptyValues" ? "false") { deleteEmptyValuesStr => - formFields(Symbol("entities")) { entitiesTSV => - complete { - val isAsync = java.lang.Boolean.valueOf(asyncStr) // for lenient parsing - val deleteEmptyValues = - java.lang.Boolean.valueOf(deleteEmptyValuesStr) // for lenient parsing - entityServiceConstructor(FlexibleModelSchema).importEntitiesFromTSV(workspaceNamespace, - workspaceName, - entitiesTSV, - userInfo, - isAsync, - deleteEmptyValues - ) - } - } - + path("methodconfigs") { + post { + requireUserInfo() { userInfo => + entity(as[MethodConfiguration]) { methodConfig => + if ( + !methodConfig.outputs.exists { param => + param._2.value + .startsWith("this.library:") || param._2.value.startsWith("workspace.library:") } + ) { + val passthroughReq = Post(workspacePath + "/methodconfigs", methodConfig) + complete(userAuthedRequest(passthroughReq)(userInfo)) + } else { + complete( + StatusCodes.Forbidden, + ErrorReport("Methods and configurations can not create or modify library attributes") + ) } } } - } ~ - path("importEntities") { - post { - requireUserInfo() { userInfo => + } + } ~ + path("flexibleImportEntities") { + post { + requireUserInfo() { userInfo => + parameter("async" ? "false") { asyncStr => parameter("deleteEmptyValues" ? "false") { deleteEmptyValuesStr => formFields(Symbol("entities")) { entitiesTSV => complete { + val isAsync = java.lang.Boolean.valueOf(asyncStr) // for lenient parsing val deleteEmptyValues = java.lang.Boolean.valueOf(deleteEmptyValuesStr) // for lenient parsing - entityServiceConstructor(FirecloudModelSchema).importEntitiesFromTSV(workspaceNamespace, - workspaceName, - entitiesTSV, - userInfo, - deleteEmptyValues = - deleteEmptyValues + entityServiceConstructor(FlexibleModelSchema).importEntitiesFromTSV(workspaceNamespace, + workspaceName, + entitiesTSV, + userInfo, + isAsync, + deleteEmptyValues ) } } + } } } - } ~ - // POST importPFB will likely be deprecated in the future; use POST importJob instead - path("importPFB") { - post { - requireUserInfo() { userInfo => - // this endpoint does not accept a filetype. We hardcode the filetype to "pfb". - entity(as[PFBImportRequest]) { pfbRequest => - val importRequest = AsyncImportRequest(pfbRequest.url, FILETYPE_PFB) + } + } ~ + path("importEntities") { + post { + requireUserInfo() { userInfo => + parameter("deleteEmptyValues" ? "false") { deleteEmptyValuesStr => + formFields(Symbol("entities")) { entitiesTSV => complete { - entityServiceConstructor(FlexibleModelSchema).importJob(workspaceNamespace, - workspaceName, - importRequest, - userInfo + val deleteEmptyValues = + java.lang.Boolean.valueOf(deleteEmptyValuesStr) // for lenient parsing + entityServiceConstructor(FirecloudModelSchema).importEntitiesFromTSV(workspaceNamespace, + workspaceName, + entitiesTSV, + userInfo, + deleteEmptyValues = + deleteEmptyValues ) } } } } - } ~ - path("importJob") { - post { - requireUserInfo() { userInfo => - entity(as[AsyncImportRequest]) { importRequest => - complete { - entityServiceConstructor(FlexibleModelSchema).importJob(workspaceNamespace, - workspaceName, - importRequest, - userInfo - ) - } + } + } ~ + // POST importPFB will likely be deprecated in the future; use POST importJob instead + path("importPFB") { + post { + requireUserInfo() { userInfo => + // this endpoint does not accept a filetype. We hardcode the filetype to "pfb". + entity(as[PFBImportRequest]) { pfbRequest => + val importRequest = AsyncImportRequest(pfbRequest.url, FILETYPE_PFB) + complete { + entityServiceConstructor(FlexibleModelSchema).importJob(workspaceNamespace, + workspaceName, + importRequest, + userInfo + ) } } } - } ~ - // GET importPFB is deprecated; use GET importJob instead - path("importPFB" | "importJob") { - get { - requireUserInfo() { userInfo => - parameter(Symbol("running_only").as[Boolean].withDefault(false)) { runningOnly => - complete { - entityServiceConstructor(FlexibleModelSchema).listJobs(workspaceNamespace, - workspaceName, - runningOnly, - userInfo - ) map { respBody => - RequestComplete(OK, respBody) - } - } + } + } ~ + path("importJob") { + post { + requireUserInfo() { userInfo => + entity(as[AsyncImportRequest]) { importRequest => + complete { + entityServiceConstructor(FlexibleModelSchema).importJob(workspaceNamespace, + workspaceName, + importRequest, + userInfo + ) } } } - } ~ - // GET importPFB/jobId is deprecated; use GET importJob/jobId instead - path(("importPFB" | "importJob") / Segment) { jobId => - get { - requireUserInfo() { userInfo => + } + } ~ + // GET importPFB is deprecated; use GET importJob instead + path("importPFB" | "importJob") { + get { + requireUserInfo() { userInfo => + parameter(Symbol("running_only").as[Boolean].withDefault(false)) { runningOnly => complete { - entityServiceConstructor(FlexibleModelSchema).getJob(workspaceNamespace, - workspaceName, - jobId, - userInfo + entityServiceConstructor(FlexibleModelSchema).listJobs(workspaceNamespace, + workspaceName, + runningOnly, + userInfo ) map { respBody => RequestComplete(OK, respBody) } } } } - } ~ - path("updateAttributes") { - patch { - requireUserInfo() { userInfo: UserInfo => - entity(as[Seq[AttributeUpdateOperation]]) { replacementAttributes => - complete { - workspaceServiceConstructor(userInfo).updateWorkspaceAttributes(workspaceNamespace, - workspaceName, - replacementAttributes - ) - } + } + } ~ + // GET importPFB/jobId is deprecated; use GET importJob/jobId instead + path(("importPFB" | "importJob") / Segment) { jobId => + get { + requireUserInfo() { userInfo => + complete { + entityServiceConstructor(FlexibleModelSchema).getJob(workspaceNamespace, + workspaceName, + jobId, + userInfo + ) map { respBody => + RequestComplete(OK, respBody) } } } - } ~ - path("setAttributes") { - patch { - requireUserInfo() { userInfo => - implicit val impAttributeFormat: AttributeFormat = new AttributeFormat - with PlainArrayAttributeListSerializer - entity(as[AttributeMap]) { newAttributes => - complete { - workspaceServiceConstructor(userInfo).setWorkspaceAttributes(workspaceNamespace, - workspaceName, - newAttributes - ) - } + } + } ~ + path("updateAttributes") { + patch { + requireUserInfo() { userInfo: UserInfo => + entity(as[Seq[AttributeUpdateOperation]]) { replacementAttributes => + complete { + workspaceServiceConstructor(userInfo).updateWorkspaceAttributes(workspaceNamespace, + workspaceName, + replacementAttributes + ) } } } - } ~ - path("exportAttributesTSV") { - get { - requireUserInfo() { userInfo => + } + } ~ + path("setAttributes") { + patch { + requireUserInfo() { userInfo => + implicit val impAttributeFormat: AttributeFormat = new AttributeFormat + with PlainArrayAttributeListSerializer + entity(as[AttributeMap]) { newAttributes => complete { - workspaceServiceConstructor(userInfo).exportWorkspaceAttributesTSV(workspaceNamespace, - workspaceName, - workspaceName + filename + workspaceServiceConstructor(userInfo).setWorkspaceAttributes(workspaceNamespace, + workspaceName, + newAttributes ) } } } - } ~ - path("importAttributesTSV") { - post { - requireUserInfo() { userInfo => - formFields(Symbol("attributes")) { attributesTSV => - complete { - workspaceServiceConstructor(userInfo).importAttributesFromTSV(workspaceNamespace, - workspaceName, - attributesTSV - ) - } - } + } + } ~ + path("exportAttributesTSV") { + get { + requireUserInfo() { userInfo => + complete { + workspaceServiceConstructor(userInfo).exportWorkspaceAttributesTSV(workspaceNamespace, + workspaceName, + workspaceName + filename + ) } } - } ~ - path("acl") { - patch { - requireUserInfo() { userInfo => - parameter(Symbol("inviteUsersNotFound").?) { inviteUsersNotFound => - entity(as[List[WorkspaceACLUpdate]]) { aclUpdates => - complete { - workspaceServiceConstructor(userInfo).updateWorkspaceACL( - workspaceNamespace, - workspaceName, - aclUpdates, - userInfo.userEmail, - userInfo.id, - inviteUsersNotFound.getOrElse("false").toBoolean - ) - } - } - } - } - } ~ - get { - requireUserInfo() { _ => - passthrough(workspacePath + "/acl", HttpMethods.GET) - } - } - } ~ - path("catalog") { - get { - requireUserInfo() { userInfo => + } + } ~ + path("importAttributesTSV") { + post { + requireUserInfo() { userInfo => + formFields(Symbol("attributes")) { attributesTSV => complete { - workspaceServiceConstructor(userInfo).getCatalog(workspaceNamespace, workspaceName, userInfo) + workspaceServiceConstructor(userInfo).importAttributesFromTSV(workspaceNamespace, + workspaceName, + attributesTSV + ) } } - } ~ - patch { - requireUserInfo() { userInfo => - entity(as[Seq[WorkspaceCatalog]]) { updates => - complete { - workspaceServiceConstructor(userInfo).updateCatalog(workspaceNamespace, - workspaceName, - updates, - userInfo - ) - } + } + } + } ~ + path("acl") { + patch { + requireUserInfo() { userInfo => + parameter(Symbol("inviteUsersNotFound").?) { inviteUsersNotFound => + entity(as[List[WorkspaceACLUpdate]]) { aclUpdates => + complete { + workspaceServiceConstructor(userInfo).updateWorkspaceACL( + workspaceNamespace, + workspaceName, + aclUpdates, + userInfo.userEmail, + userInfo.id, + inviteUsersNotFound.getOrElse("false").toBoolean + ) } } } - } ~ - path("checkBucketReadAccess") { - requireUserInfo() { _ => - passthrough(workspacePath + "/checkBucketReadAccess", HttpMethods.GET) } } ~ - path("checkIamActionWithLock" / Segment) { samAction => - requireUserInfo() { _ => - passthrough(workspacePath + "/checkIamActionWithLock/" + samAction, HttpMethods.GET) - } - } ~ - path("bucketOptions") { - requireUserInfo() { _ => - passthrough(workspacePath + "/bucketOptions", HttpMethods.GET) - } - } ~ - path("sendChangeNotification") { - requireUserInfo() { _ => - passthrough(workspacePath + "/sendChangeNotification", HttpMethods.POST) + get { + requireUserInfo() { _ => + passthrough(workspacePath + "/acl", HttpMethods.GET) + } } - } ~ - path("accessInstructions") { - requireUserInfo() { _ => - passthrough(workspacePath + "/accessInstructions", HttpMethods.GET) + } ~ + path("catalog") { + get { + requireUserInfo() { userInfo => + complete { + workspaceServiceConstructor(userInfo).getCatalog(workspaceNamespace, workspaceName, userInfo) + } } } ~ - path("clone") { - post { + patch { requireUserInfo() { userInfo => - entity(as[WorkspaceRequest]) { createRequest => - // the only reason this is not a passthrough is because library needs to overwrite any publish and discoverableByGroups values - val cloneRequest = createRequest.copy(attributes = - createRequest.attributes + (AttributeName("library", "published") -> AttributeBoolean( - false - )) + (AttributeName("library", "discoverableByGroups") -> AttributeValueEmptyList) - ) + entity(as[Seq[WorkspaceCatalog]]) { updates => complete { - workspaceServiceConstructor(userInfo).cloneWorkspace(workspaceNamespace, - workspaceName, - cloneRequest + workspaceServiceConstructor(userInfo).updateCatalog(workspaceNamespace, + workspaceName, + updates, + userInfo ) } } } } - } ~ - path("lock") { - requireUserInfo() { _ => - passthrough(workspacePath + "/lock", HttpMethods.PUT) + } ~ + path("clone") { + post { + requireUserInfo() { userInfo => + entity(as[WorkspaceRequest]) { createRequest => + // the only reason this is not a passthrough is because library needs to overwrite any publish and discoverableByGroups values + val cloneRequest = createRequest.copy(attributes = + createRequest.attributes + (AttributeName("library", "published") -> AttributeBoolean( + false + )) + (AttributeName("library", "discoverableByGroups") -> AttributeValueEmptyList) + ) + complete { + workspaceServiceConstructor(userInfo).cloneWorkspace(workspaceNamespace, + workspaceName, + cloneRequest + ) + } + } } - } ~ - path("unlock") { - requireUserInfo() { _ => - passthrough(workspacePath + "/unlock", HttpMethods.PUT) + } + } ~ + path("storageCostEstimate") { + get { + parameters("userProject".optional) { userProject => + requireUserInfo() { userInfo => + complete { + workspaceServiceConstructor(userInfo).getStorageCostEstimate(workspaceNamespace, + workspaceName, + userProject.map(GoogleProjectId) + ) + } + } } - } ~ - path("bucketUsage") { - passthrough(workspacePath + "/bucketUsage", HttpMethods.GET) - } ~ - path("storageCostEstimate") { + } + } ~ + path("tags") { + requireUserInfo() { userInfo => get { - parameters("userProject".optional) { userProject => - requireUserInfo() { userInfo => + complete(workspaceServiceConstructor(userInfo).getTags(workspaceNamespace, workspaceName)) + } ~ + put { + entity(as[List[String]]) { tags => complete { - workspaceServiceConstructor(userInfo).getStorageCostEstimate(workspaceNamespace, - workspaceName, - userProject.map(GoogleProjectId) - ) + workspaceServiceConstructor(userInfo).putTags(workspaceNamespace, workspaceName, tags) } } - } - } - } ~ - path("tags") { - requireUserInfo() { userInfo => - get { - complete(workspaceServiceConstructor(userInfo).getTags(workspaceNamespace, workspaceName)) } ~ - put { - entity(as[List[String]]) { tags => - complete { - workspaceServiceConstructor(userInfo).putTags(workspaceNamespace, workspaceName, tags) - } - } - } ~ - patch { - entity(as[List[String]]) { tags => - complete { - workspaceServiceConstructor(userInfo).patchTags(workspaceNamespace, workspaceName, tags) - } - } - } ~ - delete { - entity(as[List[String]]) { tags => - complete { - workspaceServiceConstructor(userInfo).deleteTags(workspaceNamespace, workspaceName, tags) - } + patch { + entity(as[List[String]]) { tags => + complete { + workspaceServiceConstructor(userInfo).patchTags(workspaceNamespace, workspaceName, tags) } } - } - } ~ - path("permissionReport") { - requireUserInfo() { userInfo => - post { - entity(as[PermissionReportRequest]) { reportInput => + } ~ + delete { + entity(as[List[String]]) { tags => complete { - permissionReportServiceConstructor(userInfo).getPermissionReport(workspaceNamespace, - workspaceName, - reportInput - ) + workspaceServiceConstructor(userInfo).deleteTags(workspaceNamespace, workspaceName, tags) } } } + } + } ~ + path("permissionReport") { + requireUserInfo() { userInfo => + post { + entity(as[PermissionReportRequest]) { reportInput => + complete { + permissionReportServiceConstructor(userInfo).getPermissionReport(workspaceNamespace, + workspaceName, + reportInput + ) + } + } } } - } + } + } } } } diff --git a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceV2ApiService.scala b/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceV2ApiService.scala deleted file mode 100644 index e5f6a2ab3..000000000 --- a/src/main/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceV2ApiService.scala +++ /dev/null @@ -1,17 +0,0 @@ -package org.broadinstitute.dsde.firecloud.webservice - -import akka.http.scaladsl.server.Route - -import org.broadinstitute.dsde.firecloud.FireCloudConfig -import org.broadinstitute.dsde.firecloud.service.FireCloudDirectives -import org.broadinstitute.dsde.firecloud.utils.StreamingPassthrough - -trait WorkspaceV2ApiService extends FireCloudDirectives with StreamingPassthrough { - - private val rawlsWorkspacesV2Root: String = FireCloudConfig.Rawls.workspacesV2Url - - val workspaceV2Routes: Route = - pathPrefix("api" / "workspaces" / "v2") { - streamingPassthrough(rawlsWorkspacesV2Root) - } -} diff --git a/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/EntityApiServiceSpec.scala b/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/EntityApiServiceSpec.scala index 163ead6fe..e7759fac6 100644 --- a/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/EntityApiServiceSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/EntityApiServiceSpec.scala @@ -13,8 +13,6 @@ import org.mockserver.integration.ClientAndServer import org.mockserver.integration.ClientAndServer._ import org.mockserver.model.HttpClassCallback.callback import org.mockserver.model.HttpRequest._ -import spray.json.DefaultJsonProtocol._ -import spray.json._ import scala.concurrent.ExecutionContext @@ -28,13 +26,7 @@ class EntityApiServiceSpec extends BaseServiceSpec with EntityApiService with Sp var workspaceServer: ClientAndServer = _ val apiPrefix = FireCloudConfig.Rawls.authPrefix + FireCloudConfig.Rawls.workspacesPath - val validFireCloudEntitiesPath = apiPrefix + "/broad-dsde-dev/valid/entities" - val validFireCloudEntitiesSamplePath = apiPrefix + "/broad-dsde-dev/valid/entities/sample" - val validFireCloudEntityQuerySamplePath = apiPrefix + "/broad-dsde-dev/valid/entityQuery/sample" val validFireCloudEntitiesCopyPath = apiPrefix + "/broad-dsde-dev/valid/entities/copy" - val validFireCloudEntitiesBulkDeletePath = apiPrefix + "/broad-dsde-dev/valid/entities/delete" - val invalidFireCloudEntitiesPath = apiPrefix + "/broad-dsde-dev/invalid/entities" - val invalidFireCloudEntitiesSamplePath = apiPrefix + "/broad-dsde-dev/invalid/entities/sample" val invalidFireCloudEntitiesCopyPath = apiPrefix + "/broad-dsde-dev/invalid/entities/copy" val validEntityCopy = EntityCopyWithoutDestinationDefinition( @@ -48,11 +40,6 @@ class EntityApiServiceSpec extends BaseServiceSpec with EntityApiService with Sp Seq("sample_01") ) - val validEntityDelete = Seq(EntityId("sample", "id"), EntityId("sample", "bar")) - val invalidEntityDelete = validEntityCopy // we're testing that the payload can't be unmarshalled to a Seq[EntityId] - val mixedFailEntityDelete = Seq(EntityId("sample", "foo"), EntityId("failme", "kthxbai"), EntityId("sample", "bar")) - val allFailEntityDelete = Seq(EntityId("failme", "kthxbai")) - def entityCopyWithDestination(copyDef: EntityCopyDefinition) = new EntityCopyDefinition( sourceWorkspace = copyDef.sourceWorkspace, destinationWorkspace = WorkspaceName("broad-dsde-dev", "valid"), @@ -60,70 +47,8 @@ class EntityApiServiceSpec extends BaseServiceSpec with EntityApiService with Sp entityNames = copyDef.entityNames ) - val sampleAtts = Map( - AttributeName.withDefaultNS("sample_type") -> AttributeString("Blood"), - AttributeName.withDefaultNS("ref_fasta") -> AttributeString( - "gs://cancer-exome-pipeline-demo-data/Homo_sapiens_assembly19.fasta" - ), - AttributeName.withDefaultNS("ref_dict") -> AttributeString( - "gs://cancer-exome-pipeline-demo-data/Homo_sapiens_assembly19.dict" - ), - AttributeName.withDefaultNS("participant_id") -> AttributeEntityReference("participant", "subject_HCC1143") - ) - val validSampleEntities = List(Entity("sample_01", "sample", sampleAtts)) - override def beforeAll(): Unit = { workspaceServer = startClientAndServer(MockUtils.workspaceServerPort) - // Valid Entities by sample type case - workspaceServer - .when( - request() - .withMethod("GET") - .withPath( - FireCloudConfig.Rawls.authPrefix + FireCloudConfig.Rawls.entitiesPath.format("broad-dsde-dev", - "valid" - ) + "/sample" - ) - ) - .respond( - org.mockserver.model.HttpResponse - .response() - .withHeaders(MockUtils.header) - .withBody(validSampleEntities.toJson.compactPrint) - .withStatusCode(OK.intValue) - ) - // Valid entities case - workspaceServer - .when( - request() - .withMethod("GET") - .withPath( - FireCloudConfig.Rawls.authPrefix + FireCloudConfig.Rawls.entitiesPath.format("broad-dsde-dev", "valid") - ) - ) - .respond( - org.mockserver.model.HttpResponse - .response() - .withHeaders(MockUtils.header) - .withStatusCode(OK.intValue) - ) - // Valid entity query case - workspaceServer - .when( - request() - .withMethod("GET") - .withPath( - FireCloudConfig.Rawls.authPrefix + FireCloudConfig.Rawls.entityQueryPath.format("broad-dsde-dev", - "valid" - ) + "/sample" - ) - ) - .respond( - org.mockserver.model.HttpResponse - .response() - .withHeaders(MockUtils.header) - .withStatusCode(OK.intValue) - ) // Valid/Invalid Copy cases workspaceServer .when( @@ -134,55 +59,6 @@ class EntityApiServiceSpec extends BaseServiceSpec with EntityApiService with Sp .respond( callback().withCallbackClass("org.broadinstitute.dsde.firecloud.mock.ValidEntityCopyCallback") ) - - // Invalid Entities by sample type case - workspaceServer - .when( - request() - .withMethod("GET") - .withPath( - FireCloudConfig.Rawls.authPrefix + FireCloudConfig.Rawls.entitiesPath.format("broad-dsde-dev", - "invalid" - ) + "/sample" - ) - ) - .respond( - org.mockserver.model.HttpResponse - .response() - .withHeaders(MockUtils.header) - .withStatusCode(NotFound.intValue) - .withBody(MockUtils.rawlsErrorReport(NotFound).toJson.compactPrint) - ) - // Invalid entities case - workspaceServer - .when( - request() - .withMethod("GET") - .withPath( - FireCloudConfig.Rawls.authPrefix + FireCloudConfig.Rawls.entitiesPath.format("broad-dsde-dev", "invalid") - ) - ) - .respond( - org.mockserver.model.HttpResponse - .response() - .withHeaders(MockUtils.header) - .withStatusCode(NotFound.intValue) - .withBody(MockUtils.rawlsErrorReport(NotFound).toJson.compactPrint) - ) - // Bulk delete endpoint - workspaceServer - .when( - request() - .withMethod("POST") - .withPath( - FireCloudConfig.Rawls.authPrefix + FireCloudConfig.Rawls.entitiesPath.format("broad-dsde-dev", - "valid" - ) + "/delete" - ) - ) - .respond( - callback().withCallbackClass("org.broadinstitute.dsde.firecloud.mock.ValidEntityDeleteCallback") - ) } override def afterAll(): Unit = @@ -190,40 +66,6 @@ class EntityApiServiceSpec extends BaseServiceSpec with EntityApiService with Sp "EntityService" - { - "when calling GET on valid entity type" - { - "OK response is returned" in { - Get(validFireCloudEntitiesSamplePath) ~> dummyUserIdHeaders("1234") ~> sealRoute(entityRoutes) ~> check { - status should be(OK) - } - } - } - - "when calling GET on valid entities" - { - "OK response is returned" in { - Get(validFireCloudEntitiesPath) ~> dummyUserIdHeaders("1234") ~> sealRoute(entityRoutes) ~> check { - status should be(OK) - } - } - } - - "when calling GET on valid entityQuery with no params" - { - "OK response is returned" in { - Get(validFireCloudEntityQuerySamplePath) ~> dummyUserIdHeaders("1234") ~> sealRoute(entityRoutes) ~> check { - status should be(OK) - } - } - } - - "when calling GET on valid entityQuery with params" - { - "OK response is returned" in { - Get(validFireCloudEntityQuerySamplePath + "?page=1&pageSize=1") ~> dummyUserIdHeaders("1234") ~> sealRoute( - entityRoutes - ) ~> check { - status should be(OK) - } - } - } - "when calling POST on valid copy entities" - { "Created response is returned" in { Post(validFireCloudEntitiesCopyPath, validEntityCopy) ~> dummyUserIdHeaders("1234") ~> sealRoute( @@ -245,24 +87,6 @@ class EntityApiServiceSpec extends BaseServiceSpec with EntityApiService with Sp } } - "when calling GET on an entity type in an unknown workspace" - { - "NotFound response is returned with an ErrorReport" in { - Get(invalidFireCloudEntitiesSamplePath) ~> dummyUserIdHeaders("1234") ~> sealRoute(entityRoutes) ~> check { - status should be(NotFound) - errorReportCheck("Rawls", NotFound) - } - } - } - - "when calling GET on entities in an unknown workspace" - { - "NotFound response is returned with an ErrorReport" in { - Get(invalidFireCloudEntitiesPath) ~> dummyUserIdHeaders("1234") ~> sealRoute(entityRoutes) ~> check { - status should be(NotFound) - errorReportCheck("Rawls", NotFound) - } - } - } - "when calling POST on copy entities in an unknown workspace" - { "NotFound response is returned with an ErrorReport" in { Post(invalidFireCloudEntitiesCopyPath, validEntityCopy) ~> dummyUserIdHeaders("1234") ~> sealRoute( @@ -273,48 +97,6 @@ class EntityApiServiceSpec extends BaseServiceSpec with EntityApiService with Sp } } } - - "when calling bulk entity delete with a valid payload" - { - "response is NoContent" in { - Post(validFireCloudEntitiesBulkDeletePath, validEntityDelete) ~> dummyUserIdHeaders("1234") ~> sealRoute( - entityRoutes - ) ~> check { - status should be(NoContent) - } - } - } - - "when calling bulk entity delete with an invalid payload" - { - "BadRequest is returned" in { - Post(validFireCloudEntitiesBulkDeletePath, invalidEntityDelete) ~> dummyUserIdHeaders("1234") ~> sealRoute( - entityRoutes - ) ~> check { - status should be(BadRequest) - } - } - } - - "when calling bulk entity delete with some missing entities" - { - "BadRequest is returned with an ErrorReport" in { - Post(validFireCloudEntitiesBulkDeletePath, mixedFailEntityDelete) ~> dummyUserIdHeaders("1234") ~> sealRoute( - entityRoutes - ) ~> check { - status should be(BadRequest) - errorReportCheck("Rawls", BadRequest) - } - } - } - - "when calling bulk entity delete with all missing entities" - { - "BadRequest is returned with an ErrorReport" in { - Post(validFireCloudEntitiesBulkDeletePath, allFailEntityDelete) ~> dummyUserIdHeaders("1234") ~> sealRoute( - entityRoutes - ) ~> check { - status should be(BadRequest) - errorReportCheck("Rawls", BadRequest) - } - } - } } } diff --git a/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/SubmissionApiServiceSpec.scala b/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/SubmissionApiServiceSpec.scala index 56fc7db5f..0ef7a0029 100644 --- a/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/SubmissionApiServiceSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/SubmissionApiServiceSpec.scala @@ -21,59 +21,6 @@ final class SubmissionApiServiceSpec extends BaseServiceSpec with SubmissionApiS override def afterAll(): Unit = MockWorkspaceServer.stopWorkspaceServer() - val localSubmissionsCountPath = FireCloudConfig.Rawls.submissionsCountPath - .format(MockWorkspaceServer.mockValidWorkspace.namespace, MockWorkspaceServer.mockValidWorkspace.name) - - val localSubmissionsPath = FireCloudConfig.Rawls.submissionsPath - .format(MockWorkspaceServer.mockValidWorkspace.namespace, MockWorkspaceServer.mockValidWorkspace.name) - - val localSubmissionIdPath = FireCloudConfig.Rawls.submissionsIdPath.format( - MockWorkspaceServer.mockValidWorkspace.namespace, - MockWorkspaceServer.mockValidWorkspace.name, - MockWorkspaceServer.mockValidId - ) - - val localInvalidSubmissionIdPath = FireCloudConfig.Rawls.submissionsIdPath.format( - MockWorkspaceServer.mockValidWorkspace.namespace, - MockWorkspaceServer.mockValidWorkspace.name, - MockWorkspaceServer.mockInvalidId - ) - - val localSubmissionWorkflowIdPath = FireCloudConfig.Rawls.submissionsWorkflowIdPath.format( - MockWorkspaceServer.mockValidWorkspace.namespace, - MockWorkspaceServer.mockValidWorkspace.name, - MockWorkspaceServer.mockValidId, - MockWorkspaceServer.mockValidId - ) - - val localSpacedWorkspaceWorkflowIdPath = FireCloudConfig.Rawls.submissionsWorkflowIdPath.format( - MockWorkspaceServer.mockSpacedWorkspace.namespace, - MockWorkspaceServer.mockSpacedWorkspace.name, - MockWorkspaceServer.mockValidId, - MockWorkspaceServer.mockValidId - ) - - val localInvalidSubmissionWorkflowIdPath = FireCloudConfig.Rawls.submissionsWorkflowIdPath.format( - MockWorkspaceServer.mockValidWorkspace.namespace, - MockWorkspaceServer.mockValidWorkspace.name, - MockWorkspaceServer.mockInvalidId, - MockWorkspaceServer.mockInvalidId - ) - - val localSubmissionWorkflowIdOutputsPath = FireCloudConfig.Rawls.submissionsWorkflowIdOutputsPath.format( - MockWorkspaceServer.mockValidWorkspace.namespace, - MockWorkspaceServer.mockValidWorkspace.name, - MockWorkspaceServer.mockValidId, - MockWorkspaceServer.mockValidId - ) - - val localInvalidSubmissionWorkflowIdOutputsPath = FireCloudConfig.Rawls.submissionsWorkflowIdOutputsPath.format( - MockWorkspaceServer.mockValidWorkspace.namespace, - MockWorkspaceServer.mockValidWorkspace.name, - MockWorkspaceServer.mockInvalidId, - MockWorkspaceServer.mockInvalidId - ) - "SubmissionApiService" - { "when hitting the /submissions/queueStatus path" - { "with GET" - { @@ -84,176 +31,5 @@ final class SubmissionApiServiceSpec extends BaseServiceSpec with SubmissionApiS } } } - - "when calling GET on the /workspaces/*/*/submissionsCount path" - { - "OK status is returned" in { - (Get(localSubmissionsCountPath) - ~> dummyAuthHeaders) ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(OK) - } - } - } - - "when calling GET on the /workspaces/*/*/submissions path" - { - "a list of submissions is returned" in { - (Get(localSubmissionsPath) - ~> dummyAuthHeaders) ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(OK) - } - } - } - - "when calling POST on the /workspaces/*/*/submissions path with a valid submission" - { - "OK response is returned" in { - (Post(localSubmissionsPath, MockWorkspaceServer.mockValidSubmission) - ~> dummyAuthHeaders) ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(OK) - val submission = responseAs[OrchSubmissionRequest] - submission shouldNot be(None) - } - } - } - - "when calling POST on the /workspaces/*/*/submissions path with an invalid submission" - { - "BadRequest response is returned" in { - (Post(localSubmissionsPath, MockWorkspaceServer.mockInvalidSubmission) - ~> dummyAuthHeaders) ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(BadRequest) - errorReportCheck("Rawls", BadRequest) - } - } - } - - /* TODO: this test really only checks that we've set up our mock server correctly, it does not - validate any real authentication. It should be re-evaluated and possibly deleted. - */ - "when calling POST on the /workspaces/*/*/submissions path without a valid authentication token" - { - "Found (302 redirect) response is returned" in { - Post(localSubmissionsPath, MockWorkspaceServer.mockValidSubmission) ~> sealRoute( - submissionServiceRoutes - ) ~> check { - status should equal(Found) - } - } - } - - "when calling POST on the /workspaces/*/*/submissions/validate path" - { - "with a valid submission, OK response is returned" in { - (Post(s"$localSubmissionsPath/validate", MockWorkspaceServer.mockValidSubmission) - ~> dummyAuthHeaders) ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(OK) - val submission = responseAs[OrchSubmissionRequest] - submission shouldNot be(None) - } - } - - "with an invalid submission, BadRequest response is returned" in { - (Post(s"$localSubmissionsPath/validate", MockWorkspaceServer.mockInvalidSubmission) - ~> dummyAuthHeaders) ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(BadRequest) - errorReportCheck("Rawls", BadRequest) - } - } - } - - "when calling GET on the /workspaces/*/*/submissions/* path with a valid id" - { - "OK response is returned" in { - Get(localSubmissionIdPath) ~> dummyAuthHeaders ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(OK) - } - } - } - - "when calling GET on the /workspaces/*/*/submissions/* path with an invalid id" - { - "NotFound response is returned" in { - Get(localInvalidSubmissionIdPath) ~> dummyAuthHeaders ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(NotFound) - errorReportCheck("Rawls", NotFound) - } - } - } - - "when calling DELETE on the /workspaces/*/*/submissions/* with a valid id" - { - "OK response is returned" in { - Delete(localSubmissionIdPath) ~> dummyAuthHeaders ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(NoContent) - } - } - } - - "when calling DELETE on the /workspaces/*/*/submissions/* with an invalid id" - { - "NotFound response is returned" in { - Delete(localInvalidSubmissionIdPath) ~> dummyAuthHeaders ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(NotFound) - errorReportCheck("Rawls", NotFound) - } - } - } - - "when calling PATCH on the /workspaces/*/*/submissions/* path" - { - MockWorkspaceServer.submissionIdPatchResponseMapping.foreach { case (id, expectedResponseCode, _) => - s"HTTP $expectedResponseCode responses are forwarded back correctly" in { - val submissionIdPath = - FireCloudConfig.Rawls.submissionsIdPath.format(MockWorkspaceServer.mockValidWorkspace.namespace, - MockWorkspaceServer.mockValidWorkspace.name, - id - ) - - (Patch(submissionIdPath, - "PATCH request body. The mock server will ignore this content and respond " + - "entirely based on submission ID instead" - ) - ~> dummyAuthHeaders) ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(expectedResponseCode) - if (status != OK) { - errorReportCheck("Rawls", status) - } - } - } - } - } - - "when calling GET on the /workspaces/*/*/submissions/*/workflows/* path" - { - "with a valid id, OK response is returned" in { - Get(localSubmissionWorkflowIdPath) ~> dummyAuthHeaders ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(OK) - } - } - - "with a valid id and a space in the workspace name, OK response is returned" in { - // the request inbound to orchestration should encoded, so we replace spaces with %20 in the test below. - // this test really verifies that the runtime orch code can accept an encoded URI and maintain the encoding - // when it passes through the request to rawls - i.e. it doesn't decode the request at any point. - Get(localSpacedWorkspaceWorkflowIdPath.replace(" ", "%20")) ~> dummyAuthHeaders ~> sealRoute( - submissionServiceRoutes - ) ~> check { - status should equal(OK) - } - } - - "with an invalid id, NotFound response is returned" in { - Get(localInvalidSubmissionWorkflowIdPath) ~> dummyAuthHeaders ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(NotFound) - errorReportCheck("Rawls", NotFound) - } - } - } - - "when calling GET on the /workspaces/*/*/submissions/*/workflows/*/outputs path" - { - "with a valid id, OK response is returned" in { - Get(localSubmissionWorkflowIdOutputsPath) ~> dummyAuthHeaders ~> sealRoute(submissionServiceRoutes) ~> check { - status should equal(OK) - } - } - - "with an invalid id, NotFound response is returned" in { - Get(localInvalidSubmissionWorkflowIdOutputsPath) ~> dummyAuthHeaders ~> sealRoute( - submissionServiceRoutes - ) ~> check { - status should equal(NotFound) - errorReportCheck("Rawls", NotFound) - } - } - } } } diff --git a/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceApiServiceSpec.scala b/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceApiServiceSpec.scala index d19b948a1..5545b73b9 100644 --- a/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceApiServiceSpec.scala +++ b/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceApiServiceSpec.scala @@ -418,385 +418,7 @@ class WorkspaceApiServiceSpec actual.authorizationDomain should equal(expected.authorizationDomain) } - "WorkspaceService Passthrough Negative Tests" - { - - "Passthrough tests on the /workspaces path" - { - "MethodNotAllowed error is returned for HTTP PUT, PATCH, DELETE methods" in { - List(HttpMethods.PUT, HttpMethods.PATCH, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces") ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment path" - { - "MethodNotAllowed error is returned for HTTP PUT, PATCH, POST methods" in { - List(HttpMethods.PUT, HttpMethods.PATCH, HttpMethods.POST) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name") ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/methodconfigs path" - { - "MethodNotAllowed error is returned for HTTP PUT, PATCH, DELETE methods" in { - List(HttpMethods.PUT, HttpMethods.PATCH, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name/methodconfigs") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - Seq("this", "workspace") foreach { prefix => - s"Forbidden error is returned for HTTP POST with an output to $prefix.library:" in { - val methodConfigs = MethodConfiguration( - "namespace", - "name", - Some("root"), - None, - Map.empty, - Map("value" -> AttributeString(s"$prefix.library:param")), - MethodRepoMethod("methodnamespace", "methodname", 1) - ) - Post(methodconfigsPath, methodConfigs) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - status should equal(Forbidden) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/acl path" - { - "MethodNotAllowed error is returned for HTTP PUT, POST, DELETE methods" in { - List(HttpMethods.PUT, HttpMethods.POST, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name/acl") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/clone path" - { - "MethodNotAllowed error is returned for HTTP PUT, PATCH, GET, DELETE methods" in { - List(HttpMethods.PUT, HttpMethods.PATCH, HttpMethods.GET, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name/clone") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/lock path" - { - "MethodNotAllowed error is returned for HTTP POST, PATCH, GET, DELETE methods" in { - List(HttpMethods.POST, HttpMethods.PATCH, HttpMethods.GET, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name/lock") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/unlock path" - { - "MethodNotAllowed error is returned for HTTP POST, PATCH, GET, DELETE methods" in { - List(HttpMethods.POST, HttpMethods.PATCH, HttpMethods.GET, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name/unlock") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/checkBucketReadAccess path" - { - "MethodNotAllowed error is returned for HTTP POST, PATCH, PUT, DELETE methods" in { - List(HttpMethods.POST, HttpMethods.PATCH, HttpMethods.PUT, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name/checkBucketReadAccess") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/sendChangeNotification path" - { - "MethodNotAllowed error is returned for HTTP GET, PATCH, PUT, DELETE methods" in { - List(HttpMethods.GET, HttpMethods.PATCH, HttpMethods.PUT, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name/sendChangeNotification") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/accessInstructions path" - { - "MethodNotAllowed error is returned for HTTP POST, PATCH, PUT, DELETE methods" in { - List(HttpMethods.POST, HttpMethods.PATCH, HttpMethods.PUT, HttpMethods.DELETE) map { method => - new RequestBuilder(method)("/api/workspaces/namespace/name/accessInstructions") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/segment/segment/bucketUsage path" - { - List(HttpMethods.POST, HttpMethods.PATCH, HttpMethods.PUT, HttpMethods.DELETE) foreach { method => - s"MethodNotAllowed error is returned for $method" in { - new RequestBuilder(method)("/api/workspaces/namespace/name/bucketUsage") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - - "Passthrough tests on the /workspaces/tags path" - { - List(HttpMethods.POST, HttpMethods.PATCH, HttpMethods.PUT, HttpMethods.DELETE) foreach { method => - s"MethodNotAllowed error is returned for $method" in { - new RequestBuilder(method)("/api/workspaces/tags") ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - status should equal(MethodNotAllowed) - } - } - } - } - } - - "WorkspaceService Passthrough Tests" - { - - "Passthrough tests on the /workspaces path" - { - List(HttpMethods.GET) foreach { method => - s"OK status is returned for HTTP $method" in { - val dao = new MockRawlsDAO - val rwr = dao.rawlsWorkspaceResponseWithAttributes.copy(canShare = Some(false)) - val lrwr = Seq.fill(2)(rwr) - stubRawlsService(method, workspacesRoot, OK, Some(lrwr.toJson.compactPrint)) - new RequestBuilder(method)(workspacesRoot) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - status should equal(OK) - } - } - } - } - - "Passthrough tests on the GET /workspaces/%s/%s path" - { - s"OK status is returned for HTTP GET (workspace in authdomain)" in { - stubRawlsService(HttpMethods.GET, - workspacesPath, - OK, - Some(authDomainRawlsWorkspaceResponse.toJson.compactPrint) - ) - Get(workspacesPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - // generally this is not how we want to treat the response - // it should already be returned as JSON but for some strange reason it's being returned as text/plain - // here we take the plain text and force it to be json so we can get the test to work - assert( - entityAs[String].parseJson.convertTo[UIWorkspaceResponse].workspace.get.authorizationDomain.get.nonEmpty - ) - } - } - - s"OK status is returned for HTTP GET (non-auth-domained workspace)" in { - stubRawlsService(HttpMethods.GET, - workspacesPath, - OK, - Some(nonAuthDomainRawlsWorkspaceResponse.toJson.compactPrint) - ) - Get(workspacesPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - // generally this is not how we want to treat the response - // it should already be returned as JSON but for some strange reason it's being returned as text/plain - // here we take the plain text and force it to be json so we can get the test to work - assert( - entityAs[String].parseJson.convertTo[UIWorkspaceResponse].workspace.get.authorizationDomain.get.isEmpty - ) - } - } - - s"Accepted status is returned for HTTP DELETE" in { - new RequestBuilder(HttpMethods.DELETE)(workspacesPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - status should equal(Accepted) - } - } - } - - "Passthrough tests on the /workspaces/%s/%s/methodconfigs path" - { - List(HttpMethods.GET) foreach { method => - s"OK status is returned for HTTP $method" in { - stubRawlsService(method, methodconfigsPath, OK) - new RequestBuilder(method)(methodconfigsPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - status should equal(OK) - } - } - } - - "We should pass through query parameters on the GET" in { - - // Orch should dutifully pass along any value we send it - Seq("allRepos" -> "true", "allRepos" -> "false", "allRepos" -> "banana") foreach { query => - stubRawlsService(HttpMethods.GET, methodconfigsPath, OK, None, Some(query)) - - Get(Uri(methodconfigsPath).withQuery(Query(query))) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - rawlsServer.verify( - request().withPath(methodconfigsPath).withMethod("GET").withQueryStringParameter(query._1, query._2) - ) - - status should equal(OK) - } - } - } - } - - "Passthrough tests on the /workspaces/%s/%s/acl path" - { - "OK status is returned for HTTP GET" in { - stubRawlsService(HttpMethods.GET, aclPath, OK) - Get(aclPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - } - } - } - - "Passthrough tests on the /workspaces/%s/%s/sendChangeNotification path" - { - "OK status is returned for POST" in { - stubRawlsService(HttpMethods.POST, sendChangeNotificationPath, OK) - Post(sendChangeNotificationPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - } - } - } - - "Passthrough tests on the /workspaces/%s/%s/accessInstructions path" - { - "OK status is returned for GET" in { - stubRawlsService(HttpMethods.GET, accessInstructionsPath, OK) - Get(accessInstructionsPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - } - } - } - - "Passthrough tests on the /workspaces/%s/%s/lock path" - { - "OK status is returned for PUT" in { - stubRawlsService(HttpMethods.PUT, lockPath, OK) - Put(lockPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - } - } - } - - "Passthrough tests on the /workspaces/%s/%s/unlock path" - { - "OK status is returned for PUT" in { - stubRawlsService(HttpMethods.PUT, unlockPath, OK) - Put(unlockPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - } - } - } - - "Passthrough tests on the /workspaces/%s/%s/checkBucketReadAccess path" - { - "OK status is returned for GET" in { - stubRawlsService(HttpMethods.GET, bucketPath, OK) - Get(bucketPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - } - } - } - - "Passthrough tests on the /workspaces/%s/%s/bucketUsage path" - { - "OK status is returned for GET" in { - stubRawlsService(HttpMethods.GET, bucketUsagePath, OK) - Get(bucketUsagePath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - } - } - } - - "Passthrough tests on the /version/executionEngine path" - { - "OK status is returned for GET" in { - stubRawlsService(HttpMethods.GET, executionEngineVersionPath, OK) - Get(executionEngineVersionPath) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute(workspaceRoutes) ~> check { - status should equal(OK) - } - } - } - - "Passthrough tests on the workspaces/tags path" - { - "OK status is returned for GET" in { - val tagJsonString = """{ "tag": "tagtest", "count": 3 }""" - stubRawlsService(HttpMethods.GET, tagAutocompletePath, OK, Some(tagJsonString), Some("q", "tag")) - Get("/api/workspaces/tags", ("q", "tag")) - new RequestBuilder(HttpMethods.GET)("/api/workspaces/tags?q=tag") ~> dummyUserIdHeaders( - dummyUserId - ) ~> sealRoute(workspaceRoutes) ~> check { - rawlsServer.verify( - request().withPath(tagAutocompletePath).withMethod("GET").withQueryStringParameter("q", "tag") - ) - status should equal(OK) - responseAs[String] should equal(tagJsonString) - } - } - } - } - "Workspace Non-passthrough Tests" - { - "POST on /workspaces with 'not protected' workspace request sends non-realm WorkspaceRequest to Rawls and passes back the Rawls status and body" in { - val (rawlsRequest, rawlsResponse) = stubRawlsCreateWorkspace("namespace", "name") - - val orchestrationRequest = WorkspaceRequest("namespace", "name", Map()) - Post(workspacesRoot, orchestrationRequest) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - rawlsServer.verify( - request().withPath(workspacesRoot).withMethod("POST").withBody(rawlsRequest.toJson.compactPrint) - ) - status should equal(Created) - responseAs[WorkspaceDetails] should equal(rawlsResponse) - } - } - - "POST on /workspaces with 'protected' workspace request sends NIH-realm WorkspaceRequest to Rawls and passes back the Rawls status and body" in { - val (rawlsRequest, rawlsResponse) = - stubRawlsCreateWorkspace("namespace", "name", authDomain = Set(nihProtectedAuthDomain)) - - val orchestrationRequest = WorkspaceRequest("namespace", "name", Map(), Option(Set(nihProtectedAuthDomain))) - Post(workspacesRoot, orchestrationRequest) ~> dummyUserIdHeaders(dummyUserId) ~> sealRoute( - workspaceRoutes - ) ~> check { - rawlsServer.verify( - request().withPath(workspacesRoot).withMethod("POST").withBody(rawlsRequest.toJson.compactPrint) - ) - status should equal(Created) - responseAs[WorkspaceDetails] should equal(rawlsResponse) - } - } "OK status is returned from PATCH on /workspaces/%s/%s/acl" in { Patch(aclPath, diff --git a/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceV2ApiServiceSpec.scala b/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceV2ApiServiceSpec.scala deleted file mode 100644 index 54d452602..000000000 --- a/src/test/scala/org/broadinstitute/dsde/firecloud/webservice/WorkspaceV2ApiServiceSpec.scala +++ /dev/null @@ -1,22 +0,0 @@ -package org.broadinstitute.dsde.firecloud.webservice - -import akka.http.scaladsl.model.HttpMethods -import org.broadinstitute.dsde.firecloud.service.BaseServiceSpec -import org.broadinstitute.dsde.firecloud.FireCloudConfig - -import scala.concurrent.ExecutionContext - -class WorkspaceV2ApiServiceSpec extends BaseServiceSpec with WorkspaceV2ApiService { - - override val executionContext: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global - - private val testableRoutes = workspaceV2Routes - - "WorkspaceV2 passthrough" - { - - "should pass through HTTP GET for settings" in { - val settingsEndpoint = FireCloudConfig.Rawls.workspacesV2Url + "/namespace/name/settings" - checkIfPassedThrough(testableRoutes, HttpMethods.GET, settingsEndpoint, toBeHandled = true) - } - } -}