diff --git a/src/main/kotlin/fr/dcproject/application/Application.kt b/src/main/kotlin/fr/dcproject/application/Application.kt index 539a645..8ed82c5 100644 --- a/src/main/kotlin/fr/dcproject/application/Application.kt +++ b/src/main/kotlin/fr/dcproject/application/Application.kt @@ -25,6 +25,7 @@ import fr.dcproject.component.constitution.routes.installConstitutionRoutes import fr.dcproject.component.doc.routes.installDocRoutes import fr.dcproject.component.follow.followKoinModule import fr.dcproject.component.follow.routes.article.installFollowArticleRoutes +import fr.dcproject.component.follow.routes.citizen.installFollowCitizenRoutes import fr.dcproject.component.follow.routes.constitution.installFollowConstitutionRoutes import fr.dcproject.component.notification.NotificationConsumer import fr.dcproject.component.notification.routes.installNotificationsRoutes @@ -154,6 +155,7 @@ fun Application.module(env: Env = PROD) { installCommentRoutes() installFollowArticleRoutes() installFollowConstitutionRoutes() + installFollowCitizenRoutes() installWorkgroupRoutes() installOpinionRoutes() installVoteRoutes() diff --git a/src/main/kotlin/fr/dcproject/application/KoinModule.kt b/src/main/kotlin/fr/dcproject/application/KoinModule.kt index f2e88e8..f03fb34 100644 --- a/src/main/kotlin/fr/dcproject/application/KoinModule.kt +++ b/src/main/kotlin/fr/dcproject/application/KoinModule.kt @@ -69,7 +69,7 @@ val KoinModule = module { single { val config: Configuration = get() - NotificationConsumer(get(), get(), get(), get(), get(), config.exchangeNotificationName) + NotificationConsumer(get(), get(), get(), get(), get(), get(), config.exchangeNotificationName) } // RabbitMQ diff --git a/src/main/kotlin/fr/dcproject/common/entity/Extra.kt b/src/main/kotlin/fr/dcproject/common/entity/Extra.kt index 228235f..0245c05 100644 --- a/src/main/kotlin/fr/dcproject/common/entity/Extra.kt +++ b/src/main/kotlin/fr/dcproject/common/entity/Extra.kt @@ -2,6 +2,7 @@ package fr.dcproject.common.entity import fr.dcproject.component.article.database.ArticleRef import fr.dcproject.component.citizen.database.CitizenI +import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.comment.generic.database.CommentRef import fr.dcproject.component.constitution.database.ConstitutionRef import fr.dcproject.component.opinion.database.OpinionRef @@ -34,7 +35,8 @@ interface TargetI : EntityI { Article("article"), Constitution("constitution"), Comment("comment"), - Opinion("opinion") + Opinion("opinion"), + Citizen("citizen"), } companion object { @@ -44,6 +46,7 @@ interface TargetI : EntityI { t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference t.isSubclassOf(CommentRef::class) -> TargetName.Comment.targetReference t.isSubclassOf(OpinionRef::class) -> TargetName.Opinion.targetReference + t.isSubclassOf(CitizenRef::class) -> TargetName.Citizen.targetReference else -> throw error("target not implemented: ${t.qualifiedName} \nImplement it or return 'reference' from SQL") } } diff --git a/src/main/kotlin/fr/dcproject/component/citizen/database/Citizen.kt b/src/main/kotlin/fr/dcproject/component/citizen/database/Citizen.kt index 2b1d251..14d0597 100644 --- a/src/main/kotlin/fr/dcproject/component/citizen/database/Citizen.kt +++ b/src/main/kotlin/fr/dcproject/component/citizen/database/Citizen.kt @@ -2,8 +2,9 @@ package fr.dcproject.component.citizen.database import fr.dcproject.common.entity.CreatedAt import fr.dcproject.common.entity.DeletedAt -import fr.dcproject.common.entity.Entity import fr.dcproject.common.entity.EntityI +import fr.dcproject.common.entity.TargetI +import fr.dcproject.common.entity.TargetRef import fr.dcproject.component.auth.database.User import fr.dcproject.component.auth.database.UserCreator import fr.dcproject.component.auth.database.UserForCreate @@ -95,10 +96,10 @@ open class CitizenRefWithUser( open class CitizenRef( id: UUID = UUID.randomUUID() -) : Entity(id), +) : TargetRef(id), CitizenI -interface CitizenI : EntityI { +interface CitizenI : EntityI, TargetI { data class Name( override val firstName: String, override val lastName: String, diff --git a/src/main/kotlin/fr/dcproject/component/follow/KoinModule.kt b/src/main/kotlin/fr/dcproject/component/follow/KoinModule.kt index e57738c..a86ac86 100644 --- a/src/main/kotlin/fr/dcproject/component/follow/KoinModule.kt +++ b/src/main/kotlin/fr/dcproject/component/follow/KoinModule.kt @@ -1,11 +1,13 @@ package fr.dcproject.component.follow import fr.dcproject.component.follow.database.FollowArticleRepository +import fr.dcproject.component.follow.database.FollowCitizenRepository import fr.dcproject.component.follow.database.FollowConstitutionRepository import org.koin.dsl.module val followKoinModule = module { single { FollowArticleRepository(get()) } single { FollowConstitutionRepository(get()) } + single { FollowCitizenRepository(get()) } single { FollowAccessControl() } } diff --git a/src/main/kotlin/fr/dcproject/component/follow/database/FollowRepository.kt b/src/main/kotlin/fr/dcproject/component/follow/database/FollowRepository.kt index 7db5aa0..87a277d 100644 --- a/src/main/kotlin/fr/dcproject/component/follow/database/FollowRepository.kt +++ b/src/main/kotlin/fr/dcproject/component/follow/database/FollowRepository.kt @@ -4,7 +4,9 @@ import fr.dcproject.common.entity.Entity import fr.dcproject.common.entity.TargetRef import fr.dcproject.component.article.database.ArticleForView 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.constitution.database.ConstitutionForView import fr.dcproject.component.constitution.database.ConstitutionRef import fr.postgresjson.connexion.Paginated @@ -144,3 +146,28 @@ class FollowConstitutionRepository(requester: Requester) : FollowRepository(requester) { + override fun findByCitizen( + citizenId: UUID, + page: Int, + limit: Int + ): Paginated> { + return requester.run { + getFunction("find_follows_citizen_by_citizen") + .select( + page, + limit, + "created_by_id" to citizenId + ) + } + } + + override fun findFollowsByTarget( + target: Entity, + page: Int, + limit: Int + ): Paginated> { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/FollowCitizen.kt b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/FollowCitizen.kt new file mode 100644 index 0000000..a84aa3b --- /dev/null +++ b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/FollowCitizen.kt @@ -0,0 +1,36 @@ +package fr.dcproject.component.follow.routes.citizen + +import fr.dcproject.common.security.assert +import fr.dcproject.component.auth.citizen +import fr.dcproject.component.auth.citizenOrNull +import fr.dcproject.component.auth.mustBeAuth +import fr.dcproject.component.citizen.database.CitizenRef +import fr.dcproject.component.follow.FollowAccessControl +import fr.dcproject.component.follow.database.FollowCitizenRepository +import fr.dcproject.component.follow.database.FollowForUpdate +import io.ktor.application.call +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 FollowCitizen { + @Location("/citizens/{citizen}/follows") + class CitizenFollowRequest(citizen: UUID) { + val citizen = CitizenRef(citizen) + } + + fun Route.followCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) { + post { + mustBeAuth() + val follow = FollowForUpdate(target = it.citizen, createdBy = this.citizen) + ac.assert { canCreate(follow, citizenOrNull) } + repo.follow(follow) + call.respond(HttpStatusCode.Created) + } + } +} diff --git a/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/GetFollowCitizen.kt b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/GetFollowCitizen.kt new file mode 100644 index 0000000..bd5d075 --- /dev/null +++ b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/GetFollowCitizen.kt @@ -0,0 +1,50 @@ +package fr.dcproject.component.follow.routes.citizen + +import fr.dcproject.common.response.toOutput +import fr.dcproject.common.security.assert +import fr.dcproject.component.auth.citizen +import fr.dcproject.component.auth.citizenOrNull +import fr.dcproject.component.citizen.database.CitizenRef +import fr.dcproject.component.follow.FollowAccessControl +import fr.dcproject.component.follow.database.FollowCitizenRepository +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.response.respond +import io.ktor.routing.Route +import org.joda.time.DateTime +import java.util.UUID + +@KtorExperimentalLocationsAPI +object GetFollowCitizen { + @Location("/citizens/{citizen}/follows") + class CitizenFollowRequest(citizen: UUID) { + val citizen = CitizenRef(citizen) + } + + fun Route.getFollowCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) { + get { + repo.findFollow(citizen, it.citizen)?.let { follow -> + ac.assert { canView(follow, citizenOrNull) } + call.respond( + HttpStatusCode.OK, + follow.let { f -> + object { + val id: UUID = f.id + val createdBy: Any = f.createdBy.toOutput() + val target: Any = f.target.let { t -> + object { + val id: UUID = t.id + val reference: String = f.target.reference + } + } + val createdAt: DateTime = f.createdAt + } + } + ) + } ?: call.respond(HttpStatusCode.NoContent) + } + } +} diff --git a/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/GetMyFollowsCitizen.kt b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/GetMyFollowsCitizen.kt new file mode 100644 index 0000000..24c35ef --- /dev/null +++ b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/GetMyFollowsCitizen.kt @@ -0,0 +1,50 @@ +package fr.dcproject.component.follow.routes.citizen + +import fr.dcproject.common.response.toOutput +import fr.dcproject.common.security.assert +import fr.dcproject.component.auth.citizenOrNull +import fr.dcproject.component.auth.mustBeAuth +import fr.dcproject.component.citizen.database.CitizenRef +import fr.dcproject.component.follow.FollowAccessControl +import fr.dcproject.component.follow.database.FollowCitizenRepository +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.response.respond +import io.ktor.routing.Route +import org.joda.time.DateTime +import java.util.UUID + +@KtorExperimentalLocationsAPI +object GetMyFollowsCitizen { + @Location("/citizens/{citizen}/follows/citizens") + class CitizenFollowCitizenRequest(citizen: UUID) { + val citizen = CitizenRef(citizen) + } + + fun Route.getMyFollowsCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) { + get { + mustBeAuth() + val follows = repo.findByCitizen(it.citizen) + ac.assert { canView(follows.result, citizenOrNull) } + call.respond( + HttpStatusCode.OK, + follows.toOutput { f -> + object { + val id: UUID = f.id + val createdBy: Any = f.createdBy.toOutput() + val target: Any = f.target.let { t -> + object { + val id: UUID = t.id + val reference: String = f.target.reference + } + } + val createdAt: DateTime = f.createdAt + } + } + ) + } + } +} diff --git a/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/UnfollowCitizen.kt b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/UnfollowCitizen.kt new file mode 100644 index 0000000..8b47903 --- /dev/null +++ b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/UnfollowCitizen.kt @@ -0,0 +1,36 @@ +package fr.dcproject.component.follow.routes.citizen + +import fr.dcproject.common.security.assert +import fr.dcproject.component.auth.citizen +import fr.dcproject.component.auth.citizenOrNull +import fr.dcproject.component.auth.mustBeAuth +import fr.dcproject.component.citizen.database.CitizenRef +import fr.dcproject.component.follow.FollowAccessControl +import fr.dcproject.component.follow.database.FollowCitizenRepository +import fr.dcproject.component.follow.database.FollowForUpdate +import io.ktor.application.call +import io.ktor.http.HttpStatusCode +import io.ktor.locations.KtorExperimentalLocationsAPI +import io.ktor.locations.Location +import io.ktor.locations.delete +import io.ktor.response.respond +import io.ktor.routing.Route +import java.util.UUID + +@KtorExperimentalLocationsAPI +object UnfollowCitizen { + @Location("/citizens/{citizen}/follows") + class CitizenFollowRequest(citizen: UUID) { + val citizen = CitizenRef(citizen) + } + + fun Route.unfollowCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) { + delete { + mustBeAuth() + val follow = FollowForUpdate(target = it.citizen, createdBy = this.citizen) + ac.assert { canDelete(follow, citizenOrNull) } + repo.unfollow(follow) + call.respond(HttpStatusCode.NoContent) + } + } +} diff --git a/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/install.kt b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/install.kt new file mode 100644 index 0000000..046dfd2 --- /dev/null +++ b/src/main/kotlin/fr/dcproject/component/follow/routes/citizen/install.kt @@ -0,0 +1,20 @@ +package fr.dcproject.component.follow.routes.citizen + +import fr.dcproject.component.follow.routes.citizen.FollowCitizen.followCitizen +import fr.dcproject.component.follow.routes.citizen.GetFollowCitizen.getFollowCitizen +import fr.dcproject.component.follow.routes.citizen.GetMyFollowsCitizen.getMyFollowsCitizen +import fr.dcproject.component.follow.routes.citizen.UnfollowCitizen.unfollowCitizen +import io.ktor.auth.authenticate +import io.ktor.locations.KtorExperimentalLocationsAPI +import io.ktor.routing.Routing +import org.koin.ktor.ext.get + +@KtorExperimentalLocationsAPI +fun Routing.installFollowCitizenRoutes() { + authenticate(optional = true) { + followCitizen(get(), get()) + unfollowCitizen(get(), get()) + getFollowCitizen(get(), get()) + getMyFollowsCitizen(get(), get()) + } +} diff --git a/src/main/kotlin/fr/dcproject/component/notification/NotificationConsumer.kt b/src/main/kotlin/fr/dcproject/component/notification/NotificationConsumer.kt index 8015f45..dae1758 100644 --- a/src/main/kotlin/fr/dcproject/component/notification/NotificationConsumer.kt +++ b/src/main/kotlin/fr/dcproject/component/notification/NotificationConsumer.kt @@ -8,6 +8,7 @@ import com.rabbitmq.client.DefaultConsumer import com.rabbitmq.client.Envelope import fr.dcproject.common.entity.TargetRef import fr.dcproject.component.follow.database.FollowArticleRepository +import fr.dcproject.component.follow.database.FollowCitizenRepository import fr.dcproject.component.follow.database.FollowConstitutionRepository import fr.dcproject.component.follow.database.FollowForView import io.ktor.utils.io.errors.IOException @@ -23,6 +24,7 @@ class NotificationConsumer( private val redisClient: RedisClient, private val followConstitutionRepo: FollowConstitutionRepository, private val followArticleRepo: FollowArticleRepository, + private val followCitizenRepo: FollowCitizenRepository, private val notificationEmailSender: NotificationEmailSender, private val exchangeName: String, ) { @@ -98,6 +100,7 @@ class NotificationConsumer( val follows = when (notification.type) { "article" -> followArticleRepo.findFollowsByTarget(notification.target) "constitution" -> followConstitutionRepo.findFollowsByTarget(notification.target) + "citizen" -> followCitizenRepo.findFollowsByTarget(notification.target) else -> error("event '${notification.type}' not implemented") } diff --git a/src/main/resources/openapi.yaml b/src/main/resources/openapi.yaml index 3f3989c..e42ad10 100644 --- a/src/main/resources/openapi.yaml +++ b/src/main/resources/openapi.yaml @@ -955,13 +955,105 @@ paths: description: Return only http status 204 on success 401: $ref: '#/components/responses/401' + + /citizens/{citizen}/follows: + parameters: + - $ref: '#/components/parameters/citizen' + get: + security: + - JWTAuth: [ ] + summary: Return Follows of citizen + tags: + - follow + - citizen + responses: + 200: + description: Return follows + content: + application/json: + schema: + $ref: '#/components/schemas/FollowResponse' + 404: + description: Citizen not exist + content: + application/json: + schema: + $ref: '#/components/schemas/404' + post: + security: + - JWTAuth: [] + summary: Follow citizen + description: Follow a citizen to receive notifications of his activity + tags: + - follow + - citizen + responses: + 201: + description: Return only http status 201 on success + 401: + $ref: '#/components/responses/401' + 404: + description: Citizen not exist + content: + application/json: + schema: + $ref: '#/components/schemas/404' + delete: + security: + - JWTAuth: [ ] + summary: Unfollow one citizen + tags: + - follow + - citizen + responses: + 204: + description: Return only http status 204 on success + 401: + $ref: '#/components/responses/401' + 404: + description: Citizen not exist + content: + application/json: + schema: + $ref: '#/components/schemas/404' + /citizens/{citizen}/follows/citizens: + parameters: + - $ref: '#/components/parameters/citizen' + get: + security: + - JWTAuth: [ ] + summary: Return citizen Follow of citizen + tags: + - follow + - citizen + responses: + 200: + description: Return citizen Follow of citizen + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/Paginated' + - type: object + properties: + result: + type: array + items: + $ref: '#/components/schemas/FollowResponse' + 404: + description: Citizen not exist + content: + application/json: + schema: + $ref: '#/components/schemas/404' + /citizens/{citizen}/follows/articles: parameters: - $ref: '#/components/parameters/citizen' get: security: - JWTAuth: [ ] - summary: Return Follow or nothing if you not follow + summary: Return article Follow of citizen tags: - follow - article @@ -1036,7 +1128,7 @@ paths: - citizen responses: 200: - description: Return your follows + description: Return constitution Follow of citizen content: application/json: schema: diff --git a/src/main/resources/sql/functions/follow/find_follows_citizen_by_citizen.sql b/src/main/resources/sql/functions/follow/find_follows_citizen_by_citizen.sql new file mode 100644 index 0000000..82fee60 --- /dev/null +++ b/src/main/resources/sql/functions/follow/find_follows_citizen_by_citizen.sql @@ -0,0 +1,24 @@ +create or replace function find_follows_citizen_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 follow) + into resource, total + from ( + select + f.*, + find_citizen_by_id_with_user(f.target_id) as target, + find_citizen_by_id_with_user(f.created_by_id) as created_by + from follow as f + where created_by_id = _created_by_id + order by created_at desc, + f.created_at desc + limit "limit" offset "offset" + ) as t; +end; +$$; diff --git a/src/test/kotlin/functional/NotificationConsumerTest.kt b/src/test/kotlin/functional/NotificationConsumerTest.kt index 4d35704..60e5581 100644 --- a/src/test/kotlin/functional/NotificationConsumerTest.kt +++ b/src/test/kotlin/functional/NotificationConsumerTest.kt @@ -92,7 +92,8 @@ class NotificationConsumerTest { rabbitFactory = rabbitFactory, redisClient = redisClient, followArticleRepo = followArticleRepo, - followConstitutionRepo = mockk(), + followConstitutionRepo = mockk(), // TODO test followConstitution + followCitizenRepo = mockk(), // TODO test followCitizen notificationEmailSender = emailSender, exchangeName = "notification", ).apply { start() } diff --git a/src/test/kotlin/integration/Follow citizen routes.kt b/src/test/kotlin/integration/Follow citizen routes.kt new file mode 100644 index 0000000..108a684 --- /dev/null +++ b/src/test/kotlin/integration/Follow citizen routes.kt @@ -0,0 +1,96 @@ +package integration + +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` +import integration.steps.given.`And follow citizen` +import integration.steps.given.`Given I have citizen` +import integration.steps.given.`authenticated as` +import integration.steps.given.`with no content` +import integration.steps.then.`And the response should be null` +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.Created +import io.ktor.http.HttpStatusCode.Companion.NoContent +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("article"), Tag("follow")) +class `Follow citizen routes` : BaseTest() { + @Test + fun `I can follow citizen`() { + withIntegrationApplication { + /* Followed user */ + `Given I have citizen`("John", "Glenn", id = "7e1580c5-05b7-4557-84f4-faac9f0a9441") + /* Current user */ + `Given I have citizen`("Valentina", "Terechkova") + `When I send a POST request`("/citizens/7e1580c5-05b7-4557-84f4-faac9f0a9441/follows") { + `authenticated as`("Valentina", "Terechkova") + `with no content`() + } `Then the response should be` Created + } + } + + @Test + fun `I can get my follow citizen`() { + withIntegrationApplication { + /* Followed user */ + `Given I have citizen`("Jean-Loup", "Chrétien", id = "c2432b94-a509-4116-a8b6-9774bc963372") + /* Current user */ + `Given I have citizen`("John", "Young", id = "6d41ce65-9df7-47e0-af46-8da4a909490b") { + `And follow citizen`("c2432b94-a509-4116-a8b6-9774bc963372") + } + /* Get my all follows */ + `When I send a GET request`("/citizens/6d41ce65-9df7-47e0-af46-8da4a909490b/follows/citizens") { + `authenticated as`("John", "Young") + } `Then the response should be` OK and { + `And the response should not be null`() + `And the response should contain`("$.currentPage", 1) + `And the response should contain`("$.limit", 50) + } + } + } + + @Test + fun `I can unfollow citizen`() { + withIntegrationApplication { + /* Followed user */ + `Given I have citizen`("Bruce", "McCandless", id = "680c7af7-d2de-4249-bfcb-47007ef546fe") + /* Current user */ + `Given I have citizen`("Jean-François", "Clervoy", id = "a12455ae-1047-43ff-826d-0d826dbe90f7") { + `And follow citizen`("680c7af7-d2de-4249-bfcb-47007ef546fe") + } + `When I send a DELETE request`("/citizens/680c7af7-d2de-4249-bfcb-47007ef546fe/follows") { + `authenticated as`("Jean-François", "Clervoy") + `with no content`() + } `Then the response should be` NoContent and { + `And the response should be null`() + } + } + } + + @Test + fun `I can know if I follow an citizen`() { + withIntegrationApplication { + /* Followed user */ + `Given I have citizen`("Eugene", "Cernan", id = "c755788f-7f48-4cde-8ff0-e75bcffdafc2") + /* Current user */ + `Given I have citizen`("Buzz", "Aldrin", id = "39e2915a-e96f-43ea-babd-bd339d8bf197") { + `And follow citizen`("c755788f-7f48-4cde-8ff0-e75bcffdafc2") + } + `When I send a GET request`("/citizens/c755788f-7f48-4cde-8ff0-e75bcffdafc2/follows") { + `authenticated as`("Buzz", "Aldrin") + `with no content`() + } `Then the response should be` OK and { + `And the response should not be null`() + `And the response should contain`("$.target.id", "c755788f-7f48-4cde-8ff0-e75bcffdafc2") + } + } + } +} diff --git a/src/test/kotlin/integration/steps/given/Follow.kt b/src/test/kotlin/integration/steps/given/Follow.kt index dfd7a8f..4921986 100644 --- a/src/test/kotlin/integration/steps/given/Follow.kt +++ b/src/test/kotlin/integration/steps/given/Follow.kt @@ -8,6 +8,7 @@ import fr.dcproject.component.citizen.database.CitizenRef import fr.dcproject.component.citizen.database.CitizenRepository import fr.dcproject.component.constitution.database.ConstitutionRef import fr.dcproject.component.follow.database.FollowArticleRepository +import fr.dcproject.component.follow.database.FollowCitizenRepository import fr.dcproject.component.follow.database.FollowConstitutionRepository import fr.dcproject.component.follow.database.FollowForUpdate import io.ktor.server.testing.TestApplicationEngine @@ -24,6 +25,11 @@ fun Citizen.`And follow constitution`( ) { createFollow(this, ConstitutionRef(constitution.toUUID())) } +fun Citizen.`And follow citizen`( + citizen: String, +) { + createFollow(this, CitizenRef(citizen.toUUID())) +} fun TestApplicationEngine.`Given I have follow on article`( firstName: String, @@ -56,3 +62,9 @@ fun createFollow(citizen: CitizenRef, constitution: ConstitutionRef) { val follow = FollowForUpdate(createdBy = citizen, target = constitution) followConstitutionRepository.follow(follow) } + +fun createFollow(createdBy: CitizenRef, target: CitizenRef) { + val followCitizenRepository: FollowCitizenRepository by lazy { GlobalContext.get().get() } + val follow = FollowForUpdate(createdBy = createdBy, target = target) + followCitizenRepository.follow(follow) +}