Initial commit.

This commit is contained in:
Florian THIERRY
2021-07-26 23:40:03 +02:00
commit ef04d3dbea
33 changed files with 1157 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
package org.takiguchi.cerberus.cerberusapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CerberusApplication {
public static void main(String[] args) {
SpringApplication.run(CerberusApplication.class, args);
}
}

View File

@@ -0,0 +1,83 @@
package org.takiguchi.cerberus.cerberusapp.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.model.ServiceStatus;
import org.takiguchi.cerberus.cerberusapp.service.ApplicationService;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.takiguchi.cerberus.cerberusapp.model.Application.anApplication;
@RestController
@RequestMapping("/api/applications")
public class ApplicationController {
private final ApplicationService service;
public ApplicationController(ApplicationService service) {
this.service = service;
}
@GetMapping("/{applicationId}")
public Optional<Application> getById(@PathVariable("applicationId") UUID applicationId) {
return service.getById(applicationId);
}
@GetMapping
public List<Application> getAll() {
return service.getAll();
}
@PostMapping
public Application add(@RequestBody Application application) {
Application applicationToAdd = anApplication()
.withName(application.getName())
.withServiceName(application.getServiceName())
.withServiceType(application.getServiceType())
.build();
return service.add(applicationToAdd);
}
@PutMapping("/{applicationId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void update(@PathVariable("applicationId") UUID applicationId, @RequestBody Application application) {
Application applicationToUpdate = anApplication()
.withId(applicationId)
.withName(application.getName())
.withServiceName(application.getServiceName())
.withServiceType(application.getServiceType())
.build();
service.update(applicationToUpdate);
}
@DeleteMapping("/{applicationId}")
public void remove(@PathVariable("applicationId") UUID applicationId) {
service.remove(applicationId);
}
@GetMapping("/{applicationId}/status")
public ServiceStatus getStatus(@PathVariable("applicationId") UUID applicationId) {
return service.checkStatus(applicationId);
}
@PostMapping("/{applicationId}/start")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void start(@PathVariable("applicationId") UUID applicationId) {
service.start(applicationId);
}
@PostMapping("/{applicationId}/stop")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void stop(@PathVariable("applicationId") UUID applicationId) {
service.stop(applicationId);
}
@PostMapping("/{applicationId}/restart")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void restart(@PathVariable("applicationId") UUID applicationId) {
service.restart(applicationId);
}
}

View File

@@ -0,0 +1,15 @@
package org.takiguchi.cerberus.cerberusapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class BadRequestException extends BusinessException {
public BadRequestException(String message) {
super(message);
}
public BadRequestException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,26 @@
package org.takiguchi.cerberus.cerberusapp.exception;
/**
* Business exception.
*/
public class BusinessException extends RuntimeException {
public BusinessException() {}
/**
* Constructs an exception with a message.
* @param message The description of the error met.
*/
public BusinessException(final String message) {
super(message);
}
/**
* Constructs an exception with a message and a code.
* @param message The description of the error met.
* @param cause The cause of the exception.
*/
public BusinessException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,23 @@
package org.takiguchi.cerberus.cerberusapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Exception thrown when user attempt to access a resource that he has not rights.
*/
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class ForbiddenException extends BusinessException {
public ForbiddenException() {
super();
}
public ForbiddenException(String message) {
super(message);
}
public ForbiddenException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,19 @@
package org.takiguchi.cerberus.cerberusapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerErrorException extends TechnicalException {
public InternalServerErrorException() {
super("");
}
public InternalServerErrorException(String message) {
super(message);
}
public InternalServerErrorException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,17 @@
package org.takiguchi.cerberus.cerberusapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public class NoContentException extends BusinessException {
public NoContentException() {}
public NoContentException(String message) {
super(message);
}
public NoContentException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,17 @@
package org.takiguchi.cerberus.cerberusapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NotFoundException extends BusinessException {
public NotFoundException() {}
public NotFoundException(String message) {
super(message);
}
public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,23 @@
package org.takiguchi.cerberus.cerberusapp.exception;
/**
* Technical exception.
*/
public class TechnicalException extends RuntimeException {
/**
* Constructs an exception with a message.
* @param message The description of the error met.
*/
public TechnicalException(final String message) {
super(message);
}
/**
* Constructs an exception with a message and a code.
* @param message The description of the error met.
* @param cause The cause of the exception.
*/
public TechnicalException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,22 @@
package org.takiguchi.cerberus.cerberusapp.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* Exception thrown when an anonymous user attempt to access to secured resource or if he failed to login.
*/
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends BusinessException {
public UnauthorizedException() {
super();
}
public UnauthorizedException(String message) {
super(message);
}
public UnauthorizedException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,73 @@
package org.takiguchi.cerberus.cerberusapp.model;
import java.util.UUID;
public class Application {
private final UUID id;
/** The name to display. */
private final String name;
/** The technical service name, like a docker container name or a system V service name. */
private final String serviceName;
private final ServiceType serviceType;
public static Builder anApplication() {
return new Builder();
}
private Application(UUID id, String name, String serviceName, ServiceType serviceType) {
this.id = id;
this.name = name;
this.serviceName = serviceName;
this.serviceType = serviceType;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public String getServiceName() {
return serviceName;
}
public ServiceType getServiceType() {
return serviceType;
}
public static class Builder {
private UUID id;
private String name;
private String serviceName;
private ServiceType serviceType;
private Builder() {
}
public Builder withId(UUID id) {
this.id = id;
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withServiceName(String serviceName) {
this.serviceName = serviceName;
return this;
}
public Builder withServiceType(ServiceType serviceType) {
this.serviceType = serviceType;
return this;
}
public Application build() {
return new Application(id, name, serviceName, serviceType);
}
}
}

View File

@@ -0,0 +1,7 @@
package org.takiguchi.cerberus.cerberusapp.model;
public enum ServiceStatus {
STARTED,
STOPPED,
UNKNOWN;
}

View File

@@ -0,0 +1,7 @@
package org.takiguchi.cerberus.cerberusapp.model;
public enum ServiceType {
FAKE,
SERVICE,
DOCKER;
}

View File

@@ -0,0 +1,58 @@
package org.takiguchi.cerberus.cerberusapp.persistence.adapter;
import org.springframework.stereotype.Component;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.persistence.mapper.ApplicationEntityMapper;
import org.takiguchi.cerberus.cerberusapp.persistence.model.ApplicationEntity;
import org.takiguchi.cerberus.cerberusapp.persistence.repository.ApplicationRepository;
import org.takiguchi.cerberus.cerberusapp.service.ApplicationRepositoryPort;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Component
public class ApplicationJpaRepositoryAdapter implements ApplicationRepositoryPort {
private final ApplicationRepository repository;
private final ApplicationEntityMapper applicationMapper;
public ApplicationJpaRepositoryAdapter(ApplicationRepository repository,
ApplicationEntityMapper applicationMapper) {
this.repository = repository;
this.applicationMapper = applicationMapper;
}
@Override
public Optional<Application> getById(UUID applicationId) {
return repository.findById(applicationId)
.map(applicationMapper::mapToDomain);
}
@Override
public List<Application> getAll() {
return repository.findAll()
.stream()
.map(applicationMapper::mapToDomain)
.collect(Collectors.toList());
}
@Override
public Application add(Application application) {
ApplicationEntity entityToSave = applicationMapper.mapToEntity(application);
ApplicationEntity savedApplication = repository.save(entityToSave);
return applicationMapper.mapToDomain(savedApplication);
}
@Override
public void update(Application application) {
ApplicationEntity entityToSave = applicationMapper.mapToEntity(application);
repository.save(entityToSave);
}
@Override
public void remove(UUID applicationId) {
repository.deleteById(applicationId);
}
}

View File

@@ -0,0 +1,29 @@
package org.takiguchi.cerberus.cerberusapp.persistence.mapper;
import org.springframework.stereotype.Component;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.persistence.model.ApplicationEntity;
import static org.takiguchi.cerberus.cerberusapp.model.Application.anApplication;
@Component
public class ApplicationEntityMapper {
public Application mapToDomain(ApplicationEntity entity) {
return anApplication()
.withId(entity.getId())
.withName(entity.getName())
.withServiceName(entity.getServiceName())
.withServiceType(entity.getServiceType())
.build();
}
public ApplicationEntity mapToEntity(Application application) {
return new ApplicationEntity(
application.getId(),
application.getName(),
application.getServiceName(),
application.getServiceType()
);
}
}

View File

@@ -0,0 +1,63 @@
package org.takiguchi.cerberus.cerberusapp.persistence.model;
import com.fasterxml.jackson.annotation.JsonView;
import org.takiguchi.cerberus.cerberusapp.model.ServiceType;
import javax.persistence.*;
import java.util.UUID;
import static javax.persistence.EnumType.ORDINAL;
@Entity
@Table(name = "application")
public class ApplicationEntity {
@Id
@GeneratedValue(generator = "system-uuid")
private UUID id;
private String name;
private String serviceName;
@Enumerated
private ServiceType serviceType;
public ApplicationEntity() {
}
public ApplicationEntity(UUID id, String name, String serviceName, ServiceType serviceType) {
this.id = id;
this.name = name;
this.serviceName = serviceName;
this.serviceType = serviceType;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public ServiceType getServiceType() {
return serviceType;
}
public void setServiceType(ServiceType serviceType) {
this.serviceType = serviceType;
}
}

View File

@@ -0,0 +1,12 @@
package org.takiguchi.cerberus.cerberusapp.persistence.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.takiguchi.cerberus.cerberusapp.persistence.model.ApplicationEntity;
import java.util.UUID;
@Repository
public interface ApplicationRepository extends JpaRepository<ApplicationEntity, UUID> {
}

View File

@@ -0,0 +1,15 @@
package org.takiguchi.cerberus.cerberusapp.service;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface ApplicationRepositoryPort {
Optional<Application> getById(UUID applicationId);
List<Application> getAll();
Application add(Application application);
void update(Application application);
void remove(UUID applicationId);
}

View File

@@ -0,0 +1,97 @@
package org.takiguchi.cerberus.cerberusapp.service;
import org.springframework.stereotype.Service;
import org.takiguchi.cerberus.cerberusapp.exception.NotFoundException;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.model.ServiceStatus;
import org.takiguchi.cerberus.cerberusapp.service.servicemanager.ServiceManagerProvider;
import org.takiguchi.cerberus.cerberusapp.service.validator.ApplicationValidator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.takiguchi.cerberus.cerberusapp.model.Application.anApplication;
@Service
public class ApplicationService {
private final ApplicationValidator applicationValidator;
private final ApplicationRepositoryPort applicationRepositoryPort;
private final ServiceManagerProvider serviceManagerProvider;
public ApplicationService(ApplicationValidator applicationValidator,
ApplicationRepositoryPort applicationRepositoryPort,
ServiceManagerProvider serviceManagerProvider) {
this.applicationValidator = applicationValidator;
this.applicationRepositoryPort = applicationRepositoryPort;
this.serviceManagerProvider = serviceManagerProvider;
}
public Optional<Application> getById(UUID applicationId) {
return applicationRepositoryPort.getById(applicationId);
}
public List<Application> getAll() {
return applicationRepositoryPort.getAll();
}
public Application add(Application application) {
applicationValidator.validate(application);
return applicationRepositoryPort.add(application);
}
public void update(Application application) {
applicationRepositoryPort.getById(application.getId())
.map(existingApplication -> anApplication()
.withId(existingApplication.getId())
.withName(application.getName())
.withServiceName(application.getServiceName())
.withServiceType(application.getServiceType())
.build()
)
.ifPresentOrElse(this::validateThenSave, NotFoundException::new);
}
private void validateThenSave(Application updatedApplication) {
applicationValidator.validate(updatedApplication);
applicationRepositoryPort.update(updatedApplication);
}
public void remove(UUID applicationId) {
applicationRepositoryPort.remove(applicationId);
}
public ServiceStatus checkStatus(UUID applicationId) {
return applicationRepositoryPort.getById(applicationId)
.map(application -> serviceManagerProvider.getServiceManager(application)
.getStatus(application)
).orElseThrow(NotFoundException::new);
}
public void start(UUID applicationId) {
applicationRepositoryPort.getById(applicationId)
.ifPresentOrElse(
application -> serviceManagerProvider.getServiceManager(application)
.start(application),
NotFoundException::new
);
}
public void stop(UUID applicationId) {
applicationRepositoryPort.getById(applicationId)
.ifPresentOrElse(
application -> serviceManagerProvider.getServiceManager(application)
.stop(application),
NotFoundException::new
);
}
public void restart(UUID applicationId) {
applicationRepositoryPort.getById(applicationId)
.ifPresentOrElse(
application -> serviceManagerProvider.getServiceManager(application)
.restart(application),
NotFoundException::new
);
}
}

View File

@@ -0,0 +1,7 @@
package org.takiguchi.cerberus.cerberusapp.service.exception;
public class ValidationException extends RuntimeException {
public ValidationException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,28 @@
package org.takiguchi.cerberus.cerberusapp.service.servicemanager;
import org.springframework.stereotype.Service;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.model.ServiceStatus;
@Service
public class DockerServiceManager implements ServiceManager {
@Override
public ServiceStatus getStatus(Application application) {
throw new IllegalStateException("DockerServiceManager#getStatus not implemented.");
}
@Override
public void start(Application application) {
throw new IllegalStateException("DockerServiceManager#start not implemented.");
}
@Override
public void stop(Application application) {
throw new IllegalStateException("DockerServiceManager#stop not implemented.");
}
@Override
public void restart(Application application) {
throw new IllegalStateException("DockerServiceManager#restart not implemented.");
}
}

View File

@@ -0,0 +1,39 @@
package org.takiguchi.cerberus.cerberusapp.service.servicemanager;
import org.springframework.stereotype.Service;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.model.ServiceStatus;
import java.util.*;
import static org.takiguchi.cerberus.cerberusapp.model.ServiceStatus.*;
@Service
public class FakeServiceManager implements ServiceManager {
private final Map<UUID, ServiceStatus> handledApplications = new HashMap<>();
@Override
public ServiceStatus getStatus(Application application) {
return handledApplications.entrySet()
.stream()
.filter(entry -> entry.getKey().equals(application.getId()))
.findFirst()
.map(Map.Entry::getValue)
.orElse(STOPPED);
}
@Override
public void start(Application application) {
handledApplications.put(application.getId(), STARTED);
}
@Override
public void stop(Application application) {
handledApplications.put(application.getId(), STOPPED);
}
@Override
public void restart(Application application) {
start(application);
}
}

View File

@@ -0,0 +1,28 @@
package org.takiguchi.cerberus.cerberusapp.service.servicemanager;
import org.springframework.stereotype.Service;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.model.ServiceStatus;
@Service
public class LinuxServiceManager implements ServiceManager {
@Override
public ServiceStatus getStatus(Application application) {
throw new IllegalStateException("LinuxServiceManager#getStatus not implemented.");
}
@Override
public void start(Application application) {
throw new IllegalStateException("LinuxServiceManager#start not implemented.");
}
@Override
public void stop(Application application) {
throw new IllegalStateException("LinuxServiceManager#stop not implemented.");
}
@Override
public void restart(Application application) {
throw new IllegalStateException("LinuxServiceManager#restart not implemented.");
}
}

View File

@@ -0,0 +1,11 @@
package org.takiguchi.cerberus.cerberusapp.service.servicemanager;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.model.ServiceStatus;
public interface ServiceManager {
ServiceStatus getStatus(Application application);
void start(Application application);
void stop(Application application);
void restart(Application application);
}

View File

@@ -0,0 +1,42 @@
package org.takiguchi.cerberus.cerberusapp.service.servicemanager;
import org.springframework.stereotype.Component;
import org.takiguchi.cerberus.cerberusapp.exception.InternalServerErrorException;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import java.util.List;
@Component
public class ServiceManagerProvider {
private final List<ServiceManager> serviceManagers;
public ServiceManagerProvider(List<ServiceManager> serviceManagers) {
this.serviceManagers = serviceManagers;
}
public ServiceManager getServiceManager(Application application) {
ServiceManager result;
switch (application.getServiceType()) {
case FAKE:
result = getServiceByClass(FakeServiceManager.class);
break;
case SERVICE:
result = getServiceByClass(LinuxServiceManager.class);
break;
case DOCKER:
result = getServiceByClass(DockerServiceManager.class);
break;
default:
throw new InternalServerErrorException("");
}
return result;
}
private ServiceManager getServiceByClass(Class<? extends ServiceManager> serviceManagerClass) {
return serviceManagers.stream()
.filter(serviceManagerClass::isInstance)
.findFirst()
.orElseThrow(InternalServerErrorException::new);
}
}

View File

@@ -0,0 +1,24 @@
package org.takiguchi.cerberus.cerberusapp.service.validator;
import org.springframework.stereotype.Component;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.service.exception.ValidationException;
@Component
public class ApplicationValidator {
public void validate(Application application) {
if (application == null) {
throw new ValidationException("Application is null.");
}
if (application.getName() == null) {
throw new ValidationException("Application name is mandatory.");
}
if (application.getServiceName() == null) {
throw new ValidationException("Application service name is mandatory.");
}
if (application.getServiceType() == null) {
throw new ValidationException("Application service type is mandatory.");
}
}
}

View File

@@ -0,0 +1,15 @@
server:
error:
whitelabel:
enabled: false # Disable html error responses.
include-stacktrace: never
spring:
# -------------------------------------------------
# Database configuration
# -------------------------------------------------
datasource:
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://localhost:50001/cerberus
username: h23
password: P@ssword1

9
src/main/sql/ddl.sql Normal file
View File

@@ -0,0 +1,9 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE IF NOT EXISTS application (
id UUID DEFAULT uuid_generate_v4() NOT NULL,
name VARCHAR NOT NULL,
service_name VARCHAR NOT NULL,
service_type SMALLINT NOT NULL,
CONSTRAINT application_pk PRIMARY KEY (id)
);

View File

@@ -0,0 +1,13 @@
package org.takiguchi.cerberus.cerberusapp;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class CerberusApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -0,0 +1,127 @@
package org.takiguchi.cerberus.cerberusapp.service;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.service.servicemanager.ServiceManagerProvider;
import org.takiguchi.cerberus.cerberusapp.service.validator.ApplicationValidator;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.takiguchi.cerberus.cerberusapp.model.Application.anApplication;
@ExtendWith(MockitoExtension.class)
class ApplicationServiceTest {
private ApplicationService service;
@Mock
private ApplicationRepositoryPort applicationRepositoryPort;
@Mock
private ApplicationValidator applicationValidator;
@Mock
private ServiceManagerProvider serviceManagerProvider;
@Captor
private ArgumentCaptor<Application> applicationCaptor;
@BeforeEach
void setUp() {
service = new ApplicationService(applicationValidator, applicationRepositoryPort, serviceManagerProvider);
}
@Test
void getById_should_return_the_application() {
// given
UUID applicationId = UUID.randomUUID();
Application application = anApplication().build();
given(applicationRepositoryPort.getById(applicationId)).willReturn(Optional.of(application));
// when
Optional<Application> result = service.getById(applicationId);
// then
assertThat(result).contains(application);
}
@Test
void getAll_should_return_all_applications() {
// given
Application application1 = anApplication().build();
Application application2 = anApplication().build();
given(applicationRepositoryPort.getAll()).willReturn(List.of(application1, application2));
// when
List<Application> result = service.getAll();
// then
assertThat(result).contains(application1, application2);
}
@Test
void add_should_validate_the_application_then_add_it() {
// given
Application application = anApplication().build();
InOrder inOrder = inOrder(applicationValidator, applicationRepositoryPort);
Application addedApplication = anApplication().build();
given(applicationRepositoryPort.add(application)).willReturn(addedApplication);
// when
Application result = service.add(application);
// then
assertThat(result).isEqualTo(addedApplication);
inOrder.verify(applicationValidator).validate(application);
inOrder.verify(applicationRepositoryPort).add(application);
}
@Test
void update_should_retrieve_application_then_edit_some_fields_then_validate_it__then_update_it() {
// given
UUID newId = UUID.randomUUID();
String newName = "new name";
String newServiceName = "new service name";
Application application = anApplication()
.withId(newId)
.withName(newName)
.withServiceName(newServiceName)
.build();
InOrder inOrder = inOrder(applicationRepositoryPort, applicationValidator, applicationRepositoryPort);
UUID oldId = UUID.randomUUID();
Application existingApplication = anApplication()
.withId(oldId)
.build();
given(applicationRepositoryPort.getById(newId)).willReturn(Optional.of(existingApplication));
// when
service.update(application);
// then
inOrder.verify(applicationRepositoryPort).getById(newId);
inOrder.verify(applicationValidator).validate(applicationCaptor.capture());
Application updatedApplication = applicationCaptor.getValue();
inOrder.verify(applicationRepositoryPort).update(updatedApplication);
assertThat(updatedApplication).isNotNull()
.extracting(
Application::getId,
Application::getName,
Application::getServiceName
)
.contains(
oldId,
newName,
newServiceName
);
}
}

View File

@@ -0,0 +1,96 @@
package org.takiguchi.cerberus.cerberusapp.service.validator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.model.ServiceType;
import org.takiguchi.cerberus.cerberusapp.service.exception.ValidationException;
import java.util.UUID;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowableOfType;
import static org.takiguchi.cerberus.cerberusapp.model.Application.anApplication;
import static org.takiguchi.cerberus.cerberusapp.model.ServiceType.FAKE;
class ApplicationValidatorTest {
private ApplicationValidator validator;
@BeforeEach
void setUp() {
validator = new ApplicationValidator();
}
@ParameterizedTest
@MethodSource("should_throw_a_validation_exception_data_provider")
void should_throw_a_validation_exception(Application application, String errorMessage) {
// when
ValidationException exception = catchThrowableOfType(() -> validator.validate(application), ValidationException.class);
// then
assertThat(exception).isNotNull()
.hasMessage(errorMessage);
}
private static Stream<Arguments> should_throw_a_validation_exception_data_provider() {
return Stream.of(
Arguments.of(
null,
"Application is null."
),
Arguments.of(
anApplication().build(),
"Application name is mandatory."
),
Arguments.of(
anApplication()
.withId(UUID.randomUUID())
.build(),
"Application name is mandatory."
),
Arguments.of(
anApplication()
.withName("name")
.build(),
"Application service name is mandatory."
),
Arguments.of(
anApplication()
.withServiceName("serviceName")
.build(),
"Application name is mandatory."
),
Arguments.of(
anApplication()
.withServiceType(FAKE)
.build(),
"Application name is mandatory."
),
Arguments.of(
anApplication()
.withId(UUID.randomUUID())
.withName("name")
.build(),
"Application service name is mandatory."
),
Arguments.of(
anApplication()
.withId(UUID.randomUUID())
.withServiceName("serviceName")
.build(),
"Application name is mandatory."
),
Arguments.of(
anApplication()
.withId(UUID.randomUUID())
.withName("name")
.withServiceName("serviceName")
.build(),
"Application service type is mandatory."
)
);
}
}