CartService.java

1
package com.rbctcsworld.ecommerce.cart;
2
3
import com.rbctcsworld.ecommerce.auth.AppUser;
4
import com.rbctcsworld.ecommerce.auth.UserRepository;
5
import com.rbctcsworld.ecommerce.cart.CartDtos.CartLine;
6
import com.rbctcsworld.ecommerce.cart.CartDtos.CartResponse;
7
import com.rbctcsworld.ecommerce.common.exception.BusinessRuleException;
8
import com.rbctcsworld.ecommerce.common.exception.ConflictException;
9
import com.rbctcsworld.ecommerce.common.exception.NotFoundException;
10
import com.rbctcsworld.ecommerce.product.Product;
11
import com.rbctcsworld.ecommerce.product.ProductRepository;
12
import org.springframework.stereotype.Service;
13
import org.springframework.transaction.annotation.Transactional;
14
15
import java.math.BigDecimal;
16
import java.util.List;
17
18
/**
19
 * Business rules:
20
 *  1. Only active products can be added.
21
 *  2. Adding a product already in the cart increases its quantity (no duplicate lines).
22
 *  3. Quantity per line: 1..MAX_QTY_PER_ITEM.
23
 *  4. Quantity may not exceed current stock (409 Conflict).
24
 *  5. A customer can only see/change their own cart items (others' ids give 404).
25
 *  Prices are NOT stored in the cart; the current product price is always shown.
26
 */
27
@Service
28
@Transactional
29
public class CartService {
30
31
    public static final int MAX_QTY_PER_ITEM = 10;
32
33
    private final CartItemRepository items;
34
    private final ProductRepository products;
35
    private final UserRepository users;
36
37
    public CartService(CartItemRepository items, ProductRepository products, UserRepository users) {
38
        this.items = items;
39
        this.products = products;
40
        this.users = users;
41
    }
42
43
    @Transactional(readOnly = true)
44
    public CartResponse getCart(String email) {
45 1 1. getCart : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::getCart → KILLED
        return buildCart(user(email).getId());
46
    }
47
48
    public CartResponse addItem(String email, Long productId, int quantity) {
49
        AppUser user = user(email);
50
        Product product = products.findByIdAndActiveTrue(productId)
51 1 1. lambda$addItem$0 : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::lambda$addItem$0 → KILLED
                .orElseThrow(() -> new NotFoundException("Product not found: " + productId));
52
53
        CartItem item = items.findByUserIdAndProductId(user.getId(), product.getId()).orElse(null);
54 2 1. addItem : removed conditional - replaced equality check with false → KILLED
2. addItem : Replaced integer addition with subtraction → KILLED
        int newQuantity = (item == null ? 0 : item.getQuantity()) + quantity;
55 1 1. addItem : removed call to com/rbctcsworld/ecommerce/cart/CartService::validateQuantity → KILLED
        validateQuantity(product, newQuantity);
56
57 1 1. addItem : removed conditional - replaced equality check with false → KILLED
        if (item == null) {
58
            item = new CartItem(user.getId(), product, newQuantity);
59
        } else {
60 1 1. addItem : removed call to com/rbctcsworld/ecommerce/cart/CartItem::setQuantity → KILLED
            item.setQuantity(newQuantity);
61
        }
62
        items.save(item);
63 1 1. addItem : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::addItem → KILLED
        return buildCart(user.getId());
64
    }
65
66
    public CartResponse updateItem(String email, Long itemId, int quantity) {
67
        AppUser user = user(email);
68
        CartItem item = ownedItem(itemId, user.getId());
69 1 1. updateItem : removed call to com/rbctcsworld/ecommerce/cart/CartService::validateQuantity → KILLED
        validateQuantity(item.getProduct(), quantity);
70 1 1. updateItem : removed call to com/rbctcsworld/ecommerce/cart/CartItem::setQuantity → KILLED
        item.setQuantity(quantity);
71
        items.save(item);
72 1 1. updateItem : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::updateItem → KILLED
        return buildCart(user.getId());
73
    }
74
75
    public CartResponse removeItem(String email, Long itemId) {
76
        AppUser user = user(email);
77 1 1. removeItem : removed call to com/rbctcsworld/ecommerce/cart/CartItemRepository::delete → KILLED
        items.delete(ownedItem(itemId, user.getId()));
78 1 1. removeItem : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::removeItem → KILLED
        return buildCart(user.getId());
79
    }
80
81
    public void clear(String email) {
82 1 1. clear : removed call to com/rbctcsworld/ecommerce/cart/CartItemRepository::deleteByUserId → KILLED
        items.deleteByUserId(user(email).getId());
83
    }
84
85
    private void validateQuantity(Product product, int quantity) {
86 4 1. validateQuantity : changed conditional boundary → KILLED
2. validateQuantity : removed conditional - replaced comparison check with false → KILLED
3. validateQuantity : changed conditional boundary → KILLED
4. validateQuantity : removed conditional - replaced comparison check with false → KILLED
        if (quantity < 1 || quantity > MAX_QTY_PER_ITEM) {
87
            throw new BusinessRuleException("Quantity per item must be between 1 and " + MAX_QTY_PER_ITEM);
88
        }
89 1 1. validateQuantity : removed conditional - replaced equality check with false → KILLED
        if (!product.isActive()) {
90
            throw new NotFoundException("Product not found: " + product.getId());
91
        }
92 2 1. validateQuantity : changed conditional boundary → KILLED
2. validateQuantity : removed conditional - replaced comparison check with false → KILLED
        if (quantity > product.getStock()) {
93
            throw new ConflictException("Insufficient stock for " + product.getSku()
94
                    + ": requested " + quantity + ", available " + product.getStock());
95
        }
96
    }
97
98
    private CartItem ownedItem(Long itemId, Long userId) {
99 1 1. ownedItem : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::ownedItem → KILLED
        return items.findByIdAndUserId(itemId, userId)
100 1 1. lambda$ownedItem$1 : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::lambda$ownedItem$1 → KILLED
                .orElseThrow(() -> new NotFoundException("Cart item not found: " + itemId));
101
    }
102
103
    private AppUser user(String email) {
104 1 1. user : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::user → KILLED
        return users.findByEmail(email)
105 1 1. lambda$user$2 : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::lambda$user$2 → NO_COVERAGE
                .orElseThrow(() -> new NotFoundException("User not found"));
106
    }
107
108
    private CartResponse buildCart(Long userId) {
109
        List<CartLine> lines = items.findByUserIdOrderByIdAsc(userId).stream().map(this::toLine).toList();
110
        int totalQuantity = lines.stream().mapToInt(CartLine::quantity).sum();
111
        BigDecimal subtotal = lines.stream().map(CartLine::lineTotal).reduce(BigDecimal.ZERO, BigDecimal::add);
112 1 1. buildCart : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::buildCart → KILLED
        return new CartResponse(lines, totalQuantity, subtotal);
113
    }
114
115
    private CartLine toLine(CartItem i) {
116
        Product p = i.getProduct();
117
        BigDecimal lineTotal = p.currentPrice().multiply(BigDecimal.valueOf(i.getQuantity()));
118 3 1. toLine : changed conditional boundary → KILLED
2. toLine : removed conditional - replaced comparison check with false → KILLED
3. toLine : removed conditional - replaced equality check with false → KILLED
        boolean available = p.isActive() && p.getStock() >= i.getQuantity();
119 1 1. toLine : replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::toLine → KILLED
        return new CartLine(i.getId(), p.getId(), p.getSku(), p.displayName(), p.currentPrice(),
120
                i.getQuantity(), lineTotal, available);
121
    }
122
}

Mutations

45

1.1
Location : getCart
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:productTakenOffSaleCannotBeUpdatedAndShowsUnavailable()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::getCart → KILLED

51

1.1
Location : lambda$addItem$0
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addInactiveOrUnknownProductIsNotFound()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::lambda$addItem$0 → KILLED

54

1.1
Location : addItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addMoreThanStockIsConflict()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : addItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addMoreThanStockIsConflict()]
Replaced integer addition with subtraction → KILLED

55

1.1
Location : addItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addMoreThanStockIsConflict()]
removed call to com/rbctcsworld/ecommerce/cart/CartService::validateQuantity → KILLED

57

1.1
Location : addItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addNewProductCreatesLine()]
removed conditional - replaced equality check with false → KILLED

60

1.1
Location : addItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addingSameProductAgainMergesQuantity()]
removed call to com/rbctcsworld/ecommerce/cart/CartItem::setQuantity → KILLED

63

1.1
Location : addItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addNewProductCreatesLine()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::addItem → KILLED

69

1.1
Location : updateItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMustBeBetween1And10()]
removed call to com/rbctcsworld/ecommerce/cart/CartService::validateQuantity → KILLED

70

1.1
Location : updateItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMustBeBetween1And10()]
removed call to com/rbctcsworld/ecommerce/cart/CartItem::setQuantity → KILLED

72

1.1
Location : updateItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMustBeBetween1And10()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::updateItem → KILLED

77

1.1
Location : removeItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:removeDeletesTheOwnedLineAndClearEmptiesTheCart()]
removed call to com/rbctcsworld/ecommerce/cart/CartItemRepository::delete → KILLED

78

1.1
Location : removeItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:removeDeletesTheOwnedLineAndClearEmptiesTheCart()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::removeItem → KILLED

82

1.1
Location : clear
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:removeDeletesTheOwnedLineAndClearEmptiesTheCart()]
removed call to com/rbctcsworld/ecommerce/cart/CartItemRepository::deleteByUserId → KILLED

86

1.1
Location : validateQuantity
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMustBeBetween1And10()]
changed conditional boundary → KILLED

2.2
Location : validateQuantity
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:mergedQuantityAboveLimitIsRejected()]
removed conditional - replaced comparison check with false → KILLED

3.3
Location : validateQuantity
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMustBeBetween1And10()]
changed conditional boundary → KILLED

4.4
Location : validateQuantity
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addMoreThanStockIsConflict()]
removed conditional - replaced comparison check with false → KILLED

89

1.1
Location : validateQuantity
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:productTakenOffSaleCannotBeUpdatedAndShowsUnavailable()]
removed conditional - replaced equality check with false → KILLED

92

1.1
Location : validateQuantity
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addingSameProductAgainMergesQuantity()]
changed conditional boundary → KILLED

2.2
Location : validateQuantity
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addMoreThanStockIsConflict()]
removed conditional - replaced comparison check with false → KILLED

99

1.1
Location : ownedItem
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:removeDeletesTheOwnedLineAndClearEmptiesTheCart()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::ownedItem → KILLED

100

1.1
Location : lambda$ownedItem$1
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatingAnotherCustomersItemIsNotFound()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::lambda$ownedItem$1 → KILLED

104

1.1
Location : user
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:addMoreThanStockIsConflict()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::user → KILLED

105

1.1
Location : lambda$user$2
Killed by : none
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::lambda$user$2 → NO_COVERAGE

112

1.1
Location : buildCart
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:removeDeletesTheOwnedLineAndClearEmptiesTheCart()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::buildCart → KILLED

118

1.1
Location : toLine
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMayUseTheLastUnitButNotMore()]
changed conditional boundary → KILLED

2.2
Location : toLine
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMayUseTheLastUnitButNotMore()]
removed conditional - replaced comparison check with false → KILLED

3.3
Location : toLine
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMayUseTheLastUnitButNotMore()]
removed conditional - replaced equality check with false → KILLED

119

1.1
Location : toLine
Killed by : com.rbctcsworld.ecommerce.cart.CartServiceTest.[engine:junit-jupiter]/[class:com.rbctcsworld.ecommerce.cart.CartServiceTest]/[method:updatedQuantityMustBeBetween1And10()]
replaced return value with null for com/rbctcsworld/ecommerce/cart/CartService::toLine → KILLED

Active mutators

Tests examined


Report generated by PIT 1.30.0 support