Skip to content
Merged

0.2.1 #226

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
shell: /bin/bash
command: |
docker-compose -f src/test/resources/docker-compose.yml up -d
cp src/main/resources/application.conf.test ./src/main/resources/application.conf
docker build -t flexo-mms-test:latest -f Dockerfile-Test .
docker run --network=flexo-mms-test-network --name flexo-mms-test-container flexo-mms-test:latest
SIG_INT=$?
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ dependencies {
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-server-status-pages:$ktorVersion")
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
testImplementation("io.kotest.extensions:kotest-assertions-ktor:2.0.0")

val logbackVersion = "1.5.18"
implementation("ch.qos.logback:logback-classic:$logbackVersion")
Expand Down
30 changes: 5 additions & 25 deletions src/main/kotlin/org/openmbee/flexo/mms/Conditions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,39 +88,19 @@ val BRANCH_COMMIT_CONDITIONS = REPO_CRUD_CONDITIONS.append {
}
}

val SNAPSHOT_QUERY_CONDITIONS = REPO_CRUD_CONDITIONS.append {
require("queryableSnapshotExists") {
handler = { layer1 -> "The target model is corrupt. No queryable snapshots found." to HttpStatusCode.InternalServerError }

"""
graph mor-graph:Metadata {
?__mms_ref
# select the latest commit from the current named ref
mms:commit ?__mms_baseCommit ;

# and its etag value
mms:etag ?__mms_etag ;

# and a queryable snapshot
mms:snapshot/mms:graph ?__mms_queryGraph .
}
"""
}
}

val REPO_QUERY_CONDITIONS = SNAPSHOT_QUERY_CONDITIONS.append {
val REPO_QUERY_CONDITIONS = REPO_CRUD_CONDITIONS.append {
permit(Permission.READ_REPO, Scope.REPO)
}

val BRANCH_QUERY_CONDITIONS = SNAPSHOT_QUERY_CONDITIONS.append {
val BRANCH_QUERY_CONDITIONS = REPO_CRUD_CONDITIONS.append {
permit(Permission.READ_BRANCH, Scope.BRANCH)
}

val LOCK_QUERY_CONDITIONS = SNAPSHOT_QUERY_CONDITIONS.append {
val LOCK_QUERY_CONDITIONS = REPO_CRUD_CONDITIONS.append {
permit(Permission.READ_LOCK, Scope.LOCK)
}

val ARTIFACT_QUERY_CONDITIONS = SNAPSHOT_QUERY_CONDITIONS.append {
val ARTIFACT_QUERY_CONDITIONS = REPO_CRUD_CONDITIONS.append {
permit(Permission.READ_ARTIFACT, Scope.ARTIFACT)
}

Expand All @@ -134,7 +114,7 @@ val SCRATCH_UPDATE_CONDITIONS = REPO_CRUD_CONDITIONS.append {
permit(Permission.UPDATE_SCRATCH, Scope.SCRATCH)
}

val DIFF_QUERY_CONDITIONS = SNAPSHOT_QUERY_CONDITIONS.append {
val DIFF_QUERY_CONDITIONS = REPO_CRUD_CONDITIONS.append {
permit(Permission.READ_DIFF, Scope.DIFF)
}

Expand Down
131 changes: 68 additions & 63 deletions src/test/kotlin/org/openmbee/flexo/mms/ArtifactAny.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package org.openmbee.flexo.mms

import io.kotest.assertions.ktor.client.shouldHaveStatus
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldBeEmpty
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.string.shouldStartWith
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.testing.*
import io.ktor.util.*
import org.openmbee.flexo.mms.util.*
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream


@OptIn(InternalAPI::class)
open class ArtifactAny : RefAny() {
override val logger = LoggerFactory.getLogger(LockAny::class.java)

Expand All @@ -21,61 +27,61 @@ open class ArtifactAny : RefAny() {

init {
"post artifact text/plain" {
withTest {
testApplication {
httpPost(artifactsPath) {
addHeader("Content-Type", "text/plain")
header("Content-Type", "text/plain")
setBody("foo")
}.apply {
response shouldHaveStatus HttpStatusCode.Created
response.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)
response.contentType() shouldBe ContentType.Text.Plain
this shouldHaveStatus HttpStatusCode.Created
this.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)
this.contentType() shouldBe ContentType.Text.Plain
}
}
}

// Set a content-type with parameters (like utf-8 on text/plain) and assert that parameters have
// been removed on returned content type
"post artifact text/plain with parameter" {
withTest{
testApplication {
httpPost(artifactsPath) {
addHeader("Content-Type", "text/plain; charset=utf-8")
header("Content-Type", "text/plain; charset=utf-8")
setBody("foo")
}.apply {
response shouldHaveStatus HttpStatusCode.Created
response.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)
response.contentType() shouldBe ContentType.Text.Plain
this shouldHaveStatus HttpStatusCode.Created
this.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)
this.contentType() shouldBe ContentType.Text.Plain
}
}
}

"get all artifacts empty" {
withTest{
testApplication {
httpGet("$artifactsPath?download") {
}.apply {
response shouldHaveStatus HttpStatusCode.NoContent
response.content.shouldBeNull()
this shouldHaveStatus HttpStatusCode.NoContent
this.bodyAsText().shouldBeEmpty()
}
}
}


"get all artifacts two artifacts" {
withTest{
testApplication {
httpPost(artifactsPath) {
addHeader("Content-Type", "text/plain")
header("Content-Type", "text/plain")
setBody("foo")
}.apply {
val locationFile1 = getURI(response.headers[HttpHeaders.Location].toString())
val locationFile1 = getURI(this.headers[HttpHeaders.Location].toString())
httpPost(artifactsPath) {
addHeader("Content-Type", "application/octet-stream")
header("Content-Type", "application/octet-stream")
setBody("bar".toByteArray())
}.apply {
val locationFile2 = getURI(response.headers[HttpHeaders.Location].toString())
val locationFile2 = getURI(this.headers[HttpHeaders.Location].toString())
httpGet("$artifactsPath?download") {}.apply {
response shouldHaveStatus HttpStatusCode.OK
response.contentType() shouldBe ContentType.Application.Zip
this shouldHaveStatus HttpStatusCode.OK
this.contentType() shouldBe ContentType.Application.Zip

val zipBytes = response.byteContent ?: throw IllegalStateException("Response byteContent is null")
val zipBytes = this.content.toByteArray()
val contents = readZipContents(zipBytes)
contents.size shouldBe 2
contents["$locationFile1.txt"] shouldBe "foo"
Expand All @@ -88,7 +94,7 @@ open class ArtifactAny : RefAny() {

// Not used http methods that should fail
"artifact rejects other methods" {
withTest {
testApplication {
onlyAllowsMethods(artifactsPath, setOf(
HttpMethod.Head,
HttpMethod.Get,
Expand All @@ -102,95 +108,94 @@ open class ArtifactAny : RefAny() {
*********************************************/

"get an artifact by id - turtle" {
withTest{
testApplication {
httpPost(artifactsPath) {
addHeader("Content-Type", "text/plain")
header("Content-Type", "text/plain")
setBody("foo".toByteArray())
}.apply {
response shouldHaveStatus HttpStatusCode.Created
response.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)

val uri = getLocation(response.headers[HttpHeaders.Location].toString())
httpGet(uri) {
}.apply {
response shouldHaveStatus HttpStatusCode.OK
response.contentType() shouldBe ContentType.Text.Plain
response shouldHaveContent "foo"
this shouldHaveStatus HttpStatusCode.Created
this.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)

val uri = getLocation(this.headers[HttpHeaders.Location].toString())
httpGet(uri) {}.apply {
this shouldHaveStatus HttpStatusCode.OK
this.contentType() shouldBe ContentType.Text.Plain
this.bodyAsText() shouldContain "foo"
}
}
}
}

"download an artifact by id - text" {
withTest{
testApplication {
httpPost(artifactsPath) {
addHeader("Content-Type", "text/plain")
header("Content-Type", "text/plain")
setBody("foo")
}.apply {
response shouldHaveStatus HttpStatusCode.Created
response.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)
this shouldHaveStatus HttpStatusCode.Created
this.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)

val uri = getLocation(response.headers[HttpHeaders.Location].toString())
val uri = getLocation(this.headers[HttpHeaders.Location].toString())
httpGet("$uri?download") {
}.apply {
response shouldHaveStatus HttpStatusCode.OK
response.contentType() shouldBe ContentType.Text.Plain
response shouldHaveContent "foo"
this shouldHaveStatus HttpStatusCode.OK
this.contentType() shouldBe ContentType.Text.Plain
this.bodyAsText() shouldContain "foo"
}
}
}
}

"download an artifact by id - binary" {
withTest{
testApplication {
httpPost(artifactsPath) {
addHeader("Content-Type", "application/octet-stream")
header("Content-Type", "application/octet-stream")
setBody("foo".toByteArray())
}.apply {
response shouldHaveStatus HttpStatusCode.Created
response.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)
this shouldHaveStatus HttpStatusCode.Created
this.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)

val uri = getLocation(response.headers[HttpHeaders.Location].toString())
val uri = getLocation(this.headers[HttpHeaders.Location].toString())
httpGet("$uri?download") {
}.apply {
response shouldHaveStatus HttpStatusCode.OK
response.contentType() shouldBe ContentType.Application.OctetStream
response shouldHaveContent "foo"
this shouldHaveStatus HttpStatusCode.OK
this.contentType() shouldBe ContentType.Application.OctetStream
this.bodyAsText() shouldContain "foo"
}
}
}
}

"get an artifact by id - URI" {
withTest{
testApplication {
httpPost(artifactsPath) {
addHeader("Content-Type", "text/turtle")
header("Content-Type", "text/turtle")
setBody("<http://openmbee.org> <http://openmbee.org> <http://openmbee.org> .")
}.apply {
response shouldHaveStatus HttpStatusCode.Created
response.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)
this shouldHaveStatus HttpStatusCode.Created
this.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)

val uri = getLocation(response.headers[HttpHeaders.Location].toString())
val uri = getLocation(this.headers[HttpHeaders.Location].toString())
httpGet("$uri?download") {}.apply {
response shouldHaveStatus HttpStatusCode.OK
response.contentType().toString() shouldBeEqual "text/turtle"
response shouldHaveContent "<http://openmbee.org> <http://openmbee.org> <http://openmbee.org> ."
this shouldHaveStatus HttpStatusCode.OK
this.contentType().toString() shouldBeEqual "text/turtle"
this.bodyAsText() shouldBeEqual "<http://openmbee.org> <http://openmbee.org> <http://openmbee.org> ."
}
}
}
}

// Not used http methods that should fail
"artifact/{id} rejects other methods" {
withTest {
testApplication {
httpPost(artifactsPath) {
addHeader("Content-Type", "text/plain")
header("Content-Type", "text/plain")
setBody("foo")
}.apply {
response shouldHaveStatus HttpStatusCode.Created
response.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)
this shouldHaveStatus HttpStatusCode.Created
this.headers[HttpHeaders.Location] shouldStartWith localIri(artifactsPath)

val uri = getLocation(response.headers[HttpHeaders.Location].toString())
val uri = getLocation(this.headers[HttpHeaders.Location].toString())
onlyAllowsMethods(
uri, setOf(
HttpMethod.Head,
Expand Down
Loading