Valider les resource entrente #91

Merged
flecomte merged 46 commits from 21-valid-input into master 2021-04-16 03:27:11 +02:00
3 changed files with 73 additions and 13 deletions
Showing only changes of commit 0588f88f9a - Show all commits

View File

@@ -1,5 +1,6 @@
package fr.dcproject.component.vote.routes package fr.dcproject.component.vote.routes
import fr.dcproject.application.http.badRequestIfNotValid
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.receiveOrBadRequest import fr.dcproject.common.utils.receiveOrBadRequest
import fr.dcproject.component.article.database.ArticleRef 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.VoteAccessControl
import fr.dcproject.component.vote.database.VoteArticleRepository import fr.dcproject.component.vote.database.VoteArticleRepository
import fr.dcproject.component.vote.database.VoteForUpdate 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.application.call
import io.ktor.features.NotFoundException import io.ktor.features.NotFoundException
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
@@ -25,13 +29,22 @@ object PutVoteOnArticle {
@Location("/articles/{article}/vote") @Location("/articles/{article}/vote")
class ArticleVoteRequest(article: UUID) { class ArticleVoteRequest(article: UUID) {
val article = ArticleRef(article) val article = ArticleRef(article)
data class Input(var note: Int) data class Input(var note: Int) {
fun validate() = Validation<Input> {
Input::note {
minimum(-1)
maximum(1)
}
}.validate(this)
}
} }
fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl, articleRepo: ArticleRepository) { fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl, articleRepo: ArticleRepository) {
put<ArticleVoteRequest> { put<ArticleVoteRequest> {
mustBeAuth() mustBeAuth()
val input = call.receiveOrBadRequest<ArticleVoteRequest.Input>() val input = call.receiveOrBadRequest<ArticleVoteRequest.Input>()
.apply { validate().badRequestIfNotValid() }
val article = articleRepo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found") val article = articleRepo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found")
val vote = VoteForUpdate( val vote = VoteForUpdate(
target = article, target = article,

View File

@@ -1312,6 +1312,12 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/VoteAggregation' $ref: '#/components/schemas/VoteAggregation'
400:
description: BadReqest
content:
application/json:
schema:
$ref: '#/components/schemas/400'
401: 401:
$ref: '#/components/responses/401' $ref: '#/components/responses/401'

View File

@@ -1,8 +1,8 @@
package integration package integration
import fr.dcproject.component.citizen.database.CitizenI.Name import fr.dcproject.component.citizen.database.CitizenI.Name
import integration.steps.`when`.Validate
import integration.steps.`when`.Validate.ALL import integration.steps.`when`.Validate.ALL
import integration.steps.`when`.Validate.REQUEST_BODY
import integration.steps.`when`.Validate.REQUEST_PARAM 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 GET request`
import integration.steps.`when`.`When I send a PUT 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.`And the response should not be null`
import integration.steps.then.`Then the response should be` import integration.steps.then.`Then the response should be`
import integration.steps.then.and 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.Created
import io.ktor.http.HttpStatusCode.Companion.OK import io.ktor.http.HttpStatusCode.Companion.OK
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestFactory
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tags(Tag("integration"), Tag("vote")) @Tags(Tag("integration"), Tag("vote"))
class `Vote routes` : BaseTest() { class `Vote routes` : BaseTest() {
@Test @TestFactory
fun `I can vote article`() { fun `I can vote article`(): List<DynamicTest> {
withIntegrationApplication { withIntegrationApplication {
`Given I have citizen`("Thalès", "Milet") `Given I have citizen`("Thalès", "Milet")
`Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5") `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") return (-1..1).map { note ->
`with body`( 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<DynamicTest> {
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")) `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) { `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") `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 not be null`()
`And the response should contain`("$.invalidParams[0].name", ".limit") `And the response should contain`("$.invalidParams[0].name", ".limit")
`And the response should contain`("$.invalidParams[0].reason", "must be at most '50'") `And the response should contain`("$.invalidParams[0].reason", "must be at most '50'")