From f5c1aa29e89a4fb5311403dbbba992b64a5bea6e Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Fri, 9 Apr 2021 18:06:32 +0200 Subject: [PATCH] Add validation on route GetArticleComments --- .../database/CommentArticleRepository.kt | 15 ++--------- .../article/routes/GetArticleComments.kt | 26 +++++++++++++++++-- .../database/CommentConstitutionRepository.kt | 5 ++-- .../generic/database/CommentRepository.kt | 7 +++-- src/main/resources/openapi.yaml | 6 +++++ .../comment/find_comments_by_target.sql | 2 +- .../integration/Comment articles routes.kt | 20 +++++++++++++- 7 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/fr/dcproject/component/comment/article/database/CommentArticleRepository.kt b/src/main/kotlin/fr/dcproject/component/comment/article/database/CommentArticleRepository.kt index 4c7f1a4..a659e87 100644 --- a/src/main/kotlin/fr/dcproject/component/comment/article/database/CommentArticleRepository.kt +++ b/src/main/kotlin/fr/dcproject/component/comment/article/database/CommentArticleRepository.kt @@ -41,7 +41,7 @@ class CommentArticleRepository(requester: Requester) : CommentRepositoryAbs> { return requester .getFunction("find_comments_by_target") @@ -49,18 +49,7 @@ class CommentArticleRepository(requester: Requester) : CommentRepositoryAbs> } - - enum class Sort(val sql: String) { - CREATED_AT("created_at"), - VOTES("votes"); - - companion object { - fun fromString(string: String): Sort? { - return values().firstOrNull { it.sql == string } - } - } - } } diff --git a/src/main/kotlin/fr/dcproject/component/comment/article/routes/GetArticleComments.kt b/src/main/kotlin/fr/dcproject/component/comment/article/routes/GetArticleComments.kt index 896cad6..3ec2e88 100644 --- a/src/main/kotlin/fr/dcproject/component/comment/article/routes/GetArticleComments.kt +++ b/src/main/kotlin/fr/dcproject/component/comment/article/routes/GetArticleComments.kt @@ -1,5 +1,6 @@ package fr.dcproject.component.comment.article.routes +import fr.dcproject.application.http.badRequestIfNotValid import fr.dcproject.common.response.toOutput import fr.dcproject.common.security.assert import fr.dcproject.component.article.database.ArticleRef @@ -9,6 +10,10 @@ import fr.dcproject.component.comment.generic.CommentAccessControl import fr.dcproject.component.comment.toOutput import fr.dcproject.routes.PaginatedRequest import fr.dcproject.routes.PaginatedRequestI +import io.konform.validation.Validation +import io.konform.validation.jsonschema.enum +import io.konform.validation.jsonschema.maximum +import io.konform.validation.jsonschema.minimum import io.ktor.application.call import io.ktor.http.HttpStatusCode import io.ktor.locations.KtorExperimentalLocationsAPI @@ -26,14 +31,31 @@ object GetArticleComments { page: Int = 1, limit: Int = 50, val search: String? = null, - sort: String = CommentArticleRepository.Sort.CREATED_AT.sql + val sort: String = "createdAt" ) : PaginatedRequestI by PaginatedRequest(page, limit) { val article = ArticleRef(article) - val sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.fromString(sort) ?: CommentArticleRepository.Sort.CREATED_AT + + fun validate() = Validation { + ArticleCommentsRequest::page { + minimum(1) + } + ArticleCommentsRequest::limit { + minimum(1) + maximum(50) + } + ArticleCommentsRequest::sort ifPresent { + enum( + "votes", + "createdAt", + ) + } + }.validate(this) } fun Route.getArticleComments(repo: CommentArticleRepository, ac: CommentAccessControl) { get { + it.validate().badRequestIfNotValid() + val comments = repo.findByTarget(it.article, it.page, it.limit, it.sort) if (comments.result.isNotEmpty()) { ac.assert { canView(comments.result, citizenOrNull) } diff --git a/src/main/kotlin/fr/dcproject/component/comment/constitution/database/CommentConstitutionRepository.kt b/src/main/kotlin/fr/dcproject/component/comment/constitution/database/CommentConstitutionRepository.kt index e0d03fa..2b48c0c 100644 --- a/src/main/kotlin/fr/dcproject/component/comment/constitution/database/CommentConstitutionRepository.kt +++ b/src/main/kotlin/fr/dcproject/component/comment/constitution/database/CommentConstitutionRepository.kt @@ -5,7 +5,6 @@ import fr.dcproject.common.entity.TargetI import fr.dcproject.component.citizen.database.CitizenCreator import fr.dcproject.component.citizen.database.CitizenCreatorI import fr.dcproject.component.citizen.database.CitizenI -import fr.dcproject.component.comment.article.database.CommentArticleRepository import fr.dcproject.component.comment.generic.database.CommentForView import fr.dcproject.component.comment.generic.database.CommentRepositoryAbs import fr.dcproject.component.constitution.database.ConstitutionRef @@ -41,7 +40,7 @@ class CommentConstitutionRepository(requester: Requester) : CommentRepositoryAbs target: EntityI, page: Int, limit: Int, - sort: CommentArticleRepository.Sort + sort: String ): Paginated> { return requester.run { getFunction("find_comments_by_target") @@ -49,7 +48,7 @@ class CommentConstitutionRepository(requester: Requester) : CommentRepositoryAbs page, limit, "target_id" to target.id, - "sort" to sort.sql + "sort" to sort ) as Paginated> } diff --git a/src/main/kotlin/fr/dcproject/component/comment/generic/database/CommentRepository.kt b/src/main/kotlin/fr/dcproject/component/comment/generic/database/CommentRepository.kt index 8cf12c7..6443819 100644 --- a/src/main/kotlin/fr/dcproject/component/comment/generic/database/CommentRepository.kt +++ b/src/main/kotlin/fr/dcproject/component/comment/generic/database/CommentRepository.kt @@ -6,7 +6,6 @@ import fr.dcproject.common.entity.TargetRef import fr.dcproject.component.citizen.database.CitizenCreator import fr.dcproject.component.citizen.database.CitizenCreatorI import fr.dcproject.component.citizen.database.CitizenI -import fr.dcproject.component.comment.article.database.CommentArticleRepository import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Requester import fr.postgresjson.repository.RepositoryI @@ -49,7 +48,7 @@ abstract class CommentRepositoryAbs(override var requester: Request target: EntityI, page: Int = 1, limit: Int = 50, - sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.CREATED_AT + sort: String = "createdAt" ): Paginated> { return findByTarget(target.id, page, limit, sort) } @@ -58,7 +57,7 @@ abstract class CommentRepositoryAbs(override var requester: Request targetId: UUID, page: Int = 1, limit: Int = 50, - sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.CREATED_AT + sort: String = "createdAt" ): Paginated> { return requester.run { getFunction("find_comments_by_target") @@ -66,7 +65,7 @@ abstract class CommentRepositoryAbs(override var requester: Request page, limit, "target_id" to targetId, - "sort" to sort.sql + "sort" to sort ) as Paginated> } diff --git a/src/main/resources/openapi.yaml b/src/main/resources/openapi.yaml index 6df61e5..ab15282 100644 --- a/src/main/resources/openapi.yaml +++ b/src/main/resources/openapi.yaml @@ -544,6 +544,12 @@ paths: type: array items: $ref: '#/components/schemas/CommentResponse' + 400: + description: BadReqest + content: + application/json: + schema: + $ref: '#/components/schemas/400' post: security: - JWTAuth: [ ] diff --git a/src/main/resources/sql/functions/comment/find_comments_by_target.sql b/src/main/resources/sql/functions/comment/find_comments_by_target.sql index 8a78bdc..a35fd9c 100644 --- a/src/main/resources/sql/functions/comment/find_comments_by_target.sql +++ b/src/main/resources/sql/functions/comment/find_comments_by_target.sql @@ -26,7 +26,7 @@ begin else null end desc, case sort - when 'created_at' then com.created_at::text + when 'createdAt' then com.created_at::text else null end desc, com.created_at desc diff --git a/src/test/kotlin/integration/Comment articles routes.kt b/src/test/kotlin/integration/Comment articles routes.kt index 0962e86..deebc10 100644 --- a/src/test/kotlin/integration/Comment articles routes.kt +++ b/src/test/kotlin/integration/Comment articles routes.kt @@ -3,6 +3,7 @@ package integration import fr.dcproject.component.citizen.database.CitizenI.Name 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 POST request` import integration.steps.`when`.`When I send a PUT request` @@ -78,7 +79,7 @@ class `Comment articles routes` : BaseTest() { `Given I have citizen`("Enrico", "Fermi") `Given I have article`(id = "6166c078-ca97-4366-b0aa-2a5cd558c78a") `Given I have comment on article`(article = "6166c078-ca97-4366-b0aa-2a5cd558c78a", createdBy = Name("Enrico", "Fermi")) - `When I send a GET request`("/articles/6166c078-ca97-4366-b0aa-2a5cd558c78a/comments") { + `When I send a GET request`("/articles/6166c078-ca97-4366-b0aa-2a5cd558c78a/comments?page=1&limit=40&sort=votes") { `authenticated as`("Enrico", "Fermi") } `Then the response should be` OK and { `And the response should not be null`() @@ -87,6 +88,23 @@ class `Comment articles routes` : BaseTest() { } } + @Test + @Tag("BadRequest") + fun `I cannot get all comment on article with wrong parameters`() { + withIntegrationApplication { + `Given I have citizen`("Enrico", "Fermi") + `Given I have article`(id = "6166c078-ca97-4366-b0aa-2a5cd558c78a") + `Given I have comment on article`(article = "6166c078-ca97-4366-b0aa-2a5cd558c78a", createdBy = Name("Enrico", "Fermi")) + `When I send a GET request`("/articles/6166c078-ca97-4366-b0aa-2a5cd558c78a/comments?page=1&limit=40&sort=wrong", ALL - REQUEST_PARAM) { + `authenticated as`("Enrico", "Fermi") + } `Then the response should be` BadRequest and { + `And the response should not be null`() + `And the response should contain`("$.invalidParams[*].name", ".sort") + `And the response should contain`("$.invalidParams[*].reason", "must be one of: 'votes', 'createdAt'") + } + } + } + /* TODO add votes */ @Test fun `I can get all comment on article sorted by votes`() {