diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..62464086
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+/target/
+.idea/
+bin
+.classpath
+.project
+.settings/*
diff --git a/InterviewSantanderJava-soapui-project.xml b/InterviewSantanderJava-soapui-project.xml
new file mode 100644
index 00000000..f92393bd
--- /dev/null
+++ b/InterviewSantanderJava-soapui-project.xml
@@ -0,0 +1,5 @@
+
+http://localhost:8080application/json;charset=UTF-8200exp:Responseapplication/jsonhttp://localhost:8080{ "description": "asd1231", "value": 12783.23, "userCode": 12283, "date": "2019-09-02T20:21:42.026Z" }http://localhost/expense-management/expensesadminmyadminBasicBasicGlobal HTTP SettingsuserCodeuserCodeTEMPLATEuserCodeapplication/json;charset=UTF-8401ns:Faultapplication/json;charset=UTF-8200ns:Responsehttp://localhost:8080http://localhost/expense-management/expense/userCode/12283clienttheclientBasicBasicGlobal HTTP SettingsuserCodeuserCodeuserCodeTEMPLATEuserCodedatedateTEMPLATEdateapplication/json;charset=UTF-8400ns:Faultapplication/json;charset=UTF-8200ns:Responsehttp://localhost:8080http://localhost/expense-management/expense/userCode/12283/date/02092019clienttheclientBasicBasicGlobal HTTP Settings
+
+
+userCodedateididTEMPLATEidapplication/jsonapplication/json;charset=UTF-8200afa7:Response0data0datahttp://localhost:8080{ "description": "2134asd", "value": 12351.23, "userCode": 1227883, "date": "2019-09-01T17:21:42.026Z" }http://localhost/expense-management/expense/6655afa7-7679-4098-9927-6c975f67fbd5clienttheclientBasicBasicGlobal HTTP Settingsidapplication/jsonapplication/json;charset=UTF-8200cat:Responsehttp://localhost:8080{ "detail": "teste" }http://localhost/expense-management/categoriesclienttheclientBasicBasicGlobal HTTP SettingsdetailPrefixdetailPrefixTEMPLATEdetailPrefixapplication/json;charset=UTF-8200tes:Responsehttp://localhost:8080http://localhost/expense-management/category/detail/testeclienttheclientBasicBasicGlobal HTTP SettingsdetailPrefixPARALLELLfalse1000250truetrue-1100000COUNTSimple00.5100true500interviewadminmyadmin
\ No newline at end of file
diff --git a/README.md b/README.md
index 15d8f685..184c2169 100644
--- a/README.md
+++ b/README.md
@@ -1,76 +1,29 @@
-# Show me the code
### # DESAFIO:
API REST para Gestão de Gastos!
+***
+
+#### Configurando o Mongo
```
-Funcionalidade: Integração de gastos por cartão
- Apenas sistemas credenciados poderão incluir novos gastos
- É esperado um volume de 100.000 inclusões por segundo
- Os gastos, serão informados atraves do protoloco JSON, seguindo padrão:
- { "descricao": "alfanumerico", "valor": double americano, "codigousuario": numerico, "data": Data dem formato UTC }
-```
-```
-Funcionalidade: Listagem de gastos*
- Dado que acesso como um cliente autenticado que pode visualizar os gastos do cartão
- Quando acesso a interface de listagem de gastos
- Então gostaria de ver meus gastos mais atuais.
-
-*Para esta funcionalidade é esperado 2.000 acessos por segundo.
-*O cliente espera ver gastos realizados a 5 segundos atrás.
-```
-```
-Funcionalidade: Filtro de gastos
- Dado que acesso como um cliente autenticado
- E acessei a interface de listagem de gastos
- E configure o filtro de data igual a 27/03/1992
- Então gostaria de ver meus gastos apenas deste dia.
-```
-```
-Funcionalidade: Categorização de gastos
- Dado que acesso como um cliente autenticado
- Quando acesso o detalhe de um gasto
- E este não possui uma categoria
- Então devo conseguir incluir uma categoria para este
+docker pull mongo
+docker run -d -p 27018:27017 mongo
```
+
+#### Executando o projeto
```
-Funcionalidade: Sugestão de categoria
- Dado que acesso como um cliente autenticado
- Quando acesso o detalhe do gasto que não possui categoria
- E começo a digitar a categoria que desejo
- Então uma lista de sugestões de categoria deve ser exibida, estas baseadas em categorias já informadas por outro usuários.
+mvn install
+mvn clean spring-boot:run
```
+
+As variáveis de ambiente estão configuradas no arquivo *application.properties*.
```
-Funcionalidade: Categorização automatica de gasto
- No processo de integração de gastos, a categoria deve ser incluida automaticamente
- caso a descrição de um gasto seja igual a descrição de qualquer outro gasto já categorizado pelo cliente
- o mesmo deve receber esta categoria no momento da inclusão do mesmo
+mongo.host=localhost
+mongo.port=27018
+mongo.database=app1
+thread.async_core_pool_size=5
+thread.async_max_pool_size=50
```
-### # Avaliação
-
-Você será avaliado pela usabilidade, por respeitar o design e pela arquitetura da API.
-É esperado que você consiga explicar as decisões que tomou durante o desenvolvimento através de commits.
-
-* Springboot - Java - Maven (preferêncialmente) ([https://projects.spring.io/spring-boot/](https://projects.spring.io/spring-boot/))
-* RESTFul ([https://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/](https://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/))
-* DDD ([https://airbrake.io/blog/software-design/domain-driven-design](https://airbrake.io/blog/software-design/domain-driven-design))
-* Microservices ([https://martinfowler.com/microservices/](https://martinfowler.com/microservices/))
-* Testes unitários, teste o que achar importante (De preferência JUnit + Mockito). Mas pode usar o que você tem mais experiência, só nos explique o que ele tem de bom.
-* SOAPUI para testes de carga ([https://www.soapui.org/load-testing/concept.html](https://www.soapui.org/load-testing/concept.html))
-* Uso de diferentes formas de armazenamento de dados (REDIS, Cassandra, Solr/Lucene)
-* Uso do git
-* Diferencial: Criptografia de comunicação, com troca de chaves. ([http://noiseprotocol.org/](http://noiseprotocol.org/))
-* Diferencial: CQRS ([https://martinfowler.com/bliki/CQRS.html](https://martinfowler.com/bliki/CQRS.html))
-* Diferencial: Docker File + Docker Compose (com dbs) para rodar seus jars.
-
-### # Observações gerais
-
-Adicione um arquivo [README.md](http://README.md) com os procedimentos para executar o projeto.
-Pedimos que trabalhe sozinho e não divulgue o resultado na internet.
-
-Faça um fork desse desse repositório em seu Github e nos envie um Pull Request com o resultado, por favor informe por qual empresa você esta se candidatando.
-
-### # Importante: não há prazo de entrega, faça com qualidade!
-# BOA SORTE!
+O arquivo *InterviewSantanderJava-soapui-project.xml* contém exemplos de chamadas para este projeto.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..1012a353
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,100 @@
+
+
+ 4.0.0
+
+ com.santander.interview
+ TestBackJava
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.6.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.data
+ spring-data-mongodb
+ 2.1.9.RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ io.springfox
+ springfox-swagger2
+ 2.9.2
+
+
+ io.swagger
+ swagger-models
+
+
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
+
+ io.swagger
+ swagger-models
+ 1.5.21
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ junit
+ junit
+ test
+
+
+
+
+ 1.8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.santander.interview.ExpenseManagement
+ JAR
+
+
+
+
+ repackage
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/santander/interview/ExpenseManagement.java b/src/main/java/com/santander/interview/ExpenseManagement.java
new file mode 100644
index 00000000..d28ea1ec
--- /dev/null
+++ b/src/main/java/com/santander/interview/ExpenseManagement.java
@@ -0,0 +1,17 @@
+package com.santander.interview;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+@EnableAsync
+@SpringBootApplication
+@ComponentScan(basePackages = {"com.santander.interview"})
+public class ExpenseManagement {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ExpenseManagement.class, args);
+ }
+
+}
diff --git a/src/main/java/com/santander/interview/SecurityInit.java b/src/main/java/com/santander/interview/SecurityInit.java
new file mode 100644
index 00000000..9f467bc3
--- /dev/null
+++ b/src/main/java/com/santander/interview/SecurityInit.java
@@ -0,0 +1,21 @@
+package com.santander.interview;
+
+import com.santander.interview.config.SecurityConfig;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
+
+@Configuration
+public class SecurityInit extends AbstractSecurityWebApplicationInitializer {
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ public SecurityInit() {
+ super(SecurityConfig.class);
+ }
+}
diff --git a/src/main/java/com/santander/interview/config/MongoConfig.java b/src/main/java/com/santander/interview/config/MongoConfig.java
new file mode 100644
index 00000000..78a905f7
--- /dev/null
+++ b/src/main/java/com/santander/interview/config/MongoConfig.java
@@ -0,0 +1,33 @@
+package com.santander.interview.config;
+
+import com.mongodb.MongoClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
+
+@Configuration
+public class MongoConfig extends AbstractMongoConfiguration {
+// @Value("#{environment['MONGO_HOST']}")
+ @Value("${mongo.host}")
+ private String MONGO_HOST;
+
+// @Value("#{environment['MONGO_PORT']}")
+ @Value("${mongo.port}")
+ private int MONGO_PORT;
+
+// @Value("#{environment['MONGO_DATABASE']}")
+ @Value("${mongo.database}")
+ private String MONGO_DATABASE;
+
+ @Bean
+ @Override
+ public MongoClient mongoClient() {
+ return new MongoClient(MONGO_HOST, MONGO_PORT);
+ }
+
+ @Override
+ protected String getDatabaseName() {
+ return MONGO_DATABASE;
+ }
+}
diff --git a/src/main/java/com/santander/interview/config/SecurityConfig.java b/src/main/java/com/santander/interview/config/SecurityConfig.java
new file mode 100644
index 00000000..7ee4f1e1
--- /dev/null
+++ b/src/main/java/com/santander/interview/config/SecurityConfig.java
@@ -0,0 +1,40 @@
+package com.santander.interview.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ PasswordEncoder passwordEncoder;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.csrf().disable().authorizeRequests()
+ .antMatchers(HttpMethod.POST, "/expense-management/expenses").hasRole("ADMIN")
+ .antMatchers("/expense-management/expense/**").hasRole("CLIENT")
+ .antMatchers("/expense-management/categories").hasRole("CLIENT")
+ .antMatchers("/expense-management/category/**").hasRole("CLIENT")
+ .anyRequest().authenticated()
+ .and().httpBasic();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication()
+// .passwordEncoder(passwordEncoder)
+ .withUser("admin").password(passwordEncoder.encode("myadmin")).roles("ADMIN")
+// .withUser("admin").password("{noop}myadmin").roles("ADMIN")
+ .and()
+ .withUser("client").password(passwordEncoder.encode("theclient")).roles("CLIENT");
+
+ }
+}
diff --git a/src/main/java/com/santander/interview/config/SwaggerConfig.java b/src/main/java/com/santander/interview/config/SwaggerConfig.java
new file mode 100644
index 00000000..0588a003
--- /dev/null
+++ b/src/main/java/com/santander/interview/config/SwaggerConfig.java
@@ -0,0 +1,22 @@
+package com.santander.interview.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+ @Bean
+ public Docket api () {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .select()
+ .apis(RequestHandlerSelectors.any())
+ .paths(PathSelectors.ant("/expense-management/**"))
+ .build();
+ }
+}
diff --git a/src/main/java/com/santander/interview/config/ThreadConfig.java b/src/main/java/com/santander/interview/config/ThreadConfig.java
new file mode 100644
index 00000000..8b7959ca
--- /dev/null
+++ b/src/main/java/com/santander/interview/config/ThreadConfig.java
@@ -0,0 +1,38 @@
+package com.santander.interview.config;
+
+import com.santander.interview.utils.ExpenseManagementUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+
+@Configuration
+public class ThreadConfig {
+ public static final String THREAD_NAME_PREFIX = "interview";
+
+// @Value("#{environment['ASYNC_CORE_POOL_SIZE']}")
+ @Value("${thread.async_core_pool_size}")
+ private String ASYNC_CORE_POOL_SIZE;
+
+// @Value("#{environment['ASYNC_MAX_POOL_SIZE']}")
+ @Value("${thread.async_max_pool_size}")
+ private String ASYNC_MAX_POOL_SIZE;
+
+ @Bean
+ public Executor asyncExecutor() {
+ ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
+ threadPoolTaskExecutor.setCorePoolSize(
+ ExpenseManagementUtils.convertStringtoInt(10, ASYNC_CORE_POOL_SIZE)
+ );
+ threadPoolTaskExecutor.setMaxPoolSize(
+ ExpenseManagementUtils.convertStringtoInt(100, ASYNC_MAX_POOL_SIZE)
+ );
+ threadPoolTaskExecutor.setQueueCapacity(Integer.MAX_VALUE);
+ threadPoolTaskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);
+ threadPoolTaskExecutor.initialize();
+
+ return threadPoolTaskExecutor;
+ }
+}
diff --git a/src/main/java/com/santander/interview/controller/CategoryController.java b/src/main/java/com/santander/interview/controller/CategoryController.java
new file mode 100644
index 00000000..24baf437
--- /dev/null
+++ b/src/main/java/com/santander/interview/controller/CategoryController.java
@@ -0,0 +1,49 @@
+package com.santander.interview.controller;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+import com.santander.interview.domain.Category;
+import com.santander.interview.domain.Response;
+import com.santander.interview.domain.ResponseObject;
+import com.santander.interview.enums.ResponseMessageEnum;
+import com.santander.interview.service.CategoryService;
+import com.santander.interview.utils.ExpenseManagementUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/expense-management")
+@Api(value = "Categoria")
+public class CategoryController {
+
+ @Autowired
+ CategoryService categoryService;
+
+ @ApiOperation("Adicionar uma nova categoria")
+ @PostMapping("/categories")
+ public ResponseEntity addCategory(
+ @ApiParam(value = "Nova categoria", required = true) @RequestBody Category category
+ ) {
+ categoryService.saveCategory(category);
+
+ return ExpenseManagementUtils.responseWithoutData(ADD_CATEGORY_SUCCESS, HttpStatus.OK);
+ }
+
+ @ApiOperation("Sugestão de categoria")
+ @GetMapping("/category/detail/{detailSubstring}")
+ public ResponseEntity suggestionCategory(
+ @ApiParam(value = "Substring da categoria", required = true) @PathVariable String detailSubstring
+ ) {
+ List categories = this.categoryService.searchCategoryByDetailSubstring(detailSubstring);
+
+ return ExpenseManagementUtils.responseWithData(SUGGESTION_CATEGORY_SUCCESS, HttpStatus.OK, categories);
+ }
+
+}
diff --git a/src/main/java/com/santander/interview/controller/ExpenseController.java b/src/main/java/com/santander/interview/controller/ExpenseController.java
new file mode 100644
index 00000000..3cb431f2
--- /dev/null
+++ b/src/main/java/com/santander/interview/controller/ExpenseController.java
@@ -0,0 +1,77 @@
+package com.santander.interview.controller;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+import com.santander.interview.domain.Expense;
+import com.santander.interview.domain.Response;
+import com.santander.interview.domain.ResponseObject;
+import com.santander.interview.exception.ExpenseException;
+import com.santander.interview.service.ExpenseService;
+import com.santander.interview.utils.ExpenseManagementUtils;
+import io.swagger.annotations.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/expense-management")
+@Api(value = "Gasto")
+public class ExpenseController {
+
+ @Autowired
+ ExpenseService expenseService;
+
+ @ApiOperation(value = "Adicionar gasto")
+ @PostMapping("/expenses")
+ public ResponseEntity addExpense(
+ @ApiParam(value = "Novo gasto", required = true) @RequestBody Expense expense
+ ) {
+ this.expenseService.addNewExpense(expense);
+
+ return ExpenseManagementUtils.responseWithoutData(ADD_EXPENSE_SUCCESS, HttpStatus.OK);
+ }
+
+ @ApiOperation("Buscar gastos por usuário")
+ @GetMapping("/expense/userCode/{userCode}")
+ public ResponseEntity getExpenseByUserCode(
+ @ApiParam(value = "Código do cliente", required = true) @PathVariable long userCode
+ ) {
+ List expensesByUserCode = this.expenseService.searchExpensesByUserCode(userCode);
+
+ return ExpenseManagementUtils.responseWithData(SEARCH_EXPENSE_BY_USER_CODE_SUCCESS,
+ HttpStatus.OK, expensesByUserCode);
+ }
+
+ @ApiOperation("Buscar gastos por usuário e data")
+ @GetMapping("/expense/userCode/{userCode}/date/{date}")
+ public ResponseEntity> getExpenseByUserCodeAndDate(
+ @ApiParam(value = "Código do cliente", required = true) @PathVariable long userCode,
+ @ApiParam(value = "Data a ser pesquisada", required = true) @PathVariable String date
+ ) {
+ List expensesByUserCodeAndDate;
+ try {
+ expensesByUserCodeAndDate = this.expenseService.searchExpensesByUserCodeAndDate(userCode, date);
+ return ExpenseManagementUtils.responseWithData(SEARCH_EXPENSE_BY_USER_CODE_AND_DATE_SUCCESS,
+ HttpStatus.OK, expensesByUserCodeAndDate);
+ } catch (ExpenseException ee){
+ return ExpenseManagementUtils.responseWithError(ee.getResponseMessageEnum(), ee.getStatusCode());
+ }
+ }
+
+ @ApiOperation("Atualizar gasto")
+ @PutMapping("/expense/{id}")
+ public ResponseEntity> updateExpense(
+ @ApiParam(value = "ID do gasto a ser atualizado", required = true) @PathVariable String id,
+ @ApiParam(value = "Gasto atualizado", required = true) @RequestBody Expense expense
+ ) {
+ try {
+ this.expenseService.updateExpense(id, expense);
+ return ExpenseManagementUtils.responseWithoutData(UPDATE_EXPENSE_SUCCESS, HttpStatus.OK);
+ } catch (ExpenseException ee) {
+ return ExpenseManagementUtils.responseWithError(ee.getResponseMessageEnum(), ee.getStatusCode());
+ }
+ }
+}
diff --git a/src/main/java/com/santander/interview/domain/Category.java b/src/main/java/com/santander/interview/domain/Category.java
new file mode 100644
index 00000000..933231ac
--- /dev/null
+++ b/src/main/java/com/santander/interview/domain/Category.java
@@ -0,0 +1,46 @@
+package com.santander.interview.domain;
+
+import com.santander.interview.repository.CategoryRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.annotation.Id;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.UUID;
+
+@Component
+public class Category {
+ @Id
+ private String id;
+ private String detail;
+
+ @Autowired
+ CategoryRepository categoryRepository;
+
+ private String generateID() { return UUID.randomUUID().toString(); }
+
+ public Category() { }
+
+ public Category(String detail) {
+ this.id = this.generateID();
+ this.detail = detail;
+ }
+
+ public String getId() { return id; }
+
+ public void setId(String id) { this.id = id; }
+
+ public String getDetail() { return detail; }
+
+ public void setDetail(String detail) { this.detail = detail; }
+
+ public void save(Category category) {
+ Category newCategory = new Category(category.getDetail());
+ this.categoryRepository.save(newCategory);
+ }
+
+ public List searchByDetailSubstring(String detailSubstring) {
+ return this.categoryRepository.findByDetailLike(detailSubstring);
+ }
+
+}
diff --git a/src/main/java/com/santander/interview/domain/Expense.java b/src/main/java/com/santander/interview/domain/Expense.java
new file mode 100644
index 00000000..a17c288b
--- /dev/null
+++ b/src/main/java/com/santander/interview/domain/Expense.java
@@ -0,0 +1,135 @@
+package com.santander.interview.domain;
+
+import com.santander.interview.repository.CategoryRepository;
+import com.santander.interview.repository.ExpenseRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.annotation.Id;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+
+@Component
+public class Expense {
+ @Id
+ private String id;
+ private String description;
+ private double value;
+ private long userCode;
+ private Date date;
+ private Category category;
+
+ @Autowired
+ ExpenseRepository expenseRepository;
+
+ @Autowired
+ CategoryRepository categoryRepository;
+
+ private String generateID() { return UUID.randomUUID().toString(); }
+
+ public Expense() { this.id = this.generateID(); }
+
+ public Expense(String description, double value, long userCode, Date date) {
+ this.id = UUID.randomUUID().toString();
+ this.description = description;
+ this.value = value;
+ this.userCode = userCode;
+ this.date = date;
+ }
+
+ public Expense(String description, double value, long userCode, Date date, Category category) {
+ this.id = UUID.randomUUID().toString();
+ this.description = description;
+ this.value = value;
+ this.userCode = userCode;
+ this.date = date;
+ this.category = category;
+ }
+
+ public String getId() { return id; }
+
+ public void setId(String id) { this.id = id; }
+
+ public String getDescription() { return description; }
+
+ public void setDescription(String description) { this.description = description; }
+
+ public double getValue() { return value; }
+
+ public void setValue(double value) { this.value = value; }
+
+ public long getUserCode() { return userCode; }
+
+ public void setUserCode(long userCode) { this.userCode = userCode; }
+
+ public Date getDate() { return date; }
+
+ public void setDate(Date date) { this.date = date; }
+
+ public Category getCategory() { return category; }
+
+ public void setCategory(Category category) { this.category = category; }
+
+ private Category automaticCategorization(Expense expense) {
+ Category category = expense.getCategory();
+ if(category != null && category.getDetail() != null) {
+ List list = categoryRepository.findByDetail(category.getDetail());
+ if (list.size() > 0)
+ return list.get(0);
+ }
+ return null;
+ }
+
+ public void add(Expense expense) {
+ Expense newExpense = new Expense(
+ expense.getDescription(),
+ expense.getValue(),
+ expense.getUserCode(),
+ expense.getDate()
+ );
+ newExpense.setCategory(this.automaticCategorization(expense));
+ this.expenseRepository.save(newExpense);
+ }
+
+ public List searchByUserCode(long userCode) {
+ List expenses = expenseRepository.findByUserCode(userCode);
+ Collections.sort(expenses, new Comparator() {
+ @Override
+ public int compare(Expense expense1, Expense expense2) {
+ return expense2.getDate().compareTo(expense1.getDate());
+ }
+ });
+ return expenses;
+ }
+
+ public List searchByUserCodeAndDate(long userCode, String date) throws ParseException {
+ long oneDayInMilliseconds = 1000 * 60 * 60 * 24;
+
+ Date startDate = new SimpleDateFormat("ddMMyyyy").parse(date);
+ Date endDate = new Date(startDate.getTime() + oneDayInMilliseconds);
+ return this.expenseRepository.findByUserCodeAndDateBetween(userCode, startDate, endDate);
+
+ }
+
+ public boolean update(String id, Expense expense) {
+ Optional existExpense = this.expenseRepository.findById(id);
+ if(existExpense.isPresent()) {
+ Expense expenseFound = existExpense.get();
+ expense.setId(expenseFound.getId());
+ expense.setCategory(this.automaticCategorization(expense));
+ this.expenseRepository.save(expense);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "Expense[id=%s, descricao=%s, valor=%f, codigoUsuario=%d, data=%s]",
+ this.id, this.description, this.value, this.userCode, this.date.toString()
+ );
+ }
+}
diff --git a/src/main/java/com/santander/interview/domain/Response.java b/src/main/java/com/santander/interview/domain/Response.java
new file mode 100644
index 00000000..87291829
--- /dev/null
+++ b/src/main/java/com/santander/interview/domain/Response.java
@@ -0,0 +1,28 @@
+package com.santander.interview.domain;
+
+public class Response {
+ private long statusCode;
+ private String userMessage;
+ private String internalMessage;
+
+ public Response() { }
+
+ public Response(long statusCode, String userMessage, String internalMessage) {
+ this.statusCode = statusCode;
+ this.userMessage = userMessage;
+ this.internalMessage = internalMessage;
+ }
+
+ public long getStatusCode() { return statusCode; }
+
+ public void setStatusCode(long statusCode) { this.statusCode = statusCode; }
+
+ public String getUserMessage() { return userMessage; }
+
+ public void setUserMessage(String userMessage) { this.userMessage = userMessage; }
+
+ public String getInternalMessage() { return internalMessage; }
+
+ public void setInternalMessage(String internalMessage) { this.internalMessage = internalMessage; }
+
+}
diff --git a/src/main/java/com/santander/interview/domain/ResponseError.java b/src/main/java/com/santander/interview/domain/ResponseError.java
new file mode 100644
index 00000000..9f7d70b2
--- /dev/null
+++ b/src/main/java/com/santander/interview/domain/ResponseError.java
@@ -0,0 +1,14 @@
+package com.santander.interview.domain;
+
+public class ResponseError extends Response {
+ int internalCode;
+
+ public ResponseError(long statusCode, String userMessage, String internalMessage, int internalCode) {
+ super(statusCode, userMessage, internalMessage);
+ this.internalCode = internalCode;
+ }
+
+ public int getInternalCode() { return internalCode; }
+
+ public void setInternalCode(int internalCode) { this.internalCode = internalCode; }
+}
diff --git a/src/main/java/com/santander/interview/domain/ResponseObject.java b/src/main/java/com/santander/interview/domain/ResponseObject.java
new file mode 100644
index 00000000..59377419
--- /dev/null
+++ b/src/main/java/com/santander/interview/domain/ResponseObject.java
@@ -0,0 +1,16 @@
+package com.santander.interview.domain;
+
+public class ResponseObject extends Response {
+ private Object data;
+
+ public ResponseObject() { }
+
+ public ResponseObject(long statusCode, String userMessage, String internalMessage, Object data) {
+ super(statusCode, userMessage, internalMessage);
+ this.data = data;
+ }
+
+ public Object getData() { return data; }
+
+ public void setData(Object data) { this.data = data; }
+}
diff --git a/src/main/java/com/santander/interview/enums/ResponseMessageEnum.java b/src/main/java/com/santander/interview/enums/ResponseMessageEnum.java
new file mode 100644
index 00000000..8571da37
--- /dev/null
+++ b/src/main/java/com/santander/interview/enums/ResponseMessageEnum.java
@@ -0,0 +1,39 @@
+package com.santander.interview.enums;
+
+public enum ResponseMessageEnum {
+ UNKNOWN_ERROR(0, "", ""),
+ ADD_EXPENSE_SUCCESS(1,"Gasto criado com sucesso", "Gasto criado"),
+// EXPENSE_NOT_FOUND_TO_USER_CODE(2, "Esse cliente não possui gastos",
+// "Não foram encontrados gastos para esse código de usuário"),
+ SEARCH_EXPENSE_BY_USER_CODE_SUCCESS(3, "Busca pelo cliente realizada com sucesso",
+ "Gastos do usuário encontrados"),
+ EXPENSE_BADLY_FORMATTED_DATE(4, "Data mal formatada",
+ "A data deve estar no formato ddMMyyyy"),
+ EXPENSE_NOT_FOUND(5, "Gasto não encontrado",
+ "Não foi encontrado gasto para o ID informado"),
+ SEARCH_EXPENSE_BY_USER_CODE_AND_DATE_SUCCESS(6,
+ "Busca realizada com sucesso",
+ "Busca por código do usuário e pela data realizada com sucesso"),
+ UPDATE_EXPENSE_SUCCESS(7, "Gasto atualizado com sucesso",
+ "Gasto encontrado e atualizado"),
+ ADD_CATEGORY_SUCCESS(8, "Categoria adicionada com sucesso",
+ "Categoria criada"),
+ SUGGESTION_CATEGORY_SUCCESS(9, "Busca da categoria realizada com sucesso",
+ "Lista com categorias");
+
+ private int code;
+ private String userMessage;
+ private String internalMessage;
+
+ private ResponseMessageEnum(int code, String userMessage, String internalMessage) {
+ this.code = code;
+ this.userMessage = userMessage;
+ this.internalMessage = internalMessage;
+ }
+
+ public int getCode() { return this.code; }
+
+ public String getUserMessage() { return this.userMessage; }
+
+ public String getInternalMessage() { return this.internalMessage; }
+}
diff --git a/src/main/java/com/santander/interview/exception/ExpenseException.java b/src/main/java/com/santander/interview/exception/ExpenseException.java
new file mode 100644
index 00000000..b6a54fec
--- /dev/null
+++ b/src/main/java/com/santander/interview/exception/ExpenseException.java
@@ -0,0 +1,48 @@
+package com.santander.interview.exception;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+import com.santander.interview.enums.ResponseMessageEnum;
+import org.springframework.http.HttpStatus;
+
+public class ExpenseException extends Exception{
+ private HttpStatus statusCode;
+ private ResponseMessageEnum responseMessageEnum;
+ private String message;
+
+ public ExpenseException() {
+ super();
+ this.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
+ this.message = super.getMessage();
+ this.responseMessageEnum = UNKNOWN_ERROR;
+ }
+
+ public ExpenseException(HttpStatus statusCode, String message, ResponseMessageEnum responseMessageEnum) {
+ super(message);
+ this.statusCode = statusCode;
+ this.message = message;
+ this.responseMessageEnum = responseMessageEnum;
+ }
+
+ public ExpenseException(HttpStatus statusCode, ResponseMessageEnum responseMessageEnum) {
+ super();
+ this.statusCode = statusCode;
+ this.message = super.getMessage();
+ this.responseMessageEnum = responseMessageEnum;
+ }
+
+ public HttpStatus getStatusCode() { return statusCode; }
+
+ public void setStatusCode(HttpStatus statusCode) { this.statusCode = statusCode; }
+
+ @Override
+ public String getMessage() { return message; }
+
+ public void setMessage(String message) { this.message = message; }
+
+ public ResponseMessageEnum getResponseMessageEnum() { return responseMessageEnum; }
+
+ public void setResponseMessageEnum(ResponseMessageEnum responseMessageEnum) {
+ this.responseMessageEnum = responseMessageEnum;
+ }
+}
diff --git a/src/main/java/com/santander/interview/repository/CategoryRepository.java b/src/main/java/com/santander/interview/repository/CategoryRepository.java
new file mode 100644
index 00000000..be18bdc6
--- /dev/null
+++ b/src/main/java/com/santander/interview/repository/CategoryRepository.java
@@ -0,0 +1,13 @@
+package com.santander.interview.repository;
+
+import com.santander.interview.domain.Category;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface CategoryRepository extends MongoRepository {
+ public List findByDetail(String detail);
+ public List findByDetailLike(String detail);
+}
diff --git a/src/main/java/com/santander/interview/repository/ExpenseRepository.java b/src/main/java/com/santander/interview/repository/ExpenseRepository.java
new file mode 100644
index 00000000..a494c648
--- /dev/null
+++ b/src/main/java/com/santander/interview/repository/ExpenseRepository.java
@@ -0,0 +1,14 @@
+package com.santander.interview.repository;
+
+import com.santander.interview.domain.Expense;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Date;
+import java.util.List;
+
+@Repository
+public interface ExpenseRepository extends MongoRepository {
+ public List findByUserCode(long userCode);
+ public List findByUserCodeAndDateBetween(long userCode, Date startData, Date endData);
+}
diff --git a/src/main/java/com/santander/interview/service/CategoryService.java b/src/main/java/com/santander/interview/service/CategoryService.java
new file mode 100644
index 00000000..1969ba2c
--- /dev/null
+++ b/src/main/java/com/santander/interview/service/CategoryService.java
@@ -0,0 +1,10 @@
+package com.santander.interview.service;
+
+import com.santander.interview.domain.Category;
+
+import java.util.List;
+
+public interface CategoryService {
+ public void saveCategory(Category newCategory);
+ public List searchCategoryByDetailSubstring(String detailSubstring);
+}
diff --git a/src/main/java/com/santander/interview/service/ExpenseService.java b/src/main/java/com/santander/interview/service/ExpenseService.java
new file mode 100644
index 00000000..58143f51
--- /dev/null
+++ b/src/main/java/com/santander/interview/service/ExpenseService.java
@@ -0,0 +1,13 @@
+package com.santander.interview.service;
+
+import com.santander.interview.domain.Expense;
+import com.santander.interview.exception.ExpenseException;
+
+import java.util.List;
+
+public interface ExpenseService {
+ public void addNewExpense(Expense newExpense);
+ public List searchExpensesByUserCode(long userCode);
+ public List searchExpensesByUserCodeAndDate(long userCode, String date) throws ExpenseException;
+ public void updateExpense(String id, Expense newExpense) throws ExpenseException;
+}
diff --git a/src/main/java/com/santander/interview/service/impl/CategoryServiceImpl.java b/src/main/java/com/santander/interview/service/impl/CategoryServiceImpl.java
new file mode 100644
index 00000000..7d27ef7e
--- /dev/null
+++ b/src/main/java/com/santander/interview/service/impl/CategoryServiceImpl.java
@@ -0,0 +1,24 @@
+package com.santander.interview.service.impl;
+
+import com.santander.interview.domain.Category;
+import com.santander.interview.service.CategoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class CategoryServiceImpl implements CategoryService {
+ @Autowired
+ Category category;
+
+ @Override
+ public void saveCategory(Category newCategory) {
+ this.category.save(newCategory);
+ }
+
+ @Override
+ public List searchCategoryByDetailSubstring(String detailSubstring) {
+ return this.category.searchByDetailSubstring(detailSubstring);
+ }
+}
diff --git a/src/main/java/com/santander/interview/service/impl/ExpenseServiceImpl.java b/src/main/java/com/santander/interview/service/impl/ExpenseServiceImpl.java
new file mode 100644
index 00000000..d0cc29b1
--- /dev/null
+++ b/src/main/java/com/santander/interview/service/impl/ExpenseServiceImpl.java
@@ -0,0 +1,47 @@
+package com.santander.interview.service.impl;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+import com.santander.interview.domain.Expense;
+import com.santander.interview.exception.ExpenseException;
+import com.santander.interview.service.ExpenseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.text.ParseException;
+import java.util.*;
+
+@Service
+public class ExpenseServiceImpl implements ExpenseService {
+ @Autowired
+ Expense expense;
+
+ @Async
+ @Override
+ public void addNewExpense(Expense newExpense) {
+ this.expense.add(newExpense);
+ }
+
+ @Override
+ public List searchExpensesByUserCode(long userCode) {
+ return this.expense.searchByUserCode(userCode);
+ }
+
+ @Override
+ public List searchExpensesByUserCodeAndDate(long userCode, String date) throws ExpenseException {
+ try {
+ return this.expense.searchByUserCodeAndDate(userCode, date);
+ } catch(ParseException pe) {
+ throw new ExpenseException(HttpStatus.BAD_REQUEST, EXPENSE_BADLY_FORMATTED_DATE);
+ }
+ }
+
+ @Override
+ public void updateExpense(String id, Expense newExpense) throws ExpenseException {
+ if (!this.expense.update(id, newExpense)) {
+ throw new ExpenseException(HttpStatus.NOT_FOUND, EXPENSE_NOT_FOUND);
+ }
+ }
+}
diff --git a/src/main/java/com/santander/interview/utils/ExpenseManagementUtils.java b/src/main/java/com/santander/interview/utils/ExpenseManagementUtils.java
new file mode 100644
index 00000000..2b03205c
--- /dev/null
+++ b/src/main/java/com/santander/interview/utils/ExpenseManagementUtils.java
@@ -0,0 +1,56 @@
+package com.santander.interview.utils;
+
+import com.santander.interview.domain.Response;
+import com.santander.interview.domain.ResponseError;
+import com.santander.interview.domain.ResponseObject;
+import com.santander.interview.enums.ResponseMessageEnum;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+public class ExpenseManagementUtils {
+
+ public static int convertStringtoInt(int defaultValue, String value) {
+ try {
+ return Integer.valueOf(value);
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+
+ public static ResponseEntity responseWithData (ResponseMessageEnum responseMessageEnum,
+ HttpStatus httpStatus,
+ Object data) {
+ return new ResponseEntity<>(
+ new ResponseObject(
+ httpStatus.value(),
+ responseMessageEnum.getUserMessage(),
+ responseMessageEnum.getInternalMessage(),
+ data
+ ),
+ httpStatus);
+ }
+
+ public static ResponseEntity responseWithoutData (ResponseMessageEnum responseMessageEnum,
+ HttpStatus httpStatus) {
+ return new ResponseEntity<>(
+ new Response(
+ httpStatus.value(),
+ responseMessageEnum.getUserMessage(),
+ responseMessageEnum.getInternalMessage()
+ ),
+ httpStatus);
+ }
+
+ public static ResponseEntity responseWithError (ResponseMessageEnum responseMessageEnum,
+ HttpStatus httpStatus) {
+ return new ResponseEntity<>(
+ new ResponseError(
+ httpStatus.value(),
+ responseMessageEnum.getUserMessage(),
+ responseMessageEnum.getInternalMessage(),
+ responseMessageEnum.getCode()
+ ),
+ httpStatus);
+ }
+
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 00000000..893fd39e
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+mongo.host=localhost
+mongo.port=27018
+mongo.database=app1
+thread.async_core_pool_size=5
+thread.async_max_pool_size=50
+info.app.version=@project.version@
\ No newline at end of file
diff --git a/src/test/java/com/santander/interview/config/MongoConfigTest.java b/src/test/java/com/santander/interview/config/MongoConfigTest.java
new file mode 100644
index 00000000..fe3dff85
--- /dev/null
+++ b/src/test/java/com/santander/interview/config/MongoConfigTest.java
@@ -0,0 +1,21 @@
+package com.santander.interview.config;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+
+public class MongoConfigTest {
+ @InjectMocks
+ MongoConfig mongoConfig = new MongoConfig();
+
+ @Test
+ public void mongoClientTest() {
+ Assert.assertNotNull(mongoConfig.mongoClient());
+ }
+
+ @Test
+ public void mongoDatabaseTest() {
+ Assert.assertNull(mongoConfig.getDatabaseName());
+ }
+
+}
diff --git a/src/test/java/com/santander/interview/controller/CategoryControllerTest.java b/src/test/java/com/santander/interview/controller/CategoryControllerTest.java
new file mode 100644
index 00000000..a5283bed
--- /dev/null
+++ b/src/test/java/com/santander/interview/controller/CategoryControllerTest.java
@@ -0,0 +1,58 @@
+package com.santander.interview.controller;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+import com.santander.interview.domain.Category;
+import com.santander.interview.domain.Response;
+import com.santander.interview.domain.ResponseObject;
+import com.santander.interview.service.CategoryService;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class CategoryControllerTest {
+ private static final String CATEGORY_DETAIL = "Detail";
+ private Category category;
+
+ @InjectMocks
+ CategoryController categoryController = new CategoryController();
+
+ @Mock
+ CategoryService categoryService;
+
+ @Before
+ public void init() {
+ category = new Category(CATEGORY_DETAIL);
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void suggestionCategoryTest() {
+ String detailSubstring = "teste";
+ List categories = new ArrayList<>();
+ Mockito.when(categoryService.searchCategoryByDetailSubstring(detailSubstring)).thenReturn(categories);
+ ResponseEntity response = categoryController.suggestionCategory(detailSubstring);
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
+ Assert.assertNotNull(response.getBody().getData());
+ }
+
+ @Test
+ public void addCategoryTest() {
+ ResponseEntity response = categoryController.addCategory(category);
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
+ Assert.assertEquals(response.getBody().getStatusCode(), HttpStatus.OK.value());
+ Assert.assertEquals(response.getBody().getUserMessage(), ADD_CATEGORY_SUCCESS.getUserMessage());
+ Assert.assertEquals(response.getBody().getInternalMessage(), ADD_CATEGORY_SUCCESS.getInternalMessage());
+ }
+
+}
diff --git a/src/test/java/com/santander/interview/controller/ExpenseControllerTest.java b/src/test/java/com/santander/interview/controller/ExpenseControllerTest.java
new file mode 100644
index 00000000..f5d6b6c1
--- /dev/null
+++ b/src/test/java/com/santander/interview/controller/ExpenseControllerTest.java
@@ -0,0 +1,112 @@
+package com.santander.interview.controller;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+import com.santander.interview.domain.Expense;
+import com.santander.interview.domain.Response;
+import com.santander.interview.domain.ResponseObject;
+import com.santander.interview.exception.ExpenseException;
+import com.santander.interview.service.ExpenseService;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class ExpenseControllerTest {
+ private static final String DESCRIPTION = "descricao";
+ private static final Double VALUE = 198.23;
+ private static final long USER_CODE = 129;
+ private static final Date DATE = new Date();
+ private static final String INCORRECT_DATE = "121251";
+
+ @InjectMocks
+ ExpenseController expenseController = new ExpenseController();
+
+ @Mock
+ ExpenseService expenseService;
+
+ Expense expense;
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+ this.expense = new Expense(DESCRIPTION, VALUE, USER_CODE, DATE);
+ }
+
+ @Test
+ public void addExpenseTest() {
+ ResponseEntity response = this.expenseController.addExpense(this.expense);
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
+ Assert.assertEquals(response.getBody().getStatusCode(), HttpStatus.OK.value());
+ Assert.assertEquals(response.getBody().getInternalMessage(), ADD_EXPENSE_SUCCESS.getInternalMessage());
+ Assert.assertEquals(response.getBody().getUserMessage(), ADD_EXPENSE_SUCCESS.getUserMessage());
+ }
+
+ @Test
+ public void getExpenseByUserCodeTest() {
+ long userCode = USER_CODE;
+ List list = new ArrayList<>();
+ list.add(this.expense);
+
+ Mockito.when(expenseService.searchExpensesByUserCode(userCode)).thenReturn(list);
+
+ ResponseEntity response = this.expenseController.getExpenseByUserCode(userCode);
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
+ Assert.assertEquals(response.getBody().getStatusCode(), HttpStatus.OK.value());
+ Assert.assertEquals(response.getBody().getData(), list);
+ Assert.assertEquals(response.getBody().getInternalMessage(), SEARCH_EXPENSE_BY_USER_CODE_SUCCESS.getInternalMessage());
+ Assert.assertEquals(response.getBody().getUserMessage(), SEARCH_EXPENSE_BY_USER_CODE_SUCCESS.getUserMessage());
+ }
+
+ @Test
+ public void getExpenseByUserCodeAndDateTest() throws ExpenseException {
+ long userCode = USER_CODE;
+ String date = DATE.toString();
+ List list = new ArrayList<>();
+ list.add(this.expense);
+
+ Mockito.when(this.expenseService.searchExpensesByUserCodeAndDate(userCode, date)).thenReturn(list);
+
+ ResponseEntity> response = this.expenseController.getExpenseByUserCodeAndDate(userCode, date);
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
+ Assert.assertNotNull(response.getBody());
+ }
+
+ @Test
+ public void getExpenseByUserCodeAndDate_WithDataIncorrectTest() throws ExpenseException {
+ long userCode = USER_CODE;
+ String date = INCORRECT_DATE;
+ Mockito.when(this.expenseService.searchExpensesByUserCodeAndDate(userCode, date))
+ .thenThrow(new ExpenseException(HttpStatus.BAD_REQUEST, EXPENSE_BADLY_FORMATTED_DATE));
+ ResponseEntity> response = this.expenseController.getExpenseByUserCodeAndDate(userCode, date);
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.BAD_REQUEST);
+ Assert.assertNotNull(response.getBody());
+ }
+
+ @Test
+ public void updateExpenseTest() {
+ String id = expense.getId();
+ ResponseEntity> response = this.expenseController.updateExpense(id, expense);
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.OK);
+ Assert.assertNotNull(response.getBody());
+ }
+
+ @Test
+ public void updateExpense_ExpenseWithoutIdTest() throws ExpenseException {
+ String id = expense.getId();
+ Mockito.doThrow(new ExpenseException(HttpStatus.NOT_FOUND, EXPENSE_NOT_FOUND))
+ .when(this.expenseService).updateExpense(id, expense);
+ ResponseEntity> response = this.expenseController.updateExpense(id, expense);
+ Assert.assertEquals(response.getStatusCode(), HttpStatus.NOT_FOUND);
+ Assert.assertNotNull(response.getBody());
+ }
+}
diff --git a/src/test/java/com/santander/interview/domain/CategoryTest.java b/src/test/java/com/santander/interview/domain/CategoryTest.java
new file mode 100644
index 00000000..9f444a5c
--- /dev/null
+++ b/src/test/java/com/santander/interview/domain/CategoryTest.java
@@ -0,0 +1,74 @@
+package com.santander.interview.domain;
+
+import com.santander.interview.repository.CategoryRepository;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CategoryTest {
+ private static final String ID = "123";
+ private static final String DETAIL = "teste";
+ List categoriesResult;
+ Category categoryObject;
+
+ @InjectMocks
+ Category categoryDomain = new Category();
+
+ @Mock
+ CategoryRepository categoryRepository;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ this.categoryObject = new Category(DETAIL);
+ this.categoriesResult = new ArrayList<>();
+ this.categoriesResult.add(this.categoryObject);
+ }
+
+ @Test
+ public void category_EmptyConstructorTest() {
+ Category category = new Category();
+ category.setId(ID);
+ category.setDetail(DETAIL);
+
+ Assert.assertEquals(category.getId(), ID);
+ Assert.assertEquals(category.getDetail(), DETAIL);
+ }
+
+ @Test
+ public void category_ConstructorWithParamsTest() {
+ Category category = new Category(DETAIL);
+ Assert.assertEquals(category.getDetail(), DETAIL);
+ }
+
+ @Test
+ public void saveTest() {
+ boolean isOk = true;
+ try {
+ this.categoryDomain.save(this.categoryObject);
+ } catch (Exception e) {
+ isOk = false;
+ }
+ Assert.assertTrue(isOk);
+ }
+
+ @Test
+ public void searchByDetailSubstringTest() {
+ Mockito.when(categoryRepository.findByDetailLike(this.categoryObject.getDetail()))
+ .thenReturn(this.categoriesResult);
+
+ String substring = this.categoryObject.getDetail();
+ List result = this.categoryDomain.searchByDetailSubstring(substring);
+ Assert.assertEquals(result, categoriesResult);
+ }
+}
diff --git a/src/test/java/com/santander/interview/domain/ExpenseTest.java b/src/test/java/com/santander/interview/domain/ExpenseTest.java
new file mode 100644
index 00000000..0df77d2f
--- /dev/null
+++ b/src/test/java/com/santander/interview/domain/ExpenseTest.java
@@ -0,0 +1,174 @@
+package com.santander.interview.domain;
+
+import com.santander.interview.enums.ResponseMessageEnum;
+import com.santander.interview.exception.ExpenseException;
+import com.santander.interview.repository.CategoryRepository;
+import com.santander.interview.repository.ExpenseRepository;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.text.ParseException;
+import java.util.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ExpenseTest {
+ private static final String ID = "123";
+ private static final String DESCRIPTION = "descricao";
+ private static final double VALUE = 124.2;
+ private static final long USER_CODE = 142;
+ private static final Date DATE = new Date();
+ private static final String CATEGORY_DETAIL = "teste";
+ private static final Category CATEGORY = new Category(CATEGORY_DETAIL);
+ private static final String DATE_STRING = "01102019";
+ private static final String DATE_STRING_INVALID = "011";
+ private static final String DETAIL = "Detalhes";
+
+ @InjectMocks
+ Expense expenseDomain = new Expense();
+
+ @Mock
+ ExpenseRepository expenseRepository;
+
+ @Mock
+ CategoryRepository categoryRepository;
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void expenseEmptyConstructorTest() {
+ String expectedToString = String.format(
+ "Expense[id=%s, descricao=%s, valor=%f, codigoUsuario=%d, data=%s]",
+ ID, DESCRIPTION, VALUE, USER_CODE, DATE
+ );
+ Expense expense = new Expense();
+ expense.setId(ID);
+ expense.setCategory(CATEGORY);
+ expense.setDescription(DESCRIPTION);
+ expense.setValue(VALUE);
+ expense.setUserCode(USER_CODE);
+ expense.setDate(DATE);
+
+ Assert.assertEquals(expense.getId(), ID);
+ Assert.assertEquals(expense.getCategory(), CATEGORY);
+ Assert.assertEquals(expense.getUserCode(), USER_CODE);
+ Assert.assertEquals(expense.getDate(), DATE);
+ Assert.assertEquals(expense.getDescription(), DESCRIPTION);
+ Assert.assertTrue(expense.getValue() == VALUE);
+ Assert.assertEquals(expense.toString(), expectedToString);
+ }
+
+ @Test
+ public void expenseConstrutorWithAllAttrTest() {
+ Expense expense = new Expense(DESCRIPTION, VALUE, USER_CODE, DATE, CATEGORY);
+
+ Assert.assertEquals(expense.getCategory(), CATEGORY);
+ Assert.assertEquals(expense.getUserCode(), USER_CODE);
+ Assert.assertEquals(expense.getDate(), DATE);
+ Assert.assertEquals(expense.getDescription(), DESCRIPTION);
+ Assert.assertTrue(expense.getValue() == VALUE);
+ }
+
+ @Test
+ public void expenseConstrutorWithoutCategoryAttrTest() {
+ Expense expense = new Expense(DESCRIPTION, VALUE, USER_CODE, DATE);
+
+ Assert.assertEquals(expense.getUserCode(), USER_CODE);
+ Assert.assertEquals(expense.getDate(), DATE);
+ Assert.assertEquals(expense.getDescription(), DESCRIPTION);
+ Assert.assertTrue(expense.getValue() == VALUE);
+ }
+
+
+ @Test
+ public void add_WithCategoryNullTest() {
+ List expenses = new ArrayList<>();
+ Expense expense = new Expense(DESCRIPTION, VALUE, USER_CODE, DATE);
+ expenses.add(expense);
+
+ Mockito.when(this.expenseRepository.findByUserCode(USER_CODE)).thenReturn(expenses);
+
+ this.expenseDomain.add(expense);
+ List result = this.expenseDomain.searchByUserCode(USER_CODE);
+ Assert.assertEquals(result, expenses);
+ }
+
+ @Test
+ public void add_WithExpenseWithCategoryTest() {
+ boolean isOk = true;
+ Expense expense = new Expense(DESCRIPTION, VALUE, USER_CODE, DATE);
+ Category category = new Category();
+ List categories = new ArrayList<>();
+ category.setDetail(DETAIL);
+ expense.setCategory(category);
+ categories.add(new Category(DETAIL));
+
+ Mockito.when(categoryRepository.findByDetail(category.getDetail())).thenReturn(categories);
+ try {
+ this.expenseDomain.add(expense);
+ } catch (Exception e) {
+ isOk = false;
+ }
+
+ Assert.assertTrue(isOk);
+ }
+
+ @Test
+ public void searchByUserCodeAndDateTest() throws ParseException {
+ List expenses = new ArrayList<>();
+ Expense expense = new Expense(DESCRIPTION, VALUE, USER_CODE, DATE);
+ expenses.add(expense);
+
+ Assert.assertNotNull(this.expenseDomain.searchByUserCodeAndDate(USER_CODE, DATE_STRING));
+ }
+
+ @Test
+ public void searchByUserCodeAndDate_InvalidDataTest() {
+ List expenses;
+ boolean parseException = false;
+
+ try {
+ expenses = this.expenseDomain.searchByUserCodeAndDate(USER_CODE, DATE_STRING_INVALID);
+ } catch (ParseException pe) {
+ parseException = true;
+ }
+
+ Assert.assertTrue(parseException);
+ }
+
+ @Test
+ public void update_FoundExpenseTest() throws ExpenseException {
+ boolean isOk = true;
+ String uuid = UUID.randomUUID().toString();
+ Expense expense = new Expense();
+ Optional optionalExpense = Optional.of(expense);
+ Mockito.when(this.expenseRepository.findById(uuid)).thenReturn(optionalExpense);
+
+ try {
+ this.expenseDomain.update(uuid, expense);
+ } catch (Exception e) {
+ isOk = false;
+ }
+
+ Assert.assertTrue(isOk);
+ }
+
+ @Test
+ public void update_NotFoundExpenseTest() {
+ String uuid = UUID.randomUUID().toString();
+ Expense expense = new Expense();
+ Optional optionalExpense = Optional.empty();
+ Mockito.when(this.expenseRepository.findById(uuid)).thenReturn(optionalExpense);
+
+ Assert.assertFalse(this.expenseDomain.update(uuid, expense));
+ }
+}
diff --git a/src/test/java/com/santander/interview/domain/ResponseErrorTest.java b/src/test/java/com/santander/interview/domain/ResponseErrorTest.java
new file mode 100644
index 00000000..61113753
--- /dev/null
+++ b/src/test/java/com/santander/interview/domain/ResponseErrorTest.java
@@ -0,0 +1,24 @@
+package com.santander.interview.domain;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ResponseErrorTest {
+ private static final long STATUS_CODE = 12;
+ private static final String USER_MESSAGE = "userMessage";
+ private static final String INTERNAL_MESSAGE = "internalMessage";
+ private static final int INTERNAL_CODE = 1;
+ private static final int INTERNAL_CODE_2 = 3;
+
+ @Test
+ public void responseErrorTest(){
+ ResponseError responseError = new ResponseError(STATUS_CODE, USER_MESSAGE, INTERNAL_MESSAGE, INTERNAL_CODE);
+ Assert.assertEquals(responseError.getStatusCode(), STATUS_CODE);
+ Assert.assertEquals(responseError.getUserMessage(), USER_MESSAGE);
+ Assert.assertEquals(responseError.getInternalMessage(), INTERNAL_MESSAGE);
+ Assert.assertEquals(responseError.getInternalCode(), INTERNAL_CODE);
+
+ responseError.setInternalCode(INTERNAL_CODE_2);
+ Assert.assertEquals(responseError.getInternalCode(), INTERNAL_CODE_2);
+ }
+}
diff --git a/src/test/java/com/santander/interview/domain/ResponseObjectTest.java b/src/test/java/com/santander/interview/domain/ResponseObjectTest.java
new file mode 100644
index 00000000..91a86897
--- /dev/null
+++ b/src/test/java/com/santander/interview/domain/ResponseObjectTest.java
@@ -0,0 +1,37 @@
+package com.santander.interview.domain;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ResponseObjectTest {
+ private static final long STATUS_CODE = 123;
+ private static final String USER_MESSAGE = "userMessage";
+ private static final String INTERNAL_MESSAGE = "internalMessage";
+ private static final Object DATA = new Object();
+
+ @Test
+ public void responseEmptyConstTest() {
+ ResponseObject responseObject = new ResponseObject();
+ responseObject.setData(DATA);
+ responseObject.setUserMessage(USER_MESSAGE);
+ responseObject.setInternalMessage(INTERNAL_MESSAGE);
+ responseObject.setStatusCode(STATUS_CODE);
+
+ Assert.assertEquals(responseObject.getData(), DATA);
+ Assert.assertEquals(responseObject.getUserMessage(), USER_MESSAGE);
+ Assert.assertEquals(responseObject.getInternalMessage(), INTERNAL_MESSAGE);
+ Assert.assertEquals(responseObject.getStatusCode(), STATUS_CODE);
+ }
+
+ @Test
+ public void responseConstructorWithParamsTest() {
+ ResponseObject responseObject = new ResponseObject(STATUS_CODE, USER_MESSAGE, INTERNAL_MESSAGE, DATA);
+ Assert.assertEquals(responseObject.getStatusCode(), STATUS_CODE);
+ Assert.assertEquals(responseObject.getUserMessage(), USER_MESSAGE);
+ Assert.assertEquals(responseObject.getInternalMessage(), INTERNAL_MESSAGE);
+ Assert.assertEquals(responseObject.getData(), DATA);
+ }
+}
diff --git a/src/test/java/com/santander/interview/domain/ResponseTest.java b/src/test/java/com/santander/interview/domain/ResponseTest.java
new file mode 100644
index 00000000..79e8945a
--- /dev/null
+++ b/src/test/java/com/santander/interview/domain/ResponseTest.java
@@ -0,0 +1,31 @@
+package com.santander.interview.domain;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ResponseTest {
+ private static final long STATUS_CODE = 12;
+ private static final String USER_MESSAGE = "userMessage";
+ private static final String INTERNAL_MESSAGE = "internalMessage";
+
+ @Test
+ public void responseEmptyConstructorTest() {
+ Response response = new Response();
+ response.setStatusCode(STATUS_CODE);
+ response.setInternalMessage(INTERNAL_MESSAGE);
+ response.setUserMessage(USER_MESSAGE);
+
+ Assert.assertEquals(response.getStatusCode(), STATUS_CODE);
+ Assert.assertEquals(response.getUserMessage(), USER_MESSAGE);
+ Assert.assertEquals(response.getInternalMessage(), INTERNAL_MESSAGE);
+ }
+
+ @Test
+ public void responseWithAllConstructorAttrTest() {
+ Response response = new Response(STATUS_CODE, USER_MESSAGE, INTERNAL_MESSAGE);
+
+ Assert.assertEquals(response.getStatusCode(), STATUS_CODE);
+ Assert.assertEquals(response.getUserMessage(), USER_MESSAGE);
+ Assert.assertEquals(response.getInternalMessage(), INTERNAL_MESSAGE);
+ }
+}
diff --git a/src/test/java/com/santander/interview/enums/ResponseMessageEnumTest.java b/src/test/java/com/santander/interview/enums/ResponseMessageEnumTest.java
new file mode 100644
index 00000000..1f01e500
--- /dev/null
+++ b/src/test/java/com/santander/interview/enums/ResponseMessageEnumTest.java
@@ -0,0 +1,15 @@
+package com.santander.interview.enums;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+public class ResponseMessageEnumTest {
+ @Test
+ public void responseMessageEnumTest() {
+ Assert.assertNotNull(UNKNOWN_ERROR.getUserMessage());
+ Assert.assertNotNull(UNKNOWN_ERROR.getInternalMessage());
+ Assert.assertNotNull(UNKNOWN_ERROR.getCode());
+ }
+}
diff --git a/src/test/java/com/santander/interview/exception/ExpenseExceptionTest.java b/src/test/java/com/santander/interview/exception/ExpenseExceptionTest.java
new file mode 100644
index 00000000..a68e496d
--- /dev/null
+++ b/src/test/java/com/santander/interview/exception/ExpenseExceptionTest.java
@@ -0,0 +1,41 @@
+package com.santander.interview.exception;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.http.HttpStatus;
+
+public class ExpenseExceptionTest {
+ private static final String MESSAGE = "teste";
+
+ @Test
+ public void expenseExceptionConstructor1Test() {
+ ExpenseException ee = new ExpenseException();
+ Assert.assertEquals(ee.getStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
+ Assert.assertEquals(ee.getResponseMessageEnum(), UNKNOWN_ERROR);
+ }
+
+ @Test
+ public void expenseExceptionConstructor2Test() {
+ ExpenseException ee = new ExpenseException(HttpStatus.OK, MESSAGE, EXPENSE_BADLY_FORMATTED_DATE);
+ Assert.assertEquals(ee.getStatusCode(), HttpStatus.OK);
+ Assert.assertEquals(ee.getResponseMessageEnum(), EXPENSE_BADLY_FORMATTED_DATE);
+ Assert.assertEquals(ee.getMessage(), MESSAGE);
+ }
+
+ @Test
+ public void expenseExceptionConstructor3Test() {
+ ExpenseException ee = new ExpenseException(HttpStatus.OK, EXPENSE_BADLY_FORMATTED_DATE);
+ Assert.assertEquals(ee.getStatusCode(), HttpStatus.OK);
+ Assert.assertEquals(ee.getResponseMessageEnum(), EXPENSE_BADLY_FORMATTED_DATE);
+
+ ee.setMessage(MESSAGE);
+ ee.setResponseMessageEnum(UNKNOWN_ERROR);
+ ee.setStatusCode(HttpStatus.BAD_GATEWAY);
+
+ Assert.assertEquals(ee.getMessage(), MESSAGE);
+ Assert.assertEquals(ee.getResponseMessageEnum(), UNKNOWN_ERROR);
+ Assert.assertEquals(ee.getStatusCode(), HttpStatus.BAD_GATEWAY);
+ }
+}
diff --git a/src/test/java/com/santander/interview/service/CategoryServiceTest.java b/src/test/java/com/santander/interview/service/CategoryServiceTest.java
new file mode 100644
index 00000000..e34e0d9f
--- /dev/null
+++ b/src/test/java/com/santander/interview/service/CategoryServiceTest.java
@@ -0,0 +1,59 @@
+package com.santander.interview.service;
+
+import com.santander.interview.domain.Category;
+import com.santander.interview.service.impl.CategoryServiceImpl;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CategoryServiceTest {
+ private static final String DETAIL = "teste";
+
+ @InjectMocks
+ CategoryServiceImpl categoryService = new CategoryServiceImpl();
+
+ @Mock
+ Category categoryDomain;
+
+ Category categoryObject;
+ List categoriesResult;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ this.categoryObject = new Category(DETAIL);
+ this.categoriesResult = new ArrayList<>();
+ this.categoriesResult.add(this.categoryObject);
+ }
+
+ @Test
+ public void saveCategoryTest() {
+ boolean isOk = true;
+ try {
+ this.categoryService.saveCategory(categoryObject);
+ } catch (Exception e) {
+ isOk = false;
+ }
+ Assert.assertTrue(isOk);
+ }
+
+ @Test
+ public void searchCategoryByDetailSubstringTest() {
+ Mockito.when(categoryDomain.searchByDetailSubstring(DETAIL))
+ .thenReturn(this.categoriesResult);
+
+ List categories = this.categoryService.searchCategoryByDetailSubstring(DETAIL);
+ Assert.assertEquals(categories, this.categoriesResult);
+ }
+
+}
diff --git a/src/test/java/com/santander/interview/service/ExpenseServiceTest.java b/src/test/java/com/santander/interview/service/ExpenseServiceTest.java
new file mode 100644
index 00000000..dafac6ac
--- /dev/null
+++ b/src/test/java/com/santander/interview/service/ExpenseServiceTest.java
@@ -0,0 +1,108 @@
+package com.santander.interview.service;
+
+import com.santander.interview.domain.Expense;
+import com.santander.interview.enums.ResponseMessageEnum;
+import com.santander.interview.exception.ExpenseException;
+import com.santander.interview.service.impl.ExpenseServiceImpl;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpStatus;
+
+import java.text.ParseException;
+import java.util.*;
+
+import static com.santander.interview.enums.ResponseMessageEnum.EXPENSE_BADLY_FORMATTED_DATE;
+import static com.santander.interview.enums.ResponseMessageEnum.EXPENSE_NOT_FOUND;
+
+public class ExpenseServiceTest {
+ private static final long USER_CODE = 1232;
+ private static final Date DATE = new Date();
+ private static final String DATE_STRING = "01102019";
+ private static final String DATE_STRING_INVALID = "011";
+ private static final double VALUE = 124.2;
+ private static final String DESCRIPTION = "Teste";
+ private static final String DETAIL = "Detalhes";
+ List expensesResult;
+
+ @InjectMocks
+ ExpenseServiceImpl expenseService = new ExpenseServiceImpl();
+
+ @Mock
+ Expense expenseDomain;
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+ this.expensesResult = new ArrayList<>();
+ this.expensesResult.add(new Expense());
+ }
+
+ @Test
+ public void searchExpensesByUserCodeAndDate_InvalidDataTest() throws ParseException {
+ List expenses = new ArrayList<>();
+ ResponseMessageEnum responseMessageEnumExpected = EXPENSE_BADLY_FORMATTED_DATE;
+ HttpStatus httpStatusExpected = HttpStatus.BAD_REQUEST;
+ Expense expense = new Expense(DESCRIPTION, VALUE, USER_CODE, DATE);
+ expenses.add(expense);
+ Mockito.when(this.expenseDomain.searchByUserCodeAndDate(USER_CODE, DATE_STRING_INVALID))
+ .thenThrow(ParseException.class);
+
+ try {
+ this.expenseService.searchExpensesByUserCodeAndDate(USER_CODE, DATE_STRING_INVALID);
+ } catch (ExpenseException ee) {
+ ResponseMessageEnum responseMessageEnum = ee.getResponseMessageEnum();
+ Assert.assertEquals(responseMessageEnum.getInternalMessage(), responseMessageEnumExpected.getInternalMessage());
+ Assert.assertEquals(responseMessageEnum.getUserMessage(), responseMessageEnumExpected.getUserMessage());
+ Assert.assertEquals(ee.getStatusCode(), httpStatusExpected);
+ }
+ }
+
+ @Test
+ public void searchExpensesByUserCodeTest() {
+ Mockito.when(this.expenseDomain.searchByUserCode(USER_CODE)).thenReturn(this.expensesResult);
+ Assert.assertEquals(
+ this.expenseService.searchExpensesByUserCode(USER_CODE),
+ this.expensesResult
+ );
+ }
+
+
+ @Test
+ public void updateExpenseTest() {
+ boolean isOk = true;
+ String uuid = UUID.randomUUID().toString();
+ Expense expense = new Expense();
+ Mockito.when(this.expenseDomain.update(uuid, expense)).thenReturn(true);
+
+ try {
+ this.expenseService.updateExpense(uuid, expense);
+ } catch (ExpenseException e) {
+ isOk = false;
+ }
+
+ Assert.assertTrue(isOk);
+ }
+
+ @Test
+ public void updateExpense_NotFoundExpenseTest() {
+ HttpStatus httpStatusExpected = HttpStatus.NOT_FOUND;
+ ResponseMessageEnum responseMessageEnum = EXPENSE_NOT_FOUND;
+ String uuid = UUID.randomUUID().toString();
+ Expense expense = new Expense();
+ Mockito.when(this.expenseDomain.update(uuid, expense)).thenReturn(false);
+
+ try {
+ this.expenseService.updateExpense(uuid, expense);
+ } catch (ExpenseException ee) {
+ Assert.assertEquals(ee.getStatusCode(), httpStatusExpected);
+ Assert.assertEquals(ee.getResponseMessageEnum().getUserMessage(), responseMessageEnum.getUserMessage());
+ }
+
+ }
+
+}
diff --git a/src/test/java/com/santander/interview/utils/ExpenseManagementUtilsTest.java b/src/test/java/com/santander/interview/utils/ExpenseManagementUtilsTest.java
new file mode 100644
index 00000000..7b6780e0
--- /dev/null
+++ b/src/test/java/com/santander/interview/utils/ExpenseManagementUtilsTest.java
@@ -0,0 +1,58 @@
+package com.santander.interview.utils;
+
+import static com.santander.interview.enums.ResponseMessageEnum.*;
+
+import com.santander.interview.domain.Response;
+import com.santander.interview.domain.ResponseError;
+import com.santander.interview.domain.ResponseObject;
+import com.santander.interview.enums.ResponseMessageEnum;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+public class ExpenseManagementUtilsTest {
+ private static final ResponseMessageEnum RESPONSE_MESSAGE_ENUM = ADD_EXPENSE_SUCCESS;
+ private static final HttpStatus HTTP_STATUS = HttpStatus.OK;
+ private static final Object DATA = new Object();
+
+ @Test
+ public void convertStringToInt_InvalidStringTest() {
+ int intValue = ExpenseManagementUtils.convertStringtoInt(10, "asd");
+ Assert.assertEquals(intValue, 10);
+ }
+
+ @Test
+ public void convertStringToInt_ValidStringTest() {
+ int intValue = ExpenseManagementUtils.convertStringtoInt(10, "123");
+ Assert.assertEquals(intValue, 123);
+ }
+
+ @Test
+ public void responseWithDataTest() {
+ ResponseEntity response = ExpenseManagementUtils
+ .responseWithData(RESPONSE_MESSAGE_ENUM, HTTP_STATUS, DATA);
+ Assert.assertEquals(response.getStatusCode(), HTTP_STATUS);
+ Assert.assertEquals(response.getBody().getInternalMessage(), RESPONSE_MESSAGE_ENUM.getInternalMessage());
+ Assert.assertEquals(response.getBody().getUserMessage(), RESPONSE_MESSAGE_ENUM.getUserMessage());
+ Assert.assertEquals(response.getBody().getData(), DATA);
+ }
+
+ @Test
+ public void responseWithoutData() {
+ ResponseEntity response = ExpenseManagementUtils
+ .responseWithoutData(RESPONSE_MESSAGE_ENUM, HTTP_STATUS);
+ Assert.assertEquals(response.getStatusCode(), HTTP_STATUS);
+ Assert.assertEquals(response.getBody().getInternalMessage(), RESPONSE_MESSAGE_ENUM.getInternalMessage());
+ Assert.assertEquals(response.getBody().getUserMessage(), RESPONSE_MESSAGE_ENUM.getUserMessage());
+ }
+
+ @Test
+ public void responseWithError() {
+ ResponseEntity response = ExpenseManagementUtils
+ .responseWithError(RESPONSE_MESSAGE_ENUM, HTTP_STATUS);
+ Assert.assertEquals(response.getBody().getInternalMessage(), RESPONSE_MESSAGE_ENUM.getInternalMessage());
+ Assert.assertEquals(response.getBody().getUserMessage(), RESPONSE_MESSAGE_ENUM.getUserMessage());
+ }
+
+}