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

Ramon Medel Pull Request #33

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ build/
nbbuild/
dist/
nbdist/
.nb-gradle/
.nb-gradle/
out/
9 changes: 1 addition & 8 deletions src/main/java/com/scmspain/MsFcTechTestApplication.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
package com.scmspain;

import com.scmspain.configuration.InfrastructureConfiguration;
import com.scmspain.configuration.TweetConfiguration;
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})
@SpringBootApplication
public class MsFcTechTestApplication {
public static void main(String[] args) {
SpringApplication.run(MsFcTechTestApplication.class, args);
Expand Down
22 changes: 0 additions & 22 deletions src/main/java/com/scmspain/configuration/TweetConfiguration.java

This file was deleted.

19 changes: 18 additions & 1 deletion src/main/java/com/scmspain/controller/TweetController.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.scmspain.controller;

import com.scmspain.controller.command.DiscardTweetCommand;
import com.scmspain.controller.command.PublishTweetCommand;
import com.scmspain.entities.DiscardedTweet;
import com.scmspain.entities.Tweet;
import com.scmspain.services.TweetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;

@RestController
public class TweetController {

private TweetService tweetService;

@Autowired
public TweetController(TweetService tweetService) {
this.tweetService = tweetService;
}
Expand All @@ -25,10 +31,21 @@ public List<Tweet> listAllTweets() {

@PostMapping("/tweet")
@ResponseStatus(CREATED)
public void publishTweet(@RequestBody PublishTweetCommand publishTweetCommand) {
public void publishTweet(@RequestBody @Valid PublishTweetCommand publishTweetCommand) {
this.tweetService.publishTweet(publishTweetCommand.getPublisher(), publishTweetCommand.getTweet());
}

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

@GetMapping("/discarded")
public List<DiscardedTweet> 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,17 @@
package com.scmspain.controller.command;

import javax.validation.constraints.NotNull;

public class DiscardTweetCommand {

@NotNull
private Long tweet;

public Long getTweet() {
return tweet;
}

public void setTweet(Long tweet) {
this.tweet = tweet;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
package com.scmspain.controller.command;

import com.scmspain.controller.validation.TweetTextWithUrlsConstraint;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class PublishTweetCommand {
@NotNull
@Size(min = 1)
private String publisher;
@NotNull
@Size(min = 1)
@TweetTextWithUrlsConstraint
private String tweet;

public String getPublisher() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.scmspain.controller.validation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = TweetTextWithUrlsValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TweetTextWithUrlsConstraint {
String message() default "Invalid size for a tweet text";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.scmspain.controller.validation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TweetTextWithUrlsValidator implements
ConstraintValidator<TweetTextWithUrlsConstraint, String> {
private static final int TWEET_MAX_SIZE = 140;
private static final Pattern urlPattern = Pattern.compile("((https?)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|] )");

@Override
public void initialize(TweetTextWithUrlsConstraint constraintAnnotation) {

}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return isValidTweetWithUrl(value);
}


private boolean isValidTweetWithUrl(String tweet) {
int size = tweet.length();
Matcher urlMatcher = urlPattern.matcher(tweet);
int i = 0;
while (urlMatcher.find()) {
size -= urlMatcher.group(i++).length();
}
return size <= TWEET_MAX_SIZE;
}

}
61 changes: 61 additions & 0 deletions src/main/java/com/scmspain/entities/DiscardedTweet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.scmspain.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class DiscardedTweet {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String publisher;
@Column(nullable = false)
private String tweet;
@Column (nullable=true)
private Long pre2015MigrationStatus = 0L;

public DiscardedTweet() {
}

public DiscardedTweet(Tweet tweet) {
this.publisher = tweet.getPublisher();
this.tweet = tweet.getTweet();
this.pre2015MigrationStatus = tweet.getPre2015MigrationStatus();
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getPublisher() {
return publisher;
}

public void setPublisher(String publisher) {
this.publisher = publisher;
}

public String getTweet() {
return tweet;
}

public void setTweet(String tweet) {
this.tweet = tweet;
}

public Long getPre2015MigrationStatus() {
return pre2015MigrationStatus;
}

public void setPre2015MigrationStatus(Long pre2015MigrationStatus) {
this.pre2015MigrationStatus = pre2015MigrationStatus;
}

}
2 changes: 1 addition & 1 deletion src/main/java/com/scmspain/entities/Tweet.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class Tweet {
private Long id;
@Column(nullable = false)
private String publisher;
@Column(nullable = false, length = 140)
@Column(nullable = false)
private String tweet;
@Column (nullable=true)
private Long pre2015MigrationStatus = 0L;
Expand Down
43 changes: 28 additions & 15 deletions src/main/java/com/scmspain/services/TweetService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.scmspain.services;

import com.scmspain.entities.DiscardedTweet;
import com.scmspain.entities.Tweet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.writer.Delta;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.stereotype.Service;
Expand All @@ -14,9 +16,11 @@
@Service
@Transactional
public class TweetService {

private EntityManager entityManager;
private MetricWriter metricWriter;

@Autowired
public TweetService(EntityManager entityManager, MetricWriter metricWriter) {
this.entityManager = entityManager;
this.metricWriter = metricWriter;
Expand All @@ -29,16 +33,12 @@ 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) {
Tweet tweet = new Tweet();
tweet.setTweet(text);
tweet.setPublisher(publisher);
Tweet tweet = new Tweet();
tweet.setTweet(text);
tweet.setPublisher(publisher);

this.metricWriter.increment(new Delta<Number>("published-tweets", 1));
this.entityManager.persist(tweet);
} else {
throw new IllegalArgumentException("Tweet must not be greater than 140 characters");
}
this.metricWriter.increment(new Delta<Number>("published-tweets", 1));
this.entityManager.persist(tweet);
}

/**
Expand All @@ -58,11 +58,24 @@ public Tweet getTweet(Long id) {
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);
List<Long> ids = query.getResultList();
for (Long id : ids) {
result.add(getTweet(id));
}
return result;
TypedQuery<Tweet> query = this.entityManager.createQuery("SELECT t FROM Tweet t WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", Tweet.class);
return query.getResultList();
}

public void discardTweet(Long id) {
Tweet tweet = getTweet(id);
DiscardedTweet discardedTweet = new DiscardedTweet(tweet);
this.entityManager.remove(tweet);
this.metricWriter.increment(new Delta<Number>("published-tweets", -1));

this.entityManager.persist(discardedTweet);
this.metricWriter.increment(new Delta<Number>("discarded-tweets", 1));
}

public List<DiscardedTweet> listAllDiscardedTweets() {
this.metricWriter.increment(new Delta<Number>("times-queried-discarded-tweets", 1));
TypedQuery<DiscardedTweet> query = this.entityManager.createQuery("SELECT t FROM DiscardedTweet t WHERE pre2015MigrationStatus<>99 ORDER BY id DESC", DiscardedTweet.class);
return query.getResultList();
}

}
33 changes: 33 additions & 0 deletions 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 All @@ -11,6 +13,7 @@
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.web.context.WebApplicationContext;

Expand Down Expand Up @@ -39,6 +42,8 @@ public void setUp() {
public void shouldReturn200WhenInsertingAValidTweet() throws Exception {
mockMvc.perform(newTweet("Prospect", "Breaking the law"))
.andExpect(status().is(201));
mockMvc.perform(newTweet("Prospect 2", "We are Schibsted Spain (look at our home page http://www.schibsted.es/ ), we own Vibbo, InfoJobs, fotocasa, coches.net and milanuncios. Welcome!!!!!!!!!!!!!!!!!!"))
.andExpect(status().is(201));
}

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

@Test
public void shouldReturnAllDicardedTweets() throws Exception {
mockMvc.perform(newTweet("Yo", "How are you?"))
.andExpect(status().is(201));
List<Tweet> tweets = getTweets();
mockMvc.perform(newDiscardedTweet(tweets.get(0).getId()))
.andExpect(status().is(201));

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

private MockHttpServletRequestBuilder newTweet(String publisher, String tweet) {
return post("/tweet")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(format("{\"publisher\": \"%s\", \"tweet\": \"%s\"}", publisher, tweet));
}

private List<Tweet> getTweets() throws Exception {
MvcResult result = mockMvc.perform(get("/tweet")
.contentType(MediaType.APPLICATION_JSON_UTF8)).andReturn();
return new ObjectMapper().readValue(result.getResponse().getContentAsString(), new TypeReference<List<Tweet>>() { });
}

private MockHttpServletRequestBuilder newDiscardedTweet(Long id) {
return post("/discarded")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(format("{\"tweet\": %d}", id));
}

}
Loading