diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0e926df..35d614d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,9 @@ jobs:
test:
name: Build & Test
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ checks: write
steps:
- name: Checkout code
uses: actions/checkout@v7
@@ -32,19 +35,16 @@ jobs:
run: chmod +x ./mvnw
- name: Run tests
- # Tests run against the in-memory H2 DB and spring.cache.type=simple,
- # so no Postgres/Redis service containers are needed here.
- # WEATHER_API_KEY just needs to be non-empty; tests mock the HTTP client.
env:
WEATHER_API_KEY: dummy-ci-key
- run: ./mvnw -B test
+ run: ./mvnw -B verify
- name: Publish test report
if: always()
uses: dorny/test-reporter@v3
with:
name: Maven Tests
- path: target/surefire-reports/*.xml
+ path: "target/surefire-reports/TEST-*.xml,target/failsafe-reports/TEST-*.xml"
reporter: java-junit
- name: Upload JaCoCo coverage report
diff --git a/README.md b/README.md
index 04b3586..a44b6b3 100644
--- a/README.md
+++ b/README.md
@@ -249,6 +249,30 @@ Actuator runs on a separate management port so it can be kept off the public net
Every log line is tagged with a request correlation ID, and HTTP request latency is exported as a histogram for easy percentile/SLO tracking.
+`docker-compose.yml` also runs a Prometheus + Grafana stack alongside the app, scraping `/actuator/prometheus` every 15s:
+
+```bash
+docker compose up -d
+```
+
+| Service | URL | Notes |
+|:-----------|:------------------------|:---------------------------------------------------------------|
+| Prometheus | http://localhost:9090 | Scrapes `weather_viewer:8081/actuator/prometheus` |
+| Grafana | http://localhost:3000 | Login `admin` / `admin` (dev-only default, see below) |
+
+Grafana auto-provisions the Prometheus datasource and a starter **Weather Viewer — Overview** dashboard on first startup — nothing to click through manually. It covers HTTP request rate/p95 latency, JVM heap usage, the Redis cache hit ratio, and the `weatherApi` circuit breaker state and retry calls (the same Resilience4j instance the [architecture diagrams](#architecture) above describe). Config lives under `monitoring/`:
+
+```
+monitoring/
+├── prometheus/prometheus.yml # scrape target + interval
+└── grafana/
+ ├── provisioning/datasources/datasource.yml # auto-adds Prometheus
+ ├── provisioning/dashboards/dashboards.yml # tells Grafana where to look
+ └── dashboards/weather-viewer-overview.json # the starter dashboard itself
+```
+
+Grafana's admin login comes from `GRAFANA_ADMIN_USER`/`GRAFANA_ADMIN_PASSWORD` in `.env` (same pattern as `POSTGRES_PASSWORD`), falling back to `admin`/`admin` if unset — fine for a quick local run, but set them in `.env` before running this anywhere reachable off your own machine.
+
## Security
- Passwords are hashed with BCrypt; sign-in is protected by per-account lockout after repeated failed attempts
@@ -261,16 +285,19 @@ Every log line is tagged with a request correlation ID, and HTTP request latency
## Running Tests
```bash
-./mvnw test
+./mvnw test # fast, Docker-free: unit tests + @WebMvcTest slices
+./mvnw verify # everything above, plus the *IT integration suite
```
-Every test that boots a Spring context (`@SpringBootTest`, `@DataJpaTest`) runs against real Postgres and Redis via [Testcontainers](https://testcontainers.com/) — `TestcontainersConfiguration` wires both in via `@ServiceConnection`, so no manual datasource/Redis properties are needed. This needs a running Docker daemon; without one, those tests fail to start. Pure unit tests (model/DTO/enum tests, Mockito-based service tests) don't start a Spring context at all, so they're unaffected either way. The suite includes unit tests, MVC/REST controller tests (`@WebMvcTest`, which slice the web layer and don't touch a real database), repository tests, and full integration tests for auth (including verification, password reset, and remember-me), search, profile, and weather flows. JaCoCo generates a coverage report at `target/site/jacoco/index.html` after running tests.
+Unit tests (model/DTO/enum tests, Mockito-based service tests) and `@WebMvcTest` controller slices don't start a real datasource at all, so `./mvnw test` alone needs nothing but a JDK — no Docker required. Classes named `*IT` (e.g. `UserRepositoryIT`, `SignInIT`) are the ones that boot a full Spring context against real Postgres and Redis via [Testcontainers](https://testcontainers.com/) — `TestcontainersConfiguration` wires both in via `@ServiceConnection`. Maven's Failsafe plugin only runs those during `./mvnw verify`, not `./mvnw test`, so a running Docker daemon is only required for `verify`.
+
+The suite covers unit tests, MVC/REST controller tests, repository tests, and full integration tests for auth (including verification, password reset, and remember-me), search, profile, and weather flows. JaCoCo instruments both Surefire (`test`) and Failsafe (`*IT`) runs separately, then merges the two into one combined report — that merge, and the report itself, only happen as part of `./mvnw verify`, at `target/site/jacoco/index.html`. The 90% line-coverage gate (`jacoco:check`) reads that same merged data and only runs during `verify` as well.
## CI/CD
Every push to `main` and every pull request into `main`/`dev` runs through GitHub Actions:
-1. **Build & Test** — compiles the project and runs the full test suite (Spring-context tests against real Postgres/Redis via Testcontainers), publishing a JUnit test report and a JaCoCo coverage report as workflow artifacts.
+1. **Build & Test** — runs `./mvnw verify`: unit/slice tests via Surefire plus the `*IT` integration suite via Failsafe (real Postgres/Redis via Testcontainers), publishing a JUnit test report and the merged JaCoCo coverage report as workflow artifacts.
2. **Update coverage badge** — on pushes to `main` or `dev`, regenerates that branch's `.github/badges/jacoco.svg` badge from the JaCoCo report and commits it back.
3. **Docker build & push** — on pushes to `main`, builds the application image and pushes it to Docker Hub as `podllev/weather-viewer`.
diff --git a/docker-compose.yml b/docker-compose.yml
index 0d395dc..7d1839a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,10 +1,10 @@
services:
- weather_viewer:
+ weather-viewer:
build:
context: .
dockerfile: Dockerfile
image: podllev/weather-viewer:latest
- container_name: weather_viewer
+ container_name: weather-viewer
restart: always
ports:
- "8080:8080"
@@ -28,7 +28,7 @@ services:
postgres:
image: postgres:17
- container_name: weather_viewer_db
+ container_name: weather-viewer-db
restart: always
env_file:
- .env
@@ -47,7 +47,7 @@ services:
redis:
image: redis:7
- container_name: weather_viewer_redis
+ container_name: weather-viewer-redis
restart: always
ports:
- "6379:6379"
@@ -59,6 +59,46 @@ services:
timeout: 5s
retries: 5
+ prometheus:
+ image: prom/prometheus:v3.8.1
+ container_name: weather-viewer-prometheus
+ restart: always
+ volumes:
+ - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
+ - prometheus_data:/prometheus
+ ports:
+ - "9090:9090"
+ healthcheck:
+ test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9090/-/ready" ]
+ interval: 15s
+ timeout: 5s
+ retries: 3
+ start_period: 10s
+ depends_on:
+ weather-viewer:
+ condition: service_healthy
+
+ grafana:
+ image: grafana/grafana:13.1.1
+ container_name: weather-viewer-grafana
+ restart: always
+ env_file:
+ - .env
+ environment:
+ - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-admin}
+ - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin}
+ - GF_AUTH_ANONYMOUS_ENABLED=false
+ volumes:
+ - ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro
+ - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards:ro
+ - grafana_data:/var/lib/grafana
+ ports:
+ - "3000:3000"
+ depends_on:
+ - prometheus
+
volumes:
postgres_data:
redis_data:
+ prometheus_data:
+ grafana_data:
\ No newline at end of file
diff --git a/monitoring/grafana/dashboards/weather-viewer-overview.json b/monitoring/grafana/dashboards/weather-viewer-overview.json
new file mode 100644
index 0000000..c5617b3
--- /dev/null
+++ b/monitoring/grafana/dashboards/weather-viewer-overview.json
@@ -0,0 +1,102 @@
+{
+ "title": "Weather Viewer — Overview",
+ "uid": "weather-viewer-overview",
+ "schemaVersion": 39,
+ "version": 1,
+ "editable": true,
+ "timezone": "browser",
+ "refresh": "10s",
+ "time": { "from": "now-1h", "to": "now" },
+ "tags": ["weather-viewer"],
+ "panels": [
+ {
+ "id": 1,
+ "title": "HTTP request rate",
+ "type": "timeseries",
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
+ "targets": [
+ {
+ "expr": "sum(rate(http_server_requests_seconds_count[1m])) by (uri, status)",
+ "legendFormat": "{{uri}} [{{status}}]"
+ }
+ ],
+ "fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] }
+ },
+ {
+ "id": 2,
+ "title": "HTTP p95 latency",
+ "type": "timeseries",
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
+ "targets": [
+ {
+ "expr": "histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri))",
+ "legendFormat": "{{uri}}"
+ }
+ ],
+ "fieldConfig": { "defaults": { "unit": "s" }, "overrides": [] }
+ },
+ {
+ "id": 3,
+ "title": "JVM heap used",
+ "type": "timeseries",
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
+ "targets": [
+ {
+ "expr": "sum(jvm_memory_used_bytes{area=\"heap\"}) by (id)",
+ "legendFormat": "{{id}}"
+ }
+ ],
+ "fieldConfig": { "defaults": { "unit": "bytes" }, "overrides": [] }
+ },
+ {
+ "id": 4,
+ "title": "Cache hit ratio",
+ "type": "timeseries",
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
+ "description": "Requires spring.cache.type=redis — the simple in-memory cache profile doesn't emit cache.gets metrics.",
+ "targets": [
+ {
+ "expr": "sum(rate(cache_gets_total{result=\"hit\"}[5m])) by (cache) / sum(rate(cache_gets_total[5m])) by (cache)",
+ "legendFormat": "{{cache}}"
+ }
+ ],
+ "fieldConfig": {
+ "defaults": { "unit": "percentunit", "min": 0, "max": 1 },
+ "overrides": []
+ }
+ },
+ {
+ "id": 5,
+ "title": "weatherApi circuit breaker state",
+ "type": "state-timeline",
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
+ "gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 },
+ "description": "0 = closed, 1 = open, 2 = half-open.",
+ "targets": [
+ {
+ "expr": "resilience4j_circuitbreaker_state{name=\"weatherApi\"}",
+ "legendFormat": "{{state}}"
+ }
+ ]
+ },
+ {
+ "id": 6,
+ "title": "weatherApi retry calls",
+ "type": "timeseries",
+ "datasource": { "type": "prometheus", "uid": "prometheus" },
+ "gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 },
+ "description": "kind: successful_without_retry / successful_with_retry / failed_with_retry / failed_without_retry",
+ "targets": [
+ {
+ "expr": "sum(rate(resilience4j_retry_calls_total{name=\"weatherApi\"}[5m])) by (kind)",
+ "legendFormat": "{{kind}}"
+ }
+ ],
+ "fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] }
+ }
+ ]
+}
diff --git a/monitoring/grafana/provisioning/dashboards/dashboards.yml b/monitoring/grafana/provisioning/dashboards/dashboards.yml
new file mode 100644
index 0000000..3efe969
--- /dev/null
+++ b/monitoring/grafana/provisioning/dashboards/dashboards.yml
@@ -0,0 +1,12 @@
+apiVersion: 1
+
+providers:
+ - name: WeatherViewer
+ orgId: 1
+ folder: ""
+ type: file
+ disableDeletion: false
+ updateIntervalSeconds: 30
+ allowUiUpdates: true
+ options:
+ path: /var/lib/grafana/dashboards
diff --git a/monitoring/grafana/provisioning/datasources/datasource.yml b/monitoring/grafana/provisioning/datasources/datasource.yml
new file mode 100644
index 0000000..00f9915
--- /dev/null
+++ b/monitoring/grafana/provisioning/datasources/datasource.yml
@@ -0,0 +1,10 @@
+apiVersion: 1
+
+datasources:
+ - name: Prometheus
+ uid: prometheus
+ type: prometheus
+ access: proxy
+ url: http://prometheus:9090
+ isDefault: true
+ editable: false
diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml
new file mode 100644
index 0000000..bc65d76
--- /dev/null
+++ b/monitoring/prometheus/prometheus.yml
@@ -0,0 +1,9 @@
+global:
+ scrape_interval: 15s
+ evaluation_interval: 15s
+
+scrape_configs:
+ - job_name: weather-viewer
+ metrics_path: /actuator/prometheus
+ static_configs:
+ - targets: ["weather-viewer:8081"]
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index e4ae47a..f2acdb7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -235,12 +235,42 @@
prepare-agent
+
+ prepare-agent-integration
+
+ prepare-agent-integration
+
+
+ failsafeArgLine
+
+
+
+ merge-results
+ post-integration-test
+
+ merge
+
+
+
+
+ ${project.build.directory}
+
+ *.exec
+
+
+
+ ${project.build.directory}/jacoco-merged.exec
+
+
report
- test
+ post-integration-test
report
+
+ ${project.build.directory}/jacoco-merged.exec
+
check
@@ -248,6 +278,7 @@
check
+ ${project.build.directory}/jacoco-merged.exec
BUNDLE
@@ -264,6 +295,22 @@
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ ${failsafeArgLine}
+
+
+
+
+ integration-test
+ verify
+
+
+
+
org.apache.maven.plugins
maven-javadoc-plugin
diff --git a/src/main/java/com/weatherviewer/config/CacheConfig.java b/src/main/java/com/weatherviewer/config/CacheConfig.java
new file mode 100644
index 0000000..87fcff3
--- /dev/null
+++ b/src/main/java/com/weatherviewer/config/CacheConfig.java
@@ -0,0 +1,18 @@
+package com.weatherviewer.config;
+
+import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+
+@Configuration
+@EnableCaching
+public class CacheConfig {
+
+ @Bean
+ public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
+ return RedisCacheManager.RedisCacheManagerBuilder::enableStatistics;
+ }
+
+}
diff --git a/src/main/java/com/weatherviewer/config/SecurityConfig.java b/src/main/java/com/weatherviewer/config/SecurityConfig.java
index f526b76..a6c4751 100644
--- a/src/main/java/com/weatherviewer/config/SecurityConfig.java
+++ b/src/main/java/com/weatherviewer/config/SecurityConfig.java
@@ -80,7 +80,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
"/verify-email", "/resend-verification",
"/forgot-password", "/reset-password",
"/css/**", "/images/**", "/js/**",
- "/actuator/health", "/actuator/health/**"
+ "/actuator", "/actuator/**"
).permitAll()
.requestMatchers(
"/swagger-ui.html", "/swagger-ui/**",
diff --git a/src/test/java/com/weatherviewer/WeatherViewerApplicationIT.java b/src/test/java/com/weatherviewer/WeatherViewerApplicationIT.java
new file mode 100644
index 0000000..f521e5b
--- /dev/null
+++ b/src/test/java/com/weatherviewer/WeatherViewerApplicationIT.java
@@ -0,0 +1,38 @@
+package com.weatherviewer;
+
+import com.weatherviewer.testcontainers.TestcontainersConfiguration;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.PostgreSQLContainer;
+
+@SpringBootTest
+@Import(TestcontainersConfiguration.class)
+class WeatherViewerApplicationIT {
+
+ @Autowired
+ private PostgreSQLContainer> postgresContainer;
+
+ @Autowired
+ private GenericContainer> redisContainer;
+
+ @Test
+ void contextLoads() {
+ }
+
+ @Test
+ void mainMethodStartsApplication() {
+ WeatherViewerApplication.main(new String[] {
+ "--spring.datasource.url=" + postgresContainer.getJdbcUrl(),
+ "--spring.datasource.username=" + postgresContainer.getUsername(),
+ "--spring.datasource.password=" + postgresContainer.getPassword(),
+ "--spring.datasource.driver-class-name=org.postgresql.Driver",
+ "--spring.data.redis.host=" + redisContainer.getHost(),
+ "--spring.data.redis.port=" + redisContainer.getMappedPort(6379),
+ "--server.port=0",
+ });
+ }
+
+}
diff --git a/src/test/java/com/weatherviewer/WeatherViewerApplicationTests.java b/src/test/java/com/weatherviewer/WeatherViewerApplicationTests.java
deleted file mode 100644
index f94b381..0000000
--- a/src/test/java/com/weatherviewer/WeatherViewerApplicationTests.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.weatherviewer;
-
-import org.junit.jupiter.api.Test;
-import org.mockito.MockedStatic;
-import org.springframework.boot.SpringApplication;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mockStatic;
-
-class WeatherViewerApplicationTests {
-
- @Test
- void mainStartsApplication() {
- try (MockedStatic mocked = mockStatic(SpringApplication.class)) {
-
- WeatherViewerApplication.main(new String[0]);
-
- mocked.verify(() ->
- SpringApplication.run(
- eq(WeatherViewerApplication.class),
- eq(new String[0])
- ));
- }
- }
-}
diff --git a/src/test/java/com/weatherviewer/controller/HomeControllerTest.java b/src/test/java/com/weatherviewer/controller/HomeControllerIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/controller/HomeControllerTest.java
rename to src/test/java/com/weatherviewer/controller/HomeControllerIT.java
index 13e8902..8c3909b 100644
--- a/src/test/java/com/weatherviewer/controller/HomeControllerTest.java
+++ b/src/test/java/com/weatherviewer/controller/HomeControllerIT.java
@@ -37,7 +37,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class HomeControllerTest {
+class HomeControllerIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/controller/ProfileControllerTest.java b/src/test/java/com/weatherviewer/controller/ProfileControllerIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/controller/ProfileControllerTest.java
rename to src/test/java/com/weatherviewer/controller/ProfileControllerIT.java
index 581efe8..8168279 100644
--- a/src/test/java/com/weatherviewer/controller/ProfileControllerTest.java
+++ b/src/test/java/com/weatherviewer/controller/ProfileControllerIT.java
@@ -33,7 +33,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class ProfileControllerTest {
+class ProfileControllerIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/controller/SearchControllerTest.java b/src/test/java/com/weatherviewer/controller/SearchControllerIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/controller/SearchControllerTest.java
rename to src/test/java/com/weatherviewer/controller/SearchControllerIT.java
index 6da305e..e1f044f 100644
--- a/src/test/java/com/weatherviewer/controller/SearchControllerTest.java
+++ b/src/test/java/com/weatherviewer/controller/SearchControllerIT.java
@@ -35,7 +35,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class SearchControllerTest {
+class SearchControllerIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/ForecastIntegrationTest.java b/src/test/java/com/weatherviewer/integration/ForecastIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/ForecastIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/ForecastIT.java
index fd23fc5..26cef06 100644
--- a/src/test/java/com/weatherviewer/integration/ForecastIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/ForecastIT.java
@@ -28,7 +28,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class ForecastIntegrationTest {
+class ForecastIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/LocationIntegrationTest.java b/src/test/java/com/weatherviewer/integration/LocationIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/LocationIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/LocationIT.java
index e366975..4259fd3 100644
--- a/src/test/java/com/weatherviewer/integration/LocationIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/LocationIT.java
@@ -32,7 +32,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class LocationIntegrationTest {
+class LocationIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/OpenApiDocumentationIntegrationTest.java b/src/test/java/com/weatherviewer/integration/OpenApiDocumentationIT.java
similarity index 97%
rename from src/test/java/com/weatherviewer/integration/OpenApiDocumentationIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/OpenApiDocumentationIT.java
index 282ff80..f0ede94 100644
--- a/src/test/java/com/weatherviewer/integration/OpenApiDocumentationIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/OpenApiDocumentationIT.java
@@ -14,7 +14,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class OpenApiDocumentationIntegrationTest {
+class OpenApiDocumentationIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/ProfileIntegrationTest.java b/src/test/java/com/weatherviewer/integration/ProfileIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/ProfileIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/ProfileIT.java
index 4329872..500fe24 100644
--- a/src/test/java/com/weatherviewer/integration/ProfileIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/ProfileIT.java
@@ -29,7 +29,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class ProfileIntegrationTest {
+class ProfileIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/SearchIntegrationTest.java b/src/test/java/com/weatherviewer/integration/SearchIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/SearchIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/SearchIT.java
index af3c21c..9d67770 100644
--- a/src/test/java/com/weatherviewer/integration/SearchIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/SearchIT.java
@@ -36,7 +36,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class SearchIntegrationTest {
+class SearchIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/SecurityIntegrationTest.java b/src/test/java/com/weatherviewer/integration/SecurityIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/SecurityIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/SecurityIT.java
index a97c4ad..8e28102 100644
--- a/src/test/java/com/weatherviewer/integration/SecurityIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/SecurityIT.java
@@ -18,7 +18,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class SecurityIntegrationTest {
+class SecurityIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/SignInIntegrationTest.java b/src/test/java/com/weatherviewer/integration/SignInIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/SignInIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/SignInIT.java
index 947d06f..df99ea4 100644
--- a/src/test/java/com/weatherviewer/integration/SignInIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/SignInIT.java
@@ -25,7 +25,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class SignInIntegrationTest {
+class SignInIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/UserRegistrationIntegrationTest.java b/src/test/java/com/weatherviewer/integration/UserRegistrationIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/UserRegistrationIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/UserRegistrationIT.java
index 1e25b67..c5c34fb 100644
--- a/src/test/java/com/weatherviewer/integration/UserRegistrationIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/UserRegistrationIT.java
@@ -21,7 +21,7 @@
@SpringBootTest
@AutoConfigureMockMvc
@Import(TestcontainersConfiguration.class)
-class UserRegistrationIntegrationTest {
+class UserRegistrationIT {
@Autowired
MockMvc mockMvc;
diff --git a/src/test/java/com/weatherviewer/integration/WeatherApiCacheIntegrationTest.java b/src/test/java/com/weatherviewer/integration/WeatherApiCacheIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/WeatherApiCacheIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/WeatherApiCacheIT.java
index 015e2f4..142d5e3 100644
--- a/src/test/java/com/weatherviewer/integration/WeatherApiCacheIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/WeatherApiCacheIT.java
@@ -29,7 +29,7 @@
*/
@SpringBootTest
@Import(TestcontainersConfiguration.class)
-class WeatherApiCacheIntegrationTest {
+class WeatherApiCacheIT {
private final ObjectMapper objectMapper = new ObjectMapper();
diff --git a/src/test/java/com/weatherviewer/integration/WeatherApiIntegrationTest.java b/src/test/java/com/weatherviewer/integration/WeatherApiIT.java
similarity index 99%
rename from src/test/java/com/weatherviewer/integration/WeatherApiIntegrationTest.java
rename to src/test/java/com/weatherviewer/integration/WeatherApiIT.java
index 6ad4efb..7efa9c9 100644
--- a/src/test/java/com/weatherviewer/integration/WeatherApiIntegrationTest.java
+++ b/src/test/java/com/weatherviewer/integration/WeatherApiIT.java
@@ -30,7 +30,7 @@
@SpringBootTest
@Import(TestcontainersConfiguration.class)
-class WeatherApiIntegrationTest {
+class WeatherApiIT {
@Autowired
private RestClient.Builder restClientBuilder;
diff --git a/src/test/java/com/weatherviewer/repository/LocationRepositoryTest.java b/src/test/java/com/weatherviewer/repository/LocationRepositoryIT.java
similarity index 97%
rename from src/test/java/com/weatherviewer/repository/LocationRepositoryTest.java
rename to src/test/java/com/weatherviewer/repository/LocationRepositoryIT.java
index f685679..1e534f3 100644
--- a/src/test/java/com/weatherviewer/repository/LocationRepositoryTest.java
+++ b/src/test/java/com/weatherviewer/repository/LocationRepositoryIT.java
@@ -8,6 +8,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.context.annotation.Import;
@@ -18,7 +19,8 @@
@DataJpaTest
@Import(TestcontainersConfiguration.class)
-class LocationRepositoryTest {
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+class LocationRepositoryIT {
@Autowired
TestEntityManager entityManager;
diff --git a/src/test/java/com/weatherviewer/repository/UserRepositoryTest.java b/src/test/java/com/weatherviewer/repository/UserRepositoryIT.java
similarity index 94%
rename from src/test/java/com/weatherviewer/repository/UserRepositoryTest.java
rename to src/test/java/com/weatherviewer/repository/UserRepositoryIT.java
index ec18104..d1b8585 100644
--- a/src/test/java/com/weatherviewer/repository/UserRepositoryTest.java
+++ b/src/test/java/com/weatherviewer/repository/UserRepositoryIT.java
@@ -8,6 +8,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.context.annotation.Import;
@@ -19,7 +20,8 @@
@DataJpaTest
@Import(TestcontainersConfiguration.class)
-class UserRepositoryTest {
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+class UserRepositoryIT {
@Autowired
TestEntityManager entityManager;
diff --git a/src/test/java/com/weatherviewer/repository/VerificationTokenRepositoryTest.java b/src/test/java/com/weatherviewer/repository/VerificationTokenRepositoryIT.java
similarity index 96%
rename from src/test/java/com/weatherviewer/repository/VerificationTokenRepositoryTest.java
rename to src/test/java/com/weatherviewer/repository/VerificationTokenRepositoryIT.java
index 7e16c1e..81c71d8 100644
--- a/src/test/java/com/weatherviewer/repository/VerificationTokenRepositoryTest.java
+++ b/src/test/java/com/weatherviewer/repository/VerificationTokenRepositoryIT.java
@@ -9,6 +9,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.context.annotation.Import;
@@ -20,7 +21,8 @@
@DataJpaTest
@Import(TestcontainersConfiguration.class)
-class VerificationTokenRepositoryTest {
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+class VerificationTokenRepositoryIT {
@Autowired
TestEntityManager entityManager;