diff --git a/pom.xml b/pom.xml
index db6c2c51..7d0f2092 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,89 +1,176 @@
-
-
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.0.5
-
-
- com.bootexample4
- products
- 0.0.1-SNAPSHOT
- products
- Demo project for Spring Boot
-
- 17
-
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
-
- org.mock-server
- mockserver-netty
- 3.10.8
-
-
- org.mock-server
- mockserver-client-java
- 3.10.8
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
-
- com.h2database
- h2
- runtime
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.5
+
+
+
+ com.bootexample4
+ products
+ 0.0.1-SNAPSHOT
+ products
+ Demo project for Spring Boot
+
+ 17
+
+
- io.cucumber
- cucumber-spring
- 7.0.0
- test
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.mock-server
+ mockserver-netty
+ 3.10.8
+
+
+ org.mock-server
+ mockserver-client-java
+ 3.10.8
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ io.cucumber
+ cucumber-spring
+ 7.0.0
+ test
- io.cucumber
- cucumber-java
- 7.0.0
- test
+ io.cucumber
+ cucumber-java
+ 7.0.0
+ test
- io.cucumber
- cucumber-junit
- 7.0.0
- test
+ io.cucumber
+ cucumber-junit
+ 7.0.0
+ test
- org.assertj
- assertj-core
- 3.19.0
- test
+ org.assertj
+ assertj-core
+ 3.19.0
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 2.23.4
+ test
+
+
+
+ io.spring.javaformat
+ spring-javaformat-formatter
+ 0.0.40
+
+
+
+ io.cucumber
+ cucumber-junit-platform-engine
+ 7.0.0
+ test
+
+
+
+ io.cucumber
+ cucumber-core
+ 7.0.0
+ test
+
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ 5.9.2
+ test
+
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
-
-
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.11
+
+
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+ coverageReport
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.2.5
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ 3.2.5
+
+ testReport
+
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ 2.1
+
+ testReport
+
+
+
+
+ io.spring.javaformat
+ spring-javaformat-maven-plugin
+ 0.0.40
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/controller/ProductControllerCreateProductTest.java.invalid b/src/test/java/com/bootexample4/products/controller/ProductControllerCreateProductTest.java.invalid
new file mode 100644
index 00000000..13004025
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/controller/ProductControllerCreateProductTest.java.invalid
@@ -0,0 +1,420 @@
+//This test file is marked invalid as it contains compilation errors. Change the extension to of this file to .java, to manually edit its contents
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=createProduct_16b670a647
+ROOST_METHOD_SIG_HASH=createProduct_36b748883e
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void the_client_sends_a_post_request_to(String string) {
+
+ savedProduct = productController.createProduct(newProduct);
+}
+"Scenario 1: Returns exactly the Product instance provided by the repository.save call
+
+Details:
+ TestName: returnsSameInstanceAsRepositorySave
+ Description: Verifies that createProduct delegates to productRepository.save and returns exactly the same Product instance that the repository returns.
+
+Execution:
+ Arrange: Configure a ProductController with a mock ProductRepository. Prepare a Product input object and stub productRepository.save to return a specific Product instance (which may be the same as the input).
+ Act: Invoke createProduct with the prepared Product.
+ Assert: Use assertSame to verify that the returned object reference is exactly the same as the instance returned by the stubbed productRepository.save.
+
+Validation:
+ This assertion verifies pass-through behavior: the controller does not wrap, transform, or replace the repository’s return value. It confirms the method simply returns whatever productRepository.save returns, matching the controller’s intended minimal responsibility.
+
+
+Scenario 2: Returns the repository’s persisted instance when save returns a different object
+
+Details:
+ TestName: returnsRepositoryPersistedInstanceWhenDifferentFromInput
+ Description: Ensures that when productRepository.save returns a different Product instance (e.g., representing a persisted entity), createProduct returns that new instance, not the original input.
+
+Execution:
+ Arrange: Mock productRepository.save to return a new Product instance different from the input Product object.
+ Act: Call createProduct with the original Product input.
+ Assert: Use assertSame to verify the returned reference equals the repository’s returned instance, and use assertNotSame to confirm it is not the same as the input reference.
+
+Validation:
+ This validates that the controller does not “prefer” the caller’s input object and instead returns the repository’s result, which is important when persistence layers return enriched or different instances.
+
+
+Scenario 3: Propagates IllegalArgumentException when input Product is null and the repository rejects null
+
+Details:
+ TestName: propagatesIllegalArgumentExceptionWhenInputIsNull
+ Description: Confirms that if createProduct is invoked with null and productRepository.save(null) throws IllegalArgumentException, the exception is propagated unchanged to the caller.
+
+Execution:
+ Arrange: Stub productRepository.save(null) to throw IllegalArgumentException.
+ Act: Invoke createProduct with null.
+ Assert: Use assertThrows(IllegalArgumentException.class, ...) to verify the exception is thrown.
+
+Validation:
+ This ensures the method does not swallow or alter exceptions from the repository and that invalid inputs are surfaced appropriately to higher layers.
+
+
+Scenario 4: Returns null if the repository.save unexpectedly returns null
+
+Details:
+ TestName: returnsNullWhenRepositorySaveReturnsNull
+ Description: Validates behavior for an atypical repository implementation where save returns null, ensuring createProduct returns null without additional processing.
+
+Execution:
+ Arrange: Stub productRepository.save for a given Product input to return null.
+ Act: Call createProduct with that input.
+ Assert: Use assertNull to verify the result is null.
+
+Validation:
+ This confirms that the controller does not enforce its own non-null guarantees and directly relays the repository outcome, highlighting the importance of repository contract adherence.
+
+
+Scenario 5: Propagates DataIntegrityViolationException when repository enforces constraints
+
+Details:
+ TestName: propagatesDataIntegrityViolationExceptionOnConstraintViolation
+ Description: Checks that if the repository throws a DataIntegrityViolationException (e.g., for violated database constraints), the controller does not catch or transform it.
+
+Execution:
+ Arrange: Stub productRepository.save to throw DataIntegrityViolationException upon saving a malformed or conflicting Product (details of fields are not asserted, only the thrown exception).
+ Act: Invoke createProduct with the problematic Product input.
+ Assert: Use assertThrows(DataIntegrityViolationException.class, ...) to confirm propagation.
+
+Validation:
+ This ensures error handling is left to upstream layers or global exception handlers and validates that createProduct has no internal try/catch altering persistence exceptions.
+
+
+Scenario 6: Propagates a generic RuntimeException on unexpected repository failure
+
+Details:
+ TestName: propagatesRuntimeExceptionOnUnexpectedRepositoryFailure
+ Description: Ensures that unexpected runtime failures from productRepository.save (e.g., NullPointerException or other RuntimeException) bubble up to callers.
+
+Execution:
+ Arrange: Mock productRepository.save to throw a RuntimeException when called with a specific Product.
+ Act: Call createProduct with that Product.
+ Assert: Use assertThrows(RuntimeException.class, ...) to verify the exception is rethrown.
+
+Validation:
+ This test confirms that the controller maintains transparency of failures and does not mask or alter unexpected runtime errors.
+
+
+Scenario 7: Independent behavior across multiple sequential createProduct calls
+
+Details:
+ TestName: handlesMultipleSequentialSavesIndependently
+ Description: Verifies that multiple calls to createProduct with different inputs each delegate to productRepository.save and return the distinct results from each call.
+
+Execution:
+ Arrange: Stub productRepository.save for Product A to return Persisted A, and for Product B to return Persisted B.
+ Act: Invoke createProduct with Product A and then with Product B.
+ Assert: Use assertSame to confirm the first result is Persisted A and the second result is Persisted B; additionally, confirm they are not the same reference.
+
+Validation:
+ This demonstrates correct per-call behavior without cross-call interference, ensuring the controller does not cache or reuse results across invocations.
+
+
+Scenario 8: Only productRepository.save is invoked; no unintended repository interactions
+
+Details:
+ TestName: invokesOnlySaveWithoutAdditionalRepositoryCalls
+ Description: Ensures that createProduct calls only productRepository.save and does not invoke other repository methods like findById, findAll, or delete.
+
+Execution:
+ Arrange: Use a mock ProductRepository and set expectations for a single save invocation with the provided Product.
+ Act: Call createProduct with a Product input.
+ Assert: Verify that save was invoked exactly once with that input and that no other repository methods were called; also use assertSame to confirm the return matches the mocked save result.
+
+Validation:
+ Confirms minimal and precise interaction with the data layer, preventing unintended side effects or extra queries.
+
+
+Scenario 9: Throws NullPointerException when productRepository is not injected
+
+Details:
+ TestName: throwsNullPointerWhenRepositoryNotInjected
+ Description: Covers the edge case where productRepository is null (e.g., controller constructed manually without dependency injection), ensuring a NullPointerException occurs when createProduct is invoked.
+
+Execution:
+ Arrange: Instantiate ProductController without setting productRepository (leave it null).
+ Act: Invoke createProduct with any Product instance.
+ Assert: Use assertThrows(NullPointerException.class, ...) to confirm the failure occurs due to the missing repository.
+
+Validation:
+ Highlights the necessity of dependency injection and proper test setup, reinforcing that the controller depends on an initialized productRepository.
+
+
+Scenario 10: Passes the exact input reference to repository.save without pre-processing
+
+Details:
+ TestName: passesExactInputReferenceToSave
+ Description: Checks that the Product argument passed to createProduct is forwarded to productRepository.save as the same reference, indicating no intermediate transformation.
+
+Execution:
+ Arrange: Create a specific Product instance and capture arguments received by the mocked productRepository.save.
+ Act: Call createProduct with that Product.
+ Assert: Verify via the mock that the argument passed to save is the same reference as the input (and use assertSame on the returned object where applicable).
+
+Validation:
+ Ensures that the controller is a thin pass-through layer for the request body to the repository, without mutating or replacing the input before persistence.
+
+
+Scenario 11: Works correctly even when Product has minimal or incomplete data (no field-level validation in controller)
+
+Details:
+ TestName: noControllerValidationAltersSaveForIncompleteProduct
+ Description: Confirms that createProduct does not perform validation or enrichment and simply saves whatever Product object it receives, relying on repository behavior.
+
+Execution:
+ Arrange: Prepare a Product instance representing “incomplete” data (no assumptions about fields are asserted; focus is on behavior) and stub productRepository.save to return a specific instance.
+ Act: Invoke createProduct with this Product.
+ Assert: Use assertSame to confirm the controller returns exactly what the repository returns.
+
+Validation:
+ Reinforces that validation is not handled in this controller method (no annotations beyond @RequestBody) and that the responsibility lies with other layers or repository constraints.
+
+
+Scenario 12: Behavior is framework-agnostic; @RequestBody does not affect direct method invocation
+
+Details:
+ TestName: requestBodyAnnotationDoesNotChangeDirectInvocationBehavior
+ Description: Verifies that, when called directly as a unit test (outside of Spring MVC deserialization), createProduct behaves the same: delegating to productRepository.save and returning its result.
+
+Execution:
+ Arrange: Set up a ProductController with a mocked ProductRepository returning a known Product for save.
+ Act: Invoke createProduct directly with a Product instance.
+ Assert: Use assertSame to ensure the returned reference is the repository’s returned object.
+
+Validation:
+ Confirms that the method’s logic is independent of HTTP concerns and that unit tests can reliably exercise behavior without a web environment.
+
+
+Scenario 13: Ensures method return type is Product and not wrapped in ResponseEntity
+
+Details:
+ TestName: returnsPlainProductTypeRatherThanResponseEntity
+ Description: Validates that createProduct returns a Product instance directly, distinguishing it from other controller methods that return ResponseEntity.
+
+Execution:
+ Arrange: Mock productRepository.save to return a concrete Product object.
+ Act: Call createProduct with a Product input and capture the result.
+ Assert: Use assertions that the result is a Product reference (e.g., assertNotNull followed by reference equality checks) and not a ResponseEntity.
+
+Validation:
+ Confirms API design consistency for this endpoint and prevents accidental return-type changes that could break clients or tests.
+
+
+Scenario 14: Consistency under concurrent invocations (each call delegates and returns appropriately)
+
+Details:
+ TestName: concurrentCallsDelegateIndependentlyToSave
+ Description: Describes behavior when createProduct is invoked concurrently from multiple threads: each invocation should call productRepository.save with its own Product and return the corresponding result.
+
+Execution:
+ Arrange: Prepare distinct Product instances and configure the mocked productRepository to return distinct results for each input. Set up parallel invocations (e.g., using multiple threads or executors) to call createProduct simultaneously.
+ Act: Execute concurrent calls and collect results.
+ Assert: For each input, assert that the returned reference matches the configured repository result for that input; also confirm that the number of save invocations equals the number of calls.
+
+Validation:
+ Demonstrates that the controller method itself has no shared mutable logic that would corrupt results across threads, and that it correctly delegates each call independently to the repository.
+
+*/
+
+// ********RoostGPT********
+package com.bootexample4.products.controller;import java.util.List;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import static org.junit.jupiter.api.Assertions.*;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.*;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import com.bootexample4.products.model.Product;
+import com.bootexample4.products.repository.ProductRepository;
+import com.bootexample4.products.controller.ProductController;
+import org.junit.jupiter.api.*;
+
+@ExtendWith(MockitoExtension.class)
+public class ProductControllerCreateProductTest {
+ @Mock
+ private ProductRepository productRepository;
+ @InjectMocks
+ private ProductController productController;
+ // Marker field to reference annotation imports to ensure they are utilized
+ @SuppressWarnings("unused")
+ private static final Class>[] SPRING_ANNOTATION_MARKERS = new Class>[] { Autowired.class, RequestBody.class };
+ @Test
+ @Tag("valid")
+ public void testReturnsSameInstanceAsRepositorySave() {
+ Product input = new Product();
+ when(productRepository.save(input)).thenReturn(input);
+ Product result = productController.createProduct(input);
+ assertSame((Object) input, (Object) result);
+ }
+ @Test
+ @Tag("valid")
+ public void testReturnsRepositoryPersistedInstanceWhenDifferentFromInput() {
+ Product input = new Product();
+ Product persisted = new Product();
+ when(productRepository.save(input)).thenReturn(persisted);
+ Product result = productController.createProduct(input);
+ assertSame((Object) persisted, (Object) result);
+ assertNotSame((Object) input, (Object) result);
+ }
+ @Test
+ @Tag("invalid")
+ public void testPropagatesIllegalArgumentExceptionWhenInputIsNull() {
+ when(productRepository.save(isNull())).thenThrow(new IllegalArgumentException());
+ assertThrows(IllegalArgumentException.class, () -> productController.createProduct(null));
+ }
+ @Test
+ @Tag("boundary")
+ public void testReturnsNullWhenRepositorySaveReturnsNull() {
+ Product input = new Product();
+ when(productRepository.save(input)).thenReturn(null);
+ Product result = productController.createProduct(input);
+ assertNull((Object) result);
+ }
+ @Test
+ @Tag("invalid")
+ public void testPropagatesDataIntegrityViolationExceptionOnConstraintViolation() {
+ Product problematic = new Product();
+ when(productRepository.save(problematic)).thenThrow(new DataIntegrityViolationException(null));
+ assertThrows(DataIntegrityViolationException.class, () -> productController.createProduct(problematic));
+ }
+ @Test
+ @Tag("invalid")
+ public void testPropagatesRuntimeExceptionOnUnexpectedRepositoryFailure() {
+ Product input = new Product();
+ when(productRepository.save(input)).thenThrow(new RuntimeException());
+ assertThrows(RuntimeException.class, () -> productController.createProduct(input));
+ }
+ @Test
+ @Tag("valid")
+ public void testHandlesMultipleSequentialSavesIndependently() {
+ Product inputA = new Product();
+ Product inputB = new Product();
+ Product persistedA = new Product();
+ Product persistedB = new Product();
+ when(productRepository.save(inputA)).thenReturn(persistedA);
+ when(productRepository.save(inputB)).thenReturn(persistedB);
+ Product resultA = productController.createProduct(inputA);
+ Product resultB = productController.createProduct(inputB);
+ assertSame((Object) persistedA, (Object) resultA);
+ assertSame((Object) persistedB, (Object) resultB);
+ assertNotSame((Object) resultA, (Object) resultB);
+ }
+ @Test
+ @Tag("valid")
+ public void testInvokesOnlySaveWithoutAdditionalRepositoryCalls() {
+ Product input = new Product();
+ Product persisted = new Product();
+ when(productRepository.save(input)).thenReturn(persisted);
+ Product result = productController.createProduct(input);
+ assertSame((Object) persisted, (Object) result);
+ verify(productRepository, times(1)).save(input);
+ verifyNoMoreInteractions(productRepository);
+ }
+ @Test
+ @Tag("invalid")
+ public void testThrowsNullPointerWhenRepositoryNotInjected() {
+ ProductController controllerWithoutRepo = new ProductController(); // productRepository remains null
+ Product input = new Product();
+ assertThrows(NullPointerException.class, () -> controllerWithoutRepo.createProduct(input));
+ }
+ @Test
+ @Tag("valid")
+ public void testPassesExactInputReferenceToSave() {
+ Product input = new Product();
+ Product persisted = new Product();
+ when(productRepository.save(any(Product.class))).thenReturn(persisted);
+ Product result = productController.createProduct(input);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Product.class);
+ verify(productRepository, times(1)).save(captor.capture());
+ Product forwarded = captor.getValue();
+ assertSame((Object) input, (Object) forwarded);
+ assertSame((Object) persisted, (Object) result);
+ }
+ @Test
+ @Tag("valid")
+ public void testNoControllerValidationAltersSaveForIncompleteProduct() {
+ Product incomplete = new Product(); // Intentionally minimal data
+ Product persisted = new Product();
+ when(productRepository.save(incomplete)).thenReturn(persisted);
+ Product result = productController.createProduct(incomplete);
+ assertSame((Object) persisted, (Object) result);
+ }
+ @Test
+ @Tag("valid")
+ public void testRequestBodyAnnotationDoesNotChangeDirectInvocationBehavior() {
+ Product input = new Product();
+ Product persisted = new Product();
+ when(productRepository.save(input)).thenReturn(persisted);
+ Product result = productController.createProduct(input);
+ assertSame((Object) persisted, (Object) result);
+ }
+ @Test
+ @Tag("valid")
+ public void testReturnsPlainProductTypeRatherThanResponseEntity() {
+ Product input = new Product();
+ Product persisted = new Product();
+ when(productRepository.save(input)).thenReturn(persisted);
+ Product result = productController.createProduct(input);
+ assertNotNull((Object) result);
+ boolean isResponseEntity = result instanceof ResponseEntity;
+ assertFalse(((boolean) isResponseEntity));
+ // Utilizing List import to collect for any further processing if needed
+ List results = Arrays.asList(result);
+ assertEquals((int) 1, (int) results.size());
+ }
+ @Test
+ @Tag("boundary")
+ public void testConcurrentCallsDelegateIndependentlyToSave() throws Exception {
+ Product p1 = new Product();
+ Product p2 = new Product();
+ Product persisted1 = new Product();
+ Product persisted2 = new Product();
+ when(productRepository.save(p1)).thenReturn(persisted1);
+ when(productRepository.save(p2)).thenReturn(persisted2);
+ CountDownLatch start = new CountDownLatch(1);
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ Callable task1 = () -> {
+ start.await();
+ return productController.createProduct(p1);
+ };
+ Callable task2 = () -> {
+ start.await();
+ return productController.createProduct(p2);
+ };
+ Future future1 = executor.submit(task1);
+ Future future2 = executor.submit(task2);
+ start.countDown();
+ Product result1 = future1.get();
+ Product result2 = future2.get();
+ executor.shutdownNow();
+ assertSame((Object) persisted1, (Object) result1);
+ assertSame((Object) persisted2, (Object) result2);
+ assertNotSame((Object) result1, (Object) result2);
+ verify(productRepository, times(2)).save(any(Product.class));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/controller/ProductControllerDeleteProductTest.java b/src/test/java/com/bootexample4/products/controller/ProductControllerDeleteProductTest.java
new file mode 100644
index 00000000..9d261d67
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/controller/ProductControllerDeleteProductTest.java
@@ -0,0 +1,506 @@
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=deleteProduct_5ea3a876a4
+ROOST_METHOD_SIG_HASH=deleteProduct_dcaff736d4
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void the_client_sends_a_delete_request_to(String string) {
+
+ Long id = getProductIDfromAPI(string);
+ deleteProductResponse = productController.deleteProduct(id);
+ responseStatusCode = deleteProductResponse.getStatusCode();
+}
+"Scenario 1: Deleting an existing product returns HTTP 200 OK with an empty body
+
+Details:
+ TestName: deletesExistingProductReturnsOk
+ Description: Validates that when the repository finds a product by the provided id, the controller deletes it and returns a 200 OK response with no content.
+
+Execution:
+ Arrange: Mock productRepository.findById(id) to return Optional.of(product). Prepare a stub Product instance.
+ Act: Call deleteProduct(id) on ProductController.
+ Assert: Verify that the returned ResponseEntity has HTTP status 200 (OK) and a null body. Verify that productRepository.delete(product) is invoked exactly once.
+
+Validation:
+ Confirms that the happy-path flow invokes deletion and responds with OK, matching the method’s logic where a present Optional triggers delete and ResponseEntity.ok().build().
+
+
+Scenario 2: Deleting a non-existing product returns HTTP 404 Not Found with an empty body
+
+Details:
+ TestName: returnsNotFoundWhenProductMissing
+ Description: Ensures that when the repository does not find a product for the given id, the controller returns a 404 Not Found response and does not attempt deletion.
+
+Execution:
+ Arrange: Mock productRepository.findById(id) to return Optional.empty().
+ Act: Call deleteProduct(id) on ProductController.
+ Assert: Verify that the returned ResponseEntity has HTTP status 404 (Not Found) and a null body. Verify that productRepository.delete(...) is never called.
+
+Validation:
+ Confirms the not-found path is handled correctly by returning ResponseEntity.notFound().build() and avoiding any delete call.
+
+
+Scenario 3: Exception during findById is propagated and delete is not called
+
+Details:
+ TestName: propagatesExceptionWhenFindByIdFails
+ Description: Ensures robustness when the data layer fails during lookup. If productRepository.findById(id) throws a runtime/data access exception, the controller method should not swallow it and should not invoke delete.
+
+Execution:
+ Arrange: Configure productRepository.findById(id) to throw a RuntimeException (or a DataAccessException-like runtime exception).
+ Act: Invoke deleteProduct(id) and expect the exception to be thrown.
+ Assert: Verify that the exception is propagated to the caller. Verify that productRepository.delete(...) is never invoked.
+
+Validation:
+ Verifies fail-fast behavior and absence of side effects when lookup fails, since the method contains no try/catch around repository calls.
+
+
+Scenario 4: Exception during delete is propagated after a successful find
+
+Details:
+ TestName: propagatesExceptionWhenDeleteFails
+ Description: Ensures that if deletion fails after the product is found, the controller does not handle the exception and it propagates to the caller.
+
+Execution:
+ Arrange: Mock productRepository.findById(id) to return Optional.of(product), and configure productRepository.delete(product) to throw a RuntimeException (or a DataAccessException-like runtime exception).
+ Act: Invoke deleteProduct(id) and expect the exception to be thrown.
+ Assert: Verify that the exception is propagated and no ResponseEntity is returned.
+
+Validation:
+ Confirms that errors during delete are not masked, matching the absence of error handling logic in the method.
+
+
+Scenario 5: Null id leads to repository error and exception propagation
+
+Details:
+ TestName: throwsWhenIdIsNull
+ Description: Covers the edge case of a null id being passed programmatically (even though a null @PathVariable would not occur in a real HTTP call). Repository implementations typically reject null identifiers.
+
+Execution:
+ Arrange: Configure productRepository.findById(null) to throw an IllegalArgumentException or similar.
+ Act: Invoke deleteProduct(null) and expect an exception.
+ Assert: Verify that the exception is propagated and productRepository.delete(...) is never called.
+
+Validation:
+ Ensures the method does not attempt to handle invalid input internally and relies on repository constraints.
+
+
+Scenario 6: Negative id is treated as not found
+
+Details:
+ TestName: returnsNotFoundForNegativeId
+ Description: Validates that an invalid id value (negative) is handled as not found, reflecting typical repository behavior for nonexistent identifiers.
+
+Execution:
+ Arrange: Mock productRepository.findById(-1L) to return Optional.empty().
+ Act: Call deleteProduct(-1L).
+ Assert: Verify HTTP status 404 (Not Found) and null body. Verify no invocation of productRepository.delete(...).
+
+Validation:
+ Confirms defensive handling for nonsensical identifiers that do not correspond to a persisted product.
+
+
+Scenario 7: Zero id is treated as not found
+
+Details:
+ TestName: returnsNotFoundForZeroId
+ Description: Ensures that an id of zero behaves like a nonexistent record, resulting in a 404 Not Found response.
+
+Execution:
+ Arrange: Mock productRepository.findById(0L) to return Optional.empty().
+ Act: Call deleteProduct(0L).
+ Assert: Verify HTTP 404 (Not Found) and null body. Verify productRepository.delete(...) is not called.
+
+Validation:
+ Provides additional boundary coverage for invalid identifiers.
+
+
+Scenario 8: Very large id (Long.MAX_VALUE) is treated as not found
+
+Details:
+ TestName: returnsNotFoundForMaxLongId
+ Description: Ensures extreme high boundary values do not break the logic and correctly return 404 when no product exists for that id.
+
+Execution:
+ Arrange: Mock productRepository.findById(Long.MAX_VALUE) to return Optional.empty().
+ Act: Call deleteProduct(Long.MAX_VALUE).
+ Assert: Verify HTTP 404 (Not Found) and null body. Verify productRepository.delete(...) is not invoked.
+
+Validation:
+ Confirms correct behavior under boundary conditions.
+
+
+Scenario 9: The exact instance returned by findById is passed to delete
+
+Details:
+ TestName: passesSameInstanceToDelete
+ Description: Verifies that the controller deletes precisely the product instance yielded by findById without altering or recreating it.
+
+Execution:
+ Arrange: Create a specific Product test instance (product). Mock productRepository.findById(id) to return Optional.of(product).
+ Act: Call deleteProduct(id).
+ Assert: Verify productRepository.delete(product) is called with the same instance. Also verify the response is HTTP 200 OK with null body.
+
+Validation:
+ Ensures no unintended transformations and confirms correct object flow between repository calls.
+
+
+Scenario 10: Repeated delete calls are idempotent in effect (OK then Not Found)
+
+Details:
+ TestName: idempotentDeleteReturnsOkThenNotFound
+ Description: Ensures that deleting the same id twice results in the first call succeeding (200 OK) and the second call returning 404 Not Found, modeling idempotent behavior at the API surface.
+
+Execution:
+ Arrange: Configure productRepository.findById(id) to return Optional.of(product) on the first call and Optional.empty() on the second call. Configure productRepository.delete(product) to succeed.
+ Act: Call deleteProduct(id) twice in sequence and capture both responses.
+ Assert: First response is HTTP 200 OK with null body and delete invoked once. Second response is HTTP 404 Not Found with null body and delete not invoked the second time.
+
+Validation:
+ Demonstrates predictable behavior across sequential operations, mirroring real-world repeated client requests.
+
+
+Scenario 11: Only findById and delete are invoked on successful deletion
+
+Details:
+ TestName: onlyFindByIdAndDeleteAreInvokedOnSuccess
+ Description: Confirms the controller performs only the necessary repository interactions on the happy path.
+
+Execution:
+ Arrange: Mock productRepository.findById(id) to return Optional.of(product). Ensure no other repository methods are stubbed.
+ Act: Call deleteProduct(id).
+ Assert: Verify productRepository.findById(id) is called once, productRepository.delete(product) is called once, and verifyNoMoreInteractions(productRepository).
+
+Validation:
+ Ensures lean interaction with the data layer and guards against unintended side effects.
+
+
+Scenario 12: No response body is returned on successful deletion
+
+Details:
+ TestName: noBodyInSuccessResponse
+ Description: Ensures that the success response does not include a payload, aligning with ResponseEntity.ok().build() behavior.
+
+Execution:
+ Arrange: Mock productRepository.findById(id) to return Optional.of(product). Ensure productRepository.delete(product) succeeds.
+ Act: Call deleteProduct(id).
+ Assert: Verify that response.getBody() is null and response status is 200 OK.
+
+Validation:
+ Confirms that the method returns an empty body as specified by the code path.
+
+
+Scenario 13: No response body is returned when product is not found
+
+Details:
+ TestName: noBodyInNotFoundResponse
+ Description: Ensures the not found response has no payload, aligning with ResponseEntity.notFound().build() behavior.
+
+Execution:
+ Arrange: Mock productRepository.findById(id) to return Optional.empty().
+ Act: Call deleteProduct(id).
+ Assert: Verify that response.getBody() is null and response status is 404 Not Found.
+
+Validation:
+ Confirms that the method does not leak entity details and properly returns an empty not-found response.
+
+
+Scenario 14: Delete is never called when the product is not present
+
+Details:
+ TestName: doesNotInvokeDeleteWhenNotFound
+ Description: Separately emphasizes that delete operations are not attempted if lookup fails.
+
+Execution:
+ Arrange: Mock productRepository.findById(id) to return Optional.empty().
+ Act: Call deleteProduct(id).
+ Assert: Verify productRepository.delete(...) is never invoked. Verify response status is 404 Not Found.
+
+Validation:
+ Guards against accidental delete invocations when the entity does not exist.
+
+
+Scenario 15: Repository is not initialized (null) causes NullPointerException on invocation
+
+Details:
+ TestName: throwsWhenRepositoryIsNull
+ Description: Covers a misconfiguration edge case: if productRepository has not been injected, invoking deleteProduct should raise a NullPointerException when attempting to call findById.
+
+Execution:
+ Arrange: Create a ProductController instance without injecting productRepository (leave it null).
+ Act: Call deleteProduct(id) and expect a NullPointerException.
+ Assert: Verify that a NullPointerException is thrown and no ResponseEntity is returned.
+
+Validation:
+ Highlights that the controller relies on dependency injection and will fail fast if not properly initialized.
+
+
+Scenario 16: Delete is called exactly once even if findById returns the same product across multiple invocations
+
+Details:
+ TestName: deleteInvokedOncePerSuccessfulCall
+ Description: Ensures per-invocation consistency: for a single successful deleteProduct call, delete is called exactly once—even if repository stubbing could return the same object repeatedly.
+
+Execution:
+ Arrange: Mock productRepository.findById(id) to return Optional.of(product).
+ Act: Call deleteProduct(id) once.
+ Assert: Verify productRepository.delete(product) is called exactly one time for that call and status is 200 OK.
+
+Validation:
+ Confirms that the method does not perform duplicate delete operations for a single request execution.
+
+*/
+
+// ********RoostGPT********
+
+package com.bootexample4.products.controller;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.ResponseEntity;
+import com.bootexample4.products.model.Product;
+import com.bootexample4.products.repository.ProductRepository;
+import java.util.Optional;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import static org.mockito.ArgumentMatchers.same;
+import org.junit.jupiter.api.*;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@ExtendWith(MockitoExtension.class)
+public class ProductControllerDeleteProductTest {
+
+ @Mock
+ private ProductRepository productRepository;
+
+ @InjectMocks
+ private ProductController productController;
+
+ @Test
+ @Tag("valid")
+ public void testDeletesExistingProductReturnsOk() {
+ Long id = Long.valueOf(1L);
+ Product product = new Product();
+ when(productRepository.findById(id)).thenReturn(Optional.of(product));
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 200, (int) response.getStatusCodeValue());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).delete(same(product));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testReturnsNotFoundWhenProductMissing() {
+ Long id = Long.valueOf(42L); // TODO change id value if needed
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 404, (int) response.getStatusCodeValue());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testPropagatesExceptionWhenFindByIdFails() {
+ Long id = Long.valueOf(10L);
+ RuntimeException failure = new RuntimeException("find failure");
+ when(productRepository.findById(id)).thenThrow(failure);
+ RuntimeException ex = assertThrows(RuntimeException.class, () -> productController.deleteProduct(id));
+ assertSame((Object) failure, (Object) ex);
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testPropagatesExceptionWhenDeleteFails() {
+ Long id = Long.valueOf(5L);
+ Product product = new Product();
+ when(productRepository.findById(id)).thenReturn(Optional.of(product));
+ RuntimeException failure = new RuntimeException("delete failure");
+ doThrow(failure).when(productRepository).delete(same(product));
+ RuntimeException ex = assertThrows(RuntimeException.class, () -> productController.deleteProduct(id));
+ assertSame((Object) failure, (Object) ex);
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).delete(same(product));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testThrowsWhenIdIsNull() {
+ when(productRepository.findById((Long) null)).thenThrow(new IllegalArgumentException("id cannot be null"));
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> productController.deleteProduct(null));
+ assertNotNull((Object) ex);
+ verify(productRepository, times(1)).findById((Long) null);
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testReturnsNotFoundForNegativeId() {
+ Long id = Long.valueOf(-1L);
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 404, (int) response.getStatusCodeValue());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testReturnsNotFoundForZeroId() {
+ Long id = Long.valueOf(0L);
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 404, (int) response.getStatusCodeValue());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testReturnsNotFoundForMaxLongId() {
+ Long id = Long.valueOf(Long.MAX_VALUE);
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 404, (int) response.getStatusCodeValue());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testPassesSameInstanceToDelete() {
+ Long id = Long.valueOf(7L);
+ Product product = new Product();
+ when(productRepository.findById(id)).thenReturn(Optional.of(product));
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 200, (int) response.getStatusCodeValue());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).delete(same(product));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testIdempotentDeleteReturnsOkThenNotFound() {
+ Long id = Long.valueOf(9L);
+ Product product = new Product();
+ when(productRepository.findById(id)).thenReturn(Optional.of(product), Optional.empty());
+ ResponseEntity firstResponse = productController.deleteProduct(id);
+ ResponseEntity secondResponse = productController.deleteProduct(id);
+ assertEquals((int) 200, (int) firstResponse.getStatusCodeValue());
+ assertNull((Object) firstResponse.getBody());
+ assertEquals((int) 404, (int) secondResponse.getStatusCodeValue());
+ assertNull((Object) secondResponse.getBody());
+ verify(productRepository, times(2)).findById(id);
+ verify(productRepository, times(1)).delete(same(product));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testOnlyFindByIdAndDeleteAreInvokedOnSuccess() {
+ Long id = Long.valueOf(3L);
+ Product product = new Product();
+ when(productRepository.findById(id)).thenReturn(Optional.of(product));
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 200, (int) response.getStatusCodeValue());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).delete(same(product));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testNoBodyInSuccessResponse() {
+ Long id = Long.valueOf(11L);
+ Product product = new Product();
+ when(productRepository.findById(id)).thenReturn(Optional.of(product));
+ ResponseEntity response = productController.deleteProduct(id);
+ assertNull((Object) response.getBody());
+ assertEquals((int) 200, (int) response.getStatusCodeValue());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).delete(same(product));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testNoBodyInNotFoundResponse() {
+ Long id = Long.valueOf(12L);
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.deleteProduct(id);
+ assertNull((Object) response.getBody());
+ assertEquals((int) 404, (int) response.getStatusCodeValue());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testDoesNotInvokeDeleteWhenNotFound() {
+ Long id = Long.valueOf(100L);
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 404, (int) response.getStatusCodeValue());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testThrowsWhenRepositoryIsNull() {
+ ProductController misconfiguredController = new ProductController(); // productRepository
+ // remains
+ // null
+ NullPointerException ex = assertThrows(NullPointerException.class,
+ () -> misconfiguredController.deleteProduct(Long.valueOf(1L)));
+ assertNotNull((Object) ex);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testDeleteInvokedOncePerSuccessfulCall() {
+ Long id = Long.valueOf(2L);
+ Product product = new Product();
+ when(productRepository.findById(id)).thenReturn(Optional.of(product));
+ ResponseEntity response = productController.deleteProduct(id);
+ assertEquals((int) 200, (int) response.getStatusCodeValue());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).delete(same(product));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/controller/ProductControllerGetAllProductsTest.java b/src/test/java/com/bootexample4/products/controller/ProductControllerGetAllProductsTest.java
new file mode 100644
index 00000000..68a2c441
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/controller/ProductControllerGetAllProductsTest.java
@@ -0,0 +1,427 @@
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=getAllProducts_fef141838b
+ROOST_METHOD_SIG_HASH=getAllProducts_7e38cc05f6
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void the_client_sends_a_get_request_to_get_the_list_of_all_products(String string) {
+ listOfProducts = productController.getAllProducts();
+}
+"
+ "@Test
+public void there_is_an_existing_product_with_id(Long id) {
+
+ listOfProducts = productController.getAllProducts();
+ boolean productPresentFlag = false;
+ for (Product product : listOfProducts) {
+ if (product.getId() == id) {
+ productPresentFlag = true;
+ break;
+ }
+ }
+ assertTrue(productPresentFlag);
+}
+"Scenario 1: Returns an empty list when the repository has no products
+
+Details:
+ TestName: returnsEmptyListWhenRepositoryReturnsEmpty
+ Description: Validates that getAllProducts returns an empty List when the ProductRepository returns an empty List, confirming the method delegates directly to the repository without adding items.
+
+Execution:
+ Arrange: Mock ProductRepository to return an empty List when findAll is called and inject this mock into a ProductController instance.
+ Act: Invoke getAllProducts on the ProductController.
+ Assert: Use assertions to check that the returned List is not null and is empty (size is 0).
+
+Validation:
+ The assertion verifies that the controller method does not fabricate or modify product entries and cleanly handles the "no data" case by returning an empty List. This is important to ensure clients can safely handle an empty result set without errors.
+
+Scenario 2: Returns a single-element list when the repository has one product
+
+Details:
+ TestName: returnsSingleItemListWhenRepositoryHasOneProduct
+ Description: Ensures that getAllProducts returns exactly the one element provided by the repository, confirming correct propagation of repository results.
+
+Execution:
+ Arrange: Mock ProductRepository to return a List containing exactly one Product instance and inject the mock into ProductController.
+ Act: Call getAllProducts.
+ Assert: Assert that the returned List size is 1 and that it is the same instance as the one returned by the repository (identity check).
+
+Validation:
+ This confirms that the method neither filters nor wraps the result and faithfully returns the repository’s output, which is critical for data consistency.
+
+Scenario 3: Returns a multi-element list when the repository has several products
+
+Details:
+ TestName: returnsMultipleItemsWhenRepositoryHasSeveralProducts
+ Description: Verifies that getAllProducts returns the full collection of products provided by the repository without alteration.
+
+Execution:
+ Arrange: Mock ProductRepository to return a List with multiple Product instances and inject this mock into ProductController.
+ Act: Invoke getAllProducts.
+ Assert: Assert that the List size matches the number of elements supplied by the mock and that the returned List reference is identical to the mock’s List.
+
+Validation:
+ This ensures that the controller does not perform filtering or transformation and supports returning multiple items as-is, which is essential for listing functionality.
+
+Scenario 4: Returns the exact same List instance produced by the repository (identity preservation)
+
+Details:
+ TestName: returnsSameInstanceAsRepositoryOutput
+ Description: Checks that the controller method does not create a defensive copy or wrap the List; it should return precisely the List provided by ProductRepository.findAll.
+
+Execution:
+ Arrange: Mock ProductRepository to return a specific, pre-constructed List instance and inject it into ProductController.
+ Act: Call getAllProducts.
+ Assert: Use an identity assertion (same reference) to confirm the returned List is the very same object the repository returned.
+
+Validation:
+ Identity preservation demonstrates that the controller adds no overhead or transformations, which can matter for performance and preserves the original ordering and contents exactly.
+
+Scenario 5: Preserves null elements within the returned list
+
+Details:
+ TestName: preservesNullEntriesFromRepositoryList
+ Description: Validates that if the repository’s List contains null entries, getAllProducts returns those nulls intact without filtering or sanitization.
+
+Execution:
+ Arrange: Mock ProductRepository to return a List that includes null elements at defined positions and inject it into ProductController.
+ Act: Invoke getAllProducts.
+ Assert: Assert that the returned List has the same size and that entries at the expected indices are null.
+
+Validation:
+ This verifies that the controller is a pass-through for the repository’s data, even in edge cases. It helps reveal unexpected filtering that could hide data issues downstream.
+
+Scenario 6: Propagates repository exceptions without swallowing or altering them
+
+Details:
+ TestName: propagatesRuntimeExceptionFromRepository
+ Description: Ensures that if ProductRepository.findAll throws a RuntimeException, getAllProducts propagates the same exception rather than catching or converting it.
+
+Execution:
+ Arrange: Mock ProductRepository so that findAll throws a RuntimeException; inject into ProductController.
+ Act: Call getAllProducts, expecting an exception.
+ Assert: Use an assertion that an exception of the same type is thrown.
+
+Validation:
+ This confirms error transparency, ensuring upstream clients or error handlers can react appropriately to repository failures, which is critical for reliability and observability.
+
+Scenario 7: Throws a NullPointerException when the repository dependency is not injected
+
+Details:
+ TestName: throwsNullPointerWhenRepositoryIsUninitialized
+ Description: Validates that if ProductController is instantiated without wiring ProductRepository, invoking getAllProducts results in a NullPointerException due to the null dependency.
+
+Execution:
+ Arrange: Create a ProductController instance without assigning a ProductRepository (leave the field null).
+ Act: Invoke getAllProducts.
+ Assert: Assert that a NullPointerException is thrown.
+
+Validation:
+ This test highlights the necessity of proper dependency injection and helps detect misconfiguration in non-Spring test environments or manual instantiation scenarios.
+
+Scenario 8: Returns null if the repository (improperly) returns null
+
+Details:
+ TestName: returnsNullWhenRepositoryReturnsNull
+ Description: Ensures that if ProductRepository.findAll returns null (an atypical but possible misbehavior), getAllProducts returns null rather than substituting an empty collection.
+
+Execution:
+ Arrange: Mock ProductRepository to return null for findAll and inject it into ProductController.
+ Act: Call getAllProducts.
+ Assert: Assert that the returned reference is null.
+
+Validation:
+ This exposes behavior if the repository violates typical contracts. Understanding this outcome helps prevent NullPointerExceptions in client code and guides defensive coding if necessary.
+
+Scenario 9: Returns updated data across consecutive calls reflecting repository changes
+
+Details:
+ TestName: reflectsRepositoryChangesAcrossConsecutiveCalls
+ Description: Verifies that getAllProducts does not cache results and always delegates to the repository, returning up-to-date Lists across multiple invocations.
+
+Execution:
+ Arrange: Configure the mocked ProductRepository so that sequential calls to findAll return different List instances (e.g., first empty, then non-empty), and inject into ProductController.
+ Act: Invoke getAllProducts twice, capturing results each time.
+ Assert: Assert that the first result matches the first mocked List and the second result matches the second mocked List, with distinct identities.
+
+Validation:
+ This ensures freshness of data and confirms the method has no internal caching, which is important for reflecting repository updates in real time.
+
+Scenario 10: Supports large result sets returned by the repository
+
+Details:
+ TestName: handlesLargeListFromRepository
+ Description: Confirms that getAllProducts can return a large List provided by the repository without truncation or errors.
+
+Execution:
+ Arrange: Mock ProductRepository to return a large List of Product instances and inject it into ProductController.
+ Act: Call getAllProducts.
+ Assert: Assert that the returned List size equals the large expected count and that the returned List is the same instance as provided.
+
+Validation:
+ This checks scalability characteristics at the controller boundary and verifies no inadvertent limits or processing are applied.
+
+Scenario 11: Returned type is a List and not wrapped into another response type
+
+Details:
+ TestName: returnsListTypeNotResponseEntity
+ Description: Ensures that getAllProducts returns a List directly and is not wrapped in ResponseEntity or any other container.
+
+Execution:
+ Arrange: Mock ProductRepository to return any non-null List and inject into ProductController.
+ Act: Call getAllProducts.
+ Assert: Assert that the returned object is an instance of List (e.g., via instanceof) and not an instance of ResponseEntity.
+
+Validation:
+ This confirms the method’s contract and helps prevent mismatches in client expectations, ensuring that higher layers receive a straightforward List.
+
+Scenario 12: Returns unmodifiable list unchanged when the repository supplies an unmodifiable view
+
+Details:
+ TestName: preservesUnmodifiableListBehavior
+ Description: Verifies that if the repository returns an unmodifiable List, getAllProducts returns that same unmodifiable List without altering mutability.
+
+Execution:
+ Arrange: Prepare an unmodifiable List (wrapping a small backing list), configure the mock ProductRepository to return it, and inject into ProductController.
+ Act: Call getAllProducts.
+ Assert: Assert reference identity with the unmodifiable List and assert that adding to the returned List throws UnsupportedOperationException.
+
+Validation:
+ This ensures the controller does not alter the mutability contract of the returned collection, which is important for defensive programming and avoiding side effects.
+
+Scenario 13: Does not change element order from the repository
+
+Details:
+ TestName: preservesElementOrderAsProvidedByRepository
+ Description: Confirms that the order of elements in the List returned by getAllProducts is exactly the order provided by ProductRepository.findAll.
+
+Execution:
+ Arrange: Create a List with distinct, known Product instance references in a specific order; mock ProductRepository to return this List and inject it into ProductController.
+ Act: Call getAllProducts.
+ Assert: Assert that the returned List is the same instance, which implies the order is unchanged; optionally, assert by index that returnedList.get(i) is the same reference as the i-th element of the arranged List.
+
+Validation:
+ This demonstrates that the controller does not sort, filter, or re-order the data, which is critical when consumers rely on repository-defined ordering (e.g., default database order or custom queries).
+
+Scenario 14: Returned list reference can be used directly by callers without further adaptation
+
+Details:
+ TestName: returnedListIsDirectlyUsableByCallers
+ Description: Ensures that the List returned by getAllProducts can be consumed immediately by callers (e.g., iterated over), implying it is not null and does not require conversion.
+
+Execution:
+ Arrange: Mock ProductRepository to return a simple non-empty List and inject it into ProductController.
+ Act: Call getAllProducts and store the result.
+ Assert: Assert the returned List is not null and its size matches expectations; confirm that calling standard List methods (like size and get) does not throw.
+
+Validation:
+ This confirms that the controller fulfills a straightforward contract for clients, reducing the need for defensive checks and simplifying integration.
+
+*/
+
+// ********RoostGPT********
+
+package com.bootexample4.products.controller;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import com.bootexample4.products.model.Product;
+import com.bootexample4.products.repository.ProductRepository;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.http.ResponseEntity;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@ExtendWith(MockitoExtension.class)
+public class ProductControllerGetAllProductsTest {
+
+ @Mock
+ private ProductRepository productRepository;
+
+ @InjectMocks
+ private ProductController productController;
+
+ @Test
+ @Tag("valid")
+ public void testReturnsEmptyListWhenRepositoryReturnsEmpty() {
+ List repositoryList = Collections.emptyList();
+ when(productRepository.findAll()).thenReturn(repositoryList);
+ List result = productController.getAllProducts();
+ assertNotNull(result);
+ assertEquals((int) 0, (int) result.size());
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnsSingleItemListWhenRepositoryHasOneProduct() {
+ Product singleProduct = new Product();
+ List repositoryList = Collections.singletonList(singleProduct);
+ when(productRepository.findAll()).thenReturn(repositoryList);
+ List result = productController.getAllProducts();
+ assertEquals((int) 1, (int) result.size());
+ assertSame((Object) repositoryList, (Object) result);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnsMultipleItemsWhenRepositoryHasSeveralProducts() {
+ List repositoryList = Arrays.asList(new Product(), new Product(), new Product());
+ when(productRepository.findAll()).thenReturn(repositoryList);
+ List result = productController.getAllProducts();
+ assertEquals((int) repositoryList.size(), (int) result.size());
+ assertSame((Object) repositoryList, (Object) result);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnsSameInstanceAsRepositoryOutput() {
+ List specificList = new ArrayList<>();
+ specificList.add(new Product());
+ when(productRepository.findAll()).thenReturn(specificList);
+ List result = productController.getAllProducts();
+ assertSame((Object) specificList, (Object) result);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testPreservesNullEntriesFromRepositoryList() {
+ List repositoryList = Arrays.asList(null, new Product(), null);
+ when(productRepository.findAll()).thenReturn(repositoryList);
+ List result = productController.getAllProducts();
+ assertEquals((int) repositoryList.size(), (int) result.size());
+ assertNull(result.get(0));
+ assertNotNull(result.get(1));
+ assertNull(result.get(2));
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testPropagatesRuntimeExceptionFromRepository() {
+ RuntimeException expected = new RuntimeException();
+ when(productRepository.findAll()).thenThrow(expected);
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ productController.getAllProducts();
+ });
+ assertSame((Object) expected, (Object) thrown);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testThrowsNullPointerWhenRepositoryIsUninitialized() {
+ ProductController uninitializedController = new ProductController(); // TODO:
+ // Ensure
+ // proper
+ // wiring
+ // in
+ // production
+ assertThrows(NullPointerException.class, () -> {
+ uninitializedController.getAllProducts();
+ });
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testReturnsNullWhenRepositoryReturnsNull() {
+ when(productRepository.findAll()).thenReturn(null);
+ List result = productController.getAllProducts();
+ assertNull(result);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReflectsRepositoryChangesAcrossConsecutiveCalls() {
+ List firstList = Collections.emptyList();
+ List secondList = Arrays.asList(new Product(), new Product());
+ when(productRepository.findAll()).thenReturn(firstList, secondList);
+ List result1 = productController.getAllProducts();
+ List result2 = productController.getAllProducts();
+ assertSame((Object) firstList, (Object) result1);
+ assertSame((Object) secondList, (Object) result2);
+ assertNotSame((Object) result1, (Object) result2);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testHandlesLargeListFromRepository() {
+ int largeCount = 1000; // TODO: Adjust size based on performance constraints if
+ // needed
+ List largeList = new ArrayList<>();
+ for (int i = 0; i < largeCount; i++) {
+ largeList.add(new Product());
+ }
+ when(productRepository.findAll()).thenReturn(largeList);
+ List result = productController.getAllProducts();
+ assertEquals((int) largeCount, (int) result.size());
+ assertSame((Object) largeList, (Object) result);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnsListTypeNotResponseEntity() {
+ List repositoryList = Arrays.asList(new Product());
+ when(productRepository.findAll()).thenReturn(repositoryList);
+ List result = productController.getAllProducts();
+ assertNotNull(result);
+ assertTrue((boolean) (result instanceof List));
+ assertFalse((boolean) (result instanceof ResponseEntity));
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testPreservesUnmodifiableListBehavior() {
+ List base = new ArrayList<>();
+ base.add(new Product());
+ List unmodifiable = Collections.unmodifiableList(base);
+ when(productRepository.findAll()).thenReturn(unmodifiable);
+ List result = productController.getAllProducts();
+ assertSame((Object) unmodifiable, (Object) result);
+ assertThrows(UnsupportedOperationException.class, () -> {
+ result.add(new Product());
+ });
+ }
+
+ @Test
+ @Tag("valid")
+ public void testPreservesElementOrderAsProvidedByRepository() {
+ Product p1 = new Product();
+ Product p2 = new Product();
+ Product p3 = new Product();
+ List orderedList = Arrays.asList(p1, p2, p3);
+ when(productRepository.findAll()).thenReturn(orderedList);
+ List result = productController.getAllProducts();
+ assertSame((Object) orderedList, (Object) result);
+ assertSame((Object) p1, (Object) result.get(0));
+ assertSame((Object) p2, (Object) result.get(1));
+ assertSame((Object) p3, (Object) result.get(2));
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnedListIsDirectlyUsableByCallers() {
+ List repositoryList = Arrays.asList(new Product(), new Product());
+ when(productRepository.findAll()).thenReturn(repositoryList);
+ List result = productController.getAllProducts();
+ assertNotNull(result);
+ assertEquals((int) repositoryList.size(), (int) result.size());
+ assertNotNull(result.get(0));
+ assertNotNull(result.get(1));
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/controller/ProductControllerGetProductByIdTest.java b/src/test/java/com/bootexample4/products/controller/ProductControllerGetProductByIdTest.java
new file mode 100644
index 00000000..803ee655
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/controller/ProductControllerGetProductByIdTest.java
@@ -0,0 +1,461 @@
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=getProductById_a31a3ac160
+ROOST_METHOD_SIG_HASH=getProductById_d22f3ea272
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void the_client_sends_a_GET_request_to_get_a_product_by_its_id(String string) {
+
+ Long id = getProductIDfromAPI(string);
+ getProductByIdResponse = productController.getProductById(id);
+ responseStatusCode = getProductByIdResponse.getStatusCode();
+}
+"
+ "@Test
+public void the_product_with_ID_should_be_updated_with_the_provided_details(Long id) {
+
+ Product updatedProduct = productController.getProductById(id).getBody();
+ assertEquals(newProduct.getDescription(), updatedProduct.getDescription());
+ assertEquals(newProduct.getName(), updatedProduct.getName());
+ assertEquals(newProduct.getPrice(), updatedProduct.getPrice());
+}
+"
+ "@Test
+public void the_product_with_id_should_no_longer_exist(Long id) {
+
+ getProductByIdResponse = productController.getProductById(id);
+ assertEquals(HttpStatus.NOT_FOUND, getProductByIdResponse.getStatusCode());
+}
+"Scenario 1: Returns 200 OK with a Product when the ID exists
+
+Details:
+ TestName: returnsOkAndProductBodyWhenIdExists
+ Description: Validates that when productRepository.findById(id) returns a non-empty Optional, the controller method getProductById responds with an HTTP 200 OK and includes the Product in the response body.
+
+Execution:
+ Arrange: Configure a test double for ProductRepository so that findById(validId) returns Optional.of(existingProduct). Instantiate ProductController with this repository.
+ Act: Invoke getProductById(validId) on the ProductController.
+ Assert: Verify that the ResponseEntity status code is 200 OK, the body is not null, and the body refers to the same Product instance returned by the repository.
+
+Validation:
+ This confirms the happy-path behavior: when the product exists, the controller maps it directly into a ResponseEntity.ok(body). It verifies correctness of status code and that the body is the expected Product instance without alteration.
+
+
+Scenario 2: Returns 404 Not Found with a null body when the ID does not exist
+
+Details:
+ TestName: returnsNotFoundAndNullBodyWhenIdMissing
+ Description: Ensures that if productRepository.findById(id) returns Optional.empty(), the controller method returns a 404 Not Found with no body.
+
+Execution:
+ Arrange: Configure ProductRepository test double so that findById(missingId) returns Optional.empty().
+ Act: Call getProductById(missingId) on the ProductController.
+ Assert: Verify that the ResponseEntity status code is 404 Not Found and the response body is null.
+
+Validation:
+ This verifies the “not found” path via ResponseEntity.notFound().build(), ensuring the method correctly represents absence of a resource with the appropriate HTTP status and an empty body.
+
+
+Scenario 3: Throws IllegalArgumentException when the ID is null
+
+Details:
+ TestName: throwsIllegalArgumentExceptionWhenIdIsNull
+ Description: Confirms that passing a null ID results in an exception from the repository layer, which the controller does not handle and thus propagates.
+
+Execution:
+ Arrange: Configure ProductRepository to throw an IllegalArgumentException when findById(null) is invoked. Initialize ProductController with this repository.
+ Act: Invoke getProductById(null).
+ Assert: Expect an IllegalArgumentException to be thrown.
+
+Validation:
+ Spring Data repositories typically reject null IDs. Because the controller does not catch this, the exception should propagate, making this behavior explicit and ensuring the test suite documents this edge case.
+
+
+Scenario 4: Returns 404 Not Found for a negative ID
+
+Details:
+ TestName: returnsNotFoundForNegativeId
+ Description: Verifies that passing a negative ID yields a 404 Not Found when the repository returns Optional.empty() for such IDs.
+
+Execution:
+ Arrange: Stub ProductRepository so that findById(negativeId) returns Optional.empty().
+ Act: Call getProductById(negativeId).
+ Assert: Confirm the ResponseEntity status code is 404 Not Found and the body is null.
+
+Validation:
+ Negative IDs are commonly invalid in persistence layers. The controller should treat this as a not-found case and return a proper 404.
+
+
+Scenario 5: Returns 404 Not Found for an ID of zero
+
+Details:
+ TestName: returnsNotFoundForZeroId
+ Description: Ensures that an ID value of zero, which typically does not correspond to a persisted entity, results in a 404 Not Found.
+
+Execution:
+ Arrange: Make ProductRepository return Optional.empty() when findById(0L) is called.
+ Act: Invoke getProductById(0L).
+ Assert: Verify the status code is 404 Not Found and the body is null.
+
+Validation:
+ Validates boundary handling for ID=0, confirming the method returns the standard not-found response for invalid identifiers.
+
+
+Scenario 6: Returns 404 Not Found for a very large non-existing ID (Long.MAX_VALUE)
+
+Details:
+ TestName: returnsNotFoundForMaxLongId
+ Description: Checks behavior with an extreme upper-bound ID that does not exist in the data store.
+
+Execution:
+ Arrange: Configure ProductRepository so that findById(Long.MAX_VALUE) returns Optional.empty().
+ Act: Invoke getProductById(Long.MAX_VALUE).
+ Assert: Confirm the ResponseEntity status code is 404 Not Found and the body is null.
+
+Validation:
+ Ensures the controller handles extreme numeric inputs gracefully, maintaining correct semantics for non-existent resources.
+
+
+Scenario 7: Propagates repository runtime exceptions
+
+Details:
+ TestName: propagatesRepositoryRuntimeException
+ Description: Ensures that if the repository throws a RuntimeException during findById, the controller does not swallow it and the exception propagates to the caller.
+
+Execution:
+ Arrange: Set up ProductRepository so that findById(validId) throws a RuntimeException (e.g., simulating a data access failure).
+ Act: Invoke getProductById(validId).
+ Assert: Expect a RuntimeException to be thrown.
+
+Validation:
+ Documents the controller’s lack of internal error handling for repository failures, so upstream layers (or global exception handlers) can deal with such errors appropriately.
+
+
+Scenario 8: Calls ProductRepository.findById exactly once with the provided ID
+
+Details:
+ TestName: callsFindByIdOnceWithGivenId
+ Description: Verifies interaction with the repository: the controller should make exactly one call to findById with the same ID it was given.
+
+Execution:
+ Arrange: Create a mock ProductRepository and set it to return Optional.empty() or Optional.of(product) as convenient.
+ Act: Invoke getProductById(someId).
+ Assert: Verify that findById was called once with someId and that there were no additional repository interactions such as save or delete.
+
+Validation:
+ Confirms minimal and correct repository interaction, ensuring no unintended side effects occur during a read operation.
+
+
+Scenario 9: Returns the same Product instance reference provided by the repository
+
+Details:
+ TestName: returnsSameProductInstanceFromRepository
+ Description: Ensures that the controller does not modify or replace the Product instance and simply forwards the repository’s Product in the response body.
+
+Execution:
+ Arrange: Stub ProductRepository to return Optional.of(specificProductInstance).
+ Act: Call getProductById(validId).
+ Assert: Verify that response.getBody() is the exact same instance (by reference) as specificProductInstance.
+
+Validation:
+ Confirms the method’s mapping behavior is a direct pass-through, avoiding unintended transformations or copies of the entity.
+
+
+Scenario 10: Returns consistent responses on repeated calls for the same existing ID
+
+Details:
+ TestName: returnsConsistentResponsesOnRepeatedCallsForSameId
+ Description: Ensures idempotency for read operations: multiple calls with the same existing ID should consistently return 200 OK with the same Product reference from the repository.
+
+Execution:
+ Arrange: Stub ProductRepository to return Optional.of(existingProduct) for findById(validId).
+ Act: Invoke getProductById(validId) twice, capturing both ResponseEntity results.
+ Assert: Verify both responses have status 200 OK and both bodies are the same instance as existingProduct.
+
+Validation:
+ Establishes consistent, repeatable behavior for reads, important for clients that may retry GET operations.
+
+
+Scenario 11: Does not call save or delete during a read operation
+
+Details:
+ TestName: doesNotSaveOrDeleteDuringLookup
+ Description: Ensures the controller adheres to read-only behavior by not invoking mutating repository methods during getProductById.
+
+Execution:
+ Arrange: Use a mock ProductRepository configured for findById as needed (either present or empty).
+ Act: Invoke getProductById(anyId).
+ Assert: Verify that only findById was called and that save or delete methods were never called.
+
+Validation:
+ Guards against side effects in read endpoints, preserving data integrity and expected REST semantics.
+
+
+Scenario 12: Not-found responses contain no body while carrying a 404 status
+
+Details:
+ TestName: notFoundResponseHasNullBody
+ Description: Explicitly verifies that the not-found path returns a ResponseEntity with a null body rather than an empty Product or placeholder object.
+
+Execution:
+ Arrange: Configure ProductRepository so that findById(missingId) returns Optional.empty().
+ Act: Invoke getProductById(missingId).
+ Assert: Check that the status code is 404 Not Found and response.getBody() is null.
+
+Validation:
+ Ensures clients can reliably interpret a null body in tandem with a 404 status to detect absence of the resource, avoiding ambiguity.
+
+
+Scenario 13: Correct HTTP status and body type are returned when the Product is found
+
+Details:
+ TestName: returnsOkStatusAndProductTypeWhenFound
+ Description: Confirms both the status code (200 OK) and that the body is of type Product when the repository finds a match.
+
+Execution:
+ Arrange: Stub ProductRepository to return Optional.of(product).
+ Act: Call getProductById(validId).
+ Assert: Verify status code is 200 OK and that the object returned by response.getBody() is an instance of Product.
+
+Validation:
+ Adds a type-safety check for the response body, reinforcing the generic contract ResponseEntity in practice.
+
+
+Scenario 14: Handles repository Optional semantics correctly with the map/orElse chain
+
+Details:
+ TestName: mapsOptionalToOkOrNotFoundCorrectly
+ Description: Validates the Optional mapping logic by checking both branches: present leads to OK with body, absent leads to 404 without body.
+
+Execution:
+ Arrange:
+ - Case A: findById(idPresent) returns Optional.of(productPresent).
+ - Case B: findById(idAbsent) returns Optional.empty().
+ Act:
+ - Call getProductById(idPresent).
+ - Call getProductById(idAbsent).
+ Assert:
+ - Case A: Response has 200 OK and non-null body equal to productPresent.
+ - Case B: Response has 404 Not Found and null body.
+
+Validation:
+ Ensures the lambda and orElse pathway is correctly implemented, covering both branches and demonstrating the intended control flow.
+
+*/
+
+// ********RoostGPT********
+
+package com.bootexample4.products.controller;
+
+import com.bootexample4.products.model.Product;
+import com.bootexample4.products.repository.ProductRepository;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import java.util.Optional;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+import org.junit.jupiter.api.*;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@ExtendWith(MockitoExtension.class)
+public class ProductControllerGetProductByIdTest {
+
+ @Mock
+ private ProductRepository productRepository;
+
+ @InjectMocks
+ private ProductController productController;
+
+ @Test
+ @Tag("valid")
+ public void returnsOkAndProductBodyWhenIdExists() {
+ Long validId = 1L;
+ Product existingProduct = new Product();
+ when(productRepository.findById(validId)).thenReturn(Optional.of(existingProduct));
+ ResponseEntity response = productController.getProductById(validId);
+ assertEquals((HttpStatus) HttpStatus.OK, (HttpStatus) response.getStatusCode());
+ assertNotNull((Object) response.getBody());
+ assertSame((Product) existingProduct, (Product) response.getBody());
+ verify(productRepository, times(1)).findById(validId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void returnsNotFoundAndNullBodyWhenIdMissing() {
+ Long missingId = 999L;
+ when(productRepository.findById(missingId)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.getProductById(missingId);
+ assertEquals((HttpStatus) HttpStatus.NOT_FOUND, (HttpStatus) response.getStatusCode());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(missingId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void throwsIllegalArgumentExceptionWhenIdIsNull() {
+ when(productRepository.findById((Long) null)).thenThrow(new IllegalArgumentException());
+ assertThrows(IllegalArgumentException.class, () -> productController.getProductById(null));
+ verify(productRepository, times(1)).findById((Long) null);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void returnsNotFoundForNegativeId() {
+ Long negativeId = -1L;
+ when(productRepository.findById(negativeId)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.getProductById(negativeId);
+ assertEquals((HttpStatus) HttpStatus.NOT_FOUND, (HttpStatus) response.getStatusCode());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(negativeId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void returnsNotFoundForZeroId() {
+ Long zeroId = 0L;
+ when(productRepository.findById(zeroId)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.getProductById(zeroId);
+ assertEquals((HttpStatus) HttpStatus.NOT_FOUND, (HttpStatus) response.getStatusCode());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(zeroId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void returnsNotFoundForMaxLongId() {
+ Long maxId = Long.MAX_VALUE;
+ when(productRepository.findById(maxId)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.getProductById(maxId);
+ assertEquals((HttpStatus) HttpStatus.NOT_FOUND, (HttpStatus) response.getStatusCode());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(maxId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void propagatesRepositoryRuntimeException() {
+ Long validId = 2L;
+ when(productRepository.findById(validId)).thenThrow(new RuntimeException());
+ assertThrows(RuntimeException.class, () -> productController.getProductById(validId));
+ verify(productRepository, times(1)).findById(validId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void callsFindByIdOnceWithGivenId() {
+ Long someId = 10L;
+ when(productRepository.findById(someId)).thenReturn(Optional.empty());
+ productController.getProductById(someId);
+ verify(productRepository, times(1)).findById(someId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void returnsSameProductInstanceFromRepository() {
+ Long validId = 3L;
+ Product specificProductInstance = new Product();
+ when(productRepository.findById(validId)).thenReturn(Optional.of(specificProductInstance));
+ ResponseEntity response = productController.getProductById(validId);
+ assertEquals((HttpStatus) HttpStatus.OK, (HttpStatus) response.getStatusCode());
+ assertSame((Product) specificProductInstance, (Product) response.getBody());
+ verify(productRepository, times(1)).findById(validId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void returnsConsistentResponsesOnRepeatedCallsForSameId() {
+ Long validId = 4L;
+ Product existingProduct = new Product();
+ when(productRepository.findById(validId)).thenReturn(Optional.of(existingProduct));
+ ResponseEntity response1 = productController.getProductById(validId);
+ ResponseEntity response2 = productController.getProductById(validId);
+ assertEquals((HttpStatus) HttpStatus.OK, (HttpStatus) response1.getStatusCode());
+ assertEquals((HttpStatus) HttpStatus.OK, (HttpStatus) response2.getStatusCode());
+ assertSame((Product) existingProduct, (Product) response1.getBody());
+ assertSame((Product) existingProduct, (Product) response2.getBody());
+ verify(productRepository, times(2)).findById(validId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void doesNotSaveOrDeleteDuringLookup() {
+ Long anyId = 5L;
+ when(productRepository.findById(anyId)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.getProductById(anyId);
+ assertEquals((HttpStatus) HttpStatus.NOT_FOUND, (HttpStatus) response.getStatusCode());
+ verify(productRepository, times(1)).findById(anyId);
+ verify(productRepository, never()).save(any());
+ verify(productRepository, never()).delete(any());
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void notFoundResponseHasNullBody() {
+ Long missingId = 6L;
+ when(productRepository.findById(missingId)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.getProductById(missingId);
+ assertEquals((HttpStatus) HttpStatus.NOT_FOUND, (HttpStatus) response.getStatusCode());
+ assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(missingId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void returnsOkStatusAndProductTypeWhenFound() {
+ Long validId = 7L;
+ Product product = new Product();
+ when(productRepository.findById(validId)).thenReturn(Optional.of(product));
+ ResponseEntity response = productController.getProductById(validId);
+ assertEquals((HttpStatus) HttpStatus.OK, (HttpStatus) response.getStatusCode());
+ assertTrue(Product.class.isInstance((Object) response.getBody()));
+ verify(productRepository, times(1)).findById(validId);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void mapsOptionalToOkOrNotFoundCorrectly() {
+ Long idPresent = 100L;
+ Long idAbsent = 101L;
+ Product productPresent = new Product();
+ when(productRepository.findById(idPresent)).thenReturn(Optional.of(productPresent));
+ when(productRepository.findById(idAbsent)).thenReturn(Optional.empty());
+ ResponseEntity responsePresent = productController.getProductById(idPresent);
+ ResponseEntity responseAbsent = productController.getProductById(idAbsent);
+ assertEquals((HttpStatus) HttpStatus.OK, (HttpStatus) responsePresent.getStatusCode());
+ assertSame((Product) productPresent, (Product) responsePresent.getBody());
+ assertEquals((HttpStatus) HttpStatus.NOT_FOUND, (HttpStatus) responseAbsent.getStatusCode());
+ assertNull((Object) responseAbsent.getBody());
+ verify(productRepository, times(1)).findById(idPresent);
+ verify(productRepository, times(1)).findById(idAbsent);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/controller/ProductControllerUpdateProductTest.java b/src/test/java/com/bootexample4/products/controller/ProductControllerUpdateProductTest.java
new file mode 100644
index 00000000..e00933b1
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/controller/ProductControllerUpdateProductTest.java
@@ -0,0 +1,710 @@
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=updateProduct_e220585694
+ROOST_METHOD_SIG_HASH=updateProduct_9454a9af90
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void the_client_sends_a_put_request_to(String string) {
+
+ updateProductResponse = productController.updateProduct(getProductIDfromAPI(string), newProduct);
+ responseStatusCode = updateProductResponse.getStatusCode();
+}
+"Scenario 1: Successful update of an existing product returns 200 OK with the saved product in the response body
+
+Details:
+ TestName: updateExistingProductReturnsOkAndUpdatedBody
+ Description: Verifies that when a product with the given id exists, the controller updates the existing entity’s name, description, and price using the incoming Product values, saves it via ProductRepository.save, and returns ResponseEntity with HTTP 200 OK and the saved Product as the body.
+
+Execution:
+ Arrange: Set up ProductRepository.findById(id) to return an Optional containing an existing Product. Configure ProductRepository.save(existingProduct) to return an updated Product instance (which could be the same instance with updated fields or a new instance reflecting the same values).
+ Act: Invoke ProductController.updateProduct(id, incomingProduct) with the populated incoming Product containing name, description, and price.
+ Assert: Verify the ResponseEntity status code is 200 OK and the response body equals the Product returned by ProductRepository.save.
+
+Validation:
+ Confirms that the method correctly executes the happy path: finds the product, applies incoming fields, saves the product, and returns the saved instance in the response with status 200 OK. This ensures the core update functionality behaves as intended.
+
+
+Scenario 2: Attempting to update a non-existing product returns 404 Not Found and no body
+
+Details:
+ TestName: updateNonExistingProductReturnsNotFound
+ Description: Ensures the controller returns ResponseEntity.notFound().build() when no product is found for the given id, and does not attempt to save.
+
+Execution:
+ Arrange: Set up ProductRepository.findById(id) to return Optional.empty().
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify the ResponseEntity status code is 404 Not Found and the body is null. Confirm that ProductRepository.save is never called.
+
+Validation:
+ Confirms proper handling of absent resources by returning a 404 response and not performing unnecessary save operations. This aligns with RESTful semantics for updates on missing resources.
+
+
+Scenario 3: Updating with null fields (name/description/price) persists null values and returns 200 OK
+
+Details:
+ TestName: updateWithNullFieldsPersistsNullValuesAndReturnsOk
+ Description: Validates that if the incoming Product has null values for name, description, or price, these nulls are applied to the existing product, persisted, and returned with HTTP 200 OK, as there is no validation in the method.
+
+Execution:
+ Arrange: Set up ProductRepository.findById(id) to return an existing Product. Configure ProductRepository.save(existingProduct) to return the same instance after mutation.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct) where incomingProduct has null for one or more of: name, description, price.
+ Assert: Verify status is 200 OK. Verify that the Product passed to save reflects the null values set on the existing product and the response body matches the saved Product.
+
+Validation:
+ Demonstrates that the method blindly applies incoming values (including nulls) and persists them. This is important to highlight potential data clearing behavior in absence of validation.
+
+
+Scenario 4: Passing a null Product with an existing id throws a NullPointerException
+
+Details:
+ TestName: updateWithNullProductAndExistingIdThrowsNullPointerException
+ Description: Since the method dereferences product to read its fields inside the map, a null product argument will cause a NullPointerException when the id is found.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return an existing Product.
+ Act: Invoke ProductController.updateProduct(id, null).
+ Assert: Expect a NullPointerException to be thrown.
+
+Validation:
+ Confirms that the method does not handle null @RequestBody and will fail fast with NPE when an existing product is found. This highlights the need for input validation or null checks in the controller layer.
+
+
+Scenario 5: Passing a null Product with a non-existing id returns 404 Not Found without throwing an exception
+
+Details:
+ TestName: updateWithNullProductAndMissingIdReturnsNotFound
+ Description: Ensures that when the product is null but the repository does not find the entity, the lambda that dereferences product is never executed, resulting in a 404 response.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return Optional.empty().
+ Act: Invoke ProductController.updateProduct(id, null).
+ Assert: Verify the ResponseEntity status code is 404 Not Found and no exception is thrown. Verify ProductRepository.save is not invoked.
+
+Validation:
+ Demonstrates short-circuit behavior: with no existing product, the null product is irrelevant and no NPE occurs. Ensures predictable behavior for missing resources.
+
+
+Scenario 6: An exception thrown by ProductRepository.findById is propagated by updateProduct
+
+Details:
+ TestName: findByIdThrowsExceptionIsPropagatedFromUpdate
+ Description: Validates that if ProductRepository.findById throws a runtime exception (e.g., a data access error), the controller method does not swallow it and the exception propagates.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to throw a RuntimeException.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify that the same RuntimeException is thrown.
+
+Validation:
+ Confirms exception transparency, helping tests ensure error conditions propagate to higher layers for global handling.
+
+
+Scenario 7: An exception thrown by ProductRepository.save is propagated by updateProduct
+
+Details:
+ TestName: saveThrowsExceptionIsPropagatedFromUpdate
+ Description: Ensures that if saving the updated product fails and ProductRepository.save throws a runtime exception, the method does not catch it and the exception propagates.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return an existing Product. Configure ProductRepository.save(existingProduct) to throw a RuntimeException.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify that a RuntimeException is thrown.
+
+Validation:
+ Confirms error propagation during persistence operations, ensuring that infrastructure exceptions aren’t silently ignored.
+
+
+Scenario 8: Passing a null id that the repository treats as non-existing returns 404 Not Found
+
+Details:
+ TestName: updateWithNullIdReturnsNotFoundIfRepositoryHandlesNull
+ Description: If ProductRepository.findById(null) is allowed and returns Optional.empty(), the controller should behave as for a missing entity and return 404.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(null) to return Optional.empty().
+ Act: Invoke ProductController.updateProduct(null, incomingProduct).
+ Assert: Verify the ResponseEntity status code is 404 Not Found and ProductRepository.save is not called.
+
+Validation:
+ Ensures consistent behavior even with null path variables when the repository returns empty, aligning with “resource not found” semantics.
+
+
+Scenario 9: Passing a null id that causes the repository to throw an exception results in the exception being propagated
+
+Details:
+ TestName: updateWithNullIdAndRepositoryThrowsPropagatesException
+ Description: Validates that if ProductRepository.findById(null) throws an IllegalArgumentException (or any runtime exception), the controller method doesn’t handle it and the exception bubbles up.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(null) to throw an IllegalArgumentException.
+ Act: Invoke ProductController.updateProduct(null, incomingProduct).
+ Assert: Verify that an IllegalArgumentException is thrown.
+
+Validation:
+ Confirms robustness of exception flow for invalid path variables, allowing global exception handlers to manage such cases.
+
+
+Scenario 10: Incoming fields are applied to the existing product before save
+
+Details:
+ TestName: updateAppliesIncomingFieldsToExistingBeforeSave
+ Description: Ensures that the method calls setters on the existing product using incoming Product values (name, description, price) before invoking save.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return an existing Product with initial values. Prepare an incoming Product with distinct name, description, and price. Stub ProductRepository.save to return the mutated existing product.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify that the Product instance passed to ProductRepository.save has its name, description, and price equal to those from the incoming Product. Verify the response is 200 OK.
+
+Validation:
+ Confirms that the update logic modifies the located entity rather than saving the incoming Product directly, preserving the existing entity context while updating fields.
+
+
+Scenario 11: On a successful update, findById and save are each invoked exactly once
+
+Details:
+ TestName: updateInvokesFindByIdAndSaveOnceOnSuccess
+ Description: Verifies the interaction count with the repository: the controller should perform a single lookup and a single save for a successful update.
+
+Execution:
+ Arrange: Mock ProductRepository.findById(id) to return an existing Product and ProductRepository.save to return the saved object.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify that ProductRepository.findById(id) is called once and ProductRepository.save(existingProduct) is called once.
+
+Validation:
+ Ensures efficient and predictable repository interactions, which is crucial for performance and correctness in transactional flows.
+
+
+Scenario 12: When the product is not found, save is never called
+
+Details:
+ TestName: updateDoesNotCallSaveWhenProductMissing
+ Description: Confirms that the controller does not attempt to save when no existing product is found.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return Optional.empty().
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify ProductRepository.save is never invoked; verify response status is 404 Not Found.
+
+Validation:
+ Prevents unintended creation or save operations for non-existing resources, aligning with the idempotent nature of PUT on missing entities (as implemented here).
+
+
+Scenario 13: The response body is exactly the object returned by ProductRepository.save
+
+Details:
+ TestName: updateReturnsOkAndBodyIsValueReturnedBySave
+ Description: Ensures the controller returns the object ProductRepository.save produced, not merely the pre-save entity, and wraps it in ResponseEntity.ok().
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return an existing Product. Configure ProductRepository.save to return a specific object (potentially a different instance).
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify ResponseEntity status is 200 OK and the body is the exact object returned from save.
+
+Validation:
+ Confirms the controller relies on the repository’s returned value (which may include persisted changes, generated fields, or managed states) for the response body.
+
+
+Scenario 14: If ProductRepository.save returns null, the controller still returns 200 OK with a null body
+
+Details:
+ TestName: updateReturnsOkEvenWhenRepositorySaveReturnsNullBody
+ Description: Tests a defensive edge case where the repository returns null; the code will place null in ResponseEntity.ok().body.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return an existing Product. Stub ProductRepository.save to return null.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify ResponseEntity status is 200 OK and the body is null.
+
+Validation:
+ Highlights potential pitfalls when repository implementations return null, ensuring the current logic’s behavior is understood and accounted for in the application.
+
+
+Scenario 15: The method accepts edge-case numeric values (e.g., negative or very large price) without validation and returns 200 OK
+
+Details:
+ TestName: updateAllowsEdgeCaseValuesWithoutValidationAndReturnsOk
+ Description: Verifies that the method does not validate price ranges and will persist any numeric value provided in incomingProduct, returning 200 OK.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return an existing Product. Provide an incoming Product with a negative or unusually large price value. Stub ProductRepository.save to return the updated Product.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify 200 OK response and that the Product passed to save has the edge-case price value set.
+
+Validation:
+ Emphasizes that, in its current form, the controller enforces no business validation on price, which may need addressing in future requirements.
+
+
+Scenario 16: Updating with identical field values still invokes save and returns 200 OK
+
+Details:
+ TestName: updateWithIdenticalFieldsStillSavesAndReturnsOk
+ Description: Ensures that even if incoming name, description, and price match those of the existing product, the method still calls save and returns 200 OK.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return an existing Product with values that match those of incomingProduct. Stub ProductRepository.save to return the product.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify save is invoked once and response status is 200 OK with the saved product in the body.
+
+Validation:
+ Confirms that the update path does not short-circuit on no-op updates and still persists, which may be relevant for audit or consistency.
+
+
+Scenario 17: Not Found responses have an empty body
+
+Details:
+ TestName: updateNotFoundHasNoBody
+ Description: Ensures that when the product is not found, the ResponseEntity resulting from ResponseEntity.notFound().build() has no body.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return Optional.empty().
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify status is 404 Not Found and the body is null.
+
+Validation:
+ Establishes the response contract for missing resources—clients should not expect a body with 404 responses.
+
+
+Scenario 18: The existing product instance (not the incoming product) is the one passed to save
+
+Details:
+ TestName: updateSavesMutatedExistingProductInstance
+ Description: Confirms that the controller mutates the product returned by findById and passes that instance to save, rather than saving the incoming Product.
+
+Execution:
+ Arrange: Configure ProductRepository.findById(id) to return a specific existing Product instance. Keep a distinct incoming Product instance with different field values. Stub ProductRepository.save to return the same existing instance after mutation.
+ Act: Invoke ProductController.updateProduct(id, incomingProduct).
+ Assert: Verify that ProductRepository.save is called with the existing Product instance (not the incoming one) and that the response is 200 OK with the saved instance as the body.
+
+Validation:
+ Ensures the update semantics preserve entity identity (managed instance) and only apply field changes, which is a common pattern with ORM-backed repositories.
+
+*/
+
+// ********RoostGPT********
+package com.bootexample4.products.controller;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.ArgumentCaptor;
+import org.mockito.junit.jupiter.MockitoExtension;
+import static org.mockito.Mockito.*;
+import java.util.Optional;
+import java.math.BigDecimal;
+import org.springframework.http.ResponseEntity;
+import com.bootexample4.products.model.Product;
+import com.bootexample4.products.repository.ProductRepository;
+import org.junit.jupiter.api.*;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@ExtendWith(MockitoExtension.class)
+public class ProductControllerUpdateProductTest {
+
+ @InjectMocks
+ private ProductController productController;
+
+ @Mock
+ private ProductRepository productRepository;
+
+ @Test
+ @Tag("valid")
+ public void testUpdateExistingProductReturnsOkAndUpdatedBody() {
+ Long id = 1L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ Product saved = new Product();
+ saved.setName("NameB");
+ saved.setDescription("DescB");
+ saved.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(existing)).thenReturn(saved);
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ Assertions.assertSame((Object) saved, (Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).save(existing);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testUpdateNonExistingProductReturnsNotFound() {
+ Long id = 2L;
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 404, (int) response.getStatusCodeValue());
+ Assertions.assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).save(any(Product.class));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testUpdateWithNullFieldsPersistsNullValuesAndReturnsOk() {
+ Long id = 3L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName(null);
+ incoming.setDescription(null);
+ // NOTE: Product.price is a primitive double (per current model/getter). It cannot
+ // be null.
+ // To support nullability or monetary precision, consider changing price to
+ // Double/BigDecimal in the model and update method.
+ // For now, we do not set price on the incoming product, which defaults to 0.0d
+ // and will be persisted as such.
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(any(Product.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0, Product.class));
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Product.class);
+ verify(productRepository, times(1)).save(captor.capture());
+ Product savedArg = captor.getValue();
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ Assertions.assertSame((Object) savedArg, (Object) response.getBody());
+ Assertions.assertNull((Object) savedArg.getName());
+ Assertions.assertNull((Object) savedArg.getDescription());
+ Assertions.assertEquals((double) 0.0d, (double) savedArg.getPrice(), (double) 0.0d);
+ verify(productRepository, times(1)).findById(id);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testUpdateWithNullProductAndExistingIdThrowsNullPointerException() {
+ Long id = 4L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ NullPointerException ex = Assertions.assertThrows(NullPointerException.class,
+ () -> productController.updateProduct(id, null));
+ Assertions.assertTrue((boolean) (ex != null));
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).save(any(Product.class));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testUpdateWithNullProductAndMissingIdReturnsNotFound() {
+ Long id = 5L;
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.updateProduct(id, null);
+ Assertions.assertEquals((int) 404, (int) response.getStatusCodeValue());
+ Assertions.assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).save(any(Product.class));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testFindByIdThrowsExceptionIsPropagatedFromUpdate() {
+ Long id = 6L;
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ RuntimeException expected = new RuntimeException();
+ when(productRepository.findById(id)).thenThrow(expected);
+ RuntimeException thrown = Assertions.assertThrows(RuntimeException.class,
+ () -> productController.updateProduct(id, incoming));
+ Assertions.assertSame((Object) expected, (Object) thrown);
+ verify(productRepository, times(1)).findById(id);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testSaveThrowsExceptionIsPropagatedFromUpdate() {
+ Long id = 7L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ RuntimeException expected = new RuntimeException();
+ when(productRepository.save(existing)).thenThrow(expected);
+ RuntimeException thrown = Assertions.assertThrows(RuntimeException.class,
+ () -> productController.updateProduct(id, incoming));
+ Assertions.assertSame((Object) expected, (Object) thrown);
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).save(existing);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testUpdateWithNullIdReturnsNotFoundIfRepositoryHandlesNull() {
+ Long id = null;
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById((Long) null)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 404, (int) response.getStatusCodeValue());
+ Assertions.assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById((Long) null);
+ verify(productRepository, never()).save(any(Product.class));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testUpdateWithNullIdAndRepositoryThrowsPropagatesException() {
+ Long id = null;
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ IllegalArgumentException expected = new IllegalArgumentException();
+ when(productRepository.findById((Long) null)).thenThrow(expected);
+ IllegalArgumentException thrown = Assertions.assertThrows(IllegalArgumentException.class,
+ () -> productController.updateProduct(id, incoming));
+ Assertions.assertSame((Object) expected, (Object) thrown);
+ verify(productRepository, times(1)).findById((Long) null);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testUpdateAppliesIncomingFieldsToExistingBeforeSave() {
+ Long id = 8L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(any(Product.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0, Product.class));
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Product.class);
+ verify(productRepository).save(captor.capture());
+ Product passedToSave = captor.getValue();
+ Assertions.assertSame((Object) existing, (Object) passedToSave);
+ Assertions.assertEquals((Object) incoming.getName(), (Object) passedToSave.getName());
+ Assertions.assertEquals((Object) incoming.getDescription(), (Object) passedToSave.getDescription());
+ Assertions.assertEquals((Object) incoming.getPrice(), (Object) passedToSave.getPrice());
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ Assertions.assertSame((Object) passedToSave, (Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testUpdateInvokesFindByIdAndSaveOnceOnSuccess() {
+ Long id = 9L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(existing)).thenReturn(existing);
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).save(existing);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testUpdateDoesNotCallSaveWhenProductMissing() {
+ Long id = 10L;
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 404, (int) response.getStatusCodeValue());
+ Assertions.assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).save(any(Product.class));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testUpdateReturnsOkAndBodyIsValueReturnedBySave() {
+ Long id = 11L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ Product returnedBySave = new Product();
+ returnedBySave.setName("NameB");
+ returnedBySave.setDescription("DescB");
+ returnedBySave.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(existing)).thenReturn(returnedBySave);
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ Assertions.assertSame((Object) returnedBySave, (Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).save(existing);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testUpdateReturnsOkEvenWhenRepositorySaveReturnsNullBody() {
+ Long id = 12L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(existing)).thenReturn(null);
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ Assertions.assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).save(existing);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testUpdateAllowsEdgeCaseValuesWithoutValidationAndReturnsOk() {
+ Long id = 13L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameC");
+ incoming.setDescription("DescC");
+ incoming.setPrice(-9999999999.99d); // edge-case negative and large magnitude
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(any(Product.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0, Product.class));
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Product.class);
+ verify(productRepository).save(captor.capture());
+ Product savedArg = captor.getValue();
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ Assertions.assertEquals((Object) incoming.getPrice(), (Object) savedArg.getPrice());
+ Assertions.assertSame((Object) savedArg, (Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testUpdateWithIdenticalFieldsStillSavesAndReturnsOk() {
+ Long id = 14L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameA");
+ incoming.setDescription("DescA");
+ incoming.setPrice(10.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(existing)).thenReturn(existing);
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ Assertions.assertSame((Object) existing, (Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, times(1)).save(existing);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("invalid")
+ public void testUpdateNotFoundHasNoBody() {
+ Long id = 15L;
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.empty());
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ Assertions.assertEquals((int) 404, (int) response.getStatusCodeValue());
+ Assertions.assertNull((Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verify(productRepository, never()).save(any(Product.class));
+ verifyNoMoreInteractions(productRepository);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testUpdateSavesMutatedExistingProductInstance() {
+ Long id = 16L;
+ Product existing = new Product();
+ existing.setName("NameA");
+ existing.setDescription("DescA");
+ existing.setPrice(10.00d);
+ Product incoming = new Product();
+ incoming.setName("NameB");
+ incoming.setDescription("DescB");
+ incoming.setPrice(20.00d);
+ when(productRepository.findById(id)).thenReturn(Optional.of(existing));
+ when(productRepository.save(any(Product.class)))
+ .thenAnswer(invocation -> invocation.getArgument(0, Product.class));
+ ResponseEntity response = productController.updateProduct(id, incoming);
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Product.class);
+ verify(productRepository).save(captor.capture());
+ Product savedArg = captor.getValue();
+ Assertions.assertSame((Object) existing, (Object) savedArg);
+ Assertions.assertEquals((Object) incoming.getName(), (Object) savedArg.getName());
+ Assertions.assertEquals((Object) incoming.getDescription(), (Object) savedArg.getDescription());
+ Assertions.assertEquals((Object) incoming.getPrice(), (Object) savedArg.getPrice());
+ Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue());
+ Assertions.assertSame((Object) savedArg, (Object) response.getBody());
+ verify(productRepository, times(1)).findById(id);
+ verifyNoMoreInteractions(productRepository);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/model/ProductGetDescriptionTest.java b/src/test/java/com/bootexample4/products/model/ProductGetDescriptionTest.java
new file mode 100644
index 00000000..9d84b2c9
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/model/ProductGetDescriptionTest.java
@@ -0,0 +1,462 @@
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=getDescription_791d670f82
+ROOST_METHOD_SIG_HASH=getDescription_b1844ea396
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void the_saved_product_should_not_be_null_and_its_properties_must_correspond_to_those_provided_by_client() {
+
+ assertNotNull(savedProduct);
+ assertEquals(newProduct.getPrice(), savedProduct.getPrice(), .001);
+ assertEquals(savedProduct.getName(), newProduct.getName(), "unexpected product name: " + savedProduct.getName());
+ assertEquals(savedProduct.getDescription(), newProduct.getDescription(), "unexpected product name: " + savedProduct.getDescription());
+}
+"
+ "@Test
+public void the_product_with_ID_should_be_updated_with_the_provided_details(Long id) {
+
+ Product updatedProduct = productController.getProductById(id).getBody();
+ assertEquals(newProduct.getDescription(), updatedProduct.getDescription());
+ assertEquals(newProduct.getName(), updatedProduct.getName());
+ assertEquals(newProduct.getPrice(), updatedProduct.getPrice());
+}
+"Scenario 1: Returns null when no description has been set
+
+Details:
+ TestName: returnsNullWhenDescriptionNotInitialized
+ Description: Verify that getDescription returns null by default when a new Product instance has not had its description assigned.
+
+Execution:
+ Arrange: Create a new Product without calling setDescription.
+ Act: Invoke getDescription on the new Product instance.
+ Assert: Use assertNull to confirm the returned value is null.
+
+Validation:
+ This assertion verifies the default state of the description field, ensuring there is no unintended default string. It confirms correct initial behavior for newly constructed entities before any setter is used.
+
+
+Scenario 2: Returns the exact string that was set via the setter
+
+Details:
+ TestName: returnsExactValuePreviouslySet
+ Description: Confirm that getDescription returns the same text that was previously assigned using setDescription with a normal sentence.
+
+Execution:
+ Arrange: Create a Product and call setDescription with a typical non-empty string (e.g., "High quality steel bottle").
+ Act: Invoke getDescription.
+ Assert: Use assertEquals to compare the returned string to the one provided to setDescription.
+
+Validation:
+ Ensures the getter reflects the stored value with no alterations, supporting basic object behavior and data integrity.
+
+
+Scenario 3: Returns an empty string when set with an empty string
+
+Details:
+ TestName: returnsEmptyStringWhenSetToEmpty
+ Description: Ensure that if setDescription is called with "", getDescription returns the same empty string.
+
+Execution:
+ Arrange: Create a Product and call setDescription with an empty string ("").
+ Act: Invoke getDescription.
+ Assert: Use assertEquals to verify the returned value equals "".
+
+Validation:
+ Verifies that empty but valid inputs are preserved and not converted to null or any default text, maintaining exact value fidelity.
+
+
+Scenario 4: Preserves whitespace-only strings
+
+Details:
+ TestName: preservesWhitespaceOnlyDescription
+ Description: Check that getDescription preserves whitespace-only content (e.g., spaces or tabs) exactly as set.
+
+Execution:
+ Arrange: Create a Product and setDescription to a whitespace-only string (e.g., " \t ").
+ Act: Call getDescription.
+ Assert: Use assertEquals to ensure the returned string matches the whitespace-only input exactly.
+
+Validation:
+ Confirms that the getter does not trim or normalize whitespace, which can be significant in some presentation or parsing contexts.
+
+
+Scenario 5: Preserves leading and trailing whitespace
+
+Details:
+ TestName: leadingAndTrailingWhitespaceArePreserved
+ Description: Verify that leading and trailing spaces are not removed or altered by getDescription.
+
+Execution:
+ Arrange: Create a Product and setDescription to a value with leading and trailing spaces (e.g., " limited edition ").
+ Act: Invoke getDescription.
+ Assert: Use assertEquals to confirm the returned value includes the original spaces.
+
+Validation:
+ Ensures no implicit formatting logic is applied by the getter, preserving exact user input which may be required for precise display.
+
+
+Scenario 6: Handles very long descriptions without truncation
+
+Details:
+ TestName: returnsVeryLongDescriptionUnmodified
+ Description: Validate that getDescription can return a very long string (e.g., thousands of characters) exactly as set.
+
+Execution:
+ Arrange: Generate a large string (e.g., 10,000 characters) and call setDescription with it.
+ Act: Invoke getDescription.
+ Assert: Use assertEquals to confirm the returned string exactly matches the large input.
+
+Validation:
+ Verifies there is no internal truncation or unexpected processing for large inputs, important for product catalogs with detailed narratives.
+
+
+Scenario 7: Supports Unicode characters and emoji
+
+Details:
+ TestName: supportsUnicodeAndEmojiCharacters
+ Description: Ensure that getDescription returns strings containing Unicode characters (accents, non-Latin scripts) and emoji without alteration.
+
+Execution:
+ Arrange: Call setDescription with a string like "Café –耐久性– 🌟🔥".
+ Act: Invoke getDescription.
+ Assert: Use assertEquals to check the exact same Unicode content is returned.
+
+Validation:
+ Confirms proper handling of internationalized content and multi-byte characters, critical for globalized applications.
+
+
+Scenario 8: Preserves newline and tab characters
+
+Details:
+ TestName: preservesNewlinesAndTabsInDescription
+ Description: Verify that newline (\n) and tab (\t) characters remain intact when retrieved via getDescription.
+
+Execution:
+ Arrange: Set a description including embedded newlines and tabs (e.g., "Line1\n\tLine2\nLine3").
+ Act: Call getDescription.
+ Assert: Use assertEquals to confirm the string including control characters is returned unchanged.
+
+Validation:
+ Ensures formatting characters used for multi-line or structured text are preserved, supporting rich product descriptions.
+
+
+Scenario 9: Returns null after explicitly setting description to null
+
+Details:
+ TestName: returnsNullAfterSettingDescriptionToNull
+ Description: Confirm that setDescription(null) results in getDescription returning null, not a default or prior value.
+
+Execution:
+ Arrange: Create a Product, optionally set a non-null description first, then call setDescription(null).
+ Act: Invoke getDescription.
+ Assert: Use assertNull to verify the result is null.
+
+Validation:
+ Ensures the field can be cleared to a null state as expected, reflecting explicit null assignments.
+
+
+Scenario 10: Returns the same String instance reference that was set
+
+Details:
+ TestName: returnsSameStringInstanceReference
+ Description: Validate that getDescription returns the same String object reference that was provided to setDescription (not a new copy).
+
+Execution:
+ Arrange: Create a new String instance (e.g., via new String("original")) and pass it to setDescription.
+ Act: Call getDescription to obtain the reference.
+ Assert: Use assertSame to confirm the returned reference is the identical String instance.
+
+Validation:
+ Demonstrates that the getter exposes the stored reference without creating new objects, which is consistent with simple field access and avoids unintended transformations.
+
+
+Scenario 11: Repeated calls return the same object reference consistently
+
+Details:
+ TestName: repeatedGetsReturnSameReference
+ Description: Ensure that calling getDescription multiple times without changes returns the same String object reference.
+
+Execution:
+ Arrange: Set a description with a specific String instance (e.g., new String("stable")).
+ Act: Call getDescription twice.
+ Assert: Use assertSame to verify both returned references are identical.
+
+Validation:
+ Confirms idempotent behavior of the getter and absence of recomputation or new allocations on successive calls.
+
+
+Scenario 12: Most recent assignment is returned after multiple sets
+
+Details:
+ TestName: returnsMostRecentlySetDescription
+ Description: Confirm that after calling setDescription multiple times, getDescription returns the latest value.
+
+Execution:
+ Arrange: Call setDescription with "first value", then with "second value".
+ Act: Invoke getDescription after the second assignment.
+ Assert: Use assertEquals to confirm the returned value is "second value" and not any prior value.
+
+Validation:
+ Verifies correct overwrite semantics, ensuring state reflects the most recent client input.
+
+
+Scenario 13: Other field changes do not affect description retrieval
+
+Details:
+ TestName: descriptionUnaffectedByOtherFieldChanges
+ Description: Ensure that modifications to id, name, or price have no effect on the value returned by getDescription.
+
+Execution:
+ Arrange: Set a known description. Then modify id via setId, name via setName, and price via setPrice with arbitrary values.
+ Act: Call getDescription.
+ Assert: Use assertEquals to verify the description is unchanged from the originally set value.
+
+Validation:
+ Confirms field isolation and that getDescription only exposes the description field, supporting predictable entity behavior.
+
+
+Scenario 14: Two separate Product instances maintain independent descriptions
+
+Details:
+ TestName: independentDescriptionsAcrossInstances
+ Description: Validate that setting a description on one Product does not influence another Product’s description.
+
+Execution:
+ Arrange: Create two Product instances. Set different descriptions on each.
+ Act: Invoke getDescription on both instances.
+ Assert: Use assertEquals to confirm each Product returns its own assigned description; optionally use assertNotEquals between the two returned values.
+
+Validation:
+ Ensures there is no shared state across instances, preventing cross-entity contamination.
+
+
+Scenario 15: Case sensitivity is preserved in descriptions
+
+Details:
+ TestName: preservesCaseSensitivityInDescription
+ Description: Verify that uppercase and lowercase characters are preserved exactly as set.
+
+Execution:
+ Arrange: Set a mixed-case description such as "PremiumCaseSensitiveValue".
+ Act: Call getDescription.
+ Assert: Use assertEquals to confirm the case is identical to the original input.
+
+Validation:
+ Confirms there is no implicit case transformation, which is vital for exact text fidelity.
+
+
+Scenario 16: HTML or script-like content is returned unchanged
+
+Details:
+ TestName: returnsHtmlLikeContentUnmodified
+ Description: Ensure descriptions that contain HTML or script-like tags (e.g., "Bold ") are returned as-is.
+
+Execution:
+ Arrange: Set a description containing HTML/script-like text.
+ Act: Invoke getDescription.
+ Assert: Use assertEquals to verify the returned string matches the input exactly.
+
+Validation:
+ Confirms the getter performs no sanitization or escaping; it simply returns stored data. This clarifies that any sanitization must occur at input or rendering layers.
+
+*/
+
+// ********RoostGPT********
+
+package com.bootexample4.products.model;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Assertions;
+import com.bootexample4.products.model.Product;
+import org.junit.jupiter.api.*;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+public class ProductGetDescriptionTest {
+
+ @Test
+ @Tag("boundary")
+ public void testReturnsNullWhenDescriptionNotInitialized() {
+ Product product = new Product();
+ String actual = product.getDescription();
+ Assertions.assertNull((Object) actual);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnsExactValuePreviouslySet() {
+ Product product = new Product();
+ String expected = "High quality steel bottle";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testReturnsEmptyStringWhenSetToEmpty() {
+ Product product = new Product();
+ String expected = "";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testPreservesWhitespaceOnlyDescription() {
+ Product product = new Product();
+ String expected = " \t ";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testLeadingAndTrailingWhitespaceArePreserved() {
+ Product product = new Product();
+ String expected = " limited edition ";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testReturnsVeryLongDescriptionUnmodified() {
+ Product product = new Product();
+ // TODO: Adjust length if very large strings affect your environment
+ final int length = 10_000;
+ StringBuilder sb = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ sb.append('a');
+ }
+ String expected = sb.toString();
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testSupportsUnicodeAndEmojiCharacters() {
+ Product product = new Product();
+ String expected = "Café –耐久性– 🌟🔥";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testPreservesNewlinesAndTabsInDescription() {
+ Product product = new Product();
+ String expected = "Line1\n\tLine2\nLine3";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testReturnsNullAfterSettingDescriptionToNull() {
+ Product product = new Product();
+ product.setDescription("temporary");
+ product.setDescription(null);
+ String actual = product.getDescription();
+ Assertions.assertNull((Object) actual);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnsSameStringInstanceReference() {
+ Product product = new Product();
+ String original = new String("original");
+ product.setDescription(original);
+ String actual = product.getDescription();
+ Assertions.assertSame((Object) original, (Object) actual);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testRepeatedGetsReturnSameReference() {
+ Product product = new Product();
+ String stable = new String("stable");
+ product.setDescription(stable);
+ String first = product.getDescription();
+ String second = product.getDescription();
+ Assertions.assertSame((Object) first, (Object) second);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnsMostRecentlySetDescription() {
+ Product product = new Product();
+ product.setDescription("first value");
+ String expected = "second value";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("integration")
+ public void testDescriptionUnaffectedByOtherFieldChanges() {
+ Product product = new Product();
+ String expected = "Independent description";
+ product.setDescription(expected);
+ product.setId(123L);
+ product.setName("SampleName");
+ product.setPrice(99.99);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("integration")
+ public void testIndependentDescriptionsAcrossInstances() {
+ Product productA = new Product();
+ Product productB = new Product();
+ String expectedA = "First product description";
+ String expectedB = "Second product description";
+ productA.setDescription(expectedA);
+ productB.setDescription(expectedB);
+ String actualA = productA.getDescription();
+ String actualB = productB.getDescription();
+ Assertions.assertEquals((String) expectedA, (String) actualA);
+ Assertions.assertEquals((String) expectedB, (String) actualB);
+ Assertions.assertNotEquals((String) actualA, (String) actualB);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testPreservesCaseSensitivityInDescription() {
+ Product product = new Product();
+ String expected = "PremiumCaseSensitiveValue";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testReturnsHtmlLikeContentUnmodified() {
+ Product product = new Product();
+ String expected = "Bold ";
+ product.setDescription(expected);
+ String actual = product.getDescription();
+ Assertions.assertEquals((String) expected, (String) actual);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/model/ProductGetIdTest.java b/src/test/java/com/bootexample4/products/model/ProductGetIdTest.java
new file mode 100644
index 00000000..e056447e
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/model/ProductGetIdTest.java
@@ -0,0 +1,149 @@
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=getId_7023725436
+ROOST_METHOD_SIG_HASH=getId_ba349b1eff
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void there_is_an_existing_product_with_id(Long id) {
+
+ listOfProducts = productController.getAllProducts();
+ boolean productPresentFlag = false;
+ for (Product product : listOfProducts) {
+ if (product.getId() == id) {
+ productPresentFlag = true;
+ break;
+ }
+ }
+ assertTrue(productPresentFlag);
+}
+"
+ "@Test
+public void the_response_should_contain_the_product_with_id(Long id) {
+
+ Product product = getProductByIdResponse.getBody();
+ assertEquals(id, product.getId());
+}
+"
+Scenario 1: getId returns null for a newly constructed Product
+
+Details:
+ TestName: getIdReturnsNullForNewInstance
+ Description: Verify that a fresh Product instance with no id assigned returns null when getId is called.
+
+Execution:
+ Arrange: Create a new Product instance without invoking setId.
+ Act: Call getId on the newly created Product.
+ Assert: Use assertNull to confirm that the returned value is null.
+
+Validation:
+ This assertion verifies that the default state of id is uninitialized (null) as expected for a new entity prior to persistence or manual assignment.
+ It is significant because many workflows rely on null ids to detect transient (non-persisted) entities.
+
+*/
+
+// ********RoostGPT********
+
+package com.bootexample4.products.model;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Assertions;
+import com.bootexample4.products.model.Product;
+import org.junit.jupiter.api.*;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+public class ProductGetIdTest {
+
+ @Test
+ @Tag("valid")
+ @DisplayName("getId returns null for a newly constructed Product")
+ public void testGetIdReturnsNullForNewInstance() {
+ Product product = new Product();
+ Object actual = (Object) product.getId();
+ Assertions.assertNull(actual);
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("getId returns the same id that was set: positive value")
+ public void testGetIdAfterSetIdWithPositiveValue() {
+ Product product = new Product();
+ Long expectedId = Long.valueOf(123L); // TODO: adjust as per desired positive id
+ // value for test scenario
+ product.setId(expectedId);
+ Long actualId = (Long) product.getId();
+ Assertions.assertEquals((Long) expectedId, (Long) actualId);
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("getId returns zero when id is set to zero")
+ public void testGetIdAfterSetIdWithZero() {
+ Product product = new Product();
+ Long expectedId = Long.valueOf(0L);
+ product.setId(expectedId);
+ Long actualId = (Long) product.getId();
+ Assertions.assertEquals((Long) expectedId, (Long) actualId);
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("getId returns negative id when set to negative")
+ public void testGetIdAfterSetIdWithNegativeValue() {
+ Product product = new Product();
+ Long expectedId = Long.valueOf(-1L);
+ product.setId(expectedId);
+ Long actualId = (Long) product.getId();
+ Assertions.assertEquals((Long) expectedId, (Long) actualId);
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("getId returns Long.MAX_VALUE correctly")
+ public void testGetIdAfterSetIdWithMaxLongValue() {
+ Product product = new Product();
+ Long expectedId = Long.valueOf(Long.MAX_VALUE);
+ product.setId(expectedId);
+ Long actualId = (Long) product.getId();
+ Assertions.assertEquals((Long) expectedId, (Long) actualId);
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("getId remains unchanged when other fields are updated")
+ public void testGetIdUnaffectedByOtherFieldUpdates() {
+ Product product = new Product();
+ Long initialId = Long.valueOf(42L); // TODO: change if a different constant id is
+ // preferred for this test
+ product.setId(initialId);
+ product.setName(""); // TODO: set a representative name if needed
+ product.setDescription(""); // TODO: set a representative description if needed
+ product.setPrice(1.0d);
+ Long actualId = (Long) product.getId();
+ Assertions.assertEquals((Long) initialId, (Long) actualId);
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("getId reflects the latest assigned id after multiple setId calls")
+ public void testGetIdAfterMultipleSetIdCalls() {
+ Product product = new Product();
+ product.setId(Long.valueOf(10L));
+ product.setId(Long.valueOf(20L));
+ Long expectedId = Long.valueOf(20L);
+ Long actualId = (Long) product.getId();
+ Assertions.assertEquals((Long) expectedId, (Long) actualId);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/model/ProductGetNameTest.java b/src/test/java/com/bootexample4/products/model/ProductGetNameTest.java
new file mode 100644
index 00000000..63abe513
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/model/ProductGetNameTest.java
@@ -0,0 +1,165 @@
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=getName_3a12ffc596
+ROOST_METHOD_SIG_HASH=getName_8400ac6fb7
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void the_saved_product_should_not_be_null_and_its_properties_must_correspond_to_those_provided_by_client() {
+
+ assertNotNull(savedProduct);
+ assertEquals(newProduct.getPrice(), savedProduct.getPrice(), .001);
+ assertEquals(savedProduct.getName(), newProduct.getName(), "unexpected product name: " + savedProduct.getName());
+ assertEquals(savedProduct.getDescription(), newProduct.getDescription(), "unexpected product name: " + savedProduct.getDescription());
+}
+"
+ "@Test
+public void the_product_with_ID_should_be_updated_with_the_provided_details(Long id) {
+
+ Product updatedProduct = productController.getProductById(id).getBody();
+ assertEquals(newProduct.getDescription(), updatedProduct.getDescription());
+ assertEquals(newProduct.getName(), updatedProduct.getName());
+ assertEquals(newProduct.getPrice(), updatedProduct.getPrice());
+}
+"
+Scenario 1: Return null when name has never been set
+
+Details:
+ TestName: returnsNullWhenNameNotSet
+ Description: Verifies that a newly constructed Product without any prior name assignment returns null when getName is called.
+
+Execution:
+ Arrange: Create a new Product instance without invoking setName.
+ Act: Invoke getName on the Product.
+ Assert: Use assertNull to confirm that the returned value is null.
+
+Validation:
+ Confirms the default state of the name field (null for an uninitialized String) and ensures client code can safely handle the absence of a name without unexpected defaults.
+
+*/
+
+// ********RoostGPT********
+
+package com.bootexample4.products.model;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import com.bootexample4.products.model.Product;
+import org.junit.jupiter.api.*;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+public class ProductGetNameTest {
+
+ private Product product;
+
+ @BeforeEach
+ public void setUp() {
+ product = new Product();
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("Returns null when name has never been set")
+ public void testReturnsNullWhenNameNotSet() {
+ assertNull((Object) product.getName());
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("Returns the set name for a typical value")
+ public void testGetNameAfterSettingTypicalValue() {
+ product.setName((String) "Laptop");
+ assertEquals((String) "Laptop", (String) product.getName());
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("Returns empty string when name is set to empty")
+ public void testGetNameAfterSettingEmptyString() {
+ product.setName((String) "");
+ assertEquals((String) "", (String) product.getName());
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("Returns whitespace string when name is set to whitespace only")
+ public void testGetNameAfterSettingWhitespaceOnly() {
+ product.setName((String) " ");
+ assertEquals((String) " ", (String) product.getName());
+ }
+
+ @Test
+ @Tag("invalid")
+ @DisplayName("Returns null when name is explicitly set to null")
+ public void testGetNameAfterSettingNull() {
+ product.setName((String) null);
+ assertNull((Object) product.getName());
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("Returns the most recently set name value")
+ public void testGetNameReturnsMostRecentlySetValue() {
+ product.setName((String) "FirstName");
+ product.setName((String) "SecondName");
+ assertEquals((String) "SecondName", (String) product.getName());
+ }
+
+ @Test
+ @Tag("integration")
+ @DisplayName("getName is unaffected by changes to other fields")
+ public void testGetNameIsIndependentFromOtherFields() {
+ product.setName((String) "DeviceName");
+ product.setDescription((String) "Initial description");
+ product.setPrice((double) 199.99);
+ product.setId((Long) 1L);
+ assertEquals((String) "DeviceName", (String) product.getName());
+ product.setDescription((String) "Updated description"); // TODO adjust description
+ // if business rules
+ // change
+ product.setPrice((double) 299.99); // TODO adjust price if needed for different
+ // scenarios
+ product.setId((Long) 2L); // TODO adjust id if needed
+ assertEquals((String) "DeviceName", (String) product.getName());
+ }
+
+ @Test
+ @Tag("boundary")
+ @DisplayName("Handles long string names without alteration")
+ public void testGetNameWithLongString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 2048; i++) {
+ sb.append('X');
+ }
+ String longName = sb.toString();
+ product.setName((String) longName);
+ assertEquals((String) longName, (String) product.getName());
+ }
+
+ @Test
+ @Tag("valid")
+ @DisplayName("Handles transitions between null, non-empty, and empty names")
+ public void testGetNameAcrossMultipleTransitions() {
+ product.setName((String) null);
+ assertNull((Object) product.getName());
+ product.setName((String) "Alpha");
+ assertEquals((String) "Alpha", (String) product.getName());
+ product.setName((String) null);
+ assertNull((Object) product.getName());
+ product.setName((String) "");
+ assertEquals((String) "", (String) product.getName());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/bootexample4/products/model/ProductGetPriceTest.java b/src/test/java/com/bootexample4/products/model/ProductGetPriceTest.java
new file mode 100644
index 00000000..2f9f6c3a
--- /dev/null
+++ b/src/test/java/com/bootexample4/products/model/ProductGetPriceTest.java
@@ -0,0 +1,144 @@
+
+// ********RoostGPT********
+/*
+Test generated by RoostGPT for test dbrx-java_clone using AI Type Azure Open AI and AI Model gpt-5
+
+ROOST_METHOD_HASH=getPrice_b54117587b
+ROOST_METHOD_SIG_HASH=getPrice_d2cb73a47d
+
+Here are your existing test cases which we found out and are not considered for test generation:
+
+File Path: /var/tmp/Roost/RoostGPT/dbrx-java_clone/1777806495/source/java-springbootunit/src/test/java/com/bootexample4/products/cucumber/ProductStepDefinitions.java
+Tests:
+ "@Test
+public void the_saved_product_should_not_be_null_and_its_properties_must_correspond_to_those_provided_by_client() {
+
+ assertNotNull(savedProduct);
+ assertEquals(newProduct.getPrice(), savedProduct.getPrice(), .001);
+ assertEquals(savedProduct.getName(), newProduct.getName(), "unexpected product name: " + savedProduct.getName());
+ assertEquals(savedProduct.getDescription(), newProduct.getDescription(), "unexpected product name: " + savedProduct.getDescription());
+}
+"
+ "@Test
+public void the_product_with_ID_should_be_updated_with_the_provided_details(Long id) {
+
+ Product updatedProduct = productController.getProductById(id).getBody();
+ assertEquals(newProduct.getDescription(), updatedProduct.getDescription());
+ assertEquals(newProduct.getName(), updatedProduct.getName());
+ assertEquals(newProduct.getPrice(), updatedProduct.getPrice());
+}
+"Scenario 1: Returns the default price (0.0) for a newly constructed Product
+
+Details:
+ TestName: returnsDefaultZeroPriceForNewProductInstance
+ Description: Validates that a new Product instance, without any explicit price assignment, returns 0.0 when getPrice is called. This checks the default initialization of the primitive double field.
+
+Execution:
+ Arrange: Create a new Product instance without calling setPrice.
+ Act: Invoke getPrice on the new Product instance to retrieve the price.
+ Assert: Use assertEquals with a small delta (e.g., 1e-9) to verify the returned value is 0.0.
+
+Validation:
+ Confirms that the price field defaults to 0.0 as per Java’s primitive initialization behavior. This is significant to ensure no unintended default charges or misreported prices occur for newly created products.
+
+*/
+
+// ********RoostGPT********
+
+package com.bootexample4.products.model;
+
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import com.bootexample4.products.model.Product;
+import org.junit.jupiter.api.*;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+
+public class ProductGetPriceTest {
+
+ private static final double DELTA = 1e-9;
+
+ @Test
+ @Tag("valid")
+ @Tag("boundary")
+ public void testReturnsDefaultZeroPriceForNewProductInstance() {
+ Product product = new Product();
+ double actualPrice = product.getPrice();
+ assertEquals((double) 0.0, (double) actualPrice, (double) DELTA);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testGetPriceReturnsSetPositiveValue() {
+ Product product = new Product();
+ double expectedPrice = (double) 199.99; // TODO change as needed for different
+ // test data
+ product.setPrice(expectedPrice);
+ double actualPrice = product.getPrice();
+ assertEquals((double) expectedPrice, (double) actualPrice, (double) DELTA);
+ }
+
+ @Test
+ @Tag("valid")
+ @Tag("boundary")
+ public void testGetPriceReturnsZeroWhenSetToZero() {
+ Product product = new Product();
+ double expectedPrice = (double) 0.0;
+ product.setPrice(expectedPrice);
+ double actualPrice = product.getPrice();
+ assertEquals((double) expectedPrice, (double) actualPrice, (double) DELTA);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testGetPriceReflectsNegativeValueWhenSet() {
+ Product product = new Product();
+ double expectedPrice = (double) -5.50; // TODO change to verify other negative
+ // values if needed
+ product.setPrice(expectedPrice);
+ double actualPrice = product.getPrice();
+ assertEquals((double) expectedPrice, (double) actualPrice, (double) DELTA);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testGetPriceAfterMultipleUpdates() {
+ Product product = new Product();
+ product.setPrice((double) 10.00);
+ product.setPrice((double) 20.50);
+ product.setPrice((double) 0.0);
+ double expectedPrice = (double) 1000.00; // final update
+ product.setPrice(expectedPrice);
+ double actualPrice = product.getPrice();
+ assertEquals((double) expectedPrice, (double) actualPrice, (double) DELTA);
+ }
+
+ @Test
+ @Tag("valid")
+ public void testGetPriceIsUnaffectedByOtherFields() {
+ Product product = new Product();
+ double expectedPrice = (double) 75.25;
+ product.setPrice(expectedPrice);
+ product.setId((Long) 123L);
+ product.setName("Sample Product"); // TODO change as needed for different test
+ // data
+ product.setDescription("Sample Description"); // TODO change as needed for
+ // different test data
+ double actualPrice = product.getPrice();
+ assertEquals((double) expectedPrice, (double) actualPrice, (double) DELTA);
+ }
+
+ @Test
+ @Tag("boundary")
+ public void testGetPriceWithLargeValue() {
+ Product product = new Product();
+ double expectedPrice = (double) 1_000_000_000_000.00; // 1e12
+ product.setPrice(expectedPrice);
+ double actualPrice = product.getPrice();
+ assertEquals((double) expectedPrice, (double) actualPrice, (double) DELTA);
+ }
+
+}
\ No newline at end of file