1+ /*
2+ * Copyright (c) 2024.
3+ *
4+ * This file is part of Imposter.
5+ *
6+ * "Commons Clause" License Condition v1.0
7+ *
8+ * The Software is provided to you by the Licensor under the License, as
9+ * defined below, subject to the following condition.
10+ *
11+ * Without limiting other conditions in the License, the grant of rights
12+ * under the License will not include, and the License does not grant to
13+ * you, the right to Sell the Software.
14+ *
15+ * For purposes of the foregoing, "Sell" means practicing any or all of
16+ * the rights granted to you under the License to provide to third parties,
17+ * for a fee or other consideration (including without limitation fees for
18+ * hosting or consulting/support services related to the Software), a
19+ * product or service whose value derives, entirely or substantially, from
20+ * the functionality of the Software. Any license notice or attribution
21+ * required by the License must also include this Commons Clause License
22+ * Condition notice.
23+ *
24+ * Software: Imposter
25+ *
26+ * License: GNU Lesser General Public License version 3
27+ *
28+ * Licensor: Peter Cornish
29+ *
30+ * Imposter is free software: you can redistribute it and/or modify
31+ * it under the terms of the GNU Lesser General Public License as published by
32+ * the Free Software Foundation, either version 3 of the License, or
33+ * (at your option) any later version.
34+ *
35+ * Imposter is distributed in the hope that it will be useful,
36+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
37+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38+ * GNU Lesser General Public License for more details.
39+ *
40+ * You should have received a copy of the GNU Lesser General Public License
41+ * along with Imposter. If not, see <https://www.gnu.org/licenses/>.
42+ */
43+ package io.gatehill.imposter.server
44+
45+ import io.gatehill.imposter.plugin.test.TestPluginImpl
46+ import io.gatehill.imposter.service.ResponseServiceImpl
47+ import io.restassured.RestAssured
48+ import io.vertx.core.Vertx
49+ import io.vertx.junit5.VertxTestContext
50+ import org.apache.logging.log4j.Level
51+ import org.apache.logging.log4j.LogManager
52+ import org.apache.logging.log4j.core.LogEvent
53+ import org.apache.logging.log4j.core.Logger
54+ import org.apache.logging.log4j.core.appender.AbstractAppender
55+ import org.apache.logging.log4j.core.config.Property
56+ import org.apache.logging.log4j.core.layout.PatternLayout
57+ import org.hamcrest.Matchers.equalTo
58+ import org.junit.jupiter.api.AfterEach
59+ import org.junit.jupiter.api.Assertions.assertTrue
60+ import org.junit.jupiter.api.BeforeEach
61+ import org.junit.jupiter.api.Test
62+ import java.util.*
63+ import java.util.concurrent.CopyOnWriteArrayList
64+
65+ /* *
66+ * Tests for resource logging functionality.
67+ */
68+ class ResourceLogTest : BaseVerticleTest () {
69+ override val pluginClass = TestPluginImpl ::class .java
70+
71+ private lateinit var memoryAppender: MemoryAppender
72+ private lateinit var logger: Logger
73+
74+ @BeforeEach
75+ override fun setUp (vertx : Vertx , testContext : VertxTestContext ) {
76+ super .setUp(vertx, testContext)
77+ RestAssured .baseURI = " http://$host :$listenPort "
78+ RestAssured .enableLoggingOfRequestAndResponseIfValidationFails()
79+
80+ // Setup logging capture
81+ logger = LogManager .getLogger(ResponseServiceImpl ::class .java) as Logger
82+ memoryAppender = MemoryAppender (" MemoryAppender-${UUID .randomUUID()} " )
83+ memoryAppender.start()
84+ logger.addAppender(memoryAppender)
85+ }
86+
87+ @AfterEach
88+ fun tearDownLogger () {
89+ logger.removeAppender(memoryAppender)
90+ memoryAppender.stop()
91+ }
92+
93+ override val testConfigDirs = listOf (
94+ " /resource-log"
95+ )
96+
97+ @Test
98+ fun `resource should log message with path parameters` () {
99+ // Clear logs before test
100+ memoryAppender.clear()
101+
102+ // Make the request
103+ RestAssured .given().`when `()
104+ .get(" /resource-log/123" )
105+ .then()
106+ .statusCode(200 )
107+ .body(equalTo(" resource_logged" ))
108+
109+ // Verify log message was captured
110+ val logEvents = memoryAppender.getLogEvents()
111+ assertTrue(logEvents.any { event ->
112+ event.level == Level .INFO &&
113+ event.message.formattedMessage.contains(" Resource log: Resource log message for ID: 123" )
114+ }, " Expected log message with path parameter not found" )
115+ }
116+
117+ @Test
118+ fun `interceptor should log message` () {
119+ // Clear logs before test
120+ memoryAppender.clear()
121+
122+ // Make the request
123+ RestAssured .given().`when `()
124+ .get(" /interceptor-log" )
125+ .then()
126+ .statusCode(401 )
127+ .body(equalTo(" Unauthorized" ))
128+
129+ // Verify log message was captured
130+ val logEvents = memoryAppender.getLogEvents()
131+ assertTrue(logEvents.any { event ->
132+ event.level == Level .INFO &&
133+ event.message.formattedMessage.contains(" Resource log: Interceptor log message" )
134+ }, " Expected interceptor log message not found" )
135+ }
136+
137+ @Test
138+ fun `request without log property should not log custom message` () {
139+ // Clear logs before test
140+ memoryAppender.clear()
141+
142+ // Make the request to a path without log property
143+ RestAssured .given().`when `()
144+ .get(" /simple" )
145+ .then()
146+ .statusCode(200 )
147+ .body(equalTo(" simple" ))
148+
149+ // Verify no custom log message was captured
150+ val logEvents = memoryAppender.getLogEvents()
151+ assertTrue(logEvents.none { event ->
152+ event.level == Level .INFO &&
153+ event.message.formattedMessage.contains(" Resource log:" )
154+ }, " Custom log message found when none was expected" )
155+ }
156+
157+ /* *
158+ * Custom in-memory appender for capturing log events during tests.
159+ */
160+ class MemoryAppender (name : String ) : AbstractAppender(
161+ name,
162+ null ,
163+ PatternLayout .createDefaultLayout(),
164+ false ,
165+ Property .EMPTY_ARRAY
166+ ) {
167+ private val logEvents = CopyOnWriteArrayList <LogEvent >()
168+
169+ override fun append (event : LogEvent ) {
170+ logEvents.add(event.toImmutable())
171+ }
172+
173+ fun getLogEvents (): List <LogEvent > = logEvents.toList()
174+
175+ fun clear () {
176+ logEvents.clear()
177+ }
178+ }
179+ }
0 commit comments