Skip to content

Commit 64e4bc9

Browse files
committed
✅ Bump coverage to 100%
1 parent f41ea23 commit 64e4bc9

File tree

2 files changed

+47
-23
lines changed

2 files changed

+47
-23
lines changed

src/main/kotlin/io/github/tobi/laa/spring/boot/embedded/redis/highavailability/RedisHighAvailabilityContextCustomizer.kt

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,47 +36,52 @@ internal class RedisHighAvailabilityContextCustomizer(
3636
private val manuallySpecifiedPorts =
3737
config.ports.filter { it != 0 }.toSet() + config.sentinels.map { it.port }.filter { it != 0 }.toSet()
3838
private val name = config.name.ifEmpty { BirdNameProvider.next() }.replace(Regex("[^a-zA-Z0-9]"), "")
39-
private lateinit var nodeProvider: NodeProvider
39+
private var nodeProvider: NodeProvider? = null
4040
private val customizer = config.customizer.map { c -> c.createInstance() }.toList()
4141

4242
override fun customizeContext(context: ConfigurableApplicationContext, mergedConfig: MergedContextConfiguration) {
4343
RedisStore.computeIfAbsent(context) {
44-
val redisHighAvailability = createAndStartRedisInHighAvailabilityMode(context)
45-
val sentinelAddresses = parseSentinelAddresses(redisHighAvailability)
46-
val client = createClient(sentinelAddresses)
47-
setSpringProperties(context, sentinelAddresses)
48-
addShutdownListener(context, redisHighAvailability, client)
49-
Pair(redisHighAvailability, client)
44+
val mainNode = createAndStartMainNode(context)
45+
try {
46+
val redisHighAvailability = createAndStartRedisInHighAvailabilityMode(mainNode)
47+
val sentinelAddresses = parseSentinelAddresses(redisHighAvailability)
48+
val client = createClient(sentinelAddresses)
49+
setSpringProperties(context, sentinelAddresses)
50+
addShutdownListener(context, redisHighAvailability, client)
51+
Pair(redisHighAvailability, client)
52+
} catch (e: Exception) {
53+
stopSafely(mainNode.second)
54+
throw e
55+
}
5056
}
5157
}
5258

53-
private fun createAndStartRedisInHighAvailabilityMode(context: ConfigurableApplicationContext): RedisCluster {
54-
nodeProvider = nodeProvider()
55-
val redisHighAvailability = createRedisInHighAvailabilityMode(context)
59+
private fun createAndStartRedisInHighAvailabilityMode(mainNode: Pair<Node, RedisServer>): RedisCluster {
60+
val redisHighAvailability = createRedisInHighAvailabilityMode(mainNode)
5661
redisHighAvailability.start()
5762
log.info("Started Redis in high availability mode on ports ${redisHighAvailability.ports()}")
5863
return redisHighAvailability
5964
}
6065

61-
private fun createRedisInHighAvailabilityMode(context: ConfigurableApplicationContext): RedisCluster {
62-
val replicationGroup = createReplicationGroup(context)
63-
val sentinels = config.sentinels.map { createSentinel(it, replicationGroup) }
64-
return RedisCluster(sentinels, replicationGroup.second)
66+
private fun createRedisInHighAvailabilityMode(mainNode: Pair<Node, RedisServer>): RedisCluster {
67+
val replicas = createReplicas(mainNode)
68+
val sentinels = config.sentinels.map { createSentinel(it, mainNode) }
69+
return RedisCluster(sentinels, replicas + mainNode.second)
6570
}
6671

67-
private fun createReplicationGroup(context: ConfigurableApplicationContext): Pair<Node, List<RedisServer>> {
68-
val mainNode = createAndStartMainNode(context)
72+
private fun createReplicas(mainNode: Pair<Node, RedisServer>): List<RedisServer> {
6973
val replicaBuilders =
7074
IntStream.range(0, config.replicas)
7175
.mapToObj { _ -> createReplicaBuilder(mainNode.first) }
7276
.toList()
7377
customizer.forEach { c -> c.customizeReplicas(replicaBuilders, config) }
7478

75-
return Pair(mainNode.first, replicaBuilders.map { it.build() } + mainNode.second)
79+
return replicaBuilders.map { it.build() }
7680
}
7781

7882
private fun createAndStartMainNode(context: ConfigurableApplicationContext): Pair<Node, RedisServer> {
79-
val nextNode = nodeProvider.next()
83+
nodeProvider = nodeProvider()
84+
val nextNode = nodeProvider!!.next()
8085
val builder = RedisServer.newRedisServer()
8186
.bind(nextNode.bind)
8287
.port(nextNode.port)
@@ -100,7 +105,7 @@ internal class RedisHighAvailabilityContextCustomizer(
100105
private fun createReplicaBuilder(
101106
mainNode: Node
102107
): RedisServerBuilder {
103-
val nextNode = nodeProvider.next()
108+
val nextNode = nodeProvider!!.next()
104109
val builder = RedisServer.newRedisServer()
105110
.bind(nextNode.bind)
106111
.port(nextNode.port)
@@ -137,13 +142,12 @@ internal class RedisHighAvailabilityContextCustomizer(
137142

138143
private fun createSentinel(
139144
sentinelConfig: EmbeddedRedisHighAvailability.Sentinel,
140-
replicationGroup: Pair<Node, List<RedisServer>>
145+
mainNode: Pair<Node, RedisServer>
141146
): RedisSentinel {
142-
val mainNode = replicationGroup.first
143147
val builder = RedisSentinel.newRedisSentinel()
144148
.bind(sentinelConfig.bind.ifEmpty { DEFAULT_BIND })
145149
.port(if (sentinelConfig.port == 0) unspecifiedUnusedPort(true) else sentinelConfig.port)
146-
.setting("sentinel monitor $name ${mainNode.bind} ${mainNode.port} $QUORUM_SIZE")
150+
.setting("sentinel monitor $name ${mainNode.first.bind} ${mainNode.first.port} $QUORUM_SIZE")
147151
.setting("sentinel down-after-milliseconds $name ${sentinelConfig.downAfterMillis}")
148152
.setting("sentinel failover-timeout $name ${sentinelConfig.failOverTimeoutMillis}")
149153
.setting("sentinel parallel-syncs $name ${sentinelConfig.parallelSyncs}")

src/test/kotlin/io/github/tobi/laa/spring/boot/embedded/redis/highavailability/RedisHighAvailabilityContextCustomizerTest.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ internal class RedisHighAvailabilityContextCustomizerTest {
5858

5959
@Test
6060
@DisplayName("RedisHighAvailabilityContextCustomizer should throw NoSuchElementException when no nodes are available")
61-
fun noMoreNodes_shouldThrow() {
61+
fun noNodes_shouldThrow() {
6262
mockkConstructor(NodeProvider::class) {
6363
every { anyConstructed<NodeProvider>().next() } throws NoSuchElementException()
6464
assertThatThrownBy {
@@ -73,6 +73,26 @@ internal class RedisHighAvailabilityContextCustomizerTest {
7373
}
7474
}
7575

76+
@Test
77+
@DisplayName("RedisHighAvailabilityContextCustomizer should throw NoSuchElementException when not enough nodes are available")
78+
fun notEnoughNodes_shouldThrow() {
79+
mockkConstructor(NodeProvider::class) {
80+
every { anyConstructed<NodeProvider>().next() } returns Node(
81+
12345,
82+
"127.0.0.1"
83+
) andThenThrows NoSuchElementException()
84+
assertThatThrownBy {
85+
AnnotationConfigApplicationContext().use {
86+
RedisHighAvailabilityContextCustomizerFactory()
87+
.createContextCustomizer(AnnotatedClass::class.java, mutableListOf())
88+
.customizeContext(it, mockk())
89+
it.refresh()
90+
it.start()
91+
}
92+
}.isInstanceOf(NoSuchElementException::class.java)
93+
}
94+
}
95+
7696
@Test
7797
@DisplayName("RedisHighAvailabilityContextCustomizer.NodeProvider should provide expected nodes")
7898
fun nodeProvider_shouldProvideExpectedNodes() {

0 commit comments

Comments
 (0)