Refactors Articles and Voter

- Move files into components (article)
- Split articles routes
- Refactoring for remove ktor-voter (ArticleVoter)
- Remove mutability
- Move DataConversion to separate file (Converter.kt)
- Add Schemas for Articles routes
- Fix SQL Query for Workgroup roles
- rename container_name in docker-compose
This commit is contained in:
2021-01-14 11:23:27 +01:00
parent 03401f711e
commit a1c1accc87
124 changed files with 2026 additions and 1828 deletions

View File

@@ -1,101 +0,0 @@
import fr.dcproject.entity.Article
import fr.dcproject.entity.CitizenBasic
import fr.dcproject.entity.CitizenI
import fr.dcproject.entity.User
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.util.KtorExperimentalAPI
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.shouldBe
import org.intellij.lang.annotations.Language
import org.joda.time.DateTime
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(PER_CLASS)
class ArticleTest {
@Language("JSON")
private val articleJson: String = """
{
"id": "83b0b60a-5ab3-44f2-b243-1dc469a7564f",
"title": "Hello world!",
"anonymous": true,
"content": "bla bla bla",
"description": "this is the changement !",
"tags": [],
"draft": false,
"last_version": false,
"created_by": {
"id": "94a0d350-7eab-4a6e-9f84-0c2e7635b67c",
"name": {
"first_name": "Jaque",
"last_name": "Bono",
"civility": null
},
"email": "jaque.bono@gmail.com",
"birthday": "2020-03-16T01:48:27.020Z",
"vote_anonymous": true,
"follow_anonymous": true,
"user": {
"id": "2bc356a2-4d3e-46ff-91f4-ae30fb7fa67d",
"username": "jaque",
"blocked_at": null,
"plain_password": "azerty",
"roles": [],
"created_at": "2020-03-16T01:48:24.153Z",
"updated_at": "2020-03-16T01:48:24.516Z"
},
"deleted_at": null,
"deleted": false
},
"reference": "article",
"views": {
"total": 0,
"unique": 0,
"updated_at": "2020-03-16T01:48:31.070Z"
},
"version_id": "27cb4f5d-d425-4e10-95ca-6c50fac73408",
"version_number": null,
"created_at": "2020-03-16T01:48:31.004Z",
"deleted_at": null,
"deleted": false,
"votes": {
"up": 0,
"neutral": 0,
"down": 0,
"total": 0,
"score": 0,
"updated_at": null
},
"opinions": {}
}
""".trimIndent()
@Test
fun `test Article serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = CitizenBasic(
name = CitizenI.Name("Jaque", "Bono"),
birthday = DateTime.now(),
email = "jaque.bono@gmail.com",
user = user
)
val article = Article(
title = "Hello world!",
content = "bla bla bla",
description = "this is the changement !",
createdBy = citizen
)
article.serialize().contains("""Hello world!""") shouldBe true
}
@Test
fun `test Article Deserialize`() {
val article2: Article = articleJson.deserialize()!!
article2.id.toString() `should be equal to` "83b0b60a-5ab3-44f2-b243-1dc469a7564f"
}
}

View File

@@ -1,107 +0,0 @@
import fr.dcproject.entity.CitizenBasic
import fr.dcproject.entity.CitizenI
import fr.dcproject.entity.Constitution
import fr.dcproject.entity.User
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.util.KtorExperimentalAPI
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.`should equal`
import org.amshove.kluent.shouldBe
import org.intellij.lang.annotations.Language
import org.joda.time.DateTime
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(PER_CLASS)
class ConstitutionTest {
@Language("JSON")
private val constitutionJson: String = """{
"id":"15814bb6-8d90-4c6a-a456-c3939a8ec75e",
"title":"Hello world!",
"anonymous":true,
"titles":[
{
"id":"8156b66f-a9c8-4fd9-8375-a8a1f42ccfd2",
"name":"plop",
"rank":0,
"created_by":{
"id":"18902d22-245d-4d44-b23d-9f0e82688612",
"name":{
"first_name":"Jaque",
"last_name":"Bono",
"civility":null
},
"email": "jaque.bono@gmail.com",
"birthday":"2019-08-07T20:34:08.013Z",
"user_id":null,
"vote_anonymous":null,
"follow_anonymous":null,
"user":{
"id":"257abe9f-be17-4ad3-ae6a-b1dc9706d5d7",
"username":"jaque",
"blocked_at":null,
"plain_password":"azerty",
"created_at":null,
"updated_at":null
},
"created_at":null
},
"created_at":null
}
],
"created_by":{
"id":"18902d22-245d-4d44-b23d-9f0e82688612",
"name":{
"first_name":"Jaque",
"last_name":"Bono",
"civility":null
},
"email": "jaque.bono@gmail.com",
"birthday":"2019-08-07T20:34:08.013Z",
"user_id":null,
"vote_anonymous":null,
"follow_anonymous":null,
"user":{
"id":"257abe9f-be17-4ad3-ae6a-b1dc9706d5d7",
"username":"jaque",
"plain_password":"azerty"
}
},
"created_at":null,
"version_id":"3311a7af-2a62-4e31-b4cd-889f8ead9737",
"version_number":null
}""".trimIndent()
@Test
fun `test Constitution serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = CitizenBasic(
name = CitizenI.Name("Jaque", "Bono"),
email = "jaque.bono@gmail.com",
birthday = DateTime.now(),
user = user
)
val title1 = Constitution.Title(
name = "plop"
)
val constitution = Constitution(
title = "Hello world!",
anonymous = true,
titles = mutableListOf(title1),
createdBy = citizen
)
println(constitution.serialize())
constitution.serialize().contains("""Hello world!""") shouldBe true
}
@Test
fun `test Constitution Deserialize`() {
val constitution2: Constitution = constitutionJson.deserialize()!!
constitution2.id.toString() `should be equal to` "15814bb6-8d90-4c6a-a456-c3939a8ec75e"
}
}

View File

@@ -1,109 +0,0 @@
import fr.dcproject.entity.*
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.util.KtorExperimentalAPI
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.`should equal`
import org.amshove.kluent.shouldBe
import org.intellij.lang.annotations.Language
import org.joda.time.DateTime
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(PER_CLASS)
class FollowTest {
@Language("JSON")
private val followJson: String = """{
"id":"bae81585-d985-4d7a-9b58-3a13e911688a",
"created_by":{
"id":"4a87ad24-187a-46a8-97ab-00b30a24e561",
"name":{
"first_name":"Jaque",
"last_name":"Bono",
"civility":null
},
"email": "jaque.bono@gmail.com",
"birthday":"2019-08-09T11:42:47.168Z",
"user_id":null,
"vote_anonymous":null,
"follow_anonymous":null,
"user":{
"id":"721db690-d050-46e6-92b0-056f2e8ba993",
"username":"jaque",
"blocked_at":null,
"plain_password":"azerty",
"created_at":"2019-08-09T11:42:47.168Z",
"updated_at":"2019-08-09T11:42:47.168Z"
},
"created_at":"2019-08-09T11:42:47.168Z"
},
"target":{
"id":"34588ea7-c180-4694-801b-1b5c5a6ed73f",
"title":"Hello world!",
"anonymous":true,
"content":"bla bla bla",
"description":"this is the changement !",
"tags":[
],
"created_by":{
"id":"4a87ad24-187a-46a8-97ab-00b30a24e561",
"name":{
"first_name":"Jaque",
"last_name":"Bono",
"civility":null
},
"email": "jaque.bono@gmail.com",
"birthday":"2019-08-09T11:42:47.168Z",
"user_id":null,
"vote_anonymous":null,
"follow_anonymous":null,
"user":{
"id":"721db690-d050-46e6-92b0-056f2e8ba993",
"username":"jaque",
"blocked_at":null,
"plain_password":"azerty",
"created_at":"2019-08-09T11:42:47.168Z",
"updated_at":"2019-08-09T11:42:47.168Z"
}
},
"version_id":"a4aa7dd4-d174-42d2-9ba5-ae6f1129ffce",
"version_number":null,
"created_at":null
},
"created_at":"2019-08-09T11:42:47.168Z"
}""".trimIndent()
@Test
fun `test Follow Article serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = CitizenBasic(
name = CitizenI.Name("Jaque", "Bono"),
email = "jaque.bono@gmail.com",
birthday = DateTime.now(),
user = user
)
val article = Article(
title = "Hello world!",
content = "bla bla bla",
description = "this is the changement !",
createdBy = citizen
)
val follow = Follow(
createdBy = citizen,
target = article
)
follow.serialize().contains("""Hello world!""") shouldBe true
println(follow.serialize())
}
@Test
fun `test Follow Article Deserialize`() {
val follow: Follow<ArticleSimple> = followJson.deserialize()!!
follow.id.toString() `should be equal to` "bae81585-d985-4d7a-9b58-3a13e911688a"
}
}

View File

@@ -1,11 +1,11 @@
import fr.dcproject.Env
import fr.dcproject.entity.ArticleRefVersioning
import fr.dcproject.component.article.ArticleRefVersioning
import fr.dcproject.component.article.ArticleViewManager
import fr.dcproject.entity.CitizenRef
import fr.dcproject.module
import fr.dcproject.views.ArticleViewManager
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.server.testing.withTestApplication
import io.ktor.util.KtorExperimentalAPI
import io.ktor.locations.*
import io.ktor.server.testing.*
import io.ktor.util.*
import org.amshove.kluent.`should be equal to`
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance

View File

@@ -1,132 +0,0 @@
import fr.dcproject.entity.*
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.util.KtorExperimentalAPI
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.`should equal`
import org.amshove.kluent.shouldBe
import org.intellij.lang.annotations.Language
import org.joda.time.DateTime
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(PER_CLASS)
class VoteTest {
@Language("JSON")
private val voteJson: String = """
{
"id": "032acc3d-e8c5-4cb2-9297-bec913ff8d9b",
"created_by": {
"id": "40c65a43-f9f8-45cd-aa31-953376e7c94a",
"name": {
"first_name": "Jaque",
"last_name": "Bono",
"civility": null
},
"email": "jaque.bono@gmail.com",
"birthday": "2019-10-01T10:59:40.570Z",
"user_id": null,
"vote_anonymous": true,
"follow_anonymous": true,
"user": {
"id": "f68df389-fb0d-423e-90fd-a140a9ed29b9",
"username": "jaque",
"blocked_at": null,
"plain_password": "azerty",
"roles": [],
"created_at": "2019-10-01T10:59:40.570Z",
"updated_at": "2019-10-01T10:59:40.570Z"
},
"deleted": false,
"created_at": "2019-10-01T10:59:40.570Z",
"updated_at": "2019-10-01T10:59:40.570Z",
"deleted_at": null
},
"target": {
"id": "90f28912-7bd5-4f37-a0ea-8620e3817d51",
"title": "Hello world!",
"anonymous": true,
"content": "bla bla bla",
"description": "this is the changement !",
"tags": [],
"draft": false,
"last_version": false,
"created_by": {
"id": "40c65a43-f9f8-45cd-aa31-953376e7c94a",
"name": {
"first_name": "Jaque",
"last_name": "Bono",
"civility": null
},
"email": "jaque.bono@gmail.com",
"birthday": "2019-10-01T10:59:40.570Z",
"user_id": null,
"vote_anonymous": true,
"follow_anonymous": true,
"user": {
"id": "f68df389-fb0d-423e-90fd-a140a9ed29b9",
"username": "jaque",
"blocked_at": null,
"plain_password": "azerty",
"roles": [],
"created_at": "2019-08-09T11:42:47.168Z",
"updated_at": "2019-08-09T11:42:47.168Z"
},
"deleted": false,
"created_at": "2019-08-09T11:42:47.168Z",
"deleted_at": "2019-08-09T11:42:47.168Z"
},
"votes": {
"up": 0,
"neutral": 0,
"down": 0,
"updated_at": null
},
"version_id": "48dad61e-c54b-4f4c-9f66-428f90b94045",
"version_number": null,
"deleted": false,
"created_at": "2019-10-01T10:59:40.570Z",
"deleted_at": "2019-10-01T10:59:40.570Z"
},
"note": -1,
"anonymous": true,
"updated_at": "2019-10-01T10:59:40.570Z",
"created_at": "2019-10-01T10:59:40.570Z"
}""".trimIndent()
@Test
fun `test Vote Article serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = CitizenBasic(
name = CitizenI.Name("Jaque", "Bono"),
email = "jaque.bono@gmail.com",
birthday = DateTime.now(),
user = user
)
val article = Article(
title = "Hello world!",
content = "bla bla bla",
description = "this is the changement !",
createdBy = citizen
)
val vote = Vote(
createdBy = citizen,
target = article,
note = -1
)
vote.serialize().contains("""Hello world!""") shouldBe true
vote.serialize().contains("-1") shouldBe true
println(vote.serialize())
}
@Test
fun `test Vote Article Deserialize`() {
val vote: Vote<Article> = voteJson.deserialize()!!
vote.id.toString() `should be equal to` "032acc3d-e8c5-4cb2-9297-bec913ff8d9b"
vote.note.toString() `should be equal to` "-1"
}
}

View File

@@ -1,5 +1,8 @@
package feature
import fr.dcproject.component.article.ArticleForUpdate
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.entity.*
import fr.dcproject.repository.CommentArticle
import fr.dcproject.utils.toUUID
@@ -9,10 +12,7 @@ import org.joda.time.DateTime
import org.koin.test.KoinTest
import org.koin.test.get
import java.util.*
import fr.dcproject.entity.Article as ArticleEntity
import fr.dcproject.entity.Comment as CommentEntity
import fr.dcproject.entity.User as UserEntity
import fr.dcproject.repository.Article as ArticleRepository
import fr.dcproject.repository.Citizen as CitizenRepository
class ArticleSteps : En, KoinTest {
@@ -88,7 +88,7 @@ class ArticleSteps : En, KoinTest {
("$firstName-$lastName".toLowerCase()).toLowerCase().replace(' ', '-')
) ?: error("Citizen not exist")
val comment: CommentEntity<ArticleEntity> = CommentEntity(
val comment: CommentForUpdate<ArticleForView, Citizen> = CommentForUpdate(
id = id ?: params?.get("id")?.let { UUID.fromString(it) } ?: UUID.randomUUID(),
createdBy = citizen,
target = article,

View File

@@ -26,13 +26,16 @@ class CitizenSteps : En, KoinTest {
createCitizen(firstName, lastName)
}
Given("I have citizen {word} {word} with") { firstName: String, lastName: String, extraData: DataTable? ->
createCitizen(firstName, lastName, extraData)
}
Given("I have citizen {word} {word} with ID {string}") { firstName: String, lastName: String, id: String ->
createCitizen(firstName, lastName, id = UUID.fromString(id))
}
}
private fun createCitizen(firstName: String, lastName: String, extraData: DataTable? = null, id: UUID? = null) {
val params = extraData?.asMap<String, String>(String::class.java, String::class.java)
val id: UUID = id ?: params?.get("id")?.let { UUID.fromString(it) } ?: UUID.randomUUID()
val email = params?.get("email") ?: ("$firstName-$lastName".toLowerCase()) + "@dc-project.fr"

View File

@@ -1,5 +1,6 @@
package feature
import fr.dcproject.component.article.ArticleRef
import fr.dcproject.entity.*
import fr.dcproject.repository.CommentConstitution
import fr.dcproject.utils.toUUID
@@ -9,7 +10,6 @@ import org.joda.time.DateTime
import org.koin.test.KoinTest
import org.koin.test.get
import java.util.*
import fr.dcproject.entity.Comment as CommentEntity
import fr.dcproject.entity.User as UserEntity
import fr.dcproject.repository.Citizen as CitizenRepository
import fr.dcproject.repository.Constitution as ConstitutionRepository
@@ -66,7 +66,7 @@ class ConstitutionSteps : En, KoinTest {
name = "My Title"
)
val constitution = ConstitutionSimple<CitizenSimple, ConstitutionSimple.TitleSimple<ArticleRef>>(
val constitution = ConstitutionSimple<CitizenWithUserI, ConstitutionSimple.TitleSimple<ArticleRef>>(
id = id ?: params?.get("id")?.toUUID() ?: UUID.randomUUID(),
title = "hello",
titles = mutableListOf(title1),
@@ -91,7 +91,7 @@ class ConstitutionSteps : En, KoinTest {
("$firstName-$lastName".toLowerCase()).toLowerCase().replace(' ', '-')
) ?: error("Citizen not exist")
val comment: CommentEntity<ConstitutionRef> = CommentEntity(
val comment: CommentForUpdate<ConstitutionRef, Citizen> = CommentForUpdate(
id = params?.get("id")?.let { UUID.fromString(it) } ?: UUID.randomUUID(),
createdBy = citizen,
target = constitution,

View File

@@ -1,8 +1,8 @@
package feature
import fr.dcproject.entity.ArticleRef
import fr.dcproject.component.article.ArticleRef
import fr.dcproject.entity.ConstitutionRef
import fr.dcproject.entity.Follow
import fr.dcproject.entity.FollowForUpdate
import fr.dcproject.utils.toUUID
import io.cucumber.java8.En
import org.koin.test.KoinTest
@@ -16,13 +16,13 @@ class FollowSteps : En, KoinTest {
Given("I have follow of {word} {word} on article {string}") { firstName: String, lastName: String, articleId: String ->
val username = "$firstName-$lastName".toLowerCase()
val citizen = get<CitizenRepository>().findByUsername(username) ?: error("Citizen not exist")
val follow = Follow(createdBy = citizen, target = ArticleRef(articleId.toUUID()))
val follow = FollowForUpdate(createdBy = citizen, target = ArticleRef(articleId.toUUID()))
get<FollowArticleRepository>().follow(follow)
}
Given("I have follow of {word} {word} on constitution {string}") { firstName: String, lastName: String, constitutionId: String ->
val username = "$firstName-$lastName".toLowerCase()
val citizen = get<CitizenRepository>().findByUsername(username) ?: error("Citizen not exist")
val follow = Follow(createdBy = citizen, target = ConstitutionRef(constitutionId.toUUID()))
val follow = FollowForUpdate(createdBy = citizen, target = ConstitutionRef(constitutionId.toUUID()))
get<FollowConstitutionRepository>().follow(follow)
}
}

View File

@@ -3,7 +3,7 @@ package feature
import com.auth0.jwt.JWT
import fr.dcproject.JwtConfig
import io.cucumber.java8.En
import io.ktor.http.HttpHeaders
import io.ktor.http.*
import org.koin.test.KoinTest
import org.koin.test.get
import fr.dcproject.repository.Citizen as CitizenRepository

View File

@@ -3,12 +3,9 @@ package feature
import com.jayway.jsonpath.JsonPath
import io.cucumber.datatable.DataTable
import io.cucumber.java8.En
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.setBody
import io.ktor.util.KtorExperimentalAPI
import io.ktor.http.*
import io.ktor.server.testing.*
import io.ktor.util.*
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
@@ -42,7 +39,11 @@ class KtorServerRequestSteps : En {
}
Then("the response status code should be {int}") { statusCode: Int ->
assertEquals(HttpStatusCode.fromValue(statusCode), KtorServerContext.defaultServer.call?.response?.status())
assertEquals(
HttpStatusCode.fromValue(statusCode),
KtorServerContext.defaultServer.call?.response?.status(),
KtorServerContext.defaultServer.call?.response?.content
)
}
Then("the response should contain object:") { expected: DataTable ->

View File

@@ -1,14 +1,15 @@
package feature
import fr.dcproject.entity.OpinionArticle
import fr.dcproject.component.article.ArticleRef
import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.entity.OpinionChoice
import fr.dcproject.entity.OpinionForUpdate
import fr.dcproject.utils.toUUID
import io.cucumber.datatable.DataTable
import io.cucumber.java8.En
import org.koin.test.KoinTest
import org.koin.test.get
import java.util.*
import fr.dcproject.repository.Article as ArticleRepository
import fr.dcproject.repository.Citizen as CitizenRepository
import fr.dcproject.repository.OpinionArticle as OpinionRepository
import fr.dcproject.repository.OpinionChoice as OpinionChoiceRepository
@@ -52,11 +53,11 @@ class OpinionSteps : En, KoinTest {
lastName: String,
id: String? = null
) {
val opinion = OpinionArticle(
val opinion = OpinionForUpdate(
id = id?.toUUID() ?: UUID.randomUUID(),
choice = get<OpinionChoiceRepository>().findOpinionsChoiceByName(opinionChoiceName)
?: error("Opinion Choice not exist"),
target = get<ArticleRepository>().findById(articleId.toUUID()) ?: error("Article not exist"),
target = ArticleRef(articleId.toUUID()),
createdBy = get<CitizenRepository>().findByUsername("$firstName-$lastName".toLowerCase().replace(' ', '-'))
?: error("Citizen not exist")
)
@@ -67,7 +68,7 @@ class OpinionSteps : En, KoinTest {
val params = extraInfo?.asMap<String, String>(String::class.java, String::class.java)
val username = params?.get("createdBy")?.toLowerCase()?.replace(' ', '-')
?: error("You must provide the 'createdBy' parameter")
val opinion = OpinionArticle(
val opinion = OpinionForUpdate(
choice = params["opinion"]?.let {
get<OpinionChoiceRepository>().findOpinionsChoiceByName(it) ?: error("Opinion Choice not exist")
} ?: error("You must provide the 'opinion' parameter"),

View File

@@ -1,12 +1,12 @@
package feature
import fr.dcproject.entity.Vote
import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.entity.VoteForUpdate
import fr.dcproject.utils.toUUID
import io.cucumber.java8.En
import org.koin.test.KoinTest
import org.koin.test.get
import java.util.*
import fr.dcproject.repository.Article as ArticleRepository
import fr.dcproject.repository.Citizen as CitizenRepository
import fr.dcproject.repository.VoteArticle as VoteRepository
@@ -22,7 +22,7 @@ class VoteSteps : En, KoinTest {
}
private fun createVote(note: Int, articleId: String, firstName: String, lastName: String, id: String? = null) {
val vote = Vote(
val vote = VoteForUpdate(
id = id?.toUUID() ?: UUID.randomUUID(),
note = note,
target = get<ArticleRepository>().findById(articleId.toUUID()) ?: error("Article not exist"),

View File

@@ -30,7 +30,7 @@ class WorkgroupSteps : En, KoinTest {
val creator = data["created_by"]?.let {
get<CitizenRepository>().findByUsername(it.toLowerCase().replace(' ', '-'))
} ?: kotlin.run {
} ?: run {
val username = "paul-langevin".toLowerCase() + UUID.randomUUID()
val user = User(
username = username,
@@ -46,12 +46,13 @@ class WorkgroupSteps : En, KoinTest {
}
}
val workgroup = WorkgroupSimple<CitizenRef>(
val workgroup = Workgroup(
id = UUID.fromString(data["id"] ?: UUID.randomUUID().toString()),
name = data["name"] ?: "Les Incoruptible",
description = data["description"] ?: "La vie est notre jeux",
createdBy = creator,
anonymous = (data["anonymous"] ?: false) == true
anonymous = (data["anonymous"] ?: false) == true,
members = listOf(Member(creator, listOf(Member.Role.MASTER)))
)
get<WorkgroupRepository>().upsert(workgroup)

View File

@@ -1,13 +1,14 @@
package fr.dcproject.security.voter
import fr.dcproject.citizenOrNull
import fr.dcproject.entity.*
import fr.dcproject.user
import fr.ktorVoter.Vote
import fr.ktorVoter.can
import fr.ktorVoter.canAll
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.entity.CitizenCart
import fr.dcproject.entity.CitizenI
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.postgresjson.connexion.Paginated
import io.ktor.application.ApplicationCall
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -16,33 +17,31 @@ import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import fr.dcproject.repository.Article as ArticleRepo
import java.util.*
import fr.dcproject.component.article.ArticleRepository as ArticleRepo
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tag("voter")
class ArticleVoterTest {
private val tesla = Citizen(
private val tesla = CitizenCart(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),
user = User(
username = "nicolas-tesla",
roles = listOf(UserI.Roles.ROLE_USER)
),
birthday = DateTime.now(),
email = "tesla@best.com",
name = CitizenI.Name("Nicolas", "Tesla")
)
private val einstein = Citizen(
private val einstein = CitizenCart(
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
),
birthday = DateTime.now(),
email = "einstein@best.com",
name = CitizenI.Name("Albert", "Einstein")
)
private fun getRepo(article: Article): ArticleRepo {
private fun getRepo(article: ArticleForView): ArticleRepo {
return mockk {
every { findVerionsByVersionsId(1, 1, any()) } returns Paginated(listOf(article), 0, 1, 1)
every { findVersionsByVersionId(1, 1, any()) } returns Paginated(listOf(article), 0, 1, 1)
}
}
@@ -51,26 +50,19 @@ class ArticleVoterTest {
}
@Test
fun `creator can be view the article`(): Unit {
val article = getArticle(tesla).apply { draft = true }
ArticleVoter(getRepo(article)).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
this(ArticleVoter.Action.VIEW, it, article) `should be` Vote.GRANTED
}
}
fun `creator can be view the article`() {
val article = getArticle(tesla).copy(draft = true)
ArticleVoter(getRepo(article))
.canView(article, tesla)
.vote `should be` GRANTED
}
@Test
fun `other user can be view the article`(): Unit = listOf(ArticleVoter(mockk())).run {
fun `other user can be view the article`() {
val article = getArticle(tesla)
mockk<ApplicationCall> {
every { user } returns einstein.user
}.let {
can(ArticleVoter.Action.VIEW, it, article) `should be` true
}
ArticleVoter(getRepo(article))
.canView(article, einstein)
.vote `should be` GRANTED
}
@Test
@@ -78,121 +70,93 @@ class ArticleVoterTest {
val article = getArticle(tesla)
val article2 = getArticle(tesla)
mockk<ApplicationCall> {
every { user } returns einstein.user
}.let {
canAll(ArticleVoter.Action.VIEW, it, listOf(article, article2)) `should be` true
}
ArticleVoter(getRepo(article))
.canView(listOf(article, article2), einstein)
.vote `should be` GRANTED
}
@Test
fun `the no creator can not be view the article on draft`(): Unit = listOf(ArticleVoter(mockk())).run {
val article = getArticle(tesla).apply { draft = true }
mockk<ApplicationCall> {
every { user } returns einstein.user
}.let {
can(ArticleVoter.Action.VIEW, it, article) `should be` false
}
fun `the no creator can not be view the article on draft`() {
val article = getArticle(tesla).copy(draft = true)
ArticleVoter(getRepo(article))
.canView(article, einstein)
.vote `should be` DENIED
}
@Test
fun `the no creator can not be view list of articles if one is on draft`(): Unit = listOf(ArticleVoter(mockk())).run {
fun `the no creator can not be view list of articles if one is on draft`() {
val article = getArticle(tesla)
val article2 = getArticle(tesla).apply { draft = true }
val article2 = getArticle(tesla).copy(draft = true)
mockk<ApplicationCall> {
every { user } returns einstein.user
}.let {
canAll(ArticleVoter.Action.VIEW, it, listOf(article, article2)) `should be` false
}
ArticleVoter(getRepo(article))
.canView(listOf(article, article2), einstein)
.vote `should be` DENIED
}
@Test
fun `can not view deleted article`(): Unit = listOf(ArticleVoter(mockk())).run {
val article = getArticle(tesla).apply { deletedAt = DateTime.now() }
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
can(ArticleVoter.Action.VIEW, it, article) `should be` false
}
fun `can not view deleted article`() {
val article = getArticle(tesla).copy(deletedAt = DateTime.now())
ArticleVoter(getRepo(article))
.canView(article, tesla)
.vote `should be` DENIED
}
@Test
fun `can delete article if owner`(): Unit = listOf(ArticleVoter(mockk())).run {
fun `can delete article if owner`() {
val article = getArticle(tesla)
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
can(ArticleVoter.Action.DELETE, it, article) `should be` true
}
ArticleVoter(getRepo(article))
.canDelete(article, tesla)
.vote `should be` GRANTED
}
@Test
fun `can not delete article if not owner`(): Unit = listOf(ArticleVoter(mockk())).run {
val article = getArticle(tesla).apply { deletedAt = DateTime.now() }
mockk<ApplicationCall> {
every { user } returns einstein.user
}.let {
can(ArticleVoter.Action.DELETE, it, article) `should be` false
}
fun `can not delete article if not owner`() {
val article = getArticle(tesla).copy(deletedAt = DateTime.now())
ArticleVoter(getRepo(article))
.canDelete(article, einstein)
.code `should be` "article.delete.notYours"
}
@Test
fun `can create article if logged`(): Unit = listOf(ArticleVoter(mockk())).run {
fun `can create article if logged`() {
val article = getArticle(tesla)
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
can(ArticleVoter.Action.CREATE, it, article) `should be` true
}
ArticleVoter(getRepo(article))
.canUpsert(article, tesla)
.vote `should be` GRANTED
}
@Test
fun `can not create article if not logged`(): Unit = listOf(ArticleVoter(mockk())).run {
fun `can not create article if not logged`() {
val article = getArticle(tesla)
mockk<ApplicationCall> {
every { user } returns null
}.let {
can(ArticleVoter.Action.CREATE, it, article) `should be` false
}
ArticleVoter(getRepo(article))
.canUpsert(article, null)
.code `should be` "article.create.notConnected"
}
@Test
fun `can update article if yours`(): Unit {
val article = getArticle(tesla)
listOf(ArticleVoter(getRepo(article))).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(ArticleVoter.Action.UPDATE, it, article) `should be` true
}
}
ArticleVoter(getRepo(article))
.canUpsert(article, tesla)
.vote `should be` GRANTED
}
@Test
fun `can not update article if not yours`(): Unit {
val article = getArticle(tesla)
listOf(ArticleVoter(getRepo(article))).run {
mockk<ApplicationCall> {
every { user } returns einstein.user
every { citizenOrNull } returns einstein
}.let {
can(ArticleVoter.Action.UPDATE, it, article) `should be` false
}
}
ArticleVoter(getRepo(article))
.canUpsert(article, einstein)
.code `should be` "article.update.notYours"
}
private fun getArticle(createdBy: CitizenBasic = tesla) = Article(
private fun getArticle(createdBy: CitizenCart = tesla) = ArticleForView(
id = UUID.randomUUID(),
title = "Hello world",
content = "Super",
description = "I Rocks",
createdBy = createdBy
createdBy = createdBy,
opinions = mapOf(),
versionId = UUID.randomUUID(),
versionNumber = 1
)
}

View File

@@ -5,12 +5,9 @@ import fr.dcproject.entity.CitizenI
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.user
import fr.ktorVoter.ActionI
import fr.ktorVoter.Vote
import fr.ktorVoter.can
import fr.ktorVoter.canAll
import io.ktor.application.ApplicationCall
import io.ktor.locations.KtorExperimentalLocationsAPI
import fr.ktorVoter.*
import io.ktor.application.*
import io.ktor.locations.*
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -19,6 +16,7 @@ import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
@KtorExperimentalLocationsAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -50,8 +48,9 @@ class CitizenVoterTest {
),
birthday = DateTime.now(),
email = "curie@best.com",
name = CitizenI.Name("Marie", "Curie")
).apply { deletedAt = DateTime.now() }
name = CitizenI.Name("Marie", "Curie"),
deletedAt = DateTime.now()
)
init {
mockkStatic("fr.dcproject.ApplicationContextKt")
@@ -63,8 +62,8 @@ class CitizenVoterTest {
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
this(CitizenVoter.Action.VIEW, it, einstein) `should be` Vote.GRANTED
this(p, it, einstein) `should be` Vote.ABSTAIN
this(CitizenVoter.Action.VIEW, it, einstein).vote `should be` Vote.GRANTED
this(p, it, einstein).vote `should be` Vote.ABSTAIN
}
}
@@ -109,7 +108,9 @@ class CitizenVoterTest {
mockk<ApplicationCall> {
every { user } returns einstein.user
}.let {
can(CitizenVoter.Action.UPDATE, it, tesla) `should be` false
assertThrows<OneOrMoreVoterDeniedException> {
assertCan(CitizenVoter.Action.UPDATE, it, tesla)
}
}
}

View File

@@ -1,14 +1,15 @@
package fr.dcproject.security.voter
import fr.dcproject.citizenOrNull
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRef
import fr.dcproject.entity.*
import fr.dcproject.user
import fr.ktorVoter.ActionI
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.*
import fr.ktorVoter.Vote
import fr.ktorVoter.can
import fr.ktorVoter.canAll
import fr.postgresjson.connexion.Paginated
import io.ktor.application.ApplicationCall
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.application.*
import io.ktor.locations.*
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -17,13 +18,15 @@ import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import fr.dcproject.repository.Article as ArticleRepo
import org.junit.jupiter.api.assertThrows
import java.util.*
import fr.dcproject.component.article.ArticleRepository as ArticleRepo
@KtorExperimentalLocationsAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tag("voter")
internal class CommentVoterTest {
private val tesla = CitizenBasic(
private val tesla = Citizen(
user = User(
username = "nicolas-tesla",
roles = listOf(UserI.Roles.ROLE_USER)
@@ -32,7 +35,8 @@ internal class CommentVoterTest {
email = "tesla@best.com",
name = CitizenI.Name("Nicolas", "Tesla")
)
private val einstein = CitizenBasic(
private val einstein = Citizen(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
@@ -42,45 +46,60 @@ internal class CommentVoterTest {
name = CitizenI.Name("Albert", "Einstein")
)
private val article1 = Article(
private val einstein2 = CitizenCart(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
),
name = CitizenI.Name("Albert", "Einstein")
)
private val article1 = ArticleForView(
content = "Hi",
createdBy = einstein,
createdBy = einstein2,
description = "blablabla",
title = "Super article"
)
private val comment1 = Comment(
private val comment1 = CommentForView(
content = "Hello",
createdBy = tesla,
target = article1
)
private val comment2 = Comment(
private val commentForUpdate = CommentForUpdate(
content = "Hello",
createdBy = tesla,
target = article1
)
private val comment2 = CommentForView(
content = "Hello2",
createdBy = einstein,
target = article1
)
private val commentTargetDeleted = Comment(
private val commentTargetDeleted = CommentForView(
content = "Hello",
createdBy = tesla,
target = Article(
target = ArticleForView(
content = "Hi",
createdBy = einstein,
createdBy = einstein2,
description = "blablabla",
title = "Super article",
workgroup = null
).apply { deletedAt = DateTime.now() }
).copy(deletedAt = DateTime.now())
)
private val commentTargetNoUser = Comment(
private val commentTargetNoUser = CommentForView(
content = "Hello",
createdBy = tesla,
target = ArticleRef()
)
private val repoArticle1 = mockk<ArticleRepo> {
every { findVerionsByVersionsId(1, 1, any()) } returns Paginated(listOf(article1), 0, 1, 1)
every { findVersionsByVersionId(1, 1, any()) } returns Paginated(listOf(article1), 0, 1, 1)
}
init {
@@ -91,19 +110,19 @@ internal class CommentVoterTest {
fun `support comment`(): Unit = CommentVoter().run {
val p = object : ActionI {}
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
this(CommentVoter.Action.VIEW, it, comment1) `should be` Vote.GRANTED
this(CommentVoter.Action.VIEW, it, article1) `should be` Vote.ABSTAIN
this(p, it, comment1) `should be` Vote.ABSTAIN
this(CommentVoter.Action.VIEW, it, comment1).vote `should be` Vote.GRANTED
this(CommentVoter.Action.VIEW, it, article1).vote `should be` Vote.ABSTAIN
this(p, it, comment1).vote `should be` Vote.ABSTAIN
}
}
@Test
fun `can be view the comment`(): Unit {
listOf(CommentVoter(), ArticleVoter(repoArticle1)).run {
listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(CommentVoter.Action.VIEW, it, comment1) `should be` true
}
@@ -113,7 +132,7 @@ internal class CommentVoterTest {
@Test
fun `can be view the comment list`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns einstein.user
every { citizenOrNull } returns einstein
}.let {
canAll(CommentVoter.Action.VIEW, it, listOf(comment1)) `should be` true
}
@@ -122,7 +141,7 @@ internal class CommentVoterTest {
@Test
fun `can be update your comment`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(CommentVoter.Action.UPDATE, it, comment1) `should be` true
}
@@ -131,7 +150,7 @@ internal class CommentVoterTest {
@Test
fun `can not be update other comment`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns einstein.user
every { citizenOrNull } returns einstein
}.let {
can(CommentVoter.Action.UPDATE, it, comment1) `should be` false
}
@@ -140,43 +159,34 @@ internal class CommentVoterTest {
@Test
fun `can not be delete your comment`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(CommentVoter.Action.DELETE, it, comment1) `should be` false
}
}
@Test
fun `can be create a comment`(): Unit = listOf(CommentVoter(), ArticleVoter(repoArticle1)).run {
fun `can be create a comment`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(CommentVoter.Action.CREATE, it, comment1) `should be` true
}
}
@Test
fun `can not be create a comment if target is deleted`(): Unit = listOf(CommentVoter(), ArticleVoter(repoArticle1)).run {
fun `can not be create a comment if target is deleted`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(CommentVoter.Action.CREATE, it, commentTargetDeleted) `should be` false
}
}
@Test
fun `can not be create a comment if target has no user`(): Unit = listOf(CommentVoter(), ArticleVoter(repoArticle1)).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
can(CommentVoter.Action.CREATE, it, commentTargetNoUser) `should be` false
}
}
@Test
fun `can not be create a comment with other creator`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns einstein.user
every { citizenOrNull } returns einstein
}.let {
can(CommentVoter.Action.CREATE, it, comment1) `should be` false
}
@@ -185,16 +195,18 @@ internal class CommentVoterTest {
@Test
fun `can not be create a comment if is null`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns einstein.user
every { citizenOrNull } returns einstein
}.let {
can(CommentVoter.Action.CREATE, it, null) `should be` false
assertThrows<NoSubjectDefinedException> {
assertCan(CommentVoter.Action.CREATE, it, null)
}
}
}
@Test
fun `can not be create a comment if not connected`(): Unit = listOf(CommentVoter()).run {
mockk<ApplicationCall> {
every { user } returns null
every { citizenOrNull } returns null
}.let {
can(CommentVoter.Action.CREATE, it, comment1) `should be` false
}

View File

@@ -1,13 +1,15 @@
package fr.dcproject.security.voter
import fr.dcproject.citizenOrNull
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.entity.*
import fr.dcproject.user
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.ActionI
import fr.ktorVoter.Vote
import fr.ktorVoter.can
import fr.ktorVoter.canAll
import io.ktor.application.ApplicationCall
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.application.*
import io.ktor.locations.*
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -16,6 +18,8 @@ import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
import java.util.*
@KtorExperimentalLocationsAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -31,8 +35,19 @@ internal class FollowVoterTest {
name = CitizenI.Name("Nicolas", "Tesla"),
followAnonymous = false
)
private val tesla2 = Citizen(
user = User(
username = "nicolas-tesla",
roles = listOf(UserI.Roles.ROLE_USER)
),
birthday = DateTime.now(),
email = "tesla@best.com",
name = CitizenI.Name("Nicolas", "Tesla"),
followAnonymous = false
)
private val einstein = CitizenBasic(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
@@ -43,9 +58,30 @@ internal class FollowVoterTest {
followAnonymous = true
)
private val article1 = Article(
private val einstein2 = CitizenCart(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
),
name = CitizenI.Name("Albert", "Einstein")
)
private val einstein3 = Citizen(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
),
birthday = DateTime.now(),
email = "einstein@best.com",
name = CitizenI.Name("Albert", "Einstein"),
followAnonymous = true
)
private val article1 = ArticleForView(
content = "Hi",
createdBy = einstein,
createdBy = einstein2,
description = "blablabla",
title = "Super article"
)
@@ -68,18 +104,20 @@ internal class FollowVoterTest {
fun `support follow`(): Unit = FollowVoter().run {
val p = object : ActionI {}
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla2
}.let {
this(FollowVoter.Action.VIEW, it, follow1) `should be` Vote.GRANTED
this(FollowVoter.Action.VIEW, it, article1) `should be` Vote.ABSTAIN
this(p, it, follow1) `should be` Vote.ABSTAIN
this(FollowVoter.Action.VIEW, it, follow1).vote `should be` Vote.GRANTED
assertThrows<NoSubjectDefinedException> {
this(FollowVoter.Action.VIEW, it, article1)
}
this(p, it, follow1).vote `should be` Vote.ABSTAIN
}
}
@Test
fun `can be view the follow`(): Unit = listOf(FollowVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla2
}.let {
can(FollowVoter.Action.VIEW, it, follow1) `should be` true
}
@@ -88,7 +126,7 @@ internal class FollowVoterTest {
@Test
fun `can be view the follow list`(): Unit = listOf(FollowVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla2
}.let {
canAll(FollowVoter.Action.VIEW, it, listOf(follow1)) `should be` true
}
@@ -97,7 +135,7 @@ internal class FollowVoterTest {
@Test
fun `can be view your anonymous follow`(): Unit = listOf(FollowVoter()).run {
mockk<ApplicationCall> {
every { user } returns einstein.user
every { citizenOrNull } returns einstein3
}.let {
can(FollowVoter.Action.VIEW, it, followAnon) `should be` true
}
@@ -106,7 +144,7 @@ internal class FollowVoterTest {
@Test
fun `can not be view the anonymous follow of other`(): Unit = listOf(FollowVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla2
}.let {
can(FollowVoter.Action.VIEW, it, followAnon) `should be` false
}
@@ -115,7 +153,7 @@ internal class FollowVoterTest {
@Test
fun `can be follow article`(): Unit = listOf(FollowVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla2
}.let {
can(FollowVoter.Action.CREATE, it, follow1) `should be` true
}
@@ -124,7 +162,7 @@ internal class FollowVoterTest {
@Test
fun `can not be follow article if not connected`(): Unit = listOf(FollowVoter()).run {
mockk<ApplicationCall> {
every { user } returns null
every { citizenOrNull } returns null
}.let {
can(FollowVoter.Action.CREATE, it, follow1) `should be` false
}
@@ -133,7 +171,7 @@ internal class FollowVoterTest {
@Test
fun `can be unfollow article`(): Unit = listOf(FollowVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla2
}.let {
can(FollowVoter.Action.DELETE, it, follow1) `should be` true
}
@@ -142,7 +180,7 @@ internal class FollowVoterTest {
@Test
fun `can not be unfollow article if not connected`(): Unit = listOf(FollowVoter()).run {
mockk<ApplicationCall> {
every { user } returns null
every { citizenOrNull } returns null
}.let {
can(FollowVoter.Action.DELETE, it, follow1) `should be` false
}

View File

@@ -1,13 +1,14 @@
package fr.dcproject.security.voter
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.entity.*
import fr.dcproject.user
import fr.ktorVoter.ActionI
import fr.ktorVoter.Vote
import fr.ktorVoter.can
import fr.ktorVoter.canAll
import io.ktor.application.ApplicationCall
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.application.*
import io.ktor.locations.*
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -16,12 +17,14 @@ import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.util.*
@KtorExperimentalLocationsAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tag("voter")
internal class OpinionChoiceVoterTest {
private val tesla = CitizenBasic(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),
user = User(
username = "nicolas-tesla",
roles = listOf(UserI.Roles.ROLE_USER)
@@ -32,9 +35,18 @@ internal class OpinionChoiceVoterTest {
followAnonymous = false
)
private val article1 = Article(
private val tesla2 = CitizenCart(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),
user = User(
username = "nicolas-tesla",
roles = listOf(UserI.Roles.ROLE_USER)
),
name = CitizenI.Name("Nicolas", "Tesla")
)
private val article1 = ArticleForView(
content = "Hi",
createdBy = tesla,
createdBy = tesla2,
description = "blablabla",
title = "Super article"
)
@@ -54,9 +66,9 @@ internal class OpinionChoiceVoterTest {
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
this(OpinionChoiceVoter.Action.VIEW, it, choice1) `should be` Vote.GRANTED
this(OpinionChoiceVoter.Action.VIEW, it, article1) `should be` Vote.ABSTAIN
this(p, it, choice1) `should be` Vote.ABSTAIN
this(OpinionChoiceVoter.Action.VIEW, it, choice1).vote `should be` Vote.GRANTED
this(OpinionChoiceVoter.Action.VIEW, it, article1).vote `should be` Vote.ABSTAIN
this(p, it, choice1).vote `should be` Vote.ABSTAIN
}
}

View File

@@ -1,13 +1,13 @@
package fr.dcproject.security.voter
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.entity.*
import fr.dcproject.user
import fr.ktorVoter.ActionI
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.*
import fr.ktorVoter.Vote
import fr.ktorVoter.can
import fr.ktorVoter.canAll
import io.ktor.application.ApplicationCall
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.application.*
import io.ktor.locations.*
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -16,6 +16,8 @@ import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
import java.util.*
@KtorExperimentalLocationsAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -33,6 +35,7 @@ internal class OpinionVoterTest {
)
private val einstein = CitizenBasic(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
@@ -43,9 +46,18 @@ internal class OpinionVoterTest {
followAnonymous = true
)
private val article1 = Article(
private val einstein2 = CitizenCart(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
),
name = CitizenI.Name("Albert", "Einstein")
)
private val article1 = ArticleForView(
content = "Hi",
createdBy = einstein,
createdBy = einstein2,
description = "blablabla",
title = "Super article"
)
@@ -69,10 +81,10 @@ internal class OpinionVoterTest {
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
this(OpinionVoter.Action.VIEW, it, opinion1) `should be` Vote.GRANTED
this(OpinionVoter.Action.VIEW, it, article1) `should be` Vote.GRANTED
this(OpinionVoter.Action.VIEW, it, einstein) `should be` Vote.ABSTAIN
this(p, it, opinion1) `should be` Vote.ABSTAIN
this(OpinionVoter.Action.VIEW, it, opinion1).vote `should be` Vote.GRANTED
this(OpinionVoter.Action.VIEW, it, article1).vote `should be` Vote.GRANTED
this(OpinionVoter.Action.VIEW, it, einstein).vote `should be` Vote.ABSTAIN
this(p, it, opinion1).vote `should be` Vote.ABSTAIN
}
}
@@ -90,7 +102,9 @@ internal class OpinionVoterTest {
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
can(OpinionVoter.Action.VIEW, it, null) `should be` false
assertThrows<NoSubjectDefinedException> {
assertCan(OpinionVoter.Action.VIEW, it, null)
}
}
}

View File

@@ -1,14 +1,16 @@
package fr.dcproject.security.voter
import fr.dcproject.citizenOrNull
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRef
import fr.dcproject.entity.*
import fr.dcproject.user
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.ActionI
import fr.ktorVoter.Vote
import fr.ktorVoter.can
import fr.ktorVoter.canAll
import fr.postgresjson.connexion.Paginated
import io.ktor.application.ApplicationCall
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.application.*
import io.ktor.locations.*
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
@@ -17,13 +19,27 @@ import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
import java.util.*
import fr.dcproject.entity.Vote as VoteEntity
@KtorExperimentalLocationsAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tag("voter")
internal class VoteVoterTest {
private val tesla = CitizenBasic(
private val tesla = Citizen(
id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"),
user = User(
username = "nicolas-tesla",
roles = listOf(UserI.Roles.ROLE_USER)
),
birthday = DateTime.now(),
email = "tesla@best.com",
name = CitizenI.Name("Nicolas", "Tesla"),
followAnonymous = false
)
private val tesla3 = CitizenBasic(
id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"),
user = User(
username = "nicolas-tesla",
roles = listOf(UserI.Roles.ROLE_USER)
@@ -34,7 +50,8 @@ internal class VoteVoterTest {
followAnonymous = false
)
private val einstein = CitizenBasic(
private val einstein = Citizen(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
@@ -45,31 +62,46 @@ internal class VoteVoterTest {
followAnonymous = true
)
private val article1 = Article(
private val einstein2 = CitizenCart(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
),
name = CitizenI.Name("Albert", "Einstein")
)
private val article1 = ArticleForView(
content = "Hi",
createdBy = einstein,
createdBy = einstein2,
description = "blablabla",
title = "Super article"
)
private val vote1 = VoteEntity(
createdBy = tesla3,
target = article1,
note = 1
)
private val voteForUpdate = VoteForUpdate(
createdBy = tesla,
target = article1,
note = 1
)
private val voteOnDeleted = VoteEntity(
private val voteOnDeleted = VoteForUpdate(
createdBy = tesla,
target = Article(
target = ArticleForView(
content = "Hi",
createdBy = einstein,
createdBy = einstein2,
description = "blablabla",
title = "Super article"
).apply { deletedAt = DateTime.now() },
).copy(deletedAt = DateTime.now()),
note = 1
)
private val voteWithoutUser = VoteEntity(
private val voteWithoutTargetUser = VoteForUpdate(
createdBy = tesla,
target = ArticleRef(),
note = 1
@@ -83,18 +115,18 @@ internal class VoteVoterTest {
fun `support vote`(): Unit = VoteVoter().run {
val p = object : ActionI {}
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
this(VoteVoter.Action.VIEW, it, vote1) `should be` Vote.GRANTED
this(VoteVoter.Action.VIEW, it, article1) `should be` Vote.ABSTAIN
this(p, it, vote1) `should be` Vote.ABSTAIN
this(VoteVoter.Action.VIEW, it, vote1).vote `should be` Vote.GRANTED
this(VoteVoter.Action.VIEW, it, article1).vote `should be` Vote.ABSTAIN
this(p, it, vote1).vote `should be` Vote.ABSTAIN
}
}
@Test
fun `can be view your the vote`(): Unit = listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(VoteVoter.Action.VIEW, it, vote1) `should be` true
}
@@ -103,7 +135,7 @@ internal class VoteVoterTest {
@Test
fun `can not be view vote of other`(): Unit = listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns einstein.user
every { citizenOrNull } returns einstein
}.let {
can(VoteVoter.Action.VIEW, it, vote1) `should be` false
}
@@ -112,16 +144,18 @@ internal class VoteVoterTest {
@Test
fun `can be not view the vote if is null`(): Unit = listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(VoteVoter.Action.VIEW, it, null) `should be` false
assertThrows<NoSubjectDefinedException> {
can(VoteVoter.Action.VIEW, it, null)
}
}
}
@Test
fun `can be view your votes list`(): Unit = listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
canAll(VoteVoter.Action.VIEW, it, listOf(vote1)) `should be` true
}
@@ -129,11 +163,11 @@ internal class VoteVoterTest {
@Test
fun `can be vote an article`(): Unit {
listOf(VoteVoter(), ArticleVoter(mockk())).run {
listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(VoteVoter.Action.CREATE, it, vote1) `should be` true
can(VoteVoter.Action.CREATE, it, voteForUpdate) `should be` true
}
}
}
@@ -141,36 +175,38 @@ internal class VoteVoterTest {
@Test
fun `can not be vote if not connected`(): Unit = listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns null
every { citizenOrNull } returns null
}.let {
can(VoteVoter.Action.CREATE, it, vote1) `should be` false
can(VoteVoter.Action.CREATE, it, voteForUpdate) `should be` false
}
}
@Test
fun `can not be vote an article if article is deleted`(): Unit = listOf(VoteVoter(), ArticleVoter(mockk())).run {
fun `can not be vote an article if article is deleted`(): Unit = listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(VoteVoter.Action.CREATE, it, voteOnDeleted) `should be` false
}
}
@Test
fun `can not be vote an article if article have no user`(): Unit = listOf(VoteVoter(), ArticleVoter(mockk())).run {
fun `can not be vote an article if article have no user`(): Unit = listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(VoteVoter.Action.CREATE, it, voteWithoutUser) `should be` false
assertThrows<NoSubjectDefinedException> {
can(VoteVoter.Action.CREATE, it, voteWithoutTargetUser)
}
}
}
@Test
fun `can not be comment an article if article is deleted`(): Unit = listOf(VoteVoter(), ArticleVoter(mockk())).run {
fun `can not be comment an article if article is deleted`(): Unit = listOf(VoteVoter()).run {
mockk<ApplicationCall> {
every { user } returns tesla.user
every { citizenOrNull } returns tesla
}.let {
can(CommentVoter.Action.CREATE, it, voteOnDeleted) `should be` false
can(VoteVoter.Action.CREATE, it, voteOnDeleted) `should be` false
}
}
}

View File

@@ -1,22 +1,22 @@
package fr.dcproject.security.voter
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.entity.*
import fr.dcproject.user
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.ActionI
import fr.ktorVoter.Vote
import fr.ktorVoter.VoterException
import fr.ktorVoter.can
import io.ktor.application.ApplicationCall
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.application.*
import io.ktor.locations.*
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.*
import java.util.*
import fr.dcproject.entity.Workgroup as WorkgroupEntity
@KtorExperimentalLocationsAPI
@@ -35,6 +35,7 @@ internal class WorkgroupVoterTest {
)
private val einstein = CitizenBasic(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
@@ -45,9 +46,18 @@ internal class WorkgroupVoterTest {
followAnonymous = true
)
private val article1 = Article(
private val einstein2 = CitizenCart(
id = UUID.fromString("319f1226-8f47-4df3-babd-2c7671ad0fbc"),
user = User(
username = "albert-einstein",
roles = listOf(UserI.Roles.ROLE_USER)
),
name = CitizenI.Name("Albert", "Einstein")
)
private val article1 = ArticleForView(
content = "Hi",
createdBy = einstein,
createdBy = einstein2,
description = "blablabla",
title = "Super article"
)
@@ -80,9 +90,9 @@ internal class WorkgroupVoterTest {
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
this(WorkgroupVoter.Action.VIEW, it, workgroupPublic) `should be` Vote.GRANTED
this(WorkgroupVoter.Action.VIEW, it, article1) `should be` Vote.ABSTAIN
this(p, it, workgroupPublic) `should be` Vote.ABSTAIN
this(WorkgroupVoter.Action.VIEW, it, workgroupPublic).vote `should be` Vote.GRANTED
this(WorkgroupVoter.Action.VIEW, it, article1).vote `should be` Vote.ABSTAIN
this(p, it, workgroupPublic).vote `should be` Vote.ABSTAIN
}
}
@@ -127,7 +137,9 @@ internal class WorkgroupVoterTest {
mockk<ApplicationCall> {
every { user } returns tesla.user
}.let {
can(WorkgroupVoter.Action.VIEW, it, null) `should be` false
assertThrows<NoSubjectDefinedException> {
can(WorkgroupVoter.Action.VIEW, it, null)
}
}
}

View File

@@ -17,7 +17,7 @@ Feature: citizens routes
Scenario: Can get connected citizen
Given I have citizen Henri Becquerel with ID "47356809-c8ef-4649-8b99-1c5cb9886d38"
Given I am authenticated as Henri Becquerel
And I am authenticated as Henri Becquerel
When I send a GET request to "/citizens/current"
Then the response status code should be 200
And the response should contain object:

View File

@@ -2,7 +2,7 @@
Feature: follow Article
# Article
Scenario: The route for follow article must response a 201 and return
Scenario: The route for follow article must response a 201
Given I have citizen Louis Pasteur
And I am authenticated as Louis Pasteur
And I have article

View File

@@ -44,7 +44,8 @@ Feature: Workgroup
Given I have citizen Werner Heisenberg
And I am authenticated as Werner Heisenberg
And I have workgroup:
| id | ab469134-bf14-4856-b093-ae1aa990f977 |
| id | ab469134-bf14-4856-b093-ae1aa990f977 |
| created_by | Werner Heisenberg |
When I send a DELETE request to "/workgroups/ab469134-bf14-4856-b093-ae1aa990f977"
Then the response status code should be 204
And The workgroup "ab469134-bf14-4856-b093-ae1aa990f977" not exists
@@ -66,8 +67,9 @@ Feature: Workgroup
And I have citizen Alessandro Volta with ID "b5bac515-45d4-4aeb-9b6d-2627a0bbc419"
And I am authenticated as Blaise Pascal
And I have workgroup:
| id | b0ea1922-3bc6-44e2-aa7c-40158998cfbb |
| name | Les bonobos |
| id | b0ea1922-3bc6-44e2-aa7c-40158998cfbb |
| name | Les bonobos |
| created_by | Blaise Pascal |
When I send a POST request to "/workgroups/b0ea1922-3bc6-44e2-aa7c-40158998cfbb/members" with body:
"""
[
@@ -84,13 +86,15 @@ Feature: Workgroup
Then the response status code should be 201
Scenario: Can remove member to workgroup
Given I have citizen Heinrich Hertz
Given I have citizen Heinrich Hertz with
| id | 94f92424-c257-4582-907c-98564a8c4ac9 |
And I have citizen William Thomson with ID "87909ba3-2069-431c-9924-219fd8411cf2"
And I have citizen Paul Dirac with ID "1baf48bb-02bc-4d8f-ac86-33335354f5e7"
And I am authenticated as Heinrich Hertz
And I have workgroup:
| id | b6c975df-dd44-4e99-adc1-f605746b0e11 |
| name | Les Tacos |
| id | b6c975df-dd44-4e99-adc1-f605746b0e11 |
| name | Les Tacos |
| created_by | Heinrich Hertz |
And I have members in workgroup "b6c975df-dd44-4e99-adc1-f605746b0e11":
| 87909ba3-2069-431c-9924-219fd8411cf2 |
| 1baf48bb-02bc-4d8f-ac86-33335354f5e7 |
@@ -104,9 +108,10 @@ Feature: Workgroup
]
"""
Then the response status code should be 200
And the response should contain object:
| $.[0]citizen.id | 1baf48bb-02bc-4d8f-ac86-33335354f5e7 |
And the JSON should have 2 items
And the response should contain object:
| $.[0]citizen.id | 94f92424-c257-4582-907c-98564a8c4ac9 |
| $.[1]citizen.id | 1baf48bb-02bc-4d8f-ac86-33335354f5e7 |
Scenario: Can update members on workgroup
Given I have citizen Leon Foucault
@@ -115,8 +120,9 @@ Feature: Workgroup
And I have citizen Georg Ohm with ID "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1"
And I am authenticated as Leon Foucault
And I have workgroup:
| id | 784fe6bc-7635-4ae2-b080-3a4743b998bf |
| name | Les Tacos |
| id | 784fe6bc-7635-4ae2-b080-3a4743b998bf |
| name | Les Tacos |
| created_by | Leon Foucault |
And I have members in workgroup "784fe6bc-7635-4ae2-b080-3a4743b998bf":
| be3b0926-8628-4426-804a-75188a6eb315 |
| d9671eca-abaf-4b67-9230-3ece700c1ddb |
@@ -134,7 +140,7 @@ Feature: Workgroup
]
"""
Then the response status code should be 200
And the JSON should have 2 items
And the response should contain object:
| $.[0]citizen.id | be3b0926-8628-4426-804a-75188a6eb315 |
| $.[1]citizen.id | b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1 |
And the JSON should have 2 items