diff --git a/README.md b/README.md index 6a52ee7..bc6621e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,20 @@ -# {library} +# Test Integration. -{description} +Library to test integration for aura ascend. + +## Postgres Test Container + +`@PostgresTestContainer` Annotation for integration tests. + +Initialize Postgres test container using test container default properties, script located in `src//resources/postgres/` folder. + +### Properties For Test Container + +| file | required | info | +|---------------------|:--------:|----------------------------------------------------------------| +| postgres/schema.sql | ✓ | Postgres init script containing all structure and functions | +| postgres/data.sql | | Postgres data script containing all data to populate database | +| postgres/reset.sql | | Postgres reset script containing all command to reset database | ## Publish library locally. @@ -25,8 +39,8 @@ To authenticate with Gradle to access repositories that require authentication, Here's how you can do it: 1. Open or create the `gradle.properties` file in your Gradle user home directory: - - On Unix-like systems (Linux, macOS), this directory is typically `~/.gradle/`. - - On Windows, this directory is typically `C:\Users\\.gradle\`. + - On Unix-like systems (Linux, macOS), this directory is typically `~/.gradle/`. + - On Windows, this directory is typically `C:\Users\\.gradle\`. 2. Add the following lines to the `gradle.properties` file: ```properties repository..user=your_user diff --git a/build.gradle.kts b/build.gradle.kts index e304981..c625981 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,14 +1,15 @@ plugins { alias(aa.plugins.kotlin.jvm) + alias(aa.plugins.spring.dependency.management) alias(aa.plugins.library) } dependencies { - - testImplementation(aa.kotlin.junit5) - - testRuntimeOnly(aa.junit.platform.launcher) + implementation(aa.kotlinx.coroutines) + implementation(aa.springboot.starter.r2dbc) + implementation(aa.springboot.starter.test) + implementation(aa.testcontainers.postgresql) } group = "ltd.lulz.library" -description = "library template" +description = "library test integration" diff --git a/gradle.properties b/gradle.properties index 918eb4b..0415790 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ version=0.1.0-SNAPSHOT -catalog=0.1.0 +catalog=0.2.0-SNAPSHOT diff --git a/settings.gradle.kts b/settings.gradle.kts index f7d4299..461b4c6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,4 +34,4 @@ pluginManagement.repositories { gradlePluginPortal() } -rootProject.name = "library" +rootProject.name = "test-integration" diff --git a/src/main/kotlin/ltd/lulz/library/LulzLibrary.kt b/src/main/kotlin/ltd/lulz/library/LulzLibrary.kt deleted file mode 100644 index 42340b8..0000000 --- a/src/main/kotlin/ltd/lulz/library/LulzLibrary.kt +++ /dev/null @@ -1,3 +0,0 @@ -package ltd.lulz.library - -class LulzLibrary diff --git a/src/main/kotlin/ltd/lulz/test/container/PostgresTestContainer.kt b/src/main/kotlin/ltd/lulz/test/container/PostgresTestContainer.kt new file mode 100644 index 0000000..8fd22a6 --- /dev/null +++ b/src/main/kotlin/ltd/lulz/test/container/PostgresTestContainer.kt @@ -0,0 +1,15 @@ +package ltd.lulz.test.container + +import ltd.lulz.test.container.extension.PostgresTestExtension +import ltd.lulz.test.container.postgres.PostgresTestListener +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestExecutionListeners +import org.springframework.test.context.TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +@ExtendWith(PostgresTestExtension::class) +@ContextConfiguration(initializers = [PostgresTestExtension::class]) +@TestExecutionListeners(listeners = [PostgresTestListener::class], mergeMode = MERGE_WITH_DEFAULTS) +annotation class PostgresTestContainer diff --git a/src/main/kotlin/ltd/lulz/test/container/extension/PostgresTestExtension.kt b/src/main/kotlin/ltd/lulz/test/container/extension/PostgresTestExtension.kt new file mode 100644 index 0000000..cd5fd06 --- /dev/null +++ b/src/main/kotlin/ltd/lulz/test/container/extension/PostgresTestExtension.kt @@ -0,0 +1,21 @@ +package ltd.lulz.test.container.extension + +import ltd.lulz.test.container.postgres.TestContainerPostgres +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.springframework.boot.test.util.TestPropertyValues +import org.springframework.context.ApplicationContextInitializer +import org.springframework.context.ConfigurableApplicationContext + +class PostgresTestExtension : BeforeAllCallback, ApplicationContextInitializer { + + override fun initialize(applicationContext: ConfigurableApplicationContext) { + TestPropertyValues.of(TestContainerPostgres.props()).applyTo(applicationContext.environment) + } + + override fun beforeAll(context: ExtensionContext) { + if (!TestContainerPostgres.postgres.isRunning) { + TestContainerPostgres.postgres.start() + } + } +} diff --git a/src/main/kotlin/ltd/lulz/test/container/postgres/PostgresTestListener.kt b/src/main/kotlin/ltd/lulz/test/container/postgres/PostgresTestListener.kt new file mode 100644 index 0000000..d6277b0 --- /dev/null +++ b/src/main/kotlin/ltd/lulz/test/container/postgres/PostgresTestListener.kt @@ -0,0 +1,24 @@ +package ltd.lulz.test.container.postgres + +import ltd.lulz.test.container.postgres.TestContainerPostgres.sqlFile +import ltd.lulz.test.container.util.hasAnnotation +import org.junit.jupiter.api.Nested +import org.springframework.test.context.TestContext +import org.springframework.test.context.TestExecutionListener + +class PostgresTestListener : TestExecutionListener { + + override fun beforeTestClass( + context: TestContext, + ) { + if (context.testClass.hasAnnotation()) return + sqlFile("postgres/data.sql", context) + } + + override fun afterTestClass( + context: TestContext, + ) { + if (context.testClass.hasAnnotation()) return + sqlFile("postgres/reset.sql", context) + } +} diff --git a/src/main/kotlin/ltd/lulz/test/container/postgres/TestContainerPostgres.kt b/src/main/kotlin/ltd/lulz/test/container/postgres/TestContainerPostgres.kt new file mode 100644 index 0000000..82520dc --- /dev/null +++ b/src/main/kotlin/ltd/lulz/test/container/postgres/TestContainerPostgres.kt @@ -0,0 +1,68 @@ +package ltd.lulz.test.container.postgres + +import java.io.BufferedReader +import java.io.InputStream +import java.io.InputStreamReader +import kotlinx.coroutines.runBlocking +import ltd.lulz.test.container.util.isResourceFile +import org.springframework.r2dbc.core.DatabaseClient +import org.springframework.r2dbc.core.await +import org.springframework.test.context.TestContext +import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.utility.DockerImageName + +object TestContainerPostgres { + + val postgres = PostgreSQLContainer(DockerImageName.parse("postgres:18rc1-alpine")) + .withReuse(true) + .apply { + withDatabaseName("testdb") + withUsername("test") + withPassword("test") + "postgres/schema.sql".isResourceFile()?.let { withInitScript(it.path) } + } + + fun props(): Map = postgres.let { + mapOf( + "spring.r2dbc.url" to "r2dbc:postgresql://${it.host}:${it.firstMappedPort}/${it.databaseName}", + "spring.r2dbc.username" to it.username, + "spring.r2dbc.password" to it.password, + ) + } + + fun sqlFile( + sqlFile: String, + context: TestContext, + ): Unit = runBlocking { + sqlFile.isResourceFile() + ?.inputStream + ?.use { + executeSqlStatements( + makeSqlStatements(it), + context.applicationContext.getBean(DatabaseClient::class.java), + ) + } + } + + @Suppress("TooGenericExceptionThrown", "SqlSourceToSinkFlow") + private suspend fun executeSqlStatements( + statements: List, + databaseClient: DatabaseClient, + ) = try { + statements.forEach { databaseClient.sql(it).await() } + } catch (e: Exception) { + throw RuntimeException("Failed to execute SQL statements", e) + } + + private fun makeSqlStatements( + inputStream: InputStream, + ): List = BufferedReader(InputStreamReader(inputStream)) + .lines() + .filter { it.isNotEmpty() && !it.startsWith("--") } + .map { it.trim() } + .toList() + .joinToString(" ") + .split(';') + .filter { it.isNotBlank() } + .map { "${it.trim()};" } +} diff --git a/src/main/kotlin/ltd/lulz/test/container/util/ContainerUtil.kt b/src/main/kotlin/ltd/lulz/test/container/util/ContainerUtil.kt new file mode 100644 index 0000000..e0a0472 --- /dev/null +++ b/src/main/kotlin/ltd/lulz/test/container/util/ContainerUtil.kt @@ -0,0 +1,13 @@ +package ltd.lulz.test.container.util + +import org.springframework.core.io.ClassPathResource + +fun String.isResourceFile(): ClassPathResource? = ClassPathResource(this) + .let { resource -> + when { + resource.exists() && resource.isReadable -> resource + else -> null + } + } + +inline fun Class<*>.hasAnnotation(): Boolean = getAnnotation(T::class.java) != null diff --git a/src/test/kotlin/ltd/lulz/library/LulzLibraryTest.kt b/src/test/kotlin/ltd/lulz/library/LulzLibraryTest.kt deleted file mode 100644 index 54d9750..0000000 --- a/src/test/kotlin/ltd/lulz/library/LulzLibraryTest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package ltd.lulz.library - -import kotlin.test.Test - -class LulzLibraryTest { - - @Test - fun `Lulz Library Test`() { - // Placeholder - } -}