|
6 | 6 | import java.math.BigDecimal; |
7 | 7 | import java.time.LocalDate; |
8 | 8 | import java.util.List; |
| 9 | +import java.util.Map; |
9 | 10 | import java.util.NoSuchElementException; |
10 | 11 | import java.util.UUID; |
11 | 12 |
|
@@ -118,22 +119,149 @@ void should_beEmpty_when_newlySetUp() { |
118 | 119 | .isTrue(); |
119 | 120 | } |
120 | 121 |
|
| 122 | + // --- Singleton and Factory Pattern Tests --- |
| 123 | + |
121 | 124 | @Nested |
122 | 125 | @DisplayName("Factory and Singleton Behavior") |
123 | 126 | class FactoryTests { |
124 | | - // ... (omitted for brevity, same as before) |
| 127 | + |
| 128 | + @Test |
| 129 | + @DisplayName("✅ should not have any public constructors") |
| 130 | + void should_notHavePublicConstructors() { |
| 131 | + Constructor<?>[] constructors = Warehouse.class.getConstructors(); |
| 132 | + assertThat(constructors) |
| 133 | + .as("Warehouse should only be accessed via its getInstance() factory method.") |
| 134 | + .isEmpty(); |
| 135 | + } |
| 136 | + |
| 137 | + @Test |
| 138 | + @DisplayName("✅ should be created by calling the 'getInstance' factory method") |
| 139 | + void should_beCreated_when_usingFactoryMethod() { |
| 140 | + Warehouse defaultWarehouse = Warehouse.getInstance(); |
| 141 | + assertThat(defaultWarehouse).isNotNull(); |
| 142 | + } |
| 143 | + |
| 144 | + @Test |
| 145 | + @DisplayName("✅ should return the same instance for the same name") |
| 146 | + void should_returnSameInstance_when_nameIsIdentical() { |
| 147 | + Warehouse warehouse1 = Warehouse.getInstance("GlobalStore"); |
| 148 | + Warehouse warehouse2 = Warehouse.getInstance("GlobalStore"); |
| 149 | + assertThat(warehouse1) |
| 150 | + .as("Warehouses with the same name should be the same singleton instance.") |
| 151 | + .isSameAs(warehouse2); |
| 152 | + } |
125 | 153 | } |
126 | 154 |
|
127 | 155 | @Nested |
128 | 156 | @DisplayName("Product Management") |
129 | 157 | class ProductManagementTests { |
130 | 158 |
|
131 | | - // ... (most tests omitted for brevity, same as before) |
| 159 | + @Test |
| 160 | + @DisplayName("✅ should be empty when new") |
| 161 | + void should_beEmpty_when_new() { |
| 162 | + assertThat(warehouse.isEmpty()) |
| 163 | + .as("A new warehouse instance should have no products.") |
| 164 | + .isTrue(); |
| 165 | + } |
| 166 | + |
| 167 | + @Test |
| 168 | + @DisplayName("✅ should return an empty product list when new") |
| 169 | + void should_returnEmptyProductList_when_new() { |
| 170 | + assertThat(warehouse.getProducts()) |
| 171 | + .as("A new warehouse should return an empty list, not null.") |
| 172 | + .isEmpty(); |
| 173 | + } |
| 174 | + |
| 175 | + @Test |
| 176 | + @DisplayName("✅ should store various product types (Food, Electronics)") |
| 177 | + void should_storeHeterogeneousProducts() { |
| 178 | + // Arrange |
| 179 | + Product milk = new FoodProduct(UUID.randomUUID(), "Milk", Category.of("Dairy"), new BigDecimal("15.50"), LocalDate.now().plusDays(7), new BigDecimal("1.0")); |
| 180 | + Product laptop = new ElectronicsProduct(UUID.randomUUID(), "Laptop", Category.of("Electronics"), new BigDecimal("12999"), 24, new BigDecimal("2.2")); |
| 181 | + |
| 182 | + // Act |
| 183 | + warehouse.addProduct(milk); |
| 184 | + warehouse.addProduct(laptop); |
| 185 | + |
| 186 | + // Assert |
| 187 | + assertThat(warehouse.getProducts()) |
| 188 | + .as("Warehouse should correctly store different subtypes of Product.") |
| 189 | + .hasSize(2) |
| 190 | + .containsExactlyInAnyOrder(milk, laptop); |
| 191 | + } |
| 192 | + |
| 193 | + |
| 194 | + |
| 195 | + @Test |
| 196 | + @DisplayName("❌ should throw an exception when adding a product with a duplicate ID") |
| 197 | + void should_throwException_when_addingProductWithDuplicateId() { |
| 198 | + // Arrange |
| 199 | + UUID sharedId = UUID.randomUUID(); |
| 200 | + Product milk = new FoodProduct(sharedId, "Milk", Category.of("Dairy"), BigDecimal.ONE, LocalDate.now(), BigDecimal.ONE); |
| 201 | + Product cheese = new FoodProduct(sharedId, "Cheese", Category.of("Dairy"), BigDecimal.TEN, LocalDate.now(), BigDecimal.TEN); |
| 202 | + warehouse.addProduct(milk); |
| 203 | + |
| 204 | + // Act & Assert |
| 205 | + assertThatThrownBy(() -> warehouse.addProduct(cheese)) |
| 206 | + .isInstanceOf(IllegalArgumentException.class) |
| 207 | + .hasMessage("Product with that id already exists, use updateProduct for updates."); |
| 208 | + } |
| 209 | + |
| 210 | + @Test |
| 211 | + @DisplayName("✅ should update the price of an existing product") |
| 212 | + void should_updateExistingProductPrice() { |
| 213 | + // Arrange |
| 214 | + Product milk = new FoodProduct(UUID.randomUUID(), "Milk", Category.of("Dairy"), new BigDecimal("15.50"), LocalDate.now().plusDays(7), new BigDecimal("1.0")); |
| 215 | + warehouse.addProduct(milk); |
| 216 | + BigDecimal newPrice = new BigDecimal("17.00"); |
| 217 | + |
| 218 | + // Act |
| 219 | + warehouse.updateProductPrice(milk.uuid(), newPrice); |
| 220 | + |
| 221 | + // Assert |
| 222 | + assertThat(warehouse.getProductById(milk.uuid())) |
| 223 | + .as("The product's price should be updated to the new value.") |
| 224 | + .isPresent() |
| 225 | + .hasValueSatisfying(product -> |
| 226 | + assertThat(product.price()).isEqualByComparingTo(newPrice) |
| 227 | + ); |
| 228 | + } |
| 229 | + |
| 230 | + @Test |
| 231 | + @DisplayName("✅ should group products correctly by their category") |
| 232 | + void should_groupProductsByCategories() { |
| 233 | + // Arrange |
| 234 | + Product milk = new FoodProduct(UUID.randomUUID(), "Milk", Category.of("Dairy"), BigDecimal.ONE, LocalDate.now(), BigDecimal.ONE); |
| 235 | + Product apple = new FoodProduct(UUID.randomUUID(), "Apple", Category.of("Fruit"), BigDecimal.ONE, LocalDate.now(), BigDecimal.ONE); |
| 236 | + Product laptop = new ElectronicsProduct(UUID.randomUUID(), "Laptop", Category.of("Electronics"), BigDecimal.TEN, 24, BigDecimal.TEN); |
| 237 | + warehouse.addProduct(milk); |
| 238 | + warehouse.addProduct(apple); |
| 239 | + warehouse.addProduct(laptop); |
| 240 | + |
| 241 | + Map<Category, List<Product>> expectedMap = Map.of( |
| 242 | + Category.of("Dairy"), List.of(milk), |
| 243 | + Category.of("Fruit"), List.of(apple), |
| 244 | + Category.of("Electronics"), List.of(laptop) |
| 245 | + ); |
| 246 | + |
| 247 | + // Act & Assert |
| 248 | + assertThat(warehouse.getProductsGroupedByCategories()) |
| 249 | + .as("The returned map should have categories as keys and lists of products as values.") |
| 250 | + .isEqualTo(expectedMap); |
| 251 | + } |
132 | 252 |
|
133 | 253 | @Test |
134 | 254 | @DisplayName("🔒 should return an unmodifiable list of products to protect internal state") |
135 | 255 | void should_returnUnmodifiableProductList() { |
136 | | - // ... (same as before) |
| 256 | + // Arrange |
| 257 | + Product milk = new FoodProduct(UUID.randomUUID(), "Milk", Category.of("Dairy"), BigDecimal.ONE, LocalDate.now(), BigDecimal.ONE); |
| 258 | + warehouse.addProduct(milk); |
| 259 | + List<Product> products = warehouse.getProducts(); |
| 260 | + |
| 261 | + // Act & Assert |
| 262 | + assertThatThrownBy(products::clear) |
| 263 | + .as("The list returned by getProducts() should be immutable to prevent external modification.") |
| 264 | + .isInstanceOf(UnsupportedOperationException.class); |
137 | 265 | } |
138 | 266 |
|
139 | 267 | @Test |
|
0 commit comments