Compare commits
12 Commits
frontend
...
feature/in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed95090be8 | ||
|
|
f3dcc948b0 | ||
|
|
946387ae89 | ||
|
|
340d3b2a8a | ||
|
|
d4bd59141b | ||
|
|
cbeac683d8 | ||
|
|
45665a77d8 | ||
|
|
db5b518a8a | ||
|
|
fdfea5223b | ||
|
|
550564ee2b | ||
|
|
bdc99b6f84 | ||
|
|
ea81148464 |
14
README.md
Normal file
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## Reactive time sheet app
|
||||
|
||||
It's simple REST API using Spring Webflux, MongoDB and layered architecture using basics of domain driven design approach. Exact description of this project you can find on my blog - [link](https://szymonsawicki.net/?p=60)
|
||||
|
||||
## How to launch the application ?
|
||||
|
||||
Installed Docker and Postman will be needed. At the beginning ou must download reactive-timesheet-app.zip archive from the root directory in the repository. In hte terminal enter command:
|
||||
|
||||
`docker compose up -d --build`
|
||||
|
||||
In the zip archive you can find postman collection which have set of request which are prepared for tests. Don't forget to update id in the header :)
|
||||
|
||||
|
||||
|
||||
22
pom.xml
22
pom.xml
@@ -16,6 +16,17 @@
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-bom</artifactId>
|
||||
<version>1.17.1</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -47,6 +58,17 @@
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>1.17.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mongodb</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
165
postman_collection.json
Normal file
165
postman_collection.json
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "809992e0-63cb-4d98-ae6b-30e259a35348",
|
||||
"name": "Reactive Timesheet App",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Get user by id",
|
||||
"protocolProfileBehavior": {
|
||||
"disableBodyPruning": true
|
||||
},
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\r\n \"username\" : \"John Test\",\r\n \"password\" : \"223eded3\",\r\n \"passwordConfirmation\" : \"223eded3\",\r\n \"role\" : \"LEAD\"\r\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:8080/users/id/62378d381507b40858b58695",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "8080",
|
||||
"path": [
|
||||
"users",
|
||||
"id",
|
||||
"62378d381507b40858b58695"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get user by name",
|
||||
"protocolProfileBehavior": {
|
||||
"disableBodyPruning": true
|
||||
},
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\r\n \"username\" : \"John Test\",\r\n \"password\" : \"223eded3\",\r\n \"passwordConfirmation\" : \"223eded3\",\r\n \"role\" : \"LEAD\"\r\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:8080/users/id/62378d381507b40858b58695",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "8080",
|
||||
"path": [
|
||||
"users",
|
||||
"id",
|
||||
"62378d381507b40858b58695"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create user",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\r\n \"username\" : \"John Test\",\r\n \"password\" : \"223eded3\",\r\n \"passwordConfirmation\" : \"223eded3\",\r\n \"role\" : \"LEAD\"\r\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:8080/users/",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "8080",
|
||||
"path": [
|
||||
"users",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create team",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": []
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "delete team",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": []
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "delete user",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": []
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "creating of the new time entry",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\r\n \"date\" : \"1999-12-12\",\r\n \"timeFrom\" : \" \",\r\n \"timeTo\" : \" \",\r\n \"user\" : {\r\n \"username\": \"Arnold Test\",\r\n \"password\": \"223eded3\",\r\n \"role\": \"DEVELOPER\"\r\n },\r\n \"category\": \"DEVELOPMENT\" \r\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "http://localhost:8080/team-entries/",
|
||||
"protocol": "http",
|
||||
"host": [
|
||||
"localhost"
|
||||
],
|
||||
"port": "8080",
|
||||
"path": [
|
||||
"team-entries",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "get team by id",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": []
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
reactive-timesheet-app.zip
Normal file
BIN
reactive-timesheet-app.zip
Normal file
Binary file not shown.
@@ -10,6 +10,7 @@ import net.szymonsawicki.reactivetimesheetapp.domain.team.dto.GetTeamDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.repository.TeamRepository;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.repository.UserRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Service
|
||||
@@ -20,38 +21,38 @@ public class TeamService {
|
||||
private final TeamRepository teamRepository;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public Flux<GetTeamDto> findAllTeams() {
|
||||
return teamRepository.findAll()
|
||||
.flatMap(team -> Flux.just(team.toGetTeamDto()));
|
||||
}
|
||||
|
||||
public Mono<GetTeamDto> findById(String teamId) {
|
||||
return teamRepository.findById(teamId)
|
||||
.map(Team::toGetTeamDto)
|
||||
.switchIfEmpty(Mono.error(new TeamServiceException("id doesn't exist")));
|
||||
.switchIfEmpty(Mono.error(new TeamServiceException("Team with given id doesn't exist")));
|
||||
}
|
||||
|
||||
public Mono<GetTeamDto> findByName(String name) {
|
||||
return teamRepository.findByName(name)
|
||||
.map(Team::toGetTeamDto)
|
||||
.switchIfEmpty(Mono.error(new TeamServiceException("Username doesn't exist")));
|
||||
.switchIfEmpty(Mono.error(new TeamServiceException("Team with given name doesn't exist")));
|
||||
}
|
||||
|
||||
public Mono<GetTeamDto> addTeam(Mono<CreateTeamDto> createTeamDtoMono) {
|
||||
|
||||
return createTeamDtoMono
|
||||
.flatMap(createTeamDto -> teamRepository.findByName(createTeamDto.name())
|
||||
.map(team -> {
|
||||
log.error("Team with name " + createTeamDto.name() + " already exists");
|
||||
return team.toGetTeamDto();
|
||||
})
|
||||
.switchIfEmpty(createTeamWithMembers(createTeamDto))
|
||||
);
|
||||
.doOnEach(team -> log.error("Team with name " + createTeamDto.name() + " already exists"))
|
||||
.map(Team::toGetTeamDto)
|
||||
.switchIfEmpty(Mono.defer(() -> createTeamWithMembers(createTeamDto))));
|
||||
}
|
||||
|
||||
private Mono<GetTeamDto> createTeamWithMembers(CreateTeamDto createTeamDto) {
|
||||
|
||||
var teamToInsert = createTeamDto.toTeam();
|
||||
|
||||
// at first team is inserted into db and all its member are updated with the new teamId
|
||||
|
||||
return teamRepository
|
||||
.save(teamToInsert)
|
||||
.save(createTeamDto.toTeam())
|
||||
.flatMap(insertedTeam -> {
|
||||
var membersToInsert = createTeamDto
|
||||
.members()
|
||||
@@ -64,12 +65,9 @@ public class TeamService {
|
||||
return userRepository
|
||||
.saveAll(membersToInsert)
|
||||
.collectList()
|
||||
.flatMap(insertedUsers -> {
|
||||
var teamToInsertWithMembers = insertedTeam.withMembers(insertedUsers);
|
||||
return teamRepository
|
||||
.save(teamToInsertWithMembers)
|
||||
.map(Team::toGetTeamDto);
|
||||
});
|
||||
.flatMap(insertedUsers -> teamRepository
|
||||
.save(insertedTeam.withMembers(insertedUsers))
|
||||
.map(Team::toGetTeamDto));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,5 +92,4 @@ public class TeamService {
|
||||
})
|
||||
.switchIfEmpty(Mono.error(new TeamServiceException("cannot find team to delete")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class TimeEntryService {
|
||||
return userRepository
|
||||
.findById(timeEntryToCheck.user().id())
|
||||
.hasElement()
|
||||
.flatMap(isUserPresent -> Boolean.TRUE.equals(isUserPresent)
|
||||
.flatMap(isUserPresent -> isUserPresent
|
||||
?
|
||||
findCollisions(timeEntryToCheck)
|
||||
:
|
||||
@@ -41,6 +41,9 @@ public class TimeEntryService {
|
||||
}
|
||||
|
||||
private Mono<CreateTimeEntryDto> findCollisions(CreateTimeEntryDto timeEntryToCheck) {
|
||||
|
||||
// TODO proper implementation of collision check (doesn't work at the moment). Create isAvailable() method in TimeEntry domain class
|
||||
|
||||
return timeEntryRepository.findAllByUser(timeEntryToCheck.user().toUser())
|
||||
.filter(entry -> !TimeEntryUtils.toTimeFrom.apply(entry).isAfter(timeEntryToCheck.timeTo())
|
||||
&& !TimeEntryUtils.toTimeTo.apply(entry).isBefore(timeEntryToCheck.timeFrom()))
|
||||
|
||||
@@ -38,7 +38,7 @@ public class UserService {
|
||||
.flatMap(createUserDto -> userRepository
|
||||
.findByUsername(createUserDto.username())
|
||||
.hasElement()
|
||||
.flatMap(isUserPresent -> Boolean.TRUE.equals(isUserPresent)
|
||||
.flatMap(isUserPresent -> isUserPresent
|
||||
?
|
||||
Mono.error(new UserServiceException("user with username " + createUserDto.username() + " already exists"))
|
||||
:
|
||||
|
||||
@@ -13,5 +13,7 @@ public interface CrudRepository<T, ID> {
|
||||
Mono<T> save(T t);
|
||||
Mono<T> delete(ID id);
|
||||
|
||||
Mono<Void> deleteAll();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class Team {
|
||||
return TeamEntity.builder()
|
||||
.id(id)
|
||||
.name(name)
|
||||
.members(members)
|
||||
.members(members == null ? null : members.stream().map(User::toEntity).toList())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@ import java.util.function.Function;
|
||||
public interface TeamUtils {
|
||||
|
||||
Function<Team, String> toId = team -> team.id;
|
||||
Function<Team, String> toName = team -> team.name;
|
||||
Function<Team, List<User>> toMembers = team -> team.members;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,6 @@ public class User {
|
||||
id,
|
||||
username,
|
||||
password,
|
||||
role);
|
||||
role, teamId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ package net.szymonsawicki.reactivetimesheetapp.domain.user.dto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.User;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.type.Role;
|
||||
|
||||
public record GetUserDto(String id, String username, String password, Role role) {
|
||||
public record GetUserDto(String id, String username, String password, Role role, String teamId) {
|
||||
public User toUser() {
|
||||
return User.builder()
|
||||
.id(id)
|
||||
.username(username)
|
||||
.password(password)
|
||||
.role(role)
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ public class TeamEntity {
|
||||
String id;
|
||||
|
||||
String name;
|
||||
List<User> members;
|
||||
List<UserEntity> members;
|
||||
|
||||
public Team toTeam() {
|
||||
return Team.builder()
|
||||
.id(id)
|
||||
.name(name)
|
||||
.members(members)
|
||||
.members(members.stream().map(UserEntity::toUser).toList())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package net.szymonsawicki.reactivetimesheetapp.infrastructure.persistence.reposi
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.Team;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.repository.TeamRepository;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.User;
|
||||
import net.szymonsawicki.reactivetimesheetapp.infrastructure.persistence.dao.TeamDao;
|
||||
import net.szymonsawicki.reactivetimesheetapp.infrastructure.persistence.exception.PersistenceException;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -51,6 +50,11 @@ public class TeamsRepositoryImpl implements TeamRepository {
|
||||
.switchIfEmpty(Mono.error(new PersistenceException("cannot find team to delete")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteAll() {
|
||||
return teamDao.deleteAll();
|
||||
}
|
||||
|
||||
public Mono<Team> findByName(String name) {
|
||||
return teamDao.findByName(name)
|
||||
.flatMap(teamEntity -> Mono.just(teamEntity.toTeam()));
|
||||
|
||||
@@ -52,6 +52,11 @@ public class TimeEntryRepositoryImpl implements TimeEntryRepository {
|
||||
.switchIfEmpty(Mono.error(new PersistenceException("cannot find team to delete")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteAll() {
|
||||
return timeEntryDao.deleteAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<TimeEntry> findAllByUser(User user) {
|
||||
return timeEntryDao.findAllByUser(user.toEntity())
|
||||
|
||||
@@ -74,4 +74,9 @@ public class UserRepositoryImpl implements UserRepository {
|
||||
return userDao
|
||||
.deleteAll(users.stream().map(User::toEntity).toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> deleteAll() {
|
||||
return userDao.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,14 @@ public class Routing {
|
||||
RouterFunctions.route(GET("/{name}").and(accept(MediaType.APPLICATION_JSON)), teamHandlers::findByName)
|
||||
.andRoute(GET("/id/{id}").and(accept(MediaType.APPLICATION_JSON)), teamHandlers::findById)
|
||||
.andRoute(POST("/").and(accept(MediaType.APPLICATION_JSON)), teamHandlers::addTeam)
|
||||
.andRoute(DELETE("/{id}").and(accept(MediaType.APPLICATION_JSON)), teamHandlers::deleteTeam))
|
||||
.andNest(path("/users"),
|
||||
RouterFunctions.route(GET("/id/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandlers::findById)
|
||||
.andRoute(GET("/{username}").and(accept(MediaType.APPLICATION_JSON)), userHandlers::findByUsername)
|
||||
.andRoute(POST("/").and(accept(MediaType.APPLICATION_JSON)), userHandlers::createUser)
|
||||
.andRoute(DELETE("/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandlers::deleteUser))
|
||||
.andNest(path("/time-entries"),
|
||||
RouterFunctions.route(POST("/").and(accept(MediaType.APPLICATION_JSON)), timeEntryHandlers::addTimeEntry));
|
||||
.andRoute(DELETE("/{id}").and(accept(MediaType.APPLICATION_JSON)), teamHandlers::deleteTeam)
|
||||
.andNest(path("/reset"), RouterFunctions.route(GET("/reset").and(accept(MediaType.APPLICATION_JSON)), teamHandlers::findByName))
|
||||
.andNest(path("/users"),
|
||||
RouterFunctions.route(GET("/id/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandlers::findById)
|
||||
.andRoute(GET("/{username}").and(accept(MediaType.APPLICATION_JSON)), userHandlers::findByUsername)
|
||||
.andRoute(POST("/").and(accept(MediaType.APPLICATION_JSON)), userHandlers::createUser)
|
||||
.andRoute(DELETE("/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandlers::deleteUser))
|
||||
.andNest(path("/time-entries"),
|
||||
RouterFunctions.route(POST("/").and(accept(MediaType.APPLICATION_JSON)), timeEntryHandlers::addTimeEntry)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package net.szymonsawicki.reactivetimesheetapp.web.handler;
|
||||
|
||||
import jdk.jfr.ContentType;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.szymonsawicki.reactivetimesheetapp.application.service.TeamService;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.dto.CreateTeamDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.dto.GetTeamDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.web.config.GlobalRoutingHandler;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
@@ -15,6 +20,13 @@ import reactor.core.publisher.Mono;
|
||||
public class TeamHandlers {
|
||||
private final TeamService teamService;
|
||||
|
||||
public Mono<ServerResponse> findAllTeams(ServerRequest serverRequest) {
|
||||
return ServerResponse
|
||||
.status(HttpStatus.OK)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(teamService.findAllTeams(),GetTeamDto.class);
|
||||
}
|
||||
|
||||
public Mono<ServerResponse> findById(ServerRequest serverRequest) {
|
||||
var teamId = serverRequest.pathVariable("id");
|
||||
return GlobalRoutingHandler.doRequest(teamService.findById(teamId), HttpStatus.OK);
|
||||
|
||||
8
src/main/resources/application-local.yml
Normal file
8
src/main/resources/application-local.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
spring:
|
||||
application:
|
||||
name: reactive-timesheet-app
|
||||
data:
|
||||
mongodb:
|
||||
database: db_1
|
||||
host: localhost
|
||||
port: 27017
|
||||
3
src/main/resources/application-test.yml
Normal file
3
src/main/resources/application-test.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
spring:
|
||||
application:
|
||||
name: reactive-timesheet-app
|
||||
@@ -1,13 +0,0 @@
|
||||
package net.szymonsawicki.reactivetimesheetapp;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class ReactiveTimesheetAppApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package net.szymonsawicki.reactivetimesheetapp.application.service;
|
||||
|
||||
import net.szymonsawicki.reactivetimesheetapp.application.service.exception.TeamServiceException;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.Team;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.TeamUtils;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.dto.CreateTeamDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.dto.GetTeamDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.repository.TeamRepository;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.User;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.UserUtils;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.dto.GetUserDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.repository.UserRepository;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.type.Role;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.BodyInserters;
|
||||
import org.testcontainers.containers.MongoDBContainer;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@SpringBootTest
|
||||
@Testcontainers
|
||||
@AutoConfigureWebTestClient
|
||||
@ActiveProfiles("test")
|
||||
public class TeamServiceIT {
|
||||
|
||||
/* @ClassRule
|
||||
private static final MongoDBContainer MONGO_DB_CONTAINER = TimesheetAppMongoDbContainer.getInstance();
|
||||
*/
|
||||
|
||||
private static final MongoDBContainer MONGO_DB_CONTAINER =
|
||||
new MongoDBContainer("mongo:4.2.8");
|
||||
|
||||
@Autowired
|
||||
private TeamRepository teamRepository;
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
@Autowired
|
||||
private WebTestClient webClient;
|
||||
|
||||
@BeforeEach
|
||||
void clearDb() {
|
||||
userRepository.deleteAll();
|
||||
teamRepository.deleteAll();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void setUpAll() {
|
||||
MONGO_DB_CONTAINER.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownAll() {
|
||||
if (!MONGO_DB_CONTAINER.isShouldBeReused()) {
|
||||
MONGO_DB_CONTAINER.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTeamOnGetById() {
|
||||
|
||||
String teamId = "2r872394r578";
|
||||
String teamName = "Some team";
|
||||
String username = "testsusrname";
|
||||
Role role = Role.DEVELOPER;
|
||||
|
||||
var member = User.builder()
|
||||
.username(username)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var team = Team.builder()
|
||||
.name(teamName)
|
||||
.members(List.of(member))
|
||||
.build();
|
||||
|
||||
var insertedTeam = teamRepository.save(team);
|
||||
|
||||
var insertedTeamId = TeamUtils.toId.apply(insertedTeam.block());
|
||||
|
||||
webClient.get().uri("/teams/id/{id}", insertedTeamId)
|
||||
.header(HttpHeaders.ACCEPT, "application/json")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBodyList(GetTeamDto.class);
|
||||
|
||||
StepVerifier.create(teamRepository.findById(insertedTeamId))
|
||||
.expectNextMatches(t -> TeamUtils.toMembers.apply(t).size() == 1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorOnGetByIdWhenNotExist() {
|
||||
|
||||
webClient.get().uri("/teams/id/{id}", "test123")
|
||||
.header(HttpHeaders.ACCEPT, "application/json")
|
||||
.exchange()
|
||||
.expectStatus().is5xxServerError()
|
||||
.expectBodyList(TeamServiceException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
void shouldCreateTeamWithTwoMembersOnCreate() {
|
||||
|
||||
String teamName = "Some test team2";
|
||||
String username1 = "testsusrname1";
|
||||
String username2 = "testsusrname2";
|
||||
String password1 = "kongamafonga";
|
||||
String password2 = "kupaladupa.";
|
||||
Role role = Role.DEVELOPER;
|
||||
|
||||
var member1 = new GetUserDto(null, username1, password1, role, null);
|
||||
// var member2 = new GetUserDto(null, username2, password2, role, null);
|
||||
|
||||
var createTeamDto = new CreateTeamDto(teamName, List.of(member1));
|
||||
|
||||
webClient.post().uri("/teams/")
|
||||
.header(HttpHeaders.ACCEPT, "application/json")
|
||||
.body(BodyInserters.fromValue(createTeamDto))
|
||||
.exchange()
|
||||
.expectStatus().isCreated()
|
||||
.expectBodyList(GetTeamDto.class);
|
||||
|
||||
StepVerifier.create(teamRepository.findByName(teamName))
|
||||
.assertNext(t -> {
|
||||
Assertions.assertThat(TeamUtils.toName.apply(t)).isEqualTo(teamName);
|
||||
Assertions.assertThat(TeamUtils.toMembers.apply(t).size()).isEqualTo(2);
|
||||
Assertions.assertThat(TeamUtils.toMembers.apply(t).stream().map(UserUtils.toUsername).toList())
|
||||
.containsAll(List.of(username1, username2));
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
var insertedTeamId = TeamUtils.toId.apply(teamRepository.findByName(teamName).block());
|
||||
|
||||
StepVerifier.create(userRepository.findByUsername(username1))
|
||||
.expectNextMatches(user -> UserUtils.toTeamId.apply(user).equals(insertedTeamId))
|
||||
.verifyComplete();
|
||||
|
||||
StepVerifier.create(userRepository.findByUsername(username2))
|
||||
.expectNextMatches(user -> UserUtils.toTeamId.apply(user).equals(insertedTeamId))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnExistingTeamOnCreateWhenNameTaken() {
|
||||
|
||||
String teamName = "Some test team2";
|
||||
String username1 = "testsusrname1";
|
||||
String password1 = "sdcvdfvbgdf";
|
||||
Role role = Role.DEVELOPER;
|
||||
|
||||
var team = Team.builder()
|
||||
.name(teamName)
|
||||
.members(null)
|
||||
.build();
|
||||
|
||||
var insertedTeam = teamRepository.save(team);
|
||||
|
||||
var member1 = new GetUserDto(null, username1, password1, role, null);
|
||||
|
||||
var createTeamDto = new CreateTeamDto(teamName, List.of(member1));
|
||||
|
||||
webClient.post().uri("/teams/")
|
||||
.header(HttpHeaders.ACCEPT, "application/json")
|
||||
.body(BodyInserters.fromValue(createTeamDto))
|
||||
.exchange()
|
||||
.expectStatus().is2xxSuccessful()
|
||||
.expectBodyList(TeamServiceException.class);
|
||||
|
||||
StepVerifier.create(teamRepository.findByName(teamName))
|
||||
.assertNext(t -> {
|
||||
Assertions.assertThat(TeamUtils.toName.apply(t)).isEqualTo(teamName);
|
||||
// Assertions.assertThat(TeamUtils.toMembers.apply(t).size()).isZero();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
package net.szymonsawicki.reactivetimesheetapp.application.service;
|
||||
|
||||
import net.szymonsawicki.reactivetimesheetapp.application.service.exception.TeamServiceException;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.Team;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.TeamUtils;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.dto.CreateTeamDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.dto.GetTeamDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.team.repository.TeamRepository;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.User;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.UserUtils;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.dto.GetUserDto;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.repository.UserRepository;
|
||||
import net.szymonsawicki.reactivetimesheetapp.domain.user.type.Role;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
public class TeamServiceTest {
|
||||
|
||||
@TestConfiguration
|
||||
public static class TeamServiceTestConfiguration {
|
||||
|
||||
@MockBean
|
||||
public TeamRepository teamRepository;
|
||||
@MockBean
|
||||
public UserRepository userRepository;
|
||||
|
||||
@Bean
|
||||
public TeamService teamService() {
|
||||
return new TeamService(teamRepository, userRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public TeamRepository teamRepository;
|
||||
@Autowired
|
||||
public UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
public TeamService teamService;
|
||||
|
||||
@Captor
|
||||
public ArgumentCaptor<List<User>> usersCaptor;
|
||||
|
||||
@Test
|
||||
public void shouldReturnThreeTeamsOnFindAll() {
|
||||
|
||||
String userId1 = "21344r23r34";
|
||||
String userId2 = "21344r23r34";
|
||||
String teamId1 = "2r872394r578";
|
||||
String teamId2 = "2r872394r578";
|
||||
String teamName1 = "Some team";
|
||||
String teamName2 = "Some team";
|
||||
String username1 = "testsusrname";
|
||||
String username2 = "testsusrname2";
|
||||
|
||||
var member1 = new GetUserDto(
|
||||
userId1,
|
||||
username1,
|
||||
"some password",
|
||||
null, teamId1);
|
||||
|
||||
var member2 = new GetUserDto(
|
||||
userId2,
|
||||
username2,
|
||||
"some password",
|
||||
null, teamId2);
|
||||
|
||||
var expectedTeam1 = new GetTeamDto(
|
||||
teamId1,
|
||||
teamName1,
|
||||
List.of(member1));
|
||||
|
||||
var expectedTeam2 = new GetTeamDto(
|
||||
teamId2,
|
||||
teamName2,
|
||||
List.of(member2));
|
||||
|
||||
var memberFromDb1 = User.builder()
|
||||
.id(userId1)
|
||||
.username(username1)
|
||||
.password("some password")
|
||||
.teamId(teamId1)
|
||||
.build();
|
||||
|
||||
var memberFromDb2 = User.builder()
|
||||
.id(userId2)
|
||||
.username(username2)
|
||||
.password("some password")
|
||||
.teamId(teamId2)
|
||||
.build();
|
||||
|
||||
var teamFromDb1 = Team.builder()
|
||||
.id(teamId1)
|
||||
.name(teamName1)
|
||||
.members(List.of(memberFromDb1))
|
||||
.build();
|
||||
|
||||
var teamFromDb2 = Team.builder()
|
||||
.id(teamId2)
|
||||
.name(teamName2)
|
||||
.members(List.of(memberFromDb2))
|
||||
.build();
|
||||
|
||||
Mockito.when(teamRepository.findAll())
|
||||
.thenReturn(Flux.just(teamFromDb1, teamFromDb2));
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.findAllTeams())
|
||||
.expectNext(expectedTeam1)
|
||||
.expectNext(expectedTeam2)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnTeamWithOneMemberOnGetById() {
|
||||
|
||||
String userId = "21344r23r34";
|
||||
String teamId = "2r872394r578";
|
||||
String teamName = "Some team";
|
||||
String username = "testsusrname";
|
||||
Role role = Role.DEVELOPER;
|
||||
|
||||
var member = User.builder()
|
||||
.username(username)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var teamEntityMono = Mono.just(Team.builder()
|
||||
.id(teamId)
|
||||
.name(teamName)
|
||||
.members(List.of(member))
|
||||
.build());
|
||||
|
||||
Mockito.when(teamRepository.findById(Mockito.anyString()))
|
||||
.thenReturn(teamEntityMono);
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.findById(userId))
|
||||
.assertNext(team -> {
|
||||
Assertions.assertThat(team.name().equals(teamName));
|
||||
Assertions.assertThat(team.members()).hasSize(1);
|
||||
Assertions.assertThat(team.members().get(0).username()).isEqualTo(username);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnErrorOnGetByIdWhenTeamMissing() {
|
||||
|
||||
Mockito.when(teamRepository.findById(Mockito.anyString()))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.findById("some id"))
|
||||
.expectErrorMatches(error -> error instanceof TeamServiceException && error.getMessage().equals("Team with given id doesn't exist"))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindTeamByName() {
|
||||
|
||||
String userId = "21344r23r34";
|
||||
String teamId = "2r872394r578";
|
||||
String teamName = "Some team";
|
||||
String username = "testsusrname";
|
||||
|
||||
var member = User.builder()
|
||||
.id(userId)
|
||||
.username(username)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var expectedMemberDto = new GetUserDto(
|
||||
userId,
|
||||
username,
|
||||
"some password",
|
||||
null, teamId);
|
||||
|
||||
|
||||
var teamMono = Mono.just(Team.builder()
|
||||
.id(teamId)
|
||||
.name(teamName)
|
||||
.members(List.of(member))
|
||||
.build());
|
||||
|
||||
Mockito.when(teamRepository.findByName(Mockito.anyString()))
|
||||
.thenReturn(teamMono);
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.findByName(teamName))
|
||||
.expectNextMatches(resultingTeam -> resultingTeam.name().equals(teamName))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnErrorOnGetTeamByNameWhenMissing() {
|
||||
|
||||
Mockito.when(teamRepository.findByName(Mockito.anyString()))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.findByName("some name"))
|
||||
.expectErrorMatches(error -> error instanceof TeamServiceException && error.getMessage().equals("Team with given name doesn't exist"))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateNewTeamWithMembers() {
|
||||
|
||||
String userId1 = "21344r23r34";
|
||||
String userId2 = "21344r23r34";
|
||||
String teamId = "2r872394r578";
|
||||
String teamName = "Some team";
|
||||
String username1 = "testsusrname";
|
||||
String username2 = "testsusrname2";
|
||||
|
||||
ArgumentCaptor<Team> saveTeamCaptor = ArgumentCaptor.forClass(Team.class);
|
||||
|
||||
var member1 = new GetUserDto(
|
||||
userId1,
|
||||
username1,
|
||||
"some password",
|
||||
null, null);
|
||||
|
||||
var member2 = new GetUserDto(
|
||||
userId2,
|
||||
username2,
|
||||
"some password",
|
||||
null, null);
|
||||
|
||||
var savedMember1 = User.builder()
|
||||
.id(userId1)
|
||||
.username(username1)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var savedMember2 = User.builder()
|
||||
.id(userId2)
|
||||
.username(username2)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var teamToCreateMono = Mono.just(new CreateTeamDto(
|
||||
teamName
|
||||
, List.of(member1, member2)));
|
||||
|
||||
var createdTeamMono = Mono.just(Team.builder()
|
||||
.id(teamId)
|
||||
.name(teamName)
|
||||
.members(List.of(savedMember1, savedMember2))
|
||||
.build());
|
||||
|
||||
Mockito.when(teamRepository.findByName(Mockito.anyString()))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
Mockito.when(teamRepository.save(saveTeamCaptor.capture()))
|
||||
.thenReturn(createdTeamMono);
|
||||
|
||||
Mockito.when(userRepository.saveAll(usersCaptor.capture()))
|
||||
.thenReturn(Flux.just(savedMember1, savedMember2));
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.addTeam(teamToCreateMono))
|
||||
.assertNext(team -> {
|
||||
Assertions.assertThat(team.name()).isEqualTo(teamName);
|
||||
Assertions.assertThat(team.members()).hasSize(2);
|
||||
Assertions.assertThat(team.members().stream().map(GetUserDto::username).toList()).containsAll(List.of(username1, username2));
|
||||
Assertions.assertThat(team.members().stream().filter(member -> !member.teamId().equals(teamId)).toList()).isEmpty();
|
||||
// captor assertions
|
||||
Assertions.assertThat(TeamUtils.toMembers.apply(saveTeamCaptor.getValue())).hasSize(2);
|
||||
Assertions.assertThat(TeamUtils.toId.apply(saveTeamCaptor.getValue())).isEqualTo(teamId);
|
||||
Assertions.assertThat(usersCaptor.getValue()).hasSize(2);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(teamRepository, userRepository);
|
||||
|
||||
inOrder.verify(teamRepository, Mockito.times(1)).findByName(teamName);
|
||||
inOrder.verify(teamRepository, Mockito.times(1)).save(Mockito.any(Team.class));
|
||||
inOrder.verify(userRepository, Mockito.times(1)).saveAll(Mockito.any());
|
||||
inOrder.verify(teamRepository, Mockito.times(1)).save(Mockito.any(Team.class));
|
||||
|
||||
Mockito.verifyNoMoreInteractions(teamRepository, userRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnExistingTeamWhenNameIsTakenOnAddTeam() {
|
||||
|
||||
String userId1 = "21344r23r34";
|
||||
String userId2 = "tzuh";
|
||||
String teamId = "2r872394r578";
|
||||
String teamName = "Some team";
|
||||
String username1 = "testsusrname";
|
||||
String username2 = "testsusrname2";
|
||||
|
||||
var member1 = new GetUserDto(
|
||||
userId1,
|
||||
username1,
|
||||
"some password",
|
||||
null, null);
|
||||
|
||||
var member2 = new GetUserDto(
|
||||
userId2,
|
||||
username2,
|
||||
"some password",
|
||||
null, null);
|
||||
|
||||
var expectedMember1 = new GetUserDto(
|
||||
userId1,
|
||||
username1,
|
||||
"some password",
|
||||
null, teamId);
|
||||
|
||||
var expectedMember2 = new GetUserDto(
|
||||
userId2,
|
||||
username2,
|
||||
"some password",
|
||||
null, teamId);
|
||||
|
||||
var savedMember1 = User.builder()
|
||||
.id(userId1)
|
||||
.username(username1)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var savedMember2 = User.builder()
|
||||
.id(userId2)
|
||||
.username(username2)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var teamToCreateMono = Mono.just(new CreateTeamDto(
|
||||
teamName
|
||||
, List.of(member1, member2)));
|
||||
|
||||
var expectedTeamDto = new GetTeamDto(
|
||||
teamId,
|
||||
teamName
|
||||
, List.of(expectedMember1, expectedMember2));
|
||||
|
||||
var existingTeamMono = Mono.just(Team.builder()
|
||||
.id(teamId)
|
||||
.name(teamName)
|
||||
.members(List.of(savedMember1, savedMember2))
|
||||
.build());
|
||||
|
||||
Mockito.when(teamRepository.findByName(Mockito.anyString()))
|
||||
.thenReturn(existingTeamMono);
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.addTeam(teamToCreateMono))
|
||||
.expectNext(expectedTeamDto)
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(teamRepository, Mockito.never())
|
||||
.save(Mockito.any());
|
||||
Mockito.verify(userRepository, Mockito.never())
|
||||
.saveAll(Mockito.any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ShouldDeleteTeamOnDelete() {
|
||||
|
||||
String userId1 = "21344r23r34";
|
||||
String userId2 = "21344r23r34";
|
||||
String teamId = "2r872394r578";
|
||||
String teamName = "Some team";
|
||||
String username1 = "testsusrname";
|
||||
String username2 = "testsusrname2";
|
||||
|
||||
var existingMember1 = User.builder()
|
||||
.id(userId1)
|
||||
.username(username1)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var existingMember2 = User.builder()
|
||||
.id(userId2)
|
||||
.username(username2)
|
||||
.password("some password")
|
||||
.teamId(teamId)
|
||||
.build();
|
||||
|
||||
var existingTeam = Team.builder()
|
||||
.id(teamId)
|
||||
.name(teamName)
|
||||
.members(List.of(existingMember1, existingMember2))
|
||||
.build();
|
||||
|
||||
Mockito.when(teamRepository.findById(Mockito.anyString()))
|
||||
.thenReturn(Mono.just(existingTeam));
|
||||
|
||||
Mockito.when(userRepository.saveAll(usersCaptor.capture()))
|
||||
.thenReturn(Flux.just(existingMember1, existingMember2));
|
||||
|
||||
Mockito.when(teamRepository.delete(Mockito.anyString()))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.deleteTeam(teamId))
|
||||
.assertNext(team -> {
|
||||
Assertions.assertThat(team.name()).isEqualTo(teamName);
|
||||
Assertions.assertThat(team.members()).hasSize(2);
|
||||
Assertions.assertThat(team.members().stream().map(GetUserDto::username).toList()).containsAll(List.of(username1, username2));
|
||||
Assertions.assertThat(team.members().stream().filter(member -> !member.teamId().equals(teamId)).toList()).isEmpty();
|
||||
// captor assertions
|
||||
Assertions.assertThat(usersCaptor.getValue()).hasSize(2);
|
||||
Assertions.assertThat(usersCaptor.getValue().stream().filter(user -> UserUtils.toTeamId.apply(user) != null).toList()).isEmpty();
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
Mockito.verify(teamRepository, Mockito.times(1))
|
||||
.delete(teamId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnErrorOnDeleteWhenNotExist() {
|
||||
|
||||
Mockito.when(teamRepository.findById(Mockito.anyString()))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
StepVerifier
|
||||
.create(teamService.deleteTeam("some id"))
|
||||
.expectErrorMatches(error -> error instanceof TeamServiceException && error.getMessage().equals("cannot find team to delete"))
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.szymonsawicki.reactivetimesheetapp.application.service.utils;
|
||||
|
||||
import org.testcontainers.containers.MongoDBContainer;
|
||||
|
||||
public class TimesheetAppMongoDbContainer extends MongoDBContainer {
|
||||
|
||||
private static final String IMAGE_VERSION = "mongo:4.0.10";
|
||||
|
||||
private static MongoDBContainer container;
|
||||
|
||||
public static synchronized MongoDBContainer getInstance() {
|
||||
if (container == null) {
|
||||
container = new TimesheetAppMongoDbContainer();
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
private TimesheetAppMongoDbContainer() {
|
||||
super(IMAGE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// do nothing, JVM handles shut down
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user