diff --git a/build.gradle.kts b/build.gradle.kts index 1c304e8..36f4c7e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -326,6 +326,12 @@ tasks.register("testArticles", Test::class) { includeTags("article") } } +tasks.register("testCitizens", Test::class) { + group = "tests" + useJUnitPlatform { + includeTags("citizen") + } +} dependencyCheck { formats = listOf(ReportGenerator.Format.HTML, ReportGenerator.Format.XML) diff --git a/src/main/kotlin/fr/dcproject/component/citizen/routes/FindCitizens.kt b/src/main/kotlin/fr/dcproject/component/citizen/routes/FindCitizens.kt index 6ca46f2..872c090 100644 --- a/src/main/kotlin/fr/dcproject/component/citizen/routes/FindCitizens.kt +++ b/src/main/kotlin/fr/dcproject/component/citizen/routes/FindCitizens.kt @@ -1,5 +1,6 @@ package fr.dcproject.component.citizen.routes +import fr.dcproject.application.http.badRequestIfNotValid import fr.dcproject.common.response.toOutput import fr.dcproject.common.security.assert import fr.dcproject.component.auth.citizenOrNull @@ -10,6 +11,10 @@ import fr.dcproject.component.citizen.database.CitizenRepository 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.locations.KtorExperimentalLocationsAPI import io.ktor.locations.Location @@ -27,11 +32,28 @@ object FindCitizens { val sort: String? = null, val direction: RepositoryI.Direction? = null, val search: String? = null - ) : PaginatedRequestI by PaginatedRequest(page, limit) + ) : PaginatedRequestI by PaginatedRequest(page, limit) { + fun validate() = Validation { + CitizensRequest::page { + minimum(1) + } + CitizensRequest::limit { + minimum(1) + maximum(50) + } + CitizensRequest::sort ifPresent { + enum( + "title", + "createdAt", + ) + } + }.validate(this) + } fun Route.findCitizen(ac: CitizenAccessControl, repo: CitizenRepository) { get { mustBeAuth() + it.validate().badRequestIfNotValid() val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search) ac.assert { canView(citizens.result, citizenOrNull) } call.respond( diff --git a/src/main/resources/openapi.yaml b/src/main/resources/openapi.yaml index 5800f17..af39955 100644 --- a/src/main/resources/openapi.yaml +++ b/src/main/resources/openapi.yaml @@ -395,7 +395,7 @@ paths: parameters: - $ref: '#/components/parameters/page' - $ref: '#/components/parameters/limit' - - $ref: '#/components/parameters/sort' + - $ref: '#/components/parameters/citizenSort' - $ref: '#/components/parameters/direction' - $ref: '#/components/parameters/search' responses: @@ -412,6 +412,12 @@ paths: type: array items: $ref: '#/components/schemas/CitizenListResponse' + 400: + description: BadReqest + content: + application/json: + schema: + $ref: '#/components/schemas/400' 401: $ref: '#/components/responses/401' /citizens/current: @@ -1418,6 +1424,17 @@ components: - createdAt - vote - popularity + citizenSort: + name: sort + in: query + description: The sort field name + example: createdAt + required: false + schema: + type: string + enum: + - title + - createdAt workgroupSort: name: sort in: query diff --git a/src/main/resources/sql/functions/article/find_articles.sql b/src/main/resources/sql/functions/article/find_articles.sql index 75164b2..ece47ee 100644 --- a/src/main/resources/sql/functions/article/find_articles.sql +++ b/src/main/resources/sql/functions/article/find_articles.sql @@ -45,7 +45,7 @@ begin case direction when 'asc' then case sort when 'title' then a.title - when 'created_at' then a.created_at::text + when 'createdAt' then a.created_at::text when 'vote' then ca.score::text when 'popularity' then ca.total::text else null @@ -54,7 +54,7 @@ begin case direction when 'desc' then case sort when 'title' then a.title - when 'created_at' then a.created_at::text + when 'createdAt' then a.created_at::text when 'vote' then ca.score::text when 'popularity' then ca.total::text end diff --git a/src/main/resources/sql/functions/citizen/find_citizens.sql b/src/main/resources/sql/functions/citizen/find_citizens.sql index c5f5a0f..a37a667 100644 --- a/src/main/resources/sql/functions/citizen/find_citizens.sql +++ b/src/main/resources/sql/functions/citizen/find_citizens.sql @@ -23,14 +23,14 @@ begin case direction when 'asc' then case sort when 'name' then (z.name->'first_name')::text - when 'created_at' then z.created_at::text + when 'createdAt' then z.created_at::text else null end end, case direction when 'desc' then case sort when 'name' then (z.name->'first_name')::text - when 'created_at' then z.created_at::text + when 'createdAt' then z.created_at::text end end desc, diff --git a/src/test/kotlin/integration/Citizen routes.kt b/src/test/kotlin/integration/Citizen routes.kt index 62f9843..95c445e 100644 --- a/src/test/kotlin/integration/Citizen routes.kt +++ b/src/test/kotlin/integration/Citizen routes.kt @@ -26,7 +26,7 @@ class `Citizen routes` : BaseTest() { fun `I can get Citizens information`() { withIntegrationApplication { `Given I have citizen`("Jean", "Perrin", id = "5267a5c6-af42-4a02-aa2b-6b71d2e43973") - `When I send a GET request`("/citizens") { + `When I send a GET request`("/citizens?page=1&limit=5&sort=createdAt") { `authenticated as`("Jean", "Perrin") } `Then the response should be` OK and { `And the response should not be null`() @@ -34,6 +34,19 @@ class `Citizen routes` : BaseTest() { } } + @Test + @Tag("BadRequest") + fun `I cannot get Citizens information with wrong request`() { + withIntegrationApplication { + `Given I have citizen`("Jean", "Perrin", id = "5267a5c6-af42-4a02-aa2b-6b71d2e43973") + `When I send a GET request`("/citizens?page=1&limit=5&sort=created_at", Validate.ALL - Validate.REQUEST_PARAM) { + `authenticated as`("Jean", "Perrin") + } `Then the response should be` BadRequest and { + `And the response should not be null`() + } + } + } + @Test fun `I can get specific Citizen information`() { withIntegrationApplication {