diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/application/webservice/Verein360ApplicationTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/application/webservice/Verein360ApplicationTest.kt new file mode 100644 index 000000000..8efd6eb28 --- /dev/null +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/application/webservice/Verein360ApplicationTest.kt @@ -0,0 +1,208 @@ +package app.ehrenamtskarte.backend.application.webservice + +import app.ehrenamtskarte.backend.GraphqlApiTest +import app.ehrenamtskarte.backend.application.database.ApplicationEntity +import app.ehrenamtskarte.backend.application.database.ApplicationVerificationEntity +import app.ehrenamtskarte.backend.application.database.ApplicationVerificationExternalSource +import app.ehrenamtskarte.backend.application.database.ApplicationVerifications +import app.ehrenamtskarte.backend.application.database.Applications +import app.ehrenamtskarte.backend.application.webservice.schema.create.Application +import app.ehrenamtskarte.backend.application.webservice.schema.create.ApplicationType +import app.ehrenamtskarte.backend.auth.database.ApiTokenType +import app.ehrenamtskarte.backend.auth.database.ApiTokens +import app.ehrenamtskarte.backend.helper.TestAdministrators +import app.ehrenamtskarte.backend.helper.TestApplicationBuilder +import app.ehrenamtskarte.backend.helper.TestData +import app.ehrenamtskarte.backend.util.GraphQLRequestSerializer +import io.javalin.testtools.JavalinTest +import org.jetbrains.exposed.sql.deleteAll +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +internal class Verein360ApplicationTest : GraphqlApiTest() { + + data class ValidationErrorTestCase(val application: Application, val error: String) + + companion object { + @JvmStatic + fun validationErrorTestCases(): List { + return listOf( + ValidationErrorTestCase( + application = TestApplicationBuilder.build( + isAlreadyVerified = true, + applicationType = ApplicationType.RENEWAL_APPLICATION + ), + error = "Application type must be FIRST_APPLICATION if application is already verified" + ), + ValidationErrorTestCase( + application = TestApplicationBuilder.build( + isAlreadyVerified = true, + wantsDigitalCard = false, + wantsPhysicalCard = true + ), + error = "Digital card must be true if application is already verified" + ), + ValidationErrorTestCase( + application = TestApplicationBuilder.build( + isAlreadyVerified = true, + wantsPhysicalCard = true + ), + error = "Physical card must be false if application is already verified" + ), + ValidationErrorTestCase( + application = TestApplicationBuilder.build( + isAlreadyVerified = true, + category = "Other" + ), + error = "All organizations must be of category Sport if application is already verified" + ) + ) + } + } + + private val adminVerein360 = TestAdministrators.BAYERN_VEREIN_360 + + @BeforeEach + fun cleanUp() { + transaction { + ApplicationVerifications.deleteAll() + Applications.deleteAll() + ApiTokens.deleteAll() + } + } + + @ParameterizedTest + @MethodSource("validationErrorTestCases") + fun `should return validation error when the request is not valid`(testCase: ValidationErrorTestCase) = JavalinTest.test(app) { _, client -> + TestData.createApiToken(creatorId = adminVerein360.id, type = ApiTokenType.VERIFIED_APPLICATION) + + val mutation = createMutation(application = testCase.application) + val response = post(client, mutation, token = "dummy") + + assertEquals(200, response.code) + + val jsonResponse = response.json() + + assertEquals("Error INVALID_JSON occurred.", jsonResponse.findValue("message").textValue()) + assertEquals(testCase.error, jsonResponse.findValue("reason").textValue()) + } + + @Test + fun `should return an error when region not found`() = JavalinTest.test(app) { _, client -> + val mutation = createMutation( + regionId = 99, + application = TestApplicationBuilder.build(true) + ) + val response = post(client, mutation) + + assertEquals(200, response.code) + + val jsonResponse = response.json() + + assertEquals("Error REGION_NOT_FOUND occurred.", jsonResponse.findValue("message").textValue()) + } + + @Test + fun `should return an error when the application is pre-verified but auth token is missing`() = JavalinTest.test(app) { _, client -> + val mutation = createMutation( + application = TestApplicationBuilder.build(true) + ) + val response = post(client, mutation) + + assertEquals(401, response.code) + } + + @Test + fun `should return an error when api token not found`() = JavalinTest.test(app) { _, client -> + val mutation = createMutation( + application = TestApplicationBuilder.build(true) + ) + val response = post(client, mutation, token = "non-existent") + + assertEquals(403, response.code) + } + + @Test + fun `should return an error when api token has wrong type`() = JavalinTest.test(app) { _, client -> + TestData.createApiToken(creatorId = adminVerein360.id, type = ApiTokenType.USER_IMPORT) + + val mutation = createMutation( + application = TestApplicationBuilder.build(true) + ) + val response = post(client, mutation, token = "dummy") + + assertEquals(403, response.code) + } + + @Test + fun `should create an application and approved verification if the request is pre-verified and valid`() = JavalinTest.test(app) { _, client -> + TestData.createApiToken(creatorId = adminVerein360.id, type = ApiTokenType.VERIFIED_APPLICATION) + + val mutation = createMutation( + application = TestApplicationBuilder.build(true) + ) + val response = post(client, mutation, token = "dummy") + + assertEquals(200, response.code) + + transaction { + assertEquals(1, Applications.selectAll().count()) + assertEquals(1, ApplicationVerifications.selectAll().count()) + + val application = ApplicationEntity.all().single() + + ApplicationVerificationEntity.find { ApplicationVerifications.applicationId eq application.id }.single().let { + assertNotNull(it.verifiedDate) + assertNull(it.rejectedDate) + assertEquals(ApplicationVerificationExternalSource.VEREIN360, it.automaticSource) + } + } + } + + @Test + fun `should create an application and pending verification if the request is not pre-verified`() = JavalinTest.test(app) { _, client -> + val mutation = createMutation( + application = TestApplicationBuilder.build(false) + ) + val response = post(client, mutation) + + assertEquals(200, response.code) + + transaction { + assertEquals(1, Applications.selectAll().count()) + assertEquals(1, ApplicationVerifications.selectAll().count()) + + val application = ApplicationEntity.all().single() + + ApplicationVerificationEntity.find { ApplicationVerifications.applicationId eq application.id }.single().let { + assertNull(it.verifiedDate) + assertNull(it.rejectedDate) + assertEquals(ApplicationVerificationExternalSource.NONE, it.automaticSource) + } + } + } + + private fun createMutation( + project: String = "bayern.ehrenamtskarte.app", + regionId: Int = 1, + application: Application + ): String { + val applicationJson = GraphQLRequestSerializer.serializeObject(application) + return """ + mutation AddEakApplication { + addEakApplication( + project: "$project" + regionId: $regionId + application: $applicationJson + ) + } + """.trimIndent() + } +} diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/TestApplicationBuilder.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/TestApplicationBuilder.kt index 00c3ba6dd..93a5e5828 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/TestApplicationBuilder.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/TestApplicationBuilder.kt @@ -18,7 +18,14 @@ import app.ehrenamtskarte.backend.application.webservice.schema.create.primitive class TestApplicationBuilder { companion object { - fun build(isAlreadyVerified: Boolean): Application { + fun build( + isAlreadyVerified: Boolean, + applicationType: ApplicationType = ApplicationType.FIRST_APPLICATION, + cardType: BavariaCardType = BavariaCardType.BLUE, + wantsDigitalCard: Boolean = true, + wantsPhysicalCard: Boolean = false, + category: String = "Sport" + ): Application { return Application( personalData = PersonalData( forenames = ShortTextInput("John"), @@ -36,13 +43,13 @@ class TestApplicationBuilder { emailAddress = EmailInput("johndoe@example.com") ), applicationDetails = ApplicationDetails( - applicationType = ApplicationType.FIRST_APPLICATION, - cardType = BavariaCardType.BLUE, + applicationType = applicationType, + cardType = cardType, givenInformationIsCorrectAndComplete = true, hasAcceptedEmailUsage = true, hasAcceptedPrivacyPolicy = true, - wantsDigitalCard = true, - wantsPhysicalCard = false, + wantsDigitalCard = wantsDigitalCard, + wantsPhysicalCard = wantsPhysicalCard, blueCardEntitlement = BlueCardEntitlement( juleicaEntitlement = null, militaryReserveEntitlement = null, @@ -61,7 +68,7 @@ class TestApplicationBuilder { country = ShortTextInput("Deutschland"), addressSupplement = null ), - category = ShortTextInput("Sport"), + category = ShortTextInput(category), contact = OrganizationContact( name = ShortTextInput("Jane Doe"), email = EmailInput("jane.doe@sportverein.de"), diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/util/GraphQLRequestSerializer.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/util/GraphQLRequestSerializer.kt new file mode 100644 index 000000000..ad7e441d5 --- /dev/null +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/util/GraphQLRequestSerializer.kt @@ -0,0 +1,42 @@ +package app.ehrenamtskarte.backend.util + +import kotlin.reflect.KProperty1 +import kotlin.reflect.KVisibility +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.declaredMembers + +object GraphQLRequestSerializer { + + /** + * Serialize object into graphql request format + * e.g. { stringProperty: "value", intProperty: 123, objectProperty: { booleanProperty: true } } + */ + fun serializeObject(obj: Any): String { + return obj::class.declaredMemberProperties.filter { it.visibility == KVisibility.PUBLIC } + .joinToString(", ") { property -> + val value = readInstanceProperty(obj, property.name) + val serializedValue = "${property.name}: ${ + when (value) { + is String -> "\"$value\"" + is Number, is Boolean -> value.toString() + is Enum<*> -> value.name + is List<*> -> serializeList(value) + null -> null + else -> serializeObject(value) + } + }" + serializedValue + }.let { "{ $it }" } + } + + private fun serializeList(list: List<*>): String? { + if (list.isEmpty()) return null + return list.joinToString(", ", "[", "]") { serializeObject(it!!) } + } + + @Suppress("UNCHECKED_CAST") + private fun readInstanceProperty(instance: Any, propertyName: String): R? { + val property = instance::class.declaredMembers.first { it.name == propertyName } as KProperty1 + return property.get(instance) as R? + } +} diff --git a/backend/src/test/resources/config.test.yml b/backend/src/test/resources/config.test.yml index 5a1dd6e65..0f5bb75bb 100644 --- a/backend/src/test/resources/config.test.yml +++ b/backend/src/test/resources/config.test.yml @@ -19,7 +19,7 @@ projects: - id: bayern.ehrenamtskarte.app importUrl: dummy pipelineName: dummy - administrationBaseUrl: dummy + administrationBaseUrl: https://test.app administrationName: dummy timezone: "Europe/Berlin" selfServiceEnabled: false