From 0588f88f9acbac64b653e60866862ab01d53afa1 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Thu, 15 Apr 2021 02:20:57 +0200 Subject: [PATCH] Add validation on route PutVoteOnArticle --- .../component/vote/routes/PutVoteOnArticle.kt | 15 ++++- src/main/resources/openapi.yaml | 6 ++ src/test/kotlin/integration/Vote routes.kt | 65 +++++++++++++++---- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/fr/dcproject/component/vote/routes/PutVoteOnArticle.kt b/src/main/kotlin/fr/dcproject/component/vote/routes/PutVoteOnArticle.kt index 8f45229..0b169e0 100644 --- a/src/main/kotlin/fr/dcproject/component/vote/routes/PutVoteOnArticle.kt +++ b/src/main/kotlin/fr/dcproject/component/vote/routes/PutVoteOnArticle.kt @@ -1,5 +1,6 @@ package fr.dcproject.component.vote.routes +import fr.dcproject.application.http.badRequestIfNotValid import fr.dcproject.common.security.assert import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.component.article.database.ArticleRef @@ -10,6 +11,9 @@ import fr.dcproject.component.auth.mustBeAuth import fr.dcproject.component.vote.VoteAccessControl import fr.dcproject.component.vote.database.VoteArticleRepository import fr.dcproject.component.vote.database.VoteForUpdate +import io.konform.validation.Validation +import io.konform.validation.jsonschema.maximum +import io.konform.validation.jsonschema.minimum import io.ktor.application.call import io.ktor.features.NotFoundException import io.ktor.http.HttpStatusCode @@ -25,13 +29,22 @@ object PutVoteOnArticle { @Location("/articles/{article}/vote") class ArticleVoteRequest(article: UUID) { val article = ArticleRef(article) - data class Input(var note: Int) + data class Input(var note: Int) { + fun validate() = Validation { + Input::note { + minimum(-1) + maximum(1) + } + }.validate(this) + } } fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl, articleRepo: ArticleRepository) { put { mustBeAuth() + val input = call.receiveOrBadRequest() + .apply { validate().badRequestIfNotValid() } val article = articleRepo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found") val vote = VoteForUpdate( target = article, diff --git a/src/main/resources/openapi.yaml b/src/main/resources/openapi.yaml index 76b7217..2c3293d 100644 --- a/src/main/resources/openapi.yaml +++ b/src/main/resources/openapi.yaml @@ -1312,6 +1312,12 @@ paths: application/json: schema: $ref: '#/components/schemas/VoteAggregation' + 400: + description: BadReqest + content: + application/json: + schema: + $ref: '#/components/schemas/400' 401: $ref: '#/components/responses/401' diff --git a/src/test/kotlin/integration/Vote routes.kt b/src/test/kotlin/integration/Vote routes.kt index 6d15ce2..ef65a0b 100644 --- a/src/test/kotlin/integration/Vote routes.kt +++ b/src/test/kotlin/integration/Vote routes.kt @@ -1,8 +1,8 @@ package integration import fr.dcproject.component.citizen.database.CitizenI.Name -import integration.steps.`when`.Validate import integration.steps.`when`.Validate.ALL +import integration.steps.`when`.Validate.REQUEST_BODY import integration.steps.`when`.Validate.REQUEST_PARAM import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a PUT request` @@ -18,32 +18,73 @@ import integration.steps.then.`And the response should contain` import integration.steps.then.`And the response should not be null` import integration.steps.then.`Then the response should be` import integration.steps.then.and -import io.ktor.http.HttpStatusCode +import io.ktor.http.HttpStatusCode.Companion.BadRequest import io.ktor.http.HttpStatusCode.Companion.Created import io.ktor.http.HttpStatusCode.Companion.OK +import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) @Tags(Tag("integration"), Tag("vote")) class `Vote routes` : BaseTest() { - @Test - fun `I can vote article`() { + @TestFactory + fun `I can vote article`(): List { withIntegrationApplication { `Given I have citizen`("Thalès", "Milet") `Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5") - `When I send a PUT request`("/articles/835c5101-ca39-4038-a4e6-da6ee62ca6d5/vote") { - `authenticated as`("Thalès", "Milet") - `with body`( - """ + } + return (-1..1).map { note -> + DynamicTest.dynamicTest("""I can vote article with note "$note"""") { + withIntegrationApplication { + `When I send a PUT request`("/articles/835c5101-ca39-4038-a4e6-da6ee62ca6d5/vote") { + `authenticated as`("Thalès", "Milet") + `with body`( + """ + { + "note": $note + } + """ + ) + } `Then the response should be` Created + } + } + } + } + + @TestFactory + @Tag("BadRequest") + fun `I cannot vote article with wrong request`(): List { + withIntegrationApplication { + `Given I have citizen`("Thalès", "Milet") + `Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5") + } + + return listOf(-10, -2, +2, +10).map { note -> + DynamicTest.dynamicTest("""I can vote article with note "$note"""") { + withIntegrationApplication { + `When I send a PUT request`( + "/articles/835c5101-ca39-4038-a4e6-da6ee62ca6d5/vote", + ALL - REQUEST_BODY + ) { + `authenticated as`("Thalès", "Milet") + `with body`( + """ { - "note": 1 + "note": $note } """ - ) - } `Then the response should be` Created + ) + } `Then the response should be` BadRequest and { + `And the response should not be null`() + `And the response should contain`("$.invalidParams[0].name", ".note") + `And the response should contain`("$.invalidParams[0].reason", if (note > 0) "must be at most '1'" else "must be at least '-1'") + } + } + } } } @@ -91,7 +132,7 @@ class `Vote routes` : BaseTest() { `Given I have vote +1 on article`("7c9286db-470d-448c-aab1-3f0b072213b1", Name("Carl", "Gauss")) `When I send a GET request`("/citizens/c044823d-e778-4256-9016-b1334bf933d3/votes/articles?page=1&limit=60", ALL - REQUEST_PARAM) { `authenticated as`("Carl", "Gauss") - } `Then the response should be` HttpStatusCode.BadRequest and { + } `Then the response should be` BadRequest and { `And the response should not be null`() `And the response should contain`("$.invalidParams[0].name", ".limit") `And the response should contain`("$.invalidParams[0].reason", "must be at most '50'")