Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(JAQPOT-100): add integration tests for model api #14

Merged
merged 3 commits into from
May 29, 2024
Merged
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 build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ dependencies {
testImplementation("io.rest-assured:kotlin-extensions:5.4.0")
// testcontainers
testImplementation("org.testcontainers:postgresql:1.19.8")
testImplementation("com.github.dasniko:testcontainers-keycloak:3.3.1")
testImplementation("org.testcontainers:junit-jupiter:1.19.8")
}

Expand Down
19 changes: 19 additions & 0 deletions docker/keycloak/imports/realm-export.json
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,25 @@
},
"notBefore": 0,
"groups": []
},
{
"username": "jaqpot",
"email": "[email protected]",
"firstName": "jaq",
"lastName": "pot",
"enabled": true,
"credentials": [
{
"type": "password",
"value": "jaqpot"
}
],
"clientRoles": {
"account": [
"view-profile",
"manage-account"
]
}
}
],
"scopeMappings": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.jaqpot.api.integration

import dasniko.testcontainers.keycloak.KeycloakContainer
import io.restassured.RestAssured
import io.restassured.RestAssured.given
import io.restassured.builder.RequestSpecBuilder
import io.restassured.http.ContentType
import io.restassured.parsing.Parser
import io.restassured.specification.RequestSpecification
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.context.ApplicationContextInitializer
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.test.context.ContextConfiguration
import org.testcontainers.containers.PostgreSQLContainer


@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@ContextConfiguration(initializers = [AbstractIntegrationTest.Initializer::class])
@AutoConfigureMockMvc
abstract class AbstractIntegrationTest {

companion object {
const val TEST_DB_NAME = "jaqpot-test"
const val TEST_DB_USERNAME = "test"
const val TEST_DB_PASSWORD = "test"
val postgreSQLContainer = PostgreSQLContainer("postgres:alpine3.19")
.apply {
withDatabaseName(TEST_DB_NAME)
withUsername(TEST_DB_USERNAME)
withPassword(TEST_DB_PASSWORD)
}

val keycloakContainer =
KeycloakContainer("quay.io/keycloak/keycloak:24.0.3")
.withRealmImportFile("/test-realm-export.json")
}


internal class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) {
postgreSQLContainer.start()
keycloakContainer.start()

val keycloakUrl = "http://${keycloakContainer.host}:${keycloakContainer.firstMappedPort}"
val dbUrl =
"jdbc:postgresql://${postgreSQLContainer.host}:${postgreSQLContainer.firstMappedPort}/${TEST_DB_NAME}"

TestPropertyValues.of(
"spring.datasource.url= $dbUrl",
"spring.datasource.username=${postgreSQLContainer.username}",
"spring.datasource.password=${postgreSQLContainer.password}",
"spring.security.oauth2.resourceserver.jwt.issuer-uri=${keycloakUrl}/realms/jaqpot-local"
).applyTo(configurableApplicationContext.environment)
}
}

fun getJaqpotUserAccessToken(): String {
val keycloakUrl = "http://${keycloakContainer.host}:${keycloakContainer.firstMappedPort}"

RestAssured.defaultParser = Parser.JSON
val spec: RequestSpecification = RequestSpecBuilder()
.setBaseUri(keycloakUrl).build()

val accessToken = given()
.spec(spec)
.contentType(ContentType.URLENC)
.formParam("client_id", "jaqpot-frontend")
.formParam("grant_type", "password")
.formParam("username", "jaqpot")
.formParam("password", "jaqpot")
.post("/realms/jaqpot-local/protocol/openid-connect/token")
.then()
.statusCode(200)
.extract().path<String>("access_token")

return accessToken
}


}
131 changes: 131 additions & 0 deletions src/test/kotlin/org/jaqpot/api/integration/ModelApiTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package org.jaqpot.api.integration

import io.restassured.RestAssured.given
import io.restassured.http.ContentType
import org.hamcrest.Matchers.*
import org.jaqpot.api.model.ModelDto
import org.junit.jupiter.api.Test
import java.util.*


class ModelApiTest : AbstractIntegrationTest() {

@Test
fun testGetModelsEmpty() {
val accessToken = getJaqpotUserAccessToken()

given().contentType(ContentType.JSON)
.header("Authorization", "Bearer $accessToken")
.get("/v1/models")
.then()
.statusCode(200)
.body("", equalTo(Collections.emptyList<ModelDto>()))
}

@Test
fun testPostModelWithoutLibrariesAndFeatures() {
val accessToken = getJaqpotUserAccessToken()

given()
.contentType(ContentType.JSON)
.body(
"""
{
"jaqpotpyVersion": "1.0.0",
"libraries": [],
"dependentFeatures": [],
"independentFeatures": [],
"meta": {
"id": 3,
"mpampis": "hello"
},
"public": true,
"type": "some type",
"reliability": 5,
"pretrained": false,
"actualModel": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
}
""".trimIndent()
)
.header("Authorization", "Bearer $accessToken")
.post("/v1/models")
.then()
.statusCode(201)
.header("Location", matchesRegex(".*/v1/models/[0-9]+$"))
.body(`is`(emptyString()))

// TODO figure order of execution of rest assured tests
// given().contentType(ContentType.JSON)
// .header("Authorization", "Bearer $accessToken")
// .get("/v1/models")
// .then()
// .assertThat()
// .statusCode(200)
// .body("", hasSize<ModelDto>(1))
// .body("[0].libraries", hasSize<ModelDto>(0))
// .body("[0].independentFeatures", hasSize<ModelDto>(0))
// .body("[0].dependentFeatures", hasSize<ModelDto>(0));
}

@Test
fun testPostModelWithLibrariesAndFeatures() {
val accessToken = getJaqpotUserAccessToken()

given()
.contentType(ContentType.JSON)
.body(
"""
{
"jaqpotpyVersion": "1.0.0",
"libraries": [
{
"name": "sklearn",
"version": "0.20.1"
}
],
"dependentFeatures": [{
"name": "Y",
"featureType": "DEPENDENT"
}],
"independentFeatures": [{
"name": "dose",
"featureType": "INDEPENDENT"
},
{
"name": "liability",
"featureType": "INDEPENDENT"
}],
"meta": {
"id": 3,
"mpampis": "hello"
},
"public": true,
"type": "some type",
"reliability": 5,
"pretrained": false,
"actualModel": "ewoJImluZm8iOiB7CgkJIl9wb3N0bWFuX2lkIjogIjU0MTM0N2Y1LTMyZDYtNDNkYy1iZjc5LTA4ODVkYzYzZWYyMCIsCgkJIm5hbWUiOiAiamFxcG90IGFwaSB2MiIsCgkJInNjaGVtYSI6ICJodHRwczovL3NjaGVtYS5nZXRwb3N0bWFuLmNvbS9qc29uL2NvbGxlY3Rpb24vdjIuMS4wL2NvbGxlY3Rpb24uanNvbiIsCgkJIl9leHBvcnRlcl9pZCI6ICIzNDUwMzcyNyIKCX0sCgkiaXRlbSI6IFsKCQl7CgkJCSJuYW1lIjogIm1vZGVsIiwKCQkJIml0ZW0iOiBbCgkJCQl7CgkJCQkJIm5hbWUiOiAiZ2V0QWxsIiwKCQkJCQkicmVxdWVzdCI6IHsKCQkJCQkJImF1dGgiOiB7CgkJCQkJCQkidHlwZSI6ICJub2F1dGgiCgkJCQkJCX0sCgkJCQkJCSJtZXRob2QiOiAiR0VUIiwKCQkJCQkJImhlYWRlciI6IFtdLAoJCQkJCQkidXJsIjogewoJCQkJCQkJInJhdyI6ICJ7e2Jhc2VVcmx9fS9tb2RlbHMvIiwKCQkJCQkJCSJob3N0IjogWwoJCQkJCQkJCSJ7e2Jhc2VVcmx9fSIKCQkJCQkJCV0sCgkJCQkJCQkicGF0aCI6IFsKCQkJCQkJCQkibW9kZWxzIiwKCQkJCQkJCQkiIgoJCQkJCQkJXQoJCQkJCQl9CgkJCQkJfSwKCQkJCQkicmVzcG9uc2UiOiBbXQoJCQkJfQoJCQldCgkJfSwKCQl7CgkJCSJuYW1lIjogImluZmVyZW5jZSIsCgkJCSJpdGVtIjogWwoJCQkJewoJCQkJCSJuYW1lIjogIk5ldyBGb2xkZXIiLAoJCQkJCSJpdGVtIjogW10KCQkJCX0KCQkJXQoJCX0sCgkJewoJCQkibmFtZSI6ICJyb290IiwKCQkJInJlcXVlc3QiOiB7CgkJCQkiYXV0aCI6IHsKCQkJCQkidHlwZSI6ICJiZWFyZXIiLAoJCQkJCSJiZWFyZXIiOiBbCgkJCQkJCXsKCQkJCQkJCSJrZXkiOiAidG9rZW4iLAoJCQkJCQkJInZhbHVlIjogInt7dG9rZW59fSIsCgkJCQkJCQkidHlwZSI6ICJzdHJpbmciCgkJCQkJCX0KCQkJCQldCgkJCQl9LAoJCQkJIm1ldGhvZCI6ICJHRVQiLAoJCQkJImhlYWRlciI6IFtdLAoJCQkJInVybCI6IHsKCQkJCQkicmF3IjogInt7YmFzZVVybH19LyIsCgkJCQkJImhvc3QiOiBbCgkJCQkJCSJ7e2Jhc2VVcmx9fSIKCQkJCQldLAoJCQkJCSJwYXRoIjogWwoJCQkJCQkiIgoJCQkJCV0KCQkJCX0KCQkJfSwKCQkJInJlc3BvbnNlIjogW10KCQl9LAoJCXsKCQkJIm5hbWUiOiAiY3JlYXRlIiwKCQkJInJlcXVlc3QiOiB7CgkJCQkiYXV0aCI6IHsKCQkJCQkidHlwZSI6ICJiZWFyZXIiLAoJCQkJCSJiZWFyZXIiOiBbCgkJCQkJCXsKCQkJCQkJCSJrZXkiOiAidG9rZW4iLAoJCQkJCQkJInZhbHVlIjogInt7dG9rZW59fSIsCgkJCQkJCQkidHlwZSI6ICJzdHJpbmciCgkJCQkJCX0KCQkJCQldCgkJCQl9LAoJCQkJIm1ldGhvZCI6ICJQT1NUIiwKCQkJCSJoZWFkZXIiOiBbXSwKCQkJCSJib2R5IjogewoJCQkJCSJtb2RlIjogInJhdyIsCgkJCQkJInJhdyI6ICJ7XG4gICAgXCJwdWJsaWNcIjogdHJ1ZVxufSIsCgkJCQkJIm9wdGlvbnMiOiB7CgkJCQkJCSJyYXciOiB7CgkJCQkJCQkibGFuZ3VhZ2UiOiAianNvbiIKCQkJCQkJfQoJCQkJCX0KCQkJCX0sCgkJCQkidXJsIjogewoJCQkJCSJyYXciOiAie3tiYXNlVXJsfX0vbW9kZWxzIiwKCQkJCQkiaG9zdCI6IFsKCQkJCQkJInt7YmFzZVVybH19IgoJCQkJCV0sCgkJCQkJInBhdGgiOiBbCgkJCQkJCSJtb2RlbHMiCgkJCQkJXQoJCQkJfQoJCQl9LAoJCQkicmVzcG9uc2UiOiBbXQoJCX0sCgkJewoJCQkibmFtZSI6ICJrZXljbG9hayB0b2tlbiIsCgkJCSJyZXF1ZXN0IjogewoJCQkJIm1ldGhvZCI6ICJQT1NUIiwKCQkJCSJoZWFkZXIiOiBbXSwKCQkJCSJib2R5IjogewoJCQkJCSJtb2RlIjogInVybGVuY29kZWQiLAoJCQkJCSJ1cmxlbmNvZGVkIjogWwoJCQkJCQl7CgkJCQkJCQkia2V5IjogImNsaWVudF9pZCIsCgkJCQkJCQkidmFsdWUiOiAiYWRtaW4tY2xpIiwKCQkJCQkJCSJ0eXBlIjogInRleHQiCgkJCQkJCX0sCgkJCQkJCXsKCQkJCQkJCSJrZXkiOiAiY2xpZW50X3NlY3JldCIsCgkJCQkJCQkidmFsdWUiOiAiNjJyOFFtNzlnand0QTZ3Rk1ZdDQxSW5VSzcxOG53ekoiLAoJCQkJCQkJInR5cGUiOiAidGV4dCIKCQkJCQkJfSwKCQkJCQkJewoJCQkJCQkJImtleSI6ICJncmFudF90eXBlIiwKCQkJCQkJCSJ2YWx1ZSI6ICJjbGllbnRfY3JlZGVudGlhbHMiLAoJCQkJCQkJInR5cGUiOiAidGV4dCIKCQkJCQkJfSwKCQkJCQkJewoJCQkJCQkJImtleSI6ICJyZWFsbSIsCgkJCQkJCQkidmFsdWUiOiAiIiwKCQkJCQkJCSJ0eXBlIjogInRleHQiLAoJCQkJCQkJImRpc2FibGVkIjogdHJ1ZQoJCQkJCQl9CgkJCQkJXQoJCQkJfSwKCQkJCSJ1cmwiOiB7CgkJCQkJInJhdyI6ICJodHRwOi8vbG9jYWxob3N0OjgwNzAvcmVhbG1zL2phcXBvdC1sb2NhbC9wcm90b2NvbC9vcGVuaWQtY29ubmVjdC90b2tlbiIsCgkJCQkJInByb3RvY29sIjogImh0dHAiLAoJCQkJCSJob3N0IjogWwoJCQkJCQkibG9jYWxob3N0IgoJCQkJCV0sCgkJCQkJInBvcnQiOiAiODA3MCIsCgkJCQkJInBhdGgiOiBbCgkJCQkJCSJyZWFsbXMiLAoJCQkJCQkiamFxcG90LWxvY2FsIiwKCQkJCQkJInByb3RvY29sIiwKCQkJCQkJIm9wZW5pZC1jb25uZWN0IiwKCQkJCQkJInRva2VuIgoJCQkJCV0KCQkJCX0KCQkJfSwKCQkJInJlc3BvbnNlIjogW10KCQl9CgldLAoJImV2ZW50IjogWwoJCXsKCQkJImxpc3RlbiI6ICJwcmVyZXF1ZXN0IiwKCQkJInNjcmlwdCI6IHsKCQkJCSJ0eXBlIjogInRleHQvamF2YXNjcmlwdCIsCgkJCQkicGFja2FnZXMiOiB7fSwKCQkJCSJleGVjIjogWwoJCQkJCSIiCgkJCQldCgkJCX0KCQl9LAoJCXsKCQkJImxpc3RlbiI6ICJ0ZXN0IiwKCQkJInNjcmlwdCI6IHsKCQkJCSJ0eXBlIjogInRleHQvamF2YXNjcmlwdCIsCgkJCQkicGFja2FnZXMiOiB7fSwKCQkJCSJleGVjIjogWwoJCQkJCSIiCgkJCQldCgkJCX0KCQl9CgldCn0="
}
""".trimIndent()
)
.header("Authorization", "Bearer $accessToken")
.post("/v1/models")
.then()
.statusCode(201)
.header("Location", matchesRegex(".*/v1/models/[0-9]+$"))
.body(`is`(emptyString()))


// TODO figure order of execution of rest assured tests
// given().contentType(ContentType.JSON)
// .header("Authorization", "Bearer $accessToken")
// .get("/v1/models")
// .then()
// .assertThat()
// .statusCode(200)
// .body("", hasSize<ModelDto>(1))
// .body("[0].libraries", hasSize<ModelDto>(1))
// .body("[0].independentFeatures", hasSize<ModelDto>(2))
// .body("[0].dependentFeatures", hasSize<ModelDto>(1));
}

}
Loading