Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,40 @@ Raw OpenAPI specification (JSON):
http://localhost:8080/v3/api-docs
```

### Available endpoints

#### Generate and persist random users

```http
GET /random-users?count=500
```

#### Update an existing user

```http
PUT /random-users/{id}
Content-Type: application/json
```

Example body:

```json
{
"gender": "female",
"firstname": "Albert",
"lastname": "Bing",
"civility": "Mrs",
"email": "albert.bing@example.com",
"phone": "123456789",
"picture": "pic.jpg",
"nat": "FR"
}
```

Responses:
- `200` if the user was updated successfully
- `404` if the user id does not exist

---

## 🔍 Monitoring (Actuator)
Expand Down Expand Up @@ -281,7 +315,7 @@ This project consumes the public **Random User Generator** API:
- [x] [Add PostgreSQL database with docker](https://github.com/XPEHO/spring_boot_java_random_user/issues/6)
- [X] [Add this endpoint get /user/random](https://github.com/XPEHO/spring_boot_java_random_user/issues/5)
- [ ] [Add this endpoint get /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/8)
- [ ] [Add this endpoint put /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/9)
- [X] [Add this endpoint put /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/9)
- [ ] [Add this endpoint delete /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/10)
- [ ] [Add this endpoint post /user](https://github.com/XPEHO/spring_boot_java_random_user/issues/11)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;

@Service
Expand All @@ -29,4 +30,16 @@ public List<UserEntity> saveAll(List<UserEntity> users) {
.map(userConverter::toDomain)
.toList();
}

@Override
public Optional<UserEntity> getById(long id) {
return userRepository.findById(id)
.map(userConverter::toDomain);
}

@Override
public UserEntity save(UserEntity user) {
User savedUser = userRepository.save(userConverter.toDao(user));
return userConverter.toDomain(savedUser);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.xpeho.spring_boot_java_random_user.domain.exceptions;

public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(long id) {
super("User not found with id: " + id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import java.util.List;
import java.util.Optional;

public interface UserService {
List<UserEntity> saveAll(List<UserEntity> users);

Optional<UserEntity> getById(long id);

UserEntity save(UserEntity user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.xpeho.spring_boot_java_random_user.domain.usecases;

import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException;
import com.xpeho.spring_boot_java_random_user.domain.services.UserService;
import org.springframework.stereotype.Service;

@Service
public class UpdateRandomUserUseCase {
private final UserService userService;

public UpdateRandomUserUseCase(UserService userService) {
this.userService = userService;
}

public UserEntity execute(int id, UserEntity user) {
UserEntity existingUser = userService.getById(id)
.orElseThrow(() -> new UserNotFoundException(id));

UserEntity updatedUser = new UserEntity(
existingUser.id(),
user.gender(),
user.firstname(),
user.lastname(),
user.civility(),
user.email(),
user.phone(),
user.picture(),
user.nat()
);
Comment thread
MayuriXx marked this conversation as resolved.

return userService.save(updatedUser);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

Expand Down Expand Up @@ -37,4 +40,22 @@ ResponseEntity<List<UserEntity>> getRandomUsers(
@Max(5000)
int count
);
@PutMapping("/{id}")
@Operation(
summary = "Modify a random user",
description = "Giving a random user id, modify the content of the user",
parameters = {
@Parameter(name = "id", description = "id of the requested user"),
@Parameter(name= "UserEntity", description = "changeable parameters")
}
)
@ApiResponse(responseCode = "200", description = "User successfully modified and saved")
@ApiResponse(responseCode = "404", description = "The requested user does not exist")
@ApiResponse(responseCode = "500", description = "Internal server error")
ResponseEntity<UserEntity> updateRandomUser(
@PathVariable
int id,
@RequestBody
UserEntity user
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.xpeho.spring_boot_java_random_user.presentation.handlers;

import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException;
import com.xpeho.spring_boot_java_random_user.domain.usecases.FetchAndSaveRandomUsersUseCase;
import com.xpeho.spring_boot_java_random_user.domain.usecases.UpdateRandomUserUseCase;
import com.xpeho.spring_boot_java_random_user.presentation.controllers.UserController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -20,9 +22,14 @@ public class UserHandler implements UserController {
private static final Logger logger = LoggerFactory.getLogger(UserHandler.class);

private final FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase;
private final UpdateRandomUserUseCase updateRandomUserUseCase;

public UserHandler(FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase) {
public UserHandler(
FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase,
UpdateRandomUserUseCase updateRandomUserUseCase
) {
this.fetchAndSaveRandomUsersUseCase = fetchAndSaveRandomUsersUseCase;
this.updateRandomUserUseCase = updateRandomUserUseCase;
}

@Override
Expand All @@ -36,4 +43,15 @@ public ResponseEntity<List<UserEntity>> getRandomUsers(int count) {
.body(emptyList());
}
}
}
@Override
public ResponseEntity<UserEntity> updateRandomUser(int id, UserEntity user) {
try {
UserEntity savedUser = updateRandomUserUseCase.execute(id, user);
return ResponseEntity.ok(savedUser);
} catch (UserNotFoundException e) {
logger.warn("warning: the requested user does not exist : {}", e.getMessage(), e);
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.xpeho.spring_boot_java_random_user.data.services;

import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter;
import com.xpeho.spring_boot_java_random_user.data.models.db.User;
import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository;
import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class UserServiceImplTest {
private UserRepository userRepository;
private UserConverter userConverter;
private UserServiceImpl userService;

@BeforeEach
void setUp() {
userRepository = mock(UserRepository.class);
userConverter = mock(UserConverter.class);
userService = new UserServiceImpl(userRepository, userConverter);
}

@Test
@DisplayName("Should return mapped user when id exists")
void shouldReturnMappedUserWhenIdExists() {
User dao = new User();
dao.setId(1L);
dao.setFirstname("John");

UserEntity expected = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR");

when(userRepository.findById(1L)).thenReturn(Optional.of(dao));
when(userConverter.toDomain(dao)).thenReturn(expected);

Optional<UserEntity> result = userService.getById(1L);

assertTrue(result.isPresent());
assertEquals(expected, result.get());
verify(userRepository).findById(1L);
verify(userConverter).toDomain(dao);
}

@Test
@DisplayName("Should return empty optional when id does not exist")
void shouldReturnEmptyOptionalWhenIdDoesNotExist() {
when(userRepository.findById(2L)).thenReturn(Optional.empty());

Optional<UserEntity> result = userService.getById(2L);

assertTrue(result.isEmpty());
verify(userRepository).findById(2L);
}

@Test
@DisplayName("Should save mapped user and return mapped domain entity")
void shouldSaveMappedUserAndReturnMappedDomainEntity() {
UserEntity input = new UserEntity(3L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US");

User daoToSave = new User();
daoToSave.setId(3L);
daoToSave.setFirstname("Alice");

User savedDao = new User();
savedDao.setId(3L);
savedDao.setFirstname("Alice");

UserEntity expected = new UserEntity(3L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US");

when(userConverter.toDao(input)).thenReturn(daoToSave);
when(userRepository.save(daoToSave)).thenReturn(savedDao);
when(userConverter.toDomain(savedDao)).thenReturn(expected);

UserEntity result = userService.save(input);

assertEquals(expected, result);
verify(userConverter).toDao(input);
verify(userRepository).save(daoToSave);
verify(userConverter).toDomain(savedDao);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.xpeho.spring_boot_java_random_user.domain.usecases;

import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException;
import com.xpeho.spring_boot_java_random_user.domain.services.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class UpdateRandomUserUseCaseTest {
private UserService userService;
private UpdateRandomUserUseCase useCase;

@BeforeEach
void setUp() {
userService = mock(UserService.class);
useCase = new UpdateRandomUserUseCase(userService);
}

@Test
@DisplayName("Should update an existing user and preserve its id")
void shouldUpdateExistingUserAndPreserveItsId() {
UserEntity existingUser = new UserEntity(
42L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR"
);
UserEntity payload = new UserEntity(
null, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US"
);
UserEntity savedUser = new UserEntity(
42L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US"
);

when(userService.getById(42)).thenReturn(Optional.of(existingUser));
when(userService.save(new UserEntity(
42L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US"
))).thenReturn(savedUser);

UserEntity result = useCase.execute(42, payload);

assertEquals(savedUser, result);
verify(userService).getById(42);
verify(userService).save(new UserEntity(
42L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US"
));
}

@Test
@DisplayName("Should throw when updating a user that does not exist")
void shouldThrowWhenUserDoesNotExist() {
UserEntity entity = new UserEntity(
null, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US"
);

when(userService.getById(99)).thenReturn(Optional.empty());

UserNotFoundException exception = assertThrows(
UserNotFoundException.class,
() -> useCase.execute(99, entity)
);

assertEquals("User not found with id: 99", exception.getMessage());
verify(userService).getById(99);
verify(userService, never()).save(any(UserEntity.class));
}
}