diff --git a/.gitignore b/.gitignore index c9f7010..4e3c6cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .gradle /build/ !gradle/wrapper/gradle-wrapper.jar +out ### STS ### .apt_generated diff --git a/src/main/java/com/scmspain/MsFcTechTestApplication.java b/src/main/java/com/scmspain/MsFcTechTestApplication.java old mode 100644 new mode 100755 index 28b3538..1c2a157 --- a/src/main/java/com/scmspain/MsFcTechTestApplication.java +++ b/src/main/java/com/scmspain/MsFcTechTestApplication.java @@ -2,17 +2,19 @@ import com.scmspain.configuration.InfrastructureConfiguration; import com.scmspain.configuration.TweetConfiguration; +import com.scmspain.configuration.ValidatorConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @EnableAutoConfiguration -@Import({TweetConfiguration.class, InfrastructureConfiguration.class}) +@Import({TweetConfiguration.class, InfrastructureConfiguration.class, ValidatorConfiguration.class}) public class MsFcTechTestApplication { + public static void main(String[] args) { SpringApplication.run(MsFcTechTestApplication.class, args); } + } diff --git a/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java b/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java old mode 100644 new mode 100755 index a0a2e48..032bafa --- a/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java +++ b/src/main/java/com/scmspain/configuration/InfrastructureConfiguration.java @@ -9,8 +9,11 @@ @Configuration public class InfrastructureConfiguration { - @Bean @ExportMetricWriter + + @Bean + @ExportMetricWriter public MetricWriter getMetricWriter(MBeanExporter exporter) { return new JmxMetricWriter(exporter); } + } diff --git a/src/main/java/com/scmspain/configuration/TweetConfiguration.java b/src/main/java/com/scmspain/configuration/TweetConfiguration.java old mode 100644 new mode 100755 index fbb0dbd..25a824f --- a/src/main/java/com/scmspain/configuration/TweetConfiguration.java +++ b/src/main/java/com/scmspain/configuration/TweetConfiguration.java @@ -1,7 +1,10 @@ package com.scmspain.configuration; import com.scmspain.controller.TweetController; +import com.scmspain.repository.ITweetRepository; +import com.scmspain.repository.TweetRepository; import com.scmspain.services.TweetService; +import com.scmspain.validators.TweetValidator; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,13 +13,19 @@ @Configuration public class TweetConfiguration { - @Bean - public TweetService getTweetService(EntityManager entityManager, MetricWriter metricWriter) { - return new TweetService(entityManager, metricWriter); - } @Bean public TweetController getTweetConfiguration(TweetService tweetService) { return new TweetController(tweetService); } + + @Bean + public TweetRepository getTweetRepository(EntityManager entityManager) { + return new TweetRepository(entityManager); + } + + @Bean + public TweetService getTweetService(ITweetRepository tweetRepository, MetricWriter metricWriter, TweetValidator tweetValidator) { + return new TweetService(tweetRepository, metricWriter, tweetValidator); + } } diff --git a/src/main/java/com/scmspain/configuration/ValidatorConfiguration.java b/src/main/java/com/scmspain/configuration/ValidatorConfiguration.java new file mode 100755 index 0000000..18ee38b --- /dev/null +++ b/src/main/java/com/scmspain/configuration/ValidatorConfiguration.java @@ -0,0 +1,14 @@ +package com.scmspain.configuration; + +import com.scmspain.validators.TweetValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ValidatorConfiguration { + + @Bean + public TweetValidator tweetValidator() { + return new TweetValidator(); + } +} \ No newline at end of file diff --git a/src/main/java/com/scmspain/controller/TweetController.java b/src/main/java/com/scmspain/controller/TweetController.java old mode 100644 new mode 100755 index 55ce7cd..79393c6 --- a/src/main/java/com/scmspain/controller/TweetController.java +++ b/src/main/java/com/scmspain/controller/TweetController.java @@ -1,17 +1,18 @@ package com.scmspain.controller; -import com.scmspain.controller.command.PublishTweetCommand; +import com.scmspain.controller.command.TweetCommand; import com.scmspain.entities.Tweet; +import com.scmspain.exception.NotFoundException; import com.scmspain.services.TweetService; import org.springframework.web.bind.annotation.*; import java.util.List; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.*; @RestController public class TweetController { + private TweetService tweetService; public TweetController(TweetService tweetService) { @@ -20,13 +21,24 @@ public TweetController(TweetService tweetService) { @GetMapping("/tweet") public List listAllTweets() { - return this.tweetService.listAllTweets(); + return this.tweetService.listAllPublishedTweets(); } - @PostMapping("/tweet") + @PostMapping(path = "/tweet", consumes = "application/json") @ResponseStatus(CREATED) - public void publishTweet(@RequestBody PublishTweetCommand publishTweetCommand) { - this.tweetService.publishTweet(publishTweetCommand.getPublisher(), publishTweetCommand.getTweet()); + public void publishTweet(@RequestBody TweetCommand tweetCommand) { + this.tweetService.publishTweet(tweetCommand.getPublisher(), tweetCommand.getTweet()); + } + + @PostMapping("/discarded") + @ResponseStatus(NO_CONTENT) + public void discardTweet(@RequestBody TweetCommand tweetCommand) throws NotFoundException { + this.tweetService.discardTweet(tweetCommand.getTweet()); + } + + @GetMapping("/discarded") + public List listDiscardedTweets() { + return this.tweetService.listAllDiscardedTweets(); } @ExceptionHandler(IllegalArgumentException.class) @@ -38,4 +50,14 @@ public Object invalidArgumentException(IllegalArgumentException ex) { public String exceptionClass = ex.getClass().getSimpleName(); }; } + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(NOT_FOUND) + @ResponseBody + public Object notFoundException(NotFoundException ex) { + return new Object() { + public String message = ex.getMessage(); + public String exceptionClass = ex.getClass().getSimpleName(); + }; + } } diff --git a/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java b/src/main/java/com/scmspain/controller/command/TweetCommand.java old mode 100644 new mode 100755 similarity index 91% rename from src/main/java/com/scmspain/controller/command/PublishTweetCommand.java rename to src/main/java/com/scmspain/controller/command/TweetCommand.java index 543897b..5b73cbc --- a/src/main/java/com/scmspain/controller/command/PublishTweetCommand.java +++ b/src/main/java/com/scmspain/controller/command/TweetCommand.java @@ -1,6 +1,7 @@ package com.scmspain.controller.command; -public class PublishTweetCommand { +public class TweetCommand { + private String publisher; private String tweet; diff --git a/src/main/java/com/scmspain/entities/Tweet.java b/src/main/java/com/scmspain/entities/Tweet.java old mode 100644 new mode 100755 index 3616a94..c9c8f81 --- a/src/main/java/com/scmspain/entities/Tweet.java +++ b/src/main/java/com/scmspain/entities/Tweet.java @@ -1,25 +1,51 @@ package com.scmspain.entities; +import com.fasterxml.jackson.annotation.JsonIgnore; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import java.util.Date; +import java.util.Objects; @Entity public class Tweet { + @Id @GeneratedValue private Long id; + @Column(nullable = false) private String publisher; - @Column(nullable = false, length = 140) + + @Column(nullable = false, length = 2048) private String tweet; - @Column (nullable=true) + + @Column private Long pre2015MigrationStatus = 0L; + @JsonIgnore + @Column(nullable = false) + private Date publicationDate; + + @JsonIgnore + @Column(nullable = false) + private Boolean discarded = Boolean.FALSE; + + @JsonIgnore + @Column + private Date discardedDate; + public Tweet() { } + public Tweet(String publisher, String text) { + this.publisher = publisher; + this.tweet = text; + this.publicationDate = new Date(); + } + public Long getId() { return id; } @@ -52,4 +78,59 @@ public void setPre2015MigrationStatus(Long pre2015MigrationStatus) { this.pre2015MigrationStatus = pre2015MigrationStatus; } + public Date getPublicationDate() { + return publicationDate; + } + + public void setPublicationDate(Date publicationDate) { + this.publicationDate = publicationDate; + } + + public Boolean isDiscarded() { + return discarded; + } + + public void setDiscarded(Boolean discarded) { + this.discarded = discarded; + } + + public Date getDiscardedDate() { + return discardedDate; + } + + public void setDiscardedDate(Date discardedDate) { + this.discardedDate = discardedDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Tweet tweet1 = (Tweet) o; + return Objects.equals(id, tweet1.id) && + Objects.equals(publisher, tweet1.publisher) && + Objects.equals(tweet, tweet1.tweet) && + Objects.equals(pre2015MigrationStatus, tweet1.pre2015MigrationStatus) && + Objects.equals(publicationDate, tweet1.publicationDate) && + Objects.equals(discarded, tweet1.discarded) && + Objects.equals(discardedDate, tweet1.discardedDate); + } + + @Override + public int hashCode() { + return Objects.hash(id, publisher, tweet, pre2015MigrationStatus, publicationDate, discarded, discardedDate); + } + + @Override + public String toString() { + return "Tweet{" + + "id=" + id + + ", publisher='" + publisher + '\'' + + ", tweet='" + tweet + '\'' + + ", pre2015MigrationStatus=" + pre2015MigrationStatus + + ", publicationDate=" + publicationDate + + ", discarded=" + discarded + + ", discardedDate=" + discardedDate + + '}'; + } } diff --git a/src/main/java/com/scmspain/exception/NotFoundException.java b/src/main/java/com/scmspain/exception/NotFoundException.java new file mode 100755 index 0000000..33fecc8 --- /dev/null +++ b/src/main/java/com/scmspain/exception/NotFoundException.java @@ -0,0 +1,16 @@ +package com.scmspain.exception; + +public class NotFoundException extends RuntimeException { + + private String message; + + public NotFoundException(String message) { + super(); + this.message = message; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/com/scmspain/repository/ITweetRepository.java b/src/main/java/com/scmspain/repository/ITweetRepository.java new file mode 100755 index 0000000..d078cca --- /dev/null +++ b/src/main/java/com/scmspain/repository/ITweetRepository.java @@ -0,0 +1,18 @@ +package com.scmspain.repository; + +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ITweetRepository { + + void create(T tweet); + + T get(U id); + + List getAll(Boolean discarded); + + void discard(T tweet); + +} \ No newline at end of file diff --git a/src/main/java/com/scmspain/repository/TweetRepository.java b/src/main/java/com/scmspain/repository/TweetRepository.java new file mode 100755 index 0000000..34c16c1 --- /dev/null +++ b/src/main/java/com/scmspain/repository/TweetRepository.java @@ -0,0 +1,53 @@ +package com.scmspain.repository; + +import com.scmspain.entities.Tweet; + +import javax.persistence.EntityManager; +import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +import java.util.List; + +public class TweetRepository implements ITweetRepository { + + private EntityManager entityManager; + + public TweetRepository(EntityManager entityManager) { + this.entityManager = entityManager; + } + + @Override + public void create(Tweet tweet) { + this.entityManager.persist(tweet); + } + + @Override + public Tweet get(Long id) { + return this.entityManager.find(Tweet.class, id); + } + + @Override + public List getAll(Boolean discarded) { + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(); + Root tweet = criteriaQuery.from(Tweet.class); + criteriaQuery.select(tweet); + criteriaQuery.where( + criteriaBuilder.notEqual(tweet.get("pre2015MigrationStatus"), 99), + criteriaBuilder.equal(tweet.get("discarded"), discarded) + ); + if (Boolean.FALSE.equals(discarded)) { + criteriaQuery.orderBy(criteriaBuilder.desc(tweet.get("publicationDate"))); + } else { + criteriaQuery.orderBy(criteriaBuilder.desc(tweet.get("discardedDate"))); + } + Query query = entityManager.createQuery(criteriaQuery); + return query.getResultList(); + } + + @Override + public void discard(Tweet tweet) { + this.entityManager.merge(tweet); + } +} diff --git a/src/main/java/com/scmspain/services/TweetService.java b/src/main/java/com/scmspain/services/TweetService.java old mode 100644 new mode 100755 index d61bc9d..f0be865 --- a/src/main/java/com/scmspain/services/TweetService.java +++ b/src/main/java/com/scmspain/services/TweetService.java @@ -1,68 +1,97 @@ package com.scmspain.services; import com.scmspain.entities.Tweet; +import com.scmspain.exception.NotFoundException; +import com.scmspain.repository.ITweetRepository; +import com.scmspain.validators.TweetValidator; import org.springframework.boot.actuate.metrics.writer.Delta; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import org.springframework.stereotype.Service; -import javax.persistence.EntityManager; -import javax.persistence.TypedQuery; import javax.transaction.Transactional; -import java.util.ArrayList; +import java.util.Date; import java.util.List; @Service @Transactional public class TweetService { - private EntityManager entityManager; + + private ITweetRepository repository; private MetricWriter metricWriter; + private TweetValidator validator; - public TweetService(EntityManager entityManager, MetricWriter metricWriter) { - this.entityManager = entityManager; + /** + * Constructor of the service + * + * @param repository the repository + * @param metricWriter the metric writer + * @param validator the tweet validator + */ + public TweetService(ITweetRepository repository, MetricWriter metricWriter, TweetValidator validator) { + this.repository = repository; this.metricWriter = metricWriter; + this.validator = validator; } /** - Push tweet to repository - Parameter - publisher - creator of the Tweet - Parameter - text - Content of the Tweet - Result - recovered Tweet - */ - public void publishTweet(String publisher, String text) { - if (publisher != null && publisher.length() > 0 && text != null && text.length() > 0 && text.length() < 140) { - Tweet tweet = new Tweet(); - tweet.setTweet(text); - tweet.setPublisher(publisher); - - this.metricWriter.increment(new Delta("published-tweets", 1)); - this.entityManager.persist(tweet); - } else { - throw new IllegalArgumentException("Tweet must not be greater than 140 characters"); - } + * Push tweet to repository + * + * @param publisher the publisher of the tweet + * @param text the body of the tweet + * @throws IllegalArgumentException exception thrown if requirements are not met + */ + public void publishTweet(String publisher, String text) throws IllegalArgumentException { + Tweet tweet = new Tweet(publisher, text); + validator.validateTweet(tweet); + this.repository.create(tweet); + this.metricWriter.increment(new Delta("published-tweets", 1)); } /** - Recover tweet from repository - Parameter - id - id of the Tweet to retrieve - Result - retrieved Tweet - */ + * Recover tweet from repository + * + * @param id the Id of the Tweet + * @return Tweet found by id + */ public Tweet getTweet(Long id) { - return this.entityManager.find(Tweet.class, id); + this.metricWriter.increment(new Delta("times-queried-tweet-by-id", 1)); + return this.repository.get(id); } /** - Recover tweet from repository - Parameter - id - id of the Tweet to retrieve - Result - retrieved Tweet - */ - public List listAllTweets() { - List result = new ArrayList(); - this.metricWriter.increment(new Delta("times-queried-tweets", 1)); - TypedQuery query = this.entityManager.createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class); - List ids = query.getResultList(); - for (Long id : ids) { - result.add(getTweet(id)); + * Recover published tweets from repository + * + * @return List list of published tweets + */ + public List listAllPublishedTweets() { + this.metricWriter.increment(new Delta("times-queried-published-tweets", 1)); + return this.repository.getAll(Boolean.FALSE); + } + + /** + * Recover discarded tweets from repository + * + * @return List list of discarded tweets + */ + public List listAllDiscardedTweets() { + this.metricWriter.increment(new Delta("times-queried-discarded-tweets", 1)); + return this.repository.getAll(Boolean.TRUE); + } + + /** + * Discards a tweet if exists in the database + * + * @param tweetId the Id of the Tweet + */ + public void discardTweet(String tweetId) { + Tweet tweet = this.repository.get(Long.valueOf(tweetId)); + if (tweet == null) { + throw new NotFoundException("Tweet with id " + tweetId + " does not exist in the Database"); } - return result; + tweet.setDiscarded(Boolean.TRUE); + tweet.setDiscardedDate(new Date()); + this.repository.discard(tweet); + this.metricWriter.increment(new Delta("times-discarded-tweet", 1)); } + } diff --git a/src/main/java/com/scmspain/validators/TweetValidator.java b/src/main/java/com/scmspain/validators/TweetValidator.java new file mode 100755 index 0000000..f37af9b --- /dev/null +++ b/src/main/java/com/scmspain/validators/TweetValidator.java @@ -0,0 +1,31 @@ +package com.scmspain.validators; + +import com.scmspain.entities.Tweet; +import org.springframework.util.StringUtils; + +public class TweetValidator { + + public void validateTweet(Tweet tweet) throws IllegalArgumentException { + validatePublisher(tweet.getPublisher()); + validateText(tweet.getTweet()); + } + + private void validatePublisher(String publisher) throws IllegalArgumentException { + if (StringUtils.isEmpty(publisher)) { + throw new IllegalArgumentException("Publisher must not be empty"); + } + } + + private void validateText(String text) throws IllegalArgumentException { + if (StringUtils.isEmpty(text)) { + throw new IllegalArgumentException("Tweet must not be empty"); + } + if (removeLinksFromTweet(text).length() <= 0 || removeLinksFromTweet(text).length() > 140) { + throw new IllegalArgumentException("Tweet must not be greater than 140 characters"); + } + } + + private String removeLinksFromTweet(String text) { + return text.replaceAll("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", ""); + } +} \ No newline at end of file diff --git a/src/test/java/com/scmspain/configuration/TestConfiguration.java b/src/test/java/com/scmspain/configuration/TestConfiguration.java old mode 100644 new mode 100755 index 28a6657..92d665c --- a/src/test/java/com/scmspain/configuration/TestConfiguration.java +++ b/src/test/java/com/scmspain/configuration/TestConfiguration.java @@ -11,8 +11,10 @@ @Configuration @Import({MsFcTechTestApplication.class}) public class TestConfiguration { + @Bean public MBeanExporter mockExporter() { return mock(MBeanExporter.class); } + } diff --git a/src/test/java/com/scmspain/controller/TweetControllerTest.java b/src/test/java/com/scmspain/controller/TweetControllerTest.java old mode 100644 new mode 100755 index 4368add..5d9054f --- a/src/test/java/com/scmspain/controller/TweetControllerTest.java +++ b/src/test/java/com/scmspain/controller/TweetControllerTest.java @@ -8,6 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -25,7 +26,9 @@ @RunWith(SpringRunner.class) @SpringBootTest(classes = TestConfiguration.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class TweetControllerTest { + @Autowired private WebApplicationContext context; private MockMvc mockMvc; @@ -38,18 +41,18 @@ public void setUp() { @Test public void shouldReturn200WhenInsertingAValidTweet() throws Exception { mockMvc.perform(newTweet("Prospect", "Breaking the law")) - .andExpect(status().is(201)); + .andExpect(status().is(201)); } @Test public void shouldReturn400WhenInsertingAnInvalidTweet() throws Exception { - mockMvc.perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome!")) + mockMvc.perform(newTweet("Schibsted Spain", "We are Schibsted Spain (look at our home page http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome! Lorem Ipsum is simply dummy text of the printing and typesetting industry.")) .andExpect(status().is(400)); } @Test public void shouldReturnAllPublishedTweets() throws Exception { - mockMvc.perform(newTweet("Yo", "How are you?")) + mockMvc.perform(newTweet("Yoda", "May the Force be with you.")) .andExpect(status().is(201)); MvcResult getResult = mockMvc.perform(get("/tweet")) @@ -60,10 +63,57 @@ public void shouldReturnAllPublishedTweets() throws Exception { assertThat(new ObjectMapper().readValue(content, List.class).size()).isEqualTo(1); } + @Test + public void shouldDiscardAPublishedTweet() throws Exception { + mockMvc.perform(newTweet("Robert DeNiro", "You talking to me?")) + .andExpect(status().is(201)); + + mockMvc.perform(discardTweet(Long.valueOf("1"))) + .andExpect(status().is(204)) + .andReturn(); + + MvcResult getResult = mockMvc.perform(get("/tweet")) + .andExpect(status().is(200)) + .andReturn(); + + String content = getResult.getResponse().getContentAsString(); + assertThat(new ObjectMapper().readValue(content, List.class).size()).isEqualTo(0); + } + + @Test + public void shouldReturnAllDiscardedTweets() throws Exception { + mockMvc.perform(newTweet("E.T", "E.T. phone home.")) + .andExpect(status().is(201)); + + mockMvc.perform(discardTweet(Long.valueOf("1"))) + .andExpect(status().is(204)) + .andReturn(); + + MvcResult getResult = mockMvc.perform(get("/discarded")) + .andExpect(status().is(200)) + .andReturn(); + + String content = getResult.getResponse().getContentAsString(); + assertThat(new ObjectMapper().readValue(content, List.class).size()).isEqualTo(1); + } + + @Test + public void shouldReturn404WhenTweetNotFound() throws Exception { + mockMvc.perform(discardTweet(Long.valueOf("1"))) + .andExpect(status().is(404)) + .andReturn(); + } + private MockHttpServletRequestBuilder newTweet(String publisher, String tweet) { return post("/tweet") .contentType(MediaType.APPLICATION_JSON_UTF8) .content(format("{\"publisher\": \"%s\", \"tweet\": \"%s\"}", publisher, tweet)); } + private MockHttpServletRequestBuilder discardTweet(Long id) { + return post("/discarded") + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(format("{\"tweet\": \"%s\"}", id)); + } + } diff --git a/src/test/java/com/scmspain/services/TweetServiceTest.java b/src/test/java/com/scmspain/services/TweetServiceTest.java old mode 100644 new mode 100755 index ac88fe5..9171481 --- a/src/test/java/com/scmspain/services/TweetServiceTest.java +++ b/src/test/java/com/scmspain/services/TweetServiceTest.java @@ -1,38 +1,87 @@ package com.scmspain.services; import com.scmspain.entities.Tweet; +import com.scmspain.exception.NotFoundException; +import com.scmspain.repository.TweetRepository; +import com.scmspain.validators.TweetValidator; import org.junit.Before; import org.junit.Test; import org.springframework.boot.actuate.metrics.writer.MetricWriter; import javax.persistence.EntityManager; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; public class TweetServiceTest { + private EntityManager entityManager; private MetricWriter metricWriter; private TweetService tweetService; + private TweetValidator tweetValidator; @Before - public void setUp() throws Exception { + public void setUp() { this.entityManager = mock(EntityManager.class); this.metricWriter = mock(MetricWriter.class); - - this.tweetService = new TweetService(entityManager, metricWriter); + this.tweetValidator = new TweetValidator(); + this.tweetService = new TweetService(new TweetRepository(entityManager), metricWriter, tweetValidator); } @Test - public void shouldInsertANewTweet() throws Exception { + public void shouldInsertANewTweet() { tweetService.publishTweet("Guybrush Threepwood", "I am Guybrush Threepwood, mighty pirate."); - verify(entityManager).persist(any(Tweet.class)); } @Test(expected = IllegalArgumentException.class) - public void shouldThrowAnExceptionWhenTweetLengthIsInvalid() throws Exception { + public void shouldThrowAnExceptionWhenTweetLengthIsInvalid() { tweetService.publishTweet("Pirate", "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."); } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowAnExceptionWhenTweetIsNull() { + tweetService.publishTweet("Guybrush Threepwood", null); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowAnExceptionWhenTweetIsEmpty() { + tweetService.publishTweet("Pirate", ""); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowAnExceptionWhenPublisherIsNull() { + tweetService.publishTweet(null, "I am Guybrush Threepwood, mighty pirate."); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowAnExceptionWhenPublisherIsEmpty() { + tweetService.publishTweet("", "LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to drop dead. So he did. Then things really got ugly."); + } + + @Test + public void shouldInsertANewTweetWithLinks() { + tweetService.publishTweet("Pirate", "http://www.worldofmi.com/ LeChuck? He's the guy that went to the Governor's for dinner and never wanted to leave. He fell for her in a big way, but she told him to.."); + verify(entityManager).persist(any(Tweet.class)); + } + + @Test + public void shouldDiscardAnExistingTweet() throws NotFoundException { + Tweet tweet = new Tweet(); + tweet.setTweet("Lorem Ipsum is simply dummy text of the printing and typesetting industry"); + tweet.setPublisher("Gaston D'Apice"); + + when(entityManager.find(Tweet.class, Long.valueOf("1406"))).thenReturn(tweet); + + tweetService.discardTweet("1406"); + + Tweet discardedTweet = tweetService.getTweet(Long.valueOf("1406")); + assertThat(discardedTweet.isDiscarded().equals(Boolean.TRUE)); + } + + @Test(expected = NotFoundException.class) + public void shouldThrowAnExceptionWhenTweetNotFound() throws NotFoundException { + tweetService.discardTweet("1406"); + } }