Skip to content
This repository has been archived by the owner on Dec 11, 2018. It is now read-only.

Jacob Ibáñez #35

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
1 change: 0 additions & 1 deletion src/main/java/com/scmspain/MsFcTechTestApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.scmspain.configuration.TweetConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about that... if I put the @SpringBootApplication annotation, the tests fail, ¿why is that?
Nevertheless, a unused import must be erased.

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/scmspain/controller/TweetController.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.scmspain.controller;

import com.scmspain.controller.command.DiscardTweetCommand;
import com.scmspain.controller.command.PublishTweetCommand;
import com.scmspain.entities.Tweet;
import com.scmspain.services.TweetService;
Expand Down Expand Up @@ -29,6 +30,16 @@ public void publishTweet(@RequestBody PublishTweetCommand publishTweetCommand) {
this.tweetService.publishTweet(publishTweetCommand.getPublisher(), publishTweetCommand.getTweet());
}

@PostMapping("/discarded")
public void discardTweet(@RequestBody DiscardTweetCommand discardTweetCommand) {
this.tweetService.discardTweet(discardTweetCommand.getTweet());
}

@GetMapping("/discarded")
public List<Tweet> listAllDiscardedTweets() {
return this.tweetService.listAllDiscardedTweets();
}

@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(BAD_REQUEST)
@ResponseBody
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.scmspain.controller.command;

public class DiscardTweetCommand {

private Long tweet;

public Long getTweet() {
return tweet;
}

public void setTweet(Long tweet) {
this.tweet = tweet;
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/scmspain/entities/Tweet.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.sql.Timestamp;
import java.time.LocalDateTime;

@Entity
public class Tweet {
Expand All @@ -16,6 +18,10 @@ public class Tweet {
private String tweet;
@Column (nullable=true)
private Long pre2015MigrationStatus = 0L;
@Column(nullable = false)
private Boolean discarded = Boolean.FALSE;
@Column(nullable = true)
private Timestamp discardedDate;

public Tweet() {
}
Expand Down Expand Up @@ -52,4 +58,16 @@ public void setPre2015MigrationStatus(Long pre2015MigrationStatus) {
this.pre2015MigrationStatus = pre2015MigrationStatus;
}

public Boolean isDiscarded() {
return discarded;
}

public void setDiscarded(Boolean discarded) {
this.discarded = discarded;
this.discardedDate = Timestamp.valueOf(LocalDateTime.now());
}

public Timestamp getDiscardedDate() {
return discardedDate;
}
}
66 changes: 61 additions & 5 deletions src/main/java/com/scmspain/services/TweetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Transactional
public class TweetService {
private static final String QUERY_LIST_ALL_TWEETS = "SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 AND discarded = FALSE ORDER BY id DESC";
private static final String QUERY_LIST_ALL_DISCARDED_TWEETS = "SELECT id FROM Tweet AS tweetId WHERE discarded = TRUE ORDER BY discardedDate DESC";

private EntityManager entityManager;
private MetricWriter metricWriter;

Expand All @@ -29,7 +34,10 @@ public TweetService(EntityManager entityManager, MetricWriter metricWriter) {
Result - recovered Tweet
*/
public void publishTweet(String publisher, String text) {
if (publisher != null && publisher.length() > 0 && text != null && text.length() > 0 && text.length() < 140) {

boolean publisherIsNotNullOrEmpty = publisher != null && publisher.length() > 0;

if (publisherIsNotNullOrEmpty && tweetIsValid(text)) {
Tweet tweet = new Tweet();
tweet.setTweet(text);
tweet.setPublisher(publisher);
Expand All @@ -51,18 +59,66 @@ public Tweet getTweet(Long id) {
}

/**
Recover tweet from repository
Parameter - id - id of the Tweet to retrieve
Result - retrieved Tweet
Recover tweets from repository
Result - retrieved Tweets
*/
public List<Tweet> listAllTweets() {
List<Tweet> result = new ArrayList<Tweet>();
this.metricWriter.increment(new Delta<Number>("times-queried-tweets", 1));
TypedQuery<Long> query = this.entityManager.createQuery("SELECT id FROM Tweet AS tweetId WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Long.class);
TypedQuery<Long> query = this.entityManager.createQuery(QUERY_LIST_ALL_TWEETS, Long.class);
List<Long> ids = query.getResultList();
for (Long id : ids) {
result.add(getTweet(id));
}
return result;
}

/**
* Discard a tweet
* Parameter - id - id of the Tweet to discard
*/
public void discardTweet(Long id) {
Tweet tweetToDiscard = getTweet(id);

if (tweetToDiscard != null) {
tweetToDiscard.setDiscarded(Boolean.TRUE);

this.metricWriter.increment(new Delta<Number>("discarded-tweets", 1));
this.entityManager.persist(tweetToDiscard);
} else {
throw new IllegalArgumentException("The selected tweet does not exists");
}
}

/**
Recover discarded tweets from repository
Result - retrieved Tweets
*/
public List<Tweet> listAllDiscardedTweets() {
List<Tweet> result = new ArrayList<>();
this.metricWriter.increment(new Delta<Number>("times-queried-discarded-tweets", 1));
TypedQuery<Long> query = this.entityManager.createQuery(QUERY_LIST_ALL_DISCARDED_TWEETS, Long.class);
List<Long> ids = query.getResultList();
for (Long id : ids) {
result.add(getTweet(id));
}
return result;
}

private boolean tweetIsValid(String tweet) {
String linkRegex = "(.*)https?://(.*)";
String space = " ";

String tweetWithoutLink = tweet;

if (tweet.matches(linkRegex)) {
tweetWithoutLink = Arrays.stream(tweet.split(space))
.filter(word -> !word.matches(linkRegex))
.collect(Collectors.joining(space));
}

boolean tweetIsNotNullOrEmpty = tweetWithoutLink != null && tweetWithoutLink.length() > 0;

return tweetIsNotNullOrEmpty && tweetWithoutLink.length() < 140;
}
}
65 changes: 64 additions & 1 deletion src/test/java/com/scmspain/controller/TweetControllerTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.scmspain.controller;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.scmspain.configuration.TestConfiguration;
import com.scmspain.entities.Tweet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -43,7 +45,9 @@ public void shouldReturn200WhenInsertingAValidTweet() throws Exception {

@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, " +
"it's built on latest JS framework http://www.schibsted.es/), we own Vibbo, InfoJobs, fotocasa, " +
"coches.net and milanuncios. Welcome!"))
.andExpect(status().is(400));
}

Expand All @@ -60,10 +64,69 @@ public void shouldReturnAllPublishedTweets() throws Exception {
assertThat(new ObjectMapper().readValue(content, List.class).size()).isEqualTo(1);
}

@Test
@SuppressWarnings("unchecked")
public void shouldDiscardTweet() throws Exception {
mockMvc.perform(newTweet("Yo", "How are you?"));

List<Tweet> tweets = getTweets();

int numberOfTweets = tweets.size();

assertThat(numberOfTweets).isGreaterThan(0);

Long id = tweets.get(0).getId();

mockMvc.perform(discardTweet(id))
.andExpect(status().is(200));

tweets = getTweets();
assertThat(tweets.size()).isEqualTo(--numberOfTweets);
}

@Test
public void shouldReturnAllDiscardedTweets() throws Exception {
mockMvc.perform(newTweet("1", "May be discarded"));
mockMvc.perform(newTweet("1", "May be discarded"));
mockMvc.perform(newTweet("1", "May be discarded"));
mockMvc.perform(newTweet("1", "May be discarded"));

Long randomId = getTweets().stream()
.map(Tweet::getId)
.findAny()
.orElse(0L);

mockMvc.perform(discardTweet(randomId));

MvcResult getResult = mockMvc.perform(get("/discarded"))
.andExpect(status().is(200))
.andReturn();

String content = getResult.getResponse().getContentAsString();
List<Tweet> discardedTweets = new ObjectMapper().readValue(content, new TypeReference<List<Tweet>>(){});

assertThat(discardedTweets.size()).isGreaterThanOrEqualTo(1);
}

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));
}

private List<Tweet> getTweets() throws Exception {
MvcResult getResult = mockMvc.perform(get("/tweet"))
.andExpect(status().is(200))
.andReturn();

String content = getResult.getResponse().getContentAsString();
return new ObjectMapper().readValue(content, new TypeReference<List<Tweet>>(){});
}

}
8 changes: 8 additions & 0 deletions src/test/java/com/scmspain/services/TweetServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,12 @@ public void shouldInsertANewTweet() throws Exception {
public void shouldThrowAnExceptionWhenTweetLengthIsInvalid() throws Exception {
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
public void shouldIgnoreLinksForCharacterLimit() {
tweetService.publishTweet("LeChuck", "Please visit my personal web page http://makelechuckgreatagain.com, plenty of eighties stuff " +
"like Alf pictures, Madonna music and so on. #nostalgia #backtothe80s");

verify(entityManager).persist(any(Tweet.class));
}
}