Add validation on route GetWorkgroups

This commit is contained in:
2021-04-15 21:20:15 +02:00
parent 596b7ff0c9
commit 518b59e9aa
6 changed files with 73 additions and 16 deletions

View File

@@ -368,6 +368,12 @@ tasks.register("testVotes", Test::class) {
includeTags("vote") includeTags("vote")
} }
} }
tasks.register("testWorkgroup", Test::class) {
group = "tests"
useJUnitPlatform {
includeTags("workgroup")
}
}
dependencyCheck { dependencyCheck {
formats = listOf(ReportGenerator.Format.HTML, ReportGenerator.Format.XML) formats = listOf(ReportGenerator.Format.HTML, ReportGenerator.Format.XML)

View File

@@ -1,12 +1,20 @@
package fr.dcproject.component.workgroup.routes package fr.dcproject.component.workgroup.routes
import fr.dcproject.application.http.badRequestIfNotValid
import fr.dcproject.common.response.toOutput import fr.dcproject.common.response.toOutput
import fr.dcproject.common.security.assert import fr.dcproject.common.security.assert
import fr.dcproject.common.utils.toUUID import fr.dcproject.common.utils.toUUID
import fr.dcproject.common.validation.isUuid
import fr.dcproject.component.auth.citizenOrNull import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.workgroup.WorkgroupAccessControl import fr.dcproject.component.workgroup.WorkgroupAccessControl
import fr.dcproject.component.workgroup.database.WorkgroupRepository import fr.dcproject.component.workgroup.database.WorkgroupRepository
import fr.dcproject.routes.PaginatedRequest
import fr.dcproject.routes.PaginatedRequestI
import fr.postgresjson.repository.RepositoryI 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.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -27,16 +35,33 @@ object GetWorkgroups {
val search: String? = null, val search: String? = null,
val createdBy: String? = null, val createdBy: String? = null,
members: List<String?>? = null members: List<String?>? = null
) { ) : PaginatedRequestI by PaginatedRequest(page, limit) {
val page: Int = if (page < 1) 1 else page
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
val members: List<UUID>? = members?.toUUID() val members: List<UUID>? = members?.toUUID()
fun validate() = Validation<WorkgroupsRequest> {
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) { fun Route.getWorkgroups(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
get<WorkgroupsRequest> { get<WorkgroupsRequest> {
val workgroups = it.validate().badRequestIfNotValid()
repo.find(
val workgroups = repo.find(
it.page, it.page,
it.limit, it.limit,
it.sort, it.sort,

View File

@@ -1359,6 +1359,12 @@ paths:
type: array type: array
items: items:
$ref: '#/components/schemas/WorkgroupListing' $ref: '#/components/schemas/WorkgroupListing'
400:
description: BadReqest
content:
application/json:
schema:
$ref: '#/components/schemas/400'
post: post:
summary: Create new Workgroup summary: Create new Workgroup
security: security:

View File

@@ -2,7 +2,7 @@ create or replace function find_workgroups(
_search text default null, _search text default null,
_filter json default '{}', _filter json default '{}',
direction text default 'desc', direction text default 'desc',
sort text default 'created_at', sort text default 'createdAt',
"limit" int default 50, "limit" int default 50,
"offset" int default 0, "offset" int default 0,
out resource json, out resource json,
@@ -41,14 +41,14 @@ begin
case direction when 'asc' then case direction when 'asc' then
case sort case sort
when 'name' then w.name when 'name' then w.name
when 'created_at' then w.created_at::text when 'createdAt' then w.created_at::text
else null else null
end end
end, end,
case direction when 'desc' then case direction when 'desc' then
case sort case sort
when 'name' then w.name when 'name' then w.name
when 'created_at' then w.created_at::text when 'createdAt' then w.created_at::text
end end
end end
desc, desc,

View File

@@ -1,6 +1,7 @@
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.REQUEST_PARAM
import integration.steps.`when`.`When I send a DELETE request` 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 GET request`
import integration.steps.`when`.`When I send a POST 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 be null`
import integration.steps.then.`And the response should contain list` 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 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.`Then the response should be`
import integration.steps.then.and 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.Created
import io.ktor.http.HttpStatusCode.Companion.NoContent import io.ktor.http.HttpStatusCode.Companion.NoContent
import io.ktor.http.HttpStatusCode.Companion.NotFound import io.ktor.http.HttpStatusCode.Companion.NotFound
@@ -157,7 +160,7 @@ class `Workgroup routes` : BaseTest() {
withIntegrationApplication { withIntegrationApplication {
`Given I have citizen`("Max", "Planck") `Given I have citizen`("Max", "Planck")
`Given I have workgroup`("3fd8edb6-c4b4-4c94-bc75-ddd9b290d32c") `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") `authenticated as`("Max", "Planck")
`with no content`() `with no content`()
} `Then the response should be` OK and { } `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'")
}
}
}
} }

View File

@@ -1,5 +1,6 @@
package integration.steps.`when` package integration.steps.`when`
import fr.dcproject.common.BitMask
import fr.dcproject.common.BitMaskI import fr.dcproject.common.BitMaskI
import integration.steps.then.`And the schema parameters must be valid` import integration.steps.then.`And the schema parameters must be valid`
import integration.steps.then.`And the schema request body 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_HEADER(16),
RESPONSE(8 + 16), RESPONSE(8 + 16),
ALL((1 + 2 + 4) + (8 + 16)); ALL((1 + 2 + 4) + (8 + 16));
operator fun unaryMinus(): BitMaskI = ALL - BitMask(this.bit)
} }
fun TestApplicationCall.valid(validate: BitMaskI): TestApplicationCall { fun TestApplicationCall.valid(validate: BitMaskI): TestApplicationCall {