package de.asideas.crowdsource.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import de.asideas.crowdsource.domain.exception.InvalidRequestException;
import de.asideas.crowdsource.domain.exception.ResourceNotFoundException;
import de.asideas.crowdsource.domain.model.FinancingRoundEntity;
import de.asideas.crowdsource.domain.model.UserEntity;
import de.asideas.crowdsource.presentation.FinancingRound;
import de.asideas.crowdsource.repository.FinancingRoundRepository;
import de.asideas.crowdsource.service.FinancingRoundService;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = FinancingRoundControllerMockMvcTest.Config.class)
public class FinancingRoundControllerMockMvcTest {
@Autowired
private FinancingRoundService financingRoundService;
@Autowired
private FinancingRoundRepository financingRoundRepository;
@Resource
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
private ObjectMapper mapper = new ObjectMapper();
private DateTime fixedDate;
private List<FinancingRoundEntity> financingRoundEntities;
@Before
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
reset(financingRoundRepository, financingRoundService);
financingRoundEntities = new ArrayList<>();
fixedDate = DateTime.parse("2015-01-10T10:10:10Z");
financingRoundEntities.add(financingRoundEntity(fixedDate.minusDays(100), fixedDate.minusDays(50)));
financingRoundEntities.add(financingRoundEntity(fixedDate.minusDays(40), fixedDate.minusDays(30)));
when(financingRoundRepository.findAll()).thenReturn(financingRoundEntities);
List<UserEntity> userEntities = new ArrayList<>();
userEntities.add(new UserEntity("test1@mail.com"));
userEntities.add(new UserEntity("test2@mail.com"));
mapper.registerModule(new JodaModule());
}
@Test
public void allFinancingRounds() throws Exception {
when(financingRoundService.allFinancingRounds()).thenReturn(Arrays.asList(
new FinancingRound(financingRoundEntities.get(0), null),
new FinancingRound(financingRoundEntities.get(1), null)
));
final MvcResult mvcResult = mockMvc
.perform(get("/financingrounds"))
.andExpect(status().isOk())
.andReturn();
final FinancingRound[] res = mapper.readValue(mvcResult.getResponse().getContentAsString(), FinancingRound[].class);
for (int i = 0; i < res.length; i++) {
assertFinancingRoundsEqual(res[i], new FinancingRound(financingRoundEntities.get(i), null));
}
}
@Test
public void getActive() throws Exception {
final FinancingRound expRound = anExpectedFinancingRound();
when(financingRoundService.currentlyActiveRound()).thenReturn(expRound);
final MvcResult mvcResult = mockMvc
.perform(get("/financingrounds/active"))
.andExpect(status().isOk())
.andReturn();
assertFinancingRoundsEqual(mapper.readValue(mvcResult.getResponse().getContentAsString(), FinancingRound.class), expRound, true);
}
@Test
public void getActive_ShouldReturn404IfNoneIsActive() throws Exception {
when(financingRoundService.currentlyActiveRound()).thenThrow(new ResourceNotFoundException());
mockMvc.perform(get("/financingrounds/active"))
.andExpect(status().isNotFound());
}
@Test
public void getMostRecent() throws Exception {
final FinancingRound expRound = anExpectedFinancingRound();
when(financingRoundService.mostRecentRound()).thenReturn(expRound);
final MvcResult mvcResult = mockMvc
.perform(get("/financingrounds/mostRecent"))
.andExpect(status().isOk())
.andReturn();
assertFinancingRoundsEqual(mapper.readValue(mvcResult.getResponse().getContentAsString(), FinancingRound.class), expRound, true);
}
@Test
public void startFinancingRound() throws Exception {
// create round
final FinancingRound financingRoundCreationCmd = financingRound(new DateTime().plusDays(1), 99);
final FinancingRound expectedFinancingRound = anExpectedFinancingRound();
ArgumentCaptor<FinancingRound> cmdCaptor = ArgumentCaptor.forClass(FinancingRound.class);
when(financingRoundService.startNewFinancingRound(
cmdCaptor.capture())).thenReturn(expectedFinancingRound);
final MvcResult mvcResult = mockMvc.perform(post("/financingrounds")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(financingRoundCreationCmd)))
.andExpect(status().isCreated())
.andReturn();
assertFinancingRoundsEqual(financingRoundCreationCmd, cmdCaptor.getValue() );
final FinancingRound actRes = mapper.readValue(mvcResult.getResponse().getContentAsString(), FinancingRound.class);
assertFinancingRoundsEqual(actRes, expectedFinancingRound);
}
@Test
public void startFinancingRoundEndDateNotInFuture() throws Exception {
// attempt to start a round that ends in the past
final MvcResult mvcResult = mockMvc.perform(post("/financingrounds")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(financingRound(new DateTime(), 99))))
.andExpect(status().isBadRequest())
.andReturn();
verify(financingRoundService, times(0)).startNewFinancingRound(any(FinancingRound.class));
final String contentAsString = mvcResult.getResponse().getContentAsString();
assertThat(contentAsString, containsString("end-date-in-future"));
}
@Test
public void startFinancingRoundBudgetTooLow() throws Exception {
// attempt to create round with 0-budget
final MvcResult mvcResult = mockMvc.perform(post("/financingrounds")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(financingRound(new DateTime().plusDays(1), 0))))
.andExpect(status().isBadRequest())
.andReturn();
verify(financingRoundService, times(0)).startNewFinancingRound(any(FinancingRound.class));
final String contentAsString = mvcResult.getResponse().getContentAsString();
assertThat(contentAsString, containsString("at-least-one-dollar"));
}
@Test
public void startFinancingRoundCollidingRounds() throws Exception {
// create currently running financing round
when(financingRoundRepository.findAll()).thenReturn(Collections.singletonList(financingRoundEntity(new DateTime().minusDays(5), new DateTime().plusDays(1))));
// attempt to create a new (otherwise valid) one
final MvcResult mvcResult = mockMvc.perform(post("/financingrounds")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(financingRound(new DateTime().plusDays(1), 99))))
.andExpect(status().isBadRequest())
.andReturn();
verify(financingRoundService, times(0)).startNewFinancingRound(any(FinancingRound.class));
final String contentAsString = mvcResult.getResponse().getContentAsString();
assertThat(contentAsString, containsString("non-colliding"));
}
@Test
public void stopFinancingRound() throws Exception {
final String roundId = "4711";
FinancingRound expectedFinancingRound = anExpectedFinancingRound();
when(financingRoundService.stopFinancingRound(roundId)).thenReturn(expectedFinancingRound);
// stop round
final MvcResult mvcResult = mockMvc.perform(put("/financingrounds/4711/cancel"))
.andExpect(status().isOk())
.andReturn();
final FinancingRound actRes = mapper.readValue(mvcResult.getResponse().getContentAsString(), FinancingRound.class);
assertFinancingRoundsEqual(actRes, expectedFinancingRound);
}
@Test
public void stopFinancingRoundMissingRound() throws Exception {
doThrow(ResourceNotFoundException.class).when(financingRoundService).stopFinancingRound("4711");
// stop round
mockMvc.perform(put("/financingrounds/4711/cancel"))
.andExpect(status().isNotFound());
}
@Test
public void stopFinancingRoundAlreadyStoppedRound() throws Exception {
when(financingRoundService.stopFinancingRound("4711")).thenAnswer(i -> {
throw InvalidRequestException.financingRoundAlreadyStopped();
});
// stop round
final MvcResult mvcResult = mockMvc.perform(put("/financingrounds/4711/cancel"))
.andExpect(status().isBadRequest())
.andReturn();
assertThat(mvcResult.getResponse().getContentAsString(), is("{\"errorCode\":\"financing_round_already_stopped\",\"fieldViolations\":{}}"));
}
private FinancingRound financingRound(DateTime end, Integer budget) {
FinancingRound financingRound = new FinancingRound();
financingRound.setEndDate(end);
financingRound.setBudget(budget);
return financingRound;
}
private FinancingRound anExpectedFinancingRound() {
return anExpectedFinancingRound("test_id");
}
private FinancingRound anExpectedFinancingRound(String id) {
final FinancingRound res = financingRound(new DateTime().plusDays(1), 99);
res.setActive(true);
res.setId(id);
res.setStartDate(new DateTime());
return res;
}
private FinancingRoundEntity financingRoundEntity(DateTime start, DateTime end) {
FinancingRoundEntity reference = new FinancingRoundEntity();
reference.setStartDate(start);
reference.setEndDate(end);
return reference;
}
private void assertFinancingRoundsEqual(FinancingRound actRes, FinancingRound expectedFinancingRound ) {
assertFinancingRoundsEqual(expectedFinancingRound, actRes, false);
}
private void assertFinancingRoundsEqual(FinancingRound actRes, FinancingRound expectedFinancingRound, boolean isPublicJsonView) {
assertThat(actRes.getId(), is(expectedFinancingRound.getId()));
assertThat(actRes.getStartDate().getMillis(), is(expectedFinancingRound.getStartDate().getMillis()));
assertThat(actRes.getEndDate().getMillis(), is(expectedFinancingRound.getEndDate().getMillis()));
assertThat(actRes.isActive(), is(expectedFinancingRound.isActive()));
assertThat(actRes.getPostRoundBudget(), is(expectedFinancingRound.getPostRoundBudget()));
assertThat(actRes.isPostRoundBudgetDistributable(), is(expectedFinancingRound.isPostRoundBudgetDistributable()));
if(isPublicJsonView){
assertThat(actRes.getBudget(), is(nullValue()));
}else{
assertThat(actRes.getBudget(), is(expectedFinancingRound.getBudget()));
}
}
@Configuration
@EnableWebMvc
static class Config {
@Bean
public ControllerExceptionAdvice controllerExceptionAdvice() {
return new ControllerExceptionAdvice();
}
@Bean
public FinancingRoundController financingRoundController() {
return new FinancingRoundController();
}
@Bean
public FinancingRoundRepository financingRoundRepository() {
return mock(FinancingRoundRepository.class);
}
@Bean
public FinancingRoundService financingRoundService() {
return mock(FinancingRoundService.class);
}
}
}