diff --git a/pom.xml b/pom.xml
index 543c1aa50..c01098191 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,7 +16,7 @@
org.junit.jupiter
junit-jupiter
- 5.11.4
+ 6.0.1
test
@@ -28,9 +28,14 @@
org.mockito
mockito-junit-jupiter
- 5.15.2
+ 5.21.0
test
+
+ org.awaitility
+ awaitility
+ 4.3.0
+
@@ -57,6 +62,19 @@
+
+ org.pitest
+ pitest-maven
+ 1.22.0
+
+
+ org.pitest
+ pitest-junit5-plugin
+ 1.2.2
+
+
+
+
org.jacoco
jacoco-maven-plugin
diff --git a/src/main/java/com/example/payment/DatabaseConnection.java b/src/main/java/com/example/payment/DatabaseConnection.java
new file mode 100644
index 000000000..22b856e38
--- /dev/null
+++ b/src/main/java/com/example/payment/DatabaseConnection.java
@@ -0,0 +1,5 @@
+package com.example.payment;
+
+public interface DatabaseConnection {
+ void executeUpdate(String query);
+}
diff --git a/src/main/java/com/example/payment/EmailService.java b/src/main/java/com/example/payment/EmailService.java
new file mode 100644
index 000000000..e748c2fa2
--- /dev/null
+++ b/src/main/java/com/example/payment/EmailService.java
@@ -0,0 +1,5 @@
+package com.example.payment;
+
+public interface EmailService {
+ void sendPaymentConfirmation(String email, double amount);
+}
diff --git a/src/main/java/com/example/payment/PaymentApi.java b/src/main/java/com/example/payment/PaymentApi.java
new file mode 100644
index 000000000..5f741d58c
--- /dev/null
+++ b/src/main/java/com/example/payment/PaymentApi.java
@@ -0,0 +1,5 @@
+package com.example.payment;
+
+public interface PaymentApi {
+ PaymentApiResponse charge(String apiKey, double amount);
+}
diff --git a/src/main/java/com/example/payment/PaymentApiResponse.java b/src/main/java/com/example/payment/PaymentApiResponse.java
new file mode 100644
index 000000000..208d2b111
--- /dev/null
+++ b/src/main/java/com/example/payment/PaymentApiResponse.java
@@ -0,0 +1,4 @@
+package com.example.payment;
+
+public record PaymentApiResponse(boolean success) {
+}
diff --git a/src/main/java/com/example/payment/PaymentProcessor.java b/src/main/java/com/example/payment/PaymentProcessor.java
index 137bab7d9..de22a6562 100644
--- a/src/main/java/com/example/payment/PaymentProcessor.java
+++ b/src/main/java/com/example/payment/PaymentProcessor.java
@@ -1,23 +1,32 @@
package com.example.payment;
-//public class PaymentProcessor {
-// private static final String API_KEY = "sk_test_123456";
-//
-// public boolean processPayment(double amount) {
-// // Anropar extern betaltjänst direkt med statisk API-nyckel
-// PaymentApiResponse response = PaymentApi.charge(API_KEY, amount);
-//
-// // Skriver till databas direkt
-// if (response.isSuccess()) {
-// DatabaseConnection.getInstance()
-// .executeUpdate("INSERT INTO payments (amount, status) VALUES (" + amount + ", 'SUCCESS')");
-// }
-//
-// // Skickar e-post direkt
-// if (response.isSuccess()) {
-// EmailService.sendPaymentConfirmation("user@example.com", amount);
-// }
-//
-// return response.isSuccess();
-// }
-//}
+public class PaymentProcessor {
+ private static final String API_KEY = "sk_test_123456";
+ private final DatabaseConnection databaseConnection;
+ private final EmailService emailService;
+ private final PaymentApi paymentApi;
+
+ public PaymentProcessor(DatabaseConnection databaseConnection, EmailService emailService, PaymentApi paymentApi) {
+ this.databaseConnection = databaseConnection;
+ this.emailService = emailService;
+ this.paymentApi = paymentApi;
+ }
+
+ public boolean processPayment(double amount) {
+ // Anropar extern betaltjänst direkt med statisk API-nyckel
+ PaymentApiResponse response = this.paymentApi.charge(API_KEY, amount);
+
+ // Skriver till databas direkt
+ if (response.success()) {
+ this.databaseConnection
+ .executeUpdate("INSERT INTO payments (amount, status) VALUES (" + amount + ", 'SUCCESS')");
+ }
+
+ // Skickar e-post direkt
+ if (response.success()) {
+ emailService.sendPaymentConfirmation("user@example.com", amount);
+ }
+
+ return response.success();
+ }
+}
diff --git a/src/main/java/com/example/shop/Discount.java b/src/main/java/com/example/shop/Discount.java
new file mode 100644
index 000000000..5464e4d69
--- /dev/null
+++ b/src/main/java/com/example/shop/Discount.java
@@ -0,0 +1,18 @@
+package com.example.shop;
+
+public class Discount {
+ private int percentage;
+
+ public Discount(int percentage) {
+ this.percentage = percentage;
+ }
+
+ public double applyDiscount(double totalPrice){
+ double result = totalPrice - (totalPrice * ((double) this.percentage /100));
+ return Math.max(0, result);
+ }
+
+ public int getPercentage() {
+ return percentage;
+ }
+}
diff --git a/src/main/java/com/example/shop/DiscountProvider.java b/src/main/java/com/example/shop/DiscountProvider.java
new file mode 100644
index 000000000..9b631d67a
--- /dev/null
+++ b/src/main/java/com/example/shop/DiscountProvider.java
@@ -0,0 +1,5 @@
+package com.example.shop;
+
+public interface DiscountProvider {
+ double applyDiscount(double totalPrice);
+}
diff --git a/src/main/java/com/example/shop/Item.java b/src/main/java/com/example/shop/Item.java
new file mode 100644
index 000000000..438bd768a
--- /dev/null
+++ b/src/main/java/com/example/shop/Item.java
@@ -0,0 +1,27 @@
+package com.example.shop;
+
+public class Item {
+
+ private String id;
+ private double price;
+ private int quantity;
+
+ public Item(String id, double price, int quantity) {
+ this.id = id;
+ this.price = price;
+ this.quantity = quantity;
+ }
+ public double getPrice() {
+ return price;
+ }
+ public int getQuantity() {
+ return quantity;
+ }
+ public void setQuantity(int quantity) {
+ this.quantity = quantity;
+ }
+ public String getId() {
+ return id;
+ }
+
+}
diff --git a/src/main/java/com/example/shop/ShoppingCart.java b/src/main/java/com/example/shop/ShoppingCart.java
new file mode 100644
index 000000000..0080b6956
--- /dev/null
+++ b/src/main/java/com/example/shop/ShoppingCart.java
@@ -0,0 +1,53 @@
+package com.example.shop;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShoppingCart {
+
+ private List- items = new ArrayList<>();
+ private Discount discount;
+
+ public void addItem(Item newItem){
+
+ if ( newItem.getQuantity() > 0) {
+ for (Item existingItem : items) {
+ if (existingItem.getId().equals(newItem.getId())) {
+ int updatedQuantity = existingItem.getQuantity() + newItem.getQuantity();
+ existingItem.setQuantity(updatedQuantity);
+
+ return;
+ }
+ }
+ this.items.add(newItem);
+ }
+ }
+
+ public List
- getItems() {
+ return items;
+ }
+
+ public void removeItem(Item item){
+ this.items.remove(item);
+ }
+
+ public void setDiscount(Discount discount) {
+ this.discount = discount;
+ }
+
+ public double getTotalPrice(){
+ double totalPrice = items.stream()
+ .mapToDouble(item -> item.getPrice() * item.getQuantity())
+ .sum();
+
+ if (discount != null && discount.getPercentage() <= 100 && discount.getPercentage() > 0) {
+ return discount.applyDiscount(totalPrice);
+ }
+
+ return totalPrice;
+
+ }
+
+
+
+}
diff --git a/src/test/java/com/example/BookingSystemTest.java b/src/test/java/com/example/BookingSystemTest.java
new file mode 100644
index 000000000..e30ccc29f
--- /dev/null
+++ b/src/test/java/com/example/BookingSystemTest.java
@@ -0,0 +1,267 @@
+package com.example;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.*;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class BookingSystemTest {
+
+ @Mock
+ TimeProvider timeProvider;
+
+ @Mock
+ RoomRepository roomRepository;
+
+ @Mock
+ NotificationService notificationService;
+
+ @InjectMocks
+ BookingSystem bookingSystem;
+
+ @Captor
+ ArgumentCaptor bookingCaptor;
+
+ @ParameterizedTest
+ @MethodSource("provideNullScenariosForRoomIdStartAndEndTime")
+ @DisplayName("Should throw IllegalArgumentException if room ID, start time, or end time is null")
+ void throwsExceptionWhenRoomIdStartTimeOrEndTimeIsNull(String roomId, LocalDateTime startTime, LocalDateTime endTime) {
+
+ var exception = assertThrows(IllegalArgumentException.class,
+ () -> bookingSystem.bookRoom(roomId, startTime, endTime));
+ assertThat(exception).hasMessage("Bokning kräver giltiga start- och sluttider samt rum-id");
+ }
+ private static Stream provideNullScenariosForRoomIdStartAndEndTime() {
+ LocalDateTime timeNow = LocalDateTime.now();
+ return Stream.of(
+ Arguments.of(null, timeNow, timeNow),
+ Arguments.of("Room1", null, timeNow),
+ Arguments.of("Room1", timeNow, null),
+ Arguments.of(null, null, null)
+ );
+ }
+
+ @Test
+ @DisplayName("Should throw IllegalArgumentException if the room ID provided does not exist in the repository")
+ void throwsExceptionWhenRoomDoesNotExist() {
+
+ Room room = new Room("100", "Room1");
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.plusDays(2);
+ LocalDateTime endTime = currentTime.plusDays(3);
+
+ Mockito.when(timeProvider.getCurrentTime()).thenReturn(currentTime);
+ Mockito.when(roomRepository.findById(room.getId())).thenReturn(Optional.empty());
+
+ var exception = assertThrows(IllegalArgumentException.class,
+ () -> bookingSystem.bookRoom(room.getId(), startTime, endTime));
+ assertThat(exception).hasMessage("Rummet existerar inte");
+ }
+
+ @Test
+ @DisplayName("Should throw IllegalArgumentException when attempting to book a room in the past")
+ void throwsExceptionWhenStartTimeIsBeforeCurrentDate(){
+
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.minusDays(1);
+ LocalDateTime endTime = currentTime.plusDays(2);
+ String roomId = "100";
+
+ Mockito.when(timeProvider.getCurrentTime()).thenReturn(currentTime);
+
+ var exception = assertThrows(IllegalArgumentException.class,
+ () -> bookingSystem.bookRoom(roomId, startTime, endTime));
+ assertThat(exception).hasMessage("Kan inte boka tid i dåtid");
+ }
+
+ @Test
+ @DisplayName("Should throw IllegalArgumentException if the booking end time is before the start time")
+ void throwsExceptionWhenEndTimeIsBeforeStartTime(){
+
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.plusDays(1);
+ LocalDateTime endTime = currentTime.minusDays(2);
+ String roomId = "100";
+
+ Mockito.when(timeProvider.getCurrentTime()).thenReturn(currentTime);
+
+ var exception = assertThrows(IllegalArgumentException.class,
+ () -> bookingSystem.bookRoom(roomId, startTime, endTime));
+ assertThat(exception).hasMessage("Sluttid måste vara efter starttid");
+ }
+
+ @Test
+ @DisplayName("Should successfully book a room, save it to the repository, and send a confirmation when the room is available")
+ void bookRoomIfRoomIsAvailable() throws NotificationException {
+
+ Room room = new Room("100", "Room1");
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.plusDays(2);
+ LocalDateTime endTime = currentTime.plusDays(3);
+
+ Mockito.when(timeProvider.getCurrentTime()).thenReturn(currentTime);
+ Mockito.when(roomRepository.findById(room.getId())).thenReturn(Optional.of(room));
+
+ var result = bookingSystem.bookRoom(room.getId(), startTime, endTime);
+
+ verify(notificationService).sendBookingConfirmation(bookingCaptor.capture());
+ Booking bookingCaptorValue = bookingCaptor.getValue();
+
+ assertThat(room.hasBooking(bookingCaptorValue.getId())).isTrue();
+ verify(roomRepository).save(room);
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ @DisplayName("Should return false and not save to repository if the room is already occupied during the requested time")
+ void notBookRoomIfRoomIsNotAvailable() {
+
+ Room room = new Room("100", "Room1");
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.plusDays(1);
+ LocalDateTime endTime = currentTime.plusDays(2);
+ Booking booking = new Booking("1", "100", startTime, endTime);
+
+ Mockito.when(timeProvider.getCurrentTime()).thenReturn(currentTime);
+ Mockito.when(roomRepository.findById(room.getId())).thenReturn(Optional.of(room));
+ room.addBooking(booking);
+
+ boolean result = bookingSystem.bookRoom(room.getId(), startTime, endTime);
+
+ assertThat(result).isFalse();
+
+ verify(roomRepository, Mockito.never()).save(room);
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideNullScenarios")
+ @DisplayName("Should throw IllegalArgumentException when searching for available rooms with null dates")
+ void getAvailableRoomsThrowsExceptionWhenStartOrEndTimeIsNull(LocalDateTime startTime, LocalDateTime endTime) {
+
+ var exception = assertThrows(IllegalArgumentException.class,
+ () -> bookingSystem.getAvailableRooms(startTime, endTime));
+ assertThat(exception).hasMessage("Måste ange både start- och sluttid");
+ }
+ private static Stream provideNullScenarios() {
+ LocalDateTime timeNow = LocalDateTime.now();
+ return Stream.of(
+ Arguments.of(timeNow, null),
+ Arguments.of(null, timeNow),
+ Arguments.of(null, null)
+ );
+ }
+
+ @Test
+ @DisplayName("Should throw IllegalArgumentException if the end time is before the start time during availability search")
+ void getAvailableRoomsThrowsExceptionWhenEndTimeIsBeforeStartTime() {
+
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.plusDays(4);
+ LocalDateTime endTime = currentTime.plusDays(3);
+
+ var exception = assertThrows(IllegalArgumentException.class,
+ () -> bookingSystem.getAvailableRooms(startTime, endTime));
+ assertThat(exception).hasMessage("Sluttid måste vara efter starttid");
+ }
+
+ @Test
+ @DisplayName("Should return a list of all rooms that do not have overlapping bookings for the given period")
+ void getAvailableRoomsReturnsAListOfAvailableRooms() {
+
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.plusDays(4);
+ LocalDateTime endTime = currentTime.plusDays(5);
+ Room room1 = new Room("101", "Room1");
+ Room room2 = new Room("102", "Room2");
+
+ Mockito.when(roomRepository.findAll()).thenReturn(List.of(room1, room2));
+ List result = bookingSystem.getAvailableRooms(startTime, endTime);
+
+ assertThat(result).hasSize(2);
+ }
+
+ @Test
+ @DisplayName("Should throw IllegalArgumentException if the booking ID to cancel is null")
+ void cancelBookingThrowsExceptionWhenBookingIdIsNull() {
+
+ var exception = assertThrows(IllegalArgumentException.class,
+ () -> bookingSystem.cancelBooking(null));
+
+ assertThat(exception).hasMessage("Boknings-id kan inte vara null");
+ }
+
+ @Test
+ @DisplayName("Should successfully cancel a booking, update the correct room, and send a cancellation notification")
+ void cancelBooking() throws NotificationException {
+
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.plusDays(3);
+ LocalDateTime endTime = currentTime.plusDays(5);
+ Booking booking1 = new Booking("1", "100", startTime, endTime);
+ Booking booking2 = new Booking("2", "200", startTime, endTime);
+ Room room1 = new Room("100", "Room1");
+ Room room2 = new Room("102", "Room2");
+
+ room1.addBooking(booking1);
+ room2.addBooking(booking2);
+
+ Mockito.when(timeProvider.getCurrentTime()).thenReturn(currentTime);
+ Mockito.when(roomRepository.findAll()).thenReturn(List.of(room1, room2));
+
+ boolean result = bookingSystem.cancelBooking("1");
+
+ verify(notificationService).sendCancellationConfirmation(bookingCaptor.capture());
+
+ assertThat(result).isTrue();
+ assertThat(room1.hasBooking("1")).isFalse();
+ assertThat(room2.hasBooking("2")).isTrue();
+
+ verify(roomRepository).save(room1);
+ }
+
+ @Test
+ @DisplayName("Should throw IllegalStateException when trying to cancel a booking that has already started or finished")
+ void cancelBookingThrowsExceptionWhenStartTimeIsBeforeCurrentTime(){
+
+ LocalDateTime currentTime = LocalDateTime.now();
+ LocalDateTime startTime = currentTime.minusDays(3);
+ LocalDateTime endTime = currentTime.plusDays(5);
+ Booking booking = new Booking("1", "100", startTime, endTime);
+ Room room = new Room("100", "Room1");
+
+ room.addBooking(booking);
+ Mockito.when(timeProvider.getCurrentTime()).thenReturn(currentTime);
+ Mockito.when(roomRepository.findAll()).thenReturn(List.of(room));
+
+ var exception = assertThrows(IllegalStateException.class,
+ () -> bookingSystem.cancelBooking("1"));
+
+ assertThat(exception).hasMessage("Kan inte avboka påbörjad eller avslutad bokning");
+ }
+
+ @Test
+ @DisplayName("Should return false when attempting to cancel a booking ID that is not found in any room")
+ void cancelBookingShouldReturnFalseIfBookingDoesNotExist() {
+ Room room = new Room("100", "Room1");
+
+ Mockito.when(roomRepository.findAll()).thenReturn(List.of(room));
+ boolean result = bookingSystem.cancelBooking("999");
+
+ assertThat(result).isFalse();
+ }
+
+}
diff --git a/src/test/java/com/example/PaymentProcessorTest.java b/src/test/java/com/example/PaymentProcessorTest.java
new file mode 100644
index 000000000..f81bfbeac
--- /dev/null
+++ b/src/test/java/com/example/PaymentProcessorTest.java
@@ -0,0 +1,77 @@
+package com.example;
+
+import com.example.payment.*;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.sql.SQLException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class PaymentProcessorTest {
+
+ @Mock
+ private PaymentApi paymentApi;
+
+ @Mock
+ private DatabaseConnection databaseConnection;
+
+ @Mock
+ private EmailService emailService;
+
+ @InjectMocks
+ private PaymentProcessor paymentProcessor;
+
+ @Test
+ @DisplayName("Should return true and save to database when payment is successful")
+ void paymentSuccess() throws SQLException {
+ PaymentApiResponse response = new PaymentApiResponse(true);
+ double amount = 100.0;
+
+ Mockito.when(paymentApi.charge("sk_test_123456", amount)).thenReturn(response);
+ boolean result = paymentProcessor.processPayment(amount);
+
+ assertThat(result).isTrue();
+ verify(databaseConnection).executeUpdate(anyString());
+ }
+
+ @Test
+ @DisplayName("Should send email notification with correct details on success")
+ void sendEmailNotificationWhenPaymentIsSuccessful() throws SQLException {
+ PaymentApiResponse response = new PaymentApiResponse(true);
+ double amount = 100.0;
+ String email = "user@example.com";
+
+ Mockito.when(paymentApi.charge("sk_test_123456", amount)).thenReturn(response);
+ boolean result = paymentProcessor.processPayment(amount);
+
+ assertThat(result).isTrue();
+ verify(databaseConnection).executeUpdate(anyString());
+ verify(emailService).sendPaymentConfirmation(email, amount);
+ }
+
+ @Test
+ @DisplayName("Should return false and perform no side effects when payment fails")
+ void paymentFails() throws SQLException {
+ PaymentApiResponse response = new PaymentApiResponse(false);
+ double amount = 100.0;
+
+ Mockito.when(paymentApi.charge("sk_test_123456", amount)).thenReturn(response);
+ boolean result = paymentProcessor.processPayment(amount);
+
+ assertThat(result).isFalse();
+ verify(databaseConnection, never()).executeUpdate(anyString());
+ verify(emailService, never()).sendPaymentConfirmation(anyString(), anyDouble());
+ }
+
+}
diff --git a/src/test/java/com/example/shop/ShoppingCartTest.java b/src/test/java/com/example/shop/ShoppingCartTest.java
new file mode 100644
index 000000000..e5b4f11ca
--- /dev/null
+++ b/src/test/java/com/example/shop/ShoppingCartTest.java
@@ -0,0 +1,160 @@
+package com.example.shop;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ShoppingCartTest {
+
+ @Test
+ @DisplayName("Should increase the number of items in the cart when adding products")
+ void addItemToShoppingCart(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 100.0, 1);
+
+ cart.addItem(shirt);
+
+ assertThat(cart.getItems()).hasSize(1);
+ }
+
+ @Test
+ @DisplayName("Should decrease the number of items in the cart when removing products")
+ void removeItemInShoppingCart(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 100.0, 1);
+ Item shirt2 = new Item("2", 200.0, 1);
+
+ cart.addItem(shirt);
+ cart.addItem(shirt2);
+ cart.removeItem(shirt2);
+
+ assertThat(cart.getItems()).hasSize(1);
+ }
+
+ @Test
+ @DisplayName("Should calculate the total price of the shopping cart")
+ void getTotalPriceOfShoppingCart(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 100.0, 2);
+ Item pants = new Item("2", 200.0, 1);
+ Item hat = new Item("3", 300.0, 2);
+
+ cart.addItem(shirt);
+ cart.addItem(pants);
+ cart.addItem(hat);
+
+ assertThat(cart.getTotalPrice()).isEqualTo(1000.0);
+ }
+
+ @Test
+ @DisplayName("Should apply discount to the shopping cart")
+ void applyDiscountToShoppingCart(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 100.0, 1);
+ Item pants = new Item("2", 200.0, 1);
+ Discount discount = new Discount(50);
+
+ cart.addItem(shirt);
+ cart.addItem(pants);
+ cart.setDiscount(discount);
+
+ assertThat(cart.getTotalPrice()).isEqualTo(150);
+
+ }
+
+ @Test
+ @DisplayName("Should update the quantity of an item when new item added in the cart is the same as the existing")
+ void manageQuantityUpdate(){
+ ShoppingCart cart = new ShoppingCart();
+ Item item1 = new Item("A1", 100.0, 1);
+ Item item2 = new Item("A1", 100.0, 2);
+
+ cart.addItem(item1);
+ cart.addItem(item2);
+
+ int totalQuantity = cart.getItems().getFirst().getQuantity();
+
+ assertThat(cart.getItems()).hasSize(1);
+ assertThat(totalQuantity).isEqualTo(3);
+ }
+
+ @Test
+ @DisplayName("Should return total price 0 when shopping cart is empty")
+ void totalPriceShouldBeZeroWhenShoppingCartIsEmpty(){
+ ShoppingCart cart = new ShoppingCart();
+
+ cart.getTotalPrice();
+
+ assertThat(cart.getItems()).isEmpty();
+ assertThat(cart.getTotalPrice()).isEqualTo(0.0);
+ }
+
+ @Test
+ @DisplayName("Should not colapse when removing an item that is not in the cart")
+ void removeItemThatDoesNotExist(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 100.0, 1);
+
+ cart.removeItem(shirt);
+
+ assertThat(cart.getItems()).isEmpty();
+ }
+
+ @Test
+ @DisplayName("Should return same total price when discount is more than 100%")
+ void discountShouldNotBeMoreThan100(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 1000.0, 1);
+ Discount discount = new Discount(110);
+
+ cart.addItem(shirt);
+ cart.setDiscount(discount);
+
+ assertThat(cart.getTotalPrice()).isEqualTo(1000.0);
+ }
+
+ @Test
+ @DisplayName("Should return same total price when discount is a negative number")
+ void discountShouldNotBeNegative(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 100.0, 1);
+ Discount discount = new Discount(-50);
+
+ cart.addItem(shirt);
+ cart.setDiscount(discount);
+
+ assertThat(cart.getTotalPrice()).isEqualTo(100.0);
+ }
+
+ @Test
+ @DisplayName("Should apply discount even when adding more items to the cart after the first item")
+ void shouldApplyDiscountWhenAddingMoreItemsAfter(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 100.0, 1);
+ Item pants = new Item("2", 200.0, 1);
+ Item hat = new Item("3", 300.0, 2);
+ Discount discount = new Discount(50);
+
+ cart.addItem(shirt);
+ cart.setDiscount(discount);
+ cart.addItem(pants);
+ cart.addItem(hat);
+
+ assertThat(cart.getTotalPrice()).isEqualTo(450.0);
+ }
+
+ @Test
+ @DisplayName("Should not add any item when the quantity is negative")
+ void notAddItemsWhenQuantityOfItemsIsNegative(){
+ ShoppingCart cart = new ShoppingCart();
+ Item shirt = new Item("1", 100.0, -1);
+ Item pants = new Item("2", 200.0, -1);
+
+ cart.addItem(shirt);
+ cart.addItem(pants);
+
+ assertThat(cart.getItems()).isEmpty();
+ }
+
+}