From e9bbac8296fae924f1b01ca5f794312625422d0b Mon Sep 17 00:00:00 2001 From: Swordsteel Date: Sat, 13 Sep 2025 18:43:45 +0200 Subject: [PATCH] test integration - add TransactionEndpoints - add AccountEndpoints - add sql files for test - add dependencies --- build.gradle.kts | 5 + .../kotlin/ltd/lulz/AccountEndpoints.kt | 113 ++++++++ .../kotlin/ltd/lulz/TransactionEndpoints.kt | 274 ++++++++++++++++++ .../resources/postgres/data.sql | 3 + .../resources/postgres/reset.sql | 1 + .../resources/postgres/schema.sql | 7 + 6 files changed, 403 insertions(+) create mode 100644 src/test-integration/kotlin/ltd/lulz/AccountEndpoints.kt create mode 100644 src/test-integration/kotlin/ltd/lulz/TransactionEndpoints.kt create mode 100644 src/test-integration/resources/postgres/data.sql create mode 100644 src/test-integration/resources/postgres/reset.sql create mode 100644 src/test-integration/resources/postgres/schema.sql diff --git a/build.gradle.kts b/build.gradle.kts index 5d553cd..de592c8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,11 @@ dependencies { testImplementation(aa.springboot.starter.test) testRuntimeOnly(aa.junit.platform.launcher) + + testIntegrationImplementation(aa.library.test.integration) + testIntegrationImplementation(aa.springboot.starter.test) + + testIntegrationRuntimeOnly(aa.junit.platform.launcher) } group = "ltd.lulz" diff --git a/src/test-integration/kotlin/ltd/lulz/AccountEndpoints.kt b/src/test-integration/kotlin/ltd/lulz/AccountEndpoints.kt new file mode 100644 index 0000000..0491aa8 --- /dev/null +++ b/src/test-integration/kotlin/ltd/lulz/AccountEndpoints.kt @@ -0,0 +1,113 @@ +package ltd.lulz + +import java.math.BigDecimal +import java.util.UUID +import ltd.lulz.model.Account +import ltd.lulz.test.container.PostgresTestContainer +import org.assertj.core.api.SoftAssertions +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT +import org.springframework.boot.test.web.server.LocalServerPort +import org.springframework.http.HttpStatus.BAD_REQUEST +import org.springframework.http.HttpStatus.CREATED +import org.springframework.http.HttpStatus.NOT_FOUND +import org.springframework.http.HttpStatus.OK +import org.springframework.test.web.reactive.server.WebTestClient +import org.springframework.test.web.reactive.server.expectBody + +@PostgresTestContainer +@SpringBootTest(webEnvironment = RANDOM_PORT) +@ExtendWith(SoftAssertionsExtension::class) +class AccountEndpoints { + + @InjectSoftAssertions + lateinit var softly: SoftAssertions + + @LocalServerPort + var port: Int = 0 + + lateinit var webClient: WebTestClient + + @BeforeEach + fun setup() { + webClient = WebTestClient.bindToServer().baseUrl("http://localhost:$port").build() + } + + @Test + fun `create account - success add account to db`() { + // given + val request = Account.Request( + name = "user", + amount = BigDecimal.valueOf(10.01), + ) + + // when + val result = webClient.post() + .uri("/account") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(CREATED) + .expectBody() + .consumeWith { + softly.assertThat(it.responseBody?.id?.version()).isEqualTo(7) + softly.assertThat(it.responseBody?.name).isEqualTo("user") + softly.assertThat(it.responseBody?.amount).isEqualTo("10.01") + } + } + + @Test + fun `create account - fail amount to small`() { + // given + val request = Account.Request( + name = "user", + amount = BigDecimal.valueOf(0.009), + ) + + // when + val result = webClient.post() + .uri("/account") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(BAD_REQUEST) + } + + @Test + fun `get balance - success get balance from db`() { + // given + val user1 = UUID.fromString("00000000-0000-7000-0000-000000000001") + + // when + val result = webClient.get() + .uri("/balance/account-$user1") + .exchange() + + // then + result.expectStatus().isEqualTo(OK) + .expectBody() + .consumeWith { + softly.assertThat(it.responseBody?.id).isEqualTo(user1) + softly.assertThat(it.responseBody?.name).isEqualTo("user 1") + softly.assertThat(it.responseBody?.amount).isEqualTo("10.00") + } + } + + @Test + fun `get balance - fail account not found`() { + // when + val result = webClient.get() + .uri("/balance/account-00000000-0000-7000-0000-000000000000") + .exchange() + + // then + result.expectStatus().isEqualTo(NOT_FOUND) + } +} diff --git a/src/test-integration/kotlin/ltd/lulz/TransactionEndpoints.kt b/src/test-integration/kotlin/ltd/lulz/TransactionEndpoints.kt new file mode 100644 index 0000000..e60dc55 --- /dev/null +++ b/src/test-integration/kotlin/ltd/lulz/TransactionEndpoints.kt @@ -0,0 +1,274 @@ +package ltd.lulz + +import java.math.BigDecimal +import java.util.UUID +import ltd.lulz.model.Transaction +import ltd.lulz.model.Transfer +import ltd.lulz.test.container.PostgresTestContainer +import org.assertj.core.api.SoftAssertions +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT +import org.springframework.boot.test.web.server.LocalServerPort +import org.springframework.http.HttpStatus.BAD_REQUEST +import org.springframework.http.HttpStatus.CREATED +import org.springframework.http.HttpStatus.NOT_ACCEPTABLE +import org.springframework.http.HttpStatus.NOT_FOUND +import org.springframework.test.web.reactive.server.WebTestClient + +@PostgresTestContainer +@SpringBootTest(webEnvironment = RANDOM_PORT) +@ExtendWith(SoftAssertionsExtension::class) +class TransactionEndpoints { + + @InjectSoftAssertions + lateinit var softly: SoftAssertions + + @LocalServerPort + var port: Int = 0 + + lateinit var webClient: WebTestClient + + @BeforeEach + fun setup() { + webClient = WebTestClient.bindToServer().baseUrl("http://localhost:$port").build() + } + + @Nested + inner class DepositTest { + + @Test + fun `deposit - success`() { + // given + val request = Transaction.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + amount = BigDecimal.valueOf(1.00), + ) + + // when + val result = webClient.post() + .uri("/deposit") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(CREATED) + } + + @Test + fun `deposit - fail amount to small`() { + // given + val request = Transaction.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + amount = BigDecimal.valueOf(0.009), + ) + + // when + val result = webClient.post() + .uri("/deposit") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(BAD_REQUEST) + } + + @Test + fun `deposit - fail account not found`() { + // given + val request = Transaction.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000000"), + amount = BigDecimal.valueOf(0.01), + ) + + // when + val result = webClient.post() + .uri("/deposit") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(NOT_FOUND) + } + } + + @Nested + inner class WithdrawalTest { + + @Test + fun `deposit - success`() { + // given + val request = Transaction.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + amount = BigDecimal.valueOf(1.00), + ) + + // when + val result = webClient.post() + .uri("/withdrawal") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(CREATED) + } + + @Test + fun `deposit - fail amount to small`() { + // given + val request = Transaction.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + amount = BigDecimal.valueOf(0.009), + ) + + // when + val result = webClient.post() + .uri("/withdrawal") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(BAD_REQUEST) + } + + @Test + fun `deposit - fail account not found`() { + // given + val request = Transaction.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000000"), + amount = BigDecimal.valueOf(1.00), + ) + + // when + val result = webClient.post() + .uri("/withdrawal") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(NOT_FOUND) + } + + @Test + fun `deposit - fail insufficient funds`() { + // given + val request = Transaction.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + amount = BigDecimal.valueOf(100.00), + ) + + // when + val result = webClient.post() + .uri("/withdrawal") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(NOT_ACCEPTABLE) + } + } + + @Nested + inner class TransferTest { + + @Test + fun `deposit - success`() { + // given + val request = Transfer.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + receiver = UUID.fromString("00000000-0000-7000-0000-000000000002"), + amount = BigDecimal.valueOf(1.00), + ) + + // when + val result = webClient.post() + .uri("/transfer") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(CREATED) + } + + @Test + fun `deposit - fail amount to small`() { + // given + val request = Transfer.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + receiver = UUID.fromString("00000000-0000-7000-0000-000000000002"), + amount = BigDecimal.valueOf(0.009), + ) + + // when + val result = webClient.post() + .uri("/transfer") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(BAD_REQUEST) + } + + @Test + fun `deposit - fail account not found`() { + // given + val request = Transfer.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000000"), + receiver = UUID.fromString("00000000-0000-7000-0000-000000000002"), + amount = BigDecimal.valueOf(1.00), + ) + + // when + val result = webClient.post() + .uri("/transfer") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(NOT_FOUND) + } + + @Test + fun `deposit - fail receiver not found`() { + // given + val request = Transfer.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + receiver = UUID.fromString("00000000-0000-7000-0000-000000000000"), + amount = BigDecimal.valueOf(1.00), + ) + + // when + val result = webClient.post() + .uri("/transfer") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(NOT_FOUND) + } + + @Test + fun `deposit - fail insufficient funds`() { + // given + val request = Transfer.Request( + account = UUID.fromString("00000000-0000-7000-0000-000000000001"), + receiver = UUID.fromString("00000000-0000-7000-0000-000000000002"), + amount = BigDecimal.valueOf(100.00), + ) + + // when + val result = webClient.post() + .uri("/transfer") + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isEqualTo(NOT_ACCEPTABLE) + } + } +} diff --git a/src/test-integration/resources/postgres/data.sql b/src/test-integration/resources/postgres/data.sql new file mode 100644 index 0000000..7744baf --- /dev/null +++ b/src/test-integration/resources/postgres/data.sql @@ -0,0 +1,3 @@ +INSERT INTO public.accounts(id, name, amount) +VALUES ('00000000-0000-7000-0000-000000000001'::uuid, 'user 1', 10.0), + ('00000000-0000-7000-0000-000000000002'::uuid, 'user 2', 10.0); diff --git a/src/test-integration/resources/postgres/reset.sql b/src/test-integration/resources/postgres/reset.sql new file mode 100644 index 0000000..72e07f6 --- /dev/null +++ b/src/test-integration/resources/postgres/reset.sql @@ -0,0 +1 @@ +TRUNCATE TABLE accounts CASCADE; diff --git a/src/test-integration/resources/postgres/schema.sql b/src/test-integration/resources/postgres/schema.sql new file mode 100644 index 0000000..19286d7 --- /dev/null +++ b/src/test-integration/resources/postgres/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS public.accounts +( + id UUID DEFAULT uuidv7(), + name VARCHAR(50) NOT NULL, + amount NUMERIC(19, 2) NOT NULL, + CONSTRAINT pk_contact_types PRIMARY KEY (id) +);