Implemented Accounts CRUD functionality via REST API. Closes #10

pull/21/head
Niclas Thobaben 2022-02-14 19:00:58 +01:00
parent cbe55fbd5a
commit 6015b06a87
14 changed files with 327 additions and 14 deletions

View File

@ -0,0 +1,87 @@
package de.nclazz.service.mailrelay;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.nclazz.service.mailrelay.adapter.web.RelayAccountForm;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.List;
import static org.hamcrest.core.Is.is;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@ActiveProfiles("integration")
@AutoConfigureMockMvc
public class AccountCrudTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper mapper;
@Autowired
private Relay relay;
private static final LocalDateTime FIXED_TIME = LocalDateTime.of(2019, 4, 25, 13, 12);
@TestConfiguration
static class TestConfig {
@Bean
public Clock clock() {
return FakeClock.fixed(FIXED_TIME);
}
}
@Test
void newAccountCanBeCreatedAndAccessedAfterwards() throws Exception {
RelayAccountForm form = new RelayAccountForm("my-company", List.of("vip@my-company.com"));
mockMvc.perform(post("/accounts").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(form)))
.andExpect(status().isCreated())
.andExpect(content().contentType(APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(jsonPath("$.name").value("my-company"))
.andExpect(jsonPath("$.receivers", is(List.of("vip@my-company.com"))));
}
@Test
void existingAccountCanBeRetrieved() throws Exception {
RelayAccount account = RelayAccount.of("retrievable-account", List.of("info@account.com"));
account = relay.saveAccount(account);
mockMvc.perform(get("/accounts/{guid}", account.getGuid()))
.andExpect(status().is2xxSuccessful())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(jsonPath("$.name").value("retrievable-account"))
.andExpect(jsonPath("$.receivers", is(List.of("info@account.com"))));
}
@Test
void existingAccountCanBeUpdated() throws Exception {
RelayAccount account = RelayAccount.of("updated-account", List.of("info@account.com"));
account = relay.saveAccount(account);
RelayAccountForm form = new RelayAccountForm("updated-account-renamed", List.of("vip@account.com"));
mockMvc.perform(put("/accounts/{guid}", account.getGuid()).contentType(APPLICATION_JSON).content(mapper.writeValueAsString(form)))
.andExpect(status().is2xxSuccessful())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(jsonPath("$.name").value("updated-account-renamed"))
.andExpect(jsonPath("$.receivers", is(List.of("vip@account.com"))));
}
}

View File

@ -47,7 +47,7 @@ public class ForwardMessageFormTest {
@PostConstruct
public void init() {
RelayAccount account = new RelayAccount(UUID.randomUUID(), "company-account", TOKEN, List.of("vip@company.com"));
relay.addAccount(account);
relay.saveAccount(account);
}
}

View File

@ -35,4 +35,9 @@ public class JpaRelayAccountRepositoryAdapter implements RelayAccountRepository
public RelayAccount save(@NonNull RelayAccount account) {
return this.jpaRepository.save(account);
}
@Override
public void deleteByGuid(@NonNull UUID guid) {
this.jpaRepository.deleteById(guid);
}
}

View File

@ -0,0 +1,21 @@
package de.nclazz.service.mailrelay.adapter.web;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RelayAccountForm {
private String name;
private List<String> receivers;
public RelayAccount toAccount() {
return RelayAccount.of(this.name, this.receivers);
}
}

View File

@ -0,0 +1,60 @@
package de.nclazz.service.mailrelay.adapter.web;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("accounts")
@RequiredArgsConstructor
public class RelayAccountRestController {
private final Relay relay;
@PostMapping
public ResponseEntity<RelayAccount> addAccount(@RequestBody RelayAccountForm form) {
RelayAccount account = form.toAccount();
account = this.relay.saveAccount(account);
return ResponseEntity.status(HttpStatus.CREATED).body(account);
}
@GetMapping
public List<RelayAccount> getAllAccounts() {
return this.relay.accounts();
}
@GetMapping("{guid}")
public RelayAccount findByGuid(@PathVariable("guid")UUID guid) {
return this.relay.findAccount(guid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
@DeleteMapping("{guid}")
public RelayAccount deleteByGuid(@PathVariable("guid")UUID guid) {
return this.relay.deleteAccount(guid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
@PutMapping("{guid}")
public RelayAccount updateByGuid(@PathVariable("guid")UUID guid, @RequestBody RelayAccountForm form) {
RelayAccount account = this.relay.findAccount(guid)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
account.setName(form.getName());
account.setReceivers(form.getReceivers());
return this.relay.saveAccount(account);
}
}

View File

@ -1,11 +1,14 @@
package de.nclazz.service.mailrelay.domain;
import com.sun.istack.NotNull;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Data
@Slf4j
@ -27,7 +30,24 @@ public class Relay {
return message;
}
public void addAccount(@NonNull RelayAccount account) {
this.accountRepository.save(account);
public RelayAccount saveAccount(@NonNull RelayAccount account) {
return this.accountRepository.save(account);
}
public List<RelayAccount> accounts() {
return this.accountRepository.findAll();
}
public Optional<RelayAccount> findAccount(@NonNull UUID guid) {
return this.accountRepository.findByGuid(guid);
}
public Optional<RelayAccount> deleteAccount(@NotNull UUID guid) {
Optional<RelayAccount> accountOptional = findAccount(guid);
if(accountOptional.isEmpty()) {
return Optional.empty();
}
this.accountRepository.deleteByGuid(guid);
return accountOptional;
}
}

View File

@ -1,5 +1,6 @@
package de.nclazz.service.mailrelay.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -12,6 +13,7 @@ import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Index;
@ -38,10 +40,11 @@ public class RelayAccount {
private String name;
private String token;
@ElementCollection
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "accounts_receivers")
private List<String> receivers;
@JsonIgnore
@OneToMany
private final List<Message> sentMessages = new ArrayList<>();
@ -50,8 +53,11 @@ public class RelayAccount {
this.sentMessages.add(message);
}
public boolean matchesToken(String token) {
return this.token.equals(token);
public static RelayAccount of(@NonNull String name, @NonNull List<String> receivers) {
RelayAccount account = new RelayAccount();
account.setName(name);
account.setReceivers(receivers);
return account;
}
}

View File

@ -16,5 +16,5 @@ public interface RelayAccountRepository {
RelayAccount save(@NonNull RelayAccount account);
void deleteByGuid(@NonNull UUID guid);
}

View File

@ -27,7 +27,7 @@ class MessageFormControllerTest {
);
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
relay.addAccount(account);
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.of(2020, 12, 24, 12, 54);
Clock fakeClock = FakeClock.fixed(now);

View File

@ -27,7 +27,7 @@ class MessageRestControllerTest {
);
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
relay.addAccount(account);
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.of(2020, 12, 24, 12, 54);
Clock fakeClock = FakeClock.fixed(now);

View File

@ -0,0 +1,106 @@
package de.nclazz.service.mailrelay.adapter.web;
import de.nclazz.service.mailrelay.domain.FakeMessageRepository;
import de.nclazz.service.mailrelay.domain.FakeRelayAccountRepository;
import de.nclazz.service.mailrelay.domain.Relay;
import de.nclazz.service.mailrelay.domain.RelayAccount;
import org.junit.jupiter.api.Test;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@SuppressWarnings("ConstantConditions")
class RelayAccountRestControllerTest {
@Test
void addAccountCreatesNewAccountAndReturnsIs() {
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
RelayAccountRestController controller = new RelayAccountRestController(relay);
RelayAccountForm form = new RelayAccountForm(
"my-account",
List.of("vip@my-company.com", "info@my-company.com")
);
assertThat(controller.addAccount(form))
.extracting("name", "receivers")
.containsExactly("my-account", List.of("vip@my-company.com", "info@my-company.com"));
}
@Test
void addedAccountCanBeAccessedByItsGuid() {
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
RelayAccountRestController controller = new RelayAccountRestController(relay);
RelayAccountForm form = new RelayAccountForm(
"my-account",
List.of("vip@my-company.com", "info@my-company.com")
);
RelayAccount account = controller.addAccount(form).getBody();
assertThat(controller.findByGuid(account.getGuid()))
.isNotNull()
.extracting("name", "receivers")
.containsExactly("my-account", List.of("vip@my-company.com", "info@my-company.com"));
}
@Test
void addedAccountCanBeDeletedAndIsReturned() {
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
RelayAccountRestController controller = new RelayAccountRestController(relay);
RelayAccountForm form = new RelayAccountForm(
"my-account",
List.of("vip@my-company.com", "info@my-company.com")
);
RelayAccount account = controller.addAccount(form).getBody();
assertThat(controller.deleteByGuid(account.getGuid()))
.isNotNull()
.extracting("name", "receivers")
.containsExactly("my-account", List.of("vip@my-company.com", "info@my-company.com"));
}
@Test
void addedAccountCanBeDeletedAndIsNotAccessibleAfterwards() {
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
RelayAccountRestController controller = new RelayAccountRestController(relay);
RelayAccountForm form = new RelayAccountForm(
"my-account",
List.of("vip@my-company.com", "info@my-company.com")
);
RelayAccount account = controller.addAccount(form).getBody();
controller.deleteByGuid(account.getGuid());
assertThatThrownBy(() -> controller.findByGuid(account.getGuid()))
.isInstanceOf(ResponseStatusException.class);
}
@Test
void accountCanBeUpdated() {
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
RelayAccountRestController controller = new RelayAccountRestController(relay);
RelayAccount account = controller.addAccount(
new RelayAccountForm(
"my-account",
List.of("vip@my-company.com")
)
).getBody();
RelayAccountForm form = new RelayAccountForm("my-renamed-account", List.of("vip@my-company.com", "other@my-company.com"));
assertThat(controller.updateByGuid(account.getGuid(), form))
.extracting("name", "receivers")
.containsExactly("my-renamed-account", List.of("vip@my-company.com", "other@my-company.com"));
}
}

View File

@ -8,10 +8,11 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
public class FakeRelayAccountRepository implements RelayAccountRepository {
private final Set<RelayAccount> accounts = new HashSet<>();
private Set<RelayAccount> accounts = new HashSet<>();
@Override
public List<RelayAccount> findAll() {
@ -41,4 +42,11 @@ public class FakeRelayAccountRepository implements RelayAccountRepository {
return account;
}
@Override
public void deleteByGuid(@NonNull UUID guid) {
this.accounts = this.accounts.stream()
.filter(account -> !account.getGuid().equals(guid))
.collect(Collectors.toSet());
}
}

View File

@ -21,7 +21,7 @@ class ForwardMessageTest {
);
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
relay.addAccount(account);
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.now();
Message message = Message.of(
@ -48,7 +48,7 @@ class ForwardMessageTest {
);
Relay relay = new Relay(new FakeRelayAccountRepository(), new FakeMessageRepository());
relay.addAccount(account);
relay.saveAccount(account);
LocalDateTime now = LocalDateTime.now();
Message message = Message.of(

View File

@ -20,7 +20,7 @@ class RelayTest {
FakeRelayAccountRepository repository = new FakeRelayAccountRepository();
Relay relay = new Relay(repository, new FakeMessageRepository());
relay.addAccount(account);
relay.saveAccount(account);
assertThat(repository.findAll())
.hasSize(1)