Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for #1868 - make State subclasses real classes #1869

Merged
merged 1 commit into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import java.util.concurrent.atomic.AtomicReference
internal class ClientRequest(
private val request: HttpRequestBuilder,
) : HttpRequest {
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered)
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered())

override fun getProtocolVersion(): String = HTTP_1_1.toString()
override fun getOrigin(): Origin = Origin.LOCAL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import io.ktor.client.statement.HttpResponse as KtorResponse
internal class ClientResponse(
private val response: KtorResponse,
) : HttpResponse {
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered)
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered())

override fun getProtocolVersion(): String = response.version.toString()
override fun getOrigin(): Origin = Origin.REMOTE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.zalando.logbook.client

import io.ktor.server.application.Application
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.post
import io.ktor.http.*
import io.ktor.server.application.call
import io.ktor.server.cio.CIO
import io.ktor.server.engine.embeddedServer
import io.ktor.server.request.contentType
import io.ktor.server.request.receiveText
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.post
import io.ktor.server.routing.routing
import io.ktor.util.InternalAPI
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.zalando.logbook.Correlation
import org.zalando.logbook.HttpLogWriter
import org.zalando.logbook.HttpRequest
import org.zalando.logbook.HttpResponse
import org.zalando.logbook.Logbook
import org.zalando.logbook.Precorrelation
import org.zalando.logbook.Sink
import org.zalando.logbook.common.ExperimentalLogbookKtorApi
import org.zalando.logbook.core.DefaultHttpLogFormatter
import org.zalando.logbook.core.DefaultSink
import org.zalando.logbook.test.TestStrategy
import kotlin.math.sin

@ExperimentalLogbookKtorApi
@OptIn(InternalAPI::class)
internal class LogbookClientWithSinkTest {

private val port = 8080
private val sink = mock(Sink::class.java)

private val testLogbook: Logbook = Logbook
.builder()
.strategy(TestStrategy())
.sink(sink)
.build()

private val client = HttpClient {
install(LogbookClient) {
logbook = testLogbook
}
}

private val server = embeddedServer(CIO, port = port, module = Application::stubApplicationModuleWithSink)

@BeforeEach
internal fun setUp() {
server.start(wait = false)
`when`(sink.isActive).thenReturn(true)
`when`(sink.writeBoth(any(), any(), any())).thenCallRealMethod()
}

@AfterEach
internal fun tearDown() {
server.stop(0, 5_000)
}

@Test
fun `Should log request and response`() {
val response = sendAndReceive() {
body = "ping"
}

assertThat(response).isNotBlank()

val capturedRequest = captureRequest()
assertThat(capturedRequest)
.isEqualTo("ping")

val capturedResponse = captureResponse()
assertThat(capturedResponse)
.isEqualTo("pong")
}


private fun sendAndReceive(uri: String = "/ping", block: HttpRequestBuilder.() -> Unit = {}): String {
return runBlocking {
client.post(urlString = "http://localhost:$port$uri") {
block()
}.body()
}
}

private fun captureRequest(): String {
return ArgumentCaptor
.forClass(HttpRequest::class.java)
.apply { verify(sink, timeout(1_000)).write(any(Correlation::class.java), capture(), any(HttpResponse::class.java)) }
.value
.bodyAsString
}

private fun captureResponse(): String? {
return ArgumentCaptor
.forClass(HttpResponse::class.java)
.apply { verify(sink, timeout(1_000)).write(any(Correlation::class.java), any(HttpRequest::class.java), capture()) }
.value
.bodyAsString
}
}

fun Application.stubApplicationModuleWithSink() {
routing {
post("/ping") {
call.respondText("pong", ContentType.Text.Plain)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ sealed class State {
open fun without(): State = this
open fun buffer(content: ByteArray): State = this

object Buffering : State() {
class Buffering : State() {
override fun without(): State = Ignoring(this)
override fun buffer(content: ByteArray): State = apply { body = content }
}

object Unbuffered : State() {
override fun with(): State = Offering
class Unbuffered : State() {
override fun with(): State = Offering()
}

object Offering : State() {
override fun without(): State = Unbuffered
override fun buffer(content: ByteArray): State = Buffering.buffer(content)
class Offering : State() {
override fun without(): State = Unbuffered()
override fun buffer(content: ByteArray): State = Buffering().buffer(content)
}

class Ignoring(private val delegate: Buffering) : State() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal class StateUnitTest {

@Test
fun `Should keep buffering when ignoring`() {
val state: AtomicReference<State> = AtomicReference(State.Offering)
val state: AtomicReference<State> = AtomicReference(State.Offering())
state.updateAndGet { it.without() }
state.updateAndGet { it.with() }
state.updateAndGet { it.buffer(EMPTY_BODY) }
Expand All @@ -27,7 +27,7 @@ internal class StateUnitTest {

@Test
fun `Should not buffer when unbuffered`() {
val state: AtomicReference<State> = AtomicReference(State.Unbuffered)
val state: AtomicReference<State> = AtomicReference(State.Unbuffered())
state.updateAndGet { it.with() }
state.updateAndGet { it.without() }
state.updateAndGet { it.buffer("foo".toByteArray()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import kotlin.text.Charsets.UTF_8
internal class ServerRequest(
private val request: ApplicationRequest,
) : HttpRequest {
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered)
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered())

override fun getProtocolVersion(): String = request.httpVersion
override fun getOrigin(): Origin = Origin.REMOTE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import kotlin.text.Charsets.UTF_8
internal class ServerResponse(
private val response: ApplicationResponse,
) : HttpResponse {
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered)
private val state: AtomicReference<State> = AtomicReference(State.Unbuffered())

override fun getProtocolVersion(): String = response.call.request.httpVersion
override fun getOrigin(): Origin = Origin.LOCAL
Expand Down
Loading