From 4c2da6ab71ca850a9df624a0ad7848fc2417e234 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Wed, 28 Aug 2019 23:32:43 +0200 Subject: [PATCH] Implement comment constitution fix bugs --- src/main/kotlin/fr/dcproject/Application.kt | 17 +---- src/main/kotlin/fr/dcproject/Module.kt | 4 +- .../fr/dcproject/entity/Constitution.kt | 2 +- .../kotlin/fr/dcproject/repository/Comment.kt | 32 +++++++--- .../fr/dcproject/repository/Constitution.kt | 4 +- .../kotlin/fr/dcproject/routes/Comment.kt | 5 +- .../fr/dcproject/routes/CommentArticle.kt | 4 +- .../dcproject/routes/CommentConstitution.kt | 55 ++++++++++++++++ .../security/voter/ConstitutionVoter.kt | 47 ++++++++++++++ .../sql/functions/article/upsert_article.sql | 28 ++++++--- .../sql/functions/comment/edit_comment.sql | 14 ++--- .../find_comments_article_by_citizen.sql | 27 -------- .../constitution/upsert_constitution.sql | 16 ++++- .../helpers/find_reference_by_id.sql | 4 +- src/test/kotlin/feature/ConstitutionSteps.kt | 62 +++++++++++++++++++ ...comment.feature => commentArticle.feature} | 7 +-- .../feature/commentConstitution.feature | 35 +++++++++++ 17 files changed, 277 insertions(+), 86 deletions(-) create mode 100644 src/main/kotlin/fr/dcproject/routes/CommentConstitution.kt create mode 100644 src/main/kotlin/fr/dcproject/security/voter/ConstitutionVoter.kt delete mode 100644 src/main/resources/sql/functions/comment/find_comments_article_by_citizen.sql create mode 100644 src/test/kotlin/feature/ConstitutionSteps.kt rename src/test/resources/feature/{comment.feature => commentArticle.feature} (95%) create mode 100644 src/test/resources/feature/commentConstitution.feature diff --git a/src/main/kotlin/fr/dcproject/Application.kt b/src/main/kotlin/fr/dcproject/Application.kt index e80e367..f156011 100644 --- a/src/main/kotlin/fr/dcproject/Application.kt +++ b/src/main/kotlin/fr/dcproject/Application.kt @@ -12,11 +12,7 @@ import fr.dcproject.entity.Citizen import fr.dcproject.entity.Constitution import fr.dcproject.entity.User import fr.dcproject.routes.* -import fr.dcproject.security.voter.ArticleVoter -import fr.dcproject.security.voter.AuthorizationVoter -import fr.dcproject.security.voter.CitizenVoter -import fr.dcproject.security.voter.CommentVoter -import fr.postgresjson.migration.Migrations +import fr.dcproject.security.voter.* import io.ktor.application.Application import io.ktor.application.ApplicationCall import io.ktor.application.call @@ -40,7 +36,6 @@ import java.util.* import java.util.concurrent.CompletionException import fr.dcproject.repository.Article as RepositoryArticle import fr.dcproject.repository.Citizen as RepositoryCitizen -import fr.dcproject.repository.CommentGeneric as CommentGenericRepository import fr.dcproject.repository.Constitution as RepositoryConstitution import fr.dcproject.repository.User as UserRepository @@ -92,14 +87,6 @@ fun Application.module() { } } - convert { - decode { values, _ -> - val id = values.singleOrNull()?.let { UUID.fromString(it) } - ?: throw InternalError("Cannot convert $values to UUID") - get().findById(id) ?: throw InternalError("Comment $values not found") - } - } - convert { decode { values, _ -> val id = values.singleOrNull()?.let { UUID.fromString(it) } @@ -115,6 +102,7 @@ fun Application.module() { install(AuthorizationVoter) { voters = mutableListOf( ArticleVoter(), + ConstitutionVoter(), CitizenVoter(), CommentVoter() ) @@ -166,6 +154,7 @@ fun Application.module() { followConstitution(get()) comment(get()) commentArticle(get()) + commentConstitution(get()) } } diff --git a/src/main/kotlin/fr/dcproject/Module.kt b/src/main/kotlin/fr/dcproject/Module.kt index 1798b92..df48f94 100644 --- a/src/main/kotlin/fr/dcproject/Module.kt +++ b/src/main/kotlin/fr/dcproject/Module.kt @@ -8,6 +8,7 @@ import org.koin.dsl.module import fr.dcproject.repository.Article as ArticleRepository import fr.dcproject.repository.Citizen as CitizenRepository import fr.dcproject.repository.CommentArticle as CommentArticleRepository +import fr.dcproject.repository.CommentConstitution as CommentConstitutionRepository import fr.dcproject.repository.CommentGeneric as CommentGenericRepository import fr.dcproject.repository.Constitution as ConstitutionRepository import fr.dcproject.repository.FollowArticle as FollowArticleRepository @@ -37,8 +38,7 @@ val Module = module { single { FollowConstitutionRepository(get()) } single { CommentGenericRepository(get()) } single { CommentArticleRepository(get()) } - // TODO implment constitution -// single { CommentConstitutionRepository(get()) } + single { CommentConstitutionRepository(get()) } single { Migrations(connection = get(), directory = config.sqlFiles) } } diff --git a/src/main/kotlin/fr/dcproject/entity/Constitution.kt b/src/main/kotlin/fr/dcproject/entity/Constitution.kt index a7371b1..7af1a05 100644 --- a/src/main/kotlin/fr/dcproject/entity/Constitution.kt +++ b/src/main/kotlin/fr/dcproject/entity/Constitution.kt @@ -7,7 +7,7 @@ class Constitution( id: UUID = UUID.randomUUID(), var title: String?, var annonymous: Boolean?, - var titles: List, + var titles: List<Title> = listOf(), createdBy: Citizen? ): UuidEntity(id), EntityVersioning<UUID, Int> by UuidEntityVersioning(), diff --git a/src/main/kotlin/fr/dcproject/repository/Comment.kt b/src/main/kotlin/fr/dcproject/repository/Comment.kt index 0cb39e6..781bbce 100644 --- a/src/main/kotlin/fr/dcproject/repository/Comment.kt +++ b/src/main/kotlin/fr/dcproject/repository/Comment.kt @@ -14,11 +14,7 @@ import fr.dcproject.entity.Constitution as ConstitutionEntity abstract class Comment <T: UuidEntity>(override var requester: Requester): RepositoryI<CommentEntity<T>> { override val entityName = CommentEntity::class as KClass<CommentEntity<T>> - open fun findById(id: UUID): CommentEntity<ArticleEntity>? { - return requester - .getFunction("find_comment_by_id") - .selectOne(mapOf("id" to id)) - } + abstract fun findById(id: UUID): CommentEntity<T>? abstract fun findByCitizen( citizen: CitizenEntity, @@ -81,23 +77,29 @@ abstract class Comment <T: UuidEntity>(override var requester: Requester): Repos } fun edit(comment: CommentEntity<T>) { - val reference = comment.target::class.simpleName!!.toLowerCase() requester .getFunction("edit_comment") .sendQuery( - "reference" to reference, "id" to comment.id, "content" to comment.content ) } } -class CommentGeneric (requester: Requester): Comment<UuidEntity>(requester) { +class GenericTargetEntity(id: UUID = UUID.randomUUID()): UuidEntity(id) + +class CommentGeneric (requester: Requester): Comment<GenericTargetEntity>(requester) { + override fun findById(id: UUID): CommentEntity<GenericTargetEntity>? { + return requester + .getFunction("find_comment_by_id") + .selectOne(mapOf("id" to id)) + } + override fun findByCitizen( citizen: CitizenEntity, page: Int, limit: Int - ): Paginated<CommentEntity<UuidEntity>> { + ): Paginated<CommentEntity<GenericTargetEntity>> { return requester.run { getFunction("find_comments_by_citizen") .select(page, limit, @@ -108,6 +110,12 @@ class CommentGeneric (requester: Requester): Comment<UuidEntity>(requester) { } class CommentArticle (requester: Requester): Comment<ArticleEntity>(requester) { + override fun findById(id: UUID): CommentEntity<ArticleEntity>? { + return requester + .getFunction("find_comment_by_id") + .selectOne(mapOf("id" to id)) + } + override fun findByCitizen( citizen: CitizenEntity, page: Int, @@ -125,6 +133,12 @@ class CommentArticle (requester: Requester): Comment<ArticleEntity>(requester) { } class CommentConstitution (requester: Requester): Comment<ConstitutionEntity>(requester) { + override fun findById(id: UUID): CommentEntity<ConstitutionEntity>? { + return requester + .getFunction("find_comment_by_id") + .selectOne(mapOf("id" to id)) + } + override fun findByCitizen( citizen: CitizenEntity, page: Int, diff --git a/src/main/kotlin/fr/dcproject/repository/Constitution.kt b/src/main/kotlin/fr/dcproject/repository/Constitution.kt index eadff39..62bb393 100644 --- a/src/main/kotlin/fr/dcproject/repository/Constitution.kt +++ b/src/main/kotlin/fr/dcproject/repository/Constitution.kt @@ -33,9 +33,9 @@ class Constitution(override var requester: Requester) : RepositoryI<Constitution ) } - fun upsert(article: ConstitutionEntity): ConstitutionEntity? { + fun upsert(constitution: ConstitutionEntity): ConstitutionEntity? { return requester .getFunction("upsert_constitution") - .selectOne("resource" to article) + .selectOne("resource" to constitution) } } diff --git a/src/main/kotlin/fr/dcproject/routes/Comment.kt b/src/main/kotlin/fr/dcproject/routes/Comment.kt index c98bfb0..e9deee9 100644 --- a/src/main/kotlin/fr/dcproject/routes/Comment.kt +++ b/src/main/kotlin/fr/dcproject/routes/Comment.kt @@ -3,7 +3,6 @@ package fr.dcproject.routes import fr.dcproject.security.voter.CommentVoter.Action.UPDATE import fr.dcproject.security.voter.CommentVoter.Action.VIEW import fr.dcproject.security.voter.assertCan -import fr.postgresjson.entity.UuidEntity import io.ktor.application.call import io.ktor.http.HttpStatusCode import io.ktor.locations.KtorExperimentalLocationsAPI @@ -14,10 +13,8 @@ import io.ktor.request.receiveText import io.ktor.response.respond import io.ktor.routing.Route import java.util.* -import fr.dcproject.entity.Comment as CommentEntity import fr.dcproject.repository.CommentGeneric as CommentRepository -typealias CommentEntityGeneric = CommentEntity<UuidEntity> @KtorExperimentalLocationsAPI object CommentPaths { // TODO: change UUID by entity converter @@ -38,7 +35,7 @@ fun Route.comment(repo: CommentRepository) { assertCan(UPDATE,comment) comment.content = call.receiveText() - repo.edit(comment as CommentEntity<UuidEntity>) + repo.edit(comment) call.respond(HttpStatusCode.OK, comment) } diff --git a/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt b/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt index ffc6b46..5a6c1da 100644 --- a/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt +++ b/src/main/kotlin/fr/dcproject/routes/CommentArticle.kt @@ -11,7 +11,7 @@ import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.Location import io.ktor.locations.get import io.ktor.locations.post -import io.ktor.request.receive +import io.ktor.request.receiveText import io.ktor.response.respond import io.ktor.routing.Route import fr.dcproject.entity.Article as ArticleEntity @@ -37,7 +37,7 @@ fun Route.commentArticle(repo: CommentArticleRepository) { post<CommentArticlePaths.ArticleCommentRequest> { assertCan(CREATE, it.article) - val content = call.receive<String>() + val content = call.receiveText() val comment = CommentEntity( target = it.article, createdBy = citizen, diff --git a/src/main/kotlin/fr/dcproject/routes/CommentConstitution.kt b/src/main/kotlin/fr/dcproject/routes/CommentConstitution.kt new file mode 100644 index 0000000..12449fd --- /dev/null +++ b/src/main/kotlin/fr/dcproject/routes/CommentConstitution.kt @@ -0,0 +1,55 @@ +package fr.dcproject.routes + +import fr.dcproject.citizen +import fr.dcproject.entity.Citizen +import fr.dcproject.security.voter.CommentVoter.Action.CREATE +import fr.dcproject.security.voter.CommentVoter.Action.VIEW +import fr.dcproject.security.voter.assertCan +import io.ktor.application.call +import io.ktor.http.HttpStatusCode +import io.ktor.locations.KtorExperimentalLocationsAPI +import io.ktor.locations.Location +import io.ktor.locations.get +import io.ktor.locations.post +import io.ktor.request.receiveText +import io.ktor.response.respond +import io.ktor.routing.Route +import fr.dcproject.entity.Comment as CommentEntity +import fr.dcproject.entity.Constitution as ConstitutionEntity +import fr.dcproject.repository.CommentConstitution as CommentConstitutionRepository + +@KtorExperimentalLocationsAPI +object CommentConstitutionPaths { + @Location("/constitutions/{constitution}/comments") class ConstitutionCommentRequest(val constitution: ConstitutionEntity) + @Location("/citizens/{citizen}/comments/constitutions") class CitizenCommentConstitutionRequest(val citizen: Citizen) +} + +@KtorExperimentalLocationsAPI +fun Route.commentConstitution(repo: CommentConstitutionRepository) { + get<CommentConstitutionPaths.ConstitutionCommentRequest> { + assertCan(VIEW, it.constitution) + + val comment = repo.findByTarget(it.constitution) + + call.respond(HttpStatusCode.OK, comment) + } + + post<CommentConstitutionPaths.ConstitutionCommentRequest> { + assertCan(CREATE, it.constitution) + + val content = call.receiveText() + val comment = CommentEntity( + target = it.constitution, + createdBy = citizen, + content = content + ) + repo.comment(comment) + + call.respond(HttpStatusCode.Created, comment) + } + + get<CommentConstitutionPaths.CitizenCommentConstitutionRequest> { + val comments = repo.findByCitizen(it.citizen) + call.respond(comments) + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/dcproject/security/voter/ConstitutionVoter.kt b/src/main/kotlin/fr/dcproject/security/voter/ConstitutionVoter.kt new file mode 100644 index 0000000..7bb57b3 --- /dev/null +++ b/src/main/kotlin/fr/dcproject/security/voter/ConstitutionVoter.kt @@ -0,0 +1,47 @@ +package fr.dcproject.security.voter + +import fr.dcproject.entity.Constitution +import fr.dcproject.entity.User +import io.ktor.application.ApplicationCall + +class ConstitutionVoter: Voter { + enum class Action: ActionI { + CREATE, + UPDATE, + VIEW, + DELETE + } + + override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean { + return (action is Action || action is CommentVoter.Action) && subject is Constitution? + } + + override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote { + val user = call.user + if (action == Action.CREATE && user != null) { + return Vote.GRANTED + } + + if (action == Action.VIEW) { + return Vote.GRANTED + } + + if (action == CommentVoter.Action.CREATE) { + return Vote.GRANTED + } + + if (action == CommentVoter.Action.VIEW) { + return Vote.GRANTED + } + + if (action == Action.DELETE && user is User && subject is Constitution && subject.createdBy?.userId == user.id) { + return Vote.GRANTED + } + + if (action == Action.UPDATE && user is User && subject is Constitution && subject.createdBy?.userId == user.id) { + return Vote.GRANTED + } + + return Vote.ABSTAIN + } +} diff --git a/src/main/resources/sql/functions/article/upsert_article.sql b/src/main/resources/sql/functions/article/upsert_article.sql index 36f7c5d..ce3a6b6 100644 --- a/src/main/resources/sql/functions/article/upsert_article.sql +++ b/src/main/resources/sql/functions/article/upsert_article.sql @@ -3,16 +3,28 @@ create or replace function upsert_article(inout resource json) $$ declare new_id uuid; + _id_exist boolean; begin - insert into article (version_id, created_by_id, title, annonymous, content, description, tags) + -- check if version id already exist + select count(*) >= 1 + into _id_exist + from article + where (resource->>'id')::uuid is not null + and id = (resource->>'id')::uuid +-- and draft = false + ; + + insert into article (id, version_id, created_by_id, title, annonymous, content, description, tags) select - coalesce(version_id, uuid_generate_v4()), - (resource#>>'{created_by, id}')::uuid, - title, - annonymous, - content, - description, - tags + case when _id_exist then uuid_generate_v4() + else coalesce(id, uuid_generate_v4()) end, + coalesce(version_id, uuid_generate_v4()), + (resource#>>'{created_by, id}')::uuid, + title, + annonymous, + content, + description, + tags from json_populate_record(null::article, resource) returning id into new_id; diff --git a/src/main/resources/sql/functions/comment/edit_comment.sql b/src/main/resources/sql/functions/comment/edit_comment.sql index b6a2a52..30bddc9 100644 --- a/src/main/resources/sql/functions/comment/edit_comment.sql +++ b/src/main/resources/sql/functions/comment/edit_comment.sql @@ -1,17 +1,13 @@ -create or replace function edit_comment(reference regclass, _id uuid, _content text) returns void +create or replace function edit_comment(_id uuid, _content text) returns void language plpgsql as $$ begin - if reference = 'article'::regclass then - update comment_on_article c set + update comment c set "content" = _content where c.id = _id; - elseif reference = 'constitution'::regclass then - update comment_on_constitution c set - "content" = _content - where c.id = _id; - end if; end; $$; --- drop function if exists edit_comment(regclass, uuid, text); \ No newline at end of file +-- drop function if exists edit_comment(regclass, uuid, text); + +-- select edit_comment('b0422e48-687f-bea7-b45f-b6b301246e97', 'plop4') \ No newline at end of file diff --git a/src/main/resources/sql/functions/comment/find_comments_article_by_citizen.sql b/src/main/resources/sql/functions/comment/find_comments_article_by_citizen.sql deleted file mode 100644 index 4b81f80..0000000 --- a/src/main/resources/sql/functions/comment/find_comments_article_by_citizen.sql +++ /dev/null @@ -1,27 +0,0 @@ -create or replace function find_comments_article_by_citizen( - _created_by_id uuid, - "limit" int default 50, - "offset" int default 0, - out resource json, - out total int -) language plpgsql as -$$ -begin - select json_agg(t), (select count(id) from comment where target_reference = 'article'::regclass) - into resource, total - from ( - select - com.*, - find_article_by_id(com.target_id) as target, - find_citizen_by_id(com.created_by_id) as created_by - from comment as com - where created_by_id = _created_by_id - and target_reference = 'article'::regclass - order by created_at desc, - com.created_at desc - limit "limit" offset "offset" - ) as t; -end; -$$; - --- drop function if exists find_comments_article_by_citizen(uuid, int, int); diff --git a/src/main/resources/sql/functions/constitution/upsert_constitution.sql b/src/main/resources/sql/functions/constitution/upsert_constitution.sql index 66591f1..8c134fc 100644 --- a/src/main/resources/sql/functions/constitution/upsert_constitution.sql +++ b/src/main/resources/sql/functions/constitution/upsert_constitution.sql @@ -6,9 +6,23 @@ declare _title json; _citizen_id uuid = (resource#>>'{created_by, id}')::uuid; new_id uuid; + _id_exist boolean; begin - insert into constitution (version_id, created_by_id, title, annonymous) + -- check if version id already exist + select count(*) >= 1 + into _id_exist + from constitution + where (resource->>'id')::uuid is not null + and id = (resource->>'id')::uuid +-- and draft = false + ; + + raise notice '%', _id_exist; + + insert into constitution (id, version_id, created_by_id, title, annonymous) select + case when _id_exist then uuid_generate_v4() + else coalesce(id, uuid_generate_v4()) end, version_id, _citizen_id, title, diff --git a/src/main/resources/sql/functions/helpers/find_reference_by_id.sql b/src/main/resources/sql/functions/helpers/find_reference_by_id.sql index ca42bb0..b289ce1 100644 --- a/src/main/resources/sql/functions/helpers/find_reference_by_id.sql +++ b/src/main/resources/sql/functions/helpers/find_reference_by_id.sql @@ -10,7 +10,7 @@ begin when 'article'::regclass then find_article_by_id(_id) when 'constitution'::regclass then - find_article_by_id(_id) + find_constitution_by_id(_id) else json_build_object('id', _id) end @@ -19,3 +19,5 @@ end; $$; -- drop function if exists find_reference_by_id(uuid, regclass, out json); + +-- select find_reference_by_id('8944221c-3766-f952-7064-9f229c288049'::uuid, 'constitution'::regclass) \ No newline at end of file diff --git a/src/test/kotlin/feature/ConstitutionSteps.kt b/src/test/kotlin/feature/ConstitutionSteps.kt new file mode 100644 index 0000000..0b2e069 --- /dev/null +++ b/src/test/kotlin/feature/ConstitutionSteps.kt @@ -0,0 +1,62 @@ +package feature + +import fr.dcproject.entity.Citizen +import io.cucumber.java8.En +import org.joda.time.DateTime +import org.koin.test.KoinTest +import org.koin.test.get +import java.util.* +import java.util.concurrent.CompletionException +import fr.dcproject.entity.Constitution as ConstitutionEntity +import fr.dcproject.entity.Constitution.Title as TitleEntity +import fr.dcproject.entity.User as UserEntity +import fr.dcproject.repository.Citizen as CitizenRepository +import fr.dcproject.repository.Constitution as ConstitutionRepository + +class ConstitutionSteps: En, KoinTest { + init { + Given("I have constitution with id {string}") { id: String -> + var citizen = Citizen( + name = Citizen.Name("John", "Doe"), + birthday = DateTime.now(), + user = UserEntity(username = "john-doe", plainPassword = "azerty") + ) + + try { + get<CitizenRepository>().insertWithUser(citizen) + } catch (e: CompletionException) { + citizen = get<CitizenRepository>().findByUsername("john-doe")!! + } + + val title1 = TitleEntity( + name = "My Title" + ) + + val constitution = ConstitutionEntity( + id = UUID.fromString(id), + title = "hello", + titles = listOf(title1), + createdBy = citizen, + annonymous = false + ) + get<ConstitutionRepository>().upsert(constitution) + } + + Given("I have constitution with id {string} created by {string}") { id: String, username: String -> + val citizen = get<CitizenRepository>().findByUsername(username)!! + + val title1 = TitleEntity( + name = "My Title" + ) + + val constitution = ConstitutionEntity( + id = UUID.fromString(id), + title = "hello", + titles = listOf(title1), + createdBy = citizen, + annonymous = false + ) + get<ConstitutionRepository>().upsert(constitution) + } + } +} \ No newline at end of file diff --git a/src/test/resources/feature/comment.feature b/src/test/resources/feature/commentArticle.feature similarity index 95% rename from src/test/resources/feature/comment.feature rename to src/test/resources/feature/commentArticle.feature index 907aeca..d5c8ba6 100644 --- a/src/test/resources/feature/comment.feature +++ b/src/test/resources/feature/commentArticle.feature @@ -1,6 +1,5 @@ -Feature: comment Article and Constitution +Feature: comment Article - # Article Scenario: The route for comment article must response a 201 and return object Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" And I have article with id "9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b" @@ -34,7 +33,3 @@ Feature: comment Article and Constitution Then the response status code should be 200 And the JSON should contain: | content | Hello boy | - - - # Constitution - # TODO diff --git a/src/test/resources/feature/commentConstitution.feature b/src/test/resources/feature/commentConstitution.feature new file mode 100644 index 0000000..166056b --- /dev/null +++ b/src/test/resources/feature/commentConstitution.feature @@ -0,0 +1,35 @@ +Feature: comment Constitution + + Scenario: The route for comment constitution must response a 201 and return object + Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" + And I have constitution with id "9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b" + When I send a POST request to "/constitutions/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/comments" with body: + """ + Hello mister + """ + Then the response status code should be 201 + + Scenario: The route for get comments of constitutions for the current citizen must response a 200 and return objects + Given I have citizen John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd" + And I have constitution with id "9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b" + When I send a GET request to "/citizens/64b7b379-2298-43ec-b428-ba134930cabd/comments/constitutions" + Then the response status code should be 200 + And the response should contain object: + | current_page | 1 | + | limit | 50 | + + Scenario: The route for edit comment must response a 200 and return object + Given I am authenticated as username 3 with id "92877af7-0a45-fd6a-2ed7-fe81e1236b78" + When I send a PUT request to "/comments/b0422e48-687f-bea7-b45f-b6b301246e97" with body: + """ + Hello boy + """ + Then the response status code should be 200 + And the JSON should contain: + | content | Hello boy | + + Scenario: The route for get comment must response a 200 and return object + When I send a GET request to "/comments/b0422e48-687f-bea7-b45f-b6b301246e97" + Then the response status code should be 200 + And the JSON should contain: + | content | Hello boy |