From 518b59e9aa17d6bed1af5834338f41ee8b37311e Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Thu, 15 Apr 2021 21:20:15 +0200 Subject: [PATCH] Add validation on route GetWorkgroups --- build.gradle.kts | 6 +++ .../workgroup/routes/GetWorkgroups.kt | 49 ++++++++++++++----- src/main/resources/openapi.yaml | 6 +++ .../functions/workgroup/find_workgroups.sql | 6 +-- .../kotlin/integration/Workgroup routes.kt | 19 ++++++- .../kotlin/integration/steps/when/request.kt | 3 ++ 6 files changed, 73 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3aff791..c293ef2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -368,6 +368,12 @@ tasks.register("testVotes", Test::class) { includeTags("vote") } } +tasks.register("testWorkgroup", Test::class) { + group = "tests" + useJUnitPlatform { + includeTags("workgroup") + } +} dependencyCheck { formats = listOf(ReportGenerator.Format.HTML, ReportGenerator.Format.XML) diff --git a/src/main/kotlin/fr/dcproject/component/workgroup/routes/GetWorkgroups.kt b/src/main/kotlin/fr/dcproject/component/workgroup/routes/GetWorkgroups.kt index 40f79ba..be8a606 100644 --- a/src/main/kotlin/fr/dcproject/component/workgroup/routes/GetWorkgroups.kt +++ b/src/main/kotlin/fr/dcproject/component/workgroup/routes/GetWorkgroups.kt @@ -1,12 +1,20 @@ package fr.dcproject.component.workgroup.routes +import fr.dcproject.application.http.badRequestIfNotValid import fr.dcproject.common.response.toOutput import fr.dcproject.common.security.assert import fr.dcproject.common.utils.toUUID +import fr.dcproject.common.validation.isUuid import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.workgroup.WorkgroupAccessControl import fr.dcproject.component.workgroup.database.WorkgroupRepository +import fr.dcproject.routes.PaginatedRequest +import fr.dcproject.routes.PaginatedRequestI import fr.postgresjson.repository.RepositoryI +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 @@ -27,23 +35,40 @@ object GetWorkgroups { val search: String? = null, val createdBy: String? = null, members: List? = null - ) { - val page: Int = if (page < 1) 1 else page - val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit + ) : PaginatedRequestI by PaginatedRequest(page, limit) { val members: List? = members?.toUUID() + fun validate() = Validation { + WorkgroupsRequest::page { + minimum(1) + } + WorkgroupsRequest::limit { + minimum(1) + maximum(50) + } + WorkgroupsRequest::sort ifPresent { + enum( + "name", + "createdAt", + ) + } + WorkgroupsRequest::createdBy ifPresent { + isUuid() + } + }.validate(this) } fun Route.getWorkgroups(repo: WorkgroupRepository, ac: WorkgroupAccessControl) { get { - val workgroups = - repo.find( - it.page, - it.limit, - it.sort, - it.direction, - it.search, - WorkgroupRepository.Filter(createdById = it.createdBy, members = it.members) - ) + it.validate().badRequestIfNotValid() + + val workgroups = repo.find( + it.page, + it.limit, + it.sort, + it.direction, + it.search, + WorkgroupRepository.Filter(createdById = it.createdBy, members = it.members) + ) ac.assert { canView(workgroups.result, citizenOrNull) } call.respond( HttpStatusCode.OK, diff --git a/src/main/resources/openapi.yaml b/src/main/resources/openapi.yaml index 0abb426..352de91 100644 --- a/src/main/resources/openapi.yaml +++ b/src/main/resources/openapi.yaml @@ -1359,6 +1359,12 @@ paths: type: array items: $ref: '#/components/schemas/WorkgroupListing' + 400: + description: BadReqest + content: + application/json: + schema: + $ref: '#/components/schemas/400' post: summary: Create new Workgroup security: diff --git a/src/main/resources/sql/functions/workgroup/find_workgroups.sql b/src/main/resources/sql/functions/workgroup/find_workgroups.sql index 8a1d86b..3600ed2 100644 --- a/src/main/resources/sql/functions/workgroup/find_workgroups.sql +++ b/src/main/resources/sql/functions/workgroup/find_workgroups.sql @@ -2,7 +2,7 @@ create or replace function find_workgroups( _search text default null, _filter json default '{}', direction text default 'desc', - sort text default 'created_at', + sort text default 'createdAt', "limit" int default 50, "offset" int default 0, out resource json, @@ -41,14 +41,14 @@ begin case direction when 'asc' then case sort when 'name' then w.name - when 'created_at' then w.created_at::text + when 'createdAt' then w.created_at::text else null end end, case direction when 'desc' then case sort when 'name' then w.name - when 'created_at' then w.created_at::text + when 'createdAt' then w.created_at::text end end desc, diff --git a/src/test/kotlin/integration/Workgroup routes.kt b/src/test/kotlin/integration/Workgroup routes.kt index fda1f5e..febebf2 100644 --- a/src/test/kotlin/integration/Workgroup routes.kt +++ b/src/test/kotlin/integration/Workgroup routes.kt @@ -1,6 +1,7 @@ package integration import fr.dcproject.component.citizen.database.CitizenI.Name +import integration.steps.`when`.Validate.REQUEST_PARAM import integration.steps.`when`.`When I send a DELETE request` import integration.steps.`when`.`When I send a GET request` import integration.steps.`when`.`When I send a POST request` @@ -15,8 +16,10 @@ import integration.steps.then.`And have property` import integration.steps.then.`And the response should be null` import integration.steps.then.`And the response should contain list` 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.Companion.BadRequest import io.ktor.http.HttpStatusCode.Companion.Created import io.ktor.http.HttpStatusCode.Companion.NoContent import io.ktor.http.HttpStatusCode.Companion.NotFound @@ -157,7 +160,7 @@ class `Workgroup routes` : BaseTest() { withIntegrationApplication { `Given I have citizen`("Max", "Planck") `Given I have workgroup`("3fd8edb6-c4b4-4c94-bc75-ddd9b290d32c") - `When I send a GET request`("/workgroups") { + `When I send a GET request`("/workgroups?page=1&limit=10&sort=createdAt") { `authenticated as`("Max", "Planck") `with no content`() } `Then the response should be` OK and { @@ -165,4 +168,18 @@ class `Workgroup routes` : BaseTest() { } } } + + @Test + @Tag("BadRequest") + fun `I cannot get workgroups list with wrong request`() { + withIntegrationApplication { + `Given I have workgroup`("3fd8edb6-c4b4-4c94-bc75-ddd9b290d32c") + `When I send a GET request`("/workgroups?sort=plop", -REQUEST_PARAM) { + } `Then the response should be` BadRequest and { + `And the response should not be null`() + `And the response should contain`("$.invalidParams[0].name", ".sort") + `And the response should contain`("$.invalidParams[0].reason", "must be one of: 'name', 'createdAt'") + } + } + } } diff --git a/src/test/kotlin/integration/steps/when/request.kt b/src/test/kotlin/integration/steps/when/request.kt index ca428c8..fe71e72 100644 --- a/src/test/kotlin/integration/steps/when/request.kt +++ b/src/test/kotlin/integration/steps/when/request.kt @@ -1,5 +1,6 @@ package integration.steps.`when` +import fr.dcproject.common.BitMask import fr.dcproject.common.BitMaskI import integration.steps.then.`And the schema parameters must be valid` import integration.steps.then.`And the schema request body must be valid` @@ -23,6 +24,8 @@ enum class Validate(override val bit: Long) : BitMaskI { RESPONSE_HEADER(16), RESPONSE(8 + 16), ALL((1 + 2 + 4) + (8 + 16)); + + operator fun unaryMinus(): BitMaskI = ALL - BitMask(this.bit) } fun TestApplicationCall.valid(validate: BitMaskI): TestApplicationCall {