Skip to content
Merged
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
11 changes: 6 additions & 5 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ reviews:
- Separate entities (@Entity) from DTOs
- Verify proper Lombok annotations
- Check JPA annotations for entities (@Table, @Column, @Id, etc.)
- Ensure validation annotations on DTOs (@NotBlank, @ISBN, @URL)
- Validate proper use of LocalDate converter for SQLite
- Ensure validation annotations on DTOs (@NotBlank, @Past, @Positive, @URL)
- Validate proper use of IsoDateConverter for SQLite (ISO-8601 TEXT format)

- path: "src/main/java/**/Application.java"
instructions: |
Expand All @@ -84,13 +84,14 @@ reviews:
- Test classes should use JUnit 5 (Jupiter)
- Verify proper Spring test annotations (@WebMvcTest, @DataJpaTest, etc.)
- Check use of @MockitoBean (Spring Boot 4.0 style)
- Ensure test naming follows given_when_then pattern
- Ensure test naming follows method_scenario_outcome pattern (concise)
- Validate BDD semantics in JavaDoc comments (Given/When/Then)
- Validate use of AssertJ for fluent assertions
- Check @DisplayName for readable test descriptions
- Ensure @AutoConfigureCache when testing cached operations
- Verify test data uses fake factories (BookFakes, BookDTOFakes)
- Verify test data uses fake factories (PlayerFakes, PlayerDTOFakes)

- path: "src/test/java/**/BookFakes.java"
- path: "src/test/java/**/PlayerFakes.java"
instructions: |
- Verify test data factory pattern
- Check consistency of test data generation
Expand Down
28 changes: 28 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,34 @@
- Example: `docs: optimize AI agent instructions for token efficiency (#259)`
- Types: `feat`, `fix`, `chore`, `docs`, `test`, `refactor`

## Test Naming Convention

**Pattern**: `method_scenario_outcome`

- **method**: The method being tested (e.g., `post`, `findById`, `create`)
- **scenario**: The context or condition (e.g., `playerExists`, `invalidData`, `noMatches`)
- **outcome**: The expected result (e.g., `returnsPlayer`, `returnsConflict`, `returnsEmpty`)

**Examples**:
```java
// Controller: post_squadNumberExists_returnsConflict()
// Service: create_noConflict_returnsPlayerDTO()
// Repository: findById_playerExists_returnsPlayer()
```

**JavaDoc**: Use proper BDD (Given/When/Then) structure in comments:
```java
/**
* Given a player with squad number 5 already exists in the database
* When POST /players is called with a new player using squad number 5
* Then response status is 409 Conflict
*/
@Test
void post_squadNumberExists_returnsConflict() { ... }
```

**Benefits**: Concise method names for IDE test runners, full BDD context in JavaDoc for code readability.

## Architecture at a Glance

```
Expand Down
56 changes: 56 additions & 0 deletions .vscode/java-formatter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="13">
<profile kind="CodeFormatterProfile" name="Spring Boot 127 Column" version="13">
<!-- Line wrapping -->
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="127" />
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="127" />

<!-- Indentation -->
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space" />
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4" />
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4" />
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations"
value="false" />

<!-- Blank lines -->
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0" />
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1" />
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1" />
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1" />
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1" />

<!-- Braces -->
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration"
value="end_of_line" />
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration"
value="end_of_line" />
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line" />
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration"
value="end_of_line" />

<!-- Comments (preserve formatting) -->
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true" />
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false" />
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false" />
<setting
id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments"
value="true" />

<!-- New lines -->
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter"
value="do not insert" />
<setting
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable"
value="insert" />
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field"
value="insert" />
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method"
value="insert" />
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type"
value="insert" />

<!-- Join wrapped lines -->
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false" />
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false" />
</profile>
</profiles>
9 changes: 8 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
{
"editor.rulers": [80],
"editor.rulers": [
{
"column": 127
}
],
"editor.wordWrap": "off",
"editor.wordWrapColumn": 127,
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.detectIndentation": false,
Expand All @@ -8,6 +14,7 @@
"editor.defaultFormatter": "redhat.java",
"editor.inlayHints.enabled": "off"
},
"java.format.settings.url": ".vscode/java-formatter.xml",
"java.configuration.updateBuildConfiguration": "automatic",
"java.compile.nullAnalysis.mode": "automatic",
"sonarlint.connectedMode.project": {
Expand Down
126 changes: 73 additions & 53 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ Maven wrapper (`./mvnw`) is included, so Maven installation is optional.
open target/site/jacoco/index.html

# Run specific test class
./mvnw test -Dtest=BooksControllerTest
./mvnw test -Dtest=PlayersControllerTests

# Run specific test method
./mvnw test -Dtest=BooksControllerTest#testGetAllBooks
./mvnw test -Dtest=PlayersControllerTests#getAll_playersExist_returnsOkWithAllPlayers

# Run tests without rebuilding
./mvnw surefire:test
Expand Down Expand Up @@ -99,25 +99,25 @@ java -jar target/java.samples.spring.boot-*.jar

### Database Management

This project uses **H2 in-memory database for tests** and **SQLite for runtime**.
This project uses **SQLite in-memory database for tests** and **SQLite for runtime**.

**Runtime (SQLite)**:

```bash
# Database auto-initializes on first startup
# Pre-seeded database ships in storage/books-sqlite3.db
# Pre-seeded database ships in storage/players-sqlite3.db

# To reset database to seed state
rm storage/books-sqlite3.db
rm storage/players-sqlite3.db
# WARNING: spring.jpa.hibernate.ddl-auto=none disables schema generation
# Deleting the DB will cause startup failure - restore from backup or manually reinitialize

# Database location: storage/books-sqlite3.db
# Database location: storage/players-sqlite3.db
```

**Tests (H2)**:
**Tests (SQLite)**:

- In-memory database per test run
- In-memory database per test run (jdbc:sqlite::memory:)
- Automatically cleared after each test
- Configuration in `src/test/resources/application.properties`

Expand Down Expand Up @@ -181,34 +181,37 @@ src/main/java/ar/com/nanotaboada/java/samples/spring/boot/
├── Application.java # @SpringBootApplication entry point

├── controllers/ # REST endpoints
│ └── BooksController.java # @RestController, OpenAPI annotations
│ └── PlayersController.java # @RestController, OpenAPI annotations

├── services/ # Business logic
│ └── BooksService.java # @Service, @Cacheable
│ └── PlayersService.java # @Service, @Cacheable

├── repositories/ # Data access
│ └── BooksRepository.java # @Repository, Spring Data JPA
│ └── PlayersRepository.java # @Repository, Spring Data JPA

└── models/ # Domain models
├── Book.java # @Entity, JPA model
├── BookDTO.java # Data Transfer Object, validation
└── UnixTimestampConverter.java # JPA converter
├── models/ # Domain models
│ ├── Player.java # @Entity, JPA model
│ └── PlayerDTO.java # Data Transfer Object, validation

└── converters/ # Infrastructure converters
└── IsoDateConverter.java # JPA converter for ISO-8601 dates

src/test/java/ # Test classes
├── BooksControllerTest.java
├── BooksServiceTest.java
└── BooksRepositoryTest.java
├── PlayersControllerTests.java
├── PlayersServiceTests.java
└── PlayersRepositoryTests.java
```

**Key patterns**:

- Spring Boot 4 with Spring MVC
- Spring Data JPA for database operations
- Custom validation annotations for ISBN and URL
- Custom validation annotations for PlayerDTO
- OpenAPI 3.0 annotations for Swagger docs
- `@Cacheable` for in-memory caching
- DTOs with Bean Validation (JSR-380)
- Actuator for health monitoring and metrics
- JPA derived queries and custom JPQL examples
- Maven multi-module support ready

## API Endpoints
Expand All @@ -217,11 +220,13 @@ src/test/java/ # Test classes

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/books` | Get all books |
| `GET` | `/books/{id}` | Get book by ID |
| `POST` | `/books` | Create new book |
| `PUT` | `/books/{id}` | Update book |
| `DELETE` | `/books/{id}` | Delete book |
| `GET` | `/players` | Get all players |
| `GET` | `/players/{id}` | Get player by ID |
| `GET` | `/players/search/league/{league}` | Search players by league |
| `GET` | `/players/search/squadnumber/{squadNumber}` | Get player by squad number |
| `POST` | `/players` | Create new player |
| `PUT` | `/players/{id}` | Update player |
| `DELETE` | `/players/{id}` | Delete player |
| `GET` | `/actuator/health` | Health check |
| `GET` | `/swagger-ui.html` | API documentation |

Expand Down Expand Up @@ -265,7 +270,7 @@ java --version # Should be 25.x
pkill -f "spring-boot:run"

# Reset database
rm storage/books.db
rm storage/players-sqlite3.db
```

### Test failures
Expand All @@ -275,7 +280,7 @@ rm storage/books.db
./mvnw test -X

# Run single test for debugging
./mvnw test -Dtest=BooksControllerTest#testGetAllBooks -X
./mvnw test -Dtest=PlayersControllerTests#getAll_playersExist_returnsOkWithAllPlayers -X
```

### Maven wrapper issues
Expand Down Expand Up @@ -309,40 +314,53 @@ Open <http://localhost:8080/swagger-ui.html> - Interactive documentation with "T
# Health check
curl http://localhost:8080/actuator/health

# Get all books
curl http://localhost:8080/books
# Get all players
curl http://localhost:8080/players

# Get player by ID
curl http://localhost:8080/players/1

# Search players by league (Premier League)
curl http://localhost:8080/players/search/league/Premier

# Get book by ID
curl http://localhost:8080/books/1
# Get player by squad number (Messi #10)
curl http://localhost:8080/players/search/squadnumber/10

# Create book
curl -X POST http://localhost:8080/books \
# Create player
curl -X POST http://localhost:8080/players \
-H "Content-Type: application/json" \
-d '{
"isbn": "9780132350884",
"title": "Clean Code",
"author": "Robert C. Martin",
"published": 1217548800,
"pages": 464,
"description": "A Handbook of Agile Software Craftsmanship",
"website": "https://www.pearson.com/en-us/subject-catalog/p/clean-code-a-handbook-of-agile-software-craftsmanship/P200000009044"
"firstName": "Leandro",
"middleName": "Daniel",
"lastName": "Paredes",
"dateOfBirth": "1994-06-29",
"squadNumber": 5,
"position": "Defensive Midfield",
"abbrPosition": "DM",
"team": "AS Roma",
"league": "Serie A",
"starting11": false
}'

# Update book
curl -X PUT http://localhost:8080/books/1 \
# Update player
curl -X PUT http://localhost:8080/players/1 \
-H "Content-Type: application/json" \
-d '{
"isbn": "9780132350884",
"title": "Clean Code - Updated",
"author": "Robert C. Martin",
"published": 1217548800,
"pages": 464,
"description": "Updated description",
"website": "https://www.pearson.com/example"
"id": 1,
"firstName": "Emiliano",
"middleName": null,
"lastName": "Martínez",
"dateOfBirth": "1992-09-02",
"squadNumber": 23,
"position": "Goalkeeper",
"abbrPosition": "GK",
"team": "Aston Villa FC",
"league": "Premier League",
"starting11": true
}'

# Delete book
curl -X DELETE http://localhost:8080/books/1
# Delete player
curl -X DELETE http://localhost:8080/players/21
```

## Important Notes
Expand All @@ -353,8 +371,10 @@ curl -X DELETE http://localhost:8080/books/1
- **Java version**: Must use JDK 25 for consistency with CI/CD
- **Maven wrapper**: Always use `./mvnw` instead of `mvn` for consistency
- **Database**: SQLite is for demo/development only - not production-ready
- **H2 for tests**: Tests use in-memory H2, runtime uses SQLite
- **SQLite for tests**: Tests use in-memory SQLite (jdbc:sqlite::memory:), runtime uses file-based SQLite
- **OpenAPI annotations**: Required for all new endpoints (Swagger docs)
- **Caching**: Uses Spring's `@Cacheable` - clears on updates/deletes
- **Validation**: Custom ISBN and URL validators in BookDTO
- **Unix timestamps**: Published dates stored as Unix timestamps (seconds since epoch)
- **Validation**: Bean Validation (JSR-380) annotations in PlayerDTO
- **ISO-8601 dates**: Dates stored as ISO-8601 strings for SQLite compatibility
- **Search methods**: Demonstrates JPA derived queries (findBySquadNumber) and custom JPQL (findByLeagueContainingIgnoreCase)
- **Squad numbers**: Jersey numbers (natural key) separate from database IDs
Loading