Valider les resource entrente #91
32
.github/workflows/tests.yml
vendored
32
.github/workflows/tests.yml
vendored
@@ -69,6 +69,13 @@ jobs:
|
||||
with:
|
||||
name: Build
|
||||
path: build
|
||||
|
||||
- name: Composer Up
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
arguments: testSqlComposeUp
|
||||
|
||||
- name: TestSql
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
@@ -81,19 +88,29 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: Build
|
||||
path: build
|
||||
|
||||
- name: Composer Up
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
arguments: testComposeUp
|
||||
|
||||
- name: Test
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
arguments: test -x testSql
|
||||
arguments: test
|
||||
|
||||
- name: Coverage
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
@@ -101,17 +118,28 @@ jobs:
|
||||
arguments: coveralls
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
|
||||
- name: Test
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
arguments: test
|
||||
|
||||
- name: Build and analyze
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
run: ./gradlew build sonarqube --info
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
arguments: sonarqube --info
|
||||
|
||||
lint:
|
||||
needs: build
|
||||
|
||||
2
.idea/runConfigurations/Build_without_test.xml
generated
2
.idea/runConfigurations/Build_without_test.xml
generated
@@ -4,7 +4,7 @@
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="-x test -x ktlintKotlinScriptCheck -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck" />
|
||||
<option name="scriptParameters" value="-x test -x ktlintKotlinScriptCheck -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck -x detekt" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Sonarqube without test" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<configuration default="false" name="Sonarqube (Send without run test)" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
23
.idea/runConfigurations/Test_With_Dependencies.xml
generated
Normal file
23
.idea/runConfigurations/Test_With_Dependencies.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Test With Dependencies" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="testWithDependencies" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,7 +1,5 @@
|
||||
# DC Project
|
||||
|
||||
[](https://www.codefactor.io/repository/github/flecomte/dc-project)
|
||||
[](https://www.codacy.com/gh/flecomte/dc-project/dashboard?utm_source=github.com&utm_medium=referral&utm_content=flecomte/dc-project&utm_campaign=Badge_Grade)
|
||||
[](https://sonarcloud.io/dashboard?id=dc-project)
|
||||
|
||||
[](https://github.com/flecomte/dc-project/actions/workflows/tests.yml)
|
||||
|
||||
@@ -94,7 +94,7 @@ val migration by tasks.registering {
|
||||
}
|
||||
|
||||
val migrationTest by tasks.registering {
|
||||
group = "verification"
|
||||
group = "tests"
|
||||
dependsOn(tasks.named("testComposeUp"))
|
||||
finalizedBy(tasks.named("testComposeDown"))
|
||||
doLast {
|
||||
@@ -118,11 +118,9 @@ val migrationTest by tasks.registering {
|
||||
}
|
||||
|
||||
val testSql by tasks.registering {
|
||||
group = "verification"
|
||||
group = "tests"
|
||||
dependsOn(tasks.named("processResources"))
|
||||
dependsOn(tasks.named("processTestResources"))
|
||||
dependsOn(tasks.named("testSqlComposeUp"))
|
||||
finalizedBy(tasks.named("testSqlComposeDown"))
|
||||
|
||||
doLast {
|
||||
val config = ConfigFactory.parseFile(file("$buildDir/resources/test/application-test.conf")).resolve()
|
||||
@@ -183,8 +181,6 @@ tasks.named<ShadowJar>("shadowJar") {
|
||||
}
|
||||
|
||||
tasks.sonarqube.configure {
|
||||
dependsOn(tasks.test)
|
||||
dependsOn(tasks.detekt)
|
||||
dependsOn(tasks.jacocoTestReport)
|
||||
}
|
||||
|
||||
@@ -198,7 +194,6 @@ tasks.test {
|
||||
useJUnit()
|
||||
useJUnitPlatform()
|
||||
systemProperty("junit.jupiter.execution.parallel.enabled", true)
|
||||
dependsOn(testSql)
|
||||
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
|
||||
}
|
||||
|
||||
@@ -206,13 +201,6 @@ coveralls {
|
||||
sourceDirs.add("src/main/kotlin")
|
||||
}
|
||||
|
||||
tasks.register("testAll") {
|
||||
group = "verification"
|
||||
dependsOn(testSql)
|
||||
dependsOn(tasks.test)
|
||||
dependsOn(tasks.ktlintCheck)
|
||||
}
|
||||
|
||||
apply(plugin = "docker-compose")
|
||||
dockerCompose {
|
||||
projectName = "dc-project"
|
||||
@@ -228,14 +216,12 @@ dockerCompose {
|
||||
useComposeFiles = listOf("docker-compose-test.yml")
|
||||
startedServices = listOf("db", "elasticsearch")
|
||||
stopContainers = false
|
||||
isRequiredBy(project.tasks.named("testSql"))
|
||||
}
|
||||
|
||||
createNested("test").apply {
|
||||
projectName = "dc-project_test"
|
||||
useComposeFiles = listOf("docker-compose-test.yml")
|
||||
stopContainers = false
|
||||
isRequiredBy(project.tasks.test)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +306,74 @@ tasks.named("testComposeUp").configure {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("testWithDependencies", Test::class) {
|
||||
group = "tests"
|
||||
dependsOn(tasks.named("testComposeUp"))
|
||||
dependsOn(tasks.ktlintCheck)
|
||||
dependsOn(testSql)
|
||||
finalizedBy(tasks.sonarqube) // report is always generated after tests run
|
||||
}
|
||||
tasks.register("testArticles", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("article")
|
||||
}
|
||||
}
|
||||
tasks.register("testCitizens", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("citizen")
|
||||
}
|
||||
}
|
||||
tasks.register("testComments", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("comment")
|
||||
}
|
||||
}
|
||||
tasks.register("testConstitutions", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("constitution")
|
||||
}
|
||||
}
|
||||
tasks.register("testFollows", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("follow")
|
||||
}
|
||||
}
|
||||
tasks.register("testNotifications", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("notification")
|
||||
}
|
||||
}
|
||||
tasks.register("testOpinions", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("opinion")
|
||||
}
|
||||
}
|
||||
tasks.register("testVotes", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("vote")
|
||||
}
|
||||
}
|
||||
tasks.register("testWorkgroups", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("workgroup")
|
||||
}
|
||||
}
|
||||
tasks.register("testViews", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("view")
|
||||
}
|
||||
}
|
||||
|
||||
dependencyCheck {
|
||||
formats = listOf(ReportGenerator.Format.HTML, ReportGenerator.Format.XML)
|
||||
}
|
||||
@@ -327,8 +381,9 @@ dependencyCheck {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven { url = uri("https://kotlin.bintray.com/ktor") }
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
maven("https://kotlin.bintray.com/ktor")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://dl.bintray.com/konform-kt/konform")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -359,6 +414,7 @@ dependencies {
|
||||
implementation("org.elasticsearch.client:elasticsearch-rest-client:6.7.1")
|
||||
implementation("com.jayway.jsonpath:json-path:2.5.0")
|
||||
implementation("com.avast.gradle:gradle-docker-compose-plugin:0.14.0")
|
||||
implementation("io.konform:konform-jvm:0.2.0")
|
||||
|
||||
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
||||
testImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package fr.dcproject.application
|
||||
|
||||
import fr.dcproject.application.http.BadRequestException
|
||||
import fr.dcproject.application.http.HttpErrorBadRequest
|
||||
import fr.dcproject.application.http.HttpErrorBadRequest.InvalidParam
|
||||
import io.ktor.features.DataConversion
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.parameter.ParametersDefinition
|
||||
@@ -8,6 +12,7 @@ import org.koin.core.qualifier.Qualifier
|
||||
import java.util.UUID
|
||||
|
||||
private typealias ConverterDeclaration = DataConversion.Configuration.() -> Unit
|
||||
|
||||
private inline fun <reified T> DataConversion.Configuration.get(
|
||||
qualifier: Qualifier? = null,
|
||||
noinline parameters: ParametersDefinition? = null
|
||||
@@ -17,7 +22,21 @@ private inline fun <reified T> DataConversion.Configuration.get(
|
||||
val converters: ConverterDeclaration = {
|
||||
convert<UUID> {
|
||||
decode { values, _ ->
|
||||
try {
|
||||
values.singleOrNull()?.let { UUID.fromString(it) }
|
||||
} catch (e: Throwable) {
|
||||
throw BadRequestException(
|
||||
HttpErrorBadRequest(
|
||||
HttpStatusCode.BadRequest,
|
||||
invalidParams = listOf(
|
||||
InvalidParam(
|
||||
"ID",
|
||||
"must be UUID"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
encode { value ->
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package fr.dcproject.application.http
|
||||
|
||||
import fr.dcproject.application.http.HttpErrorBadRequest.InvalidParam
|
||||
import io.konform.validation.ValidationResult
|
||||
import io.ktor.http.HttpStatusCode
|
||||
|
||||
class BadRequestException(val httpError: HttpErrorBadRequest) : Exception()
|
||||
|
||||
class HttpErrorBadRequest(
|
||||
statusCode: HttpStatusCode,
|
||||
val title: String = statusCode.description,
|
||||
val invalidParams: List<InvalidParam>,
|
||||
) {
|
||||
val statusCode: Int = statusCode.value
|
||||
data class InvalidParam(
|
||||
val name: String,
|
||||
val reason: String
|
||||
)
|
||||
}
|
||||
|
||||
fun ValidationResult<*>.toOutput() = HttpErrorBadRequest(
|
||||
HttpStatusCode.BadRequest,
|
||||
invalidParams = this.errors.map {
|
||||
InvalidParam(
|
||||
it.dataPath,
|
||||
it.message
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
fun ValidationResult<*>.badRequestIfNotValid() {
|
||||
if (errors.size > 0) {
|
||||
throw BadRequestException(toOutput())
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import fr.dcproject.component.auth.ForbiddenException
|
||||
import fr.dcproject.component.auth.user
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.features.ParameterConversionException
|
||||
import io.ktor.features.StatusPages
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.response.respond
|
||||
@@ -13,18 +14,10 @@ import java.util.concurrent.CompletionException
|
||||
|
||||
class HttpError(
|
||||
statusCode: HttpStatusCode,
|
||||
val cause: Throwable? = null,
|
||||
val type: String? = null,
|
||||
cause: Throwable? = null,
|
||||
val title: String = cause?.message ?: statusCode.description,
|
||||
val detail: String? = null,
|
||||
val invalidParams: List<InvalidParam>? = null,
|
||||
val stackTrace: String? = cause?.stackTraceToString()
|
||||
) {
|
||||
val statusCode: Int = statusCode.value
|
||||
data class InvalidParam(
|
||||
val name: String,
|
||||
val reason: String
|
||||
)
|
||||
}
|
||||
|
||||
fun statusPagesInstallation(): StatusPages.Configuration.() -> Unit = {
|
||||
@@ -79,4 +72,15 @@ fun statusPagesInstallation(): StatusPages.Configuration.() -> Unit = {
|
||||
call.respond(HttpStatusCode.Forbidden, it)
|
||||
}
|
||||
}
|
||||
exception<BadRequestException> { e ->
|
||||
call.respond(HttpStatusCode.BadRequest, e.httpError)
|
||||
}
|
||||
exception<ParameterConversionException> { e ->
|
||||
val parent = e.cause
|
||||
if (parent is BadRequestException) {
|
||||
call.respond(HttpStatusCode.BadRequest, parent.httpError)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ interface PaginatedRequestI {
|
||||
}
|
||||
|
||||
open class PaginatedRequest(
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
) : PaginatedRequestI {
|
||||
override val page: Int = if (page < 1) 1 else page
|
||||
override val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
||||
}
|
||||
override val page: Int = 1,
|
||||
override val limit: Int = 50
|
||||
) : PaginatedRequestI
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.jayway.jsonpath.JsonPath
|
||||
import com.jayway.jsonpath.PathNotFoundException
|
||||
import org.apache.http.util.EntityUtils
|
||||
import org.elasticsearch.client.Response
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
fun Response.contentToString(): String {
|
||||
return EntityUtils.toString(this.entity)
|
||||
@@ -22,8 +21,6 @@ fun String.getJsonField(jsonPath: String): Int? {
|
||||
return try {
|
||||
JsonPath.read(this, jsonPath)
|
||||
} catch (e: PathNotFoundException) {
|
||||
LoggerFactory.getLogger("fr.dcproject.utils.getJsonField")
|
||||
.warn("No value for Json path ${JsonPath.compile(jsonPath).path}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
41
src/main/kotlin/fr/dcproject/common/utils/retry.kt
Normal file
41
src/main/kotlin/fr/dcproject/common/utils/retry.kt
Normal file
@@ -0,0 +1,41 @@
|
||||
package fr.dcproject.common.utils
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@ExperimentalTime
|
||||
fun <T> retry(numOfRetries: Int, duration: Duration = Duration.ZERO, block: (RetryContext) -> T): T {
|
||||
val logger: Logger = LoggerFactory.getLogger("fr.dcproject.utils.retry")
|
||||
var throwable: Throwable? = null
|
||||
for (attempt in 1..numOfRetries) {
|
||||
val context = RetryContext()
|
||||
try {
|
||||
val output = block(context)
|
||||
if (context.hasStop()) {
|
||||
break
|
||||
}
|
||||
return output
|
||||
} catch (e: Throwable) {
|
||||
throwable = e
|
||||
logger.debug("Failed attempt $attempt / $numOfRetries. Wait ${duration.inSeconds} seconds")
|
||||
Thread.sleep(duration.inMilliseconds.toLong())
|
||||
} finally {
|
||||
if (context.hasStop()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
throw throwable!!
|
||||
}
|
||||
|
||||
class RetryContext() {
|
||||
var stoped = false
|
||||
|
||||
fun stop() {
|
||||
stoped = true
|
||||
}
|
||||
|
||||
fun hasStop(): Boolean = stoped
|
||||
}
|
||||
6
src/main/kotlin/fr/dcproject/common/validation/Email.kt
Normal file
6
src/main/kotlin/fr/dcproject/common/validation/Email.kt
Normal file
@@ -0,0 +1,6 @@
|
||||
package fr.dcproject.common.validation
|
||||
|
||||
import io.konform.validation.ValidationBuilder
|
||||
import io.konform.validation.jsonschema.pattern
|
||||
|
||||
fun ValidationBuilder<String>.email() = pattern(""".+@.+\..+""")
|
||||
22
src/main/kotlin/fr/dcproject/common/validation/Password.kt
Normal file
22
src/main/kotlin/fr/dcproject/common/validation/Password.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package fr.dcproject.common.validation
|
||||
|
||||
import io.konform.validation.ValidationBuilder
|
||||
|
||||
fun ValidationBuilder<String>.passwordScore(minScore: Int) =
|
||||
addConstraint("is not enough strong. Use Upper case, Lower case and special characters or juste use more characters.") { value ->
|
||||
value.passwordScore() >= minScore
|
||||
}
|
||||
|
||||
fun String.passwordScore(): Int {
|
||||
var score: Int = length
|
||||
val alphaNum = ('a'..'z').toList() + ('A'..'Z').toList() + ('0'..'9').toList()
|
||||
val specialCount = length - toList().intersect(alphaNum).size
|
||||
score += specialCount.let { if (it > 3) 3 else it }
|
||||
|
||||
val hasAlphaLower = toList().intersect(('a'..'z').toList()).size.let { if (it > 2) 2 else it }
|
||||
val hasAlphaUpper = toList().intersect(('A'..'Z').toList()).size.let { if (it > 2) 2 else it }
|
||||
val hasNum = toList().intersect(('0'..'9').toList()).size.let { if (it > 2) 2 else it }
|
||||
score += (hasAlphaLower + hasAlphaUpper + hasNum - 2) * 2
|
||||
|
||||
return score
|
||||
}
|
||||
15
src/main/kotlin/fr/dcproject/common/validation/Url.kt
Normal file
15
src/main/kotlin/fr/dcproject/common/validation/Url.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package fr.dcproject.common.validation
|
||||
|
||||
import io.konform.validation.ValidationBuilder
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
fun ValidationBuilder<String>.isUrl() =
|
||||
addConstraint("is not url") {
|
||||
try {
|
||||
URL(it)
|
||||
true
|
||||
} catch (e: MalformedURLException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
14
src/main/kotlin/fr/dcproject/common/validation/Uuid.kt
Normal file
14
src/main/kotlin/fr/dcproject/common/validation/Uuid.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package fr.dcproject.common.validation
|
||||
|
||||
import io.konform.validation.ValidationBuilder
|
||||
import java.util.UUID
|
||||
|
||||
fun ValidationBuilder<String>.isUuid() =
|
||||
addConstraint("must be UUID") {
|
||||
try {
|
||||
UUID.fromString(it)
|
||||
true
|
||||
} catch (exception: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,69 @@
|
||||
package fr.dcproject.component.article.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.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.database.ArticleForListing
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
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
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object FindArticleVersions {
|
||||
@Location("/articles/{article}/versions")
|
||||
class ArticleVersionsRequest(
|
||||
article: UUID,
|
||||
val article: String,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
val sort: String? = null,
|
||||
val direction: RepositoryI.Direction? = null,
|
||||
val search: String? = null
|
||||
) {
|
||||
val page: Int = if (page < 1) 1 else page
|
||||
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
||||
val article = ArticleRef(article)
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
fun validate() = Validation<ArticleVersionsRequest> {
|
||||
ArticleVersionsRequest::page {
|
||||
minimum(1)
|
||||
maximum(100)
|
||||
}
|
||||
ArticleVersionsRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
ArticleVersionsRequest::sort ifPresent {
|
||||
enum(
|
||||
"title",
|
||||
"createdAt",
|
||||
"vote",
|
||||
"popularity",
|
||||
)
|
||||
}
|
||||
ArticleVersionsRequest::article {
|
||||
isUuid()
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) =
|
||||
findVersionsById(request.page, request.limit, request.article.id)
|
||||
findVersionsById(request.page, request.limit, request.article.toUUID())
|
||||
|
||||
fun Route.findArticleVersions(repo: ArticleRepository, ac: ArticleAccessControl) {
|
||||
get<ArticleVersionsRequest> {
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
repo.findVersions(it)
|
||||
.apply { ac.assert { canView(result, citizenOrNull) } }
|
||||
.run {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.validation.isUuid
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
import fr.dcproject.component.article.database.ArticleForListing
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
@@ -10,6 +12,10 @@ import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
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
|
||||
@@ -28,7 +34,31 @@ object FindArticles {
|
||||
val search: String? = null,
|
||||
val createdBy: String? = null,
|
||||
val workgroup: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
fun validate() = Validation<ArticlesRequest> {
|
||||
ArticlesRequest::page {
|
||||
minimum(1)
|
||||
}
|
||||
ArticlesRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
ArticlesRequest::sort ifPresent {
|
||||
enum(
|
||||
"title",
|
||||
"createdAt",
|
||||
"vote",
|
||||
"popularity",
|
||||
)
|
||||
}
|
||||
ArticlesRequest::createdBy ifPresent {
|
||||
isUuid()
|
||||
}
|
||||
ArticlesRequest::workgroup ifPresent {
|
||||
isUuid()
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
private fun ArticleRepository.findArticles(request: ArticlesRequest): Paginated<ArticleForListing> {
|
||||
return find(
|
||||
@@ -43,6 +73,8 @@ object FindArticles {
|
||||
|
||||
fun Route.findArticles(repo: ArticleRepository, ac: ArticleAccessControl) {
|
||||
get<ArticlesRequest> {
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
repo.findArticles(it)
|
||||
.apply { ac.assert { canView(result, citizenOrNull) } }
|
||||
.let {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.article.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.article.ArticleAccessControl
|
||||
@@ -12,6 +13,11 @@ import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotification
|
||||
import fr.dcproject.component.notification.Publisher
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRef
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxItems
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minItems
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -35,11 +41,31 @@ object UpsertArticle {
|
||||
val draft: Boolean = false,
|
||||
val versionId: UUID,
|
||||
val workgroup: WorkgroupRef? = null,
|
||||
)
|
||||
) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::title {
|
||||
minLength(5)
|
||||
maxLength(80)
|
||||
}
|
||||
Input::content {
|
||||
minLength(50)
|
||||
maxLength(6000)
|
||||
}
|
||||
Input::description {
|
||||
minLength(50)
|
||||
maxLength(6000)
|
||||
}
|
||||
Input::tags {
|
||||
minItems(0)
|
||||
maxItems(15)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.upsertArticle(repo: ArticleRepository, publisher: Publisher, ac: ArticleAccessControl) {
|
||||
suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receiveOrBadRequest<Input>().run {
|
||||
validate().badRequestIfNotValid()
|
||||
ArticleForUpdate(
|
||||
id = id ?: UUID.randomUUID(),
|
||||
title = title,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package fr.dcproject.component.auth.routes
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.common.validation.email
|
||||
import fr.dcproject.common.validation.passwordScore
|
||||
import fr.dcproject.component.auth.database.UserForCreate
|
||||
import fr.dcproject.component.auth.database.UserI
|
||||
import fr.dcproject.component.auth.jwt.makeToken
|
||||
@@ -9,6 +12,9 @@ import fr.dcproject.component.auth.routes.Register.RegisterRequest.Input
|
||||
import fr.dcproject.component.citizen.database.CitizenForCreate
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.BadRequestException
|
||||
import io.ktor.http.ContentType
|
||||
@@ -43,6 +49,35 @@ object Register {
|
||||
val username: String,
|
||||
val password: String
|
||||
)
|
||||
|
||||
fun validate() = Validation<Input> {
|
||||
Input::name {
|
||||
Name::firstName {
|
||||
minLength(2)
|
||||
maxLength(50)
|
||||
}
|
||||
Name::lastName {
|
||||
minLength(2)
|
||||
maxLength(50)
|
||||
}
|
||||
Name::civility ifPresent {
|
||||
minLength(1)
|
||||
maxLength(10)
|
||||
}
|
||||
}
|
||||
Input::user {
|
||||
User::username {
|
||||
minLength(7)
|
||||
maxLength(30)
|
||||
}
|
||||
User::password {
|
||||
passwordScore(15)
|
||||
}
|
||||
}
|
||||
Input::email {
|
||||
email()
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +97,10 @@ object Register {
|
||||
|
||||
post<RegisterRequest> {
|
||||
try {
|
||||
val citizen = call.receiveOrBadRequest<Input>().toCitizen()
|
||||
val citizen = call.receiveOrBadRequest<Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
.toCitizen()
|
||||
|
||||
citizenRepo.insertWithUser(citizen)?.user?.makeToken()?.let { token ->
|
||||
if (call.request.accept() == ContentType.Application.Json.toString()) {
|
||||
call.respond(
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package fr.dcproject.component.citizen.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.common.validation.passwordScore
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.database.UserRepository
|
||||
@@ -9,6 +11,7 @@ import fr.dcproject.component.auth.database.UserWithPassword
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.CitizenAccessControl
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import io.konform.validation.Validation
|
||||
import io.ktor.application.call
|
||||
import io.ktor.auth.UserPasswordCredential
|
||||
import io.ktor.features.BadRequestException
|
||||
@@ -25,14 +28,21 @@ object ChangeMyPassword {
|
||||
@Location("/citizens/{citizen}/password/change")
|
||||
class ChangePasswordCitizenRequest(citizen: UUID) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
data class Input(val oldPassword: String, val newPassword: String)
|
||||
data class Input(val oldPassword: String, val newPassword: String) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::newPassword {
|
||||
passwordScore(15)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.changeMyPassword(ac: CitizenAccessControl, userRepository: UserRepository) {
|
||||
put<ChangePasswordCitizenRequest> {
|
||||
mustBeAuth()
|
||||
ac.assert { canChangePassword(it.citizen, citizenOrNull) }
|
||||
val content = call.receiveOrBadRequest<ChangePasswordCitizenRequest.Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
ac.assert { canChangePassword(it.citizen, citizenOrNull) }
|
||||
userRepository.findByCredentials(UserPasswordCredential(citizen.user.username, content.oldPassword)) ?: throw BadRequestException("Bad Password")
|
||||
userRepository.changePassword(
|
||||
UserWithPassword(
|
||||
|
||||
@@ -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> {
|
||||
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<CitizensRequest> {
|
||||
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(
|
||||
|
||||
@@ -41,7 +41,7 @@ class CommentArticleRepository(requester: Requester) : CommentRepositoryAbs<Arti
|
||||
target: EntityI,
|
||||
page: Int,
|
||||
limit: Int,
|
||||
sort: Sort
|
||||
sort: String
|
||||
): Paginated<CommentForView<ArticleForView, CitizenCreatorI>> {
|
||||
return requester
|
||||
.getFunction("find_comments_by_target")
|
||||
@@ -49,18 +49,7 @@ class CommentArticleRepository(requester: Requester) : CommentRepositoryAbs<Arti
|
||||
page,
|
||||
limit,
|
||||
"target_id" to target.id,
|
||||
"sort" to sort.sql
|
||||
"sort" to sort
|
||||
) as Paginated<CommentForView<ArticleForView, CitizenCreatorI>>
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package fr.dcproject.component.comment.article.routes
|
||||
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
@@ -12,6 +12,9 @@ import fr.dcproject.component.comment.article.routes.CreateCommentArticle.PostAr
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.database.CommentForUpdate
|
||||
import fr.dcproject.component.comment.toOutput
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -26,13 +29,22 @@ object CreateCommentArticle {
|
||||
@Location("/articles/{article}/comments")
|
||||
class PostArticleCommentRequest(article: UUID) {
|
||||
val article = ArticleRef(article)
|
||||
class Input(val content: String)
|
||||
class Input(val content: String) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::content {
|
||||
minLength(20)
|
||||
maxLength(6000)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.createCommentArticle(repo: CommentArticleRepository, ac: CommentAccessControl) {
|
||||
post<PostArticleCommentRequest> {
|
||||
mustBeAuth()
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
call.receiveOrBadRequest<Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
.run {
|
||||
CommentForUpdate(
|
||||
target = it.article,
|
||||
createdBy = citizen,
|
||||
|
||||
@@ -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> {
|
||||
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<ArticleCommentsRequest> {
|
||||
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) }
|
||||
|
||||
@@ -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<CommentForView<ConstitutionRef, CitizenCreatorI>> {
|
||||
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<CommentForView<ConstitutionRef, CitizenCreatorI>>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.comment.constitution.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
@@ -12,6 +13,9 @@ import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.database.CommentForUpdate
|
||||
import fr.dcproject.component.comment.toOutput
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -26,13 +30,23 @@ object CreateConstitutionComment {
|
||||
@Location("/constitutions/{constitution}/comments")
|
||||
class CreateConstitutionCommentRequest(constitution: UUID) {
|
||||
val constitution = ConstitutionRef(constitution)
|
||||
class Input(val content: String)
|
||||
class Input(val content: String) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::content {
|
||||
minLength(20)
|
||||
maxLength(6000)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.createConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
|
||||
post<CreateConstitutionCommentRequest> {
|
||||
mustBeAuth()
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
|
||||
call.receiveOrBadRequest<Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
.run {
|
||||
CommentForUpdate(
|
||||
target = it.constitution,
|
||||
createdBy = citizen,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.comment.constitution.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
|
||||
@@ -7,6 +8,12 @@ import fr.dcproject.component.comment.constitution.database.CommentConstitutionR
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.toOutput
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
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
|
||||
@@ -19,12 +26,36 @@ import java.util.UUID
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetConstitutionComment {
|
||||
@Location("/constitutions/{constitution}/comments")
|
||||
class GetConstitutionCommentRequest(constitution: UUID) {
|
||||
class GetConstitutionCommentRequest(
|
||||
constitution: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
val search: String? = null,
|
||||
val sort: String = "createdAt"
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val constitution = ConstitutionRef(constitution)
|
||||
|
||||
fun validate() = Validation<GetConstitutionCommentRequest> {
|
||||
GetConstitutionCommentRequest::page {
|
||||
minimum(1)
|
||||
}
|
||||
GetConstitutionCommentRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
GetConstitutionCommentRequest::sort ifPresent {
|
||||
enum(
|
||||
"votes",
|
||||
"createdAt",
|
||||
)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
fun Route.getConstitutionComment(repo: CommentConstitutionRepository, ac: CommentAccessControl) {
|
||||
get<GetConstitutionCommentRequest> {
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
val comments = repo.findByTarget(it.constitution)
|
||||
ac.assert { canView(comments.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -63,12 +63,14 @@ open class CommentForUpdate<T : TargetI, C : CitizenI>(
|
||||
constructor(
|
||||
createdBy: C,
|
||||
parent: CommentParent<T>,
|
||||
content: String
|
||||
content: String,
|
||||
id: UUID? = null,
|
||||
) : this(
|
||||
createdBy = createdBy,
|
||||
parent = parent,
|
||||
target = parent.target,
|
||||
content = content
|
||||
content = content,
|
||||
id = id ?: UUID.randomUUID(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -22,7 +21,7 @@ abstract class CommentRepositoryAbs<T : TargetI>(override var requester: Request
|
||||
): Paginated<CommentForView<T, CitizenCreatorI>>
|
||||
|
||||
open fun findByParent(
|
||||
parent: CommentForView<T, CitizenCreatorI>,
|
||||
parent: CommentI,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentForView<T, CitizenCreatorI>> {
|
||||
@@ -49,7 +48,7 @@ abstract class CommentRepositoryAbs<T : TargetI>(override var requester: Request
|
||||
target: EntityI,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.CREATED_AT
|
||||
sort: String = "createdAt"
|
||||
): Paginated<CommentForView<T, CitizenCreatorI>> {
|
||||
return findByTarget(target.id, page, limit, sort)
|
||||
}
|
||||
@@ -58,36 +57,30 @@ abstract class CommentRepositoryAbs<T : TargetI>(override var requester: Request
|
||||
targetId: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.CREATED_AT
|
||||
): Paginated<CommentForView<T, CitizenCreatorI>> {
|
||||
return requester.run {
|
||||
getFunction("find_comments_by_target")
|
||||
sort: String = "createdAt"
|
||||
): Paginated<CommentForView<T, CitizenCreatorI>> = requester
|
||||
.getFunction("find_comments_by_target")
|
||||
.select<CommentForView<T, CitizenCreator>>(
|
||||
page,
|
||||
limit,
|
||||
"target_id" to targetId,
|
||||
"sort" to sort.sql
|
||||
)
|
||||
as Paginated<CommentForView<T, CitizenCreatorI>>
|
||||
}
|
||||
}
|
||||
"sort" to sort
|
||||
) as Paginated<CommentForView<T, CitizenCreatorI>>
|
||||
|
||||
fun <I : TargetI, C : CitizenCreatorI> comment(comment: CommentForUpdate<I, C>) {
|
||||
requester
|
||||
fun <I : TargetI, C : CitizenCreatorI> comment(comment: CommentForUpdate<I, C>): CommentForView<TargetRef, CitizenCreator> = requester
|
||||
.getFunction("comment")
|
||||
.sendQuery(
|
||||
.selectOne(
|
||||
"reference" to comment.target.reference,
|
||||
"resource" to comment
|
||||
)
|
||||
}
|
||||
)!!
|
||||
|
||||
fun <I : T> edit(comment: CommentForUpdate<I, CitizenCreatorI>) {
|
||||
requester
|
||||
fun <I : T> edit(comment: CommentForUpdate<I, CitizenCreatorI>): CommentForView<TargetRef, CitizenCreator> {
|
||||
return requester
|
||||
.getFunction("edit_comment")
|
||||
.sendQuery(
|
||||
.selectOne(
|
||||
"id" to comment.id,
|
||||
"content" to comment.content
|
||||
)
|
||||
)!!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.database.CommentForUpdate
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.comment.toOutput
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.post
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object CreateComment {
|
||||
@Location("/comments/{comment}")
|
||||
class CreateCommentRequest(comment: UUID) {
|
||||
val comment = CommentRef(comment)
|
||||
class Input(val content: String) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::content {
|
||||
minLength(20)
|
||||
maxLength(6000)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.createCommentChildren(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
post<CreateCommentRequest> {
|
||||
mustBeAuth()
|
||||
|
||||
call.receiveOrBadRequest<CreateCommentRequest.Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
.run {
|
||||
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
CommentForUpdate(
|
||||
content = content,
|
||||
createdBy = citizen,
|
||||
target = parent.target,
|
||||
parent = parent,
|
||||
)
|
||||
}.let { newComment ->
|
||||
ac.assert { canCreate(newComment, citizenOrNull) }
|
||||
repo.comment(newComment)
|
||||
call.respond(HttpStatusCode.Created, newComment.toOutput())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.database.CommentForUpdate
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.comment.toOutput
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.post
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object CreateCommentChildren {
|
||||
@Location("/comments/{comment}/children")
|
||||
class CreateCommentChildrenRequest(comment: UUID) {
|
||||
val comment = CommentRef(comment)
|
||||
class Input(val content: String)
|
||||
}
|
||||
|
||||
fun Route.createCommentChildren(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
post<CreateCommentChildrenRequest> {
|
||||
mustBeAuth()
|
||||
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
val newComment = CommentForUpdate(
|
||||
content = call.receiveOrBadRequest<CreateCommentChildrenRequest.Input>().content,
|
||||
createdBy = citizen,
|
||||
parent = parent
|
||||
)
|
||||
|
||||
ac.assert { canCreate(newComment, citizenOrNull) }
|
||||
repo.comment(newComment)
|
||||
|
||||
call.respond(HttpStatusCode.Created, newComment.toOutput())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.database.CommentForUpdate
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.comment.toOutput
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -24,22 +28,40 @@ object EditComment {
|
||||
@Location("/comments/{comment}")
|
||||
class EditCommentRequest(comment: UUID) {
|
||||
val comment = CommentRef(comment)
|
||||
class Input(val content: String)
|
||||
class Input(val content: String) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::content {
|
||||
minLength(20)
|
||||
maxLength(6000)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.editComment(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
put<EditCommentRequest> {
|
||||
mustBeAuth()
|
||||
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
ac.assert { canUpdate(comment, citizenOrNull) }
|
||||
|
||||
comment.content = call.receiveOrBadRequest<EditCommentRequest.Input>().content
|
||||
repo.edit(comment)
|
||||
val commentOld = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
|
||||
ac.assert { canUpdate(commentOld, citizenOrNull) }
|
||||
|
||||
call.receiveOrBadRequest<EditCommentRequest.Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
.run {
|
||||
CommentForUpdate(
|
||||
id = commentOld.id,
|
||||
createdBy = commentOld.createdBy,
|
||||
target = commentOld.target,
|
||||
parent = commentOld.parent,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
.let { repo.edit(it) }
|
||||
.let {
|
||||
call.respond(
|
||||
HttpStatusCode.OK,
|
||||
comment.toOutput()
|
||||
it.toOutput()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.comment.toOutput
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
@@ -21,11 +22,13 @@ import java.util.UUID
|
||||
object GetCommentChildren {
|
||||
@Location("/comments/{comment}/children")
|
||||
class CommentChildrenRequest(
|
||||
val comment: UUID,
|
||||
comment: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
val search: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val comment = CommentRef(comment)
|
||||
}
|
||||
|
||||
fun Route.getChildrenComments(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
get<CommentChildrenRequest> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package fr.dcproject.component.comment.generic.routes
|
||||
|
||||
import fr.dcproject.component.comment.generic.routes.CreateCommentChildren.createCommentChildren
|
||||
import fr.dcproject.component.comment.generic.routes.CreateComment.createCommentChildren
|
||||
import fr.dcproject.component.comment.generic.routes.EditComment.editComment
|
||||
import fr.dcproject.component.comment.generic.routes.GetCommentChildren.getChildrenComments
|
||||
import fr.dcproject.component.comment.generic.routes.GetOneComment.getOneComment
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.constitution.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
@@ -15,6 +16,9 @@ import fr.dcproject.component.constitution.database.ConstitutionForUpdate.TitleF
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRepository
|
||||
import fr.dcproject.component.constitution.routes.CreateConstitution.PostConstitutionRequest.Input
|
||||
import fr.dcproject.component.constitution.routes.CreateConstitution.PostConstitutionRequest.Input.Title
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -36,7 +40,6 @@ object CreateConstitution {
|
||||
val draft: Boolean = false,
|
||||
val versionId: UUID = UUID.randomUUID()
|
||||
) {
|
||||
|
||||
class Title(
|
||||
val id: UUID = UUID.randomUUID(),
|
||||
val name: String,
|
||||
@@ -44,10 +47,25 @@ object CreateConstitution {
|
||||
) {
|
||||
class ArticleRef(val id: UUID)
|
||||
}
|
||||
|
||||
fun validate() = Validation<Input> {
|
||||
Input::title {
|
||||
minLength(10)
|
||||
maxLength(80)
|
||||
}
|
||||
Input::titles onEach {
|
||||
Title::name {
|
||||
minLength(10)
|
||||
maxLength(80)
|
||||
}
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNewConstitution(input: Input, citizen: Citizen) = input.run {
|
||||
validate().badRequestIfNotValid()
|
||||
|
||||
ConstitutionForUpdate<CitizenWithUserI, TitleForUpdate<ArticleRef>>(
|
||||
id = UUID.randomUUID(),
|
||||
title = title,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.constitution.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
|
||||
@@ -8,6 +9,10 @@ import fr.dcproject.component.constitution.database.ConstitutionRepository
|
||||
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,10 +32,27 @@ object FindConstitutions {
|
||||
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<FindConstitutionsRequest> {
|
||||
FindConstitutionsRequest::page {
|
||||
minimum(1)
|
||||
}
|
||||
FindConstitutionsRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
FindConstitutionsRequest::sort ifPresent {
|
||||
enum(
|
||||
"title",
|
||||
"createdAt",
|
||||
)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
fun Route.findConstitutions(repo: ConstitutionRepository, ac: ConstitutionAccessControl) {
|
||||
get<FindConstitutionsRequest> {
|
||||
it.validate().badRequestIfNotValid()
|
||||
val constitutions = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
|
||||
ac.assert { canView(constitutions.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package fr.dcproject.component.notification
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
@@ -12,6 +14,10 @@ import fr.dcproject.component.article.database.ArticleForView
|
||||
import org.joda.time.DateTime
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
|
||||
@JsonSubTypes(
|
||||
JsonSubTypes.Type(value = ArticleUpdateNotification::class, name = "article")
|
||||
)
|
||||
open class Notification(
|
||||
val type: String,
|
||||
val createdAt: DateTime = DateTime.now()
|
||||
|
||||
@@ -28,12 +28,12 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class NotificationsPush private constructor(
|
||||
class NotificationsPush(
|
||||
private val redis: RedisAsyncCommands<String, String>,
|
||||
private val redisConnectionPubSub: StatefulRedisPubSubConnection<String, String>,
|
||||
citizen: CitizenI,
|
||||
incoming: Flow<Notification>,
|
||||
onRecieve: suspend (Notification) -> Unit,
|
||||
onReceive: suspend (Notification) -> Unit,
|
||||
) {
|
||||
class Builder(val redisClient: RedisClient) {
|
||||
private val redisConnection = redisClient.connect() ?: error("Unable to connect to redis")
|
||||
@@ -43,8 +43,8 @@ class NotificationsPush private constructor(
|
||||
fun build(
|
||||
citizen: CitizenI,
|
||||
incoming: Flow<Notification>,
|
||||
onRecieve: suspend (Notification) -> Unit,
|
||||
): NotificationsPush = NotificationsPush(redis, redisConnectionPubSub, citizen, incoming, onRecieve)
|
||||
onReceive: suspend (Notification) -> Unit,
|
||||
): NotificationsPush = NotificationsPush(redis, redisConnectionPubSub, citizen, incoming, onReceive)
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
fun build(ws: DefaultWebSocketServerSession): NotificationsPush {
|
||||
@@ -69,7 +69,7 @@ class NotificationsPush private constructor(
|
||||
override fun message(pattern: String?, channel: String?, message: String?) {
|
||||
runBlocking {
|
||||
getNotifications().collect {
|
||||
onRecieve(it)
|
||||
onReceive(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,10 +85,12 @@ class NotificationsPush private constructor(
|
||||
|
||||
/* Get old notification and sent it to websocket */
|
||||
runBlocking {
|
||||
getNotifications().collect { onRecieve(it) }
|
||||
getNotifications().collect {
|
||||
onReceive(it)
|
||||
}
|
||||
}
|
||||
|
||||
/* Lisen redis event, and sent the new notification into websocket */
|
||||
/* Listen redis event, and sent the new notification into websocket */
|
||||
redisConnectionPubSub.run {
|
||||
addListener(listener)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.opinion.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
@@ -12,6 +13,9 @@ import fr.dcproject.component.opinion.database.Opinion
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import io.konform.validation.Validation
|
||||
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
|
||||
@@ -34,11 +38,22 @@ object GetMyOpinionsArticle {
|
||||
limit: Int = 50
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
fun validate() = Validation<CitizenOpinionsArticleRequest> {
|
||||
CitizenOpinionsArticleRequest::page {
|
||||
minimum(1)
|
||||
}
|
||||
CitizenOpinionsArticleRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
|
||||
get<CitizenOpinionsArticleRequest> {
|
||||
mustBeAuth()
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
val opinions: Paginated<Opinion<TargetRef>> = repo.findCitizenOpinions(citizen, it.page, it.limit)
|
||||
ac.assert { canView(opinions.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.vote.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
|
||||
@@ -9,6 +10,9 @@ import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteArticleRepository
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import io.konform.validation.Validation
|
||||
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
|
||||
@@ -28,11 +32,22 @@ object GetCitizenVotesOnArticle {
|
||||
val search: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
fun validate() = Validation<CitizenVoteArticleRequest> {
|
||||
CitizenVoteArticleRequest::page {
|
||||
minimum(1)
|
||||
}
|
||||
CitizenVoteArticleRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) {
|
||||
get<CitizenVoteArticleRequest> {
|
||||
mustBeAuth()
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
val votes = repo.findByCitizen(it.citizen, it.page, it.limit)
|
||||
ac.assert { canView(votes.result, citizenOrNull) }
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
@@ -10,6 +11,9 @@ import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteArticleRepository
|
||||
import fr.dcproject.component.vote.database.VoteForUpdate
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -25,13 +29,22 @@ object PutVoteOnArticle {
|
||||
@Location("/articles/{article}/vote")
|
||||
class ArticleVoteRequest(article: UUID) {
|
||||
val article = ArticleRef(article)
|
||||
data class Input(var note: Int)
|
||||
data class Input(var note: Int) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::note {
|
||||
minimum(-1)
|
||||
maximum(1)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl, articleRepo: ArticleRepository) {
|
||||
put<ArticleVoteRequest> {
|
||||
mustBeAuth()
|
||||
|
||||
val input = call.receiveOrBadRequest<ArticleVoteRequest.Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
val article = articleRepo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found")
|
||||
val vote = VoteForUpdate(
|
||||
target = article,
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteCommentRepository
|
||||
import fr.dcproject.component.vote.database.VoteForUpdate
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -21,18 +27,29 @@ import java.util.UUID
|
||||
@KtorExperimentalLocationsAPI
|
||||
object PutVoteOnComment {
|
||||
@Location("/comments/{comment}/vote")
|
||||
class CommentVoteRequest(val comment: UUID) {
|
||||
data class Content(var note: Int)
|
||||
class CommentVoteRequest(comment: UUID) {
|
||||
val comment = CommentRef(comment)
|
||||
data class Input(var note: Int) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::note {
|
||||
minimum(-1)
|
||||
maximum(1)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.putVoteOnComment(voteCommentRepo: VoteCommentRepository, commentRepo: CommentRepository, ac: VoteAccessControl) {
|
||||
put<CommentVoteRequest> {
|
||||
mustBeAuth()
|
||||
val comment = commentRepo.findById(it.comment)!!
|
||||
val content = call.receiveOrBadRequest<CommentVoteRequest.Content>()
|
||||
|
||||
val comment = commentRepo.findById(it.comment.id) ?: throw NotFoundException("Comment ${it.comment.id} not found")
|
||||
val input = call.receiveOrBadRequest<CommentVoteRequest.Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
|
||||
val vote = VoteForUpdate(
|
||||
target = comment,
|
||||
note = content.note,
|
||||
note = input.note,
|
||||
createdBy = this.citizen
|
||||
)
|
||||
ac.assert { canCreate(vote, citizenOrNull) }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
@@ -11,6 +12,9 @@ import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteConstitutionRepository
|
||||
import fr.dcproject.component.vote.database.VoteForUpdate
|
||||
import fr.dcproject.component.vote.routes.PutVoteOnConstitution.ConstitutionVoteRequest.Input
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -26,17 +30,25 @@ object PutVoteOnConstitution {
|
||||
@Location("/constitutions/{constitution}/vote")
|
||||
class ConstitutionVoteRequest(constitution: UUID) {
|
||||
val constitution = ConstitutionRef(constitution)
|
||||
data class Input(var note: Int)
|
||||
data class Input(var note: Int) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::note {
|
||||
minimum(-1)
|
||||
maximum(1)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.voteConstitution(repo: VoteConstitutionRepository, ac: VoteAccessControl, constitutionRepo: ConstitutionRepository) {
|
||||
put<ConstitutionVoteRequest> {
|
||||
mustBeAuth()
|
||||
val constitution = constitutionRepo.findById(it.constitution.id) ?: throw NotFoundException("Unable to find constitution ${it.constitution.id}")
|
||||
val content = call.receiveOrBadRequest<Input>()
|
||||
val input = call.receiveOrBadRequest<Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
val vote = VoteForUpdate(
|
||||
target = constitution,
|
||||
note = content.note,
|
||||
note = input.note,
|
||||
createdBy = this.citizen
|
||||
)
|
||||
ac.assert { canCreate(vote, citizenOrNull) }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.common.validation.isUrl
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
@@ -10,6 +11,9 @@ import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupForUpdate
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.routes.CreateWorkgroup.PostWorkgroupRequest.Input
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -29,13 +33,30 @@ object CreateWorkgroup {
|
||||
val description: String,
|
||||
val logo: String?,
|
||||
val anonymous: Boolean?
|
||||
)
|
||||
) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::name {
|
||||
minLength(5)
|
||||
maxLength(80)
|
||||
}
|
||||
Input::description {
|
||||
minLength(50)
|
||||
maxLength(6000)
|
||||
}
|
||||
Input::logo ifPresent {
|
||||
isUrl()
|
||||
maxLength(2048)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.createWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
post<PostWorkgroupRequest> {
|
||||
mustBeAuth()
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
validate().badRequestIfNotValid()
|
||||
|
||||
WorkgroupForUpdate(
|
||||
id ?: UUID.randomUUID(),
|
||||
name,
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.common.validation.isUrl
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupForUpdate
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.routes.EditWorkgroup.PutWorkgroupRequest.Input
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -27,7 +32,22 @@ object EditWorkgroup {
|
||||
val description: String?,
|
||||
val logo: String?,
|
||||
val anonymous: Boolean?
|
||||
)
|
||||
) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::name ifPresent {
|
||||
minLength(5)
|
||||
maxLength(80)
|
||||
}
|
||||
Input::description ifPresent {
|
||||
minLength(50)
|
||||
maxLength(6000)
|
||||
}
|
||||
Input::logo ifPresent {
|
||||
isUrl()
|
||||
maxLength(2048)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.editWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
@@ -35,6 +55,7 @@ object EditWorkgroup {
|
||||
mustBeAuth()
|
||||
repo.findById(it.workgroupId)?.let { old ->
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
validate().badRequestIfNotValid()
|
||||
WorkgroupForUpdate(
|
||||
id = old.id,
|
||||
name = name ?: old.name,
|
||||
|
||||
@@ -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,16 +35,33 @@ object GetWorkgroups {
|
||||
val search: String? = null,
|
||||
val createdBy: String? = null,
|
||||
members: List<String?>? = 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<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) {
|
||||
get<WorkgroupsRequest> {
|
||||
val workgroups =
|
||||
repo.find(
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
val workgroups = repo.find(
|
||||
it.page,
|
||||
it.limit,
|
||||
it.sort,
|
||||
|
||||
@@ -41,6 +41,12 @@ paths:
|
||||
maxItems: 50
|
||||
items:
|
||||
$ref: '#/components/schemas/ArticleListingResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
post:
|
||||
security:
|
||||
- JWTAuth: []
|
||||
@@ -65,16 +71,21 @@ paths:
|
||||
Limit power of press
|
||||
content:
|
||||
type: string
|
||||
minLength: 50
|
||||
maxLength: 6000
|
||||
example:
|
||||
Lorem upsum...
|
||||
description:
|
||||
type: string
|
||||
minLength: 50
|
||||
maxLength: 6000
|
||||
example:
|
||||
I think is the bether choice
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
maxItems: 15
|
||||
default: [ ]
|
||||
example: [ power, press ]
|
||||
anonymous:
|
||||
@@ -106,6 +117,12 @@ paths:
|
||||
format: uuid
|
||||
versionNumber:
|
||||
type: integer
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
403:
|
||||
@@ -128,6 +145,12 @@ paths:
|
||||
tags:
|
||||
- article
|
||||
operationId: getArticle
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page'
|
||||
- $ref: '#/components/parameters/limit'
|
||||
- $ref: '#/components/parameters/sort'
|
||||
- $ref: '#/components/parameters/direction'
|
||||
- $ref: '#/components/parameters/search'
|
||||
responses:
|
||||
200:
|
||||
description: The Article objects
|
||||
@@ -135,6 +158,19 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ArticleResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
404:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/404'
|
||||
|
||||
/articles/{article}/versions:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/article'
|
||||
@@ -143,6 +179,12 @@ paths:
|
||||
tags:
|
||||
- article
|
||||
operationId: getArticleVersions
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page'
|
||||
- $ref: '#/components/parameters/limit'
|
||||
- $ref: '#/components/parameters/sort'
|
||||
- $ref: '#/components/parameters/direction'
|
||||
- $ref: '#/components/parameters/search'
|
||||
responses:
|
||||
200:
|
||||
description: The versions of Article
|
||||
@@ -193,6 +235,12 @@ paths:
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
|
||||
/login:
|
||||
post:
|
||||
@@ -310,7 +358,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: sdf
|
||||
$ref: '#/components/schemas/400'
|
||||
/auth/passwordless:
|
||||
post:
|
||||
summary: Send a connexion link by email
|
||||
@@ -354,7 +402,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:
|
||||
@@ -371,6 +419,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:
|
||||
@@ -443,6 +497,10 @@ paths:
|
||||
description: Password changed
|
||||
400:
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
404:
|
||||
@@ -464,13 +522,13 @@ paths:
|
||||
in: query
|
||||
required: false
|
||||
example:
|
||||
- created_at
|
||||
- createdAt
|
||||
- votes
|
||||
schema:
|
||||
type: string
|
||||
default: created_at
|
||||
default: createdAt
|
||||
enum:
|
||||
- created_at
|
||||
- createdAt
|
||||
- votes
|
||||
responses:
|
||||
200:
|
||||
@@ -486,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: [ ]
|
||||
@@ -503,8 +567,10 @@ paths:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
minLength: 20
|
||||
maxLength: 6000
|
||||
example:
|
||||
Lorem ipsum...
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.Lorem ipsum...
|
||||
responses:
|
||||
201:
|
||||
description: Return created Comment
|
||||
@@ -512,6 +578,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CommentResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
/comments/{comment}:
|
||||
@@ -528,6 +600,42 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CommentResponse'
|
||||
post:
|
||||
security:
|
||||
- JWTAuth: []
|
||||
summary: create comment
|
||||
tags:
|
||||
- comment
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
required:
|
||||
- content
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
minLength: 20
|
||||
maxLength: 6000
|
||||
example:
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
responses:
|
||||
201:
|
||||
description: Return updated comment
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CommentResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
404:
|
||||
description: No comment found
|
||||
put:
|
||||
security:
|
||||
- JWTAuth: []
|
||||
@@ -543,8 +651,10 @@ paths:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
minLength: 20
|
||||
maxLength: 6000
|
||||
example:
|
||||
Lorem ipsum...
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
responses:
|
||||
200:
|
||||
description: Return updated comment
|
||||
@@ -552,6 +662,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CommentResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
/comments/{comment}/children:
|
||||
@@ -637,13 +753,42 @@ paths:
|
||||
tags:
|
||||
- comment
|
||||
- constitution
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page'
|
||||
- $ref: '#/components/parameters/limit'
|
||||
- $ref: '#/components/parameters/search'
|
||||
- name: sort
|
||||
in: query
|
||||
required: false
|
||||
example:
|
||||
- createdAt
|
||||
- votes
|
||||
schema:
|
||||
type: string
|
||||
default: createdAt
|
||||
enum:
|
||||
- createdAt
|
||||
- votes
|
||||
responses:
|
||||
200:
|
||||
description: Return Comment and children
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Paginated'
|
||||
- type: object
|
||||
properties:
|
||||
result:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/CommentResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
post:
|
||||
security:
|
||||
- JWTAuth: []
|
||||
@@ -660,8 +805,10 @@ paths:
|
||||
properties:
|
||||
content:
|
||||
type: string
|
||||
minLength: 20
|
||||
maxLength: 6000
|
||||
example:
|
||||
Lorem ipsum...
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
responses:
|
||||
201:
|
||||
description: Return created comment
|
||||
@@ -669,6 +816,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CommentResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
|
||||
@@ -698,6 +851,12 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ConstitutionListingResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
post:
|
||||
security:
|
||||
- JWTAuth: [ ]
|
||||
@@ -722,7 +881,11 @@ paths:
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
400:
|
||||
$ref: '#/components/responses/400'
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
/constitutions/{constitution}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/constitution'
|
||||
@@ -955,6 +1118,9 @@ paths:
|
||||
tags:
|
||||
- opinion
|
||||
- citizen
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page'
|
||||
- $ref: '#/components/parameters/limit'
|
||||
responses:
|
||||
200:
|
||||
description: Opinions
|
||||
@@ -969,6 +1135,13 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Opinion'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
|
||||
/articles/{article}/opinions:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/article'
|
||||
@@ -1021,6 +1194,12 @@ paths:
|
||||
responses:
|
||||
201:
|
||||
description: Return only http status 201 on success
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
/citizens/{citizen}/votes:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/citizen'
|
||||
@@ -1079,6 +1258,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VoteAggregation'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
/citizens/{citizen}/votes/articles:
|
||||
@@ -1092,6 +1277,9 @@ paths:
|
||||
- vote
|
||||
- article
|
||||
- citizen
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page'
|
||||
- $ref: '#/components/parameters/limit'
|
||||
responses:
|
||||
200:
|
||||
description: Votes
|
||||
@@ -1106,6 +1294,12 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VoteResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
/articles/{article}/vote:
|
||||
@@ -1130,6 +1324,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VoteAggregation'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
|
||||
@@ -1159,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:
|
||||
@@ -1197,6 +1403,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Workgroup'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
/workgroups/{workgroup}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/workgroup'
|
||||
@@ -1245,6 +1457,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Workgroup'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
delete:
|
||||
summary: Delete one workgroup
|
||||
security:
|
||||
@@ -1373,6 +1591,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
|
||||
@@ -1855,6 +2084,8 @@ components:
|
||||
$ref: '#/components/schemas/UUID'
|
||||
title:
|
||||
type: string
|
||||
minLength: 10
|
||||
maxLength: 80
|
||||
example:
|
||||
Constitution for the liberty
|
||||
titles:
|
||||
@@ -1870,6 +2101,8 @@ components:
|
||||
$ref: '#/components/schemas/UUID'
|
||||
name:
|
||||
type: string
|
||||
minLength: 10
|
||||
maxLength: 80
|
||||
example:
|
||||
The liberties
|
||||
articles:
|
||||
@@ -2209,6 +2442,47 @@ components:
|
||||
- REPORTER
|
||||
example: MASTER
|
||||
|
||||
400:
|
||||
description: Bad Request
|
||||
required:
|
||||
- title
|
||||
- invalidParams
|
||||
additionalProperties: false
|
||||
properties:
|
||||
statusCode:
|
||||
type: integer
|
||||
example: 400
|
||||
title:
|
||||
type: string
|
||||
example: Bad Request
|
||||
invalidParams:
|
||||
type: array
|
||||
items:
|
||||
required:
|
||||
- name
|
||||
- reason
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: '.title'
|
||||
reason:
|
||||
type: string
|
||||
example: 'Cannot be null'
|
||||
|
||||
404:
|
||||
description: Not Found
|
||||
required:
|
||||
- title
|
||||
- statusCode
|
||||
additionalProperties: false
|
||||
properties:
|
||||
statusCode:
|
||||
type: integer
|
||||
example: 404
|
||||
title:
|
||||
type: string
|
||||
example: Bad Request
|
||||
|
||||
securitySchemes:
|
||||
JWTAuth:
|
||||
type: http
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
create or replace function comment(reference regclass, resource json, out _id uuid)
|
||||
create or replace function comment(reference regclass, inout resource json)
|
||||
language plpgsql as
|
||||
$$
|
||||
declare
|
||||
@@ -17,7 +17,8 @@ begin
|
||||
else
|
||||
raise exception 'comment with target as "%", is not implemented', reference::text;
|
||||
end if;
|
||||
_id = _new_id;
|
||||
|
||||
select find_comment_by_id(_new_id) into resource;
|
||||
end;
|
||||
$$;
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
create or replace function edit_comment(_id uuid, _content text) returns void
|
||||
create or replace function edit_comment(_id uuid, _content text, out resource json)
|
||||
language plpgsql as
|
||||
$$
|
||||
begin
|
||||
update comment c set
|
||||
"content" = _content
|
||||
where c.id = _id;
|
||||
|
||||
select find_comment_by_id(_id) into resource;
|
||||
end;
|
||||
$$;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,14 +22,14 @@ begin
|
||||
case direction when 'asc' then
|
||||
case sort
|
||||
when 'title' then c.title
|
||||
when 'created_at' then c.created_at::text
|
||||
when 'createdAt' then c.created_at::text
|
||||
else null
|
||||
end
|
||||
end,
|
||||
case direction when 'desc' then
|
||||
case sort
|
||||
when 'title' then c.title
|
||||
when 'created_at' then c.created_at::text
|
||||
when 'createdAt' then c.created_at::text
|
||||
end
|
||||
end
|
||||
desc,
|
||||
|
||||
@@ -21,7 +21,7 @@ begin
|
||||
f.created_at,
|
||||
f.target_reference,
|
||||
json_build_object('id', f.target_id) as target,
|
||||
json_build_object('id', f.created_by_id) as created_by
|
||||
find_citizen_by_id_with_user(f.created_by_id) as created_by
|
||||
from follow_article as f
|
||||
join article a on f.target_id = a.id
|
||||
where a.version_id = _version_id
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -30,7 +30,7 @@ internal class NotificationsPushTest {
|
||||
@BeforeAll
|
||||
@JvmStatic
|
||||
fun before() {
|
||||
val config: Configuration = Configuration("application-test.conf")
|
||||
val config = Configuration("application-test.conf")
|
||||
RedisClient.create(config.redis).connect().sync().flushall()
|
||||
|
||||
/* Purge rabbit notification queues */
|
||||
@@ -45,7 +45,7 @@ internal class NotificationsPushTest {
|
||||
|
||||
@Test
|
||||
fun `Notification from redis is well catch and return`() = runBlocking {
|
||||
val config: Configuration = Configuration("application-test.conf")
|
||||
val config = Configuration("application-test.conf")
|
||||
/* Redis client for test */
|
||||
val redisClientTest = RedisClient.create(config.redis)
|
||||
|
||||
@@ -74,7 +74,7 @@ internal class NotificationsPushTest {
|
||||
}
|
||||
val notifAfterSubscribe = ArticleUpdateNotification(article)
|
||||
|
||||
/* init event for emulate incomint message from websocket */
|
||||
/* init event for emulate incoming message from websocket */
|
||||
val event = MutableSharedFlow<Notification>()
|
||||
val incomingFlow = event.asSharedFlow()
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package functional
|
||||
|
||||
import fr.dcproject.application.Env.TEST
|
||||
import fr.dcproject.application.module
|
||||
import fr.dcproject.common.utils.retry
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.article.database.ArticleViewRepository
|
||||
import fr.dcproject.component.auth.database.UserCreator
|
||||
@@ -20,6 +21,8 @@ import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||
import org.koin.ktor.ext.get
|
||||
import java.util.UUID
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.seconds
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@KtorExperimentalAPI
|
||||
@@ -27,6 +30,7 @@ import java.util.UUID
|
||||
@TestInstance(PER_CLASS)
|
||||
@Tags(Tag("functional"), Tag("view"))
|
||||
class ViewTest {
|
||||
@ExperimentalTime
|
||||
@Test
|
||||
fun `test View Article`() {
|
||||
val article = ArticleForView(
|
||||
@@ -75,9 +79,8 @@ class ViewTest {
|
||||
article
|
||||
)
|
||||
|
||||
/* Sleep because ES is not sync ! */
|
||||
Thread.sleep(1000)
|
||||
|
||||
/* Retry because ES is not sync ! */
|
||||
retry(10, 0.3.seconds) {
|
||||
/* Get view */
|
||||
val afterView = viewRepository.getViewsCount(article)
|
||||
|
||||
@@ -87,3 +90,4 @@ class ViewTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import integration.steps.`when`.Validate
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
@@ -16,9 +18,11 @@ import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`And the response should not contain`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.`whish contains`
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.then.and
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Forbidden
|
||||
import io.ktor.http.HttpStatusCode.Companion.NotFound
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
@@ -32,13 +36,24 @@ class `Article routes` : BaseTest() {
|
||||
fun `I can get article list`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have articles`(3)
|
||||
`When I send a GET request`("/articles") `Then the response should be` OK and {
|
||||
`Given I have article`(createdBy = "ddb17f17-e8ab-4ada-bdf7-bfd6b0f1b5ed".toUUID())
|
||||
`When I send a GET request`("/articles?page=1&limit=10&sort=title&createdBy=ddb17f17-e8ab-4ada-bdf7-bfd6b0f1b5ed") `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain pattern`("$.result[0].createdBy.name.firstName", "firstName.+")
|
||||
`And the response should contain pattern`("$.result[1].createdBy.name.firstName", "firstName.+")
|
||||
`And the response should contain pattern`("$.result[2].createdBy.name.firstName", "firstName.+")
|
||||
`And the response should not contain`("$.result[3]")
|
||||
`And the response should contain list`("$.result", 3)
|
||||
`And the response should not contain`("$.result[1]")
|
||||
`And the response should contain list`("$.result", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get article list`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have articles`(3)
|
||||
`When I send a GET request`("/articles?page=1&limit=10&sort=title&createdBy=hello", Validate.ALL - Validate.REQUEST_PARAM) `Then the response should be` BadRequest and {
|
||||
`And the response should contain`("$.invalidParams[*].name", ".createdBy")
|
||||
`And the response should contain`("$.invalidParams[*].reason", "must be UUID")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,8 +66,8 @@ class `Article routes` : BaseTest() {
|
||||
`Given I have article created by workgroup`("2bccd5a7-9082-4b31-88f8-e25d70b22b12")
|
||||
`When I send a GET request`("/articles?workgroup=2bccd5a7-9082-4b31-88f8-e25d70b22b12") `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.total") `whish contains` 1
|
||||
`And have property`("$.result[0]workgroup.name") `whish contains` "Les papy"
|
||||
`And have property`("$.total") `which contains` 1
|
||||
`And have property`("$.result[0]workgroup.name") `which contains` "Les papy"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +78,31 @@ class `Article routes` : BaseTest() {
|
||||
`Given I have article`(id = "65cda9f3-8991-4420-8d41-1da9da72c9bb")
|
||||
`When I send a GET request`("/articles/65cda9f3-8991-4420-8d41-1da9da72c9bb") `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.id") `whish contains` "65cda9f3-8991-4420-8d41-1da9da72c9bb"
|
||||
`And have property`("$.id") `which contains` "65cda9f3-8991-4420-8d41-1da9da72c9bb"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I cannot get article with id doesn't exist`() {
|
||||
withIntegrationApplication {
|
||||
`When I send a GET request`("/articles/635fe2e8-2dbc-4c80-b306-101d38a4ab23") `Then the response should be` NotFound and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.title", "Article 635fe2e8-2dbc-4c80-b306-101d38a4ab23 not found")
|
||||
`And the response should contain`("$.statusCode", 404)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get article by id with wrong id format`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have article`(id = "65cda9f3-8991-4420-8d41-1da9da72c9bb")
|
||||
`When I send a GET request`("/articles/abcd") `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", "ID")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must be UUID")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,10 +111,36 @@ class `Article routes` : BaseTest() {
|
||||
fun `I can get versions of article by the id`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have article`(id = "13e6091c-8fed-4600-b079-a97a6b7a9800")
|
||||
`When I send a GET request`("/articles/13e6091c-8fed-4600-b079-a97a6b7a9800/versions") `Then the response should be` OK and {
|
||||
`When I send a GET request`("/articles/13e6091c-8fed-4600-b079-a97a6b7a9800/versions?page=1&limit=10&sort=title") `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.total") `whish contains` 1
|
||||
`And have property`("$.result[0].id") `whish contains` "13e6091c-8fed-4600-b079-a97a6b7a9800"
|
||||
`And have property`("$.total") `which contains` 1
|
||||
`And have property`("$.result[0].id") `which contains` "13e6091c-8fed-4600-b079-a97a6b7a9800"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get versions of article by the id with wrong id`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have article`(id = "13e6091c-8fed-4600-b079-a97a6b7a9800")
|
||||
`When I send a GET request`("/articles/abcd/versions") `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".article")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must be UUID")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get versions of article by the id with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have article`(id = "13e6091c-8fed-4600-b079-a97a6b7a9800")
|
||||
`When I send a GET request`("/articles/13e6091c-8fed-4600-b079-a97a6b7a9800/versions?page=1&limit=10&sort=wrong") `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 pattern`("$.invalidParams[0].reason", "must be one of: ('[^']+'(, )?)+")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,8 +157,8 @@ class `Article routes` : BaseTest() {
|
||||
"versionId": "09c418b6-63ba-448b-b38b-502b41cd500e",
|
||||
"title": "title2",
|
||||
"anonymous": false,
|
||||
"content": "content2",
|
||||
"description": "description2",
|
||||
"content": "Sed malesuada ante et sem congue, scelerisque feugiat lorem viverra.",
|
||||
"description": "Sed vulputate, ligula id porta posuere, sapien lorem mattis arcu, sit amet luctus erat orci sed tellus.",
|
||||
"tags": [
|
||||
"green"
|
||||
]
|
||||
@@ -102,12 +167,13 @@ class `Article routes` : BaseTest() {
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.versionId") `whish contains` "09c418b6-63ba-448b-b38b-502b41cd500e"
|
||||
`And have property`("$.versionId") `which contains` "09c418b6-63ba-448b-b38b-502b41cd500e"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("Forbidden")
|
||||
fun `I cannot create an article if I'm not connected`() {
|
||||
withIntegrationApplication {
|
||||
`When I send a POST request`("/articles") {
|
||||
@@ -117,8 +183,8 @@ class `Article routes` : BaseTest() {
|
||||
"versionId": "e3c7ce42-241c-4caf-9a59-aba4e466440e",
|
||||
"title": "title2",
|
||||
"anonymous": false,
|
||||
"content": "content2",
|
||||
"description": "description2",
|
||||
"content": "Sed malesuada ante et sem congue, scelerisque feugiat lorem viverra.",
|
||||
"description": "Sed vulputate, ligula id porta posuere, sapien lorem mattis arcu, sit amet luctus erat orci sed tellus.",
|
||||
"tags": [
|
||||
"green"
|
||||
]
|
||||
@@ -132,4 +198,35 @@ class `Article routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot create an article with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("John", "Doe")
|
||||
`When I send a POST request`("/articles", Validate.NONE) {
|
||||
`authenticated as`("John", "Doe")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"versionId": "09c418b6-63ba-448b-b38b-502b41cd500e",
|
||||
"title": "title2",
|
||||
"anonymous": false,
|
||||
"content": "content2",
|
||||
"description": "description2",
|
||||
"tags": [
|
||||
"green"
|
||||
]
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".content")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 50 characters")
|
||||
`And the response should contain`("$.invalidParams[1].name", ".description")
|
||||
`And the response should contain`("$.invalidParams[1].reason", "must have at least 50 characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And have property`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.`whish contains`
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.then.and
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
@@ -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 {
|
||||
@@ -42,7 +55,7 @@ class `Citizen routes` : BaseTest() {
|
||||
`authenticated as`("Linus", "Pauling")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.id") `whish contains` "47a05c0f-7329-46c3-a7d0-325db37e9114"
|
||||
`And have property`("$.id") `which contains` "47a05c0f-7329-46c3-a7d0-325db37e9114"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +68,7 @@ class `Citizen routes` : BaseTest() {
|
||||
`authenticated as`("Henri", "Becquerel")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.id") `whish contains` "47356809-c8ef-4649-8b99-1c5cb9886d38"
|
||||
`And have property`("$.id") `which contains` "47356809-c8ef-4649-8b99-1c5cb9886d38"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,8 +82,8 @@ class `Citizen routes` : BaseTest() {
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"oldPassword": "azerty",
|
||||
"newPassword": "qwerty"
|
||||
"oldPassword": "Azerty123!",
|
||||
"newPassword": "Qwerty123!"
|
||||
}
|
||||
"""
|
||||
)
|
||||
@@ -79,6 +92,7 @@ class `Citizen routes` : BaseTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot change my password if request is bad formatted`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Louis", "Breguet", id = "6cf2a19d-d15d-4ee5-b2a9-907afd26b525")
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
@@ -13,6 +15,7 @@ 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.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
@@ -33,14 +36,37 @@ class `Comment articles routes` : BaseTest() {
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "Hello mister"
|
||||
"content": "Hello mister MARABOUTCHA"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.target.id", "aa16c635-28da-46f0-9a89-934eef88c7ca")
|
||||
`And the response should contain`("$.content", "Hello mister")
|
||||
`And the response should contain`("$.content", "Hello mister MARABOUTCHA")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot comment article with bad request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Michael", "Faraday")
|
||||
`Given I have article`(id = "aa16c635-28da-46f0-9a89-934eef88c7ca")
|
||||
`When I send a POST request`("/articles/aa16c635-28da-46f0-9a89-934eef88c7ca/comments", ALL - REQUEST_BODY) {
|
||||
`authenticated as`("Michael", "Faraday")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "To small content"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".content")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 20 characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +78,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`()
|
||||
@@ -61,6 +87,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`() {
|
||||
@@ -93,45 +136,4 @@ class `Comment articles routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can edit comment`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Hubert", "Reeves")
|
||||
`Given I have article`(id = "bb05e4a3-55a1-4088-85e7-8d8c23be29b1")
|
||||
`Given I have comment on article`(article = "bb05e4a3-55a1-4088-85e7-8d8c23be29b1", createdBy = Name("Hubert", "Reeves"), id = "fd30d20f-656c-42c6-8955-f61c04537464")
|
||||
`When I send a PUT request`("/comments/fd30d20f-656c-42c6-8955-f61c04537464") {
|
||||
`authenticated as`("Hubert", "Reeves")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "Hello boy"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.content", "Hello boy")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get comment by its ID`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Alfred", "Kastler")
|
||||
`Given I have article`(id = "3897465b-19d2-43a0-86ea-1e29dbb11ec9")
|
||||
`Given I have comment on article`(
|
||||
article = "3897465b-19d2-43a0-86ea-1e29dbb11ec9",
|
||||
createdBy = Name("Alfred", "Kastler"),
|
||||
id = "edd296a8-fc7a-4717-a2bb-9f035ceca3c2",
|
||||
content = "Hello boy"
|
||||
)
|
||||
`When I send a GET request`("/comments/edd296a8-fc7a-4717-a2bb-9f035ceca3c2") {
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.content", "Hello boy")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.`when`.Validate
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
@@ -13,6 +16,7 @@ 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.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
@@ -33,12 +37,69 @@ class `Comment constitutions routes` : BaseTest() {
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "Hello mister"
|
||||
"content": "Hello mister MARABOUTCHA"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.target.id", "1707c287-a472-4a62-89f2-9e85030e915c")
|
||||
`And the response should contain`("$.content", "Hello mister MARABOUTCHA")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot comment constitution with bad request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Nicolas", "Copernic")
|
||||
`Given I have constitution`(id = "aa16c635-28da-46f0-9a89-934eef88c7ca")
|
||||
`When I send a POST request`("/constitutions/aa16c635-28da-46f0-9a89-934eef88c7ca/comments", ALL - REQUEST_BODY) {
|
||||
`authenticated as`("Nicolas", "Copernic")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "To small content"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".content")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 20 characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get all comment on constitution`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Enrico", "Fermi")
|
||||
`Given I have constitution`(id = "6166c078-ca97-4366-b0aa-2a5cd558c78a")
|
||||
`Given I have comment on constitution`(constitution = "6166c078-ca97-4366-b0aa-2a5cd558c78a", createdBy = Name("Enrico", "Fermi"))
|
||||
`When I send a GET request`("/constitutions/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`()
|
||||
`And the response should contain`("$.result[0].target.id", "6166c078-ca97-4366-b0aa-2a5cd558c78a")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get all comment on constitution with wrong parameters`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Enrico", "Fermi")
|
||||
`Given I have constitution`(id = "6166c078-ca97-4366-b0aa-2a5cd558c78a")
|
||||
`Given I have comment on constitution`(constitution = "6166c078-ca97-4366-b0aa-2a5cd558c78a", createdBy = Name("Enrico", "Fermi"))
|
||||
`When I send a GET request`("/constitutions/6166c078-ca97-4366-b0aa-2a5cd558c78a/comments?page=1&limit=40&sort=wrong", ALL - Validate.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'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
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`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have comment on article`
|
||||
import integration.steps.given.`Given I have comment on comment`
|
||||
import integration.steps.given.`authenticated as`
|
||||
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.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
@@ -30,4 +40,126 @@ class `Comment routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can create comment`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Hubert", "Reeves")
|
||||
`Given I have comment on comment`(id = "49933147-fc0f-4e5c-aa8d-f77fa0d88fa6")
|
||||
`When I send a POST request`("/comments/49933147-fc0f-4e5c-aa8d-f77fa0d88fa6") {
|
||||
`authenticated as`("Hubert", "Reeves")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.content", "Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot create comment with bad request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Hubert", "Reeves")
|
||||
`Given I have comment on comment`(id = "49933147-fc0f-4e5c-aa8d-f77fa0d88fa6")
|
||||
`When I send a POST request`("/comments/49933147-fc0f-4e5c-aa8d-f77fa0d88fa6", ALL - REQUEST_BODY) {
|
||||
`authenticated as`("Hubert", "Reeves")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "small content"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".content")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 20 characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can edit comment`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Hubert", "Reeves")
|
||||
`Given I have article`(id = "bb05e4a3-55a1-4088-85e7-8d8c23be29b1")
|
||||
`Given I have comment on article`(
|
||||
article = "bb05e4a3-55a1-4088-85e7-8d8c23be29b1",
|
||||
createdBy = CitizenI.Name(
|
||||
"Hubert",
|
||||
"Reeves"
|
||||
),
|
||||
id = "fd30d20f-656c-42c6-8955-f61c04537464"
|
||||
)
|
||||
`When I send a PUT request`("/comments/fd30d20f-656c-42c6-8955-f61c04537464") {
|
||||
`authenticated as`("Hubert", "Reeves")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.content", "Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I cannot edit comment with bad request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Hubert", "Reeves")
|
||||
`Given I have article`(id = "bb05e4a3-55a1-4088-85e7-8d8c23be29b1")
|
||||
`Given I have comment on article`(
|
||||
article = "bb05e4a3-55a1-4088-85e7-8d8c23be29b1",
|
||||
createdBy = CitizenI.Name(
|
||||
"Hubert",
|
||||
"Reeves"
|
||||
),
|
||||
id = "fd30d20f-656c-42c6-8955-f61c04537464"
|
||||
)
|
||||
`When I send a PUT request`("/comments/fd30d20f-656c-42c6-8955-f61c04537464", ALL - REQUEST_BODY) {
|
||||
`authenticated as`("Hubert", "Reeves")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"content": "small content"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".content")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 20 characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get comment by its ID`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Alfred", "Kastler")
|
||||
`Given I have article`(id = "3897465b-19d2-43a0-86ea-1e29dbb11ec9")
|
||||
`Given I have comment on article`(
|
||||
article = "3897465b-19d2-43a0-86ea-1e29dbb11ec9",
|
||||
createdBy = CitizenI.Name("Alfred", "Kastler"),
|
||||
id = "edd296a8-fc7a-4717-a2bb-9f035ceca3c2",
|
||||
content = "Hello boy"
|
||||
)
|
||||
`When I send a GET request`("/comments/edd296a8-fc7a-4717-a2bb-9f035ceca3c2") {
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.content", "Hello boy")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package integration
|
||||
|
||||
import integration.steps.`when`.Validate
|
||||
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`.`with body`
|
||||
@@ -9,9 +11,10 @@ import integration.steps.given.`Given I have constitution`
|
||||
import integration.steps.given.`Given I have constitutions`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And have property`
|
||||
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.`whish contains`
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.then.and
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
@@ -28,12 +31,25 @@ class `Constitution routes` : BaseTest() {
|
||||
fun `I can get constitution list`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have constitutions`(3)
|
||||
`When I send a GET request`("/constitutions") `Then the response should be` OK and {
|
||||
`When I send a GET request`("/constitutions?page=1&limit=10&sort=title&direction=desc") `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get constitution list with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have constitutions`(3)
|
||||
`When I send a GET request`("/constitutions?page=1&limit=5000&sort=title&direction=desc", ALL - REQUEST_PARAM) `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".limit")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must be at most '50'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get constitution by ID`() {
|
||||
withIntegrationApplication {
|
||||
@@ -41,7 +57,7 @@ class `Constitution routes` : BaseTest() {
|
||||
`Given I have constitution`("0321c8d1-4ce3-4763-b5f4-a92611d280b4")
|
||||
`When I send a GET request`("/constitutions/0321c8d1-4ce3-4763-b5f4-a92611d280b4") `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.id") `whish contains` "0321c8d1-4ce3-4763-b5f4-a92611d280b4"
|
||||
`And have property`("$.id") `which contains` "0321c8d1-4ce3-4763-b5f4-a92611d280b4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,11 +86,11 @@ class `Constitution routes` : BaseTest() {
|
||||
"""
|
||||
{
|
||||
"versionId":"15814bb6-8d90-4c6a-a456-c3939a8ec75e",
|
||||
"title":"Hello world!",
|
||||
"title":"Cras sit amet sapien mattis nulla rutrum blandit.",
|
||||
"anonymous":true,
|
||||
"titles":[
|
||||
{
|
||||
"name":"plop"
|
||||
"name":"Cras sit amet sapien mattis nulla rutrum blandit."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -82,17 +98,18 @@ class `Constitution routes` : BaseTest() {
|
||||
)
|
||||
} `Then the response should be` Created and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.versionId") `whish contains` "15814bb6-8d90-4c6a-a456-c3939a8ec75e"
|
||||
`And have property`("$.title") `whish contains` "Hello world!"
|
||||
`And have property`("$.versionId") `which contains` "15814bb6-8d90-4c6a-a456-c3939a8ec75e"
|
||||
`And have property`("$.title") `which contains` "Cras sit amet sapien mattis nulla rutrum blandit."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot create an constitution if bad request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Henri", "Poincaré")
|
||||
`When I send a POST request`("/constitutions", Validate.ALL - Validate.REQUEST_BODY) {
|
||||
`When I send a POST request`("/constitutions", ALL - REQUEST_BODY) {
|
||||
`authenticated as`("Henri", "Poincaré")
|
||||
`with body`(
|
||||
"""
|
||||
@@ -112,4 +129,34 @@ class `Constitution routes` : BaseTest() {
|
||||
} `Then the response should be` BadRequest
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot create an constitution if request is not valid`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Henri", "Poincaré")
|
||||
`When I send a POST request`("/constitutions", ALL - REQUEST_BODY) {
|
||||
`authenticated as`("Henri", "Poincaré")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"versionId":"15814bb6-8d90-4c6a-a456-c3939a8ec75e",
|
||||
"title":"too small",
|
||||
"anonymous":true,
|
||||
"titles":[
|
||||
{
|
||||
"name":"too small"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should contain`("$.invalidParams[0].name", ".title")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 10 characters")
|
||||
`And the response should contain`("$.invalidParams[1].name", ".titles[0].name")
|
||||
`And the response should contain`("$.invalidParams[1].reason", "must have at least 10 characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class `Login routes` : BaseTest() {
|
||||
"""
|
||||
{
|
||||
"username": "niels-bohr",
|
||||
"password": "azerty"
|
||||
"password": "Azerty123!"
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
78
src/test/kotlin/integration/Notification routes.kt
Normal file
78
src/test/kotlin/integration/Notification routes.kt
Normal file
@@ -0,0 +1,78 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.auth.database.UserCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotification
|
||||
import fr.dcproject.component.notification.Notification
|
||||
import fr.dcproject.component.notification.Publisher
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have follow on article`
|
||||
import integration.steps.given.`authenticated in url as`
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.cio.websocket.readText
|
||||
import kotlinx.coroutines.launch
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.koin.test.get
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Tags(Tag("integration"), Tag("notification"))
|
||||
class `Notification routes` : BaseTest() {
|
||||
@Test
|
||||
fun `I can send notification`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("John", "Doe", id = "1a34191a-9cde-45ba-8ac1-230138a102d3")
|
||||
`Given I have article`(id = "a06cbfb7-3094-4d64-aaa1-7486c0c292f4", createdBy = Name(firstName = "John", lastName = "Doe"))
|
||||
`Given I have follow on article`("John", "Doe", article = "a06cbfb7-3094-4d64-aaa1-7486c0c292f4")
|
||||
val notification = ArticleUpdateNotification(
|
||||
ArticleForView(
|
||||
id = "a06cbfb7-3094-4d64-aaa1-7486c0c292f4".toUUID(),
|
||||
title = "MyTitle",
|
||||
content = "myContent",
|
||||
description = "myDescription",
|
||||
createdBy = CitizenCreator(
|
||||
id = "1a34191a-9cde-45ba-8ac1-230138a102d3".toUUID(),
|
||||
name = Name(firstName = "John", lastName = "Doe"),
|
||||
email = "john-doe@plop.com",
|
||||
user = UserCreator(username = "john-doe"),
|
||||
)
|
||||
)
|
||||
)
|
||||
val publisher = get<Publisher>()
|
||||
launch {
|
||||
publisher
|
||||
.publish(notification)
|
||||
.await()
|
||||
}
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
handleWebSocketConversation(
|
||||
"/notifications",
|
||||
{
|
||||
`authenticated in url as`("John", "Doe")
|
||||
}
|
||||
) { incoming, outgoing ->
|
||||
incoming.receive().let {
|
||||
when (it) {
|
||||
is Frame.Text -> Notification.fromString<ArticleUpdateNotification>(it.readText()).let { notif ->
|
||||
assertEquals(
|
||||
"a06cbfb7-3094-4d64-aaa1-7486c0c292f4",
|
||||
notif.target.id.toString()
|
||||
)
|
||||
outgoing.send(it)
|
||||
}
|
||||
else -> error(it.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
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 PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
@@ -11,8 +13,10 @@ import integration.steps.given.`Given I have opinion on article`
|
||||
import integration.steps.given.`authenticated as`
|
||||
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
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
@@ -133,7 +137,7 @@ class `Opinion routes` : BaseTest() {
|
||||
article = "8651b530-ac1b-4214-a784-706781371074",
|
||||
Name("Albert", "Einstein")
|
||||
)
|
||||
`When I send a GET request`("/citizens/c1542096-3431-432d-8e35-9dc071d4c818/opinions/articles") {
|
||||
`When I send a GET request`("/citizens/c1542096-3431-432d-8e35-9dc071d4c818/opinions/articles?page=1&limit=10") {
|
||||
`authenticated as`("Albert", "Einstein")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain`("$.result[0].name", "Opinion9")
|
||||
@@ -141,4 +145,26 @@ class `Opinion routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tags(Tag("article"), Tag("BadRequest"))
|
||||
fun `I cannot get all my opinion of one article with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Albert", "Einstein", id = "c1542096-3431-432d-8e35-9dc071d4c818")
|
||||
`Given I have an opinion choice`("Opinion9")
|
||||
`Given I have article`("8651b530-ac1b-4214-a784-706781371074")
|
||||
`Given I have opinion on article`(
|
||||
"Opinion9",
|
||||
article = "8651b530-ac1b-4214-a784-706781371074",
|
||||
Name("Albert", "Einstein")
|
||||
)
|
||||
`When I send a GET request`("/citizens/c1542096-3431-432d-8e35-9dc071d4c818/opinions/articles?page=1&limit=60", ALL - REQUEST_PARAM) {
|
||||
`authenticated as`("Albert", "Einstein")
|
||||
} `Then the response should be` HttpStatusCode.BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".limit")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must be at most '50'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class `Register routes` : BaseTest() {
|
||||
"birthday": "2001-01-01",
|
||||
"user":{
|
||||
"username": "george-junior",
|
||||
"password": "azerty"
|
||||
"password": "Azerty123!"
|
||||
},
|
||||
"email": "george-junior@gmail.com"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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 PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
@@ -12,53 +15,135 @@ import integration.steps.given.`Given I have vote +1 on article`
|
||||
import integration.steps.given.`Given I have vote -1 on article`
|
||||
import integration.steps.given.`authenticated as`
|
||||
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.OK
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Tags(Tag("integration"), Tag("vote"))
|
||||
class `Vote routes` : BaseTest() {
|
||||
@Test
|
||||
fun `I can vote article`() {
|
||||
@TestFactory
|
||||
fun `I can vote article`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Thalès", "Milet")
|
||||
`Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5")
|
||||
}
|
||||
return (-1..1).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote article with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`("/articles/835c5101-ca39-4038-a4e6-da6ee62ca6d5/vote") {
|
||||
`authenticated as`("Thalès", "Milet")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": 1
|
||||
"note": $note
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can vote constitution`() {
|
||||
@TestFactory
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot vote article with wrong request`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Thalès", "Milet")
|
||||
`Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5")
|
||||
}
|
||||
|
||||
return listOf(-10, -2, +2, +10).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote article with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`(
|
||||
"/articles/835c5101-ca39-4038-a4e6-da6ee62ca6d5/vote",
|
||||
ALL - REQUEST_BODY
|
||||
) {
|
||||
`authenticated as`("Thalès", "Milet")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": $note
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".note")
|
||||
`And the response should contain`("$.invalidParams[0].reason", if (note > 0) "must be at most '1'" else "must be at least '-1'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
fun `I can vote constitution`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Gregor", "Mendel")
|
||||
`Given I have constitution`(id = "76e79c89-efc1-492d-9e8f-dc9717363a11")
|
||||
}
|
||||
return (-1..1).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote constitution with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`("/constitutions/76e79c89-efc1-492d-9e8f-dc9717363a11/vote") {
|
||||
`authenticated as`("Gregor", "Mendel")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": 1
|
||||
}
|
||||
"note": $note
|
||||
}²
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot vote constitution with wrong request`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Gregor", "Mendel")
|
||||
`Given I have constitution`(id = "76e79c89-efc1-492d-9e8f-dc9717363a11")
|
||||
}
|
||||
|
||||
return listOf(-10, -2, +2, +10).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote constitution with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`(
|
||||
"/constitutions/76e79c89-efc1-492d-9e8f-dc9717363a11/vote",
|
||||
ALL - REQUEST_BODY
|
||||
) {
|
||||
`authenticated as`("Gregor", "Mendel")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": $note
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".note")
|
||||
`And the response should contain`("$.invalidParams[0].reason", if (note > 0) "must be at most '1'" else "must be at least '-1'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get votes of current citizen`() {
|
||||
@@ -66,7 +151,7 @@ class `Vote routes` : BaseTest() {
|
||||
`Given I have citizen`("Carl", "Gauss", id = "c044823d-e778-4256-9016-b1334bf933d3")
|
||||
`Given I have article`("7c9286db-470d-448c-aab1-3f0b072213b1")
|
||||
`Given I have vote +1 on article`("7c9286db-470d-448c-aab1-3f0b072213b1", Name("Carl", "Gauss"))
|
||||
`When I send a GET request`("/citizens/c044823d-e778-4256-9016-b1334bf933d3/votes/articles") {
|
||||
`When I send a GET request`("/citizens/c044823d-e778-4256-9016-b1334bf933d3/votes/articles?page=1&limit=50") {
|
||||
`authenticated as`("Carl", "Gauss")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain`("$.currentPage", 1)
|
||||
@@ -77,6 +162,23 @@ class `Vote routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get votes of current citizen with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Carl", "Gauss", id = "c044823d-e778-4256-9016-b1334bf933d3")
|
||||
`Given I have article`("7c9286db-470d-448c-aab1-3f0b072213b1")
|
||||
`Given I have vote +1 on article`("7c9286db-470d-448c-aab1-3f0b072213b1", Name("Carl", "Gauss"))
|
||||
`When I send a GET request`("/citizens/c044823d-e778-4256-9016-b1334bf933d3/votes/articles?page=1&limit=60", ALL - REQUEST_PARAM) {
|
||||
`authenticated as`("Carl", "Gauss")
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".limit")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must be at most '50'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get votes of current citizen by target ids`() {
|
||||
withIntegrationApplication {
|
||||
@@ -118,4 +220,39 @@ class `Vote routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot vote comment with wrong request`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Antoine", "Lavoisier")
|
||||
`Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5")
|
||||
`Given I have comment on article`(
|
||||
createdBy = Name("Antoine", "Lavoisier"),
|
||||
article = "835c5101-ca39-4038-a4e6-da6ee62ca6d5",
|
||||
id = "e793eccc-456b-4450-a292-46d592229b74",
|
||||
)
|
||||
}
|
||||
|
||||
return listOf(-10, -2, +2, +10).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote comment with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`("/comments/e793eccc-456b-4450-a292-46d592229b74/vote", ALL - REQUEST_BODY) {
|
||||
`authenticated as`("Antoine", "Lavoisier")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": $note
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".note")
|
||||
`And the response should contain`("$.invalidParams[0].reason", if (note > 0) "must be at most '1'" else "must be at least '-1'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
118
src/test/kotlin/integration/Workgroup Members routes.kt
Normal file
118
src/test/kotlin/integration/Workgroup Members routes.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have workgroup`
|
||||
import integration.steps.given.`With members`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Tags(Tag("integration"), Tag("workgroup"), Tag("workgroupMember"))
|
||||
class `Workgroup Members routes` : BaseTest() {
|
||||
@Test
|
||||
fun `I can add member to workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Blaise", "Pascal")
|
||||
`Given I have citizen`("Roger", "Penrose", id = "6d883fe7-5fc0-4a50-8858-72230673eba4")
|
||||
`Given I have citizen`("Alessandro", "Volta", id = "b5bac515-45d4-4aeb-9b6d-2627a0bbc419")
|
||||
`Given I have workgroup`("b0ea1922-3bc6-44e2-aa7c-40158998cfbb", createdBy = Name("Blaise", "Pascal"))
|
||||
`When I send a POST request`("/workgroups/b0ea1922-3bc6-44e2-aa7c-40158998cfbb/members") {
|
||||
`authenticated as`("Blaise", "Pascal")
|
||||
`with body`(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"6d883fe7-5fc0-4a50-8858-72230673eba4"},
|
||||
"roles": ["MASTER"]
|
||||
},
|
||||
{
|
||||
"citizen": {"id":"b5bac515-45d4-4aeb-9b6d-2627a0bbc419"},
|
||||
"roles": ["MASTER"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can remove member to workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Heinrich", "Hertz", id = "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`Given I have citizen`("William", "Thomson", id = "87909ba3-2069-431c-9924-219fd8411cf2")
|
||||
`Given I have citizen`("Paul", "Dirac", id = "1baf48bb-02bc-4d8f-ac86-33335354f5e7")
|
||||
`Given I have workgroup`("b6c975df-dd44-4e99-adc1-f605746b0e11", createdBy = Name("Heinrich", "Hertz")) {
|
||||
`With members`(
|
||||
Name("William", "Thomson"),
|
||||
Name("Paul", "Dirac"),
|
||||
)
|
||||
}
|
||||
`When I send a DELETE request`("/workgroups/b6c975df-dd44-4e99-adc1-f605746b0e11/members") {
|
||||
`authenticated as`("Heinrich", "Hertz")
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"87909ba3-2069-431c-9924-219fd8411cf2"}
|
||||
}
|
||||
]
|
||||
"""
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain list`("$", 2)
|
||||
`And the response should contain`("$.[0]citizen.id", "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`And the response should contain`("$.[1]citizen.id", "1baf48bb-02bc-4d8f-ac86-33335354f5e7")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can update members on workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Leon", "Foucault")
|
||||
`Given I have citizen`("Sadi", "Carnot", id = "be3b0926-8628-4426-804a-75188a6eb315")
|
||||
`Given I have citizen`("Joseph", "Fourier", id = "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
|
||||
`Given I have citizen`("Georg", "Ohm")
|
||||
`Given I have workgroup`("784fe6bc-7635-4ae2-b080-3a4743b998bf", createdBy = Name("Leon", "Foucault")) {
|
||||
`With members`(
|
||||
Name("Sadi", "Carnot"),
|
||||
Name("Joseph", "Fourier"),
|
||||
)
|
||||
}
|
||||
`When I send a PUT request`("/workgroups/784fe6bc-7635-4ae2-b080-3a4743b998bf/members") {
|
||||
`authenticated as`("Leon", "Foucault")
|
||||
`with body`(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"be3b0926-8628-4426-804a-75188a6eb315"},
|
||||
"roles": ["MASTER"]
|
||||
},
|
||||
{
|
||||
"citizen": {"id":"b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1"},
|
||||
"roles": ["MASTER"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain list`("$", 2)
|
||||
`And the response should contain`("$.[0]citizen.id", "be3b0926-8628-4426-804a-75188a6eb315")
|
||||
`And the response should contain`("$.[1]citizen.id", "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
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 +17,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
|
||||
@@ -73,7 +77,7 @@ class `Workgroup routes` : BaseTest() {
|
||||
{
|
||||
"id":"f496d86d-6654-4068-91ff-90e1dbcc5f38",
|
||||
"name":"Les Bouffons",
|
||||
"description":"La vie est belle",
|
||||
"description":"Pellentesque eleifend malesuada aliquam. Maecenas et urna quis nunc lacinia scelerisque.",
|
||||
"anonymous":false
|
||||
}
|
||||
"""
|
||||
@@ -81,7 +85,7 @@ class `Workgroup routes` : BaseTest() {
|
||||
} `Then the response should be` Created and {
|
||||
`And the response should contain`("$.id", "f496d86d-6654-4068-91ff-90e1dbcc5f38")
|
||||
`And the response should contain`("$.name", "Les Bouffons")
|
||||
`And the response should contain`("$.description", "La vie est belle")
|
||||
`And the response should contain`("$.description", "Pellentesque eleifend malesuada aliquam. Maecenas et urna quis nunc lacinia scelerisque.")
|
||||
`And the response should contain`("$.anonymous", false)
|
||||
}
|
||||
|
||||
@@ -91,6 +95,36 @@ class `Workgroup routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot create a workgroup with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Werner", "Heisenberg")
|
||||
`When I send a POST request`("/workgroups") {
|
||||
`authenticated as`("Werner", "Heisenberg")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"id":"f496d86d-6654-4068-91ff-90e1dbcc5f38",
|
||||
"name":"sm",
|
||||
"description":"small",
|
||||
"anonymous":false,
|
||||
"logo": "www.plop.com"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".name")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 5 characters")
|
||||
`And the response should contain`("$.invalidParams[1].name", ".description")
|
||||
`And the response should contain`("$.invalidParams[1].reason", "must have at least 50 characters")
|
||||
`And the response should contain`("$.invalidParams[2].name", ".logo")
|
||||
`And the response should contain`("$.invalidParams[2].reason", "is not url")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can edit a workgroup`() {
|
||||
withIntegrationApplication {
|
||||
@@ -109,14 +143,15 @@ class `Workgroup routes` : BaseTest() {
|
||||
"""
|
||||
{
|
||||
"name":"La ratatouille",
|
||||
"description":"Une petite souris"
|
||||
"description":"Une petite souris avec un chapeau et qui aime la cuisine",
|
||||
"logo": "http://sdf@exemple.com/sdfsd?sdf=sss"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain`("$.id", "aa875a24-0050-4252-9130-d37391714e26")
|
||||
`And the response should contain`("$.name", "La ratatouille")
|
||||
`And the response should contain`("$.description", "Une petite souris")
|
||||
`And the response should contain`("$.description", "Une petite souris avec un chapeau et qui aime la cuisine")
|
||||
|
||||
`And have property`("$.members")
|
||||
`And the response should contain list`("$.members", 3)
|
||||
@@ -129,7 +164,43 @@ class `Workgroup routes` : BaseTest() {
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain`("$.id", "aa875a24-0050-4252-9130-d37391714e26")
|
||||
`And the response should contain`("$.name", "La ratatouille")
|
||||
`And the response should contain`("$.description", "Une petite souris")
|
||||
`And the response should contain`("$.description", "Une petite souris avec un chapeau et qui aime la cuisine")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot edit a workgroup with bad request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("John", "Wheeler")
|
||||
`Given I have citizen`("Heinrich", "Hertz", id = "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`Given I have citizen`("William", "Thomson", id = "87909ba3-2069-431c-9924-219fd8411cf2")
|
||||
`Given I have workgroup`("aa875a24-0050-4252-9130-d37391714e26", createdBy = Name("John", "Wheeler")) {
|
||||
`With members`(
|
||||
Name("Heinrich", "Hertz"),
|
||||
Name("William", "Thomson"),
|
||||
)
|
||||
}
|
||||
`When I send a PUT request`("/workgroups/aa875a24-0050-4252-9130-d37391714e26", -REQUEST_BODY) {
|
||||
`authenticated as`("John", "Wheeler")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"name":"sm",
|
||||
"description":"small2",
|
||||
"logo": "ws://sdfs.sdok"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".name")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 5 characters")
|
||||
`And the response should contain`("$.invalidParams[1].name", ".description")
|
||||
`And the response should contain`("$.invalidParams[1].reason", "must have at least 50 characters")
|
||||
`And the response should contain`("$.invalidParams[2].name", ".logo")
|
||||
`And the response should contain`("$.invalidParams[2].reason", "is not url")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +228,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 {
|
||||
@@ -167,94 +238,15 @@ class `Workgroup routes` : BaseTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can add member to workgroup`() {
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get workgroups list with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Blaise", "Pascal")
|
||||
`Given I have citizen`("Roger", "Penrose", id = "6d883fe7-5fc0-4a50-8858-72230673eba4")
|
||||
`Given I have citizen`("Alessandro", "Volta", id = "b5bac515-45d4-4aeb-9b6d-2627a0bbc419")
|
||||
`Given I have workgroup`("b0ea1922-3bc6-44e2-aa7c-40158998cfbb", createdBy = Name("Blaise", "Pascal"))
|
||||
`When I send a POST request`("/workgroups/b0ea1922-3bc6-44e2-aa7c-40158998cfbb/members") {
|
||||
`authenticated as`("Blaise", "Pascal")
|
||||
`with body`(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"6d883fe7-5fc0-4a50-8858-72230673eba4"},
|
||||
"roles": ["MASTER"]
|
||||
},
|
||||
{
|
||||
"citizen": {"id":"b5bac515-45d4-4aeb-9b6d-2627a0bbc419"},
|
||||
"roles": ["MASTER"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can remove member to workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Heinrich", "Hertz", id = "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`Given I have citizen`("William", "Thomson", id = "87909ba3-2069-431c-9924-219fd8411cf2")
|
||||
`Given I have citizen`("Paul", "Dirac", id = "1baf48bb-02bc-4d8f-ac86-33335354f5e7")
|
||||
`Given I have workgroup`("b6c975df-dd44-4e99-adc1-f605746b0e11", createdBy = Name("Heinrich", "Hertz")) {
|
||||
`With members`(
|
||||
Name("William", "Thomson"),
|
||||
Name("Paul", "Dirac"),
|
||||
)
|
||||
}
|
||||
`When I send a DELETE request`("/workgroups/b6c975df-dd44-4e99-adc1-f605746b0e11/members") {
|
||||
`authenticated as`("Heinrich", "Hertz")
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"87909ba3-2069-431c-9924-219fd8411cf2"}
|
||||
}
|
||||
]
|
||||
"""
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain list`("$", 2)
|
||||
`And the response should contain`("$.[0]citizen.id", "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`And the response should contain`("$.[1]citizen.id", "1baf48bb-02bc-4d8f-ac86-33335354f5e7")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can update members on workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Leon", "Foucault")
|
||||
`Given I have citizen`("Sadi", "Carnot", id = "be3b0926-8628-4426-804a-75188a6eb315")
|
||||
`Given I have citizen`("Joseph", "Fourier", id = "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
|
||||
`Given I have citizen`("Georg", "Ohm")
|
||||
`Given I have workgroup`("784fe6bc-7635-4ae2-b080-3a4743b998bf", createdBy = Name("Leon", "Foucault")) {
|
||||
`With members`(
|
||||
Name("Sadi", "Carnot"),
|
||||
Name("Joseph", "Fourier"),
|
||||
)
|
||||
}
|
||||
`When I send a PUT request`("/workgroups/784fe6bc-7635-4ae2-b080-3a4743b998bf/members") {
|
||||
`authenticated as`("Leon", "Foucault")
|
||||
`with body`(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"be3b0926-8628-4426-804a-75188a6eb315"},
|
||||
"roles": ["MASTER"]
|
||||
},
|
||||
{
|
||||
"citizen": {"id":"b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1"},
|
||||
"roles": ["MASTER"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain list`("$", 2)
|
||||
`And the response should contain`("$.[0]citizen.id", "be3b0926-8628-4426-804a-75188a6eb315")
|
||||
`And the response should contain`("$.[1]citizen.id", "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
|
||||
`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'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import fr.dcproject.component.article.database.ArticleForUpdate
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRef
|
||||
import io.ktor.server.testing.TestApplicationEngine
|
||||
import org.koin.core.context.GlobalContext
|
||||
@@ -16,7 +17,15 @@ fun TestApplicationEngine.`Given I have article`(
|
||||
workgroup: WorkgroupRef? = null,
|
||||
createdBy: Name? = null
|
||||
) {
|
||||
createArticle(id?.toUUID(), workgroup, createdBy)
|
||||
createArticle(id?.toUUID(), workgroup, createCitizen(name = createdBy))
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`Given I have article`(
|
||||
id: String? = null,
|
||||
workgroup: WorkgroupRef? = null,
|
||||
createdBy: UUID
|
||||
) {
|
||||
createArticle(id?.toUUID(), workgroup, createCitizen(id = createdBy))
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`Given I have articles`(
|
||||
@@ -35,18 +44,16 @@ fun TestApplicationEngine.`Given I have article created by workgroup`(
|
||||
fun createArticle(
|
||||
id: UUID? = null,
|
||||
workgroup: WorkgroupRef? = null,
|
||||
createdBy: Name? = null
|
||||
createdBy: CitizenRef = createCitizen()
|
||||
): ArticleForView {
|
||||
val articleRepository: ArticleRepository by lazy { GlobalContext.get().koin.get() }
|
||||
|
||||
val citizen = createCitizen(createdBy)
|
||||
|
||||
val article = ArticleForUpdate(
|
||||
id = id ?: UUID.randomUUID(),
|
||||
title = LoremIpsum().getTitle(3),
|
||||
content = LoremIpsum().getParagraphs(1, 2),
|
||||
description = LoremIpsum().getParagraphs(1, 2),
|
||||
createdBy = citizen,
|
||||
createdBy = createdBy,
|
||||
workgroup = workgroup,
|
||||
versionId = UUID.randomUUID()
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package integration.steps.given
|
||||
import com.auth0.jwt.JWT
|
||||
import fr.dcproject.component.auth.jwt.JwtConfig
|
||||
import fr.dcproject.component.citizen.database.Citizen
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.server.testing.TestApplicationRequest
|
||||
@@ -25,3 +26,23 @@ fun TestApplicationRequest.`authenticated as`(
|
||||
|
||||
return citizen
|
||||
}
|
||||
fun TestApplicationRequest.`authenticated in url as`(
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
): Citizen {
|
||||
val repo: CitizenRepository by lazy<CitizenRepository> { GlobalContext.get().koin.get() }
|
||||
val citizen = repo.findByName(CitizenI.Name(firstName, lastName)) ?: error("Citizen not exist with name $firstName $lastName")
|
||||
val algorithm = GlobalContext.get().koin.get<JwtConfig>().algorithm
|
||||
val jwtAsString: String = JWT.create()
|
||||
.withIssuer("dc-project.fr")
|
||||
.withClaim("id", citizen.user.id.toString())
|
||||
.sign(algorithm)
|
||||
|
||||
uri += when (uri.contains('?')) {
|
||||
true -> '&'
|
||||
false -> '?'
|
||||
}
|
||||
uri += "token=$jwtAsString"
|
||||
|
||||
return citizen
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ fun TestApplicationEngine.`Given I have citizen`(
|
||||
val user = UserForCreate(
|
||||
id = id.toUUID(),
|
||||
username = "$firstName-$lastName".toLowerCase(),
|
||||
password = "azerty",
|
||||
password = "Azerty123!",
|
||||
)
|
||||
val citizen = CitizenForCreate(
|
||||
id = id.toUUID(),
|
||||
@@ -36,23 +36,24 @@ fun TestApplicationEngine.`Given I have citizen`(
|
||||
return repo.insertWithUser(citizen)?.also { callback(it) }
|
||||
}
|
||||
|
||||
fun createCitizen(createdBy: CitizenI.Name? = null): Citizen {
|
||||
fun createCitizen(name: CitizenI.Name? = null, id: UUID = UUID.randomUUID()): Citizen {
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().koin.get() }
|
||||
|
||||
return if (createdBy != null) {
|
||||
citizenRepository.findByName(createdBy) ?: error("Citizen not exist")
|
||||
return if (name != null) {
|
||||
citizenRepository.findByName(name) ?: error("Citizen not exist")
|
||||
} else {
|
||||
val first = "firstName" + UUID.randomUUID().toString()
|
||||
val last = "lastName" + UUID.randomUUID().toString()
|
||||
val username = ("username" + UUID.randomUUID().toString())
|
||||
CitizenForCreate(
|
||||
id = id,
|
||||
birthday = DateTime.now(),
|
||||
name = CitizenI.Name(
|
||||
first,
|
||||
last
|
||||
),
|
||||
email = "$first@fakeemail.com",
|
||||
user = UserForCreate(username = username, password = "azerty")
|
||||
user = UserForCreate(username = username, password = "Azerty123!")
|
||||
).let {
|
||||
citizenRepository.insertWithUser(it) ?: error("Unable to create User")
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ package integration.steps.given
|
||||
|
||||
import com.thedeanda.lorem.LoremIpsum
|
||||
import fr.dcproject.common.entity.TargetI
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.citizen.database.CitizenCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import fr.dcproject.component.comment.generic.database.CommentForUpdate
|
||||
import fr.dcproject.component.comment.generic.database.CommentForView
|
||||
import fr.dcproject.component.comment.generic.database.CommentI
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRepository
|
||||
@@ -32,14 +37,14 @@ fun TestApplicationEngine.`Given I have comments on article`(
|
||||
}
|
||||
}
|
||||
|
||||
fun createComment(
|
||||
fun <A : ArticleRef> createComment(
|
||||
id: UUID? = null,
|
||||
article: ArticleRef? = null,
|
||||
article: A? = null,
|
||||
createdBy: Name? = null,
|
||||
content: String? = null
|
||||
) {
|
||||
): CommentForView<TargetRef, CitizenCreator> {
|
||||
val articleRepository: ArticleRepository by lazy { GlobalContext.get().koin.get() }
|
||||
createCommentOnTarget(
|
||||
return createCommentOnTarget(
|
||||
id,
|
||||
article?.id?.let { articleRepository.findById(article.id) } ?: createArticle(article?.id),
|
||||
createdBy,
|
||||
@@ -56,14 +61,14 @@ fun TestApplicationEngine.`Given I have comment on constitution`(
|
||||
createComment(id?.toUUID(), ConstitutionRef(constitution?.toUUID()), createdBy, content)
|
||||
}
|
||||
|
||||
fun createComment(
|
||||
fun <C : ConstitutionRef> createComment(
|
||||
id: UUID? = null,
|
||||
constitution: ConstitutionRef? = null,
|
||||
constitution: C? = null,
|
||||
createdBy: Name? = null,
|
||||
content: String? = null
|
||||
) {
|
||||
): CommentForView<TargetRef, CitizenCreator> {
|
||||
val constitutionRepository: ConstitutionRepository by lazy { GlobalContext.get().koin.get() }
|
||||
createCommentOnTarget(
|
||||
return createCommentOnTarget(
|
||||
id,
|
||||
constitution?.id?.let { constitutionRepository.findById(constitution.id) } ?: createConstitution(constitution?.id),
|
||||
createdBy,
|
||||
@@ -71,12 +76,12 @@ fun createComment(
|
||||
)
|
||||
}
|
||||
|
||||
fun createCommentOnTarget(
|
||||
fun <T : TargetI> createCommentOnTarget(
|
||||
id: UUID? = null,
|
||||
target: TargetI,
|
||||
target: T,
|
||||
createdBy: Name? = null,
|
||||
content: String? = null
|
||||
) {
|
||||
): CommentForView<TargetRef, CitizenCreator> {
|
||||
val commentRepository: CommentRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val creator = createCitizen(createdBy)
|
||||
val comment = CommentForUpdate(
|
||||
@@ -85,5 +90,41 @@ fun createCommentOnTarget(
|
||||
target = target,
|
||||
content = content ?: LoremIpsum().getParagraphs(1, 3)
|
||||
)
|
||||
commentRepository.comment(comment)
|
||||
return commentRepository.comment(comment)
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`Given I have comment on comment`(
|
||||
id: String? = null,
|
||||
parent: String? = null,
|
||||
createdBy: Name? = null,
|
||||
content: String? = null,
|
||||
): CommentForView<out TargetRef, CitizenCreator> {
|
||||
return createCommentOnComment(
|
||||
id?.toUUID() ?: UUID.randomUUID(),
|
||||
parent?.run { CommentRef(toUUID()) },
|
||||
createdBy,
|
||||
content,
|
||||
)
|
||||
}
|
||||
|
||||
fun createCommentOnComment(
|
||||
id: UUID? = null,
|
||||
parent: CommentI? = createComment<ArticleRef>(),
|
||||
createdBy: Name? = null,
|
||||
content: String? = null
|
||||
): CommentForView<out TargetRef, CitizenCreator> {
|
||||
val creator = createCitizen(createdBy)
|
||||
val commentRepository: CommentRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val parentComment = if (parent == null) {
|
||||
createComment<ArticleRef>()
|
||||
} else {
|
||||
commentRepository.findById(parent.id) ?: error("Parent of comment not found")
|
||||
}
|
||||
val comment = CommentForUpdate(
|
||||
id = id ?: UUID.randomUUID(),
|
||||
createdBy = creator,
|
||||
content = content ?: LoremIpsum().getParagraphs(1, 3),
|
||||
parent = parentComment,
|
||||
)
|
||||
return commentRepository.comment(comment)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package integration.steps.given
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.citizen.database.Citizen
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
@@ -30,7 +31,7 @@ fun TestApplicationEngine.`Given I have follow on article`(
|
||||
article: String,
|
||||
) {
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val citizen = citizenRepository.findByUsername("$firstName-$lastName".toLowerCase()) ?: error("Citizen not exist")
|
||||
val citizen = citizenRepository.findByName(CitizenI.Name(firstName, lastName)) ?: error("Citizen not exist")
|
||||
createFollow(citizen, ArticleRef(article.toUUID()))
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ fun TestApplicationEngine.`Given I have follow on constitution`(
|
||||
constitution: String,
|
||||
) {
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val citizen = citizenRepository.findByUsername("$firstName-$lastName".toLowerCase()) ?: error("Citizen not exist")
|
||||
val citizen = citizenRepository.findByName(CitizenI.Name(firstName, lastName)) ?: error("Citizen not exist")
|
||||
createFollow(citizen, ArticleRef(constitution.toUUID()))
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ private fun createWorkgroup(
|
||||
.toLowerCase().replace(' ', '-')
|
||||
val user = UserForCreate(
|
||||
username = username,
|
||||
password = "azerty",
|
||||
password = "Azerty123!",
|
||||
)
|
||||
CitizenForCreate(
|
||||
name = creatorName,
|
||||
|
||||
@@ -46,7 +46,7 @@ infix fun TestApplicationResponse.`And have property`(path: String): Pair<JsonPa
|
||||
} ?: throw AssertionError("\"${path}\" element not found on json response")
|
||||
}
|
||||
|
||||
infix fun Pair<JsonPath, Any>.`whish contains`(expected: Any): Pair<JsonPath, Any> = this.apply {
|
||||
infix fun Pair<JsonPath, Any>.`which contains`(expected: Any): Pair<JsonPath, Any> = this.apply {
|
||||
second `should be equal to` expected
|
||||
}
|
||||
|
||||
|
||||
@@ -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`
|
||||
@@ -14,6 +15,7 @@ import io.ktor.server.testing.TestApplicationRequest
|
||||
import io.ktor.server.testing.setBody
|
||||
|
||||
enum class Validate(override val bit: Long) : BitMaskI {
|
||||
NONE(0),
|
||||
REQUEST_BODY(1),
|
||||
REQUEST_PARAM(2),
|
||||
REQUEST_HEADER(4),
|
||||
@@ -22,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 {
|
||||
@@ -40,7 +44,7 @@ fun TestApplicationCall.valid(validate: BitMaskI): TestApplicationCall {
|
||||
return this
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`When I send a GET request`(uri: String? = null, validate: Validate = Validate.ALL, setup: (TestApplicationRequest.() -> Unit)? = null): TestApplicationCall {
|
||||
fun TestApplicationEngine.`When I send a GET request`(uri: String? = null, validate: BitMaskI = Validate.ALL, setup: (TestApplicationRequest.() -> Unit)? = null): TestApplicationCall {
|
||||
return handleRequest(true) {
|
||||
method = HttpMethod.Get
|
||||
if (uri != null) {
|
||||
@@ -74,7 +78,7 @@ fun TestApplicationEngine.`When I send a PUT request`(uri: String? = null, valid
|
||||
}.valid(validate)
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`When I send a DELETE request`(uri: String? = null, validate: Validate = Validate.ALL, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall {
|
||||
fun TestApplicationEngine.`When I send a DELETE request`(uri: String? = null, validate: BitMaskI = Validate.ALL, setup: (TestApplicationRequest.() -> String?)? = null): TestApplicationCall {
|
||||
return handleRequest(true) {
|
||||
method = HttpMethod.Delete
|
||||
if (uri != null) {
|
||||
|
||||
32
src/test/kotlin/unit/Email Validation.kt
Normal file
32
src/test/kotlin/unit/Email Validation.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
package unit
|
||||
|
||||
import fr.dcproject.common.validation.email
|
||||
import io.konform.validation.Invalid
|
||||
import io.konform.validation.Valid
|
||||
import io.konform.validation.Validation
|
||||
import org.amshove.kluent.`should be instance of`
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.parallel.Execution
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
@Tags(Tag("validation"), Tag("unit"))
|
||||
internal class `Email Validation` {
|
||||
@Test
|
||||
fun passwordScore() {
|
||||
Validation<ObjectToValid> {
|
||||
ObjectToValid::email {
|
||||
email()
|
||||
}
|
||||
}.run {
|
||||
validate(ObjectToValid("abc@123.com")) `should be instance of` Valid::class
|
||||
validate(ObjectToValid("abc123.com")) `should be instance of` Invalid::class
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectToValid(val email: String)
|
||||
}
|
||||
46
src/test/kotlin/unit/Password Validation.kt
Normal file
46
src/test/kotlin/unit/Password Validation.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
package unit
|
||||
|
||||
import fr.dcproject.common.validation.passwordScore
|
||||
import io.konform.validation.Invalid
|
||||
import io.konform.validation.Valid
|
||||
import io.konform.validation.Validation
|
||||
import org.amshove.kluent.`should be equal to`
|
||||
import org.amshove.kluent.`should be instance of`
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.parallel.Execution
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
@Tags(Tag("validation"), Tag("unit"))
|
||||
internal class `Password Validation` {
|
||||
@Test
|
||||
fun password() {
|
||||
"1234567890".passwordScore() `should be equal to` 10
|
||||
"1234567A".passwordScore() `should be equal to` 10
|
||||
"1234Aa".passwordScore() `should be equal to` 10
|
||||
"12Aab".passwordScore() `should be equal to` 11
|
||||
"1234Aa".passwordScore() `should be equal to` 10
|
||||
"12abCD-+".passwordScore() `should be equal to` 18
|
||||
"Abcde12!".passwordScore() `should be equal to` 15
|
||||
"Hello world".passwordScore() `should be equal to` 16
|
||||
"hello WORLD".passwordScore() `should be equal to` 17
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordScore() {
|
||||
Validation<ObjectToValid> {
|
||||
ObjectToValid::password {
|
||||
this.passwordScore(10)
|
||||
}
|
||||
}.run {
|
||||
validate(ObjectToValid("1234567890")) `should be instance of` Valid::class
|
||||
validate(ObjectToValid("12345678")) `should be instance of` Invalid::class
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectToValid(val password: String)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ begin
|
||||
select "comment"(
|
||||
reference => 'article'::regclass,
|
||||
resource => _comment
|
||||
) into _comment_id;
|
||||
)->>'id' into _comment_id;
|
||||
|
||||
assert (select count(*) = 1 from "comment"), 'comment must be inserted, "' || (select count(*) from "comment") || '" exist';
|
||||
assert (select com.content = 'Ho my god !' from "comment" com), 'the content of comment must be "Ho my god !" instead of "' || (select com.content from "comment" as com) || '"';
|
||||
@@ -67,7 +67,7 @@ begin
|
||||
select "comment"(
|
||||
reference => 'article'::regclass,
|
||||
resource => _comment
|
||||
) into _comment_id_response;
|
||||
)->>'id' into _comment_id_response;
|
||||
|
||||
|
||||
_comment = json_build_object(
|
||||
@@ -80,7 +80,7 @@ begin
|
||||
select "comment"(
|
||||
reference => 'article'::regclass,
|
||||
resource => _comment
|
||||
) into _comment_id_response2;
|
||||
)->>'id' into _comment_id_response2;
|
||||
assert (select count(*) = 3 from "comment"), 'response must be inserted';
|
||||
assert (select com.parents_ids @> ARRAY[_comment_id] from "comment" com where id = _comment_id_response), 'parents_ids not contain "' || _comment_id::text || '" ' || (select com.parents_ids::text[] from "comment" com where id = _comment_id_response);
|
||||
assert (select com.parents_ids @> ARRAY[_comment_id_response] from "comment" com where id = _comment_id_response2), 'parents_ids not contain "' || _comment_id_response::text || '" ' || (select com.parents_ids::text[] from "comment" com where id = _comment_id_response2);
|
||||
|
||||
@@ -29,6 +29,9 @@ begin
|
||||
assert (select following = true from find_follow(first_article_id, _citizen_id, 'article')), '(v1) find_follow must return the following';
|
||||
assert (select following = true from find_follow(first_article_updated_id, _citizen_id, 'article')), '(v2) find_follow must return the following';
|
||||
|
||||
assert (select f.total = 1 from find_follows_article_by_target(first_article_id) as f), 'find_follows_article_by_target must return 1 follow';
|
||||
assert (select (f.resource#>>'{0, created_by, id}')::uuid = _citizen_id from find_follows_article_by_target(first_article_id) as f), 'find_follows_article_by_target must return follows with creator';
|
||||
|
||||
perform unfollow('article'::regclass, first_article_id, _citizen_id);
|
||||
assert (select count(*) = 0 from follow), 'follow must be deleted after unfollow, event if article is on other version';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user