Add tests
This commit is contained in:
@@ -2,10 +2,8 @@ package demo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableJpaAuditing
|
||||
public class AccountServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -36,6 +36,13 @@ public class Account extends BaseEntity {
|
||||
status = AccountStatus.ACCOUNT_CREATED;
|
||||
}
|
||||
|
||||
public Account(Long userId, String accountNumber, Boolean defaultAccount) {
|
||||
this();
|
||||
this.accountNumber = accountNumber;
|
||||
this.defaultAccount = defaultAccount;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Account(String accountNumber, Boolean defaultAccount, AccountStatus status) {
|
||||
this.accountNumber = accountNumber;
|
||||
this.defaultAccount = defaultAccount;
|
||||
|
||||
@@ -1,89 +1,266 @@
|
||||
package demo.account;
|
||||
|
||||
import demo.event.AccountEvent;
|
||||
import demo.event.AccountEvents;
|
||||
import demo.event.EventController;
|
||||
import demo.event.EventService;
|
||||
import org.springframework.hateoas.LinkBuilder;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/v1")
|
||||
public class AccountController {
|
||||
|
||||
private final AccountService accountService;
|
||||
private final EventService eventService;
|
||||
|
||||
public AccountController(AccountService accountService) {
|
||||
public AccountController(AccountService accountService, EventService eventService) {
|
||||
this.accountService = accountService;
|
||||
this.eventService = eventService;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/accounts")
|
||||
public ResponseEntity createAccount(@RequestBody Account account) {
|
||||
return Optional.ofNullable(accountService.createAccountResource(account))
|
||||
return Optional.ofNullable(createAccountResource(account))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Account creation failed"));
|
||||
}
|
||||
|
||||
@PutMapping(path = "/accounts/{id}")
|
||||
public ResponseEntity updateAccount(@RequestBody Account account, @PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.updateAccountResource(id, account))
|
||||
return Optional.ofNullable(updateAccountResource(id, account))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Account update failed"));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/accounts/{id}")
|
||||
public ResponseEntity getAccount(@PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.getAccountResource(id))
|
||||
return Optional.ofNullable(getAccountResource(id))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/accounts/{id}/events")
|
||||
public ResponseEntity getAccountEvents(@PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.getAccountEventResources(id))
|
||||
return Optional.ofNullable(getAccountEventResources(id))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Could not get account events"));
|
||||
}
|
||||
|
||||
@PostMapping(path = "/accounts/{id}/events")
|
||||
public ResponseEntity createAccount(@PathVariable Long id, @RequestBody AccountEvent event) {
|
||||
return Optional.ofNullable(accountService.appendEventResource(id, event))
|
||||
return Optional.ofNullable(appendEventResource(id, event))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.CREATED))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Append account event failed"));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/accounts/{id}/commands")
|
||||
public ResponseEntity getAccountCommands(@PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.getCommandsResource(id))
|
||||
return Optional.ofNullable(getCommandsResource(id))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new IllegalArgumentException("The account could not be found"));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/accounts/{id}/commands/confirm")
|
||||
public ResponseEntity confirmAccount(@PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.applyCommand(id, AccountCommand.CONFIRM_ACCOUNT))
|
||||
return Optional.ofNullable(getAccountResource(
|
||||
accountService.applyCommand(id, AccountCommand.CONFIRM_ACCOUNT)))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new IllegalArgumentException("The command could not be applied"));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/accounts/{id}/commands/activate")
|
||||
public ResponseEntity activateAccount(@PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.applyCommand(id, AccountCommand.ACTIVATE_ACCOUNT))
|
||||
return Optional.ofNullable(getAccountResource(
|
||||
accountService.applyCommand(id, AccountCommand.ACTIVATE_ACCOUNT)))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new IllegalArgumentException("The command could not be applied"));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/accounts/{id}/commands/suspend")
|
||||
public ResponseEntity suspendAccount(@PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.applyCommand(id, AccountCommand.SUSPEND_ACCOUNT))
|
||||
return Optional.ofNullable(getAccountResource(
|
||||
accountService.applyCommand(id, AccountCommand.SUSPEND_ACCOUNT)))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new IllegalArgumentException("The command could not be applied"));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/accounts/{id}/commands/archive")
|
||||
public ResponseEntity archiveAccount(@PathVariable Long id) {
|
||||
return Optional.ofNullable(accountService.applyCommand(id, AccountCommand.ARCHIVE_ACCOUNT))
|
||||
return Optional.ofNullable(getAccountResource(
|
||||
accountService.applyCommand(id, AccountCommand.ARCHIVE_ACCOUNT)))
|
||||
.map(e -> new ResponseEntity<>(e, HttpStatus.OK))
|
||||
.orElseThrow(() -> new IllegalArgumentException("The command could not be applied"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a hypermedia resource for {@link Account} with the specified identifier.
|
||||
*
|
||||
* @param id is the unique identifier for looking up the {@link Account} entity
|
||||
* @return a hypermedia resource for the fetched {@link Account}
|
||||
*/
|
||||
private Resource<Account> getAccountResource(Long id) {
|
||||
Resource<Account> accountResource = null;
|
||||
|
||||
// Get the account for the provided id
|
||||
Account account = accountService.getAccount(id);
|
||||
|
||||
// If the account exists, wrap the hypermedia response
|
||||
if (account != null)
|
||||
accountResource = getAccountResource(account);
|
||||
|
||||
|
||||
return accountResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Account} entity and persists the result to the repository.
|
||||
*
|
||||
* @param account is the {@link Account} model used to create a new account
|
||||
* @return a hypermedia resource for the newly created {@link Account}
|
||||
*/
|
||||
private Resource<Account> createAccountResource(Account account) {
|
||||
Assert.notNull(account, "Account body must not be null");
|
||||
Assert.notNull(account.getUserId(), "UserId is required");
|
||||
Assert.notNull(account.getAccountNumber(), "AccountNumber is required");
|
||||
Assert.notNull(account.getDefaultAccount(), "DefaultAccount is required");
|
||||
|
||||
// Create the new account
|
||||
account = accountService.createAccount(account);
|
||||
|
||||
return getAccountResource(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a {@link Account} entity for the provided identifier.
|
||||
*
|
||||
* @param id is the unique identifier for the {@link Account} update
|
||||
* @param account is the entity representation containing any updated {@link Account} fields
|
||||
* @return a hypermedia resource for the updated {@link Account}
|
||||
*/
|
||||
private Resource<Account> updateAccountResource(Long id, Account account) {
|
||||
return getAccountResource(accountService.updateAccount(id, account));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an {@link AccountEvent} domain event to the event log of the {@link Account}
|
||||
* aggregate with the specified accountId.
|
||||
*
|
||||
* @param accountId is the unique identifier for the {@link Account}
|
||||
* @param event is the {@link AccountEvent} that attempts to alter the state of the {@link Account}
|
||||
* @return a hypermedia resource for the newly appended {@link AccountEvent}
|
||||
*/
|
||||
private Resource<AccountEvent> appendEventResource(Long accountId, AccountEvent event) {
|
||||
Resource<AccountEvent> eventResource = null;
|
||||
|
||||
event = accountService.appendEvent(accountId, event);
|
||||
|
||||
if (event != null) {
|
||||
eventResource = new Resource<>(event,
|
||||
linkTo(EventController.class)
|
||||
.slash("events")
|
||||
.slash(event.getEventId())
|
||||
.withSelfRel(),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(accountId)
|
||||
.withRel("account")
|
||||
);
|
||||
}
|
||||
|
||||
return eventResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link AccountCommand} hypermedia resource that lists the available commands that can be applied
|
||||
* to an {@link Account} entity.
|
||||
*
|
||||
* @param id is the {@link Account} identifier to provide command links for
|
||||
* @return an {@link AccountCommandsResource} with a collection of embedded command links
|
||||
*/
|
||||
private AccountCommandsResource getCommandsResource(Long id) {
|
||||
// Get the account resource for the identifier
|
||||
Resource<Account> accountResource = getAccountResource(id);
|
||||
|
||||
// Create a new account commands hypermedia resource
|
||||
AccountCommandsResource commandResource = new AccountCommandsResource();
|
||||
|
||||
// Add account command hypermedia links
|
||||
if (accountResource != null) {
|
||||
commandResource.add(
|
||||
getCommandLinkBuilder(id)
|
||||
.slash("confirm")
|
||||
.withRel("confirm"),
|
||||
getCommandLinkBuilder(id)
|
||||
.slash("activate")
|
||||
.withRel("activate"),
|
||||
getCommandLinkBuilder(id)
|
||||
.slash("suspend")
|
||||
.withRel("suspend"),
|
||||
getCommandLinkBuilder(id)
|
||||
.slash("archive")
|
||||
.withRel("archive")
|
||||
);
|
||||
}
|
||||
|
||||
return commandResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link AccountEvents} for the supplied {@link Account} identifier.
|
||||
*
|
||||
* @param id is the unique identifier of the {@link Account}
|
||||
* @return a list of {@link AccountEvent} wrapped in a hypermedia {@link AccountEvents} resource
|
||||
*/
|
||||
private AccountEvents getAccountEventResources(Long id) {
|
||||
return eventService.getEvents(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a {@link LinkBuilder} for generating the {@link AccountCommandsResource}.
|
||||
*
|
||||
* @param id is the unique identifier for a {@link Account}
|
||||
* @return a {@link LinkBuilder} for the {@link AccountCommandsResource}
|
||||
*/
|
||||
private LinkBuilder getCommandLinkBuilder(Long id) {
|
||||
return linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(id)
|
||||
.slash("commands");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a hypermedia enriched {@link Account} entity.
|
||||
*
|
||||
* @param account is the {@link Account} to enrich with hypermedia links
|
||||
* @return is a hypermedia enriched resource for the supplied {@link Account} entity
|
||||
*/
|
||||
private Resource<Account> getAccountResource(Account account) {
|
||||
Resource<Account> accountResource;
|
||||
|
||||
// Prepare hypermedia response
|
||||
accountResource = new Resource<>(account,
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(account.getAccountId())
|
||||
.withSelfRel(),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(account.getAccountId())
|
||||
.slash("events")
|
||||
.withRel("events"),
|
||||
getCommandLinkBuilder(account.getAccountId())
|
||||
.withRel("commands")
|
||||
);
|
||||
|
||||
return accountResource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package demo.account;
|
||||
|
||||
import demo.event.*;
|
||||
import org.springframework.hateoas.LinkBuilder;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import demo.event.AccountEvent;
|
||||
import demo.event.AccountEventType;
|
||||
import demo.event.EventService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -11,7 +11,6 @@ import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static demo.account.AccountStatus.*;
|
||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
|
||||
|
||||
/**
|
||||
* The {@link AccountService} provides transactional support for managing {@link Account}
|
||||
@@ -34,84 +33,6 @@ public class AccountService {
|
||||
this.eventService = eventService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a hypermedia resource for {@link Account} with the specified identifier.
|
||||
*
|
||||
* @param id is the unique identifier for looking up the {@link Account} entity
|
||||
* @return a hypermedia resource for the fetched {@link Account}
|
||||
*/
|
||||
public Resource<Account> getAccountResource(Long id) {
|
||||
Resource<Account> accountResource = null;
|
||||
|
||||
// Get the account for the provided id
|
||||
Account account = getAccount(id);
|
||||
|
||||
// If the account exists, wrap the hypermedia response
|
||||
if (account != null)
|
||||
accountResource = getAccountResource(account);
|
||||
|
||||
|
||||
return accountResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Account} entity and persists the result to the repository.
|
||||
*
|
||||
* @param account is the {@link Account} model used to create a new account
|
||||
* @return a hypermedia resource for the newly created {@link Account}
|
||||
*/
|
||||
public Resource<Account> createAccountResource(Account account) {
|
||||
Assert.notNull(account, "Account body must not be null");
|
||||
Assert.notNull(account.getUserId(), "UserId is required");
|
||||
Assert.notNull(account.getAccountNumber(), "AccountNumber is required");
|
||||
Assert.notNull(account.getDefaultAccount(), "DefaultAccount is required");
|
||||
|
||||
// Create the new account
|
||||
account = createAccount(account);
|
||||
|
||||
return getAccountResource(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a {@link Account} entity for the provided identifier.
|
||||
*
|
||||
* @param id is the unique identifier for the {@link Account} update
|
||||
* @param account is the entity representation containing any updated {@link Account} fields
|
||||
* @return a hypermedia resource for the updated {@link Account}
|
||||
*/
|
||||
public Resource<Account> updateAccountResource(Long id, Account account) {
|
||||
return getAccountResource(updateAccount(id, account));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an {@link AccountEvent} domain event to the event log of the {@link Account}
|
||||
* aggregate with the specified accountId.
|
||||
*
|
||||
* @param accountId is the unique identifier for the {@link Account}
|
||||
* @param event is the {@link AccountEvent} that attempts to alter the state of the {@link Account}
|
||||
* @return a hypermedia resource for the newly appended {@link AccountEvent}
|
||||
*/
|
||||
public Resource<AccountEvent> appendEventResource(Long accountId, AccountEvent event) {
|
||||
Resource<AccountEvent> eventResource = null;
|
||||
|
||||
event = appendEvent(accountId, event);
|
||||
|
||||
if (event != null) {
|
||||
eventResource = new Resource<>(event,
|
||||
linkTo(EventController.class)
|
||||
.slash("events")
|
||||
.slash(event.getEventId())
|
||||
.withSelfRel(),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(accountId)
|
||||
.withRel("account")
|
||||
);
|
||||
}
|
||||
|
||||
return eventResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an {@link AccountCommand} to the {@link Account} with a specified identifier.
|
||||
*
|
||||
@@ -119,21 +40,21 @@ public class AccountService {
|
||||
* @param accountCommand is the command to apply to the {@link Account}
|
||||
* @return a hypermedia resource containing the updated {@link Account}
|
||||
*/
|
||||
public Resource<Account> applyCommand(Long id, AccountCommand accountCommand) {
|
||||
Resource<Account> account = getAccountResource(id);
|
||||
public Account applyCommand(Long id, AccountCommand accountCommand) {
|
||||
Account account = getAccount(id);
|
||||
|
||||
Assert.notNull(account, "The account for the supplied id could not be found");
|
||||
|
||||
AccountStatus status = account.getContent().getStatus();
|
||||
AccountStatus status = account.getStatus();
|
||||
|
||||
switch (accountCommand) {
|
||||
case CONFIRM_ACCOUNT:
|
||||
Assert.isTrue(status == ACCOUNT_PENDING, "The account has already been confirmed");
|
||||
|
||||
// Confirm the account
|
||||
Account updateAccount = account.getContent();
|
||||
Account updateAccount = account;
|
||||
updateAccount.setStatus(ACCOUNT_CONFIRMED);
|
||||
account = updateAccountResource(id, updateAccount);
|
||||
account = updateAccount(id, updateAccount);
|
||||
appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED));
|
||||
break;
|
||||
case ACTIVATE_ACCOUNT:
|
||||
@@ -142,24 +63,24 @@ public class AccountService {
|
||||
.contains(status), "The account cannot be activated");
|
||||
|
||||
// Activate the account
|
||||
account.getContent().setStatus(ACCOUNT_ACTIVE);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
account.setStatus(ACCOUNT_ACTIVE);
|
||||
account = updateAccount(id, account);
|
||||
appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED));
|
||||
break;
|
||||
case SUSPEND_ACCOUNT:
|
||||
Assert.isTrue(status == ACCOUNT_ACTIVE, "An inactive account cannot be suspended");
|
||||
|
||||
// Suspend the account
|
||||
account.getContent().setStatus(ACCOUNT_SUSPENDED);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
account.setStatus(ACCOUNT_SUSPENDED);
|
||||
account = updateAccount(id, account);
|
||||
appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED));
|
||||
break;
|
||||
case ARCHIVE_ACCOUNT:
|
||||
Assert.isTrue(status == ACCOUNT_ACTIVE, "An inactive account cannot be archived");
|
||||
|
||||
// Archive the account
|
||||
account.getContent().setStatus(ACCOUNT_ARCHIVED);
|
||||
account = updateAccountResource(id, account.getContent());
|
||||
account.setStatus(ACCOUNT_ARCHIVED);
|
||||
account = updateAccount(id, account);
|
||||
appendEvent(id, new AccountEvent(AccountEventType.ACCOUNT_ARCHIVED));
|
||||
break;
|
||||
default:
|
||||
@@ -170,90 +91,6 @@ public class AccountService {
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link AccountCommand} hypermedia resource that lists the available commands that can be applied
|
||||
* to an {@link Account} entity.
|
||||
*
|
||||
* @param id is the {@link Account} identifier to provide command links for
|
||||
* @return an {@link AccountCommandsResource} with a collection of embedded command links
|
||||
*/
|
||||
public AccountCommandsResource getCommandsResource(Long id) {
|
||||
// Get the account resource for the identifier
|
||||
Resource<Account> accountResource = getAccountResource(id);
|
||||
|
||||
// Create a new account commands hypermedia resource
|
||||
AccountCommandsResource commandResource = new AccountCommandsResource();
|
||||
|
||||
// Add account command hypermedia links
|
||||
if (accountResource != null) {
|
||||
commandResource.add(
|
||||
getCommandLinkBuilder(id)
|
||||
.slash("confirm")
|
||||
.withRel("confirm"),
|
||||
getCommandLinkBuilder(id)
|
||||
.slash("activate")
|
||||
.withRel("activate"),
|
||||
getCommandLinkBuilder(id)
|
||||
.slash("suspend")
|
||||
.withRel("suspend"),
|
||||
getCommandLinkBuilder(id)
|
||||
.slash("archive")
|
||||
.withRel("archive")
|
||||
);
|
||||
}
|
||||
|
||||
return commandResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link AccountEvents} for the supplied {@link Account} identifier.
|
||||
*
|
||||
* @param id is the unique identifier of the {@link Account}
|
||||
* @return a list of {@link AccountEvent} wrapped in a hypermedia {@link AccountEvents} resource
|
||||
*/
|
||||
public AccountEvents getAccountEventResources(Long id) {
|
||||
return eventService.getEvents(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a {@link LinkBuilder} for generating the {@link AccountCommandsResource}.
|
||||
*
|
||||
* @param id is the unique identifier for a {@link Account}
|
||||
* @return a {@link LinkBuilder} for the {@link AccountCommandsResource}
|
||||
*/
|
||||
private LinkBuilder getCommandLinkBuilder(Long id) {
|
||||
return linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(id)
|
||||
.slash("commands");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a hypermedia enriched {@link Account} entity.
|
||||
*
|
||||
* @param account is the {@link Account} to enrich with hypermedia links
|
||||
* @return is a hypermedia enriched resource for the supplied {@link Account} entity
|
||||
*/
|
||||
private Resource<Account> getAccountResource(Account account) {
|
||||
Resource<Account> accountResource;
|
||||
|
||||
// Prepare hypermedia response
|
||||
accountResource = new Resource<>(account,
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(account.getAccountId())
|
||||
.withSelfRel(),
|
||||
linkTo(AccountController.class)
|
||||
.slash("accounts")
|
||||
.slash(account.getAccountId())
|
||||
.slash("events")
|
||||
.withRel("events"),
|
||||
getCommandLinkBuilder(account.getAccountId())
|
||||
.withRel("commands")
|
||||
);
|
||||
|
||||
return accountResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an {@link Account} entity for the supplied identifier.
|
||||
@@ -261,7 +98,7 @@ public class AccountService {
|
||||
* @param id is the unique identifier of a {@link Account} entity
|
||||
* @return an {@link Account} entity
|
||||
*/
|
||||
private Account getAccount(Long id) {
|
||||
public Account getAccount(Long id) {
|
||||
return accountRepository.findOne(id);
|
||||
}
|
||||
|
||||
@@ -271,7 +108,7 @@ public class AccountService {
|
||||
* @param account is the {@link Account} to create
|
||||
* @return the newly created {@link Account}
|
||||
*/
|
||||
private Account createAccount(Account account) {
|
||||
public Account createAccount(Account account) {
|
||||
// Assert for uniqueness constraint
|
||||
Assert.isNull(accountRepository.findAccountByUserId(account.getUserId()),
|
||||
"An account with the supplied userId already exists");
|
||||
@@ -282,7 +119,7 @@ public class AccountService {
|
||||
account = accountRepository.save(account);
|
||||
|
||||
// Trigger the account creation event
|
||||
appendEventResource(account.getAccountId(),
|
||||
appendEvent(account.getAccountId(),
|
||||
new AccountEvent(AccountEventType.ACCOUNT_CREATED));
|
||||
|
||||
return account;
|
||||
@@ -295,11 +132,11 @@ public class AccountService {
|
||||
* @param account is the {@link Account} containing updated fields
|
||||
* @return the updated {@link Account} entity
|
||||
*/
|
||||
private Account updateAccount(Long id, Account account) {
|
||||
public Account updateAccount(Long id, Account account) {
|
||||
Assert.notNull(id, "Account id must be present in the resource URL");
|
||||
Assert.notNull(account, "Account request body cannot be null");
|
||||
|
||||
if(account.getAccountId() != null) {
|
||||
if (account.getAccountId() != null) {
|
||||
Assert.isTrue(Objects.equals(id, account.getAccountId()),
|
||||
"The account id in the request body must match the resource URL");
|
||||
} else {
|
||||
@@ -322,11 +159,11 @@ public class AccountService {
|
||||
* @param event is the {@link AccountEvent} to append to the {@link Account} entity
|
||||
* @return the newly appended {@link AccountEvent}
|
||||
*/
|
||||
private AccountEvent appendEvent(Long accountId, AccountEvent event) {
|
||||
public AccountEvent appendEvent(Long accountId, AccountEvent event) {
|
||||
Account account = getAccount(accountId);
|
||||
Assert.notNull(account, "The account with the supplied id does not exist");
|
||||
event.setAccount(account);
|
||||
event = eventService.createEvent(event).getContent();
|
||||
event = eventService.createEvent(event);
|
||||
account.getEvents().add(event);
|
||||
accountRepository.save(account);
|
||||
return event;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package demo;
|
||||
package demo.config;
|
||||
|
||||
import demo.account.Account;
|
||||
import demo.account.AccountController;
|
||||
@@ -1,4 +1,4 @@
|
||||
package demo;
|
||||
package demo.config;
|
||||
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.messaging.Source;
|
||||
@@ -0,0 +1,13 @@
|
||||
package demo.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
/**
|
||||
* Enable JPA auditing on an empty configuration class to disable auditing on
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
public class JpaConfig {
|
||||
}
|
||||
@@ -50,23 +50,18 @@ public class EventService {
|
||||
* @param event is the {@link AccountEvent} to publish to the account stream
|
||||
* @return a hypermedia {@link AccountEvent} resource
|
||||
*/
|
||||
public Resource<AccountEvent> createEvent(AccountEvent event) {
|
||||
Resource<AccountEvent> eventResource = null;
|
||||
|
||||
public AccountEvent createEvent(AccountEvent event) {
|
||||
// Save new event
|
||||
event = addEvent(event);
|
||||
Assert.notNull(event, "The event could not be appended to the account");
|
||||
|
||||
// Create account event resource
|
||||
eventResource = getAccountEventResource(event);
|
||||
|
||||
// Append the account event to the stream
|
||||
accountStreamSource.output()
|
||||
.send(MessageBuilder
|
||||
.withPayload(eventResource)
|
||||
.withPayload(getAccountEventResource(event))
|
||||
.build());
|
||||
|
||||
return eventResource;
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,4 +9,6 @@ spring:
|
||||
bindings:
|
||||
output:
|
||||
destination: account
|
||||
contentType: 'application/json'
|
||||
contentType: 'application/json'
|
||||
server:
|
||||
port: 0
|
||||
@@ -1,5 +1,4 @@
|
||||
spring:
|
||||
application:
|
||||
name: account-web
|
||||
server:
|
||||
port: 0
|
||||
---
|
||||
@@ -1,16 +0,0 @@
|
||||
package demo;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class AccountServiceApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package demo.account;
|
||||
|
||||
import demo.event.EventService;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebMvcTest(AccountController.class)
|
||||
public class AccountControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
|
||||
@MockBean
|
||||
private AccountService accountService;
|
||||
|
||||
@MockBean
|
||||
private EventService eventService;
|
||||
|
||||
@Test
|
||||
public void getUserAccountResourceShouldReturnAccount() throws Exception {
|
||||
String content = "{\"userId\": 1, \"accountNumber\": \"123456789\", \"defaultAccount\": true}";
|
||||
|
||||
Account account = new Account(1L, "123456789", true);
|
||||
|
||||
given(this.accountService.getAccount(1L))
|
||||
.willReturn(account);
|
||||
|
||||
this.mvc.perform(get("/v1/accounts/1").accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk()).andExpect(content().json(content));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package demo.account;
|
||||
|
||||
import demo.event.AccountEvent;
|
||||
import demo.event.AccountEventType;
|
||||
import demo.event.EventService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
public class AccountServiceTests {
|
||||
|
||||
@MockBean
|
||||
private EventService eventService;
|
||||
|
||||
@MockBean
|
||||
private AccountRepository accountRepository;
|
||||
|
||||
private AccountService accountService;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
accountService = new AccountService(accountRepository, eventService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAccountReturnsAccount() throws Exception {
|
||||
Account expected = new Account(1L, "123456789", true);
|
||||
expected.setUserId(1L);
|
||||
|
||||
given(this.accountRepository.findOne(1L)).willReturn(expected);
|
||||
|
||||
Account actual = accountService.getAccount(1L);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual.getUserId()).isEqualTo(1L);
|
||||
assertThat(actual.getAccountNumber()).isEqualTo("123456789");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAccountReturnsAccount() throws Exception {
|
||||
Account expected = new Account(1L, "123456789", true);
|
||||
expected.setUserId(1L);
|
||||
Account actual = accountService.createAccount(expected);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual.getStatus()).isEqualTo(AccountStatus.ACCOUNT_CREATED);
|
||||
assertThat(actual.getUserId()).isEqualTo(1L);
|
||||
assertThat(actual.getAccountNumber()).isEqualTo("123456789");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyCommandSuspendsAccount() throws Exception {
|
||||
Account account = new Account(1L, "123456789", true);
|
||||
account.setStatus(AccountStatus.ACCOUNT_ACTIVE);
|
||||
account.setUserId(1L);
|
||||
|
||||
AccountEvent accountEvent = new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED);
|
||||
accountEvent.setAccount(account);
|
||||
accountEvent.setEventId(1L);
|
||||
|
||||
given(this.accountRepository.findOne(1L)).willReturn(account);
|
||||
given(this.accountRepository.save(account)).willReturn(account);
|
||||
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_SUSPENDED)))
|
||||
.willReturn(accountEvent);
|
||||
|
||||
Account actual = accountService.applyCommand(1L, AccountCommand.SUSPEND_ACCOUNT);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual.getStatus()).isEqualTo(AccountStatus.ACCOUNT_SUSPENDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyCommandUnsuspendsAccount() throws Exception {
|
||||
Account account = new Account(1L, "123456789", true);
|
||||
account.setStatus(AccountStatus.ACCOUNT_SUSPENDED);
|
||||
account.setUserId(1L);
|
||||
|
||||
AccountEvent accountEvent = new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED);
|
||||
accountEvent.setAccount(account);
|
||||
accountEvent.setEventId(1L);
|
||||
|
||||
given(this.accountRepository.findOne(1L)).willReturn(account);
|
||||
given(this.accountRepository.save(account)).willReturn(account);
|
||||
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED)))
|
||||
.willReturn(accountEvent);
|
||||
|
||||
Account actual = accountService.applyCommand(1L, AccountCommand.ACTIVATE_ACCOUNT);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual.getStatus()).isEqualTo(AccountStatus.ACCOUNT_ACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyCommandArchivesAccount() throws Exception {
|
||||
Account account = new Account(1L, "123456789", true);
|
||||
account.setStatus(AccountStatus.ACCOUNT_ACTIVE);
|
||||
account.setUserId(1L);
|
||||
|
||||
AccountEvent accountEvent = new AccountEvent(AccountEventType.ACCOUNT_ARCHIVED);
|
||||
accountEvent.setAccount(account);
|
||||
accountEvent.setEventId(1L);
|
||||
|
||||
given(this.accountRepository.findOne(1L)).willReturn(account);
|
||||
given(this.accountRepository.save(account)).willReturn(account);
|
||||
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_ARCHIVED)))
|
||||
.willReturn(accountEvent);
|
||||
|
||||
Account actual = accountService.applyCommand(1L, AccountCommand.ARCHIVE_ACCOUNT);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual.getStatus()).isEqualTo(AccountStatus.ACCOUNT_ARCHIVED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyCommmandUnarchivesAccount() throws Exception {
|
||||
Account account = new Account(1L, "123456789", true);
|
||||
account.setStatus(AccountStatus.ACCOUNT_ARCHIVED);
|
||||
account.setUserId(1L);
|
||||
|
||||
AccountEvent accountEvent = new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED);
|
||||
accountEvent.setAccount(account);
|
||||
accountEvent.setEventId(1L);
|
||||
|
||||
given(this.accountRepository.findOne(1L)).willReturn(account);
|
||||
given(this.accountRepository.save(account)).willReturn(account);
|
||||
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED)))
|
||||
.willReturn(accountEvent);
|
||||
|
||||
Account actual = accountService.applyCommand(1L, AccountCommand.ACTIVATE_ACCOUNT);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual.getStatus()).isEqualTo(AccountStatus.ACCOUNT_ACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyCommmandConfirmsAccount() throws Exception {
|
||||
Account account = new Account(1L, "123456789", true);
|
||||
account.setStatus(AccountStatus.ACCOUNT_PENDING);
|
||||
account.setUserId(1L);
|
||||
|
||||
AccountEvent accountEvent = new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED);
|
||||
accountEvent.setAccount(account);
|
||||
accountEvent.setEventId(1L);
|
||||
|
||||
given(this.accountRepository.findOne(1L)).willReturn(account);
|
||||
given(this.accountRepository.save(account)).willReturn(account);
|
||||
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_CONFIRMED)))
|
||||
.willReturn(accountEvent);
|
||||
|
||||
Account actual = accountService.applyCommand(1L, AccountCommand.CONFIRM_ACCOUNT);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual.getStatus()).isEqualTo(AccountStatus.ACCOUNT_CONFIRMED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyCommmandActivatesAccount() throws Exception {
|
||||
Account account = new Account(1L, "123456789", true);
|
||||
account.setStatus(AccountStatus.ACCOUNT_CONFIRMED);
|
||||
account.setUserId(1L);
|
||||
|
||||
AccountEvent accountEvent = new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED);
|
||||
accountEvent.setAccount(account);
|
||||
accountEvent.setEventId(1L);
|
||||
|
||||
given(this.accountRepository.findOne(1L)).willReturn(account);
|
||||
given(this.accountRepository.save(account)).willReturn(account);
|
||||
given(this.eventService.createEvent(new AccountEvent(AccountEventType.ACCOUNT_ACTIVATED)))
|
||||
.willReturn(accountEvent);
|
||||
|
||||
Account actual = accountService.applyCommand(1L, AccountCommand.ACTIVATE_ACCOUNT);
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual.getStatus()).isEqualTo(AccountStatus.ACCOUNT_ACTIVE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
INSERT INTO ACCOUNT(ID, USER_ID, ACCOUNT_NUMBER, DEFAULT_ACCOUNT) values (1, 1, '123456789', FALSE);
|
||||
Reference in New Issue
Block a user