Skip to content

Commit 249faf6

Browse files
committed
✅ Bump coverage
1 parent 6683415 commit 249faf6

File tree

3 files changed

+92
-30
lines changed

3 files changed

+92
-30
lines changed

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

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ 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 nodePorts: Iterator<Int>
40-
private lateinit var nodeBinds: Iterator<String>
39+
private lateinit var nodeProvider: NodeProvider
4140
private val customizer = config.customizer.map { c -> c.createInstance() }.toList()
4241

4342
override fun customizeContext(context: ConfigurableApplicationContext, mergedConfig: MergedContextConfiguration) {
@@ -52,8 +51,7 @@ internal class RedisHighAvailabilityContextCustomizer(
5251
}
5352

5453
private fun createAndStartRedisInHighAvailabilityMode(context: ConfigurableApplicationContext): RedisCluster {
55-
nodePorts = ports().iterator()
56-
nodeBinds = binds().iterator()
54+
nodeProvider = nodeProvider()
5755
val redisHighAvailability = createRedisInHighAvailabilityMode(context)
5856
redisHighAvailability.start()
5957
log.info("Started Redis in high availability mode on ports ${redisHighAvailability.ports()}")
@@ -63,24 +61,25 @@ internal class RedisHighAvailabilityContextCustomizer(
6361
private fun createRedisInHighAvailabilityMode(context: ConfigurableApplicationContext): RedisCluster {
6462
val replicationGroup = createReplicationGroup(context)
6563
val sentinels = config.sentinels.map { createSentinel(it, replicationGroup) }
66-
return RedisCluster(sentinels, replicationGroup.second + replicationGroup.first.node)
64+
return RedisCluster(sentinels, replicationGroup.second)
6765
}
6866

6967
private fun createReplicationGroup(context: ConfigurableApplicationContext): Pair<Node, List<RedisServer>> {
7068
val mainNode = createAndStartMainNode(context)
7169
val replicaBuilders =
7270
IntStream.range(0, config.replicas)
73-
.mapToObj { _ -> createReplicaBuilder(mainNode) }
71+
.mapToObj { _ -> createReplicaBuilder(mainNode.first) }
7472
.toList()
7573
customizer.forEach { c -> c.customizeReplicas(replicaBuilders, config) }
7674

77-
return Pair(mainNode, replicaBuilders.map { it.build() })
75+
return Pair(mainNode.first, replicaBuilders.map { it.build() } + mainNode.second)
7876
}
7977

80-
private fun createAndStartMainNode(context: ConfigurableApplicationContext): Node {
78+
private fun createAndStartMainNode(context: ConfigurableApplicationContext): Pair<Node, RedisServer> {
79+
val nextNode = nodeProvider.next()
8180
val builder = RedisServer.newRedisServer()
82-
.bind(nodeBinds.next())
83-
.port(nodePorts.next())
81+
.bind(nextNode.bind)
82+
.port(nextNode.port)
8483
if (config.executeInDirectory.isNotEmpty()) {
8584
builder.executableProvider(ExecutableProvider.newJarResourceProvider(File(config.executeInDirectory)))
8685
}
@@ -95,29 +94,37 @@ internal class RedisHighAvailabilityContextCustomizer(
9594
stopSafely(mainNode)
9695
}
9796
}
98-
return Node(mainNode)
97+
return Node(mainNode) to mainNode
9998
}
10099

101100
private fun createReplicaBuilder(
102101
mainNode: Node
103102
): RedisServerBuilder {
103+
val nextNode = nodeProvider.next()
104104
val builder = RedisServer.newRedisServer()
105-
.bind(nodeBinds.next())
106-
.port(nodePorts.next())
105+
.bind(nextNode.bind)
106+
.port(nextNode.port)
107107
.slaveOf(mainNode.bind, mainNode.port)
108108
if (config.executeInDirectory.isNotEmpty()) {
109109
builder.executableProvider(ExecutableProvider.newJarResourceProvider(File(config.executeInDirectory)))
110110
}
111111
return builder
112112
}
113113

114-
private fun ports(): List<Int> {
115-
return if (config.ports.isEmpty()) {
114+
private fun nodeProvider(): NodeProvider {
115+
val ports = if (config.ports.isEmpty()) {
116116
val nOfNodes = config.replicas + 1
117117
IntStream.range(0, nOfNodes).map { _ -> unspecifiedUnusedPort() }.toList()
118118
} else {
119119
config.ports.map { if (it == 0) unspecifiedUnusedPort() else it }.toList()
120120
}
121+
val binds = if (config.binds.isEmpty()) {
122+
val nOfNodes = config.replicas + 1
123+
IntStream.range(0, nOfNodes).mapToObj { _ -> DEFAULT_BIND }.toList()
124+
} else {
125+
config.binds.map { it.ifEmpty { DEFAULT_BIND } }.toList()
126+
}
127+
return NodeProvider(ports, binds)
121128
}
122129

123130
private fun unspecifiedUnusedPort(sentinel: Boolean = false): Int {
@@ -128,15 +135,6 @@ internal class RedisHighAvailabilityContextCustomizer(
128135
return port
129136
}
130137

131-
private fun binds(): List<String> {
132-
return if (config.binds.isEmpty()) {
133-
val nOfNodes = config.replicas + 1
134-
IntStream.range(0, nOfNodes).mapToObj { _ -> DEFAULT_BIND }.toList()
135-
} else {
136-
config.binds.map { it.ifEmpty { DEFAULT_BIND } }.toList()
137-
}
138-
}
139-
140138
private fun createSentinel(
141139
sentinelConfig: EmbeddedRedisHighAvailability.Sentinel,
142140
replicationGroup: Pair<Node, List<RedisServer>>
@@ -213,9 +211,18 @@ internal class RedisHighAvailabilityContextCustomizer(
213211
return config.hashCode()
214212
}
215213

216-
internal data class Node(val node: RedisServer, val port: Int, val bind: String) {
214+
internal class NodeProvider(private val addresses: Iterator<Pair<Int, String>>) {
215+
216+
constructor(ports: List<Int>, binds: List<String>) : this(ports.zip(binds).iterator())
217+
218+
fun next(): Node {
219+
val (port, bind) = addresses.next()
220+
return Node(port, bind)
221+
}
222+
}
223+
224+
internal data class Node(val port: Int, val bind: String) {
217225
constructor(node: RedisServer) : this(
218-
node,
219226
node.ports().first(),
220227
parseBindAddress(node)
221228
)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired
1212
name = "Zigzag Heron",
1313
replicas = 2,
1414
binds = ["", "localhost", "::1"],
15-
ports = [7001, 26379, 7002],
15+
ports = [0, 26379, 7002],
1616
sentinels = [
1717
Sentinel(
1818
bind = "127.0.0.1",
@@ -56,7 +56,7 @@ internal class CustomSettingsTest {
5656
.embeddedRedis()
5757
.shouldHaveNodes()
5858
.thatHaveASizeOf(3)
59-
.withOne().thatRunsOn("127.0.0.1", 7001)
59+
.withOne().thatRunsOn("127.0.0.1", 6379)
6060
.and().withOne().thatRunsOn("localhost", 26379)
6161
.and().withOne().thatRunsOn("::1", 7002)
6262
}

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

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package io.github.tobi.laa.spring.boot.embedded.redis.highavailability
22

33
import io.github.tobi.laa.spring.boot.embedded.redis.RedisClient
44
import io.github.tobi.laa.spring.boot.embedded.redis.RedisStore
5+
import io.github.tobi.laa.spring.boot.embedded.redis.highavailability.RedisHighAvailabilityContextCustomizer.Node
6+
import io.github.tobi.laa.spring.boot.embedded.redis.highavailability.RedisHighAvailabilityContextCustomizer.NodeProvider
57
import io.github.tobi.laa.spring.boot.embedded.redis.ports.PortProvider
8+
import io.mockk.every
69
import io.mockk.impl.annotations.RelaxedMockK
710
import io.mockk.junit5.MockKExtension
811
import io.mockk.mockk
12+
import io.mockk.mockkConstructor
913
import org.assertj.core.api.Assertions.assertThat
1014
import org.assertj.core.api.Assertions.assertThatThrownBy
1115
import org.junit.jupiter.api.BeforeEach
@@ -31,7 +35,6 @@ internal class RedisHighAvailabilityContextCustomizerTest {
3135
givenCustomizer = RedisHighAvailabilityContextCustomizer(config, portProvider)
3236
}
3337

34-
3538
@Test
3639
@DisplayName("RedisHighAvailabilityContextCustomizer.equals() should be true for itself")
3740
fun sameObject_equals_shouldBeTrue() {
@@ -53,6 +56,58 @@ internal class RedisHighAvailabilityContextCustomizerTest {
5356
assertThat(comparedToDifferentClass).isFalse()
5457
}
5558

59+
@Test
60+
@DisplayName("RedisHighAvailabilityContextCustomizer should throw NoSuchElementException when no nodes are available")
61+
fun noMoreNodes_shouldThrow() {
62+
mockkConstructor(NodeProvider::class) {
63+
every { anyConstructed<NodeProvider>().next() } throws NoSuchElementException()
64+
assertThatThrownBy {
65+
AnnotationConfigApplicationContext().use {
66+
RedisHighAvailabilityContextCustomizerFactory()
67+
.createContextCustomizer(AnnotatedClass::class.java, mutableListOf())
68+
.customizeContext(it, mockk())
69+
it.refresh()
70+
it.start()
71+
}
72+
}.isInstanceOf(NoSuchElementException::class.java)
73+
}
74+
}
75+
76+
@Test
77+
@DisplayName("RedisHighAvailabilityContextCustomizer.NodeProvider should provide expected nodes")
78+
fun nodeProvider_shouldProvideExpectedNodes() {
79+
val ports = listOf(1, 2, 3)
80+
val binds = listOf("foo", "bar", "baz")
81+
val nodeProvider = NodeProvider(ports, binds)
82+
val actual = ports.indices.map { nodeProvider.next() }
83+
val expected = ports.zip(binds).map { (port, bind) -> Node(port, bind) }
84+
assertThat(actual).containsExactlyElementsOf(expected)
85+
}
86+
87+
@Test
88+
@DisplayName("RedisHighAvailabilityContextCustomizer.NodeProvider should throw NoSuchElementException when no more ports are available")
89+
fun nodeProvider_noMorePorts_shouldThrow() {
90+
val ports = listOf(1)
91+
val binds = listOf("foo", "bar", "baz")
92+
val nodeProvider = NodeProvider(ports, binds)
93+
val actual = ports.indices.map { nodeProvider.next() }
94+
val expected = ports.zip(binds).map { (port, bind) -> Node(port, bind) }
95+
assertThat(actual).containsExactlyElementsOf(expected)
96+
assertThatThrownBy { nodeProvider.next() }.isInstanceOf(NoSuchElementException::class.java)
97+
}
98+
99+
@Test
100+
@DisplayName("RedisHighAvailabilityContextCustomizer.NodeProvider should throw NoSuchElementException when no more binds are available")
101+
fun nodeProvider_noMoreBinds_shouldThrow() {
102+
val ports = listOf(1, 2, 3)
103+
val binds = listOf("foo")
104+
val nodeProvider = NodeProvider(ports, binds)
105+
val actual = binds.indices.map { nodeProvider.next() }
106+
val expected = ports.zip(binds).map { (port, bind) -> Node(port, bind) }
107+
assertThat(actual).containsExactlyElementsOf(expected)
108+
assertThatThrownBy { nodeProvider.next() }.isInstanceOf(NoSuchElementException::class.java)
109+
}
110+
56111
@Test
57112
@DisplayName("Closing ApplicationContext should stop Redis server and Redis client")
58113
fun closingApplicationContext_shouldStopRedisServerAndRedisClient() {
@@ -71,6 +126,6 @@ internal class RedisHighAvailabilityContextCustomizerTest {
71126
assertThatThrownBy { client!!.get("FOO") }.isInstanceOf(Exception::class.java)
72127
}
73128

74-
@EmbeddedRedisHighAvailability
129+
@EmbeddedRedisHighAvailability(ports = [0, 0, 0])
75130
private class AnnotatedClass
76131
}

0 commit comments

Comments
 (0)