Skip to content

Commit 7e8cc41

Browse files
committed
Add more tests
Signed-off-by: Dmitry Sulman <[email protected]>
1 parent 6ba4e43 commit 7e8cc41

File tree

8 files changed

+178
-38
lines changed

8 files changed

+178
-38
lines changed

logback-access-reactor-netty/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ dependencies {
1111

1212
testImplementation(libs.junit.jupiter)
1313
testImplementation(libs.kotest.assertions.core.jvm)
14+
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
1415
testImplementation(libs.logback.classic)
1516
testImplementation(libs.logback.core)
1617
testImplementation(libs.mockk)

logback-access-reactor-netty/src/main/kotlin/io/github/dmitrysulman/logback/access/reactor/netty/AccessEvent.kt

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,24 @@ class AccessEvent(
114114
argProvider
115115
.requestHeaderIterator()
116116
?.asSequence()
117-
?.associate { it.key.toString() to it.value.toString() }
117+
?.mapNotNull { (name, value) ->
118+
if (name.isNullOrEmpty()) return@mapNotNull null
119+
if (value == null) return@mapNotNull null
120+
name.toString() to value.toString()
121+
}?.toMap()
118122
?: emptyMap()
119123
}
120124

121125
@Transient
122126
private val _serverAdapter = ReactorNettyServerAdapter(argProvider)
123127

128+
private fun String.decodeCatching() =
129+
try {
130+
URLDecoder.decode(this, StandardCharsets.UTF_8)
131+
} catch (_: Exception) {
132+
this
133+
}
134+
124135
override fun prepareForDeferredProcessing() {
125136
requestURI
126137
requestURL
@@ -211,7 +222,7 @@ class AccessEvent(
211222

212223
override fun getResponseHeader(key: String) = _responseHeaderMap[key] ?: NA
213224

214-
override fun getResponseHeaderMap(): Map<String, String> = _responseHeaderMap
225+
override fun getResponseHeaderMap() = _responseHeaderMap
215226

216227
override fun getResponseHeaderNameList() = _responseHeaderMap.keys.toList()
217228

@@ -222,11 +233,4 @@ class AccessEvent(
222233

223234
private val NA_ARRAY = arrayOf(NA)
224235
}
225-
226-
private fun String.decodeCatching(): String =
227-
try {
228-
URLDecoder.decode(this, StandardCharsets.UTF_8)
229-
} catch (_: Exception) {
230-
this
231-
}
232236
}

logback-access-reactor-netty/src/main/kotlin/io/github/dmitrysulman/logback/access/reactor/netty/ReactorNettyServerAdapter.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class ReactorNettyServerAdapter(
1616
argProvider
1717
.responseHeaderIterator()
1818
?.asSequence()
19-
?.associate { it.key.toString() to it.value.toString() }
19+
?.mapNotNull { (name, value) ->
20+
if (name.isNullOrEmpty()) return@mapNotNull null
21+
if (value == null) return@mapNotNull null
22+
name.toString() to value.toString()
23+
}?.toMap()
2024
?: emptyMap()
2125
}

logback-access-reactor-netty/src/test/kotlin/io/github/dmitrysulman/logback/access/reactor/netty/AccessEventTests.kt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import java.net.SocketAddress
2020
import java.util.Collections
2121
import io.netty.handler.codec.http.cookie.Cookie as NettyCookie
2222

23-
// TODO request/response headers
2423
class AccessEventTests {
2524
@Test
2625
fun `test basic properties`() {
@@ -316,6 +315,10 @@ class AccessEventTests {
316315
accessEvent.remoteUser shouldBe NA
317316
accessEvent.getRequestHeader(HEADER) shouldBe NA
318317
accessEvent.getResponseHeader(HEADER) shouldBe NA
318+
accessEvent.requestHeaderMap.isEmpty() shouldBe true
319+
accessEvent.requestHeaderNames.hasMoreElements() shouldBe false
320+
accessEvent.responseHeaderMap.isEmpty() shouldBe true
321+
accessEvent.responseHeaderNameList.isEmpty() shouldBe true
319322
accessEvent.getCookie(COOKIE) shouldBe NA
320323
}
321324

@@ -378,6 +381,45 @@ class AccessEventTests {
378381
accessEvent.getCookie(" ") shouldBe NA
379382
}
380383

384+
@Test
385+
fun `test request headers`() {
386+
val mockContext = mockk<AccessContext>(relaxed = true)
387+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
388+
every { mockArgProvider.requestHeaderIterator() } returns headerListIterator()
389+
390+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
391+
392+
accessEvent.requestHeaderMap shouldBe
393+
mapOf(
394+
"name1" to "value1",
395+
"name2" to "value2",
396+
"empty_value" to "",
397+
)
398+
accessEvent.requestHeaderNames
399+
.asIterator()
400+
.asSequence()
401+
.toList() shouldBe
402+
listOf("name1", "name2", "empty_value")
403+
}
404+
405+
@Test
406+
fun `test response headers`() {
407+
val mockContext = mockk<AccessContext>(relaxed = true)
408+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
409+
every { mockArgProvider.responseHeaderIterator() } returns headerListIterator()
410+
411+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
412+
413+
accessEvent.responseHeaderMap shouldBe
414+
mapOf(
415+
"name1" to "value1",
416+
"name2" to "value2",
417+
"empty_value" to "",
418+
)
419+
accessEvent.responseHeaderNameList shouldBe
420+
listOf("name1", "name2", "empty_value")
421+
}
422+
381423
@Test
382424
fun `test serialization`() {
383425
val mockContext = mockk<AccessContext>(relaxed = true)
@@ -422,6 +464,7 @@ class AccessEventTests {
422464
accessEvent.getRequestHeader(HEADER) shouldBe NA
423465
accessEvent.getResponseHeader(HEADER) shouldBe NA
424466
accessEvent.requestHeaderMap.isEmpty() shouldBe true
467+
accessEvent.requestHeaderNames.hasMoreElements() shouldBe false
425468
accessEvent.responseHeaderMap.isEmpty() shouldBe true
426469
accessEvent.responseHeaderNameList.isEmpty() shouldBe true
427470
accessEvent.cookies.isEmpty() shouldBe true

logback-access-reactor-netty/src/test/kotlin/io/github/dmitrysulman/logback/access/reactor/netty/ReactorNettyServerAdapterTests.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@ class ReactorNettyServerAdapterTests {
1616
every { mockArgProvider.accessDateTime() } returns ZonedDateTime.ofInstant(Instant.ofEpochMilli(1746734856000), ZoneId.of("UTC"))
1717
every { mockArgProvider.contentLength() } returns 100
1818
every { mockArgProvider.status() } returns "200"
19-
every { mockArgProvider.responseHeaderIterator() } returns
20-
listOf(
21-
object : Map.Entry<CharSequence, CharSequence> {
22-
override val key = "key"
23-
override val value = "value"
24-
},
25-
).iterator()
19+
every { mockArgProvider.responseHeaderIterator() } returns headerListIterator()
2620

2721
val reactorNettyServerAdapter = ReactorNettyServerAdapter(mockArgProvider)
2822

2923
reactorNettyServerAdapter.requestTimestamp shouldBe 1746734856000
3024
reactorNettyServerAdapter.contentLength shouldBe 100
3125
reactorNettyServerAdapter.statusCode shouldBe 200
32-
reactorNettyServerAdapter.buildResponseHeaderMap() shouldBe mapOf("key" to "value")
26+
reactorNettyServerAdapter.buildResponseHeaderMap() shouldBe
27+
mapOf(
28+
"name1" to "value1",
29+
"name2" to "value2",
30+
"empty_value" to "",
31+
)
3332
}
3433

3534
@Test
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.github.dmitrysulman.logback.access.reactor.netty
2+
3+
val headerList =
4+
listOf(
5+
object : Map.Entry<CharSequence, CharSequence> {
6+
override val key = "name1"
7+
override val value = "value1"
8+
},
9+
object : Map.Entry<CharSequence, CharSequence> {
10+
override val key = "name2"
11+
override val value = "value2"
12+
},
13+
object : Map.Entry<CharSequence, CharSequence> {
14+
override val key = ""
15+
override val value = "empty_name"
16+
},
17+
object : Map.Entry<CharSequence?, CharSequence> {
18+
override val key = null
19+
override val value = "null_name"
20+
},
21+
object : Map.Entry<CharSequence, CharSequence> {
22+
override val key = "empty_value"
23+
override val value = ""
24+
},
25+
object : Map.Entry<CharSequence, CharSequence?> {
26+
override val key = "null_value"
27+
override val value = null
28+
},
29+
)
30+
31+
fun headerListIterator() = headerList.iterator()

logback-access-reactor-netty/src/test/kotlin/io/github/dmitrysulman/logback/access/reactor/netty/integration/IntegrationTests.kt

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,20 @@ package io.github.dmitrysulman.logback.access.reactor.netty.integration
33
import ch.qos.logback.access.common.joran.JoranConfigurator
44
import ch.qos.logback.access.common.spi.IAccessEvent
55
import io.github.dmitrysulman.logback.access.reactor.netty.ReactorNettyAccessLogFactory
6+
import io.kotest.assertions.nondeterministic.eventually
7+
import io.kotest.matchers.collections.shouldBeSorted
8+
import io.kotest.matchers.collections.shouldBeUnique
69
import io.kotest.matchers.comparables.shouldBeGreaterThan
10+
import io.kotest.matchers.longs.shouldBeGreaterThanOrEqual
711
import io.kotest.matchers.nulls.shouldNotBeNull
812
import io.kotest.matchers.shouldBe
913
import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH
1014
import io.netty.handler.codec.http.cookie.DefaultCookie
15+
import kotlinx.coroutines.CoroutineScope
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.joinAll
18+
import kotlinx.coroutines.launch
19+
import kotlinx.coroutines.runBlocking
1120
import org.junit.jupiter.api.AfterEach
1221
import org.junit.jupiter.api.BeforeEach
1322
import org.junit.jupiter.api.Test
@@ -20,13 +29,11 @@ import java.net.InetSocketAddress
2029
import kotlin.time.Duration.Companion.seconds
2130
import kotlin.time.toJavaDuration
2231

23-
// TODO sequence number
2432
// TODO filters
25-
// TODO Performance test
2633
class IntegrationTests {
2734
private lateinit var server: DisposableServer
2835
private lateinit var eventCaptureAppender: EventCaptureAppender
29-
private var now: Long = 0
36+
private var now = 0L
3037

3138
@BeforeEach
3239
fun setUp() {
@@ -40,22 +47,71 @@ class IntegrationTests {
4047
}
4148

4249
@Test
43-
fun `test basic request`() {
44-
val accessLogFactory =
45-
ReactorNettyAccessLogFactory("logback-access-stdout.xml", JoranConfigurator(), true)
46-
.apply {
47-
accessContext.addAppender(eventCaptureAppender)
48-
}
50+
fun `test basic request`(): Unit =
51+
runBlocking {
52+
val accessLogFactory =
53+
ReactorNettyAccessLogFactory("logback-access-stdout.xml", JoranConfigurator(), true)
54+
.apply {
55+
accessContext.addAppender(eventCaptureAppender)
56+
}
4957

50-
server = createServer(accessLogFactory, "mock response")
51-
val response = performGetRequest("/test?name=value")
52-
response.shouldNotBeNull()
58+
server = createServer(accessLogFactory, "mock response")
59+
val response = performGetRequest("/test?name=value")
60+
response.shouldNotBeNull()
5361

54-
Thread.sleep(100)
55-
eventCaptureAppender.list.size shouldBe 1
56-
val accessEvent = eventCaptureAppender.list[0]
57-
assertAccessEvent(accessEvent, response)
58-
}
62+
eventually(1.seconds) {
63+
eventCaptureAppender.list.size shouldBe 1
64+
val accessEvent = eventCaptureAppender.list[0]
65+
accessEvent.sequenceNumber shouldBe 0
66+
assertAccessEvent(accessEvent, response)
67+
}
68+
}
69+
70+
@Test
71+
fun `test sequence number generator`(): Unit =
72+
runBlocking {
73+
val accessLogFactory =
74+
ReactorNettyAccessLogFactory("logback-access-sequence-number-generator.xml", JoranConfigurator(), true)
75+
.apply {
76+
accessContext.addAppender(eventCaptureAppender)
77+
}
78+
79+
server = createServer(accessLogFactory, "")
80+
81+
repeat(500) {
82+
performGetRequest("/test")
83+
}
84+
85+
eventually(1.seconds) {
86+
eventCaptureAppender.list.size shouldBe 500
87+
eventCaptureAppender.list
88+
.sortedBy { it.timeStamp }
89+
.map { it.sequenceNumber }
90+
.shouldBeUnique()
91+
.shouldBeSorted()
92+
}
93+
}
94+
95+
@Test
96+
fun `performance test`(): Unit =
97+
runBlocking {
98+
val accessLogFactory =
99+
ReactorNettyAccessLogFactory("logback-access-sequence-number-generator.xml", JoranConfigurator(), true)
100+
.apply {
101+
accessContext.addAppender(eventCaptureAppender)
102+
}
103+
104+
server = createServer(accessLogFactory, "test")
105+
106+
val jobs =
107+
(1..4000).map {
108+
CoroutineScope(Dispatchers.Default).launch {
109+
performGetRequest("/test")
110+
}
111+
}
112+
jobs.joinAll()
113+
eventually(5.seconds) { eventCaptureAppender.list.size shouldBe jobs.size }
114+
}
59115

60116
private fun assertAccessEvent(
61117
accessEvent: IAccessEvent,
@@ -71,9 +127,8 @@ class IntegrationTests {
71127
accessEvent.protocol shouldBe response.version().text()
72128
accessEvent.method shouldBe response.method().name()
73129
accessEvent.statusCode shouldBe response.status().code()
74-
accessEvent.elapsedTime shouldBeGreaterThan 0
130+
accessEvent.elapsedTime shouldBeGreaterThanOrEqual 0
75131
accessEvent.timeStamp shouldBeGreaterThan now
76-
accessEvent.sequenceNumber shouldBe 0
77132
accessEvent.remoteUser shouldBe "-"
78133
accessEvent.getRequestHeader(TEST_REQUEST_HEADER_NAME) shouldBe TEST_REQUEST_HEADER_VALUE
79134
accessEvent.getResponseHeader(TEST_RESPONSE_HEADER_NAME) shouldBe TEST_RESPONSE_HEADER_VALUE
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<configuration>
2+
<sequenceNumberGenerator class="ch.qos.logback.core.spi.BasicSequenceNumberGenerator"/>
3+
</configuration>

0 commit comments

Comments
 (0)