diff --git a/account_requests.http b/account_requests.http new file mode 100644 index 0000000..9276225 --- /dev/null +++ b/account_requests.http @@ -0,0 +1,39 @@ +### Step 1: Generate X-Request-Id (UUID for the scenario) +@requestId = {{uuid}} // Automatically generates a valid UUID for X-Request-Id + +### Step 2: Create a new account (capture the accountId from the response) +POST http://localhost:8090/api/accounts +Content-Type: application/json +X-Request-Id: 550e8400-e29b-41d4-a716-446655440000 // Use the generated requestId + +{ + "owner": "John Doe", + "initialBalance": "1000.00" +} + +### Capture the accountId from the response +> {% +client.test("Status Code is 201", function() { + client.assert(response.status === 201, "Expected status code 201, but got " + response.status); +}); + +var jsonData = JSON.parse(response.body); +client.global.set("accountId", jsonData.id); // Save the accountId globally for use in the next request +%} + + +### Step 3: Exchange PLN to USD (use captured accountId) +PUT http://localhost:8090/api/accounts/exchange-pln-to-usd +Content-Type: application/json +X-Request-Id: 550e8400-e29b-41d4-a716-446655449999 + +{ + "accountId": "f0cf47c7-24d8-40ff-9a14-5c935596f7e7", + "amount": "100.00" +} + +> {% + client.test("Status Code is 200", function() { + client.assert(response.status === 200, "Expected status code 200, but got " + response.status); + }); +%} diff --git a/build.gradle.kts b/build.gradle.kts index 1b47003..af1cee2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -57,6 +57,8 @@ dependencies { 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") + implementation("org.postgresql:postgresql:42.3.1") + implementation("org.testcontainers:postgresql:1.20.2") // Spring Boot Test testImplementation("org.springframework.boot:spring-boot-starter-test") @@ -72,7 +74,6 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.wiremock:wiremock:3.9.1") testImplementation("org.testcontainers:junit-jupiter:1.20.2") - testImplementation("org.testcontainers:postgresql:1.20.2") testImplementation("org.flywaydb:flyway-core") testRuntimeOnly("org.postgresql:postgresql") } diff --git a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/postgres/PostgresInitializer.kt b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/postgres/PostgresInitializer.kt index 7e0d769..c9fa4a9 100644 --- a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/postgres/PostgresInitializer.kt +++ b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/postgres/PostgresInitializer.kt @@ -3,8 +3,10 @@ package camilyed.github.io.currencyexchangeapi.testing.postgres import mu.KotlinLogging import org.springframework.context.ApplicationContextInitializer import org.springframework.context.ConfigurableApplicationContext +import org.springframework.context.annotation.Profile import org.testcontainers.containers.PostgreSQLContainer +@Profile("!test") class PostgresInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> { override fun initialize(applicationContext: ConfigurableApplicationContext) { log.info("Overriding system properties") diff --git a/src/main/kotlin/camilyed/github/io/CurrencyExchangeApiApplication.kt b/src/main/kotlin/camilyed/github/io/CurrencyExchangeApiApplication.kt index 743770e..ece10c9 100644 --- a/src/main/kotlin/camilyed/github/io/CurrencyExchangeApiApplication.kt +++ b/src/main/kotlin/camilyed/github/io/CurrencyExchangeApiApplication.kt @@ -1,8 +1,9 @@ package camilyed.github.io +import camilyed.github.io.currencyexchangeapi.infrastructure.PostgresInitializer +import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.ImportAutoConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication import org.springframework.cloud.openfeign.EnableFeignClients import org.springframework.cloud.openfeign.FeignAutoConfiguration @@ -12,5 +13,7 @@ import org.springframework.cloud.openfeign.FeignAutoConfiguration class CurrencyExchangeApiApplication fun main(args: Array<String>) { - runApplication<CurrencyExchangeApiApplication>(*args) + val application = SpringApplication(CurrencyExchangeApiApplication::class.java) + application.addInitializers(PostgresInitializer()) + application.run(*args) } diff --git a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/domain/AccountOperationRepository.kt b/src/main/kotlin/camilyed/github/io/currencyexchangeapi/domain/AccountOperationRepository.kt index e1d2043..4263d71 100644 --- a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/domain/AccountOperationRepository.kt +++ b/src/main/kotlin/camilyed/github/io/currencyexchangeapi/domain/AccountOperationRepository.kt @@ -1,9 +1,6 @@ package camilyed.github.io.currencyexchangeapi.domain -import org.springframework.context.annotation.Profile -import org.springframework.stereotype.Component import java.util.UUID -import java.util.concurrent.ConcurrentHashMap interface AccountOperationRepository { @@ -11,22 +8,3 @@ interface AccountOperationRepository { fun save(events: List<AccountEvent>) } - -@Component -@Profile("!test") -class InMemoryAccountOperationRepository : AccountOperationRepository { - - private val operations = ConcurrentHashMap<UUID, UUID>() - private val events = mutableListOf<AccountEvent>() - - override fun findAccountIdBy(operationId: UUID): UUID? { - return operations[operationId] - } - - override fun save(events: List<AccountEvent>) { - events.forEach { event -> - this.events.add(event) - operations[event.operationId] = event.accountId - } - } -} diff --git a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/domain/AccountRepository.kt b/src/main/kotlin/camilyed/github/io/currencyexchangeapi/domain/AccountRepository.kt index f1082e5..3a40f6c 100644 --- a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/domain/AccountRepository.kt +++ b/src/main/kotlin/camilyed/github/io/currencyexchangeapi/domain/AccountRepository.kt @@ -1,9 +1,6 @@ package camilyed.github.io.currencyexchangeapi.domain -import org.springframework.context.annotation.Profile -import org.springframework.stereotype.Component import java.util.UUID -import java.util.concurrent.ConcurrentHashMap interface AccountRepository { fun nextAccountId(): UUID = UUID.randomUUID() @@ -12,20 +9,3 @@ interface AccountRepository { fun find(id: UUID): Account? } - -@Component -@Profile("!test") -class InMemoryAccountRepository : AccountRepository { - private val accounts = ConcurrentHashMap<UUID, AccountSnapshot>() - - override fun nextAccountId(): UUID = UUID.randomUUID() - - override fun save(account: Account) { - val snapshot = account.toSnapshot() - accounts[snapshot.id] = snapshot - } - - override fun find(id: UUID): Account? { - return accounts[id]?.let { Account.fromSnapshot(it) } - } -} diff --git a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/infrastructure/PostgresInitializer.kt b/src/main/kotlin/camilyed/github/io/currencyexchangeapi/infrastructure/PostgresInitializer.kt new file mode 100644 index 0000000..29a2e5a --- /dev/null +++ b/src/main/kotlin/camilyed/github/io/currencyexchangeapi/infrastructure/PostgresInitializer.kt @@ -0,0 +1,47 @@ +package camilyed.github.io.currencyexchangeapi.infrastructure + +import mu.KotlinLogging +import org.springframework.beans.factory.DisposableBean +import org.springframework.context.ApplicationContextInitializer +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.context.annotation.Profile +import org.testcontainers.containers.PostgreSQLContainer + +@Profile("!test") +class PostgresInitializer : ApplicationContextInitializer<ConfigurableApplicationContext>, DisposableBean { + override fun initialize(applicationContext: ConfigurableApplicationContext) { + log.info("Overriding system properties") + overrideSystemProperties() + } + + override fun destroy() { + if (pg.isRunning) { + log.info("Stopping PostgreSQL container...") + pg.stop() + } + } + + private fun overrideSystemProperties() { + System.setProperty("spring.datasource.url", pg.jdbcUrl) + System.setProperty("spring.datasource.username", pg.username) + System.setProperty("spring.datasource.password", pg.password) + } + + private companion object { + private val log = KotlinLogging.logger {} + private val pg: PostgreSQLContainer<*> = + PostgreSQLContainer("postgres:13.4-alpine") + .apply { + withReuse(true) + withDatabaseName("test_db") + withUsername("test_user") + withPassword("test_password") + } + + init { + pg.withInitScript("init.sql") + pg.start() + log.info("Postgres started") + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index df8ede1..767f9ca 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,10 +2,12 @@ spring: flyway: baseline-on-migrate: true datasource: - url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/currency_exchange_db} - username: ${DATABASE_USERNAME:myuser} - password: ${DATABASE_PASSWORD:mypassword} + url: jdbc:postgresql://localhost:${POSTGRES_PORT}/test + username: test + password: test driver-class-name: org.postgresql.Driver +server: + port: 8090 nbp: url: http://api.nbp.pl/api/exchangerates/rates/A/USD