Skip to content
Open
Show file tree
Hide file tree
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
3 changes: 1 addition & 2 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ jobs:
- uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
with:
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- run: docker compose -f docker-compose.yml --profile app up --wait --build
- run: "./gradlew integrationTest --continue"
- run: "./gradlew integrationTest --continue --info"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: "gradle-integration-artifacts"
Expand Down
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
### VisualStudioCode template
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class DefaultFeignConfig(
@param:Value("\${bss.client.username}") val username: String,
@param:Value("\${bss.client.password}") val password: String,
) {
class DefaultFeignConfig {
@Bean
fun interceptor() =
RequestInterceptor { template ->
Expand Down
8 changes: 4 additions & 4 deletions docker-compose.ci.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
services:
app:
ports: !override
ports:
- "8080"
postgres:
ports: !override
ports:
- "5432"
mock-file-api:
ports: !override
ports:
- "8080"
volumes: []
mock-oidc:
ports: !override
ports:
- "5556"
49 changes: 44 additions & 5 deletions integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,54 @@ It stores the integration tests for the application.

To run the integration tests, you need to have Docker installed on your machine.

Testcontainers didn't have a clean way to start the compose file before all the tests.
So it's required to start the compose file manually before running the tests.
Each test will clear the database tables.
The integration tests now use **Testcontainers with Docker Compose** managed by **Spring's dependency injection** to automatically start and configure the required services from your existing `docker-compose.yml`:
- PostgreSQL database
- Your Spring Boot application
- WireMock servers for external API mocking (file-api and OIDC)

## Architecture

The integration test setup uses Spring's proper dependency injection patterns:

- **`TestContainerConfiguration`**: A Spring `@TestConfiguration` that manages Docker Compose as a singleton bean
- **`SharedDockerComposeContainer`**: Thread-safe singleton holder that ensures only one Docker Compose instance
- **`ContainerPropertyConfigurer`**: Spring-managed bean for configuring dynamic properties
- **`IntegrationTest`**: Base class that uses Spring's `@DynamicPropertySource` to configure properties

### Spring Benefits

✅ **Proper Spring patterns**: Uses `@TestConfiguration`, `@Bean`, and `@Scope("singleton")`
✅ **Dependency injection**: Container lifecycle managed by Spring's IoC container
✅ **Thread safety**: Double-checked locking pattern for safe singleton initialization
✅ **Automatic cleanup**: Spring handles bean lifecycle and cleanup
✅ **Configuration management**: Spring manages all container-related beans

The Docker Compose services are automatically started before the first test runs and stopped after all tests complete.
Each test will clear the database tables to ensure test isolation.

Each Integration test has to extend the `IntegrationTest` class.

```shell
docker compose up -d
./gradlew integrationTest
docker compose down
```

## What's changed from manual Docker Compose

- **Spring-managed lifecycle**: Docker Compose containers are managed as Spring beans
- **Singleton scope**: Spring ensures only one Docker Compose instance across all tests
- **Dependency injection**: Proper Spring IoC patterns instead of static objects
- **Thread-safe initialization**: Safe concurrent access to shared container instance
- **Dynamic port mapping**: Tests use the actual exposed ports from Docker Compose
- **Same service configuration**: Uses your existing `docker-compose.yml` exactly as configured
- **No manual setup required**: No need to run `docker compose up/down` manually

## Container Configuration

The integration tests use your existing `docker-compose.yml` services:
- **PostgreSQL 16.3**: Exact same configuration as your compose file
- **Spring Boot App**: Your actual application with all its dependencies
- **WireMock services**: File-api and OIDC mocks with the same stub mappings
- **Spring-managed cleanup**: All compose services are cleaned up by Spring's lifecycle management

This approach ensures your integration tests run against the exact same service configuration as your development and production environments, while using proper Spring dependency injection patterns for container management.

3 changes: 3 additions & 0 deletions integration/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ dependencies {
integrationTestImplementation("org.springframework.cloud:spring-cloud-starter-openfeign")
integrationTestImplementation("org.springframework.boot:spring-boot-starter-data-jpa")
integrationTestImplementation("org.springframework.boot:spring-boot-starter-json")
integrationTestImplementation("org.springframework.boot:spring-boot-testcontainers")
integrationTestImplementation("org.testcontainers:testcontainers")
integrationTestImplementation("org.testcontainers:junit-jupiter")
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,11 @@ import hu.bsstudio.bssweb.video.repository.DetailedVideoRepository
import hu.bsstudio.bssweb.videocrew.repository.VideoCrewRepository
import org.junit.jupiter.api.BeforeEach
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.TestPropertySource
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig

@SpringJUnitConfig(classes = [BssFeignConfig::class, DataConfig::class])
@TestPropertySource(
properties = [
"bss.client.url=http://localhost:8080",
"bss.client.username=user",
"bss.client.password=password",
"spring.flyway.enabled=false",
"spring.datasource.url=jdbc:postgresql://localhost:5432/bss?currentSchema=private",
"spring.datasource.username=user",
"spring.datasource.password=password",
],
)
@SpringJUnitConfig(classes = [TestContainerConfiguration::class, BssFeignConfig::class, DataConfig::class])
open class IntegrationTest {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove extraneous blank line to fix spotlessKotlinCheck failure.

The pipeline failure indicates formatting violations including an extraneous blank line near the class declaration.

 @SpringJUnitConfig(classes = [TestContainerConfiguration::class, BssFeignConfig::class, DataConfig::class])
-
 open class IntegrationTest {
🤖 Prompt for AI Agents
In integration/src/integrationTest/kotlin/hu/bsstudio/bssweb/IntegrationTest.kt
around line 16, there is an extraneous blank line causing spotlessKotlinCheck to
fail; remove that blank line so the class declaration and surrounding code
follow the project's Kotlin formatting rules (no extra empty lines) and then
re-run the formatter/check to confirm the violation is resolved.

@Autowired protected lateinit var eventRepository: DetailedEventRepository

@Autowired protected lateinit var videoRepository: DetailedVideoRepository
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package hu.bsstudio.bssweb

import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.DockerComposeContainer
import java.io.File

@TestConfiguration
class TestContainerConfiguration {

@Bean
fun dockerComposeContainer(): DockerComposeContainer<*> {
return DockerComposeContainer(File("../docker-compose.yml"), File("../docker-compose.ci.yml"))
.withExposedService("postgres", 5432)
.withExposedService("app", 8080)
.withOptions("--profile app")
.withBuild(true)
}

@DynamicPropertySource
fun containerPropertyConfigurer(registry: DynamicPropertyRegistry, dockerComposeContainer: DockerComposeContainer<*>) {
val postgresHost = dockerComposeContainer.getServiceHost("postgres", 5432)
val postgresPort = dockerComposeContainer.getServicePort("postgres", 5432)
val appHost = dockerComposeContainer.getServiceHost("app", 8080)
val appPort = dockerComposeContainer.getServicePort("app", 8080)

registry.add("spring.datasource.url") {
"jdbc:postgresql://$postgresHost:$postgresPort/bss?currentSchema=private"
}
registry.add("spring.datasource.username") { "user" }
registry.add("spring.datasource.password") { "password" }
registry.add("spring.flyway.enabled") { "false" }
registry.add("bss.client.url") { "http://$appHost:$appPort" }
}
Comment on lines +22 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

@DynamicPropertySource cannot inject beans as method parameters.

The @DynamicPropertySource annotation only accepts a single DynamicPropertyRegistry parameter. You cannot inject the dockerComposeContainer bean as a method parameter. This will cause a runtime failure.

To fix this, either:

  1. Make dockerComposeContainer a constructor parameter or field and access it from the property source method
  2. Use a companion object with @JvmStatic for the property source method

Option 1 (Recommended): Use constructor injection

 @TestConfiguration
-class TestContainerConfiguration {
+class TestContainerConfiguration(
+    private val dockerComposeContainer: DockerComposeContainer<*>
+) {
 
-    @Bean
-    fun dockerComposeContainer(): DockerComposeContainer<*> {
-        return DockerComposeContainer(File("../docker-compose.yml"), File("../docker-compose.ci.yml"))
-            .withExposedService("postgres", 5432)
-            .withExposedService("app", 8080)
-            .withOptions("--profile app")
-            .withBuild(true)
-    }
-
     @DynamicPropertySource
-    fun containerPropertyConfigurer(registry: DynamicPropertyRegistry, dockerComposeContainer: DockerComposeContainer<*>) {
+    fun containerPropertyConfigurer(registry: DynamicPropertyRegistry) {
         val postgresHost = dockerComposeContainer.getServiceHost("postgres", 5432)
         val postgresPort = dockerComposeContainer.getServicePort("postgres", 5432)
         val appHost = dockerComposeContainer.getServiceHost("app", 8080)
         val appPort = dockerComposeContainer.getServicePort("app", 8080)
 
         registry.add("spring.datasource.url") {
             "jdbc:postgresql://$postgresHost:$postgresPort/bss?currentSchema=private"
         }
         registry.add("spring.datasource.username") { "user" }
         registry.add("spring.datasource.password") { "password" }
         registry.add("spring.flyway.enabled") { "false" }
         registry.add("bss.client.url") { "http://$appHost:$appPort" }
     }
+
+    companion object {
+        @Bean
+        @JvmStatic
+        fun dockerComposeContainer(): DockerComposeContainer<*> {
+            return DockerComposeContainer(File("../docker-compose.yml"), File("../docker-compose.ci.yml"))
+                .withExposedService("postgres", 5432)
+                .withExposedService("app", 8080)
+                .withOptions("--profile app")
+                .withBuild(true)
+        }
+    }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
integration/src/integrationTest/kotlin/hu/bsstudio/bssweb/TestContainerConfiguration.kt
around lines 22 to 36, the @DynamicPropertySource method currently declares two
parameters (DynamicPropertyRegistry and dockerComposeContainer) which is
invalid; change the method so it only accepts DynamicPropertyRegistry and obtain
the DockerComposeContainer from a class field (set via constructor or property)
or move the method into a companion object as a @JvmStatic function that
accesses a static container reference; recommended: make dockerComposeContainer
a constructor-injected or top-level field on the test configuration class and
update the property configurer to use that field so the @DynamicPropertySource
method signature is exactly (registry: DynamicPropertyRegistry).

}
Comment on lines +1 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix Kotlin formatting to resolve spotlessKotlinCheck failure.

The pipeline indicates formatting violations in this file.

Run the following command to automatically fix the formatting:

#!/bin/bash
./gradlew spotlessApply
🧰 Tools
🪛 GitHub Actions: Gradle

[error] 1-1: integration:spotlessKotlinCheck failed: format violations detected in TestContainerConfiguration.kt. There are formatting/value assignment style issues in dockerComposeContainer and containerPropertyConfigurer definitions. Run './gradlew spotlessApply' to fix formatting.

🤖 Prompt for AI Agents
integration/src/integrationTest/kotlin/hu/bsstudio/bssweb/TestContainerConfiguration.kt
lines 1-37: the file fails spotlessKotlinCheck due to formatting issues; run the
formatter and commit the result to fix the pipeline. Execute ./gradlew
spotlessApply (or use your IDE’s Kotlin reformat) to auto-fix formatting, verify
the file is updated, then add and commit the changed file and push the branch so
CI picks up the formatted code.

Loading