genofire/hs_monolith
genofire
/
hs_monolith
Archived
1
0
Fork 0

Resolve "Add Reviews"

This commit is contained in:
Matthias Stock 2017-03-29 22:08:39 +00:00
parent 275128c389
commit 36fc1fa8be
22 changed files with 677 additions and 83 deletions

View File

@ -4,6 +4,8 @@ import java.util.List;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
@ -17,6 +19,7 @@ import javax.persistence.Table;
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int ordinal;
@OneToMany(mappedBy = "category")

View File

@ -0,0 +1,122 @@
package de.mstock.monolith.domain;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.springframework.stereotype.Component;
import de.mstock.monolith.web.CategoryDTO;
import de.mstock.monolith.web.ProductDTO;
import de.mstock.monolith.web.ReviewDTO;
@Component
public class DataTransferObjectFactory {
/**
* Creates a Data Transfer Object (DTO).
*
* @param category database entity
* @param locale the requested locale
* @return DTO
*/
public CategoryDTO createCategoryDTO(Category category, Locale locale) {
CategoryI18n i18n = category.getI18n().get(locale.getLanguage());
CategoryDTO categoryDTO = new CategoryDTO();
categoryDTO.setName(i18n.getName());
categoryDTO.setPrettyUrlFragment(i18n.getPrettyUrlFragment());
return categoryDTO;
}
/**
* Creates a Data Transfer Object (DTO).
*
* @param product database entity
* @param locale the requested locale
* @return DTO
*/
public ProductDTO createProductDTO(Product product, Locale locale) {
return createProductDTO(product, locale, NumberFormat.getCurrencyInstance(locale));
}
private ProductDTO createProductDTO(Product product, Locale locale, NumberFormat numberFormat) {
ProductDTO productDTO = createProductWithoutReviewsDTO(product, locale, numberFormat);
ProductI18n i18n = product.getI18n().get(locale.getLanguage());
productDTO.setReviews(createReviewDTOs(i18n.getReviews()));
return productDTO;
}
/**
* Creates Data Transfer Objects (DTOs).
*
* @param products database entities
* @param locale the requested locale
* @return DTOs
*/
public List<ProductDTO> createProductDTOs(List<Product> products, Locale locale) {
List<ProductDTO> productDTOs = new ArrayList<>(products.size());
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
for (Product product : products) {
productDTOs.add(createProductDTO(product, locale, numberFormat));
}
return productDTOs;
}
/**
* Creates Data Transfer Objects (DTOs) without loading their reviews.
*
* @param products database entities
* @param locale the requested locale
* @return DTOs
*/
public List<ProductDTO> createProductWithoutReviewsDTOs(List<Product> products, Locale locale) {
List<ProductDTO> productDTOs = new ArrayList<>(products.size());
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
for (Product product : products) {
productDTOs.add(createProductWithoutReviewsDTO(product, locale, numberFormat));
}
return productDTOs;
}
private ProductDTO createProductWithoutReviewsDTO(Product product, Locale locale,
NumberFormat numberFormat) {
ProductI18n i18n = product.getI18n().get(locale.getLanguage());
String price = numberFormat.format(i18n.getPrice());
ProductDTO productDTO = new ProductDTO();
productDTO.setItemNumber(product.getItemNumber());
productDTO.setUnit(product.getUnit());
productDTO.setName(i18n.getName());
productDTO.setPrettyUrlFragment(i18n.getPrettyUrlFragment());
productDTO.setPrice(price);
productDTO.setDescription(i18n.getDescription());
return productDTO;
}
/**
* Creates a Data Transfer Object (DTO).
*
* @param review database entity
* @return DTO
*/
public ReviewDTO createReviewDTO(Review review) {
ReviewDTO dto = new ReviewDTO();
dto.setLanguage(review.getLocaleLanguage());
dto.setRatingStars(review.getRatingStars());
dto.setFirstName(review.getFirstName());
dto.setLastName(review.getLastName());
dto.setText(review.getText());
return dto;
}
private List<ReviewDTO> createReviewDTOs(List<Review> reviews) {
List<ReviewDTO> ratingDTOs = new ArrayList<>(reviews.size());
for (Review review : reviews) {
ratingDTOs.add(createReviewDTO(review));
}
return Collections.unmodifiableList(ratingDTOs);
}
}

View File

@ -5,6 +5,8 @@ import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
@ -15,6 +17,7 @@ import javax.persistence.Table;
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String itemNumber;
@Enumerated(EnumType.ORDINAL)

View File

@ -1,11 +1,15 @@
package de.mstock.monolith.domain;
import java.math.BigDecimal;
import java.util.List;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@ -21,6 +25,9 @@ public class ProductI18n {
private String prettyUrlFragment;
private BigDecimal price;
private String description;
@OneToMany
@JoinColumns({@JoinColumn(name = "localeLanguage"), @JoinColumn(name = "productId")})
private List<Review> reviews;
public ProductI18nPk getProductI18nPk() {
return productI18nPk;
@ -61,4 +68,12 @@ public class ProductI18n {
public void setDescription(String description) {
this.description = description;
}
public List<Review> getReviews() {
return reviews;
}
public void setReviews(List<Review> reviews) {
this.reviews = reviews;
}
}

View File

@ -5,7 +5,7 @@ import org.springframework.data.repository.Repository;
public interface ProductRepository extends Repository<Product, Integer> {
@Query("select p from Product p join fetch p.i18n i "
@Query("select p from Product p join fetch p.i18n i left join fetch i.reviews r "
+ "where key(i) = ?1 and lower(i.name) = ?2")
Product findByI18nName(String language, String name);

View File

@ -0,0 +1,79 @@
package de.mstock.monolith.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "reviews")
public class Review {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int productId;
private String localeLanguage;
private String firstName;
private String lastName;
private int ratingStars;
private String text;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public String getLocaleLanguage() {
return localeLanguage;
}
public void setLocaleLanguage(String localeLanguage) {
this.localeLanguage = localeLanguage;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getRatingStars() {
return ratingStars;
}
public void setRatingStars(int ratingStars) {
this.ratingStars = ratingStars;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}

View File

@ -0,0 +1,7 @@
package de.mstock.monolith.domain;
import org.springframework.data.repository.CrudRepository;
public interface ReviewRepository extends CrudRepository<Review, Integer> {
}

View File

@ -0,0 +1,47 @@
package de.mstock.monolith.service;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.mstock.monolith.domain.DataTransferObjectFactory;
import de.mstock.monolith.domain.Product;
import de.mstock.monolith.domain.ProductRepository;
import de.mstock.monolith.domain.Review;
import de.mstock.monolith.domain.ReviewRepository;
import de.mstock.monolith.web.ReviewDTO;
import de.mstock.monolith.web.form.ReviewForm;
@Service
public class ReviewService {
@Autowired
private ReviewRepository reviewRepository;
@Autowired
private ProductRepository productRepository;
@Autowired
private DataTransferObjectFactory dtoFactory;
/**
* Stores a review from a posted form.
*
* @param reviewForm Post data
* @param locale Language context
* @param prettyUrlFragment Used to get the product context
* @return DTO
*/
public ReviewDTO saveReview(ReviewForm reviewForm, Locale locale, String prettyUrlFragment) {
Product product = productRepository.findByI18nName(locale.getLanguage(), prettyUrlFragment);
Review review = new Review();
review.setProductId(product.getId());
review.setLocaleLanguage(locale.getLanguage());
review.setFirstName(reviewForm.getFirstName());
review.setLastName(reviewForm.getLastName());
review.setRatingStars(reviewForm.getRatingStars());
review.setText(reviewForm.getText());
return dtoFactory.createReviewDTO(reviewRepository.save(review));
}
}

View File

@ -1,6 +1,5 @@
package de.mstock.monolith.service;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -10,10 +9,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.mstock.monolith.domain.Category;
import de.mstock.monolith.domain.CategoryI18n;
import de.mstock.monolith.domain.CategoryRepository;
import de.mstock.monolith.domain.DataTransferObjectFactory;
import de.mstock.monolith.domain.Product;
import de.mstock.monolith.domain.ProductI18n;
import de.mstock.monolith.domain.ProductRepository;
import de.mstock.monolith.web.CategoryDTO;
import de.mstock.monolith.web.ProductDTO;
@ -27,6 +25,9 @@ public class ShopService {
@Autowired
private ProductRepository productRepository;
@Autowired
private DataTransferObjectFactory dtoFactory;
/**
* Gets all categories of the current language.
*
@ -36,8 +37,7 @@ public class ShopService {
String language = locale.getLanguage();
List<CategoryDTO> categories = new ArrayList<>();
for (Category category : categoryRepository.findAllOrdered(language)) {
CategoryI18n i18n = category.getI18n().get(language);
categories.add(new CategoryDTO(i18n.getName(), i18n.getPrettyUrlFragment()));
categories.add(dtoFactory.createCategoryDTO(category, locale));
}
return Collections.unmodifiableList(categories);
}
@ -53,33 +53,22 @@ public class ShopService {
if (category == null) {
throw new NotFoundException();
}
List<ProductDTO> products = new ArrayList<>();
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(locale);
for (Product product : category.getProducts()) {
ProductI18n i18n = product.getI18n().get(language);
String price = currencyFormat.format(i18n.getPrice());
products.add(new ProductDTO(product.getItemNumber(), product.getUnit(), i18n.getName(),
i18n.getPrettyUrlFragment(), price, i18n.getDescription()));
}
List<ProductDTO> products =
dtoFactory.createProductWithoutReviewsDTOs(category.getProducts(), locale);
return Collections.unmodifiableList(products);
}
/**
* Gets a products in the current language.
* Gets a product in the current language.
*
* @return A simplified Data Transfer Object.
*/
public ProductDTO getProduct(Locale locale, String prettyUrlFragment) {
String language = locale.getLanguage();
Product product = productRepository.findByI18nName(language, prettyUrlFragment);
Product product = productRepository.findByI18nName(locale.getLanguage(), prettyUrlFragment);
if (product == null) {
throw new NotFoundException();
}
ProductI18n i18n = product.getI18n().get(language);
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(locale);
String price = currencyFormat.format(i18n.getPrice());
return new ProductDTO(product.getItemNumber(), product.getUnit(), i18n.getName(),
i18n.getPrettyUrlFragment(), price, i18n.getDescription());
return dtoFactory.createProductDTO(product, locale);
}
}

View File

@ -2,20 +2,23 @@ package de.mstock.monolith.web;
public class CategoryDTO {
private final String name;
private final String prettyUrlFragment;
public CategoryDTO(String name, String prettyUrlFragment) {
this.name = name;
this.prettyUrlFragment = prettyUrlFragment;
}
private String name;
private String prettyUrlFragment;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPrettyUrlFragment() {
return prettyUrlFragment;
}
public void setPrettyUrlFragment(String prettyUrlFragment) {
this.prettyUrlFragment = prettyUrlFragment;
}
}

View File

@ -2,14 +2,19 @@ package de.mstock.monolith.web;
import java.util.Locale;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import de.mstock.monolith.service.ReviewService;
import de.mstock.monolith.service.ShopService;
import de.mstock.monolith.web.form.ReviewForm;
@Controller
public class ProductController {
@ -19,6 +24,9 @@ public class ProductController {
@Autowired
private ShopService shopService;
@Autowired
private ReviewService reviewService;
/**
* Product page
*
@ -28,7 +36,32 @@ public class ProductController {
* @return The template's name.
*/
@RequestMapping(value = "/products/{prettyUrlFragment:[\\w-]+}", method = RequestMethod.GET)
public String homepage(@PathVariable String prettyUrlFragment, Model model, Locale locale) {
public String product(@PathVariable String prettyUrlFragment, Model model, Locale locale) {
model.addAttribute("categories", shopService.getCategories(locale));
model.addAttribute("product", shopService.getProduct(locale, prettyUrlFragment));
return TEMPLATE;
}
/**
* Post a review
*
* @param reviewForm Form data
* @param bindingResult Form binding result after validation
* @param prettyUrlFragment Product context
* @param model Template model
* @param locale Language context
* @return The template's name.
*/
@RequestMapping(value = "/products/{prettyUrlFragment:[\\w-]+}", method = RequestMethod.POST)
public String post(@Valid ReviewForm reviewForm, BindingResult bindingResult,
@PathVariable String prettyUrlFragment, Model model, Locale locale) {
if (bindingResult.hasErrors()) {
model.addAttribute("success", false);
} else {
model.addAttribute("success", true);
model.addAttribute("reviewPost",
reviewService.saveReview(reviewForm, locale, prettyUrlFragment));
}
model.addAttribute("categories", shopService.getCategories(locale));
model.addAttribute("product", shopService.getProduct(locale, prettyUrlFragment));
return TEMPLATE;

View File

@ -1,57 +1,73 @@
package de.mstock.monolith.web;
import java.util.List;
import de.mstock.monolith.domain.ProductWeightUnit;
public class ProductDTO {
private final String itemNumber;
private final ProductWeightUnit unit;
private final String name;
private final String prettyUrlFragment;
private final String price;
private final String description;
/**
* A simplified representation of a product.
*
* @param itemNumber A product's unique item number.
* @param unit The unit of a product that relates to its price.
* @param name The localized name.
* @param price The product's price per unit.
* @param description The description of the product.
*/
public ProductDTO(String itemNumber, ProductWeightUnit unit, String name,
String prettyUrlFragment, String price, String description) {
this.itemNumber = itemNumber;
this.unit = unit;
this.name = name;
this.prettyUrlFragment = prettyUrlFragment;
this.price = price;
this.description = description;
}
private String itemNumber;
private ProductWeightUnit unit;
private String name;
private String prettyUrlFragment;
private String price;
private String description;
private List<ReviewDTO> reviews;
public String getItemNumber() {
return itemNumber;
}
public void setItemNumber(String itemNumber) {
this.itemNumber = itemNumber;
}
public ProductWeightUnit getUnit() {
return unit;
}
public void setUnit(ProductWeightUnit unit) {
this.unit = unit;
}
public String getName() {
return name;
}
public String getPrice() {
return price;
}
public String getDescription() {
return description;
public void setName(String name) {
this.name = name;
}
public String getPrettyUrlFragment() {
return prettyUrlFragment;
}
public void setPrettyUrlFragment(String prettyUrlFragment) {
this.prettyUrlFragment = prettyUrlFragment;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<ReviewDTO> getReviews() {
return reviews;
}
public void setReviews(List<ReviewDTO> reviews) {
this.reviews = reviews;
}
}

View File

@ -0,0 +1,66 @@
package de.mstock.monolith.web;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
public class ReviewDTO {
private Locale locale;
private String firstName;
private String lastName;
private int ratingStars;
private String text;
public String getLanguage() {
return locale.getLanguage();
}
public void setLanguage(String language) {
this.locale = new Locale(language);
}
/**
* Presentation layer can use this method to access the full name of the reviewer.
*
* @return concatenated name
*/
public String getDisplayName() {
if (StringUtils.isNoneBlank(firstName, lastName)) {
return firstName + " " + lastName.charAt(0) + ".";
}
return StringUtils.defaultString(firstName);
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getRatingStars() {
return ratingStars;
}
public void setRatingStars(int ratingStars) {
this.ratingStars = ratingStars;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}

View File

@ -0,0 +1,53 @@
package de.mstock.monolith.web.form;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class ReviewForm {
@Size(max = 80)
private String firstName;
@Size(max = 80)
private String lastName;
@Min(1)
@Max(5)
@NotNull
private int ratingStars;
@Size(max = 1000)
private String text;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getRatingStars() {
return ratingStars;
}
public void setRatingStars(int ratingStars) {
this.ratingStars = ratingStars;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}

View File

@ -0,0 +1,9 @@
alter table categories_i18n
add constraint categories_i18n_category_id_fkey foreign key (category_id)
references categories
on delete cascade;
alter table products_i18n
add constraint products_i18n_product_id_fkey foreign key (product_id)
references products
on delete cascade;

View File

@ -0,0 +1,18 @@
create table reviews (
id serial primary key,
product_id integer,
locale_language varchar(2) not null,
first_name varchar(80),
last_name varchar(80),
rating_stars integer not null check (rating_stars between 1 and 5),
text varchar(1000),
foreign key (product_id, locale_language) references products_i18n(product_id, locale_language)
);
insert into reviews (id, product_id, locale_language, first_name, last_name, rating_stars, text) values
(1, 1, 'en', 'John', 'Doe', 5, 'Absolutely perfect!'),
(2, 1, 'de', 'Max', 'Mustermann', 3, null),
(3, 1, 'de', 'Erika', 'Mustermann', 4, 'Ich liebe dieses Produkt! Leider ist ab und zu auch eine matschige Kiwi dabei.'),
(4, 4, 'de', 'Otto', 'Normalverbraucher', 4, 'Genau das was ich gesucht habe!'),
(5, 5, 'de', null, null, 1, 'Schmeckt nicht!'),
(6, 5, 'en', 'John', null, 2, 'What''s the country of origin of these tomatoes?');

View File

@ -0,0 +1,3 @@
alter sequence categories_id_seq restart with 100;
alter sequence products_id_seq restart with 100;
alter sequence reviews_id_seq restart with 100;

View File

@ -15,3 +15,6 @@ features.headline2 = Das ist gut.
features.subheadline2 = Sieh' selbst.
features.headline3 = Und zum Schluss, das hier.
features.subheadline3 = Bingo.
reviews.title = Rezensionen
validation.review.success = Danke für deine Rezension!
validation.error = Es ist ein Fehler aufgetreten!

View File

@ -15,3 +15,6 @@ features.headline2 = Oh yeah, it's that good.
features.subheadline2 = See for yourself.
features.headline3 = And lastly, this one.
features.subheadline3 = Checkmate.
reviews.title = Reviews
validation.review.success = Thanks for your review!
validation.error = An error occured!

View File

@ -11,15 +11,66 @@
<div class="container">
<div class="row reviews" th:fragment="reviews">
<div class="col-md-12">
<h2 th:inline="text">Reviews</h2>
<div class="review">
<h2 th:text="#{reviews.title}">Reviews</h2>
<div th:if="${success != null and success}" class="alert alert-success" role="alert">
<p th:text="#{validation.review.success}">Thanks for your review!</p>
<p>
<span class="rating"></span> Great product!
<span class="rating" th:switch="${reviewPost.ratingStars}">
<th:block th:case="1"></th:block>
<th:block th:case="2">★★</th:block>
<th:block th:case="3">★★★</th:block>
<th:block th:case="4">★★★★</th:block>
<th:block th:case="5">★★★★★</th:block>
</span> <span th:text="${reviewPost.displayName}">John D.</span>
</p>
<p>John D.</p>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing
elitr, sed diam nonumy eirmod tempor invidunt ut labore et
dolore magna aliquyam.</p>
<p th:text="${reviewPost.text}">Lorem ipsum</p>
</div>
<div th:if="${success != null and !success}" th:text="#{validation.error}" class="alert alert-danger" role="alert">An error occured!</div>
<form th:if="${success == null}" class="form-horizontal" action="#" th:action="@{/products/__${product.prettyUrlFragment}__.html}" th:object="${reviewPost}" method="post">
<div class="form-group">
<label for="firstName" class="col-sm-2 control-label">First Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="firstName" name="firstName" />
</div>
</div>
<div class="form-group">
<label for="lastName" class="col-sm-2 control-label">Last Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="lastName" name="lastName" />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Rating</label>
<div class="col-sm-10">
<input type="radio" id="ratingStars1" name="ratingStars" value="1" checked="checked" />&nbsp;<label for="ratingStars1"></label>
<input type="radio" id="ratingStars2" name="ratingStars" value="2" />&nbsp;<label for="ratingStars2">★★</label>
<input type="radio" id="ratingStars3" name="ratingStars" value="3" />&nbsp;<label for="ratingStars3">★★★</label>
<input type="radio" id="ratingStars4" name="ratingStars" value="4" />&nbsp;<label for="ratingStars4">★★★★</label>
<input type="radio" id="ratingStars5" name="ratingStars" value="5" />&nbsp;<label for="ratingStars5">★★★★★</label>
</div>
</div>
<div class="form-group">
<label for="text" class="col-sm-2 control-label">Text</label>
<div class="col-sm-10">
<textarea class="form-control" id="text" name="text"></textarea>
</div>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<hr />
<div class="review" th:each="review : ${product.reviews}">
<p>
<span class="rating" th:switch="${review.ratingStars}">
<th:block th:case="1"></th:block>
<th:block th:case="2">★★</th:block>
<th:block th:case="3">★★★</th:block>
<th:block th:case="4">★★★★</th:block>
<th:block th:case="5">★★★★★</th:block>
</span> <span th:text="${review.displayName}">John D.</span>
</p>
<p th:text="${review.text}">Lorem ipsum dolor sit amet,
consetetur sadipscing elitr, sed diam nonumy eirmod tempor
invidunt ut labore et dolore magna aliquyam.</p>
</div>
</div>
</div>

View File

@ -0,0 +1,79 @@
package de.mstock.monolith.domain;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner;
import de.mstock.monolith.web.ProductDTO;
@RunWith(MockitoJUnitRunner.class)
public class DataTransferObjectFactoryTest {
@InjectMocks
private DataTransferObjectFactory dtoFactory;
@Test
public void shouldCreateProductDTOWithoutReviews() {
Product product = mock(Product.class, RETURNS_DEEP_STUBS);
ProductI18n productI18n = mock(ProductI18n.class);
when(product.getI18n().get(any())).thenReturn(productI18n);
when(productI18n.getPrice()).thenReturn(BigDecimal.valueOf(1.23));
verify(productI18n, never()).getReviews();
List<ProductDTO> productDTOs =
dtoFactory.createProductWithoutReviewsDTOs(Arrays.asList(product), new Locale("de"));
assertThat("The review list is not present", productDTOs.get(0).getReviews(), is(nullValue()));
}
@Test
public void shouldCreateProductDTOWithReviews() {
Product product = mock(Product.class, RETURNS_DEEP_STUBS);
Review review = mock(Review.class);
when(review.getLocaleLanguage()).thenReturn("de");
when(product.getI18n().get(any()).getPrice()).thenReturn(BigDecimal.valueOf(1.23));
when(product.getI18n().get(any()).getReviews()).thenReturn(Arrays.asList(review));
List<ProductDTO> productDTOs =
dtoFactory.createProductDTOs(Arrays.asList(product), new Locale("de"));
assertThat("The review list is filled", productDTOs.get(0).getReviews(), is(not(empty())));
}
@Test
public void shouldCreateProductDTOWithEmptyReviews() {
Product product = mock(Product.class, RETURNS_DEEP_STUBS);
when(product.getI18n().get(any()).getPrice()).thenReturn(BigDecimal.valueOf(1.23));
when(product.getI18n().get(any()).getReviews()).thenReturn(Collections.emptyList());
List<ProductDTO> productDTOs =
dtoFactory.createProductDTOs(Arrays.asList(product), new Locale("de"));
assertThat("The review list is present, but empty", productDTOs.get(0).getReviews(),
is(empty()));
}
@Test
public void shouldFormatPrice() {
Locale locale = new Locale("en", "US");
Product product = mock(Product.class, RETURNS_DEEP_STUBS);
when(product.getI18n().get(anyString()).getPrice()).thenReturn(BigDecimal.valueOf(1.47));
ProductDTO productDTO = dtoFactory.createProductDTO(product, locale);
assertThat("Product has a formatted price", productDTO.getPrice(), is(equalTo("$1.47")));
}
}

View File

@ -3,13 +3,12 @@ package de.mstock.monolith.service;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@ -22,10 +21,9 @@ import org.mockito.runners.MockitoJUnitRunner;
import de.mstock.monolith.domain.Category;
import de.mstock.monolith.domain.CategoryRepository;
import de.mstock.monolith.domain.Product;
import de.mstock.monolith.domain.DataTransferObjectFactory;
import de.mstock.monolith.domain.ProductRepository;
import de.mstock.monolith.web.CategoryDTO;
import de.mstock.monolith.web.ProductDTO;
@RunWith(MockitoJUnitRunner.class)
public class ShopServiceTest {
@ -36,6 +34,9 @@ public class ShopServiceTest {
@Mock
private ProductRepository productRepository;
@Mock
private DataTransferObjectFactory dataTransferObjectFactory;
@InjectMocks
private ShopService shopService;
@ -45,20 +46,11 @@ public class ShopServiceTest {
Category category = mock(Category.class, RETURNS_DEEP_STUBS);
List<Category> categoryEntities = Arrays.asList(category, category, category);
when(categoryRepository.findAllOrdered(eq(locale.getLanguage()))).thenReturn(categoryEntities);
when(dataTransferObjectFactory.createCategoryDTO(any(), any()))
.thenReturn(mock(CategoryDTO.class));
List<CategoryDTO> categories = shopService.getCategories(locale);
assertThat("Same amount of categories", categories.size(),
is(equalTo(categoryEntities.size())));
}
@Test
public void shouldFormatPrice() {
Locale locale = new Locale("en", "US");
Product product = mock(Product.class, RETURNS_DEEP_STUBS);
when(product.getI18n().get(anyString()).getPrice()).thenReturn(BigDecimal.valueOf(1.47));
when(productRepository.findByI18nName(eq(locale.getLanguage()), anyString()))
.thenReturn(product);
ProductDTO productDTO = shopService.getProduct(locale, "foo");
assertThat("Product has a formatted price", productDTO.getPrice(), is(equalTo("$1.47")));
}
}