diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..1ee92668
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+*.sw?
+.#*
+*#
+*~
+.classpath
+.project
+.settings
+bin
+build
+target
+dependency-reduced-pom.xml
+*.sublime-*
+/scratch
+.gradle
+README.html
+.idea
+*.iml
diff --git a/README.md b/README.md
index 15d8f685..0da12fa4 100644
--- a/README.md
+++ b/README.md
@@ -1,76 +1,19 @@
-# Show me the code
-
-### # DESAFIO:
-
API REST para Gestão de Gastos!
-```
-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
-```
-```
-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.
-```
-```
-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
-```
-### # 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.
+**Procedimentos de execução**
-* 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.
+Dentro do diretório gastos-api digitar
+- mvn install -Dmaven.test.skip=true
-### # Observações gerais
+Observação: é necessário o parâmetro -Dmaven.test.skip=true porque alguns testes exigem a conexão com o Solr (que só estará disponível após executar os procedimentos do Docker)
-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.
+No diretório raiz (TestBackJava) digitar os comandos
+- docker-compose build
+- docker-compose up -d
-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.
+Neste ponto um container com projeto e outro com o Solr estará disponível.
-### # Importante: não há prazo de entrega, faça com qualidade!
+Caso queira executar os testes (unitário e integração), basta editar o arquivo application.properties do projeto Spring e modificar o atributo _spring.data.solr.host=http://solrnode:8983/solr_ para _spring.data.solr.host=http://localhost:8983/solr_ e adicionando um novo atributo para porta _port:8081_.
-# BOA SORTE!
+Assim, dentro do diretório gastos-api (fora do container) basta digitar o seguinte comando para executar os testes:
+- mvn test
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..204e79a0
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,22 @@
+version: '3'
+services:
+ solrnode:
+ image: solr
+ ports:
+ - "8983:8983"
+ volumes:
+ - data:/opt/solr/server/solr/gasto
+ entrypoint:
+ - docker-entrypoint.sh
+ - solr-precreate
+ - gasto
+ gastos-api:
+ build: gastos-api
+ ports:
+ - "8080:8080"
+ volumes:
+ - ~/.m2:/root/.m2
+ links:
+ - solrnode
+volumes:
+ data:
diff --git a/gastos-api/.gitignore b/gastos-api/.gitignore
new file mode 100644
index 00000000..1ee92668
--- /dev/null
+++ b/gastos-api/.gitignore
@@ -0,0 +1,17 @@
+*.sw?
+.#*
+*#
+*~
+.classpath
+.project
+.settings
+bin
+build
+target
+dependency-reduced-pom.xml
+*.sublime-*
+/scratch
+.gradle
+README.html
+.idea
+*.iml
diff --git a/gastos-api/.mvn/wrapper/MavenWrapperDownloader.java b/gastos-api/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 00000000..72308aa4
--- /dev/null
+++ b/gastos-api/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,114 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL =
+ "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: : " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/gastos-api/.mvn/wrapper/maven-wrapper.jar b/gastos-api/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 00000000..01e67997
Binary files /dev/null and b/gastos-api/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/gastos-api/.mvn/wrapper/maven-wrapper.properties b/gastos-api/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 00000000..cd0d451c
--- /dev/null
+++ b/gastos-api/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip
diff --git a/gastos-api/Dockerfile b/gastos-api/Dockerfile
new file mode 100644
index 00000000..49495122
--- /dev/null
+++ b/gastos-api/Dockerfile
@@ -0,0 +1,9 @@
+FROM maven:3-jdk-8
+
+RUN mkdir data && cd /data && mkdir gastos-api && cd gastos-api
+
+WORKDIR /data/gastos-api
+
+ADD . /data/gastos-api
+
+CMD ["mvn","spring-boot:run"]
diff --git a/gastos-api/pom.xml b/gastos-api/pom.xml
new file mode 100644
index 00000000..0d339195
--- /dev/null
+++ b/gastos-api/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.21.RELEASE
+
+
+ com.santander
+ gastos-api
+ 0.0.1-SNAPSHOT
+ Gastos-API
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-data-solr
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/gastos-api/src/main/java/com/santander/gastosapi/GastosApiApplication.java b/gastos-api/src/main/java/com/santander/gastosapi/GastosApiApplication.java
new file mode 100644
index 00000000..f90eef35
--- /dev/null
+++ b/gastos-api/src/main/java/com/santander/gastosapi/GastosApiApplication.java
@@ -0,0 +1,22 @@
+package com.santander.gastosapi;
+
+import java.util.TimeZone;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class GastosApiApplication {
+
+ @PostConstruct
+ void started() {
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(GastosApiApplication.class, args);
+ }
+
+}
diff --git a/gastos-api/src/main/java/com/santander/gastosapi/model/Gasto.java b/gastos-api/src/main/java/com/santander/gastosapi/model/Gasto.java
new file mode 100644
index 00000000..60187369
--- /dev/null
+++ b/gastos-api/src/main/java/com/santander/gastosapi/model/Gasto.java
@@ -0,0 +1,109 @@
+package com.santander.gastosapi.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import org.apache.solr.client.solrj.beans.Field;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.solr.core.mapping.SolrDocument;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+@SolrDocument(solrCoreName = "gasto")
+public class Gasto implements Serializable {
+
+ @Id
+ @Field
+ private String id;
+
+ @Field
+ private String descricao;
+
+ @Field
+ private Double valor;
+
+ @Field
+ private int codigousuario;
+
+ @Field
+ @JsonFormat(pattern="dd/MM/yyyy HH:mm:ss")
+ private Date data;
+
+ @Field
+ private String categoria;
+
+ public Gasto() {}
+
+ public Gasto(String id, String descricao, Double valor, int codigousuario, Date data, String categoria) {
+ super();
+ this.id = id;
+ this.descricao = descricao;
+ this.valor = valor;
+ this.codigousuario = codigousuario;
+ this.data = data;
+ this.categoria = categoria;
+ }
+
+ public Gasto(String id, String descricao, Double valor, int codigousuario, Date data) {
+ this.id = id;
+ this.descricao = descricao;
+ this.valor = valor;
+ this.codigousuario = codigousuario;
+ this.data = data;
+ }
+
+ public Gasto(String descricao, Double valor, int codigousuario, Date data) {
+ this.descricao = descricao;
+ this.valor = valor;
+ this.codigousuario = codigousuario;
+ this.data = data;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getDescricao() {
+ return descricao;
+ }
+
+ public void setDescricao(String descricao) {
+ this.descricao = descricao;
+ }
+
+ public Double getValor() {
+ return valor;
+ }
+
+ public void setValor(Double valor) {
+ this.valor = valor;
+ }
+
+ public int getCodigousuario() {
+ return codigousuario;
+ }
+
+ public void setCodigousuario(int codigousuario) {
+ this.codigousuario = codigousuario;
+ }
+
+ public Date getData() {
+ return data;
+ }
+
+ public void setData(Date data) {
+ this.data = data;
+ }
+
+ public String getCategoria() {
+ return categoria;
+ }
+
+ public void setCategoria(String categoria) {
+ this.categoria = categoria;
+ }
+}
\ No newline at end of file
diff --git a/gastos-api/src/main/java/com/santander/gastosapi/repository/GastosRepository.java b/gastos-api/src/main/java/com/santander/gastosapi/repository/GastosRepository.java
new file mode 100644
index 00000000..4fcca610
--- /dev/null
+++ b/gastos-api/src/main/java/com/santander/gastosapi/repository/GastosRepository.java
@@ -0,0 +1,8 @@
+package com.santander.gastosapi.repository;
+
+import org.springframework.data.solr.repository.SolrCrudRepository;
+
+import com.santander.gastosapi.model.Gasto;
+
+public interface GastosRepository extends SolrCrudRepository{
+}
diff --git a/gastos-api/src/main/java/com/santander/gastosapi/resource/GastosResource.java b/gastos-api/src/main/java/com/santander/gastosapi/resource/GastosResource.java
new file mode 100644
index 00000000..edb9eae6
--- /dev/null
+++ b/gastos-api/src/main/java/com/santander/gastosapi/resource/GastosResource.java
@@ -0,0 +1,48 @@
+package com.santander.gastosapi.resource;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.santander.gastosapi.model.Gasto;
+import com.santander.gastosapi.service.GastosService;
+
+@RestController
+@RequestMapping("/v1")
+public class GastosResource {
+
+ @Autowired
+ private GastosService gastosService;
+
+ @RequestMapping(path="/accredited/gastos/cartao", method=RequestMethod.POST)
+ public void gastosPorCartao(@RequestBody Gasto gasto){
+ gastosService.gastosPorCartao(gasto);
+ }
+
+ @RequestMapping(path="/protected/gastos/listagem", method=RequestMethod.GET)
+ public @ResponseBody List listagemGastos(){
+ return gastosService.listagemGastos();
+ }
+
+ @RequestMapping(path="/protected/gastos/listagem/filtro", method=RequestMethod.GET)
+ public @ResponseBody List filtroGastos(@RequestHeader String filtro){
+ return gastosService.filtroGastos(filtro);
+ }
+
+ @RequestMapping(path="/protected/gastos/categoria", method=RequestMethod.PUT)
+ public void categorizacaoGastos(@RequestHeader String categoria, @RequestHeader String gastoID){
+ gastosService.categorizacaoGastos(categoria, gastoID);
+ }
+
+ @RequestMapping(path="/protected/gastos/categoria/sugestao", method=RequestMethod.GET)
+ public @ResponseBody LinkedHashSet sugestaoCategoria(){
+ return gastosService.sugestaoCategoria();
+ }
+}
\ No newline at end of file
diff --git a/gastos-api/src/main/java/com/santander/gastosapi/security/SpringSecurityConfig.java b/gastos-api/src/main/java/com/santander/gastosapi/security/SpringSecurityConfig.java
new file mode 100644
index 00000000..0687307a
--- /dev/null
+++ b/gastos-api/src/main/java/com/santander/gastosapi/security/SpringSecurityConfig.java
@@ -0,0 +1,31 @@
+package com.santander.gastosapi.security;
+
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+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.web.cors.CorsConfiguration;
+
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled=true)
+public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues())
+ .and().csrf().disable()
+ .authorizeRequests()
+ .antMatchers("/*/accredited/**").hasRole("SYSTEM")
+ .antMatchers("/*/protected/**").hasRole("USER")
+ .and().httpBasic();
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.inMemoryAuthentication().
+ withUser("cliente01").password("asd123").roles("USER")
+ .and()
+ .withUser("sistema01").password("asd321").roles("SYSTEM");
+ }
+}
\ No newline at end of file
diff --git a/gastos-api/src/main/java/com/santander/gastosapi/service/GastosService.java b/gastos-api/src/main/java/com/santander/gastosapi/service/GastosService.java
new file mode 100644
index 00000000..b45977e9
--- /dev/null
+++ b/gastos-api/src/main/java/com/santander/gastosapi/service/GastosService.java
@@ -0,0 +1,82 @@
+package com.santander.gastosapi.service;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.santander.gastosapi.model.Gasto;
+import com.santander.gastosapi.repository.GastosRepository;
+
+@Service
+public class GastosService {
+
+ @Autowired
+ private GastosRepository gastosRepository;
+
+ public void gastosPorCartao(Gasto gasto) {
+ if(gasto.getCategoria()==null)
+ categorizacaoAutomaticaGastos(gasto);
+ gastosRepository.save(gasto);
+ }
+
+ public List listagemGastos(){
+ List gastos = new ArrayList<>();
+ gastosRepository.findAll().forEach(gastos::add);
+ return gastos;
+ }
+
+ public Gasto getGasto(String gastoID){
+ return gastosRepository.findOne(gastoID);
+ }
+
+ public List filtroGastos(String filtro){
+ List gastos = listagemGastos();
+ List gastosDiaEspecifico = new ArrayList<>();
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
+
+ gastos.forEach(g->{
+ if(g.getData()!=null){
+ String dataValue = simpleDateFormat.format(g.getData());
+
+ if(dataValue.equals(filtro))
+ gastosDiaEspecifico.add(g);
+ }
+ });
+ return gastosDiaEspecifico;
+ }
+
+ public void categorizacaoGastos(String categoria, String gastoID) {
+ Gasto gasto = gastosRepository.findOne(gastoID);
+ gasto.setCategoria(categoria);
+ gastosRepository.save(gasto);
+ }
+
+ public LinkedHashSet sugestaoCategoria(){
+ LinkedHashSet categoria = new LinkedHashSet<>();
+
+ gastosRepository.findAll().forEach(g->{
+ if(g.getCategoria()!=null)
+ categoria.add(g.getCategoria());
+ });
+
+ return categoria;
+ }
+
+ private void categorizacaoAutomaticaGastos(Gasto gasto){
+ List gastos = listagemGastos();
+
+ // busca todos os gastos do cliente
+ List gastosUserFilter1 = gastos.stream().filter(g -> g.getCodigousuario()==g.getCodigousuario()).collect(Collectors.toList());
+
+ // verifica se a descrição do gasto atual é igual a descrição de algum outro gasto
+ List gastosUserFilter2 = gastosUserFilter1.stream().filter(g -> g.getDescricao().equals(gasto.getDescricao())).collect(Collectors.toList());
+
+ if(gastosUserFilter2.size()>0)
+ gasto.setCategoria(gastosUserFilter2.get(0).getCategoria());
+ }
+}
\ No newline at end of file
diff --git a/gastos-api/src/main/resources/application.properties b/gastos-api/src/main/resources/application.properties
new file mode 100644
index 00000000..a2841038
--- /dev/null
+++ b/gastos-api/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.data.solr.host=http://solrnode:8983/solr
\ No newline at end of file
diff --git a/gastos-api/src/test/java/com/santander/gastosapi/GastoEndpointTest.java b/gastos-api/src/test/java/com/santander/gastosapi/GastoEndpointTest.java
new file mode 100644
index 00000000..39623861
--- /dev/null
+++ b/gastos-api/src/test/java/com/santander/gastosapi/GastoEndpointTest.java
@@ -0,0 +1,181 @@
+package com.santander.gastosapi;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.List;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.BDDMockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.santander.gastosapi.model.Gasto;
+import com.santander.gastosapi.repository.GastosRepository;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+public class GastoEndpointTest {
+
+ @Autowired
+ private TestRestTemplate testRestTemplate;
+
+ @MockBean
+ private GastosRepository gastoRepository;
+
+ @TestConfiguration
+ static class Config {
+
+ @Bean
+ public RestTemplateBuilder restTemplateBuilder(){
+ return new RestTemplateBuilder().basicAuthorization("sistema01", "asd321");
+ }
+ }
+
+ @Test
+ public void listGastosIncorretUser(){
+ testRestTemplate = testRestTemplate.withBasicAuth("cliente02", "asd123");
+ ResponseEntity response = testRestTemplate.getForEntity("/v1/protected/gastos/listagem", String.class);
+ Assertions.assertThat(response.getStatusCodeValue()).isEqualTo(401);
+ }
+
+ @Test
+ public void listGastosCorretUser() throws ParseException{
+ List gastos = Arrays.asList(
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45")),
+ new Gasto("id02", "descricao gasto 2", 2.31, 2, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45"))
+ );
+ testRestTemplate = testRestTemplate.withBasicAuth("cliente01", "asd123");
+ BDDMockito.when(gastoRepository.findAll()).thenReturn(gastos);
+ ResponseEntity response = testRestTemplate.getForEntity("/v1/protected/gastos/listagem", Gasto[].class);
+ Assertions.assertThat(response.getStatusCodeValue()).isEqualTo(200);
+ Assertions.assertThat(response.getBody().length).isEqualTo(2);
+ }
+
+
+ @Test
+ public void filtroGastosEndpoint() throws ParseException{
+ List gastos = Arrays.asList(
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("25/12/2015 10:11:32")),
+ new Gasto("id02", "descricao gasto 2", 2.31, 2, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("01/01/2020 12:11:45")),
+ new Gasto("id03", "descricao gasto 2", 2.31, 2, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("01/01/2020 14:15:00")),
+ new Gasto("id04", "descricao gasto 3", 3.31, 3, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("11/07/2017 12:11:45"))
+ );
+
+ testRestTemplate = testRestTemplate.withBasicAuth("cliente01", "asd123");
+ BDDMockito.when(gastoRepository.findAll()).thenReturn(gastos);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.set("filtro", "01/01/2020");
+
+ ResponseEntity responseEntity = testRestTemplate.exchange("/v1/protected/gastos/listagem/filtro", HttpMethod.GET, new HttpEntity<>(headers), Gasto[].class);
+ Assertions.assertThat(responseEntity.getStatusCodeValue()).isEqualTo(200);
+ Assertions.assertThat(responseEntity.getBody().length).isEqualTo(2);
+ }
+
+ @Test
+ public void categorizacaoEndpoint() throws ParseException, JsonProcessingException {
+ List gastos = Arrays.asList(
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("25/12/2015 10:11:32")),
+ new Gasto("id02", "descricao gasto 2", 2.31, 2, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("01/01/2020 12:11:45")),
+ new Gasto("id03", "descricao gasto 2", 2.31, 2, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("01/01/2020 14:15:00")),
+ new Gasto("id04", "descricao gasto 3", 3.31, 3, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("11/07/2017 12:11:45"))
+ );
+
+ testRestTemplate = testRestTemplate.withBasicAuth("cliente01", "asd123");
+ BDDMockito.when(gastoRepository.findOne("id03")).thenReturn(gastos.get(2));
+ BDDMockito.when(gastoRepository.findAll()).thenReturn(gastos);
+
+ Assertions.assertThat(gastos.get(0).getCategoria()).isNull();
+
+ ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.set("categoria", "Categoria 025");
+ headers.set("gastoID", "id03");
+
+ // categoriza um gasto específico
+ String requestJson = ow.writeValueAsString("Categoria 025");
+ HttpEntity entity = new HttpEntity(requestJson, headers);
+
+ ResponseEntity responseEntity = testRestTemplate.exchange("/v1/protected/gastos/categoria", HttpMethod.PUT, entity, Void.class);
+ Assertions.assertThat(responseEntity.getStatusCodeValue()).isEqualTo(200);
+
+ // Verifica se o gasto foi categorizado
+ BDDMockito.when(gastoRepository.findAll()).thenReturn(gastos);
+ ResponseEntity response = testRestTemplate.getForEntity("/v1/protected/gastos/listagem", Gasto[].class);
+ Assertions.assertThat(response.getBody()[2].getCategoria()).isNotNull();
+ Assertions.assertThat(response.getBody()[2].getCategoria()).isEqualTo("Categoria 025");
+ }
+
+ @Test
+ public void gastosPorCartaoEndpoint() throws ParseException, JsonProcessingException {
+ List categorias = Arrays.asList(
+ "Categoria01",
+ "Categoria02",
+ "Categoria03",
+ "Categoria04"
+ );
+
+ List gastos = Arrays.asList(
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("25/12/2015 10:11:32"), categorias.get(0)),
+ new Gasto("id02", "descricao gasto 2", 2.31, 2, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("01/01/2020 12:11:45"), categorias.get(1)),
+ new Gasto("id03", "descricao gasto 3", 2.31, 2, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("01/01/2020 14:15:00"), categorias.get(2)),
+ new Gasto("id04", "descricao gasto 4", 3.31, 3, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("11/07/2017 12:11:45"), categorias.get(3))
+ );
+
+ Gasto novoGasto = new Gasto("descricao gasto 3", 4.2, 2, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("11/07/2017 12:11:45"));
+
+ BDDMockito.when(gastoRepository.findAll()).thenReturn(gastos);
+
+ ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ String requestJson = ow.writeValueAsString(novoGasto);
+ HttpEntity entity = new HttpEntity(requestJson, headers);
+
+ ResponseEntity responseEntity = testRestTemplate.exchange("/v1/accredited/gastos/cartao", HttpMethod.POST, entity, Void.class);
+ Assertions.assertThat(responseEntity.getStatusCodeValue()).isEqualTo(200);
+ }
+
+ @Test
+ public void listSugestaoCategoria() throws ParseException{
+ List gastos = Arrays.asList(
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45"), "Categoria X"),
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45"), "Categoria X"),
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45"), "Categoria X"),
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45"), "Categoria Y"),
+ new Gasto("id01", "descricao gasto 1", 1.31, 1, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45"), "Categoria Y")
+ );
+ testRestTemplate = testRestTemplate.withBasicAuth("cliente01", "asd123");
+ BDDMockito.when(gastoRepository.findAll()).thenReturn(gastos);
+ ResponseEntity response = testRestTemplate.getForEntity("/v1/protected/gastos/categoria/sugestao", String[].class);
+ Assertions.assertThat(response.getStatusCodeValue()).isEqualTo(200);
+ Assertions.assertThat(response.getBody().length).isEqualTo(2);
+ }
+
+
+}
diff --git a/gastos-api/src/test/java/com/santander/gastosapi/GastoRepositoryTest.java b/gastos-api/src/test/java/com/santander/gastosapi/GastoRepositoryTest.java
new file mode 100644
index 00000000..026c1517
--- /dev/null
+++ b/gastos-api/src/test/java/com/santander/gastosapi/GastoRepositoryTest.java
@@ -0,0 +1,54 @@
+package com.santander.gastosapi;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootContextLoader;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.santander.gastosapi.model.Gasto;
+import com.santander.gastosapi.repository.GastosRepository;
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = GastosApiApplication.class, loader=SpringBootContextLoader.class)
+public class GastoRepositoryTest {
+
+ @Autowired
+ private GastosRepository gastosRepository;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void createGasto() throws ParseException{
+ gastosRepository.save(new Gasto("id-teste-create-gasto-001", "Descrição do gasto 01", 2.64, 3, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45")));
+ Gasto novoGasto = gastosRepository.findOne("id-teste-create-gasto-001");
+ Assertions.assertThat(novoGasto).isNotNull();
+ Assertions.assertThat(novoGasto.getId()).isEqualTo("id-teste-create-gasto-001");
+ }
+
+ @Test
+ public void getGasto() throws ParseException{
+ gastosRepository.save(new Gasto("id-teste-get-gasto-001", "Descrição do gasto 99", 2.64, 3, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45")));
+ Gasto novoGasto = gastosRepository.findOne("id-teste-get-gasto-001");
+ Assertions.assertThat(novoGasto).isNotNull();
+ Assertions.assertThat(novoGasto.getId()).isEqualTo("id-teste-get-gasto-001");
+ }
+
+
+ @Test
+ public void updateGasto() throws ParseException{
+ gastosRepository.save(new Gasto("id-teste-update-gasto-001", "Descrição do gasto 99", 2.64, 3, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45")));
+ gastosRepository.save(new Gasto("id-teste-update-gasto-001", "Descrição do gasto 100", 2.64, 3, new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse("21/04/2019 12:11:45")));
+ Gasto novoGasto = gastosRepository.findOne("id-teste-update-gasto-001");
+ Assertions.assertThat(novoGasto.getId()).isEqualTo("id-teste-update-gasto-001");
+ Assertions.assertThat(novoGasto.getDescricao()).isEqualTo("Descrição do gasto 100");
+ }
+}
diff --git a/gastos-api/src/test/java/com/santander/gastosapi/GastosApiApplicationTests.java b/gastos-api/src/test/java/com/santander/gastosapi/GastosApiApplicationTests.java
new file mode 100644
index 00000000..63e1b94a
--- /dev/null
+++ b/gastos-api/src/test/java/com/santander/gastosapi/GastosApiApplicationTests.java
@@ -0,0 +1,16 @@
+package com.santander.gastosapi;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class GastosApiApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}