Big refactoring #77
@@ -347,4 +347,6 @@ dependencies {
|
||||
testImplementation("io.mockk:mockk-agent-jvm:1.10.6")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
|
||||
testImplementation("com.thedeanda:lorem:2.1")
|
||||
testImplementation("org.openapi4j:openapi-operation-validator:1.0.6")
|
||||
testImplementation("org.openapi4j:openapi-parser:1.0.6")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
package fr.dcproject.common.utils
|
||||
|
||||
import java.net.URL
|
||||
|
||||
fun String.readResource(callback: (String) -> Unit = {}): String {
|
||||
val content = callback::class.java.getResource(this).readText()
|
||||
val content = callback::class.java.getResource(this)?.readText() ?: error("File not found")
|
||||
callback(content)
|
||||
return content
|
||||
}
|
||||
|
||||
fun String.getResource(callback: (URL) -> Unit = {}): URL {
|
||||
val content = callback::class.java.getResource(this) ?: error("File not found")
|
||||
callback(content)
|
||||
return content
|
||||
}
|
||||
|
||||
188
src/main/resources/openapi2.yaml
Normal file
188
src/main/resources/openapi2.yaml
Normal file
@@ -0,0 +1,188 @@
|
||||
openapi: 3.0.2
|
||||
info:
|
||||
version: '0.1'
|
||||
title: 'DC Project'
|
||||
description: 'A free comunity program for create constitution'
|
||||
|
||||
paths:
|
||||
/articles:
|
||||
get:
|
||||
summary: Get all articles
|
||||
tags:
|
||||
- article
|
||||
operationId: getArticles
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page'
|
||||
- $ref: '#/components/parameters/limit'
|
||||
- $ref: '#/components/parameters/articleSort'
|
||||
- $ref: '#/components/parameters/direction'
|
||||
- $ref: '#/components/parameters/search'
|
||||
- $ref: '#/components/parameters/createdBy'
|
||||
- name: workgroup
|
||||
in: query
|
||||
description: ID of workgroup
|
||||
example: 82a0e60a-bb55-dbc0-1c3d-0a804df2b5df
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
200:
|
||||
description: The Article objects
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Paginated'
|
||||
- type: object
|
||||
properties:
|
||||
result:
|
||||
type: array
|
||||
items:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
created_by:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: object
|
||||
properties:
|
||||
first_name:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
workgroup:
|
||||
type: object
|
||||
nullable: true
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
draft:
|
||||
type: boolean
|
||||
|
||||
components:
|
||||
parameters:
|
||||
page:
|
||||
name: page
|
||||
in: query
|
||||
description: The current page
|
||||
example: 1
|
||||
required: false
|
||||
schema:
|
||||
default: 1
|
||||
type: integer
|
||||
minimum: 1
|
||||
limit:
|
||||
name: limit
|
||||
in: query
|
||||
description: The number of object per page
|
||||
example: 50
|
||||
required: false
|
||||
schema:
|
||||
default: 50
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 50
|
||||
sort:
|
||||
name: sort
|
||||
in: query
|
||||
description: The sort field name
|
||||
example: first_name
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
articleSort:
|
||||
name: sort
|
||||
in: query
|
||||
description: The sort field name
|
||||
example: createdAt
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- title
|
||||
- createdAt
|
||||
- vote
|
||||
- popularity
|
||||
direction:
|
||||
name: direction
|
||||
in: query
|
||||
description: The sort direction
|
||||
example: asc
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
default: asc
|
||||
enum: [asc, desc]
|
||||
search:
|
||||
name: search
|
||||
in: query
|
||||
description: A text to seach
|
||||
example: content50
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
createdBy:
|
||||
name: createdBy
|
||||
in: query
|
||||
description: filter by Author
|
||||
example: 4d673bfa-eaef-4290-b52f-85a9c8a7eba5
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
schemas:
|
||||
UUID:
|
||||
type: string
|
||||
pattern: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
||||
description: UUID
|
||||
format: uuid
|
||||
example:
|
||||
e74be8e4-6823-47c4-bd1b-789725b2fa8e
|
||||
UuidEntity:
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/UUID'
|
||||
Paginated:
|
||||
properties:
|
||||
result:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/UuidEntity'
|
||||
count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 1
|
||||
currentPage:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 1
|
||||
limit:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 50
|
||||
offset:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 1
|
||||
total:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 1
|
||||
servers:
|
||||
- description: localhost
|
||||
url: http://localhost:8080
|
||||
- description: production
|
||||
url: http://dc-project.fr
|
||||
@@ -14,6 +14,7 @@ import integration.steps.given.`Given I have articles`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have workgroup`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And schema must be valid`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should not contain`
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
@@ -36,6 +37,7 @@ class `Article routes` : BaseTest() {
|
||||
`And the response should contain pattern`("$.result[2].created_by.name.first_name", "firstName.+")
|
||||
`And the response should not contain`("$.result[3]")
|
||||
`And the response should contain list`("$.result", 3, 3)
|
||||
`And schema must be valid`()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
src/test/kotlin/integration/steps/then/schema.kt
Normal file
45
src/test/kotlin/integration/steps/then/schema.kt
Normal file
@@ -0,0 +1,45 @@
|
||||
package integration.steps.then
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import fr.dcproject.common.utils.getResource
|
||||
import io.ktor.request.contentType
|
||||
import io.ktor.request.httpMethod
|
||||
import io.ktor.request.uri
|
||||
import io.ktor.server.testing.TestApplicationResponse
|
||||
import org.openapi4j.core.model.v3.OAI3
|
||||
import org.openapi4j.parser.OpenApi3Parser
|
||||
import org.openapi4j.parser.model.v3.OpenApi3
|
||||
import org.openapi4j.parser.model.v3.Schema
|
||||
import org.openapi4j.schema.validator.ValidationContext
|
||||
import org.openapi4j.schema.validator.ValidationData
|
||||
import org.openapi4j.schema.validator.v3.SchemaValidator
|
||||
import java.io.File
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
fun TestApplicationResponse.`And schema must be valid`() {
|
||||
// Parse without validation, setting to true is strongly recommended for further data validation.
|
||||
val api: OpenApi3 = OpenApi3Parser().parse(File("/openapi2.yaml".getResource().toURI()), true)
|
||||
|
||||
val mediaType = this.call.request.contentType()
|
||||
val operation = this.call.request.httpMethod
|
||||
val uri = this.call.request.uri
|
||||
val status = this.call.response.status()
|
||||
|
||||
val schema: Schema = api
|
||||
.getPath(uri)
|
||||
.getOperation(operation.value.toLowerCase())
|
||||
.getResponse(status?.value?.toString() ?: error("HttpStatus not found"))
|
||||
.getContentMediaType(mediaType.toString())
|
||||
.schema
|
||||
|
||||
val validationContext: ValidationContext<OAI3> = ValidationContext(api.context)
|
||||
val jsonNode: JsonNode = schema.toNode()
|
||||
val schemaValidator = SchemaValidator(validationContext, "", jsonNode)
|
||||
|
||||
val mapper = ObjectMapper()
|
||||
val results = ValidationData<Unit>()
|
||||
schemaValidator.validate(mapper.readTree(content), results)
|
||||
|
||||
assertTrue(results.isValid, results.results().toString())
|
||||
}
|
||||
Reference in New Issue
Block a user