cleanup and refactoring of notification

close rabbit and redis connexion on application close
Refactoring of Configuration class
fix notification id increment
Add builder for NotificationPush
Add close to notificationPush to remove listener
Clean tags of tests
purge queue before functional tests
This commit is contained in:
2021-02-04 02:36:02 +01:00
parent a05b5edc86
commit 89c15eb1cf
26 changed files with 289 additions and 149 deletions

View File

@@ -1,3 +1,5 @@
import com.rabbitmq.client.Channel
import com.rabbitmq.client.ConnectionFactory
import fr.dcproject.application.Configuration
import fr.dcproject.application.Env.CUCUMBER
import fr.dcproject.application.module
@@ -10,6 +12,8 @@ import io.cucumber.java8.Scenario
import io.cucumber.junit.Cucumber
import io.cucumber.junit.CucumberOptions
import io.ktor.server.testing.withTestApplication
import io.lettuce.core.RedisClient
import io.lettuce.core.api.sync.RedisCommands
import kotlinx.coroutines.InternalCoroutinesApi
import org.junit.runner.RunWith
import org.koin.test.KoinTest
@@ -24,6 +28,12 @@ var unitialized: Boolean = false
@CucumberOptions(plugin = ["pretty"], strict = true)
class CucumberTest : En, KoinTest {
private val logger: Logger? by LoggerDelegate()
val config = Configuration("application-test.conf")
val redis: RedisCommands<String, String> = RedisClient.create(config.redis).connect().sync()
val rabbit: Channel = ConnectionFactory()
.apply { setUri(config.rabbitmq) }
.newConnection()
.createChannel()
@InternalCoroutinesApi
val ktorContext = KtorServerContext {
@@ -47,6 +57,14 @@ class CucumberTest : En, KoinTest {
After { _: Scenario ->
//language=PostgreSQL
get<Connection>().sendQuery("rollback;", listOf())
redis.flushall()
/* Purge rabbit notification queues */
rabbit.run {
queuePurge("push")
queuePurge("email")
}
ktorContext.stop()
}
}
@@ -75,7 +93,7 @@ class CucumberTest : En, KoinTest {
private fun getFixturesRequester(): Requester {
return Requester.RequesterFactory(
connection = get(),
queriesDirectory = Configuration.Sql.fixtureFiles
queriesDirectory = config.sql.fixtureFiles
).createRequester()
}
}

View File

@@ -11,6 +11,7 @@ import io.ktor.server.testing.withTestApplication
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.InternalCoroutinesApi
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.koin.test.AutoCloseKoinTest
@@ -20,10 +21,11 @@ import org.koin.test.get
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tags(Tag("functional"))
class MailerTest : KoinTest, AutoCloseKoinTest() {
@InternalCoroutinesApi
@Test
@Tag("online, functional")
@Tags(Tag("online"))
fun `can be send an email`() {
withTestApplication({ module(TEST) }) {
get<Mailer>().sendEmail {

View File

@@ -7,10 +7,10 @@ import fr.dcproject.component.article.ArticleRef
import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.component.follow.FollowArticleRepository
import fr.dcproject.component.follow.FollowSimple
import fr.dcproject.messages.NotificationEmailSender
import fr.dcproject.notification.ArticleUpdateNotification
import fr.dcproject.notification.NotificationConsumer
import fr.dcproject.notification.publisher.Publisher
import fr.dcproject.messages.NotificationEmailSender
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.util.KtorExperimentalAPI
import io.lettuce.core.RedisClient
@@ -22,31 +22,53 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.BeforeAll
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)
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@Tags(Tag("functional"))
class NotificationConsumerTest {
companion object {
@BeforeAll
@JvmStatic
fun before() {
val config: Configuration = Configuration("application-test.conf")
RedisClient.create(config.redis).connect().sync().flushall()
/* Purge rabbit notification queues */
ConnectionFactory()
.apply { setUri(config.rabbitmq) }
.run {
newConnection().createChannel().apply {
queuePurge("push")
queuePurge("email")
}
}
}
}
@InternalCoroutinesApi
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@ExperimentalCoroutinesApi
@Test
@Tag("functional")
fun `can be send notification`() = runBlocking {
val config: Configuration = Configuration("application-test.conf")
/* Create mocks and spy's */
val emailSender = mockk<NotificationEmailSender>() {
every { sendEmail(any()) } returns Unit
}
/* Init Spy on redis client */
val redisClient = spyk<RedisClient>(RedisClient.create(Configuration.redis))
val redisClient = spyk<RedisClient>(RedisClient.create(config.redis))
val asyncCommand = spyk(redisClient.connect().async())
every { redisClient.connect().async() } returns asyncCommand
val rabbitFactory: ConnectionFactory = spyk {
ConnectionFactory().apply { setUri(Configuration.rabbitmq) }
ConnectionFactory().apply { setUri(config.rabbitmq) }
}
val followArticleRepo = mockk<FollowArticleRepository> {
every { findFollowsByTarget(any()) } returns flow {
@@ -57,21 +79,15 @@ class NotificationConsumerTest {
}
}
/* Purge rabbit notification queues */
rabbitFactory.newConnection().createChannel().apply {
queuePurge("push")
queuePurge("email")
}
/* Config consumer */
NotificationConsumer(
val consumer = NotificationConsumer(
rabbitFactory = rabbitFactory,
redisClient = redisClient,
followArticleRepo = followArticleRepo,
followConstitutionRepo = mockk(),
notificationEmailSender = emailSender,
exchangeName = "notification_test",
).config()
).apply { start() }
verify { rabbitFactory.newConnection() }
/* Push message */
@@ -93,5 +109,7 @@ class NotificationConsumerTest {
verify(timeout = 1000) { followArticleRepo.findFollowsByTarget(any()) }
verify(timeout = 1000) { emailSender.sendEmail(any()) }
verify(timeout = 1000) { asyncCommand.zadd(any<String>(), any<Double>(), any<String>()) }
// consumer.close()
}
}

View File

@@ -1,35 +1,54 @@
package functional
import com.rabbitmq.client.ConnectionFactory
import fr.dcproject.application.Configuration
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.citizen.CitizenRef
import fr.dcproject.notification.ArticleUpdateNotification
import fr.dcproject.notification.Notification
import io.lettuce.core.Limit
import io.lettuce.core.RedisClient
import io.mockk.every
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.runBlocking
import notification.NotificationsPush
import org.amshove.kluent.`should be equal to`
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.koin.test.AutoCloseKoinTest
import org.koin.test.KoinTest
import kotlin.test.assertEquals
@Tags(Tag("functional"))
internal class NotificationsPushTest {
companion object {
@BeforeAll
@JvmStatic
fun before() {
val config: Configuration = Configuration("application-test.conf")
RedisClient.create(config.redis).connect().sync().flushall()
/* Purge rabbit notification queues */
ConnectionFactory()
.apply { setUri(config.rabbitmq) }
.newConnection().createChannel().apply {
queuePurge("push")
queuePurge("email")
}
}
}
@Test
@Tag("functional")
fun `Notification from redis is well catch and return`() = runBlocking {
val config: Configuration = Configuration("application-test.conf")
/* Redis client for test */
val redisClientTest = RedisClient.create(Configuration.redis)
val redisClientTest = RedisClient.create(config.redis)
/* Init Spy on redis client */
val redisClient = spyk<RedisClient>(RedisClient.create(Configuration.redis))
val redisClient = spyk<RedisClient>(RedisClient.create(config.redis))
val asyncCommand = spyk(redisClient.connect().async())
every { redisClient.connect().async() } returns asyncCommand
@@ -44,36 +63,41 @@ internal class NotificationsPushTest {
)
/* Init two notification, one called before subscription, and the other after */
val notifBeforeSubscribe = ArticleUpdateNotification(article)
runBlocking {
delay(100)
}
val notifAfterSubscribe = ArticleUpdateNotification(article)
/* init event for emulate incomint message from websocket */
val event = MutableSharedFlow<Notification>()
val incomingFlow = event.asSharedFlow()
spyk(object { var counter = 0}).run { /* Counter for count the callback of notification */
spyk(object { var counter = 0 }).run { /* Counter for count the callback of notification */
/* Sent notification */
redisClientTest.connect().sync().run {
zadd(
redisClientTest.connect().run {
sync().zadd(
"notification:${citizen.id}",
notifBeforeSubscribe.id,
notifBeforeSubscribe.toString()
)
close()
}
/* Init NotificationPush system, and set assertion in callback */
NotificationsPush(redisClient, citizen, incomingFlow) {
val notificationPush = NotificationsPush.Builder(redisClient).build(citizen, incomingFlow) {
counter++
if (counter == 1) it.id `should be equal to` notifBeforeSubscribe.id
else it.id `should be equal to` notifAfterSubscribe.id
}
/* Sent the notification */
redisClientTest.connect().sync().run {
zadd(
redisClientTest.connect().run {
sync().zadd(
"notification:${citizen.id}",
notifAfterSubscribe.id,
notifAfterSubscribe.toString()
)
close()
}
/* Verify if the callback is called 2 times */
@@ -83,7 +107,8 @@ internal class NotificationsPushTest {
/* Emit an event to delete notification */
event.emit(notifAfterSubscribe)
/* Verify the "mark as read" is called */
verify(timeout = 300) { asyncCommand.zremrangebyscore(any(), any()) }
verify(timeout = 500) { asyncCommand.zremrangebyscore(any(), any()) }
notificationPush.close()
}
}
}
}

View File

@@ -1,11 +1,14 @@
package functional
import fr.dcproject.utils.readResource
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.test.assertEquals
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tags(Tag("functional"))
class ResourcesKtTest {
@Test
fun readResource() {

View File

@@ -10,6 +10,7 @@ import io.ktor.server.testing.withTestApplication
import io.ktor.util.KtorExperimentalAPI
import org.amshove.kluent.`should be equal to`
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
@@ -19,7 +20,7 @@ import java.util.UUID
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
@TestInstance(PER_CLASS)
@Tag("functional")
@Tags(Tag("functional"))
class ViewTest {
@Test
fun `test View Article`() {

View File

@@ -14,6 +14,7 @@ import io.mockk.mockk
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.parallel.Execution
@@ -23,7 +24,7 @@ import fr.dcproject.component.article.ArticleRepository as ArticleRepo
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("security")
@Tags(Tag("security"), Tag("unit"))
internal class ArticleAccessControlTest {
private val tesla = CitizenCart(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),

View File

@@ -10,6 +10,7 @@ import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.parallel.Execution
@@ -17,7 +18,7 @@ import org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("security")
@Tags(Tag("security"), Tag("unit"))
internal class CitizenAccessControlTest {
private val tesla = CitizenBasic(
user = User(

View File

@@ -15,6 +15,7 @@ import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.parallel.Execution
@@ -23,7 +24,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("security")
@Tags(Tag("security"), Tag("unit"))
internal class CommentAccessControlTest {
private val tesla = Citizen(
user = User(

View File

@@ -14,6 +14,7 @@ import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.parallel.Execution
@@ -22,7 +23,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("security")
@Tags(Tag("security"), Tag("unit"))
internal class FollowAccessControlTest {
private val tesla = CitizenBasic(
user = User(

View File

@@ -14,6 +14,7 @@ import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.parallel.Execution
@@ -22,7 +23,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("security")
@Tags(Tag("security"), Tag("unit"))
internal class OpinionAccessControlTest {
private val tesla = CitizenBasic(
user = User(

View File

@@ -12,6 +12,7 @@ import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.parallel.Execution
@@ -20,7 +21,7 @@ import java.util.UUID
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("security")
@Tags(Tag("security"), Tag("unit"))
internal class OpinionChoiceAccessControlTest {
private val tesla = CitizenBasic(
id = UUID.fromString("e6efc288-4283-4729-a268-6debb18de1a0"),

View File

@@ -14,6 +14,7 @@ import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.parallel.Execution
@@ -23,7 +24,7 @@ import fr.dcproject.component.vote.entity.Vote as VoteEntity
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("security")
@Tags(Tag("security"), Tag("unit"))
internal class VoteAccessControlTest {
private val tesla = Citizen(
id = UUID.fromString("a1e35c99-9d33-4fb4-9201-58d7071243bb"),

View File

@@ -12,6 +12,7 @@ import fr.dcproject.security.AccessDecision.GRANTED
import org.amshove.kluent.`should be`
import org.joda.time.DateTime
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Tags
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.parallel.Execution
@@ -21,7 +22,7 @@ import fr.dcproject.component.workgroup.Workgroup as WorkgroupEntity
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(CONCURRENT)
@Tag("security")
@Tags(Tag("security"), Tag("unit"))
internal class WorkgroupAccessControlTest {
private val tesla = CitizenBasic(
user = User(