T-4: add JPA, Liquibase, Testcontainers, and deployment docs
Set up development infrastructure for TDD: JPA + Liquibase for database migrations, Testcontainers for integration tests against real PostgreSQL, profile-based configuration (prod/local), and README deployment documentation with docker-compose example. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
567
docs/agents/plan/2026-03-04-t4-development-infrastructure.md
Normal file
567
docs/agents/plan/2026-03-04-t4-development-infrastructure.md
Normal file
@@ -0,0 +1,567 @@
|
||||
---
|
||||
date: 2026-03-04T20:09:31.044992+00:00
|
||||
git_commit: cb0bcad145b03fec63be0ee3c1fca46ee545329e
|
||||
branch: master
|
||||
topic: "T-4: Development Infrastructure Setup"
|
||||
tags: [plan, database, liquibase, testcontainers, configuration, docker-compose]
|
||||
status: draft
|
||||
---
|
||||
|
||||
# T-4: Development Infrastructure Setup — Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Set up the remaining development infrastructure needed before the first user story (US-1) can be implemented with TDD. This adds JPA + Liquibase for database migrations, PostgreSQL connectivity via environment variables, Testcontainers for integration tests, app-specific configuration properties, and README deployment documentation with a docker-compose example.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
**Already complete (no work needed):**
|
||||
- SPA router: Vue Router with `createWebHistory`, backend SPA fallback in `WebConfig.java`
|
||||
- Frontend test infrastructure: Vitest + `@vue/test-utils`, sample test passing
|
||||
- Both test suites executable: `./mvnw test` (3 tests) and `npm run test:unit` (1 test)
|
||||
|
||||
**Missing (all work in this plan):**
|
||||
- JPA, Liquibase, PostgreSQL driver — no database dependencies in `pom.xml`
|
||||
- Testcontainers — not configured
|
||||
- Database connectivity — no datasource properties
|
||||
- App-specific config — no `@ConfigurationProperties`
|
||||
- Profile separation — no `application-prod.properties`
|
||||
- Deployment docs — no docker-compose in README
|
||||
|
||||
### Key Discoveries:
|
||||
- `backend/pom.xml:1-170` — Spring Boot 3.5.11, no DB dependencies
|
||||
- `backend/src/main/resources/application.properties:1-4` — Only app name + actuator
|
||||
- `HexagonalArchitectureTest.java:22` — `config` is already an adapter in ArchUnit rules
|
||||
- `FeteApplicationTest.java` — Uses `@SpringBootTest` + MockMvc; will need datasource after JPA is added
|
||||
- `Dockerfile:26` — No `SPRING_PROFILES_ACTIVE` set
|
||||
- `.gitignore:47-51` — `.env*` patterns exist but no `application-local.properties`
|
||||
|
||||
## Desired End State
|
||||
|
||||
After this plan is complete:
|
||||
- `./mvnw test` runs all backend tests (including new Testcontainers-backed integration tests) against a real PostgreSQL without external setup
|
||||
- `./mvnw spring-boot:run -Dspring-boot.run.profiles=local` starts the app against a local PostgreSQL
|
||||
- Docker container starts with `DATABASE_URL`/`DATABASE_USERNAME`/`DATABASE_PASSWORD` env vars, runs Liquibase migrations, and responds to health checks
|
||||
- README contains a copy-paste-ready docker-compose example for deployment
|
||||
- `FeteProperties` scaffolds `fete.unsplash.api-key` and `fete.max-active-events` (no business logic yet)
|
||||
|
||||
### Verification:
|
||||
- `cd backend && ./mvnw verify` — all tests green, checkstyle + spotbugs pass
|
||||
- `cd frontend && npm run test:unit -- --run` — unchanged, still green
|
||||
- `docker build .` — succeeds
|
||||
- docker-compose (app + postgres) — container starts, `/actuator/health` returns `{"status":"UP"}`
|
||||
|
||||
## What We're NOT Doing
|
||||
|
||||
- No JPA entities or repositories — those come with US-1
|
||||
- No domain model classes — those come with US-1
|
||||
- No business logic for `FeteProperties` (Unsplash, max events) — US-13/US-16
|
||||
- No standalone `docker-compose.yml` file in repo — inline in README per CLAUDE.md
|
||||
- No `application-local.properties` committed — only the `.example` template
|
||||
- No changes to frontend code — AC 4/6/7 are already met
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: JPA + Liquibase + PostgreSQL Dependencies
|
||||
|
||||
### Overview
|
||||
Add all database-related dependencies to `pom.xml`, create the Liquibase changelog structure with an empty baseline changeset, and update `application.properties` with JPA and Liquibase settings.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Add database dependencies to `pom.xml`
|
||||
**File**: `backend/pom.xml`
|
||||
**Changes**: Add four dependencies after the existing `spring-boot-starter-validation` block.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Add Testcontainers dependencies in test scope (after `archunit-junit5`):
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Spring Boot's dependency management handles versions for all of these — no explicit version tags needed (except `archunit-junit5` which is already versioned).
|
||||
|
||||
#### [x] 2. Create Liquibase master changelog
|
||||
**File**: `backend/src/main/resources/db/changelog/db.changelog-master.xml` (new)
|
||||
**Changes**: Create the master changelog that includes individual changesets.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<include file="db/changelog/000-baseline.xml"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
#### [x] 3. Create empty baseline changeset
|
||||
**File**: `backend/src/main/resources/db/changelog/000-baseline.xml` (new)
|
||||
**Changes**: Empty changeset that proves the tooling works. Liquibase creates its tracking tables (`databasechangelog`, `databasechangeloglock`) automatically.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<!-- T-4: Baseline changeset. Proves Liquibase tooling works.
|
||||
First real schema comes with US-1. -->
|
||||
<changeSet id="000-baseline" author="fete">
|
||||
<comment>Baseline changeset — Liquibase tooling verification</comment>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
#### [x] 4. Update application.properties with JPA and Liquibase settings
|
||||
**File**: `backend/src/main/resources/application.properties`
|
||||
**Changes**: Add JPA and Liquibase configuration (environment-independent, always active).
|
||||
|
||||
```properties
|
||||
spring.application.name=fete
|
||||
|
||||
# JPA
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
spring.jpa.open-in-view=false
|
||||
|
||||
# Liquibase
|
||||
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=health
|
||||
management.endpoint.health.show-details=never
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw compile` succeeds (dependencies resolve, checkstyle passes)
|
||||
- [ ] Changelog XML files are well-formed (Maven compile does not fail on resource processing)
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] Verify `pom.xml` has all six new dependencies with correct scopes
|
||||
- [ ] Verify changelog directory structure: `db/changelog/db.changelog-master.xml` includes `000-baseline.xml`
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Profile-Based Configuration and App Properties
|
||||
|
||||
### Overview
|
||||
Create the profile-based property files for production and local development, add the `FeteProperties` configuration class, update `.gitignore`, and set the production profile in the Dockerfile.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Create production properties file
|
||||
**File**: `backend/src/main/resources/application-prod.properties` (new)
|
||||
**Changes**: Production profile with environment variable placeholders. Activated in Docker via `SPRING_PROFILES_ACTIVE=prod`.
|
||||
|
||||
```properties
|
||||
# Database (required)
|
||||
spring.datasource.url=${DATABASE_URL}
|
||||
spring.datasource.username=${DATABASE_USERNAME}
|
||||
spring.datasource.password=${DATABASE_PASSWORD}
|
||||
|
||||
# App-specific (optional)
|
||||
fete.unsplash.api-key=${UNSPLASH_API_KEY:}
|
||||
fete.max-active-events=${MAX_ACTIVE_EVENTS:0}
|
||||
```
|
||||
|
||||
#### [x] 2. Create local development properties template
|
||||
**File**: `backend/src/main/resources/application-local.properties.example` (new)
|
||||
**Changes**: Template that developers copy to `application-local.properties` (which is gitignored).
|
||||
|
||||
```properties
|
||||
# Local development database
|
||||
# Copy this file to application-local.properties and adjust as needed.
|
||||
# Start with: ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/fete
|
||||
spring.datasource.username=fete
|
||||
spring.datasource.password=fete
|
||||
```
|
||||
|
||||
#### [x] 3. Add `application-local.properties` to `.gitignore`
|
||||
**File**: `.gitignore`
|
||||
**Changes**: Add the gitignore entry for the local properties file (under the Environment section).
|
||||
|
||||
```
|
||||
# Spring Boot local profile (developer-specific, not committed)
|
||||
backend/src/main/resources/application-local.properties
|
||||
```
|
||||
|
||||
#### ~~4. Create `FeteProperties` configuration properties class~~ (deferred)
|
||||
**File**: `backend/src/main/java/de/fete/config/FeteProperties.java` (new)
|
||||
**Changes**: Type-safe configuration for app-specific settings. Both properties are only scaffolded — business logic comes with US-13/US-16.
|
||||
|
||||
```java
|
||||
package de.fete.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Application-specific configuration properties.
|
||||
*
|
||||
* <p>Mapped from {@code fete.*} properties. Both properties are optional:
|
||||
* <ul>
|
||||
* <li>{@code fete.unsplash.api-key} — Unsplash API key (empty = feature disabled)
|
||||
* <li>{@code fete.max-active-events} — Maximum active events (0 = unlimited)
|
||||
* </ul>
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "fete")
|
||||
public class FeteProperties {
|
||||
|
||||
private final Unsplash unsplash;
|
||||
private final int maxActiveEvents;
|
||||
|
||||
/** Creates FeteProperties with the given values. */
|
||||
public FeteProperties(Unsplash unsplash, int maxActiveEvents) {
|
||||
this.unsplash = unsplash != null ? unsplash : new Unsplash("");
|
||||
this.maxActiveEvents = maxActiveEvents;
|
||||
}
|
||||
|
||||
/** Returns the Unsplash configuration. */
|
||||
public Unsplash getUnsplash() {
|
||||
return unsplash;
|
||||
}
|
||||
|
||||
/** Returns the maximum number of active events (0 = unlimited). */
|
||||
public int getMaxActiveEvents() {
|
||||
return maxActiveEvents;
|
||||
}
|
||||
|
||||
/** Unsplash-related configuration. */
|
||||
public record Unsplash(String apiKey) {
|
||||
|
||||
/** Creates Unsplash config with the given API key. */
|
||||
public Unsplash {
|
||||
if (apiKey == null) {
|
||||
apiKey = "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if an API key is configured. */
|
||||
public boolean isEnabled() {
|
||||
return !apiKey.isBlank();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ~~5. Create `FetePropertiesConfig` configuration class~~ (deferred)
|
||||
**File**: `backend/src/main/java/de/fete/config/FetePropertiesConfig.java` (new)
|
||||
**Changes**: Separate `@Configuration` that enables `FeteProperties`.
|
||||
|
||||
```java
|
||||
package de.fete.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/** Activates {@link FeteProperties} binding. */
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(FeteProperties.class)
|
||||
public class FetePropertiesConfig {
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 6. Set production profile in Dockerfile
|
||||
**File**: `Dockerfile`
|
||||
**Changes**: Add `ENV SPRING_PROFILES_ACTIVE=prod` in the runtime stage, before `ENTRYPOINT`.
|
||||
|
||||
```dockerfile
|
||||
# Stage 3: Runtime
|
||||
FROM eclipse-temurin:25-jre-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=backend-build /app/backend/target/*.jar app.jar
|
||||
EXPOSE 8080
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw compile` succeeds (FeteProperties compiles, checkstyle passes)
|
||||
- [ ] `docker build .` succeeds
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] `application-prod.properties` contains all five env-var placeholders
|
||||
- [ ] `application-local.properties.example` is committed; `application-local.properties` is gitignored
|
||||
- [ ] `FeteProperties` fields: `unsplash.apiKey` (String), `maxActiveEvents` (int)
|
||||
- [ ] Dockerfile has `ENV SPRING_PROFILES_ACTIVE=prod` before `ENTRYPOINT`
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Testcontainers Integration
|
||||
|
||||
### Overview
|
||||
Set up the TestApplication pattern so that all `@SpringBootTest` tests automatically get a Testcontainers-managed PostgreSQL instance. This is critical: once JPA is on the classpath, every `@SpringBootTest` needs a datasource. Without this, all three existing `@SpringBootTest` tests break.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Create Testcontainers configuration
|
||||
**File**: `backend/src/test/java/de/fete/TestcontainersConfig.java` (new)
|
||||
**Changes**: Registers a PostgreSQL Testcontainer with `@ServiceConnection` for automatic datasource wiring.
|
||||
|
||||
```java
|
||||
package de.fete;
|
||||
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
/** Provides a Testcontainers PostgreSQL instance for integration tests. */
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
public class TestcontainersConfig {
|
||||
|
||||
@Bean
|
||||
@ServiceConnection
|
||||
PostgreSQLContainer<?> postgresContainer() {
|
||||
return new PostgreSQLContainer<>("postgres:17-alpine");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 2. Create TestFeteApplication for `spring-boot:test-run`
|
||||
**File**: `backend/src/test/java/de/fete/TestFeteApplication.java` (new)
|
||||
**Changes**: Entry point that imports `TestcontainersConfig`. Enables `./mvnw spring-boot:test-run` for local development with Testcontainers (no external PostgreSQL needed).
|
||||
|
||||
```java
|
||||
package de.fete;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
|
||||
/** Test entry point — starts the app with Testcontainers PostgreSQL. */
|
||||
public class TestFeteApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.from(FeteApplication::main)
|
||||
.with(TestcontainersConfig.class)
|
||||
.run(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 3. Import TestcontainersConfig in existing `@SpringBootTest` tests
|
||||
**File**: `backend/src/test/java/de/fete/FeteApplicationTest.java`
|
||||
**Changes**: Add `@Import(TestcontainersConfig.class)` so the test gets a datasource.
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Import(TestcontainersConfig.class)
|
||||
class FeteApplicationTest {
|
||||
// ... existing tests unchanged
|
||||
}
|
||||
```
|
||||
|
||||
**File**: `backend/src/test/java/de/fete/config/WebConfigTest.java`
|
||||
**Changes**: Same — add `@Import(TestcontainersConfig.class)`.
|
||||
|
||||
Note: `HexagonalArchitectureTest` uses `@AnalyzeClasses` (ArchUnit), not `@SpringBootTest` — it needs no changes.
|
||||
|
||||
#### [x] 4. Add SpotBugs exclusion for Testcontainers resource management
|
||||
**File**: `backend/spotbugs-exclude.xml`
|
||||
**Changes**: Testcontainers `PostgreSQLContainer` bean intentionally has container lifecycle managed by Spring, not try-with-resources. SpotBugs may flag this. Add exclusion if needed — check after running `./mvnw verify`.
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw test` — all existing tests pass (context loads, health endpoint, ArchUnit)
|
||||
- [ ] `cd backend && ./mvnw verify` — full verify including SpotBugs passes
|
||||
- [ ] Testcontainers starts a PostgreSQL container during test execution (visible in test output)
|
||||
- [ ] Liquibase baseline migration runs against Testcontainers PostgreSQL
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] `./mvnw spring-boot:test-run` starts the app with a Testcontainers PostgreSQL (for local dev without external DB)
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: README Deployment Documentation
|
||||
|
||||
### Overview
|
||||
Add a deployment section to the README with a docker-compose example, environment variable documentation, and local development setup instructions.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Add deployment section to README
|
||||
**File**: `README.md`
|
||||
**Changes**: Add a `## Deployment` section after the existing `## Code quality` section and before `## License`. Contains the docker-compose example, environment variable table, and notes.
|
||||
|
||||
```markdown
|
||||
## Deployment
|
||||
|
||||
### Docker Compose
|
||||
|
||||
The app ships as a single Docker image. It requires an external PostgreSQL database.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: fete
|
||||
POSTGRES_USER: fete
|
||||
POSTGRES_PASSWORD: changeme
|
||||
volumes:
|
||||
- fete-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U fete"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
app:
|
||||
image: gitea.example.com/user/fete:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
DATABASE_URL: jdbc:postgresql://db:5432/fete
|
||||
DATABASE_USERNAME: fete
|
||||
DATABASE_PASSWORD: changeme
|
||||
# MAX_ACTIVE_EVENTS: 100
|
||||
# UNSPLASH_API_KEY: your-key-here
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
fete-db:
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------------------|----------|-----------|------------------------------------------------|
|
||||
| `DATABASE_URL` | Yes | — | JDBC connection string for PostgreSQL |
|
||||
| `DATABASE_USERNAME` | Yes | — | Database username |
|
||||
| `DATABASE_PASSWORD` | Yes | — | Database password |
|
||||
| `MAX_ACTIVE_EVENTS` | No | Unlimited | Maximum number of simultaneously active events |
|
||||
| `UNSPLASH_API_KEY` | No | — | Unsplash API key for header image search |
|
||||
```
|
||||
|
||||
#### [x] 2. Add local development setup section to README
|
||||
**File**: `README.md`
|
||||
**Changes**: Extend the `## Development` section with database setup instructions.
|
||||
|
||||
```markdown
|
||||
### Local database setup
|
||||
|
||||
**Option A: Testcontainers (no external PostgreSQL needed)**
|
||||
|
||||
```bash
|
||||
cd backend && ./mvnw spring-boot:test-run
|
||||
```
|
||||
|
||||
This starts the app with a Testcontainers-managed PostgreSQL that is created and destroyed automatically.
|
||||
|
||||
**Option B: External PostgreSQL**
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
cp src/main/resources/application-local.properties.example \
|
||||
src/main/resources/application-local.properties
|
||||
# Edit application-local.properties if your PostgreSQL uses different credentials
|
||||
./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
```
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd frontend && npm run test:unit -- --run` — frontend tests still pass (no regression)
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] README docker-compose example is syntactically correct YAML
|
||||
- [ ] Environment variable table lists all five variables with correct Required/Default values
|
||||
- [ ] Local development section documents both Testcontainers and external PostgreSQL options
|
||||
- [ ] docker-compose startup: `docker compose up` starts app + postgres, `/actuator/health` returns `{"status":"UP"}`
|
||||
|
||||
**Implementation Note**: After completing this phase, all T-4 acceptance criteria should be met. Run the full verification checklist below.
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests:
|
||||
- `FeteProperties` — verify defaults (empty API key = disabled, maxActiveEvents=0 = unlimited)
|
||||
- No other new unit tests in T-4 — the infrastructure is verified by integration tests
|
||||
|
||||
### Integration Tests:
|
||||
- Existing `FeteApplicationTest.contextLoads()` — validates that Spring context starts with JPA + Liquibase + Testcontainers
|
||||
- Existing `FeteApplicationTest.healthEndpointReturns200()` — validates health check includes DB health
|
||||
- Existing `WebConfigTest` — validates SPA routing still works with JPA on classpath
|
||||
- ArchUnit rules — validate `FeteProperties`/`FetePropertiesConfig` in `config` adapter is properly isolated
|
||||
|
||||
### Manual Testing Steps:
|
||||
1. `cd backend && ./mvnw verify` — full backend pipeline green
|
||||
2. `cd frontend && npm run test:unit -- --run` — frontend unchanged
|
||||
3. `docker build .` — image builds successfully
|
||||
4. docker-compose (app + postgres) — start, wait for health, verify `/actuator/health` returns UP
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Testcontainers PostgreSQL startup adds ~3-5 seconds to backend test execution. This is acceptable for integration tests.
|
||||
- Testcontainers reuses the container across all `@SpringBootTest` classes in a single Maven run (Spring's test context caching).
|
||||
- The empty baseline changeset adds negligible startup time.
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- **Existing tests**: `FeteApplicationTest` and `WebConfigTest` need `@Import(TestcontainersConfig.class)` — without it, they fail because JPA requires a datasource.
|
||||
- **CI pipeline**: `./mvnw -B verify` now requires Docker for Testcontainers. Gitea Actions `ubuntu-latest` runners have Docker available. If the runner uses Docker-in-Docker, `DOCKER_HOST` may need configuration — verify after implementation.
|
||||
- **Local development**: Developers now need either Docker (for Testcontainers via `./mvnw spring-boot:test-run`) or a local PostgreSQL (with `application-local.properties`).
|
||||
|
||||
## References
|
||||
|
||||
- Research: `docs/agents/research/2026-03-04-t4-development-infrastructure.md`
|
||||
- T-4 spec: `spec/setup-tasks.md` (lines 79-98)
|
||||
- Spring Boot Testcontainers: `@ServiceConnection` pattern (Spring Boot 3.1+)
|
||||
- Liquibase Spring Boot integration: auto-configured when `liquibase-core` is on classpath
|
||||
Reference in New Issue
Block a user