Skip to content

Commit

Permalink
Added: controller for handling overview information and respective se…
Browse files Browse the repository at this point in the history
…rvice. Pull out into abstraction mutual error handling, from services, and then in the controllers as well. Created necessary tests, and fixed some broken ones.
  • Loading branch information
edinfazlic committed Mar 20, 2020
1 parent 239af7f commit 8b152a0
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 39 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.minitwitter.controller;

import com.minitwitter.domain.dto.ErrorMessage;
import com.minitwitter.service.ExceptionHandlingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@Slf4j
public abstract class ExceptionHandlingController {

@ExceptionHandler
@ResponseStatus(BAD_REQUEST)
public ErrorMessage handleUnknownUsernameException(ExceptionHandlingService.UnknownUsernameException e) {
log.warn("", e);
return new ErrorMessage(String.format("Unknown user '%s'", e.getUsername()));
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/minitwitter/controller/OverviewController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.minitwitter.controller;

import com.minitwitter.domain.dto.UserOverviewDTO;
import com.minitwitter.service.OverviewService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("overview")
@Slf4j
public class OverviewController extends ExceptionHandlingController{

private OverviewService overviewService;

public OverviewController(OverviewService overviewService) {
this.overviewService = overviewService;
}

@GetMapping(value = "{username}")
public UserOverviewDTO followers(@PathVariable String username) {
return overviewService.getUsersFollowers(username);
}

}
12 changes: 2 additions & 10 deletions src/main/java/com/minitwitter/controller/TweetController.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.minitwitter.controller;

import com.minitwitter.domain.dto.ErrorMessage;
import com.minitwitter.domain.dto.TweetDTO;
import com.minitwitter.service.TweetService;
import com.minitwitter.service.TweetService.InvalidTweetException;
import com.minitwitter.service.TweetService.UnknownUsernameException;
import com.minitwitter.domain.dto.TweetDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -24,7 +23,7 @@
@RestController
@RequestMapping("tweets")
@Slf4j
public class TweetController {
public class TweetController extends ExceptionHandlingController {

private TweetService tweetService;

Expand All @@ -48,13 +47,6 @@ public Collection<TweetDTO> tweetsFromUser(@PathVariable String username) {
return tweetService.tweetsFromUser(username);
}

@ExceptionHandler
@ResponseStatus(BAD_REQUEST)
public ErrorMessage handleUnknownUsernameException(UnknownUsernameException e) {
log.warn("", e);
return new ErrorMessage(String.format("Unknown user '%s'", e.getUsername()));
}

@ExceptionHandler
@ResponseStatus(BAD_REQUEST)
public ErrorMessage handleInvalidTweetException(InvalidTweetException e) {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/minitwitter/domain/dto/UserOverviewDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.minitwitter.domain.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import static lombok.AccessLevel.PRIVATE;

@Getter
@AllArgsConstructor
@NoArgsConstructor(access = PRIVATE)
public class UserOverviewDTO {
private int tweets;
private int followers;
private int following;
}
2 changes: 2 additions & 0 deletions src/main/java/com/minitwitter/repository/TweetRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
@Repository
public interface TweetRepository extends JpaRepository<Tweet, Long> {
List<Tweet> findAllByAuthor(User author);

int countByAuthorUsername(String username);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.minitwitter.service;

import com.minitwitter.domain.User;
import com.minitwitter.repository.UserRepository;
import lombok.Getter;
import org.springframework.stereotype.Service;

@Service
public abstract class ExceptionHandlingService {

private UserRepository userRepository;

public ExceptionHandlingService(UserRepository userRepository) {
this.userRepository = userRepository;
}

protected User getUser(String username) {
User user = userRepository.findOneByUsername(username);
if (user != null) {
return user;
}
throw new UnknownUsernameException(username);
}

public static class UnknownUsernameException extends RuntimeException {
@Getter
private String username;

private UnknownUsernameException(String username) {
super(username);
this.username = username;
}
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/minitwitter/service/OverviewService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.minitwitter.service;

import com.minitwitter.domain.User;
import com.minitwitter.domain.dto.UserOverviewDTO;
import com.minitwitter.repository.TweetRepository;
import com.minitwitter.repository.UserRepository;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
public class OverviewService extends ExceptionHandlingService {

private TweetRepository tweetRepository;

public OverviewService(TweetRepository tweetRepository, UserRepository userRepository) {
super(userRepository);
this.tweetRepository = tweetRepository;
}

@Transactional
public UserOverviewDTO getUsersFollowers(String username) {
int tweets = this.tweetRepository.countByAuthorUsername(username);

User user = this.getUser(username);
int followers = user.getFollowers().size();
int following = user.getFollowing().size();

return new UserOverviewDTO(tweets, followers, following);
}

}
23 changes: 2 additions & 21 deletions src/main/java/com/minitwitter/service/TweetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
import static java.util.stream.Collectors.toList;

@Service
public class TweetService {
public class TweetService extends ExceptionHandlingService {

private TweetRepository tweetRepository;
private UserRepository userRepository;

public TweetService(TweetRepository tweetRepository, UserRepository userRepository) {
super(userRepository);
this.tweetRepository = tweetRepository;
this.userRepository = userRepository;
}

public Collection<TweetDTO> followingUsersTweets(Principal principal) {
Expand Down Expand Up @@ -53,24 +52,6 @@ private List<TweetDTO> tweetsFromUser(User user) {
return tweetRepository.findAllByAuthor(user).stream().map(TweetDTO::new).collect(toList());
}

private User getUser(String username) {
User user = userRepository.findOneByUsername(username);
if (user != null) {
return user;
}
throw new UnknownUsernameException(username);
}

public static class UnknownUsernameException extends RuntimeException {
@Getter
private String username;

private UnknownUsernameException(String username) {
super(username);
this.username = username;
}
}

public static class InvalidTweetException extends RuntimeException {

private InvalidTweetException(String tweet) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.minitwitter.controller;

import com.minitwitter.domain.dto.ErrorMessage;
import com.minitwitter.domain.dto.UserOverviewDTO;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.util.Collections;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

public class OverviewControllerIntegrationTest extends RestIntegrationTest {

@Test
public void overviewOfUser_onlyThatUserTweetsAreReturned() {
ResponseEntity<UserOverviewDTO> response = withAuthTestRestTemplate().getForEntity("/overview/{username}",
UserOverviewDTO.class, Collections.singletonMap("username", getUsernameOfAuthUser()));
assertThat(response.getStatusCode().is2xxSuccessful(), is(true));
UserOverviewDTO overview = response.getBody();
assertThat(overview.getTweets(), equalTo(ownTweetsCount()));
assertThat(overview.getFollowers(), equalTo(followersCount()));
assertThat(overview.getFollowing(), equalTo(followingUsers().length));
}

@Test
public void overviewOfUserWithWrongUsername_badRequestReturned() {
ResponseEntity<ErrorMessage> response = withAuthTestRestTemplate().getForEntity("/overview/{username}",
ErrorMessage.class, Collections.singletonMap("username", "unknown"));
assertThat(response.getStatusCode(), is(HttpStatus.BAD_REQUEST));
assertThat(response.getBody().getMessage(), containsString("unknown"));
}

}
24 changes: 21 additions & 3 deletions src/test/java/com/minitwitter/controller/RestIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,36 @@
@SpringBootTest(webEnvironment = RANDOM_PORT)
public abstract class RestIntegrationTest {

private static final String AUTH_USERNAME = "aantonop";
private static final int OWN_TWEETS_COUNT = 3;
private static final int FOLLOWERS_COUNT = 1;
private static final String[] FOLLOWING_USERS = new String[]{"rogerkver", "satoshiNakamoto", "SatoshiLite", "VitalikButerin"};
private static final String[] FOLLOWERS = new String[]{"rogerkver"};

@Autowired
private TestRestTemplate testRestTemplate;

TestRestTemplate withAuthTestRestTemplate() {
return testRestTemplate.withBasicAuth("aantonop", "password");
return testRestTemplate.withBasicAuth(AUTH_USERNAME, "password");
}

String getUsernameOfAuthUser() {
return "aantonop";
return AUTH_USERNAME;
}

String[] followingUsers() {
return new String[]{"rogerkver", "satoshiNakamoto", "SatoshiLite", "VitalikButerin"};
return FOLLOWING_USERS;
}

String[] followers() {
return FOLLOWERS;
}

int ownTweetsCount() {
return OWN_TWEETS_COUNT;
}

int followersCount() {
return FOLLOWERS_COUNT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void createTweet_tweetIsSaved() {
Tweet fromDb = tweetRepository.findOne(tweet.getId());
assertThat(fromDb, notNullValue());
assertThat(fromDb.getContent(), equalTo(tweet.getContent()));
assertThat(fromDb.getAuthor().getUsername(), equalTo(tweet.getAuthor()));
assertThat(fromDb.getAuthor().getUsername(), equalTo(tweet.getAuthorUsername()));
}

@Test
Expand Down Expand Up @@ -76,6 +76,6 @@ public void tweetsFromUserWithWrongUsername_badRequestReturned() {
}

private List<String> extractAuthorNames(TweetDTO[] body) {
return Arrays.stream(body).map(TweetDTO::getAuthor).distinct().collect(toList());
return Arrays.stream(body).map(TweetDTO::getAuthorUsername).distinct().collect(toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ public void followersRequested_allFollowersReturned() {
ResponseEntity<UserDTO[]> response = withAuthTestRestTemplate().getForEntity("/followers", UserDTO[].class);
assertThat(response.getStatusCode().is2xxSuccessful(), is(true));
List<UserDTO> followers = Arrays.asList(response.getBody());
assertThat(followers, hasSize(1));
assertThat(extractUsernames(followers), contains("rogerkver"));
assertThat(followers, hasSize(followers().length));
assertThat(extractUsernames(followers), containsInAnyOrder(followers()));
}

@Test
public void getFollowingFromFirstPage() {
ResponseEntity<UserDTO[]> response = withAuthTestRestTemplate().getForEntity("/following", UserDTO[].class);
assertThat(response.getStatusCode().is2xxSuccessful(), is(true));
List<UserDTO> following = Arrays.asList(response.getBody());
assertThat(following, hasSize(4));
assertThat(following, hasSize(followingUsers().length));
assertThat(extractUsernames(following), containsInAnyOrder(followingUsers()));
}

Expand Down

0 comments on commit 8b152a0

Please sign in to comment.