diff --git a/.claude/commands/pre-commit.md b/.claude/commands/pre-commit.md
index f856769..4a5cad0 100644
--- a/.claude/commands/pre-commit.md
+++ b/.claude/commands/pre-commit.md
@@ -3,13 +3,12 @@ Before running the checklist, run `git fetch origin`. If the current branch is b
Run the pre-commit checklist for this project:
1. Update `CHANGELOG.md` `[Unreleased]` section — add an entry under the appropriate subsection (Added / Changed / Fixed / Removed) describing the changes made, referencing the issue number.
-2. Run `./mvnw clean install` — must succeed with no compilation warnings and all tests passing.
-3. Remind me to open `target/site/jacoco/index.html` to verify coverage after the build completes.
-4. If Docker is running, run `docker compose build` — must succeed with no
+2. Run `./mvnw clean install` — must succeed with no compilation warnings, all tests passing, and the JaCoCo check reporting `All coverage checks have been met.` (80% instruction and branch coverage enforced by the build).
+3. If Docker is running, run `docker compose build` — must succeed with no
errors. Skip this step with a note if Docker Desktop is not running.
-5. If `coderabbit` CLI is installed, run `coderabbit review --type uncommitted --prompt-only`:
+4. If `coderabbit` CLI is installed, run `coderabbit review --type uncommitted --prompt-only`:
- If actionable/serious findings are reported, stop and address them before proposing the commit.
- If only nitpick-level findings, report them and continue to the commit proposal.
- If `coderabbit` is not installed, skip this step with a note.
-Run steps 1–4, report the results clearly, then run step 5 (CodeRabbit review) if available, then propose a branch name and commit message for my approval using the format `type(scope): description (#issue)` (max 80 chars; types: `feat` `fix` `chore` `docs` `test` `refactor` `ci` `perf`). Do not create the branch or commit until I explicitly confirm.
+Run steps 1–3, report the results clearly, then run step 4 (CodeRabbit review) if available, then propose a branch name and commit message for my approval using the format `type(scope): description (#issue)` (max 80 chars; types: `feat` `fix` `chore` `docs` `test` `refactor` `ci` `perf`). Do not create the branch or commit until I explicitly confirm.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0627af7..c5ad73f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,23 @@ Release names follow the **historic football clubs** naming convention (A–Z):
## [Unreleased]
+### Changed
+
+- `Player` entity: `id` demoted from `@Id Long` to surrogate `UUID` (non-PK,
+ `updatable=false`, `unique`, generated via `@PrePersist`); `squadNumber`
+ promoted to `@Id Integer` (natural key) (#268)
+- `PlayerDTO`: `id` type changed from `Long` to `UUID` (#268)
+- `PlayersRepository`: keyed on `Integer` (squad number as PK); added
+ `findById(UUID)` overload for admin/internal lookup (#268)
+- `PlayersService`: `retrieveById` now accepts `UUID`; `update` keyed on
+ `Integer squadNumber`; `delete` renamed to `deleteBySquadNumber(Integer)` (#268)
+- `PUT /players/{squadNumber}` and `DELETE /players/{squadNumber}`: path
+ variable changed from `Long id` to `Integer squadNumber` (#268)
+- `GET /players/{id}`: path variable changed from `Long` to `UUID` (admin use) (#268)
+- `storage/players-sqlite3.db`: schema migrated to `id VARCHAR(36) NOT NULL UNIQUE`,
+ `squadNumber INTEGER PRIMARY KEY`; 25 players preserved with generated UUIDs (#268)
+- `ddl.sql` and `dml.sql`: test schema and seed data updated for new structure (#268)
+
### Added
- `.sonarcloud.properties`: SonarCloud Automatic Analysis configuration —
diff --git a/pom.xml b/pom.xml
index 122e6f0..2b5ba25 100644
--- a/pom.xml
+++ b/pom.xml
@@ -310,6 +310,31 @@
report
+
+ check
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.80
+
+
+ BRANCH
+ COVEREDRATIO
+ 0.80
+
+
+
+
+
+
diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.java
index 90f6418..979c218 100644
--- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.java
+++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/controllers/PlayersController.java
@@ -4,6 +4,7 @@
import java.net.URI;
import java.util.List;
+import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -37,12 +38,12 @@
*
Base Path:
*
* - GET {@code /players} - Retrieve all players
- * - GET {@code /players/{id}} - Retrieve player by ID
+ * - GET {@code /players/{id}} - Retrieve player by UUID (admin/internal use)
* - GET {@code /players/search/league/{league}} - Search players by league name
- * - GET {@code /players/squadnumber/{number}} - Retrieve player by squad number
+ * - GET {@code /players/squadnumber/{squadNumber}} - Retrieve player by squad number
* - POST {@code /players} - Create a new player
- * - PUT {@code /players/{id}} - Update an existing player
- * - DELETE {@code /players/{id}} - Delete a player by ID
+ * - PUT {@code /players/{squadNumber}} - Update an existing player by squad number
+ * - DELETE {@code /players/{squadNumber}} - Delete a player by squad number
*
*
* Response Codes:
@@ -74,12 +75,8 @@ public class PlayersController {
/**
* Creates a new player resource.
*
- * Validates the request body and creates a new player in the database. Returns a 201 Created response with a Location
- * header pointing to the new resource.
- *
- *
- * Conflict Detection: If a player with the same squad number already exists, returns 409 Conflict.
- * Squad numbers must be unique (jersey numbers like Messi's #10).
+ * Validates the request body and creates a new player. Returns 201 Created with a Location
+ * header pointing to the new resource (UUID-based path).
*
*
* @param playerDTO the player data to create (validated with JSR-380 constraints)
@@ -115,9 +112,6 @@ public ResponseEntity post(@RequestBody @Valid PlayerDTO playerDTO) {
/**
* Retrieves all players in the squad.
- *
- * Returns the complete Argentina 2022 FIFA World Cup squad (26 players).
- *
*
* @return 200 OK with array of all players (empty array if none found)
*/
@@ -132,18 +126,18 @@ public ResponseEntity> getAll() {
}
/**
- * Retrieves a single player by their unique identifier.
+ * Retrieves a single player by their surrogate UUID (admin/internal use only).
*
- * @param id the unique identifier of the player
+ * @param id the UUID surrogate key of the player
* @return 200 OK with player data, or 404 Not Found if player doesn't exist
*/
@GetMapping("/players/{id}")
- @Operation(summary = "Retrieves a player by ID")
+ @Operation(summary = "Retrieves a player by UUID (admin/internal use)")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "OK", content = @Content(mediaType = "application/json", schema = @Schema(implementation = PlayerDTO.class))),
@ApiResponse(responseCode = "404", description = "Not Found", content = @Content)
})
- public ResponseEntity getById(@PathVariable Long id) {
+ public ResponseEntity getById(@PathVariable UUID id) {
PlayerDTO playerDTO = playersService.retrieveById(id);
return (playerDTO != null)
? ResponseEntity.status(HttpStatus.OK).body(playerDTO)
@@ -151,9 +145,8 @@ public ResponseEntity getById(@PathVariable Long id) {
}
/**
- * Retrieves a player by their squad number (unique identifier).
+ * Retrieves a player by their squad number.
*
- * Squad numbers are unique jersey numbers (e.g., Messi is #10). This is a direct lookup similar to getById().
* Example: {@code /players/squadnumber/10} returns Lionel Messi
*
*
@@ -199,30 +192,29 @@ public ResponseEntity> searchByLeague(@PathVariable String leagu
*/
/**
- * Updates an existing player resource (full update).
+ * Updates an existing player resource (full update) identified by squad number.
*
- * Performs a complete replacement of the player entity. The ID in the path must match the ID in the request body.
+ * Performs a complete replacement of the player entity. The squad number in the path
+ * must match the squad number in the request body (if provided).
*
*
- * @param id the unique identifier of the player to update
+ * @param squadNumber the squad number (natural key) of the player to update
* @param playerDTO the complete player data (must pass validation)
- * @return 204 No Content if successful, 404 Not Found if player doesn't exist, or 400 Bad Request if validation fails or
- * ID mismatch
+ * @return 204 No Content if successful, 404 Not Found if player doesn't exist, or 400 Bad Request if validation fails
*/
- @PutMapping("/players/{id}")
- @Operation(summary = "Updates (entirely) a player by ID")
+ @PutMapping("/players/{squadNumber}")
+ @Operation(summary = "Updates (entirely) a player by squad number")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "No Content", content = @Content),
@ApiResponse(responseCode = "400", description = "Bad Request", content = @Content),
@ApiResponse(responseCode = "404", description = "Not Found", content = @Content)
})
- public ResponseEntity put(@PathVariable Long id, @RequestBody @Valid PlayerDTO playerDTO) {
- // Ensure path ID matches body ID
- if (playerDTO.getId() != null && !playerDTO.getId().equals(id)) {
+ public ResponseEntity put(@PathVariable Integer squadNumber, @RequestBody @Valid PlayerDTO playerDTO) {
+ if (playerDTO.getSquadNumber() != null && !playerDTO.getSquadNumber().equals(squadNumber)) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
- playerDTO.setId(id); // Set ID from path to ensure consistency
- boolean updated = playersService.update(playerDTO);
+ playerDTO.setSquadNumber(squadNumber);
+ boolean updated = playersService.update(squadNumber, playerDTO);
return (updated)
? ResponseEntity.status(HttpStatus.NO_CONTENT).build()
: ResponseEntity.status(HttpStatus.NOT_FOUND).build();
@@ -235,19 +227,19 @@ public ResponseEntity put(@PathVariable Long id, @RequestBody @Valid Playe
*/
/**
- * Deletes a player resource by their unique identifier.
+ * Deletes a player resource by their squad number.
*
- * @param id the unique identifier of the player to delete
+ * @param squadNumber the squad number of the player to delete
* @return 204 No Content if successful, or 404 Not Found if player doesn't exist
*/
- @DeleteMapping("/players/{id}")
- @Operation(summary = "Deletes a player by ID")
+ @DeleteMapping("/players/{squadNumber}")
+ @Operation(summary = "Deletes a player by squad number")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "No Content", content = @Content),
@ApiResponse(responseCode = "404", description = "Not Found", content = @Content)
})
- public ResponseEntity delete(@PathVariable Long id) {
- boolean deleted = playersService.delete(id);
+ public ResponseEntity delete(@PathVariable Integer squadNumber) {
+ boolean deleted = playersService.deleteBySquadNumber(squadNumber);
return (deleted)
? ResponseEntity.status(HttpStatus.NO_CONTENT).build()
: ResponseEntity.status(HttpStatus.NOT_FOUND).build();
diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/Player.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/Player.java
index 1a47dbb..89400e1 100644
--- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/Player.java
+++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/Player.java
@@ -1,6 +1,7 @@
package ar.com.nanotaboada.java.samples.spring.boot.models;
import java.time.LocalDate;
+import java.util.UUID;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@@ -28,9 +29,9 @@
*
* Key Features:
*
- * - Auto-generated ID using IDENTITY strategy
- * - ISO-8601 date storage for SQLite compatibility
- * ({@link IsoDateConverter})
+ * - UUID primary key — generated at application level via {@code GenerationType.UUID}
+ * - Squad number natural key — unique domain identifier, used as path variable for mutations
+ * - ISO-8601 date storage for SQLite compatibility ({@link IsoDateConverter})
* - JSON serialization support for LocalDate fields
*
*
@@ -44,9 +45,22 @@
@NoArgsConstructor
@AllArgsConstructor
public class Player {
+
+ /**
+ * Primary key — UUID generated at application level.
+ */
@Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
+ @GeneratedValue(strategy = GenerationType.UUID)
+ @Column(name = "id", nullable = false, updatable = false, columnDefinition = "VARCHAR(36)")
+ private UUID id;
+
+ /**
+ * Natural key — unique domain identifier, path variable for PUT and DELETE.
+ * Squad number (jersey number) is unique per team and stable.
+ */
+ @Column(name = "squadNumber", nullable = false, unique = true, updatable = false)
+ private Integer squadNumber;
+
private String firstName;
private String middleName;
private String lastName;
@@ -59,16 +73,6 @@ public class Player {
@Convert(converter = IsoDateConverter.class)
private LocalDate dateOfBirth;
- /**
- * Squad number (jersey number) - unique natural key.
- *
- * Used for player lookups via /players/search/squadnumber/{squadNumber}.
- * Database constraint enforces uniqueness.
- *
- */
- @Column(unique = true)
- private Integer squadNumber;
-
private String position;
private String abbrPosition;
private String team;
diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerDTO.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerDTO.java
index 40fede9..00181e8 100644
--- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerDTO.java
+++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/PlayerDTO.java
@@ -7,6 +7,8 @@
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import java.util.UUID;
+
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
@@ -46,7 +48,7 @@
*/
@Data
public class PlayerDTO {
- private Long id;
+ private UUID id;
@NotBlank
private String firstName;
private String middleName;
diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/PlayersRepository.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/PlayersRepository.java
index 34a281b..35f98a6 100644
--- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/PlayersRepository.java
+++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/PlayersRepository.java
@@ -2,6 +2,7 @@
import java.util.List;
import java.util.Optional;
+import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@@ -12,56 +13,39 @@
* Spring Data JPA Repository for {@link Player} entities.
*
* Provides data access methods for the {@code players} table using Spring Data's repository abstraction.
- * Extends {@link JpaRepository} for CRUD operations, batch operations, and query methods.
+ * Extends {@link JpaRepository} for CRUD operations keyed on the UUID primary key.
*
*
* Provided Methods:
*
- * - Inherited from JpaRepository: save, findAll (returns List), findById, delete, flush, etc.
- * - Custom Query Methods: League search with case-insensitive wildcard matching
- * - Derived Queries: findBySquadNumber (method name conventions)
- *
- *
- * Query Strategies:
- *
- * - Derived Queries: Spring Data derives queries from method names (findBySquadNumber,
- * findByLeagueContainingIgnoreCase)
+ * - Inherited from JpaRepository: save, findAll, findById(UUID), existsById, deleteById, etc.
+ * - Derived Queries: findBySquadNumber, findByLeagueContainingIgnoreCase
*
*
* @see Player
* @see org.springframework.data.jpa.repository.JpaRepository
- * @see Query
- * Creation from Method Names
* @since 4.0.2025
*/
@Repository
-public interface PlayersRepository extends JpaRepository {
+public interface PlayersRepository extends JpaRepository {
/**
* Finds a player by their squad number (exact match).
*
- * This is a derived query method - Spring Data JPA generates the query automatically.
- * Squad numbers are jersey numbers that users recognize (e.g., Messi is #10).
- * This demonstrates Spring Data's method name query derivation with a natural key.
+ * Squad numbers are unique jersey numbers (e.g., Messi is #10).
+ * Used as the natural key for mutation endpoints (PUT, DELETE).
*
*
- * @param squadNumber the squad number to search for (jersey number, typically
- * 1-99)
+ * @param squadNumber the squad number to search for (jersey number, typically 1-99)
* @return an Optional containing the player if found, empty Optional otherwise
*/
Optional findBySquadNumber(Integer squadNumber);
/**
* Finds players by league name using case-insensitive wildcard matching.
- *
- * This method uses Spring Data's derived query mechanism to perform partial matching.
- * For example, searching for "Premier" will match "Premier League".
- *
*
* @param league the league name to search for (partial matches allowed)
- * @return a list of players whose league name contains the search term (empty
- * list if none found)
+ * @return a list of players whose league name contains the search term
*/
List findByLeagueContainingIgnoreCase(String league);
}
diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java
index 763b501..e9c3fb7 100644
--- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java
+++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/services/PlayersService.java
@@ -1,6 +1,7 @@
package ar.com.nanotaboada.java.samples.spring.boot.services;
import java.util.List;
+import java.util.UUID;
import org.modelmapper.ModelMapper;
import org.springframework.cache.annotation.CacheEvict;
@@ -65,29 +66,26 @@ public class PlayersService {
/**
* Creates a new player and stores it in the database.
*
- * This method converts the PlayerDTO to a Player entity, persists it, and returns the saved player with its auto-generated
- * ID. The result is automatically cached using the player's ID as the cache key.
+ * Converts the PlayerDTO to a Player entity, persists it (UUID generated via
+ * {@code GenerationType.UUID}), and returns the saved player with its assigned UUID.
*
*
- * Conflict Detection: Checks if a player with the same squad number already exists (optimization).
- * Squad numbers are unique identifiers (jersey numbers). Database constraint ensures uniqueness even under
- * concurrent operations. If a race condition occurs between check and save, DataIntegrityViolationException
- * is caught and null is returned to indicate conflict.
+ * Conflict Detection: Checks if a player with the same squad number already exists.
+ * If a race condition occurs between check and save, DataIntegrityViolationException is caught
+ * and null is returned to indicate conflict.
*
*
* @param playerDTO the player data to create (must not be null)
- * @return the created player with auto-generated ID, or null if squad number already exists
- * @see org.springframework.cache.annotation.CacheEvict
+ * @return the created player with generated UUID, or null if squad number already exists
*/
@Transactional
@CacheEvict(value = "players", allEntries = true)
public PlayerDTO create(PlayerDTO playerDTO) {
log.debug("Creating new player with squad number: {}", playerDTO.getSquadNumber());
- // Check if squad number already exists (optimization to avoid unnecessary DB write)
if (playersRepository.findBySquadNumber(playerDTO.getSquadNumber()).isPresent()) {
log.warn("Cannot create player - squad number {} already exists", playerDTO.getSquadNumber());
- return null; // Conflict: squad number already taken
+ return null;
}
try {
@@ -97,8 +95,6 @@ public PlayerDTO create(PlayerDTO playerDTO) {
log.info("Player created successfully - ID: {}, Squad Number: {}", result.getId(), result.getSquadNumber());
return result;
} catch (DataIntegrityViolationException _) {
- // Handle race condition: concurrent request created player with same squad number
- // between our check and save operation
log.warn("Cannot create player - squad number {} already exists (race condition)", playerDTO.getSquadNumber());
return null;
}
@@ -112,13 +108,8 @@ public PlayerDTO create(PlayerDTO playerDTO) {
/**
* Retrieves all players from the database.
- *
- * This method returns the complete Argentina 2022 FIFA World Cup squad (26 players).
- * Results are cached to improve performance on subsequent calls.
- *
*
* @return a list of all players (empty list if none found)
- * @see org.springframework.cache.annotation.Cacheable
*/
@Cacheable(value = "players")
public List retrieveAll() {
@@ -129,34 +120,29 @@ public List retrieveAll() {
}
/**
- * Retrieves a player by their unique identifier.
+ * Retrieves a player by their UUID primary key.
*
- * This method uses caching to improve performance. If the player is found in the cache, it will be returned without
- * hitting the database. Otherwise, it queries the database and caches the result.
- * Null results (player not found) are not cached to avoid serving stale misses.
+ * Uses caching to improve performance. Null results are not cached.
*
*
- * @param id the unique identifier of the player (must not be null)
+ * @param id the UUID primary key (must not be null)
* @return the player DTO if found, null otherwise
- * @see org.springframework.cache.annotation.Cacheable
*/
@Cacheable(value = "players", key = "#id", unless = "#result == null")
- public PlayerDTO retrieveById(Long id) {
+ public PlayerDTO retrieveById(UUID id) {
return playersRepository.findById(id)
.map(this::mapFrom)
.orElse(null);
}
/**
- * Retrieves a player by their squad number (unique identifier).
+ * Retrieves a player by their squad number.
*
- * Squad numbers are unique jersey numbers (e.g., Messi is #10). This is a direct lookup by unique identifier,
- * similar to retrieveById(). Results are cached to improve performance.
+ * Squad numbers are unique jersey numbers (e.g., Messi is #10). Results are cached.
*
*
* @param squadNumber the squad number to retrieve (jersey number, typically 1-99)
* @return the player DTO if found, null otherwise
- * @see org.springframework.cache.annotation.Cacheable
*/
@Cacheable(value = "players", key = "'squad-' + #squadNumber", unless = "#result == null")
public PlayerDTO retrieveBySquadNumber(Integer squadNumber) {
@@ -173,10 +159,6 @@ public PlayerDTO retrieveBySquadNumber(Integer squadNumber) {
/**
* Searches for players by league name (case-insensitive, partial match).
- *
- * This method performs a wildcard search on the league field, matching any player whose league name contains the search
- * term (e.g., "Premier" matches "Premier League").
- *
*
* @param league the league name to search for (must not be null or blank)
* @return a list of matching players (empty list if none found)
@@ -195,30 +177,33 @@ public List searchByLeague(String league) {
*/
/**
- * Updates an existing player's information.
+ * Updates an existing player identified by their squad number.
*
- * This method performs a full update (PUT semantics) of the player entity. If the player exists, it updates all fields and
- * refreshes the cache. If the player doesn't exist, returns false without making changes.
+ * Looks up the existing player by squad number to retrieve the UUID primary key,
+ * maps the DTO to an entity, preserves the UUID, and saves. Returns false if not found.
*
*
- * @param playerDTO the player data to update (must include a valid ID)
+ * @param squadNumber the squad number (natural key) of the player to update
+ * @param playerDTO the player data to update
* @return true if the player was updated successfully, false if not found
- * @see org.springframework.cache.annotation.CacheEvict
*/
@Transactional
@CacheEvict(value = "players", allEntries = true)
- public boolean update(PlayerDTO playerDTO) {
- log.debug("Updating player with ID: {}", playerDTO.getId());
+ public boolean update(Integer squadNumber, PlayerDTO playerDTO) {
+ log.debug("Updating player with squad number: {}", squadNumber);
- if (playerDTO.getId() != null && playersRepository.existsById(playerDTO.getId())) {
- Player player = mapFrom(playerDTO);
- playersRepository.save(player);
- log.info("Player updated successfully - ID: {}", playerDTO.getId());
- return true;
- } else {
- log.warn("Cannot update player - ID {} not found", playerDTO.getId());
- return false;
- }
+ return playersRepository.findBySquadNumber(squadNumber)
+ .map(existing -> {
+ Player player = mapFrom(playerDTO);
+ player.setId(existing.getId());
+ playersRepository.save(player);
+ log.info("Player updated successfully - Squad Number: {}", squadNumber);
+ return true;
+ })
+ .orElseGet(() -> {
+ log.warn("Cannot update player - squad number {} not found", squadNumber);
+ return false;
+ });
}
/*
@@ -228,29 +213,30 @@ public boolean update(PlayerDTO playerDTO) {
*/
/**
- * Deletes a player by their unique identifier.
+ * Deletes a player by their squad number.
*
- * This method removes the player from the database and evicts it from the cache. If the player doesn't exist, returns
- * false without making changes.
+ * Looks up the player by squad number to retrieve the UUID primary key, then deletes by UUID.
+ * Returns false if the player doesn't exist.
*
*
- * @param id the unique identifier of the player to delete (must not be null)
+ * @param squadNumber the squad number of the player to delete (must not be null)
* @return true if the player was deleted successfully, false if not found
- * @see org.springframework.cache.annotation.CacheEvict
*/
@Transactional
@CacheEvict(value = "players", allEntries = true)
- public boolean delete(Long id) {
- log.debug("Deleting player with ID: {}", id);
+ public boolean deleteBySquadNumber(Integer squadNumber) {
+ log.debug("Deleting player with squad number: {}", squadNumber);
- if (playersRepository.existsById(id)) {
- playersRepository.deleteById(id);
- log.info("Player deleted successfully - ID: {}", id);
- return true;
- } else {
- log.warn("Cannot delete player - ID {} not found", id);
- return false;
- }
+ return playersRepository.findBySquadNumber(squadNumber)
+ .map(existing -> {
+ playersRepository.deleteById(existing.getId());
+ log.info("Player deleted successfully - Squad Number: {}", squadNumber);
+ return true;
+ })
+ .orElseGet(() -> {
+ log.warn("Cannot delete player - squad number {} not found", squadNumber);
+ return false;
+ });
}
private PlayerDTO mapFrom(Player player) {
diff --git a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerDTOFakes.java b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerDTOFakes.java
index 8cadd71..195300f 100644
--- a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerDTOFakes.java
+++ b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerDTOFakes.java
@@ -3,6 +3,7 @@
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
import ar.com.nanotaboada.java.samples.spring.boot.models.PlayerDTO;
@@ -12,6 +13,10 @@ private PlayerDTOFakes() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
+ private static UUID uuid(int index) {
+ return UUID.fromString(String.format("00000000-0000-0000-0000-%012d", index));
+ }
+
/**
* Leandro Paredes - Test data for CREATE operations
*
@@ -19,16 +24,16 @@ private PlayerDTOFakes() {
* - Service tests: Mock expected data for playersService.create()
* - Controller tests: Mock expected data for POST /players
*
- * Note: Not pre-seeded in test DB (ID 19 slot is empty)
+ * Note: Not pre-seeded in test DB (squad number 5 slot is empty)
*/
public static PlayerDTO createOneValid() {
PlayerDTO playerDTO = new PlayerDTO();
- playerDTO.setId(null); // Will be auto-generated as 19
+ playerDTO.setId(null); // Will be generated by @PrePersist
+ playerDTO.setSquadNumber(5);
playerDTO.setFirstName("Leandro");
playerDTO.setMiddleName("Daniel");
playerDTO.setLastName("Paredes");
playerDTO.setDateOfBirth(LocalDate.of(1994, 6, 29));
- playerDTO.setSquadNumber(5);
playerDTO.setPosition("Defensive Midfield");
playerDTO.setAbbrPosition("DM");
playerDTO.setTeam("AS Roma");
@@ -38,22 +43,20 @@ public static PlayerDTO createOneValid() {
}
/**
- * Damián Martínez - Player ID 1 BEFORE update
+ * Damián Martínez - Player squad number 23 BEFORE update
*
* Usage:
- * - Service tests: Mock expected data for playersService.retrieveById(1L)
- * - Controller tests: Mock expected data for GET /players/1
- *
- * Note: Repository tests query DB directly (pre-seeded in dml.sql)
+ * - Service tests: Mock expected data for playersService.retrieveById(uuid)
+ * - Controller tests: Mock expected data for GET /players/{id}
*/
public static PlayerDTO createOneForUpdate() {
PlayerDTO playerDTO = new PlayerDTO();
- playerDTO.setId(1L);
+ playerDTO.setId(uuid(1));
+ playerDTO.setSquadNumber(23);
playerDTO.setFirstName("Damián");
playerDTO.setMiddleName("Emiliano");
playerDTO.setLastName("Martínez");
playerDTO.setDateOfBirth(LocalDate.of(1992, 9, 2));
- playerDTO.setSquadNumber(23);
playerDTO.setPosition("Goalkeeper");
playerDTO.setAbbrPosition("GK");
playerDTO.setTeam("Aston Villa FC");
@@ -63,11 +66,7 @@ public static PlayerDTO createOneForUpdate() {
}
/**
- * Emiliano Martínez - Expected result AFTER updating player ID 1
- *
- * Usage:
- * - Service tests: Mock expected data after playersService.update()
- * - Controller tests: Mock expected data after PUT /players/1
+ * Emiliano Martínez - Expected result AFTER updating player squad number 23
*
* Update changes:
* - firstName: "Damián" → "Emiliano"
@@ -75,12 +74,12 @@ public static PlayerDTO createOneForUpdate() {
*/
public static PlayerDTO createOneUpdated() {
PlayerDTO playerDTO = new PlayerDTO();
- playerDTO.setId(1L);
+ playerDTO.setId(uuid(1));
+ playerDTO.setSquadNumber(23);
playerDTO.setFirstName("Emiliano");
playerDTO.setMiddleName(null);
playerDTO.setLastName("Martínez");
playerDTO.setDateOfBirth(LocalDate.of(1992, 9, 2));
- playerDTO.setSquadNumber(23);
playerDTO.setPosition("Goalkeeper");
playerDTO.setAbbrPosition("GK");
playerDTO.setTeam("Aston Villa FC");
@@ -92,20 +91,16 @@ public static PlayerDTO createOneUpdated() {
/**
* Invalid player data - Test data for validation failure scenarios
*
- * Usage:
- * - Controller tests: Verify validation annotations work
- * (@NotBlank, @Past, @Positive)
- *
* Violations: blank names, future date, negative squad number, blank fields
*/
public static PlayerDTO createOneInvalid() {
PlayerDTO playerDTO = new PlayerDTO();
- playerDTO.setId(999L);
+ playerDTO.setId(null);
+ playerDTO.setSquadNumber(-1); // Invalid (must be positive)
playerDTO.setFirstName(""); // Invalid (blank)
playerDTO.setMiddleName(null);
playerDTO.setLastName(""); // Invalid (blank)
playerDTO.setDateOfBirth(LocalDate.now()); // Invalid (must be a past date)
- playerDTO.setSquadNumber(-1); // Invalid (must be positive)
playerDTO.setPosition(""); // Invalid (blank)
playerDTO.setAbbrPosition(null);
playerDTO.setTeam(""); // Invalid (blank)
@@ -116,87 +111,50 @@ public static PlayerDTO createOneInvalid() {
/**
* ALL 26 players - Complete Argentina 2022 FIFA World Cup squad
- *
- * Usage:
- * - Service tests: Mock expected data for playersService.retrieveAll()
- * - Controller tests: Mock expected data for GET /players
- *
- * Includes:
- * - 25 players pre-seeded in test DB (IDs 1-26, excluding 19)
- * - Leandro Paredes (ID 19, created during tests)
- *
- * Note: Repository tests query real in-memory DB directly (25 players
- * pre-seeded)
*/
public static List createAll() {
return Arrays.asList(
// Starting 11
- createPlayerDTOWithId(1L, "Damián", "Emiliano", "Martínez", LocalDate.of(1992, 9, 2), 23, "Goalkeeper",
- "GK", "Aston Villa FC", "Premier League", true),
- createPlayerDTOWithId(2L, "Nahuel", null, "Molina", LocalDate.of(1998, 4, 6), 26, "Right-Back", "RB",
- "Atlético Madrid", "La Liga", true),
- createPlayerDTOWithId(3L, "Cristian", "Gabriel", "Romero", LocalDate.of(1998, 4, 27), 13, "Centre-Back",
- "CB", "Tottenham Hotspur", "Premier League", true),
- createPlayerDTOWithId(4L, "Nicolás", "Hernán Gonzalo", "Otamendi", LocalDate.of(1988, 2, 12), 19,
- "Centre-Back", "CB", "SL Benfica", "Liga Portugal", true),
- createPlayerDTOWithId(5L, "Nicolás", "Alejandro", "Tagliafico", LocalDate.of(1992, 8, 31), 3,
- "Left-Back", "LB", "Olympique Lyon", "Ligue 1", true),
- createPlayerDTOWithId(6L, "Ángel", "Fabián", "Di María", LocalDate.of(1988, 2, 14), 11, "Right Winger",
- "RW", "SL Benfica", "Liga Portugal", true),
- createPlayerDTOWithId(7L, "Rodrigo", "Javier", "de Paul", LocalDate.of(1994, 5, 24), 7,
- "Central Midfield", "CM", "Atlético Madrid", "La Liga", true),
- createPlayerDTOWithId(8L, "Enzo", "Jeremías", "Fernández", LocalDate.of(2001, 1, 17), 24,
- "Central Midfield", "CM", "Chelsea FC", "Premier League", true),
- createPlayerDTOWithId(9L, "Alexis", null, "Mac Allister", LocalDate.of(1998, 12, 24), 20,
- "Central Midfield", "CM", "Liverpool FC", "Premier League", true),
- createPlayerDTOWithId(10L, "Lionel", "Andrés", "Messi", LocalDate.of(1987, 6, 24), 10, "Right Winger",
- "RW", "Inter Miami CF", "Major League Soccer", true),
- createPlayerDTOWithId(11L, "Julián", null, "Álvarez", LocalDate.of(2000, 1, 31), 9, "Centre-Forward",
- "CF", "Manchester City", "Premier League", true),
+ createPlayerDTO(uuid(1), 23, "Damián", "Emiliano", "Martínez", LocalDate.of(1992, 9, 2), "Goalkeeper", "GK", "Aston Villa FC", "Premier League", true),
+ createPlayerDTO(uuid(2), 26, "Nahuel", null, "Molina", LocalDate.of(1998, 4, 6), "Right-Back", "RB", "Atlético Madrid", "La Liga", true),
+ createPlayerDTO(uuid(3), 13, "Cristian", "Gabriel", "Romero", LocalDate.of(1998, 4, 27), "Centre-Back", "CB", "Tottenham Hotspur", "Premier League", true),
+ createPlayerDTO(uuid(4), 19, "Nicolás", "Hernán Gonzalo", "Otamendi", LocalDate.of(1988, 2, 12), "Centre-Back", "CB", "SL Benfica", "Liga Portugal", true),
+ createPlayerDTO(uuid(5), 3, "Nicolás", "Alejandro", "Tagliafico", LocalDate.of(1992, 8, 31), "Left-Back", "LB", "Olympique Lyon", "Ligue 1", true),
+ createPlayerDTO(uuid(6), 11, "Ángel", "Fabián", "Di María", LocalDate.of(1988, 2, 14), "Right Winger", "RW", "SL Benfica", "Liga Portugal", true),
+ createPlayerDTO(uuid(7), 7, "Rodrigo", "Javier", "de Paul", LocalDate.of(1994, 5, 24), "Central Midfield", "CM", "Atlético Madrid", "La Liga", true),
+ createPlayerDTO(uuid(8), 24, "Enzo", "Jeremías", "Fernández", LocalDate.of(2001, 1, 17), "Central Midfield", "CM", "Chelsea FC", "Premier League", true),
+ createPlayerDTO(uuid(9), 20, "Alexis", null, "Mac Allister", LocalDate.of(1998, 12, 24), "Central Midfield", "CM", "Liverpool FC", "Premier League", true),
+ createPlayerDTO(uuid(10), 10, "Lionel", "Andrés", "Messi", LocalDate.of(1987, 6, 24), "Right Winger", "RW", "Inter Miami CF", "Major League Soccer", true),
+ createPlayerDTO(uuid(11), 9, "Julián", null, "Álvarez", LocalDate.of(2000, 1, 31), "Centre-Forward", "CF", "Manchester City", "Premier League", true),
// Substitutes
- createPlayerDTOWithId(12L, "Franco", "Daniel", "Armani", LocalDate.of(1986, 10, 16), 1, "Goalkeeper",
- "GK", "River Plate", "Copa de la Liga", false),
- createPlayerDTOWithId(13L, "Gerónimo", null, "Rulli", LocalDate.of(1992, 5, 20), 12, "Goalkeeper", "GK",
- "Ajax Amsterdam", "Eredivisie", false),
- createPlayerDTOWithId(14L, "Juan", "Marcos", "Foyth", LocalDate.of(1998, 1, 12), 2, "Right-Back", "RB",
- "Villarreal", "La Liga", false),
- createPlayerDTOWithId(15L, "Gonzalo", "Ariel", "Montiel", LocalDate.of(1997, 1, 1), 4, "Right-Back",
- "RB", "Nottingham Forest", "Premier League", false),
- createPlayerDTOWithId(16L, "Germán", "Alejo", "Pezzella", LocalDate.of(1991, 6, 27), 6, "Centre-Back",
- "CB", "Real Betis Balompié", "La Liga", false),
- createPlayerDTOWithId(17L, "Marcos", "Javier", "Acuña", LocalDate.of(1991, 10, 28), 8, "Left-Back",
- "LB", "Sevilla FC", "La Liga", false),
- createPlayerDTOWithId(18L, "Lisandro", null, "Martínez", LocalDate.of(1998, 1, 18), 25, "Centre-Back",
- "CB", "Manchester United", "Premier League", false),
- // Leandro Paredes (ID 19) - created during tests
- createPlayerDTOWithId(19L, "Leandro", "Daniel", "Paredes", LocalDate.of(1994, 6, 29), 5,
- "Defensive Midfield", "DM", "AS Roma", "Serie A", false),
- createPlayerDTOWithId(20L, "Exequiel", "Alejandro", "Palacios", LocalDate.of(1998, 10, 5), 14,
- "Central Midfield", "CM", "Bayer 04 Leverkusen", "Bundesliga", false),
- createPlayerDTOWithId(21L, "Alejandro", "Darío", "Gómez", LocalDate.of(1988, 2, 15), 17, "Left Winger",
- "LW", "AC Monza", "Serie A", false),
- createPlayerDTOWithId(22L, "Guido", null, "Rodríguez", LocalDate.of(1994, 4, 12), 18,
- "Defensive Midfield", "DM", "Real Betis Balompié", "La Liga", false),
- createPlayerDTOWithId(23L, "Ángel", "Martín", "Correa", LocalDate.of(1995, 3, 9), 15, "Right Winger",
- "RW", "Atlético Madrid", "La Liga", false),
- createPlayerDTOWithId(24L, "Thiago", "Ezequiel", "Almada", LocalDate.of(2001, 4, 26), 16,
- "Attacking Midfield", "AM", "Atlanta United FC", "Major League Soccer", false),
- createPlayerDTOWithId(25L, "Paulo", "Exequiel", "Dybala", LocalDate.of(1993, 11, 15), 21,
- "Second Striker", "SS", "AS Roma", "Serie A", false),
- createPlayerDTOWithId(26L, "Lautaro", "Javier", "Martínez", LocalDate.of(1997, 8, 22), 22,
- "Centre-Forward", "CF", "Inter Milan", "Serie A", false));
+ createPlayerDTO(uuid(12), 1, "Franco", "Daniel", "Armani", LocalDate.of(1986, 10, 16), "Goalkeeper", "GK", "River Plate", "Copa de la Liga", false),
+ createPlayerDTO(uuid(13), 12, "Gerónimo", null, "Rulli", LocalDate.of(1992, 5, 20), "Goalkeeper", "GK", "Ajax Amsterdam", "Eredivisie", false),
+ createPlayerDTO(uuid(14), 2, "Juan", "Marcos", "Foyth", LocalDate.of(1998, 1, 12), "Right-Back", "RB", "Villarreal", "La Liga", false),
+ createPlayerDTO(uuid(15), 4, "Gonzalo", "Ariel", "Montiel", LocalDate.of(1997, 1, 1), "Right-Back", "RB", "Nottingham Forest", "Premier League", false),
+ createPlayerDTO(uuid(16), 6, "Germán", "Alejo", "Pezzella", LocalDate.of(1991, 6, 27), "Centre-Back", "CB", "Real Betis Balompié", "La Liga", false),
+ createPlayerDTO(uuid(17), 8, "Marcos", "Javier", "Acuña", LocalDate.of(1991, 10, 28), "Left-Back", "LB", "Sevilla FC", "La Liga", false),
+ createPlayerDTO(uuid(18), 25, "Lisandro", null, "Martínez", LocalDate.of(1998, 1, 18), "Centre-Back", "CB", "Manchester United", "Premier League", false),
+ // Leandro Paredes (squad number 5) - created during tests
+ createPlayerDTO(uuid(19), 5, "Leandro", "Daniel", "Paredes", LocalDate.of(1994, 6, 29), "Defensive Midfield", "DM", "AS Roma", "Serie A", false),
+ createPlayerDTO(uuid(20), 14, "Exequiel", "Alejandro", "Palacios", LocalDate.of(1998, 10, 5), "Central Midfield", "CM", "Bayer 04 Leverkusen", "Bundesliga", false),
+ createPlayerDTO(uuid(21), 17, "Alejandro", "Darío", "Gómez", LocalDate.of(1988, 2, 15), "Left Winger", "LW", "AC Monza", "Serie A", false),
+ createPlayerDTO(uuid(22), 18, "Guido", null, "Rodríguez", LocalDate.of(1994, 4, 12), "Defensive Midfield", "DM", "Real Betis Balompié", "La Liga", false),
+ createPlayerDTO(uuid(23), 15, "Ángel", "Martín", "Correa", LocalDate.of(1995, 3, 9), "Right Winger", "RW", "Atlético Madrid", "La Liga", false),
+ createPlayerDTO(uuid(24), 16, "Thiago", "Ezequiel", "Almada", LocalDate.of(2001, 4, 26), "Attacking Midfield", "AM", "Atlanta United FC", "Major League Soccer", false),
+ createPlayerDTO(uuid(25), 21, "Paulo", "Exequiel", "Dybala", LocalDate.of(1993, 11, 15), "Second Striker", "SS", "AS Roma", "Serie A", false),
+ createPlayerDTO(uuid(26), 22, "Lautaro", "Javier", "Martínez", LocalDate.of(1997, 8, 22), "Centre-Forward", "CF", "Inter Milan", "Serie A", false));
}
- private static PlayerDTO createPlayerDTOWithId(Long id, String firstName, String middleName, String lastName,
- LocalDate dateOfBirth, Integer squadNumber, String position,
- String abbrPosition, String team, String league, Boolean starting11) {
+ private static PlayerDTO createPlayerDTO(UUID id, Integer squadNumber, String firstName, String middleName,
+ String lastName, LocalDate dateOfBirth, String position, String abbrPosition,
+ String team, String league, Boolean starting11) {
PlayerDTO playerDTO = new PlayerDTO();
playerDTO.setId(id);
+ playerDTO.setSquadNumber(squadNumber);
playerDTO.setFirstName(firstName);
playerDTO.setMiddleName(middleName);
playerDTO.setLastName(lastName);
playerDTO.setDateOfBirth(dateOfBirth);
- playerDTO.setSquadNumber(squadNumber);
playerDTO.setPosition(position);
playerDTO.setAbbrPosition(abbrPosition);
playerDTO.setTeam(team);
diff --git a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerFakes.java b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerFakes.java
index 5eea56c..f4639d9 100644
--- a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerFakes.java
+++ b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/PlayerFakes.java
@@ -3,6 +3,7 @@
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
+import java.util.UUID;
import ar.com.nanotaboada.java.samples.spring.boot.models.Player;
@@ -12,24 +13,28 @@ private PlayerFakes() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
+ private static UUID uuid(int index) {
+ return UUID.fromString(String.format("00000000-0000-0000-0000-%012d", index));
+ }
+
/**
* Leandro Paredes - Test data for CREATE operations
*
* Usage:
- * - Repository tests: Insert into real in-memory DB (gets ID 19)
+ * - Repository tests: Insert into real in-memory DB
* - Service tests: Mock expected data for playersService.create()
* - Controller tests: Mock expected data for POST /players
*
- * Note: Not pre-seeded in test DB (ID 19 slot is empty)
+ * Note: Not pre-seeded in test DB (squad number 5 slot is empty)
*/
public static Player createOneValid() {
Player player = new Player();
- player.setId(null); // Will be auto-generated as 19
+ player.setId(null); // Will be generated by @PrePersist
+ player.setSquadNumber(5);
player.setFirstName("Leandro");
player.setMiddleName("Daniel");
player.setLastName("Paredes");
player.setDateOfBirth(LocalDate.of(1994, 6, 29));
- player.setSquadNumber(5);
player.setPosition("Defensive Midfield");
player.setAbbrPosition("DM");
player.setTeam("AS Roma");
@@ -39,22 +44,22 @@ public static Player createOneValid() {
}
/**
- * Damián Martínez - Player ID 1 BEFORE update
+ * Damián Martínez - Player squad number 23 BEFORE update
*
* Usage:
- * - Service tests: Mock expected data for playersService.retrieveById(1L)
- * - Controller tests: Mock expected data for GET /players/1
+ * - Service tests: Mock expected data for playersService.retrieveById(uuid)
+ * - Controller tests: Mock expected data for GET /players/{id}
*
* Note: Repository tests query DB directly (pre-seeded in dml.sql)
*/
public static Player createOneForUpdate() {
Player player = new Player();
- player.setId(1L);
+ player.setId(uuid(1));
+ player.setSquadNumber(23);
player.setFirstName("Damián");
player.setMiddleName("Emiliano");
player.setLastName("Martínez");
player.setDateOfBirth(LocalDate.of(1992, 9, 2));
- player.setSquadNumber(23);
player.setPosition("Goalkeeper");
player.setAbbrPosition("GK");
player.setTeam("Aston Villa FC");
@@ -64,26 +69,20 @@ public static Player createOneForUpdate() {
}
/**
- * Emiliano Martínez - Expected result AFTER updating player ID 1
- *
- * Usage:
- * - Service tests: Mock expected data after playersService.update()
- * - Controller tests: Mock expected data after PUT /players/1
+ * Emiliano Martínez - Expected result AFTER updating player squad number 23
*
* Update changes:
* - firstName: "Damián" → "Emiliano"
* - middleName: "Emiliano" → null
- *
- * Note: Repository tests should query DB directly for before/after states
*/
public static Player createOneUpdated() {
Player player = new Player();
- player.setId(1L);
+ player.setId(uuid(1));
+ player.setSquadNumber(23);
player.setFirstName("Emiliano");
player.setMiddleName(null);
player.setLastName("Martínez");
player.setDateOfBirth(LocalDate.of(1992, 9, 2));
- player.setSquadNumber(23);
player.setPosition("Goalkeeper");
player.setAbbrPosition("GK");
player.setTeam("Aston Villa FC");
@@ -94,21 +93,15 @@ public static Player createOneUpdated() {
/**
* Invalid player data - Test data for validation failure scenarios
- *
- * Usage:
- * - Controller tests: Verify validation annotations work
- * (@NotBlank, @Past, @Positive)
- *
- * Violations: blank names, future date, negative squad number, blank fields
*/
public static Player createOneInvalid() {
Player player = new Player();
- player.setId(999L);
+ player.setId(null);
+ player.setSquadNumber(-1); // Invalid (must be positive)
player.setFirstName(""); // Invalid (blank)
player.setMiddleName(null);
player.setLastName(""); // Invalid (blank)
player.setDateOfBirth(LocalDate.now()); // Invalid (must be a past date)
- player.setSquadNumber(-1); // Invalid (must be positive)
player.setPosition(""); // Invalid (blank)
player.setAbbrPosition(null);
player.setTeam(""); // Invalid (blank)
@@ -119,87 +112,50 @@ public static Player createOneInvalid() {
/**
* ALL 26 players - Complete Argentina 2022 FIFA World Cup squad
- *
- * Usage:
- * - Service tests: Mock expected data for playersService.retrieveAll()
- * - Controller tests: Mock expected data for GET /players
- *
- * Includes:
- * - 25 players pre-seeded in test DB (IDs 1-26, excluding 19)
- * - Leandro Paredes (ID 19, created during tests)
- *
- * Note: Repository tests query real in-memory DB directly (25 players
- * pre-seeded)
*/
public static List createAll() {
return Arrays.asList(
// Starting 11
- createPlayerWithId(1L, "Damián", "Emiliano", "Martínez", LocalDate.of(1992, 9, 2), 23, "Goalkeeper",
- "GK", "Aston Villa FC", "Premier League", true),
- createPlayerWithId(2L, "Nahuel", null, "Molina", LocalDate.of(1998, 4, 6), 26, "Right-Back", "RB",
- "Atlético Madrid", "La Liga", true),
- createPlayerWithId(3L, "Cristian", "Gabriel", "Romero", LocalDate.of(1998, 4, 27), 13, "Centre-Back",
- "CB", "Tottenham Hotspur", "Premier League", true),
- createPlayerWithId(4L, "Nicolás", "Hernán Gonzalo", "Otamendi", LocalDate.of(1988, 2, 12), 19,
- "Centre-Back", "CB", "SL Benfica", "Liga Portugal", true),
- createPlayerWithId(5L, "Nicolás", "Alejandro", "Tagliafico", LocalDate.of(1992, 8, 31), 3, "Left-Back",
- "LB", "Olympique Lyon", "Ligue 1", true),
- createPlayerWithId(6L, "Ángel", "Fabián", "Di María", LocalDate.of(1988, 2, 14), 11, "Right Winger",
- "RW", "SL Benfica", "Liga Portugal", true),
- createPlayerWithId(7L, "Rodrigo", "Javier", "de Paul", LocalDate.of(1994, 5, 24), 7, "Central Midfield",
- "CM", "Atlético Madrid", "La Liga", true),
- createPlayerWithId(8L, "Enzo", "Jeremías", "Fernández", LocalDate.of(2001, 1, 17), 24,
- "Central Midfield", "CM", "Chelsea FC", "Premier League", true),
- createPlayerWithId(9L, "Alexis", null, "Mac Allister", LocalDate.of(1998, 12, 24), 20,
- "Central Midfield", "CM", "Liverpool FC", "Premier League", true),
- createPlayerWithId(10L, "Lionel", "Andrés", "Messi", LocalDate.of(1987, 6, 24), 10, "Right Winger",
- "RW", "Inter Miami CF", "Major League Soccer", true),
- createPlayerWithId(11L, "Julián", null, "Álvarez", LocalDate.of(2000, 1, 31), 9, "Centre-Forward", "CF",
- "Manchester City", "Premier League", true),
+ createPlayer(uuid(1), 23, "Damián", "Emiliano", "Martínez", LocalDate.of(1992, 9, 2), "Goalkeeper", "GK", "Aston Villa FC", "Premier League", true),
+ createPlayer(uuid(2), 26, "Nahuel", null, "Molina", LocalDate.of(1998, 4, 6), "Right-Back", "RB", "Atlético Madrid", "La Liga", true),
+ createPlayer(uuid(3), 13, "Cristian", "Gabriel", "Romero", LocalDate.of(1998, 4, 27), "Centre-Back", "CB", "Tottenham Hotspur", "Premier League", true),
+ createPlayer(uuid(4), 19, "Nicolás", "Hernán Gonzalo", "Otamendi", LocalDate.of(1988, 2, 12), "Centre-Back", "CB", "SL Benfica", "Liga Portugal", true),
+ createPlayer(uuid(5), 3, "Nicolás", "Alejandro", "Tagliafico", LocalDate.of(1992, 8, 31), "Left-Back", "LB", "Olympique Lyon", "Ligue 1", true),
+ createPlayer(uuid(6), 11, "Ángel", "Fabián", "Di María", LocalDate.of(1988, 2, 14), "Right Winger", "RW", "SL Benfica", "Liga Portugal", true),
+ createPlayer(uuid(7), 7, "Rodrigo", "Javier", "de Paul", LocalDate.of(1994, 5, 24), "Central Midfield", "CM", "Atlético Madrid", "La Liga", true),
+ createPlayer(uuid(8), 24, "Enzo", "Jeremías", "Fernández", LocalDate.of(2001, 1, 17), "Central Midfield", "CM", "Chelsea FC", "Premier League", true),
+ createPlayer(uuid(9), 20, "Alexis", null, "Mac Allister", LocalDate.of(1998, 12, 24), "Central Midfield", "CM", "Liverpool FC", "Premier League", true),
+ createPlayer(uuid(10), 10, "Lionel", "Andrés", "Messi", LocalDate.of(1987, 6, 24), "Right Winger", "RW", "Inter Miami CF", "Major League Soccer", true),
+ createPlayer(uuid(11), 9, "Julián", null, "Álvarez", LocalDate.of(2000, 1, 31), "Centre-Forward", "CF", "Manchester City", "Premier League", true),
// Substitutes
- createPlayerWithId(12L, "Franco", "Daniel", "Armani", LocalDate.of(1986, 10, 16), 1, "Goalkeeper", "GK",
- "River Plate", "Copa de la Liga", false),
- createPlayerWithId(13L, "Gerónimo", null, "Rulli", LocalDate.of(1992, 5, 20), 12, "Goalkeeper", "GK",
- "Ajax Amsterdam", "Eredivisie", false),
- createPlayerWithId(14L, "Juan", "Marcos", "Foyth", LocalDate.of(1998, 1, 12), 2, "Right-Back", "RB",
- "Villarreal", "La Liga", false),
- createPlayerWithId(15L, "Gonzalo", "Ariel", "Montiel", LocalDate.of(1997, 1, 1), 4, "Right-Back", "RB",
- "Nottingham Forest", "Premier League", false),
- createPlayerWithId(16L, "Germán", "Alejo", "Pezzella", LocalDate.of(1991, 6, 27), 6, "Centre-Back",
- "CB", "Real Betis Balompié", "La Liga", false),
- createPlayerWithId(17L, "Marcos", "Javier", "Acuña", LocalDate.of(1991, 10, 28), 8, "Left-Back", "LB",
- "Sevilla FC", "La Liga", false),
- createPlayerWithId(18L, "Lisandro", null, "Martínez", LocalDate.of(1998, 1, 18), 25, "Centre-Back",
- "CB", "Manchester United", "Premier League", false),
- // Leandro Paredes (ID 19) - created during tests
- createPlayerWithId(19L, "Leandro", "Daniel", "Paredes", LocalDate.of(1994, 6, 29), 5,
- "Defensive Midfield", "DM", "AS Roma", "Serie A", false),
- createPlayerWithId(20L, "Exequiel", "Alejandro", "Palacios", LocalDate.of(1998, 10, 5), 14,
- "Central Midfield", "CM", "Bayer 04 Leverkusen", "Bundesliga", false),
- createPlayerWithId(21L, "Alejandro", "Darío", "Gómez", LocalDate.of(1988, 2, 15), 17, "Left Winger",
- "LW", "AC Monza", "Serie A", false),
- createPlayerWithId(22L, "Guido", null, "Rodríguez", LocalDate.of(1994, 4, 12), 18, "Defensive Midfield",
- "DM", "Real Betis Balompié", "La Liga", false),
- createPlayerWithId(23L, "Ángel", "Martín", "Correa", LocalDate.of(1995, 3, 9), 15, "Right Winger", "RW",
- "Atlético Madrid", "La Liga", false),
- createPlayerWithId(24L, "Thiago", "Ezequiel", "Almada", LocalDate.of(2001, 4, 26), 16,
- "Attacking Midfield", "AM", "Atlanta United FC", "Major League Soccer", false),
- createPlayerWithId(25L, "Paulo", "Exequiel", "Dybala", LocalDate.of(1993, 11, 15), 21, "Second Striker",
- "SS", "AS Roma", "Serie A", false),
- createPlayerWithId(26L, "Lautaro", "Javier", "Martínez", LocalDate.of(1997, 8, 22), 22,
- "Centre-Forward", "CF", "Inter Milan", "Serie A", false));
+ createPlayer(uuid(12), 1, "Franco", "Daniel", "Armani", LocalDate.of(1986, 10, 16), "Goalkeeper", "GK", "River Plate", "Copa de la Liga", false),
+ createPlayer(uuid(13), 12, "Gerónimo", null, "Rulli", LocalDate.of(1992, 5, 20), "Goalkeeper", "GK", "Ajax Amsterdam", "Eredivisie", false),
+ createPlayer(uuid(14), 2, "Juan", "Marcos", "Foyth", LocalDate.of(1998, 1, 12), "Right-Back", "RB", "Villarreal", "La Liga", false),
+ createPlayer(uuid(15), 4, "Gonzalo", "Ariel", "Montiel", LocalDate.of(1997, 1, 1), "Right-Back", "RB", "Nottingham Forest", "Premier League", false),
+ createPlayer(uuid(16), 6, "Germán", "Alejo", "Pezzella", LocalDate.of(1991, 6, 27), "Centre-Back", "CB", "Real Betis Balompié", "La Liga", false),
+ createPlayer(uuid(17), 8, "Marcos", "Javier", "Acuña", LocalDate.of(1991, 10, 28), "Left-Back", "LB", "Sevilla FC", "La Liga", false),
+ createPlayer(uuid(18), 25, "Lisandro", null, "Martínez", LocalDate.of(1998, 1, 18), "Centre-Back", "CB", "Manchester United", "Premier League", false),
+ // Leandro Paredes (squad number 5) - created during tests
+ createPlayer(uuid(19), 5, "Leandro", "Daniel", "Paredes", LocalDate.of(1994, 6, 29), "Defensive Midfield", "DM", "AS Roma", "Serie A", false),
+ createPlayer(uuid(20), 14, "Exequiel", "Alejandro", "Palacios", LocalDate.of(1998, 10, 5), "Central Midfield", "CM", "Bayer 04 Leverkusen", "Bundesliga", false),
+ createPlayer(uuid(21), 17, "Alejandro", "Darío", "Gómez", LocalDate.of(1988, 2, 15), "Left Winger", "LW", "AC Monza", "Serie A", false),
+ createPlayer(uuid(22), 18, "Guido", null, "Rodríguez", LocalDate.of(1994, 4, 12), "Defensive Midfield", "DM", "Real Betis Balompié", "La Liga", false),
+ createPlayer(uuid(23), 15, "Ángel", "Martín", "Correa", LocalDate.of(1995, 3, 9), "Right Winger", "RW", "Atlético Madrid", "La Liga", false),
+ createPlayer(uuid(24), 16, "Thiago", "Ezequiel", "Almada", LocalDate.of(2001, 4, 26), "Attacking Midfield", "AM", "Atlanta United FC", "Major League Soccer", false),
+ createPlayer(uuid(25), 21, "Paulo", "Exequiel", "Dybala", LocalDate.of(1993, 11, 15), "Second Striker", "SS", "AS Roma", "Serie A", false),
+ createPlayer(uuid(26), 22, "Lautaro", "Javier", "Martínez", LocalDate.of(1997, 8, 22), "Centre-Forward", "CF", "Inter Milan", "Serie A", false));
}
- private static Player createPlayerWithId(Long id, String firstName, String middleName, String lastName,
- LocalDate dateOfBirth, Integer squadNumber, String position,
- String abbrPosition, String team, String league, Boolean starting11) {
+ private static Player createPlayer(UUID id, Integer squadNumber, String firstName, String middleName,
+ String lastName, LocalDate dateOfBirth, String position, String abbrPosition,
+ String team, String league, Boolean starting11) {
Player player = new Player();
player.setId(id);
+ player.setSquadNumber(squadNumber);
player.setFirstName(firstName);
player.setMiddleName(middleName);
player.setLastName(lastName);
player.setDateOfBirth(dateOfBirth);
- player.setSquadNumber(squadNumber);
player.setPosition(position);
player.setAbbrPosition(abbrPosition);
player.setTeam(team);
diff --git a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java
index a31f1f6..74e763e 100644
--- a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java
+++ b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/controllers/PlayersControllerTests.java
@@ -2,12 +2,13 @@
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.List;
+import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -77,7 +78,8 @@ void givenValidPlayer_whenPost_thenReturnsCreated()
// Given
PlayerDTO dto = PlayerDTOFakes.createOneValid();
PlayerDTO savedDTO = PlayerDTOFakes.createOneValid();
- savedDTO.setId(19L); // Simulating auto-generated ID
+ UUID savedUuid = UUID.fromString("00000000-0000-0000-0000-000000000019");
+ savedDTO.setId(savedUuid);
String content = objectMapper.writeValueAsString(dto);
Mockito
.when(playersServiceMock.create(any(PlayerDTO.class)))
@@ -95,7 +97,7 @@ void givenValidPlayer_whenPost_thenReturnsCreated()
verify(playersServiceMock, times(1)).create(any(PlayerDTO.class));
then(response.getStatus()).isEqualTo(HttpStatus.CREATED.value());
then(response.getHeader(HttpHeaders.LOCATION)).isNotNull();
- then(response.getHeader(HttpHeaders.LOCATION)).contains(PATH + "/19");
+ then(response.getHeader(HttpHeaders.LOCATION)).contains(PATH + "/" + savedUuid);
}
/**
@@ -189,7 +191,7 @@ void givenPlayersExist_whenGetAll_thenReturnsOkWithAllPlayers()
/**
* Given a player exists
- * When requesting that player by ID
+ * When requesting that player by UUID
* Then response status is 200 OK and the player data is returned
*/
@Test
@@ -197,11 +199,12 @@ void givenPlayerExists_whenGetById_thenReturnsOk()
throws Exception {
// Given
PlayerDTO expected = PlayerDTOFakes.createOneForUpdate();
+ UUID id = expected.getId();
Mockito
- .when(playersServiceMock.retrieveById(1L))
+ .when(playersServiceMock.retrieveById(id))
.thenReturn(expected);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
- .get(PATH + "/{id}", 1L);
+ .get(PATH + "/{id}", id);
// When
MockHttpServletResponse response = application
.perform(request)
@@ -211,23 +214,23 @@ void givenPlayerExists_whenGetById_thenReturnsOk()
PlayerDTO actual = objectMapper.readValue(content, PlayerDTO.class);
// Then
then(response.getContentType()).contains("application/json");
- verify(playersServiceMock, times(1)).retrieveById(1L);
+ verify(playersServiceMock, times(1)).retrieveById(id);
then(response.getStatus()).isEqualTo(HttpStatus.OK.value());
then(actual).usingRecursiveComparison().isEqualTo(expected);
}
/**
- * Given a player with a specific ID does not exist
- * When requesting that player by ID
+ * Given a player with a specific UUID does not exist
+ * When requesting that player by UUID
* Then response status is 404 Not Found
*/
@Test
void givenPlayerDoesNotExist_whenGetById_thenReturnsNotFound()
throws Exception {
// Given
- Long id = 999L;
+ UUID id = UUID.randomUUID();
Mockito
- .when(playersServiceMock.retrieveById(anyLong()))
+ .when(playersServiceMock.retrieveById(any(UUID.class)))
.thenReturn(null);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
.get(PATH + "/{id}", id);
@@ -237,7 +240,7 @@ void givenPlayerDoesNotExist_whenGetById_thenReturnsNotFound()
.andReturn()
.getResponse();
// Then
- verify(playersServiceMock, times(1)).retrieveById(anyLong());
+ verify(playersServiceMock, times(1)).retrieveById(any(UUID.class));
then(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
}
@@ -371,7 +374,7 @@ void givenNoPlayersExist_whenSearchByLeague_thenReturnsOk()
/**
* Given a player exists and valid update data is provided
- * When updating that player
+ * When updating that player by squad number
* Then response status is 204 No Content
*/
@Test
@@ -379,13 +382,13 @@ void givenPlayerExists_whenPut_thenReturnsNoContent()
throws Exception {
// Given
PlayerDTO dto = PlayerDTOFakes.createOneValid();
- dto.setId(1L); // Set ID for update operation
+ Integer squadNumber = dto.getSquadNumber();
String content = objectMapper.writeValueAsString(dto);
Mockito
- .when(playersServiceMock.update(any(PlayerDTO.class)))
+ .when(playersServiceMock.update(squadNumber, dto))
.thenReturn(true);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
- .put(PATH + "/{id}", dto.getId())
+ .put(PATH + "/{squadNumber}", squadNumber)
.content(content)
.contentType(MediaType.APPLICATION_JSON);
// When
@@ -394,12 +397,12 @@ void givenPlayerExists_whenPut_thenReturnsNoContent()
.andReturn()
.getResponse();
// Then
- verify(playersServiceMock, times(1)).update(any(PlayerDTO.class));
+ verify(playersServiceMock, times(1)).update(anyInt(), any(PlayerDTO.class));
then(response.getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
}
/**
- * Given a player with the provided ID does not exist
+ * Given a player with the provided squad number does not exist
* When attempting to update that player
* Then response status is 404 Not Found
*/
@@ -408,13 +411,13 @@ void givenPlayerDoesNotExist_whenPut_thenReturnsNotFound()
throws Exception {
// Given
PlayerDTO dto = PlayerDTOFakes.createOneValid();
- dto.setId(999L); // Set ID for update operation
+ Integer squadNumber = dto.getSquadNumber();
String content = objectMapper.writeValueAsString(dto);
Mockito
- .when(playersServiceMock.update(any(PlayerDTO.class)))
+ .when(playersServiceMock.update(anyInt(), any(PlayerDTO.class)))
.thenReturn(false);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
- .put(PATH + "/{id}", dto.getId())
+ .put(PATH + "/{squadNumber}", squadNumber)
.content(content)
.contentType(MediaType.APPLICATION_JSON);
// When
@@ -423,7 +426,7 @@ void givenPlayerDoesNotExist_whenPut_thenReturnsNotFound()
.andReturn()
.getResponse();
// Then
- verify(playersServiceMock, times(1)).update(any(PlayerDTO.class));
+ verify(playersServiceMock, times(1)).update(anyInt(), any(PlayerDTO.class));
then(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
}
@@ -439,7 +442,7 @@ void givenInvalidPlayer_whenPut_thenReturnsBadRequest()
PlayerDTO dto = PlayerDTOFakes.createOneInvalid();
String content = objectMapper.writeValueAsString(dto);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
- .put(PATH + "/{id}", dto.getId())
+ .put(PATH + "/{squadNumber}", 1)
.content(content)
.contentType(MediaType.APPLICATION_JSON);
// When
@@ -448,25 +451,25 @@ void givenInvalidPlayer_whenPut_thenReturnsBadRequest()
.andReturn()
.getResponse();
// Then
- verify(playersServiceMock, never()).update(any(PlayerDTO.class));
+ verify(playersServiceMock, never()).update(anyInt(), any(PlayerDTO.class));
then(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
/**
- * Given the path ID does not match the body ID
+ * Given the path squad number does not match the body squad number
* When attempting to update a player
* Then response status is 400 Bad Request and service is never called
*/
@Test
- void givenIdMismatch_whenPut_thenReturnsBadRequest()
+ void givenSquadNumberMismatch_whenPut_thenReturnsBadRequest()
throws Exception {
// Given
PlayerDTO dto = PlayerDTOFakes.createOneValid();
- dto.setId(999L); // Body has different ID
- Long pathId = 1L; // Path has different ID
+ dto.setSquadNumber(999); // Body has different squad number
+ Integer pathSquadNumber = 5; // Path has different squad number
String content = objectMapper.writeValueAsString(dto);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
- .put(PATH + "/{id}", pathId)
+ .put(PATH + "/{squadNumber}", pathSquadNumber)
.content(content)
.contentType(MediaType.APPLICATION_JSON);
// When
@@ -475,28 +478,24 @@ void givenIdMismatch_whenPut_thenReturnsBadRequest()
.andReturn()
.getResponse();
// Then
- verify(playersServiceMock, never()).update(any(PlayerDTO.class));
+ verify(playersServiceMock, never()).update(anyInt(), any(PlayerDTO.class));
then(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
/**
- * Given the body ID is null (ID only in path)
- * When updating a player
- * Then the ID is set from the path and the update proceeds normally
+ * Given the body squad number is null
+ * When attempting to update a player
+ * Then response status is 400 Bad Request (squad number is required)
*/
@Test
- void givenNullBodyId_whenPut_thenSetsIdFromPath()
+ void givenNullBodySquadNumber_whenPut_thenReturnsBadRequest()
throws Exception {
// Given
PlayerDTO dto = PlayerDTOFakes.createOneValid();
- dto.setId(null); // Body has null ID
- Long pathId = 1L;
+ dto.setSquadNumber(null); // Body has null squad number (violates @NotNull)
String content = objectMapper.writeValueAsString(dto);
- Mockito
- .when(playersServiceMock.update(any(PlayerDTO.class)))
- .thenReturn(true);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
- .put(PATH + "/{id}", pathId)
+ .put(PATH + "/{squadNumber}", 5)
.content(content)
.contentType(MediaType.APPLICATION_JSON);
// When
@@ -505,8 +504,8 @@ void givenNullBodyId_whenPut_thenSetsIdFromPath()
.andReturn()
.getResponse();
// Then
- verify(playersServiceMock, times(1)).update(any(PlayerDTO.class));
- then(response.getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
+ verify(playersServiceMock, never()).update(anyInt(), any(PlayerDTO.class));
+ then(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
}
/*
@@ -517,50 +516,51 @@ void givenNullBodyId_whenPut_thenSetsIdFromPath()
/**
* Given a player exists
- * When deleting that player by ID
+ * When deleting that player by squad number
* Then response status is 204 No Content
*/
@Test
void givenPlayerExists_whenDelete_thenReturnsNoContent()
throws Exception {
// Given
+ Integer squadNumber = 17;
Mockito
- .when(playersServiceMock.delete(1L))
+ .when(playersServiceMock.deleteBySquadNumber(squadNumber))
.thenReturn(true);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
- .delete(PATH + "/{id}", 1L);
+ .delete(PATH + "/{squadNumber}", squadNumber);
// When
MockHttpServletResponse response = application
.perform(request)
.andReturn()
.getResponse();
// Then
- verify(playersServiceMock, times(1)).delete(1L);
+ verify(playersServiceMock, times(1)).deleteBySquadNumber(squadNumber);
then(response.getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
}
/**
- * Given a player with a specific ID does not exist
- * When attempting to delete that player by ID
+ * Given a player with a specific squad number does not exist
+ * When attempting to delete that player
* Then response status is 404 Not Found
*/
@Test
void givenPlayerDoesNotExist_whenDelete_thenReturnsNotFound()
throws Exception {
// Given
- Long id = 999L;
+ Integer squadNumber = 999;
Mockito
- .when(playersServiceMock.delete(id))
+ .when(playersServiceMock.deleteBySquadNumber(squadNumber))
.thenReturn(false);
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
- .delete(PATH + "/{id}", id);
+ .delete(PATH + "/{squadNumber}", squadNumber);
// When
MockHttpServletResponse response = application
.perform(request)
.andReturn()
.getResponse();
// Then
- verify(playersServiceMock, times(1)).delete(id);
+ verify(playersServiceMock, times(1)).deleteBySquadNumber(squadNumber);
then(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
}
}
diff --git a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/repositories/PlayersRepositoryTests.java b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/repositories/PlayersRepositoryTests.java
index 8ee6b04..fe6c20e 100644
--- a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/repositories/PlayersRepositoryTests.java
+++ b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/repositories/PlayersRepositoryTests.java
@@ -4,6 +4,7 @@
import java.util.List;
import java.util.Optional;
+import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -27,30 +28,31 @@ class PlayersRepositoryTests {
private PlayersRepository repository;
/**
- * Given a player exists in the database
- * When findById() is called with that player's ID
+ * Given a player is saved to the database
+ * When findById() is called with the player's UUID surrogate key
* Then the player is returned
*/
@Test
void givenPlayerExists_whenFindById_thenReturnsPlayer() {
// Given
Player expected = repository.save(PlayerFakes.createOneValid());
+ UUID savedUuid = expected.getId();
// When
- Optional actual = repository.findById(expected.getId());
+ Optional actual = repository.findById(savedUuid);
// Then
then(actual).isPresent();
then(actual.get()).usingRecursiveComparison().isEqualTo(expected);
}
/**
- * Given the database does not contain a player with a specific ID
- * When querying by that ID
+ * Given the database does not contain a player with a specific UUID
+ * When querying by that UUID
* Then an empty Optional is returned
*/
@Test
void givenPlayerDoesNotExist_whenFindById_thenReturnsEmpty() {
// Given
- Long nonExistentId = 999L;
+ UUID nonExistentId = UUID.randomUUID();
// When
Optional actual = repository.findById(nonExistentId);
// Then
diff --git a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java
index 4ae52d9..7727c73 100644
--- a/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java
+++ b/src/test/java/ar/com/nanotaboada/java/samples/spring/boot/test/services/PlayersServiceTests.java
@@ -2,13 +2,13 @@
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.List;
import java.util.Optional;
+import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -58,7 +58,7 @@ void givenNoExistingPlayer_whenCreate_thenReturnsPlayerDTO() {
PlayerDTO expected = PlayerDTOFakes.createOneValid();
Mockito
.when(playersRepositoryMock.findBySquadNumber(expected.getSquadNumber()))
- .thenReturn(Optional.empty()); // No conflict
+ .thenReturn(Optional.empty());
Mockito
.when(modelMapperMock.map(expected, Player.class))
.thenReturn(entity);
@@ -115,7 +115,7 @@ void givenRaceCondition_whenCreate_thenReturnsNull() {
Player entity = PlayerFakes.createOneValid();
Mockito
.when(playersRepositoryMock.findBySquadNumber(dto.getSquadNumber()))
- .thenReturn(Optional.empty()); // No conflict initially
+ .thenReturn(Optional.empty());
Mockito
.when(modelMapperMock.map(dto, Player.class))
.thenReturn(entity);
@@ -149,7 +149,6 @@ void givenAllPlayersExist_whenRetrieveAll_thenReturns26Players() {
Mockito
.when(playersRepositoryMock.findAll())
.thenReturn(entities);
- // Mock modelMapper to convert each player correctly
for (int i = 0; i < entities.size(); i++) {
Mockito
.when(modelMapperMock.map(entities.get(i), PlayerDTO.class))
@@ -163,8 +162,8 @@ void givenAllPlayersExist_whenRetrieveAll_thenReturns26Players() {
}
/**
- * Given a player exists with a specific ID
- * When retrieving that player by ID
+ * Given a player exists with a specific UUID
+ * When retrieving that player by UUID
* Then the corresponding PlayerDTO is returned
*/
@Test
@@ -172,36 +171,37 @@ void givenPlayerExists_whenRetrieveById_thenReturnsPlayerDTO() {
// Given
Player entity = PlayerFakes.createOneForUpdate();
PlayerDTO expected = PlayerDTOFakes.createOneForUpdate();
+ UUID id = entity.getId();
Mockito
- .when(playersRepositoryMock.findById(1L))
+ .when(playersRepositoryMock.findById(id))
.thenReturn(Optional.of(entity));
Mockito
.when(modelMapperMock.map(entity, PlayerDTO.class))
.thenReturn(expected);
// When
- PlayerDTO actual = playersService.retrieveById(1L);
+ PlayerDTO actual = playersService.retrieveById(id);
// Then
- verify(playersRepositoryMock, times(1)).findById(1L);
+ verify(playersRepositoryMock, times(1)).findById(id);
verify(modelMapperMock, times(1)).map(entity, PlayerDTO.class);
then(actual).isEqualTo(expected);
}
/**
- * Given no player exists with a specific ID
- * When retrieving by that ID
+ * Given no player exists with a specific UUID
+ * When retrieving by that UUID
* Then null is returned
*/
@Test
void givenPlayerDoesNotExist_whenRetrieveById_thenReturnsNull() {
// Given
- Long id = 999L;
+ UUID id = UUID.randomUUID();
Mockito
- .when(playersRepositoryMock.findById(anyLong()))
+ .when(playersRepositoryMock.findById(id))
.thenReturn(Optional.empty());
// When
PlayerDTO actual = playersService.retrieveById(id);
// Then
- verify(playersRepositoryMock, times(1)).findById(anyLong());
+ verify(playersRepositoryMock, times(1)).findById(id);
verify(modelMapperMock, never()).map(any(Player.class), any());
then(actual).isNull();
}
@@ -282,7 +282,6 @@ void givenPlayersExist_whenSearchByLeague_thenReturns7Players() {
Mockito
.when(playersRepositoryMock.findByLeagueContainingIgnoreCase(any()))
.thenReturn(entities);
- // Mock modelMapper to convert each player correctly
for (int i = 0; i < entities.size(); i++) {
Mockito
.when(modelMapperMock.map(entities.get(i), PlayerDTO.class))
@@ -326,7 +325,7 @@ void givenNoPlayersExist_whenSearchByLeague_thenReturnsEmptyList() {
/**
* Given a player exists
- * When update() is called with modified player data
+ * When update() is called with the player's squad number and modified data
* Then the player is updated and true is returned
*/
@Test
@@ -334,23 +333,24 @@ void givenPlayerExists_whenUpdate_thenReturnsTrue() {
// Given
Player entity = PlayerFakes.createOneUpdated();
PlayerDTO dto = PlayerDTOFakes.createOneUpdated();
+ Integer squadNumber = dto.getSquadNumber();
Mockito
- .when(playersRepositoryMock.existsById(1L))
- .thenReturn(true);
+ .when(playersRepositoryMock.findBySquadNumber(squadNumber))
+ .thenReturn(Optional.of(entity));
Mockito
.when(modelMapperMock.map(dto, Player.class))
.thenReturn(entity);
// When
- boolean actual = playersService.update(dto);
+ boolean actual = playersService.update(squadNumber, dto);
// Then
- verify(playersRepositoryMock, times(1)).existsById(1L);
+ verify(playersRepositoryMock, times(1)).findBySquadNumber(squadNumber);
verify(playersRepositoryMock, times(1)).save(any(Player.class));
verify(modelMapperMock, times(1)).map(dto, Player.class);
then(actual).isTrue();
}
/**
- * Given no player exists with the specified ID
+ * Given no player exists with the specified squad number
* When update() is called
* Then false is returned without saving
*/
@@ -358,33 +358,34 @@ void givenPlayerExists_whenUpdate_thenReturnsTrue() {
void givenPlayerDoesNotExist_whenUpdate_thenReturnsFalse() {
// Given
PlayerDTO dto = PlayerDTOFakes.createOneValid();
- dto.setId(999L);
+ Integer squadNumber = 999;
Mockito
- .when(playersRepositoryMock.existsById(999L))
- .thenReturn(false);
+ .when(playersRepositoryMock.findBySquadNumber(squadNumber))
+ .thenReturn(Optional.empty());
// When
- boolean actual = playersService.update(dto);
+ boolean actual = playersService.update(squadNumber, dto);
// Then
- verify(playersRepositoryMock, times(1)).existsById(999L);
+ verify(playersRepositoryMock, times(1)).findBySquadNumber(squadNumber);
verify(playersRepositoryMock, never()).save(any(Player.class));
verify(modelMapperMock, never()).map(dto, Player.class);
then(actual).isFalse();
}
/**
- * Given a PlayerDTO has null ID
+ * Given a null squad number is passed
* When update() is called
- * Then false is returned without checking repository or saving
+ * Then false is returned without hitting the repository
*/
@Test
- void givenNullId_whenUpdate_thenReturnsFalse() {
+ void givenNullSquadNumber_whenUpdate_thenReturnsFalse() {
// Given
PlayerDTO dto = PlayerDTOFakes.createOneValid();
- dto.setId(null);
+ Mockito
+ .when(playersRepositoryMock.findBySquadNumber(null))
+ .thenReturn(Optional.empty());
// When
- boolean actual = playersService.update(dto);
+ boolean actual = playersService.update(null, dto);
// Then
- verify(playersRepositoryMock, never()).existsById(any());
verify(playersRepositoryMock, never()).save(any(Player.class));
verify(modelMapperMock, never()).map(any(), any());
then(actual).isFalse();
@@ -398,39 +399,45 @@ void givenNullId_whenUpdate_thenReturnsFalse() {
/**
* Given a player exists
- * When deleting that player
- * Then the player is deleted and true is returned
+ * When deleting that player by squad number
+ * Then the player is deleted by UUID and true is returned
*/
@Test
void givenPlayerExists_whenDelete_thenReturnsTrue() {
// Given
+ Integer squadNumber = 17;
+ Player entity = PlayerFakes.createAll().stream()
+ .filter(p -> squadNumber.equals(p.getSquadNumber()))
+ .findFirst()
+ .orElseThrow();
Mockito
- .when(playersRepositoryMock.existsById(21L))
- .thenReturn(true);
+ .when(playersRepositoryMock.findBySquadNumber(squadNumber))
+ .thenReturn(Optional.of(entity));
// When
- boolean actual = playersService.delete(21L);
+ boolean actual = playersService.deleteBySquadNumber(squadNumber);
// Then
- verify(playersRepositoryMock, times(1)).existsById(21L);
- verify(playersRepositoryMock, times(1)).deleteById(21L);
+ verify(playersRepositoryMock, times(1)).findBySquadNumber(squadNumber);
+ verify(playersRepositoryMock, times(1)).deleteById(entity.getId());
then(actual).isTrue();
}
/**
- * Given no player exists with a specific ID
+ * Given no player exists with a specific squad number
* When attempting to delete that player
* Then false is returned without deleting
*/
@Test
void givenPlayerDoesNotExist_whenDelete_thenReturnsFalse() {
// Given
+ Integer squadNumber = 999;
Mockito
- .when(playersRepositoryMock.existsById(999L))
- .thenReturn(false);
+ .when(playersRepositoryMock.findBySquadNumber(squadNumber))
+ .thenReturn(Optional.empty());
// When
- boolean actual = playersService.delete(999L);
+ boolean actual = playersService.deleteBySquadNumber(squadNumber);
// Then
- verify(playersRepositoryMock, times(1)).existsById(999L);
- verify(playersRepositoryMock, never()).deleteById(anyLong());
+ verify(playersRepositoryMock, times(1)).findBySquadNumber(squadNumber);
+ verify(playersRepositoryMock, never()).deleteById(any());
then(actual).isFalse();
}
}
diff --git a/src/test/resources/ddl.sql b/src/test/resources/ddl.sql
index ed4ac22..cd73b2a 100644
--- a/src/test/resources/ddl.sql
+++ b/src/test/resources/ddl.sql
@@ -5,15 +5,15 @@
DROP TABLE IF EXISTS players;
CREATE TABLE players (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- firstName TEXT NOT NULL,
- middleName TEXT,
- lastName TEXT NOT NULL,
- dateOfBirth TEXT NOT NULL,
- squadNumber INTEGER NOT NULL UNIQUE,
- position TEXT NOT NULL,
- abbrPosition TEXT NOT NULL,
- team TEXT NOT NULL,
- league TEXT NOT NULL,
- starting11 INTEGER NOT NULL
+ id VARCHAR(36) PRIMARY KEY,
+ squadNumber INTEGER NOT NULL UNIQUE,
+ firstName TEXT NOT NULL,
+ middleName TEXT,
+ lastName TEXT NOT NULL,
+ dateOfBirth TEXT NOT NULL,
+ position TEXT NOT NULL,
+ abbrPosition TEXT NOT NULL,
+ team TEXT NOT NULL,
+ league TEXT NOT NULL,
+ starting11 INTEGER NOT NULL
);
diff --git a/src/test/resources/dml.sql b/src/test/resources/dml.sql
index 13f8558..978216d 100644
--- a/src/test/resources/dml.sql
+++ b/src/test/resources/dml.sql
@@ -1,37 +1,36 @@
-- Test Database Data (DML - Data Manipulation Language)
--- Contains all 26 players from production database EXCEPT Leandro Paredes (ID 19)
+-- Contains all 26 players from production database EXCEPT Leandro Paredes (squadNumber 5)
-- Leandro Paredes will be created during tests (for POST/create operations)
--- Damián Emiliano Martínez (ID 1) will be updated during tests
--- Alejandro Gómez (ID 21) will be deleted during tests
+-- Damián Emiliano Martínez (squadNumber 23) will be updated during tests
+-- Alejandro Gómez (squadNumber 17) will be deleted during tests
--- Starting 11 (IDs 1-11, excluding 19 if applicable)
-INSERT INTO players (id, firstName, middleName, lastName, dateOfBirth, squadNumber, position, abbrPosition, team, league, starting11) VALUES
-(1, 'Damián', 'Emiliano', 'Martínez', '1992-09-02T00:00:00.000Z', 23, 'Goalkeeper', 'GK', 'Aston Villa FC', 'Premier League', 1),
-(2, 'Nahuel', NULL, 'Molina', '1998-04-06T00:00:00.000Z', 26, 'Right-Back', 'RB', 'Atlético Madrid', 'La Liga', 1),
-(3, 'Cristian', 'Gabriel', 'Romero', '1998-04-27T00:00:00.000Z', 13, 'Centre-Back', 'CB', 'Tottenham Hotspur', 'Premier League', 1),
-(4, 'Nicolás', 'Hernán Gonzalo', 'Otamendi', '1988-02-12T00:00:00.000Z', 19, 'Centre-Back', 'CB', 'SL Benfica', 'Liga Portugal', 1),
-(5, 'Nicolás', 'Alejandro', 'Tagliafico', '1992-08-31T00:00:00.000Z', 3, 'Left-Back', 'LB', 'Olympique Lyon', 'Ligue 1', 1),
-(6, 'Ángel', 'Fabián', 'Di María', '1988-02-14T00:00:00.000Z', 11, 'Right Winger', 'RW', 'SL Benfica', 'Liga Portugal', 1),
-(7, 'Rodrigo', 'Javier', 'de Paul', '1994-05-24T00:00:00.000Z', 7, 'Central Midfield', 'CM', 'Atlético Madrid', 'La Liga', 1),
-(8, 'Enzo', 'Jeremías', 'Fernández', '2001-01-17T00:00:00.000Z', 24, 'Central Midfield', 'CM', 'Chelsea FC', 'Premier League', 1),
-(9, 'Alexis', NULL, 'Mac Allister', '1998-12-24T00:00:00.000Z', 20, 'Central Midfield', 'CM', 'Liverpool FC', 'Premier League', 1),
-(10, 'Lionel', 'Andrés', 'Messi', '1987-06-24T00:00:00.000Z', 10, 'Right Winger', 'RW', 'Inter Miami CF', 'Major League Soccer', 1),
-(11, 'Julián', NULL, 'Álvarez', '2000-01-31T00:00:00.000Z', 9, 'Centre-Forward', 'CF', 'Manchester City', 'Premier League', 1);
+-- Starting 11 (id is PRIMARY KEY, squadNumber is UNIQUE)
+INSERT INTO players (id, squadNumber, firstName, middleName, lastName, dateOfBirth, position, abbrPosition, team, league, starting11) VALUES
+('00000000-0000-0000-0000-000000000001', 23, 'Damián', 'Emiliano', 'Martínez', '1992-09-02T00:00:00.000Z', 'Goalkeeper', 'GK', 'Aston Villa FC', 'Premier League', 1),
+('00000000-0000-0000-0000-000000000002', 26, 'Nahuel', NULL, 'Molina', '1998-04-06T00:00:00.000Z', 'Right-Back', 'RB', 'Atlético Madrid', 'La Liga', 1),
+('00000000-0000-0000-0000-000000000003', 13, 'Cristian', 'Gabriel', 'Romero', '1998-04-27T00:00:00.000Z', 'Centre-Back', 'CB', 'Tottenham Hotspur', 'Premier League', 1),
+('00000000-0000-0000-0000-000000000004', 19, 'Nicolás', 'Hernán Gonzalo', 'Otamendi', '1988-02-12T00:00:00.000Z', 'Centre-Back', 'CB', 'SL Benfica', 'Liga Portugal', 1),
+('00000000-0000-0000-0000-000000000005', 3, 'Nicolás', 'Alejandro', 'Tagliafico', '1992-08-31T00:00:00.000Z', 'Left-Back', 'LB', 'Olympique Lyon', 'Ligue 1', 1),
+('00000000-0000-0000-0000-000000000006', 11, 'Ángel', 'Fabián', 'Di María', '1988-02-14T00:00:00.000Z', 'Right Winger', 'RW', 'SL Benfica', 'Liga Portugal', 1),
+('00000000-0000-0000-0000-000000000007', 7, 'Rodrigo', 'Javier', 'de Paul', '1994-05-24T00:00:00.000Z', 'Central Midfield', 'CM', 'Atlético Madrid', 'La Liga', 1),
+('00000000-0000-0000-0000-000000000008', 24, 'Enzo', 'Jeremías', 'Fernández', '2001-01-17T00:00:00.000Z', 'Central Midfield', 'CM', 'Chelsea FC', 'Premier League', 1),
+('00000000-0000-0000-0000-000000000009', 20, 'Alexis', NULL, 'Mac Allister', '1998-12-24T00:00:00.000Z', 'Central Midfield', 'CM', 'Liverpool FC', 'Premier League', 1),
+('00000000-0000-0000-0000-000000000010', 10, 'Lionel', 'Andrés', 'Messi', '1987-06-24T00:00:00.000Z', 'Right Winger', 'RW', 'Inter Miami CF', 'Major League Soccer', 1),
+('00000000-0000-0000-0000-000000000011', 9, 'Julián', NULL, 'Álvarez', '2000-01-31T00:00:00.000Z', 'Centre-Forward', 'CF', 'Manchester City', 'Premier League', 1);
--- Substitutes (IDs 12-26, excluding 19)
-INSERT INTO players (id, firstName, middleName, lastName, dateOfBirth, squadNumber, position, abbrPosition, team, league, starting11) VALUES
-(12, 'Franco', 'Daniel', 'Armani', '1986-10-16T00:00:00.000Z', 1, 'Goalkeeper', 'GK', 'River Plate', 'Copa de la Liga', 0),
-(13, 'Gerónimo', NULL, 'Rulli', '1992-05-20T00:00:00.000Z', 12, 'Goalkeeper', 'GK', 'Ajax Amsterdam', 'Eredivisie', 0),
-(14, 'Juan', 'Marcos', 'Foyth', '1998-01-12T00:00:00.000Z', 2, 'Right-Back', 'RB', 'Villarreal', 'La Liga', 0),
-(15, 'Gonzalo', 'Ariel', 'Montiel', '1997-01-01T00:00:00.000Z', 4, 'Right-Back', 'RB', 'Nottingham Forest', 'Premier League', 0),
-(16, 'Germán', 'Alejo', 'Pezzella', '1991-06-27T00:00:00.000Z', 6, 'Centre-Back', 'CB', 'Real Betis Balompié', 'La Liga', 0),
-(17, 'Marcos', 'Javier', 'Acuña', '1991-10-28T00:00:00.000Z', 8, 'Left-Back', 'LB', 'Sevilla FC', 'La Liga', 0),
-(18, 'Lisandro', NULL, 'Martínez', '1998-01-18T00:00:00.000Z', 25, 'Centre-Back', 'CB', 'Manchester United', 'Premier League', 0),
--- ID 19 (Leandro Paredes) intentionally skipped - will be created during tests
-(20, 'Exequiel', 'Alejandro', 'Palacios', '1998-10-05T00:00:00.000Z', 14, 'Central Midfield', 'CM', 'Bayer 04 Leverkusen', 'Bundesliga', 0),
-(21, 'Alejandro', 'Darío', 'Gómez', '1988-02-15T00:00:00.000Z', 17, 'Left Winger', 'LW', 'AC Monza', 'Serie A', 0),
-(22, 'Guido', NULL, 'Rodríguez', '1994-04-12T00:00:00.000Z', 18, 'Defensive Midfield', 'DM', 'Real Betis Balompié', 'La Liga', 0),
-(23, 'Ángel', 'Martín', 'Correa', '1995-03-09T00:00:00.000Z', 15, 'Right Winger', 'RW', 'Atlético Madrid', 'La Liga', 0),
-(24, 'Thiago', 'Ezequiel', 'Almada', '2001-04-26T00:00:00.000Z', 16, 'Attacking Midfield', 'AM', 'Atlanta United FC', 'Major League Soccer', 0),
-(25, 'Paulo', 'Exequiel', 'Dybala', '1993-11-15T00:00:00.000Z', 21, 'Second Striker', 'SS', 'AS Roma', 'Serie A', 0),
-(26, 'Lautaro', 'Javier', 'Martínez', '1997-08-22T00:00:00.000Z', 22, 'Centre-Forward', 'CF', 'Inter Milan', 'Serie A', 0);
+-- Substitutes (squad number 5 intentionally skipped - Leandro Paredes will be created during tests)
+INSERT INTO players (id, squadNumber, firstName, middleName, lastName, dateOfBirth, position, abbrPosition, team, league, starting11) VALUES
+('00000000-0000-0000-0000-000000000012', 1, 'Franco', 'Daniel', 'Armani', '1986-10-16T00:00:00.000Z', 'Goalkeeper', 'GK', 'River Plate', 'Copa de la Liga', 0),
+('00000000-0000-0000-0000-000000000013', 12, 'Gerónimo', NULL, 'Rulli', '1992-05-20T00:00:00.000Z', 'Goalkeeper', 'GK', 'Ajax Amsterdam', 'Eredivisie', 0),
+('00000000-0000-0000-0000-000000000014', 2, 'Juan', 'Marcos', 'Foyth', '1998-01-12T00:00:00.000Z', 'Right-Back', 'RB', 'Villarreal', 'La Liga', 0),
+('00000000-0000-0000-0000-000000000015', 4, 'Gonzalo', 'Ariel', 'Montiel', '1997-01-01T00:00:00.000Z', 'Right-Back', 'RB', 'Nottingham Forest', 'Premier League', 0),
+('00000000-0000-0000-0000-000000000016', 6, 'Germán', 'Alejo', 'Pezzella', '1991-06-27T00:00:00.000Z', 'Centre-Back', 'CB', 'Real Betis Balompié', 'La Liga', 0),
+('00000000-0000-0000-0000-000000000017', 8, 'Marcos', 'Javier', 'Acuña', '1991-10-28T00:00:00.000Z', 'Left-Back', 'LB', 'Sevilla FC', 'La Liga', 0),
+('00000000-0000-0000-0000-000000000018', 25, 'Lisandro', NULL, 'Martínez', '1998-01-18T00:00:00.000Z', 'Centre-Back', 'CB', 'Manchester United', 'Premier League', 0),
+('00000000-0000-0000-0000-000000000020', 14, 'Exequiel', 'Alejandro', 'Palacios', '1998-10-05T00:00:00.000Z', 'Central Midfield', 'CM', 'Bayer 04 Leverkusen', 'Bundesliga', 0),
+('00000000-0000-0000-0000-000000000021', 17, 'Alejandro', 'Darío', 'Gómez', '1988-02-15T00:00:00.000Z', 'Left Winger', 'LW', 'AC Monza', 'Serie A', 0),
+('00000000-0000-0000-0000-000000000022', 18, 'Guido', NULL, 'Rodríguez', '1994-04-12T00:00:00.000Z', 'Defensive Midfield', 'DM', 'Real Betis Balompié', 'La Liga', 0),
+('00000000-0000-0000-0000-000000000023', 15, 'Ángel', 'Martín', 'Correa', '1995-03-09T00:00:00.000Z', 'Right Winger', 'RW', 'Atlético Madrid', 'La Liga', 0),
+('00000000-0000-0000-0000-000000000024', 16, 'Thiago', 'Ezequiel', 'Almada', '2001-04-26T00:00:00.000Z', 'Attacking Midfield', 'AM', 'Atlanta United FC', 'Major League Soccer', 0),
+('00000000-0000-0000-0000-000000000025', 21, 'Paulo', 'Exequiel', 'Dybala', '1993-11-15T00:00:00.000Z', 'Second Striker', 'SS', 'AS Roma', 'Serie A', 0),
+('00000000-0000-0000-0000-000000000026', 22, 'Lautaro', 'Javier', 'Martínez', '1997-08-22T00:00:00.000Z', 'Centre-Forward', 'CF', 'Inter Milan', 'Serie A', 0);
diff --git a/storage/players-sqlite3.db b/storage/players-sqlite3.db
index 14a1d8e..d7a8987 100644
Binary files a/storage/players-sqlite3.db and b/storage/players-sqlite3.db differ