Skip to content

Commit

Permalink
Small refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
kamil.jedrzejuk committed Oct 7, 2024
1 parent 1c83255 commit 7f72fb6
Show file tree
Hide file tree
Showing 27 changed files with 546 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ end_of_line = lf
insert_final_newline = true

[*.kt]
max_line_length = 100
max_line_length = 120

[*.{kt,kts}]
import_order = camelcase, spaces, semicolons
Expand Down
7 changes: 6 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ val integrationTest: SourceSet =
configurations[integrationTest.implementationConfigurationName].extendsFrom(configurations.testImplementation.get())
configurations[integrationTest.runtimeOnlyConfigurationName].extendsFrom(configurations.testRuntimeOnly.get())

dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:2022.0.4") // Dodanie BOM dla Spring Cloud
}
}

dependencies {
implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
implementation("org.jetbrains.exposed:exposed-core:0.55.0")
implementation("org.jetbrains.exposed:exposed-java-time:0.55.0")
// implementation("org.jetbrains.exposed:exposed-kotlin-datetime:0.55.0")
implementation("org.jetbrains.exposed:exposed-jdbc:0.55.0")
implementation("javax.servlet:javax.servlet-api:4.0.1")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package camilyed.github.io.currencyexchangeapi.testing
import camilyed.github.io.CurrencyExchangeApiApplication
import camilyed.github.io.currencyexchangeapi.testing.abilties.MakeRequestAbility
import camilyed.github.io.currencyexchangeapi.testing.postgres.PostgresInitializer
import camilyed.github.io.currencyexchangeapi.testing.utils.DatabaseCleaner
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import com.github.tomakehurst.wiremock.core.WireMockConfiguration.options
import com.github.tomakehurst.wiremock.client.WireMock
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
Expand All @@ -16,6 +16,8 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource

@ContextConfiguration(
initializers = [PostgresInitializer::class],
Expand All @@ -24,37 +26,50 @@ import org.springframework.test.context.ContextConfiguration
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class BaseIntegrationTest : MakeRequestAbility {

@Autowired
override lateinit var restTemplate: TestRestTemplate

private val objectMapper: ObjectMapper by lazy { jacksonObjectMapper() }

val wireMockServer = WireMockServer(options().port(8080))

@BeforeEach
fun beforeEach() {
wireMockServer.resetRequests()
wireMock.resetAll()
DatabaseCleaner.cleanAllTables()
}

override fun <T> toJson(obj: T): String {
return objectMapper.writeValueAsString(obj)
}

companion object {
lateinit var wireMockServer: WireMockServer
protected val wireMock = WireMockServer(0)
.apply {
start()
println("WireMock Started")
}
.also { WireMock.configureFor(it.port()) }

@JvmStatic
@DynamicPropertySource
fun properties(registry: DynamicPropertyRegistry) {
registry.add("wiremock.server.port", wireMock::port)
}

@JvmStatic
@BeforeAll
fun startWireMockServer() {
wireMockServer = WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort())
wireMockServer.start()
System.setProperty("wiremock.server.port", wireMockServer.port().toString())
fun startWireMock() {
if (!wireMock.isRunning) {
wireMock.start()
WireMock.configureFor(wireMock.port())
System.clearProperty("wiremock.server.port")
}
}

@JvmStatic
@AfterAll
fun stopWireMockServer() {
wireMockServer.stop()
wireMock.stop()
System.clearProperty("wiremock.server.port")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package camilyed.github.io.currencyexchangeapi.testing.abilties

import camilyed.github.io.currencyexchangeapi.api.AccountEndpoint
import camilyed.github.io.currencyexchangeapi.testing.assertion.isOkResponse
import camilyed.github.io.currencyexchangeapi.testing.builders.CreateAccountJsonBuilder
import camilyed.github.io.currencyexchangeapi.testing.utils.parseBodyToType
import org.springframework.http.HttpHeaders
import org.springframework.http.ResponseEntity
import strikt.api.expectThat

interface CreateAccountAbility : MakeRequestAbility {
fun createAccount(builder: CreateAccountJsonBuilder): ResponseEntity<String> {
Expand All @@ -16,4 +20,10 @@ interface CreateAccountAbility : MakeRequestAbility {
responseType = String::class.java,
)
}

fun thereIsAnAccount(builder: CreateAccountJsonBuilder): String {
val response = createAccount(builder)
expectThat(response).isOkResponse()
return parseBodyToType<AccountEndpoint.AccountCreatedJson>(response).id
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package camilyed.github.io.currencyexchangeapi.testing.abilties

import camilyed.github.io.currencyexchangeapi.testing.builders.ExchangePlnToUsdJsonBuilder
import org.springframework.http.HttpHeaders
import org.springframework.http.ResponseEntity

interface ExchangePlnToUsdAbility : MakeRequestAbility {
fun exchangePlnToUsd(builder: ExchangePlnToUsdJsonBuilder): ResponseEntity<String> {
val exchangeJson = builder.build()
val httpHeaders = HttpHeaders()
httpHeaders["X-Request-Id"] = builder.xRequestId
return put(
url = "/api/accounts//exchange-pln-to-usd",
body = exchangeJson,
headers = httpHeaders,
responseType = String::class.java,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package camilyed.github.io.currencyexchangeapi.testing.abilties

import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock

interface GetCurrentExchangeRateAbility {
val wireMockServer: WireMockServer

fun currentExchangeRateIs(rate: String) {
wireMockServer.stubFor(
WireMock.get("/api/exchangerates/rates/A/USD")
WireMock.stubFor(
WireMock.get(WireMock.urlEqualTo("/api/exchangerates/rates/A/USD"))
.willReturn(
WireMock.aResponse()
.withHeader("Content-Type", "application/json")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package camilyed.github.io.currencyexchangeapi.testing.abilties
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity

Expand All @@ -28,5 +29,17 @@ interface MakeRequestAbility {
return restTemplate.postForEntity(url, requestEntity, responseType)
}

fun <T> put(
url: String,
body: Map<String, Any?>,
headers: HttpHeaders,
responseType: Class<T>,
): ResponseEntity<T> {
val jsonString = toJson(body)
headers.contentType = MediaType.APPLICATION_JSON
val requestEntity = HttpEntity(jsonString, headers)
return restTemplate.exchange(url, HttpMethod.PUT, requestEntity, responseType)
}

fun <T> toJson(obj: T): String
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package camilyed.github.io.currencyexchangeapi.testing.assertion

import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import camilyed.github.io.currencyexchangeapi.testing.utils.parseBodyToType
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import strikt.api.Assertion
Expand All @@ -18,7 +17,7 @@ fun <T> Assertion.Builder<ResponseEntity<T>>.isOkResponse(): Assertion.Builder<R

fun Assertion.Builder<ResponseEntity<String>>.hasUUID(): Assertion.Builder<ResponseEntity<String>> =
assert("should contain a valid UUID in 'id' field") {
val body = parseBodyToMap(it)
val body = parseBodyToType<Map<String, Any>>(it)
val actualId =
body["id"] as? String ?: fail("Response does not contain 'id' or 'id' is not a String")
try {
Expand All @@ -38,12 +37,21 @@ fun <T> Assertion.Builder<ResponseEntity<T>>.isBadRequest(): Assertion.Builder<R
}
}

fun <T> Assertion.Builder<ResponseEntity<T>>.isUnprocessableEntity(): Assertion.Builder<ResponseEntity<T>> =
assert("should have an UNPROCESSABLE ENTITY response status") {
if (it.statusCode == HttpStatus.UNPROCESSABLE_ENTITY) {
pass()
} else {
fail("Expected UNPROCESSABLE ENTITY, but got ${it.statusCode}")
}
}

fun Assertion.Builder<ResponseEntity<String>>.hasProblemDetail(
expectedField: String,
expectedDetail: String,
): Assertion.Builder<ResponseEntity<String>> =
assert("should contain problem detail, field '$expectedField' with value '$expectedDetail'") {
val body = parseBodyToMap(it)
val body = parseBodyToType<Map<String, Any>>(it)
val actualDetail =
body["detail"] as? String ?: fail("No 'detail' field in response body") as String
if (actualDetail.contains("$expectedField: $expectedDetail")) {
Expand All @@ -57,9 +65,3 @@ fun Assertion.Builder<ResponseEntity<String>>.hasProblemDetail(
)
}
}

val objectMapper = jacksonObjectMapper()

private fun parseBodyToMap(response: ResponseEntity<String>): Map<String, Any> {
return objectMapper.readValue(response.body!!, object : TypeReference<Map<String, Any>>() {})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package camilyed.github.io.currencyexchangeapi.testing.assertion

import camilyed.github.io.currencyexchangeapi.testing.utils.parseBodyToType
import org.springframework.http.ResponseEntity
import strikt.api.Assertion.Builder

fun Builder<ResponseEntity<String>>.hasPlnAmount(expectedPlnAmount: String): Builder<ResponseEntity<String>> =
assert("should contain correct PLN amount") {
val body = parseBodyToType<Map<String, Any>>(it)

val actualPlnAmount =
body["balancePln"] as? String ?: fail(
"Response does not contain 'balancePln' or 'plnAmount' is not a String",
)

if (actualPlnAmount == expectedPlnAmount) {
pass()
} else {
fail("Expected PLN amount: $expectedPlnAmount, but got $actualPlnAmount")
}
}

fun Builder<ResponseEntity<String>>.hasUsdAmount(expectedUsdAmount: String): Builder<ResponseEntity<String>> =
assert("should contain correct USD amount") {
val body = parseBodyToType<Map<String, Any>>(it)

val actualUsdAmount =
body["balanceUsd"] as? String ?: fail(
"Response does not contain 'balanceUsd' or 'balanceUsd' is not a String",
)

if (actualUsdAmount == expectedUsdAmount) {
pass()
} else {
fail("Expected USD amount: $expectedUsdAmount, but got $actualUsdAmount")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package camilyed.github.io.currencyexchangeapi.testing.builders

import java.util.UUID

class ExchangePlnToUsdJsonBuilder {
var accountId: String? = UUID.randomUUID().toString()
var amount: String? = "100.00"
var xRequestId: String? = UUID.randomUUID().toString()

fun withAccountId(accountId: String) = apply {
this.accountId = accountId
}

fun withAmount(amount: String) = apply {
this.amount = amount
}

fun withXRequestId(xRequestId: String) = apply {
this.xRequestId = xRequestId
}

fun withoutXRequestId() = apply {
this.xRequestId = null
}

fun withoutAccountId() = apply {
this.accountId = null
}

fun withoutAmount() = apply {
this.amount = null
}

fun build(): Map<String, Any?> {
return mapOf(
"accountId" to accountId,
"amount" to amount,
)
}

companion object {
fun anExchangePlnToUsd(): ExchangePlnToUsdJsonBuilder {
return ExchangePlnToUsdJsonBuilder()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package camilyed.github.io.currencyexchangeapi.testing.config

import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary

@Configuration
class WireMockConfig {

@Bean
@Primary
fun wireMockServer(): WireMockServer {
val wireMockServer = WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort())
wireMockServer.start()
return wireMockServer
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package camilyed.github.io.currencyexchangeapi.testing.utils

import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction

object DatabaseCleaner {

fun cleanAllTables() {
transaction {
val tables = TransactionManager.current().db.dialect.allTablesNames()
tables.forEach { tableName ->
exec("DELETE FROM $tableName")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package camilyed.github.io.currencyexchangeapi.testing.utils

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.http.ResponseEntity

val objectMapper = jacksonObjectMapper()

inline fun <reified T> parseBodyToType(response: ResponseEntity<String>): T {
return objectMapper.readValue(response.body!!, T::class.java)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package camilyed.github.io.currencyexchangeapi.web

import camilyed.github.io.currencyexchangeapi.testing.BaseIntegrationTest
import camilyed.github.io.currencyexchangeapi.testing.abilties.CreateAccountAbility
import camilyed.github.io.currencyexchangeapi.testing.abilties.GetCurrentExchangeRateAbility
import camilyed.github.io.currencyexchangeapi.testing.assertion.hasProblemDetail
import camilyed.github.io.currencyexchangeapi.testing.assertion.hasUUID
import camilyed.github.io.currencyexchangeapi.testing.assertion.isBadRequest
Expand All @@ -14,8 +13,7 @@ import strikt.assertions.isEqualTo

class AccountCreationIntegrationTest :
BaseIntegrationTest(),
CreateAccountAbility,
GetCurrentExchangeRateAbility {
CreateAccountAbility {

@Test
fun `should create a new account`() {
Expand Down
Loading

0 comments on commit 7f72fb6

Please sign in to comment.