Skip to content

Commit fab65a9

Browse files
committed
Add new tests
Signed-off-by: Dmitry Sulman <[email protected]>
1 parent 16983e5 commit fab65a9

File tree

12 files changed

+492
-121
lines changed

12 files changed

+492
-121
lines changed

buildSrc/src/main/kotlin/conventions.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
import org.jlleitschuh.gradle.ktlint.KtlintExtension
23

34
plugins {
45
kotlin("jvm")
@@ -105,7 +106,7 @@ tasks.jacocoTestReport {
105106
}
106107
}
107108

108-
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
109+
configure<KtlintExtension> {
109110
version = "1.5.0"
110111
additionalEditorconfig.set(
111112
mapOf(

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ kotlin = "2.1.20"
77
ktlint = "12.2.0"
88
logbackAccess = "2.0.6"
99
logbackClassic = "1.5.18"
10+
mockk = "1.14.2"
1011
reactorNetty = "1.2.6-SNAPSHOT"
1112
slf4j = "2.0.17"
1213

@@ -21,6 +22,7 @@ ktlintPlugin = { group = "org.jlleitschuh.gradle.ktlint", name = "org.jlleitschu
2122
logback-access-common = { group = "ch.qos.logback.access", name = "logback-access-common", version.ref = "logbackAccess" }
2223
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logbackClassic" }
2324
logback-core = { group = "ch.qos.logback", name = "logback-core", version.ref = "logbackClassic" }
25+
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
2426
reactorNetty-http = { group = "io.projectreactor.netty", name = "reactor-netty-http", version.ref = "reactorNetty" }
2527
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
2628

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313
testImplementation(libs.kotest.assertions.core.jvm)
1414
testImplementation(libs.logback.classic)
1515
testImplementation(libs.logback.core)
16+
testImplementation(libs.mockk)
1617

1718
testRuntimeOnly(libs.junit.platformLauncher)
1819
}

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,10 @@ class AccessEvent(
194194
override fun getResponseHeaderNameList() = _responseHeaderMap.keys.toList()
195195

196196
companion object {
197-
@JvmStatic
198-
private val EMPTY_STRING = ""
197+
private const val EMPTY_STRING = ""
199198

200-
@JvmStatic
201-
private val NA = "-"
199+
private const val NA = "-"
202200

203-
@JvmStatic
204201
private val NA_ARRAY = arrayOf(NA)
205202
}
206203
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class AccessLog(
3838
* If an error occurs during the logging process, it is recorded in the status manager of the [AccessContext] and logged
3939
* using the internal logger.
4040
*/
41-
override fun log() {
41+
public override fun log() {
4242
try {
4343
val accessEvent = AccessEvent(argProvider, accessContext)
4444
accessEvent.threadName = Thread.currentThread().name
@@ -52,7 +52,6 @@ class AccessLog(
5252
}
5353

5454
companion object {
55-
@JvmStatic
5655
private val logger = LoggerFactory.getLogger(AccessLog::class.java)
5756
}
5857
}

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,11 @@ class ReactorNettyAccessLogFactory : AccessLogFactory {
203203
/**
204204
* The name of the system property used to specify the Logback Access configuration file.
205205
*/
206-
@JvmStatic
207-
val CONFIG_FILE_NAME_PROPERTY = "logback.access.reactor.netty.config"
206+
const val CONFIG_FILE_NAME_PROPERTY = "logback.access.reactor.netty.config"
208207

209208
/**
210209
* The default configuration file name used for the Logback Access configuration.
211210
*/
212-
@JvmStatic
213-
val DEFAULT_CONFIG_FILE_NAME = "logback-access.xml"
211+
const val DEFAULT_CONFIG_FILE_NAME = "logback-access.xml"
214212
}
215213
}
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
package io.github.dmitrysulman.logback.access.reactor.netty
2+
3+
import ch.qos.logback.access.common.spi.AccessContext
4+
import io.kotest.matchers.maps.shouldContainKey
5+
import io.kotest.matchers.shouldBe
6+
import io.mockk.every
7+
import io.mockk.mockk
8+
import io.mockk.verify
9+
import org.junit.jupiter.api.Test
10+
import reactor.netty.http.server.ConnectionInformation
11+
import reactor.netty.http.server.logging.AccessLogArgProvider
12+
import java.net.SocketAddress
13+
import java.util.Collections
14+
15+
// TODO serialization of AccessEvent
16+
// TODO cookies
17+
// TODO request/response headers
18+
class AccessEventTests {
19+
@Test
20+
fun `test basic properties`() {
21+
val mockContext = mockk<AccessContext>(relaxed = true)
22+
val mockConnectionInformation = mockk<ConnectionInformation>()
23+
every { mockConnectionInformation.connectionRemoteAddress() } returns
24+
object : SocketAddress() {
25+
override fun toString() = "192.168.1.1"
26+
}
27+
every { mockConnectionInformation.hostPort() } returns 1000
28+
val mockArgProvider = mockk<AccessLogArgProvider>()
29+
every { mockArgProvider.method() } returns "GET"
30+
every { mockArgProvider.uri() } returns "/test?param=value"
31+
every { mockArgProvider.protocol() } returns "HTTP/1.1"
32+
every { mockArgProvider.status() } returns "200"
33+
every { mockArgProvider.contentLength() } returns 100
34+
every { mockArgProvider.duration() } returns 1001
35+
every { mockArgProvider.user() } returns "username"
36+
every { mockArgProvider.requestHeaderIterator() } returns Collections.emptyIterator()
37+
every { mockArgProvider.responseHeaderIterator() } returns Collections.emptyIterator()
38+
every { mockArgProvider.cookies() } returns emptyMap()
39+
every { mockArgProvider.connectionInformation() } returns mockConnectionInformation
40+
41+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
42+
43+
accessEvent.method shouldBe "GET"
44+
accessEvent.requestURI shouldBe "/test"
45+
accessEvent.queryString shouldBe "?param=value"
46+
accessEvent.getRequestParameter("param") shouldBe arrayOf("value")
47+
accessEvent.getRequestParameter("no_param") shouldBe NA_ARRAY
48+
accessEvent.protocol shouldBe "HTTP/1.1"
49+
accessEvent.requestURL shouldBe "GET /test?param=value HTTP/1.1"
50+
accessEvent.statusCode shouldBe 200
51+
accessEvent.contentLength shouldBe 100L
52+
accessEvent.elapsedTime shouldBe 1001L
53+
accessEvent.elapsedSeconds shouldBe 1L
54+
accessEvent.remoteAddr shouldBe "192.168.1.1"
55+
accessEvent.remoteHost shouldBe "192.168.1.1"
56+
accessEvent.serverName shouldBe "192.168.1.1"
57+
accessEvent.localPort shouldBe 1000
58+
accessEvent.remoteUser shouldBe "username"
59+
accessEvent.sequenceNumber shouldBe 0
60+
accessEvent.getRequestHeader("Header") shouldBe NA
61+
accessEvent.getResponseHeader("Header") shouldBe NA
62+
accessEvent.getCookie("cookie") shouldBe NA
63+
}
64+
65+
@Test
66+
fun `test laziness of the properties`() {
67+
val mockContext = mockk<AccessContext>(relaxed = true)
68+
val mockConnectionInformation = mockk<ConnectionInformation>(relaxed = true)
69+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
70+
every { mockArgProvider.connectionInformation() } returns mockConnectionInformation
71+
72+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
73+
74+
verify(exactly = 1) { mockContext.sequenceNumberGenerator }
75+
verify(exactly = 1) { mockArgProvider.duration() }
76+
verify(exactly = 0) { mockArgProvider.method() }
77+
verify(exactly = 0) { mockArgProvider.uri() }
78+
verify(exactly = 0) { mockArgProvider.protocol() }
79+
verify(exactly = 0) { mockArgProvider.status() }
80+
verify(exactly = 0) { mockArgProvider.contentLength() }
81+
verify(exactly = 0) { mockArgProvider.user() }
82+
verify(exactly = 0) { mockArgProvider.requestHeaderIterator() }
83+
verify(exactly = 0) { mockArgProvider.responseHeaderIterator() }
84+
verify(exactly = 0) { mockArgProvider.cookies() }
85+
verify(exactly = 0) { mockArgProvider.connectionInformation() }
86+
verify(exactly = 0) { mockConnectionInformation.connectionRemoteAddress() }
87+
verify(exactly = 0) { mockConnectionInformation.hostPort() }
88+
89+
repeat(2) {
90+
accessEvent.method
91+
accessEvent.requestURI
92+
accessEvent.queryString
93+
accessEvent.requestParameterMap
94+
accessEvent.getRequestParameter("param")
95+
accessEvent.protocol
96+
accessEvent.requestURL
97+
accessEvent.statusCode
98+
accessEvent.contentLength
99+
accessEvent.elapsedTime
100+
accessEvent.elapsedSeconds
101+
accessEvent.remoteAddr
102+
accessEvent.remoteHost
103+
accessEvent.serverName
104+
accessEvent.localPort
105+
accessEvent.remoteUser
106+
accessEvent.getRequestHeader("Header")
107+
accessEvent.getResponseHeader("Header")
108+
accessEvent.requestHeaderMap
109+
accessEvent.requestHeaderNames
110+
accessEvent.responseHeaderMap
111+
accessEvent.responseHeaderNameList
112+
accessEvent.getCookie("cookie")
113+
}
114+
115+
verifyAllMethods(mockArgProvider, mockConnectionInformation)
116+
}
117+
118+
@Test
119+
fun `test prepare for deferred processing`() {
120+
val mockContext = mockk<AccessContext>(relaxed = true)
121+
val mockConnectionInformation = mockk<ConnectionInformation>(relaxed = true)
122+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
123+
every { mockArgProvider.connectionInformation() } returns mockConnectionInformation
124+
125+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
126+
127+
repeat(2) {
128+
accessEvent.prepareForDeferredProcessing()
129+
}
130+
131+
verifyAllMethods(mockArgProvider, mockConnectionInformation)
132+
}
133+
134+
private fun verifyAllMethods(
135+
mockArgProvider: AccessLogArgProvider,
136+
mockConnectionInformation: ConnectionInformation,
137+
) {
138+
verify(exactly = 1) { mockArgProvider.method() }
139+
verify(exactly = 2) { mockArgProvider.uri() }
140+
verify(exactly = 1) { mockArgProvider.protocol() }
141+
verify(exactly = 1) { mockArgProvider.status() }
142+
verify(exactly = 1) { mockArgProvider.contentLength() }
143+
verify(exactly = 1) { mockArgProvider.user() }
144+
verify(exactly = 1) { mockArgProvider.requestHeaderIterator() }
145+
verify(exactly = 1) { mockArgProvider.responseHeaderIterator() }
146+
verify(exactly = 1) { mockArgProvider.cookies() }
147+
verify(exactly = 3) { mockArgProvider.connectionInformation() }
148+
verify(exactly = 2) { mockConnectionInformation.connectionRemoteAddress() }
149+
verify(exactly = 1) { mockConnectionInformation.hostPort() }
150+
}
151+
152+
@Test
153+
fun `test request URL with no query parameters`() {
154+
val mockContext = mockk<AccessContext>(relaxed = true)
155+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
156+
every { mockArgProvider.method() } returns "GET"
157+
every { mockArgProvider.uri() } returns "/test"
158+
every { mockArgProvider.protocol() } returns "HTTP/1.1"
159+
160+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
161+
162+
accessEvent.requestURL shouldBe "GET /test HTTP/1.1"
163+
}
164+
165+
@Test
166+
fun `test query string parsing - no parameters`() {
167+
val mockContext = mockk<AccessContext>(relaxed = true)
168+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
169+
every { mockArgProvider.uri() } returns "/test"
170+
171+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
172+
173+
accessEvent.queryString.isEmpty() shouldBe true
174+
accessEvent.requestParameterMap.isEmpty() shouldBe true
175+
accessEvent.getRequestParameter("param") shouldBe NA_ARRAY
176+
}
177+
178+
@Test
179+
fun `test query string parsing - single parameter`() {
180+
val mockContext = mockk<AccessContext>(relaxed = true)
181+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
182+
every { mockArgProvider.uri() } returns "/test?param1=value1"
183+
184+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
185+
186+
accessEvent.queryString shouldBe "?param1=value1"
187+
accessEvent.requestParameterMap.size shouldBe 1
188+
accessEvent.requestParameterMap shouldContainKey "param1"
189+
accessEvent.getRequestParameter("param1") shouldBe arrayOf("value1")
190+
}
191+
192+
@Test
193+
fun `test query string parsing - multiple parameters`() {
194+
val mockContext = mockk<AccessContext>(relaxed = true)
195+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
196+
every { mockArgProvider.uri() } returns "/test?param1=value1&param2=value2"
197+
198+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
199+
200+
accessEvent.queryString shouldBe "?param1=value1&param2=value2"
201+
accessEvent.requestParameterMap.size shouldBe 2
202+
accessEvent.requestParameterMap shouldContainKey "param1"
203+
accessEvent.requestParameterMap shouldContainKey "param2"
204+
accessEvent.getRequestParameter("param1") shouldBe arrayOf("value1")
205+
accessEvent.getRequestParameter("param2") shouldBe arrayOf("value2")
206+
}
207+
208+
@Test
209+
fun `test query string parsing - multiple values for same parameter`() {
210+
val mockContext = mockk<AccessContext>(relaxed = true)
211+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
212+
every { mockArgProvider.uri() } returns "/test?param1=value1&param1=value2"
213+
214+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
215+
216+
accessEvent.queryString shouldBe "?param1=value1&param1=value2"
217+
accessEvent.requestParameterMap.size shouldBe 1
218+
accessEvent.requestParameterMap shouldContainKey "param1"
219+
accessEvent.getRequestParameter("param1") shouldBe arrayOf("value1", "value2")
220+
}
221+
222+
@Test
223+
fun `test query string parsing - empty parameter value`() {
224+
val mockContext = mockk<AccessContext>(relaxed = true)
225+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
226+
every { mockArgProvider.uri() } returns "/test?param1="
227+
228+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
229+
230+
accessEvent.queryString shouldBe "?param1="
231+
accessEvent.requestParameterMap.isEmpty() shouldBe true
232+
accessEvent.getRequestParameter("param1") shouldBe NA_ARRAY
233+
}
234+
235+
@Test
236+
fun `test query string parsing - URL encoded parameters`() {
237+
val mockContext = mockk<AccessContext>(relaxed = true)
238+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
239+
every { mockArgProvider.uri() } returns "/test?param%201=value%201&param2=value+2"
240+
241+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
242+
243+
accessEvent.queryString shouldBe "?param%201=value%201&param2=value+2"
244+
accessEvent.requestParameterMap.size shouldBe 2
245+
accessEvent.requestParameterMap shouldContainKey "param 1"
246+
accessEvent.requestParameterMap shouldContainKey "param2"
247+
accessEvent.getRequestParameter("param 1") shouldBe arrayOf("value 1")
248+
accessEvent.getRequestParameter("param2") shouldBe arrayOf("value 2")
249+
}
250+
251+
@Test
252+
fun `test query string parsing - special characters`() {
253+
val mockContext = mockk<AccessContext>(relaxed = true)
254+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
255+
every { mockArgProvider.uri() } returns "/test?param1=value%40%23%24&param2=%26%3D%3F"
256+
257+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
258+
259+
accessEvent.queryString shouldBe "?param1=value%40%23%24&param2=%26%3D%3F"
260+
accessEvent.requestParameterMap.size shouldBe 2
261+
accessEvent.requestParameterMap shouldContainKey "param1"
262+
accessEvent.requestParameterMap shouldContainKey "param2"
263+
accessEvent.getRequestParameter("param1") shouldBe arrayOf("value@#$")
264+
accessEvent.getRequestParameter("param2") shouldBe arrayOf("&=?")
265+
}
266+
267+
@Test
268+
fun `test query string parsing - malformed query string`() {
269+
val mockContext = mockk<AccessContext>(relaxed = true)
270+
val mockArgProvider = mockk<AccessLogArgProvider>(relaxed = true)
271+
every { mockArgProvider.uri() } returns "/test?param1&param2=value2&=value3&param4="
272+
273+
val accessEvent = AccessEvent(mockArgProvider, mockContext)
274+
275+
accessEvent.queryString shouldBe "?param1&param2=value2&=value3&param4="
276+
accessEvent.requestParameterMap.size shouldBe 1
277+
accessEvent.requestParameterMap shouldContainKey "param2"
278+
accessEvent.getRequestParameter("param1") shouldBe NA_ARRAY
279+
accessEvent.getRequestParameter("param2") shouldBe arrayOf("value2")
280+
accessEvent.getRequestParameter("param4") shouldBe NA_ARRAY
281+
}
282+
283+
companion object {
284+
private const val NA = "-"
285+
private val NA_ARRAY = arrayOf(NA)
286+
}
287+
}

0 commit comments

Comments
 (0)