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

Small refactor #7

Merged
merged 2 commits into from
Oct 7, 2024
Merged
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
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -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
13 changes: 12 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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")

@@ -50,6 +55,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("io.github.openfeign:feign-jackson:12.4")
implementation("org.springframework.cloud:spring-cloud-starter-openfeign:3.1.2")

// Spring Boot Test
@@ -113,3 +119,8 @@ ktlint {
exclude("**/integrationTest/**")
}
}

tasks.wrapper {
gradleVersion = "8.10.2"
distributionType = Wrapper.DistributionType.ALL
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Original file line number Diff line number Diff line change
@@ -3,19 +3,20 @@ 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 org.junit.jupiter.api.AfterAll
import com.github.tomakehurst.wiremock.client.WireMock
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.springframework.beans.factory.annotation.Autowired
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],
@@ -24,38 +25,52 @@ 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()
WireMock.reset()
DatabaseCleaner.cleanAllTables()
}

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

companion object {
lateinit var wireMockServer: WireMockServer
protected val wireMock = WireMockServer(0)

@JvmStatic
@BeforeAll
fun startWireMockServer() {
wireMockServer = WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort())
wireMockServer.start()
System.setProperty("wiremock.server.port", wireMockServer.port().toString())
@DynamicPropertySource
fun properties(registry: DynamicPropertyRegistry) {
registry.add("nbp.url") { "http://localhost:${wireMock.port()}/api/exchangerates/rates/A/USD" }
}

@JvmStatic
@AfterAll
fun stopWireMockServer() {
wireMockServer.stop()
System.clearProperty("wiremock.server.port")
@BeforeAll
fun startWireMock() {
if (!wireMock.isRunning) {
wireMock.start()
WireMock.configureFor(wireMock.port())
println("WIREMOCK STARTED at port: ${wireMock.port()}")
}
}

init {
Runtime.getRuntime().addShutdownHook(
Thread {
if (wireMock.isRunning) {
println("Shutting down WireMock server...")
wireMock.stop()
println("WireMock stopped")
}
},
)
}
}
}
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> {
@@ -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")
@@ -25,7 +23,7 @@ interface GetCurrentExchangeRateAbility {
}]
}
""".trimIndent(),
),
).withStatus(200),
),
)
}
Original file line number Diff line number Diff line change
@@ -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

@@ -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
@@ -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 {
@@ -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")) {
@@ -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
}
}
Loading