Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • xvicenik/formula-team-management
1 result
Show changes
Commits on Source (34)
Showing
with 418 additions and 38 deletions
...@@ -14,14 +14,36 @@ default: ...@@ -14,14 +14,36 @@ default:
stages: stages:
- build - build
- test - tests
build: build:
stage: build stage: build
script: script:
- echo "We are building your project"
- mvn clean install $MAVEN_CLI_OPTS -DskipTests - mvn clean install $MAVEN_CLI_OPTS -DskipTests
test: unit_test:
stage: test stage: tests
script: script:
- mvn test $MAVEN_CLI_OPTS - echo "We are testing your project build with unit tests"
\ No newline at end of file - mvn test $MAVEN_CLI_OPTS
artifacts:
expire_in: 10 min
paths:
- "*/target/surefire-reports/*"
reports:
junit:
- "*/target/surefire-reports/*.xml"
integration_test:
stage: tests
script:
- echo "We are testing your project build with integration tests"
- mvn verify $MAVEN_CLI_OPTS
artifacts:
expire_in: 10 min
paths:
- "*/target/failsafe-reports/*"
reports:
junit:
- "*/target/failsafe-reports/*.xml"
...@@ -97,6 +97,15 @@ Just note that each module runs on a different port by default, so care where yo ...@@ -97,6 +97,15 @@ Just note that each module runs on a different port by default, so care where yo
These ports can be overridden either in the application.properties file of each module, or by specifying the port These ports can be overridden either in the application.properties file of each module, or by specifying the port
manually when running the "mvn" command to run the module. manually when running the "mvn" command to run the module.
## Seed and clear DB
DB is at the start of the app seeded with some Data, thus you don't have to do it manually.
For users are available 2 endpoints:
- /seed - Seed DB with some Data
- /clear - Clear DB whenever during running
*Note: /seed will always seed DB with the same Data. Keep in mind, If you seed DB after startup, DB will contain the same data twice.*
## Build and run the app with Docker ## Build and run the app with Docker
For purpose of this build and run the installation of Docker/Podman For purpose of this build and run the installation of Docker/Podman
...@@ -153,10 +162,40 @@ To run all modules at once in one container, you can run in root directory ...@@ -153,10 +162,40 @@ To run all modules at once in one container, you can run in root directory
docker-compose up docker-compose up
``` ```
*Note: in case you want to also rebuild images, use flag `--build`*
*Note: use flag `-d` for detached mode* *Note: use flag `-d` for detached mode*
To stop container To stop container run
```bash ```bash
docker-compose down docker-compose down
``` ```
## Collecting Metrics
Create network for being able to communicate between two separate
docker images
```bash
docker network create grafana-prometheus
```
To collect the metrics in an automated way we use Prometheus.
First, make sure that every service you want to observe is running on predefined port.
Then run Prometheus in docker with respect to location of `prometheus.yml` file:
```bash
docker run --rm --name prometheus --network grafana-prometheus --network-alias prometheus -p 9090:9090 -v ${PWD}/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus:v2.43.0 --config.file=/etc/prometheus/prometheus.yml
```
## Grafana
Run Grafana in separate container:
```bash
docker run --rm --name grafana --network grafana-prometheus -p 3000:3000 grafana/grafana:9.1.7
```
1. Log into Grafana with default user and password is `admin`.
2. Add data source in Configuration -> Data sources (http://prometheus:9090)
3. Import Dashboard as JSON file (two options - [simple](https://gitlab.fi.muni.cz/xvicenik/formula-team-management/-/blob/11-actuator/grafana.json), [advanced](https://gitlab.fi.muni.cz/xvicenik/formula-team-management/-/blob/11-actuator/justai-system-monitor_rev2.json))
...@@ -5,15 +5,17 @@ info: ...@@ -5,15 +5,17 @@ info:
Hand-made OpenAPI document. Hand-made OpenAPI document.
version: 1.0.0 version: 1.0.0
servers: servers:
- url: "http://localhost:8080" - url: "http://localhost:8081"
tags: tags:
- name: ApplicationService - name: ApplicationService
- name: DBManagementService
components: components:
schemas: schemas:
ApplicationStatus: ApplicationStatus:
type: string type: string
description: Status of application description: Status of application
enum: enum:
- pending
- rejected - rejected
- accepted - accepted
...@@ -26,6 +28,7 @@ components: ...@@ -26,6 +28,7 @@ components:
- name - name
- surname - surname
- fillingOutDate - fillingOutDate
- email
properties: properties:
id: id:
type: integer type: integer
...@@ -36,15 +39,20 @@ components: ...@@ -36,15 +39,20 @@ components:
type: string type: string
description: Name of the person description: Name of the person
example: Max example: Max
maxLength: 35
surname: surname:
type: string type: string
description: Surname of the person description: Surname of the person
example: Verstappen example: Verstappen
maxLength: 35
birthday: birthday:
type: string type: string
description: Date of birth description: Date of birth
example: 09-30-1997 example: 09-30-1997
format: date format: date
email:
type: string
description: Email address of the person
fillingOutDate: fillingOutDate:
type: string type: string
description: Date of filling out the application description: Date of filling out the application
...@@ -58,17 +66,23 @@ components: ...@@ -58,17 +66,23 @@ components:
name: name:
type: string type: string
description: Name of the person description: Name of the person
maxLength: 35
surname: surname:
type: string type: string
description: Name of the person description: Name of the person
maxLength: 35
birthday: birthday:
type: string type: string
description: Date of birth description: Date of birth
example: 1977-09-30 example: 1977-09-30
format: date format: date
email:
type: string
description: Email address of the person
required: required:
- name - name
- surname - surname
- email
responses: responses:
NotFound: NotFound:
...@@ -91,6 +105,16 @@ components: ...@@ -91,6 +105,16 @@ components:
properties: properties:
message: message:
type: string type: string
Successful:
description: Successfully processed
content:
application/json:
schema:
type: object
title: Successfully processed
properties:
message:
type: string
paths: paths:
/application: /application:
...@@ -170,5 +194,28 @@ paths: ...@@ -170,5 +194,28 @@ paths:
$ref: '#/components/schemas/ApplicationDto' $ref: '#/components/schemas/ApplicationDto'
"404": "404":
$ref: '#/components/responses/NotFound' $ref: '#/components/responses/NotFound'
default:
$ref: '#/components/responses/Unexpected'
/seed:
post:
tags:
- DBManagementService
summary: Seed DB with data
operationId: seedDB
responses:
"200":
$ref: '#/components/responses/Successful'
default:
$ref: '#/components/responses/Unexpected'
/clear:
post:
tags:
- DBManagementService
summary: Clear DB
operationId: clearDB
responses:
"200":
$ref: '#/components/responses/Successful'
default: default:
$ref: '#/components/responses/Unexpected' $ref: '#/components/responses/Unexpected'
\ No newline at end of file
...@@ -152,5 +152,17 @@ ...@@ -152,5 +152,17 @@
<groupId>com.github.ben-manes.caffeine</groupId> <groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId> <artifactId>caffeine</artifactId>
</dependency> </dependency>
<!-- Actuator dependency-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Prometheus dependency -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>
package cz.muni.pa165.config; package cz.muni.pa165.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.client.RestTemplate;
/** /**
* @author Michal Badin * @author Michal Badin
...@@ -11,4 +13,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; ...@@ -11,4 +13,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@ComponentScan @ComponentScan
@EnableTransactionManagement @EnableTransactionManagement
public class ServiceConfig { public class ServiceConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
} }
\ No newline at end of file
...@@ -7,8 +7,11 @@ import com.fasterxml.jackson.annotation.JsonValue; ...@@ -7,8 +7,11 @@ import com.fasterxml.jackson.annotation.JsonValue;
* @author Michal Badin * @author Michal Badin
*/ */
public enum ApplicationStatusEnum { public enum ApplicationStatusEnum {
ACCEPTED("accepted"), PENDING("pending"),
REJECTED("rejected");
REJECTED("rejected"),
ACCEPTED("accepted");
private final String value; private final String value;
......
...@@ -2,10 +2,7 @@ package cz.muni.pa165.data.model; ...@@ -2,10 +2,7 @@ package cz.muni.pa165.data.model;
import cz.muni.pa165.data.enums.ApplicationStatusEnum; import cz.muni.pa165.data.enums.ApplicationStatusEnum;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.PastOrPresent;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDate; import java.time.LocalDate;
...@@ -25,26 +22,33 @@ public class Application implements Serializable { ...@@ -25,26 +22,33 @@ public class Application implements Serializable {
private ApplicationStatusEnum status; private ApplicationStatusEnum status;
@NotEmpty @NotEmpty
@Size(max = 35)
private String name; private String name;
@NotEmpty @NotEmpty
@Size(max = 35)
private String surname; private String surname;
@Past @Past
@NotNull @NotNull
private LocalDate birthday; private LocalDate birthday;
@Email
private String email;
@Column(name = "filling_out_date") @Column(name = "filling_out_date")
@PastOrPresent @PastOrPresent
private LocalDate fillingOutDate; private LocalDate fillingOutDate;
public Application() {} public Application() {
}
public Application(ApplicationStatusEnum status, String name, String surname, LocalDate birthday, LocalDate fillingOutDate) { public Application(ApplicationStatusEnum status, String name, String surname, LocalDate birthday, String email, LocalDate fillingOutDate) {
this.status = status; this.status = status;
this.name = name; this.name = name;
this.surname = surname; this.surname = surname;
this.birthday = birthday; this.birthday = birthday;
this.email = email;
this.fillingOutDate = fillingOutDate; this.fillingOutDate = fillingOutDate;
} }
...@@ -88,6 +92,14 @@ public class Application implements Serializable { ...@@ -88,6 +92,14 @@ public class Application implements Serializable {
this.birthday = birthday; this.birthday = birthday;
} }
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDate getFillingOutDate() { public LocalDate getFillingOutDate() {
return fillingOutDate; return fillingOutDate;
} }
......
...@@ -4,8 +4,6 @@ import cz.muni.pa165.data.model.Application; ...@@ -4,8 +4,6 @@ import cz.muni.pa165.data.model.Application;
import cz.muni.pa165.generated.model.ApplicationCreateDto; import cz.muni.pa165.generated.model.ApplicationCreateDto;
import cz.muni.pa165.generated.model.ApplicationDto; import cz.muni.pa165.generated.model.ApplicationDto;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import java.util.List; import java.util.List;
...@@ -19,9 +17,5 @@ public interface ApplicationMapper { ...@@ -19,9 +17,5 @@ public interface ApplicationMapper {
Application mapFromCreateDto(ApplicationCreateDto carCreateDto); Application mapFromCreateDto(ApplicationCreateDto carCreateDto);
List<ApplicationDto> mapToList(List<Application> cars); List<ApplicationDto> mapToList(List<Application> cars);
default Page<ApplicationDto> mapToPageDto(Page<Application> cars) {
return new PageImpl<>(mapToList(cars.getContent()), cars.getPageable(), cars.getTotalPages());
}
} }
package cz.muni.pa165.rest;
import cz.muni.pa165.generated.api.DbManagementServiceApiDelegate;
import cz.muni.pa165.generated.model.SuccessfullyProcessed;
import cz.muni.pa165.service.DBManagementService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
/**
* @author Michal Badin
*/
@Profile("dev")
@Component
public class DBManagementController implements DbManagementServiceApiDelegate {
private static final Logger log = LoggerFactory.getLogger(DBManagementController.class);
private final DBManagementService dbManagementService;
@Autowired
public DBManagementController(DBManagementService dbManagementService) {
this.dbManagementService = dbManagementService;
}
@Override
public ResponseEntity<SuccessfullyProcessed> seedDB() {
log.debug("seedDB() called");
dbManagementService.seed();
return new ResponseEntity<>(HttpStatus.OK);
}
@Override
public ResponseEntity<SuccessfullyProcessed> clearDB() {
log.debug("clearDB() called");
dbManagementService.clear();
return new ResponseEntity<>(HttpStatus.OK);
}
}
...@@ -4,12 +4,21 @@ import cz.muni.pa165.data.enums.ApplicationStatusEnum; ...@@ -4,12 +4,21 @@ import cz.muni.pa165.data.enums.ApplicationStatusEnum;
import cz.muni.pa165.data.model.Application; import cz.muni.pa165.data.model.Application;
import cz.muni.pa165.data.repository.ApplicationRepository; import cz.muni.pa165.data.repository.ApplicationRepository;
import cz.muni.pa165.exceptions.ResourceNotFoundException; import cz.muni.pa165.exceptions.ResourceNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
/** /**
...@@ -17,12 +26,18 @@ import java.util.Optional; ...@@ -17,12 +26,18 @@ import java.util.Optional;
*/ */
@Service @Service
public class ApplicationService { public class ApplicationService {
private static final Logger log = LoggerFactory.getLogger(ApplicationService.class);
private static final String NOTIFICATION_MODULE_URL_NEW = "http://localhost:8083/notification/application/new";
private static final String NOTIFICATION_MODULE_URL_STATUS = "http://localhost:8083/notification/application/status";
private static final List<String> NOTIFICATION_RECEIVERS = List.of("formula.team.management@gmail.com");
private final ApplicationRepository applicationRepository; private final ApplicationRepository applicationRepository;
private final RestTemplate restTemplate;
@Autowired @Autowired
public ApplicationService(ApplicationRepository applicationRepository) { public ApplicationService(ApplicationRepository applicationRepository, RestTemplate restTemplate) {
this.applicationRepository = applicationRepository; this.applicationRepository = applicationRepository;
this.restTemplate = restTemplate;
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
...@@ -32,7 +47,17 @@ public class ApplicationService { ...@@ -32,7 +47,17 @@ public class ApplicationService {
public Application create(Application application) { public Application create(Application application) {
application.setFillingOutDate(LocalDate.now()); application.setFillingOutDate(LocalDate.now());
return applicationRepository.save(application); application.setStatus(ApplicationStatusEnum.PENDING);
var savedApplication = applicationRepository.save(application);
try {
sendPostRequest(savedApplication, NOTIFICATION_MODULE_URL_NEW, List.of(savedApplication.getEmail()));
} catch (RestClientException | IllegalArgumentException e) {
log.debug(String.format("The notification module is not reachable on the URL: %s", NOTIFICATION_MODULE_URL_NEW));
}
return savedApplication;
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
...@@ -50,6 +75,27 @@ public class ApplicationService { ...@@ -50,6 +75,27 @@ public class ApplicationService {
applicationFromDb.get().setStatus(applicationStatusEnum); applicationFromDb.get().setStatus(applicationStatusEnum);
return applicationRepository.save(applicationFromDb.get()); var savedApplication = applicationRepository.save(applicationFromDb.get());
if (savedApplication.getStatus() != ApplicationStatusEnum.PENDING) {
try {
sendPostRequest(savedApplication, NOTIFICATION_MODULE_URL_STATUS, NOTIFICATION_RECEIVERS);
} catch (RestClientException | IllegalArgumentException e) {
log.debug(String.format("The notification module is not reachable on the URL: %s", NOTIFICATION_MODULE_URL_NEW));
}
}
return savedApplication;
}
private void sendPostRequest(Application application, String url, List<String> receivers) {
Map<String, Object> notification = new HashMap<>();
notification.put("application", application);
notification.put("receivers", receivers);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> request = new HttpEntity<>(notification, headers);
restTemplate.postForObject(url, request, String.class);
} }
} }
package cz.muni.pa165.service;
import cz.muni.pa165.data.enums.ApplicationStatusEnum;
import cz.muni.pa165.data.model.Application;
import cz.muni.pa165.data.repository.ApplicationRepository;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
/**
* @author Michal Badin
*/
@Service
@Profile("dev")
public class DBManagementService {
private final ApplicationRepository applicationRepository;
@Autowired
public DBManagementService(ApplicationRepository applicationRepository) {
this.applicationRepository = applicationRepository;
}
@Transactional
@PostConstruct
public void seed() {
saveApplication(ApplicationStatusEnum.PENDING,
"Marek",
"Mrkvicka",
"marek.mrkvicka@example.com",
LocalDate.of(1999, 3, 8),
LocalDate.now());
saveApplication(ApplicationStatusEnum.ACCEPTED,
"Anuja",
"Ankska",
"ankska.anuja@example.com",
LocalDate.of(2000, 1, 30),
LocalDate.of(2010, 7, 18));
saveApplication(ApplicationStatusEnum.ACCEPTED,
"Xiao",
"Chen",
"xiao.chen@example.com",
LocalDate.of(1995, 7, 29),
LocalDate.of(2012, 10, 9));
saveApplication(ApplicationStatusEnum.REJECTED,
"Gertruda",
"Fialkova",
"gertruda.fialkova@example.com",
LocalDate.of(1965, 6, 2),
LocalDate.of(2023, 4, 26));
}
private void saveApplication(ApplicationStatusEnum status, String name, String surname, String email, LocalDate birthday, LocalDate fillingOutDate) {
Application application = new Application(status, name, surname, birthday, email, fillingOutDate);
applicationRepository.save(application);
}
@Transactional
public void clear() {
applicationRepository.deleteAll();
}
}
...@@ -10,4 +10,10 @@ spring.jpa.database-platform=org.hibernate.dialect.H2Dialect ...@@ -10,4 +10,10 @@ spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true spring.jpa.show-sql=true
spring.jackson.property-naming-strategy=SNAKE_CASE spring.jackson.property-naming-strategy=SNAKE_CASE
spring.cache.type=NONE spring.cache.type=NONE
appconfig.enablecache=false spring.profiles.active=dev
\ No newline at end of file appconfig.enablecache=false
management.endpoints.web.exposure.include=health,metrics,loggers,beans,prometheus
management.endpoint.health.show-details=always
management.endpoint.health.show-components=always
management.endpoint.health.probes.enabled=true
\ No newline at end of file
package cz.muni.pa165.rest; package cz.muni.pa165;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import java.time.LocalDate; import java.time.LocalDate;
...@@ -21,9 +22,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. ...@@ -21,9 +22,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/ */
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
class IntegrationTests { @ActiveProfiles("test")
class ApplicationIT {
private static final Logger log = LoggerFactory.getLogger(IntegrationTests.class); private static final Logger log = LoggerFactory.getLogger(ApplicationIT.class);
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
...@@ -32,7 +34,7 @@ class IntegrationTests { ...@@ -32,7 +34,7 @@ class IntegrationTests {
void testCreateApplication() throws Exception { void testCreateApplication() throws Exception {
log.info("testApplicationModuleResponse() running"); log.info("testApplicationModuleResponse() running");
String testRequestContent = "{\"name\":\"John\",\"surname\":\"Doe\",\"birthday\":\"2000-01-01\"}"; String testRequestContent = "{\"name\":\"John\",\"surname\":\"Doe\",\"birthday\":\"2000-01-01\",\"email\":\"john.doe@aaa.com\"}";
String response = mockMvc.perform( String response = mockMvc.perform(
post("/application") post("/application")
...@@ -43,7 +45,7 @@ class IntegrationTests { ...@@ -43,7 +45,7 @@ class IntegrationTests {
.andReturn().getResponse().getContentAsString(); .andReturn().getResponse().getContentAsString();
log.info("response: {}", response); log.info("response: {}", response);
String expectedResponse = "{\"id\":1,\"name\":\"John\",\"surname\":\"Doe\",\"birthday\":\"2000-01-01\",\"fillingOutDate\":\"" + LocalDate.now() + "\",\"status\":null}"; String expectedResponse = "{\"id\":1,\"name\":\"John\",\"surname\":\"Doe\",\"birthday\":\"2000-01-01\",\"email\":\"john.doe@aaa.com\",\"fillingOutDate\":\"" + LocalDate.now() + "\",\"status\":\"pending\"}";
assertEquals(expectedResponse, response); assertEquals(expectedResponse, response);
} }
......
...@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; ...@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.test.context.ActiveProfiles;
class ApplicationControllerTest { class ApplicationControllerTest {
......
...@@ -6,6 +6,8 @@ import cz.muni.pa165.data.repository.ApplicationRepository; ...@@ -6,6 +6,8 @@ import cz.muni.pa165.data.repository.ApplicationRepository;
import cz.muni.pa165.exceptions.ResourceNotFoundException; import cz.muni.pa165.exceptions.ResourceNotFoundException;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Optional; import java.util.Optional;
...@@ -16,8 +18,10 @@ import static org.mockito.Mockito.when; ...@@ -16,8 +18,10 @@ import static org.mockito.Mockito.when;
class ApplicationServiceTest { class ApplicationServiceTest {
ApplicationRepository applicationRepository = Mockito.mock(ApplicationRepository.class); ApplicationRepository applicationRepository = Mockito.mock(ApplicationRepository.class);
ApplicationService service = new ApplicationService(applicationRepository); RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
Application application = new Application(ApplicationStatusEnum.ACCEPTED, "John", "Doe", LocalDate.now().minusYears(20), LocalDate.now()); ApplicationService service = new ApplicationService(applicationRepository, restTemplate);
Application application = new Application(ApplicationStatusEnum.ACCEPTED, "John", "Doe",
LocalDate.now().minusYears(20), "john.doe@example.com", LocalDate.now());
@Test @Test
...@@ -27,11 +31,13 @@ class ApplicationServiceTest { ...@@ -27,11 +31,13 @@ class ApplicationServiceTest {
Application created = service.create(application); Application created = service.create(application);
assertEquals(application, created); assertEquals(application, created);
assertEquals(ApplicationStatusEnum.PENDING, created.getStatus());
} }
@Test @Test
void setStatus() { void setStatus() {
Application expected = new Application(ApplicationStatusEnum.REJECTED, "John", "Doe", LocalDate.now().minusYears(20), LocalDate.now()); Application expected = new Application(ApplicationStatusEnum.REJECTED, "John", "Doe",
LocalDate.now().minusYears(20), "john.doe@example.com", LocalDate.now());
when(applicationRepository.save(application)).thenReturn(expected); when(applicationRepository.save(application)).thenReturn(expected);
when(applicationRepository.findById(application.getId())).thenReturn(java.util.Optional.ofNullable(application)); when(applicationRepository.findById(application.getId())).thenReturn(java.util.Optional.ofNullable(application));
...@@ -44,7 +50,7 @@ class ApplicationServiceTest { ...@@ -44,7 +50,7 @@ class ApplicationServiceTest {
void setStatusUnknownApplication() { void setStatusUnknownApplication() {
when(applicationRepository.findById(application.getId())).thenReturn(Optional.empty()); when(applicationRepository.findById(application.getId())).thenReturn(Optional.empty());
assertThrows(ResourceNotFoundException.class, () -> service.setStatus(application.getId(), ApplicationStatusEnum.REJECTED)); assertThrows(ResourceNotFoundException.class,
() -> service.setStatus(application.getId(), ApplicationStatusEnum.REJECTED));
} }
} }
\ No newline at end of file
spring.datasource.url=jdbc:h2:mem:testApplicationdb;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE spring.datasource.url=jdbc:h2:mem:testApplicationdb;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;
spring.datasource.username=formulky-application-test spring.datasource.username=formulky-application-test
spring.datasource.password= spring.datasource.password=
spring.datasource.driverClassName=org.h2.Driver spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.generate_statistics=true spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.format_sql=true
......
...@@ -47,6 +47,7 @@ components: ...@@ -47,6 +47,7 @@ components:
type: string type: string
description: Specific information about component description: Specific information about component
example: lightweight front wing v2 (black) example: lightweight front wing v2 (black)
maxLength: 100
CarComponentCreateDto: CarComponentCreateDto:
type: object type: object
properties: properties:
...@@ -58,6 +59,7 @@ components: ...@@ -58,6 +59,7 @@ components:
information: information:
type: string type: string
description: Specific information about component description: Specific information about component
maxLength: 100
required: required:
- componentType - componentType
- weight - weight
...@@ -69,10 +71,11 @@ components: ...@@ -69,10 +71,11 @@ components:
$ref: '#/components/schemas/CarComponentType' $ref: '#/components/schemas/CarComponentType'
weight: weight:
type: number type: number
description: car's weight description: car component's weight
information: information:
type: string type: string
description: Specific information about component description: Specific information about component
maxLength: 100
CarDto: CarDto:
type: object type: object
...@@ -130,10 +133,12 @@ components: ...@@ -130,10 +133,12 @@ components:
type: string type: string
description: Name of the driver description: Name of the driver
example: Max example: Max
maxLength: 35
surname: surname:
type: string type: string
description: Surname of the driver description: Surname of the driver
example: Verstappen example: Verstappen
maxLength: 35
height: height:
type: integer type: integer
description: Height in cm description: Height in cm
...@@ -147,6 +152,7 @@ components: ...@@ -147,6 +152,7 @@ components:
type: string type: string
description: Nationality of the driver description: Nationality of the driver
example: Dutch example: Dutch
maxLength: 20
characteristics: characteristics:
type: array type: array
description: Set of driver's characteristics description: Set of driver's characteristics
...@@ -158,12 +164,15 @@ components: ...@@ -158,12 +164,15 @@ components:
name: name:
type: string type: string
description: Name of the driver description: Name of the driver
maxLength: 35
surname: surname:
type: string type: string
description: Name of the driver description: Name of the driver
maxLength: 35
nationality: nationality:
type: string type: string
description: nationality of the driver description: nationality of the driver
maxLength: 20
height: height:
type: integer type: integer
description: Height in cm description: Height in cm
...@@ -178,12 +187,15 @@ components: ...@@ -178,12 +187,15 @@ components:
name: name:
type: string type: string
description: Name of the driver description: Name of the driver
maxLength: 35
surname: surname:
type: string type: string
description: Name of the driver description: Name of the driver
maxLength: 35
nationality: nationality:
type: string type: string
description: Nationality of the driver description: Nationality of the driver
maxLength: 20
height: height:
type: integer type: integer
description: Height in cm description: Height in cm
...@@ -220,10 +232,12 @@ components: ...@@ -220,10 +232,12 @@ components:
type: string type: string
description: Name of the engineer description: Name of the engineer
example: John example: John
maxLength: 35
surname: surname:
type: string type: string
description: Surname of the engineer description: Surname of the engineer
example: Doe example: Doe
maxLength: 35
EngineerCreateDto: EngineerCreateDto:
type: object type: object
title: Engineer title: Engineer
...@@ -236,10 +250,12 @@ components: ...@@ -236,10 +250,12 @@ components:
type: string type: string
description: Name of the engineer description: Name of the engineer
example: John example: John
maxLength: 35
surname: surname:
type: string type: string
description: Surname of the engineer description: Surname of the engineer
example: Doe example: Doe
maxLength: 35
DepartmentDto: DepartmentDto:
type: object type: object
...@@ -259,6 +275,7 @@ components: ...@@ -259,6 +275,7 @@ components:
specialization: specialization:
type: string type: string
example: "Aerodynamics" example: "Aerodynamics"
maxLength: 100
DepartmentUpdateDto: DepartmentUpdateDto:
type: object type: object
properties: properties:
...@@ -266,6 +283,7 @@ components: ...@@ -266,6 +283,7 @@ components:
type: string type: string
example: "Aerodynamics" example: "Aerodynamics"
description: New specialization description: New specialization
maxLength: 100
required: required:
- specialization - specialization
DepartmentCreateDto: DepartmentCreateDto:
...@@ -274,6 +292,7 @@ components: ...@@ -274,6 +292,7 @@ components:
specialization: specialization:
type: string type: string
example: "Aerodynamics" example: "Aerodynamics"
maxLength: 100
required: required:
- specialization - specialization
Pageable: Pageable:
...@@ -347,6 +366,16 @@ components: ...@@ -347,6 +366,16 @@ components:
properties: properties:
message: message:
type: string type: string
Successful:
description: Successfully processed
content:
application/json:
schema:
type: object
title: Successfully processed
properties:
message:
type: string
paths: paths:
/driver: /driver:
...@@ -949,3 +978,26 @@ paths: ...@@ -949,3 +978,26 @@ paths:
$ref: '#/components/responses/NotFound' $ref: '#/components/responses/NotFound'
default: default:
$ref: '#/components/responses/Unexpected' $ref: '#/components/responses/Unexpected'
/seed:
post:
tags:
- DBManagementService
summary: Seed DB with data
operationId: seedDB
responses:
"200":
$ref: '#/components/responses/Successful'
default:
$ref: '#/components/responses/Unexpected'
/clear:
post:
tags:
- DBManagementService
summary: Clear DB
operationId: clearDB
responses:
"200":
$ref: '#/components/responses/Successful'
default:
$ref: '#/components/responses/Unexpected'
...@@ -159,6 +159,18 @@ ...@@ -159,6 +159,18 @@
<artifactId>jackson-datatype-jsr310</artifactId> <artifactId>jackson-datatype-jsr310</artifactId>
</dependency> </dependency>
<!-- Actuator dependency-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Prometheus dependency -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>
\ No newline at end of file
package cz.muni.pa165.actuator;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class CustomInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("course", "PA165").withDetail("module", "core").build();
}
}
...@@ -11,7 +11,7 @@ import java.util.Set; ...@@ -11,7 +11,7 @@ import java.util.Set;
@Entity @Entity
@Table(name = "car") @Table(name = "car")
public class Car extends DomainObject implements Serializable { public class Car extends DomainObject<Long> implements Serializable {
@OneToOne(fetch = FetchType.LAZY) @OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "driver_id") @JoinColumn(name = "driver_id")
@Nullable @Nullable
......