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 b/src/test/java/com/bootexample4/products/controller/ProductControllerCreateProductTest.java new file mode 100644 index 00000000..1dd49df2 --- /dev/null +++ b/src/test/java/com/bootexample4/products/controller/ProductControllerCreateProductTest.java @@ -0,0 +1,94 @@ + +// ********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 + + +Scenario 1: Successfully saves a valid Product and returns the saved instance + +Details: + TestName: savesValidProductAndReturnsSavedInstance + Description: Verifies that when a well-formed Product object is provided, the controller delegates to productRepository.save and returns exactly the instance provided by the repository, confirming pass-through behavior. + +Execution: + Arrange: Create a ProductController with an injected mock/stub ProductRepository. Prepare a Product input and configure the repository stub so that save(input) returns a distinct Product instance named savedProduct. + Act: Invoke createProduct with the prepared Product input. + Assert: Check that the returned value is the exact same object instance as savedProduct, and that productRepository.save was invoked exactly once with the input Product. + +Validation: + Confirms the method’s core responsibility: delegating to productRepository.save and returning whatever the repository returns. Ensures no additional processing occurs in the controller and verifies correct interaction with the autowired productRepository field. + +*/ + +// ********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 static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; +import org.junit.jupiter.api.*; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@ExtendWith(MockitoExtension.class) +public class ProductControllerCreateProductTest { + + @Mock + private ProductRepository productRepository; + + @InjectMocks + private ProductController productController; + + @Test + @Tag("valid") + public void testSavesValidProductAndReturnsSavedInstance() { + Product inputProduct = mock(Product.class); + Product savedProduct = mock(Product.class); + when(productRepository.save(inputProduct)).thenReturn(savedProduct); + Product result = productController.createProduct(inputProduct); + assertSame((Product) savedProduct, (Product) result); + verify(productRepository, times(1)).save(inputProduct); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("invalid") + public void testCreateProductPropagatesRepositoryException() { + Product inputProduct = mock(Product.class); + RuntimeException repositoryException = new RuntimeException(); // TODO: Customize + // exception if + // needed + when(productRepository.save(inputProduct)).thenThrow(repositoryException); + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> productController.createProduct(inputProduct)); + assertSame((RuntimeException) repositoryException, (RuntimeException) thrown); + verify(productRepository, times(1)).save(inputProduct); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("boundary") + public void testCreateProductWithNullInputDelegatesToRepositoryAndReturnsResult() { + Product savedProduct = mock(Product.class); + when(productRepository.save((Product) null)).thenReturn(savedProduct); + Product result = productController.createProduct(null); + assertSame((Product) savedProduct, (Product) result); + verify(productRepository, times(1)).save(null); + verifyNoMoreInteractions(productRepository); + } + +} \ 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..994e1cf2 --- /dev/null +++ b/src/test/java/com/bootexample4/products/controller/ProductControllerDeleteProductTest.java @@ -0,0 +1,410 @@ + +// ********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 + +Scenario 1: Deleting an existing product returns HTTP 200 OK + +Details: + TestName: deleteExistingProductReturnsOk + Description: Validates that when a product with the specified id exists, ProductController.deleteProduct returns an HTTP 200 OK response, and the repository’s delete method is invoked exactly once with the found product. + +Execution: + Arrange: Set up a ProductController with a mocked ProductRepository. Stub productRepository.findById(givenId) to return Optional.of(existingProduct). + Act: Invoke deleteProduct(givenId) on the controller. + Assert: Assert that the ResponseEntity status code is 200 OK and the response body is null. Verify productRepository.delete(existingProduct) is called exactly once and productRepository.findById(givenId) is called exactly once. + +Validation: + Confirms the normal success path: a found product is deleted and the controller responds with HTTP 200 and no body. Ensures the method correctly maps the Optional presence to a successful deletion and response. + +Scenario 2: Deleting a non-existent product returns HTTP 404 Not Found + +Details: + TestName: deleteNonExistingProductReturnsNotFound + Description: Ensures that when the specified id does not match any product, the controller responds with HTTP 404 Not Found and does not attempt to delete anything. + +Execution: + Arrange: Prepare ProductController with a mocked ProductRepository. Stub productRepository.findById(missingId) to return Optional.empty(). + Act: Call deleteProduct(missingId). + Assert: Assert that the ResponseEntity status code is 404 Not Found and the response body is null. Verify productRepository.delete(...) is never called. + +Validation: + Verifies the orElse branch is executed when no product is found and no deletion occurs. Confirms correct HTTP semantics for missing resources. + +Scenario 3: Passing a null id results in an exception from the repository + +Details: + TestName: deleteWithNullIdThrowsIllegalArgumentException + Description: Checks the behavior when deleteProduct is called with a null id. Spring Data repositories typically throw IllegalArgumentException on null ids, and the controller does not catch exceptions. + +Execution: + Arrange: Configure ProductController with a mocked ProductRepository. Arrange productRepository.findById(null) to throw IllegalArgumentException. + Act: Invoke deleteProduct(null). + Assert: Assert that an IllegalArgumentException is thrown and no ResponseEntity is returned. Verify productRepository.delete(...) is never called. + +Validation: + Confirms that invalid input (null id) leads to a propagated repository exception, as the controller provides no special handling for null ids. + +Scenario 4: Deleting with a negative id returns HTTP 404 Not Found + +Details: + TestName: deleteWithNegativeIdReturnsNotFound + Description: Ensures that negative ids are treated as non-existent resources, resulting in HTTP 404 Not Found without invoking delete. + +Execution: + Arrange: Mock ProductRepository to return Optional.empty() for a negativeId (e.g., -1L). + Act: Call deleteProduct(negativeId). + Assert: Assert HTTP 404 Not Found and null body. Verify productRepository.delete(...) is never called. + +Validation: + Confirms that non-sensical ids that aren’t present result in a not-found response, matching repository behavior. + +Scenario 5: Deleting with an id of zero returns HTTP 404 Not Found + +Details: + TestName: deleteWithZeroIdReturnsNotFound + Description: Verifies that id = 0 behaves as a non-existent resource, leading to HTTP 404 Not Found, and no deletion occurs. + +Execution: + Arrange: Stub productRepository.findById(0L) to return Optional.empty(). + Act: Invoke deleteProduct(0L). + Assert: Assert HTTP 404 Not Found and null body. Verify delete is never called. + +Validation: + Ensures consistent handling of boundary id values unlikely to exist in persistence. + +Scenario 6: Repository delete throws a runtime exception and it propagates + +Details: + TestName: deleteWhenRepositoryDeleteThrowsPropagatesException + Description: Validates that if the repository throws a runtime exception during delete, the controller does not swallow it and no ResponseEntity is returned. + +Execution: + Arrange: Stub productRepository.findById(validId) to return Optional.of(existingProduct). Stub productRepository.delete(existingProduct) to throw a RuntimeException (e.g., data access error). + Act: Call deleteProduct(validId). + Assert: Assert that a RuntimeException is thrown. No status code assertion is applicable as no ResponseEntity is produced. Verify findById was called once. + +Validation: + Confirms exception propagation for failures occurring during the delete operation, which is the current method’s intended behavior (no try/catch). + +Scenario 7: Repository findById throws a runtime exception and it propagates + +Details: + TestName: deleteWhenFindByIdThrowsPropagatesException + Description: Ensures that if the repository fails while fetching the product (e.g., connectivity issue), the controller does not handle the error and the exception propagates. + +Execution: + Arrange: Stub productRepository.findById(problematicId) to throw a RuntimeException. + Act: Invoke deleteProduct(problematicId). + Assert: Assert that a RuntimeException is thrown. Verify productRepository.delete(...) is never called. + +Validation: + Confirms consistent exception propagation for failures in the initial lookup phase. + +Scenario 8: Successful deletion returns an empty response body + +Details: + TestName: deleteSuccessReturnsEmptyBody + Description: Confirms that on success the controller returns ResponseEntity.ok().build(), which has a null body (no content). + +Execution: + Arrange: Stub productRepository.findById(validId) to return Optional.of(existingProduct). Ensure delete succeeds without exceptions. + Act: Call deleteProduct(validId). + Assert: Assert that response.getBody() is null and response.getStatusCodeValue() is 200. + +Validation: + Ensures the controller adheres to the contract of returning HTTP 200 with no body on successful deletion. + +Scenario 9: Correct id is passed to repository findById + +Details: + TestName: findByIdCalledWithExactId + Description: Verifies that the controller forwards the exact id received to productRepository.findById without alteration. + +Execution: + Arrange: Mock ProductRepository and record interactions. Stub findById(exactId) to return Optional.empty() (or Optional.of(product)). + Act: Invoke deleteProduct(exactId). + Assert: Verify productRepository.findById(exactId) is called exactly once (and not with any other value). + +Validation: + Ensures parameter integrity: the controller uses the provided id as-is for the lookup. + +Scenario 10: Consecutive deletions of the same id: first OK, then 404 + +Details: + TestName: consecutiveDeleteFirstOkThenNotFound + Description: Simulates idempotent delete semantics at the API level: the first deletion succeeds (200 OK), the second attempt finds nothing (404 Not Found). + +Execution: + Arrange: Configure the mock so that productRepository.findById(targetId) returns Optional.of(existingProduct) on first call and Optional.empty() on second call. Deletion succeeds the first time. + Act: Call deleteProduct(targetId) twice in sequence. + Assert: First response: 200 OK with null body. Second response: 404 Not Found with null body. Verify productRepository.delete(existingProduct) is called exactly once across both calls. + +Validation: + Demonstrates practical API behavior over repeated calls: a resource can be deleted once; subsequent attempts correctly report absence. + +Scenario 11: Very large id (Long.MAX_VALUE) not found results in HTTP 404 + +Details: + TestName: deleteWithMaxLongIdReturnsNotFound + Description: Checks boundary behavior when using the largest possible id value, expecting the repository to return empty and the controller to respond with 404. + +Execution: + Arrange: Stub productRepository.findById(Long.MAX_VALUE) to return Optional.empty(). + Act: Invoke deleteProduct(Long.MAX_VALUE). + Assert: Assert HTTP 404 Not Found and verify delete is never invoked. + +Validation: + Ensures correct behavior for upper boundary id values that are unlikely to exist in storage. + +Scenario 12: Very small id (Long.MIN_VALUE) not found results in HTTP 404 + +Details: + TestName: deleteWithMinLongIdReturnsNotFound + Description: Checks boundary behavior for the most negative id value, expecting no match and a 404 response with no deletion attempted. + +Execution: + Arrange: Stub productRepository.findById(Long.MIN_VALUE) to return Optional.empty(). + Act: Call deleteProduct(Long.MIN_VALUE). + Assert: Assert HTTP 404 Not Found and verify delete is never invoked. + +Validation: + Complements boundary testing by covering the lower extreme of the id range. + +Scenario 13: The same product instance returned by findById is passed to delete + +Details: + TestName: deletePassesSameProductInstanceToRepository + Description: Ensures that the controller passes exactly the Product instance obtained from findById to the repository’s delete method, not a different or newly constructed object. + +Execution: + Arrange: Stub productRepository.findById(validId) to return Optional.of(specificProductInstance). + Act: Invoke deleteProduct(validId). + Assert: Verify productRepository.delete(...) is called with the same specificProductInstance reference. + +Validation: + Confirms that the controller uses the repository result directly in delete, avoiding unintended object substitutions. + +Scenario 14: Missing repository injection leads to a NullPointerException + +Details: + TestName: deleteWhenRepositoryNotInjectedThrowsNullPointerException + Description: Validates that if ProductController is constructed without injecting ProductRepository (i.e., the field remains null), calling deleteProduct will cause a NullPointerException. + +Execution: + Arrange: Instantiate ProductController without providing a ProductRepository (leave the private field null). + Act: Call deleteProduct(anyId). + Assert: Assert that a NullPointerException is thrown. + +Validation: + Highlights the necessity of proper dependency injection for the controller to function, consistent with the @Autowired design. + + +*/ + +// ********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.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.ResponseEntity; +import java.util.Optional; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +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 ProductControllerDeleteProductTest { + + @Mock + private ProductRepository productRepository; + + @InjectMocks + private ProductController controller; + + @Test + @Tag("valid") + public void testDeleteExistingProductReturnsOk() { + Long givenId = 1L; + Product existingProduct = new Product(); + when(productRepository.findById(eq(givenId))).thenReturn(Optional.of(existingProduct)); + doNothing().when(productRepository).delete(same(existingProduct)); + ResponseEntity response = controller.deleteProduct(givenId); + assertEquals((int) 200, (int) response.getStatusCodeValue()); + assertNull((Object) response.getBody()); + verify(productRepository, times(1)).findById(eq(givenId)); + verify(productRepository, times(1)).delete(same(existingProduct)); + } + + @Test + @Tag("valid") + public void testDeleteNonExistingProductReturnsNotFound() { + Long missingId = 999L; // TODO: adjust if repository uses a different strategy for + // IDs + when(productRepository.findById(eq(missingId))).thenReturn(Optional.empty()); + ResponseEntity response = controller.deleteProduct(missingId); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Object) response.getBody()); + verify(productRepository, times(1)).findById(eq(missingId)); + verify(productRepository, never()).delete(any(Product.class)); + } + + @Test + @Tag("invalid") + public void testDeleteWithNullIdThrowsIllegalArgumentException() { + when(productRepository.findById(isNull())).thenThrow(new IllegalArgumentException("Null id")); + assertThrows(IllegalArgumentException.class, () -> controller.deleteProduct(null)); + verify(productRepository, never()).delete(any(Product.class)); + } + + @Test + @Tag("boundary") + public void testDeleteWithNegativeIdReturnsNotFound() { + Long negativeId = -1L; + when(productRepository.findById(eq(negativeId))).thenReturn(Optional.empty()); + ResponseEntity response = controller.deleteProduct(negativeId); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Object) response.getBody()); + verify(productRepository, times(1)).findById(eq(negativeId)); + verify(productRepository, never()).delete(any(Product.class)); + } + + @Test + @Tag("boundary") + public void testDeleteWithZeroIdReturnsNotFound() { + Long zeroId = 0L; + when(productRepository.findById(eq(zeroId))).thenReturn(Optional.empty()); + ResponseEntity response = controller.deleteProduct(zeroId); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Object) response.getBody()); + verify(productRepository, times(1)).findById(eq(zeroId)); + verify(productRepository, never()).delete(any(Product.class)); + } + + @Test + @Tag("invalid") + public void testDeleteWhenRepositoryDeleteThrowsPropagatesException() { + Long validId = 2L; + Product existingProduct = new Product(); + when(productRepository.findById(eq(validId))).thenReturn(Optional.of(existingProduct)); + doThrow(new RuntimeException("Delete failure")).when(productRepository).delete(same(existingProduct)); + assertThrows(RuntimeException.class, () -> controller.deleteProduct(validId)); + verify(productRepository, times(1)).findById(eq(validId)); + verify(productRepository, times(1)).delete(same(existingProduct)); + } + + @Test + @Tag("invalid") + public void testDeleteWhenFindByIdThrowsPropagatesException() { + Long problematicId = 3L; + when(productRepository.findById(eq(problematicId))).thenThrow(new RuntimeException("Find failure")); + assertThrows(RuntimeException.class, () -> controller.deleteProduct(problematicId)); + verify(productRepository, never()).delete(any(Product.class)); + } + + @Test + @Tag("valid") + public void testDeleteSuccessReturnsEmptyBody() { + Long validId = 4L; + Product existingProduct = new Product(); + when(productRepository.findById(eq(validId))).thenReturn(Optional.of(existingProduct)); + doNothing().when(productRepository).delete(same(existingProduct)); + ResponseEntity response = controller.deleteProduct(validId); + assertNull((Object) response.getBody()); + assertEquals((int) 200, (int) response.getStatusCodeValue()); + verify(productRepository, times(1)).findById(eq(validId)); + verify(productRepository, times(1)).delete(same(existingProduct)); + } + + @Test + @Tag("valid") + public void testFindByIdCalledWithExactId() { + Long exactId = 55L; + when(productRepository.findById(eq(exactId))).thenReturn(Optional.empty()); + ResponseEntity response = controller.deleteProduct(exactId); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Object) response.getBody()); + verify(productRepository, times(1)).findById(eq(exactId)); + verify(productRepository, never()).delete(any(Product.class)); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("valid") + public void testConsecutiveDeleteFirstOkThenNotFound() { + Long targetId = 77L; // TODO: change for different test data if needed + Product existingProduct = new Product(); + when(productRepository.findById(eq(targetId))).thenReturn(Optional.of(existingProduct)) + .thenReturn(Optional.empty()); + doNothing().when(productRepository).delete(same(existingProduct)); + ResponseEntity firstResponse = controller.deleteProduct(targetId); + ResponseEntity secondResponse = controller.deleteProduct(targetId); + 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(eq(targetId)); + verify(productRepository, times(1)).delete(same(existingProduct)); + } + + @Test + @Tag("boundary") + public void testDeleteWithMaxLongIdReturnsNotFound() { + Long id = Long.MAX_VALUE; + when(productRepository.findById(eq(id))).thenReturn(Optional.empty()); + ResponseEntity response = controller.deleteProduct(id); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Object) response.getBody()); + verify(productRepository, times(1)).findById(eq(id)); + verify(productRepository, never()).delete(any(Product.class)); + } + + @Test + @Tag("boundary") + public void testDeleteWithMinLongIdReturnsNotFound() { + Long id = Long.MIN_VALUE; + when(productRepository.findById(eq(id))).thenReturn(Optional.empty()); + ResponseEntity response = controller.deleteProduct(id); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Object) response.getBody()); + verify(productRepository, times(1)).findById(eq(id)); + verify(productRepository, never()).delete(any(Product.class)); + } + + @Test + @Tag("valid") + public void testDeletePassesSameProductInstanceToRepository() { + Long validId = 88L; + Product specificProductInstance = new Product(); + when(productRepository.findById(eq(validId))).thenReturn(Optional.of(specificProductInstance)); + doNothing().when(productRepository).delete(any(Product.class)); + controller.deleteProduct(validId); + ArgumentCaptor captor = ArgumentCaptor.forClass(Product.class); + verify(productRepository, times(1)).delete(captor.capture()); + assertSame((Object) specificProductInstance, (Object) captor.getValue()); + } + + @Test + @Tag("invalid") + public void testDeleteWhenRepositoryNotInjectedThrowsNullPointerException() { + ProductController controllerWithoutRepo = new ProductController(); + Long anyId = 5L; + assertThrows(NullPointerException.class, () -> controllerWithoutRepo.deleteProduct(anyId)); + } + +} \ 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..720377c7 --- /dev/null +++ b/src/test/java/com/bootexample4/products/controller/ProductControllerGetAllProductsTest.java @@ -0,0 +1,111 @@ + +// ********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 + +Scenario 1: Returns an empty list when no products exist + +Details: + TestName: returnsEmptyListWhenRepositoryHasNoProducts + Description: Verifies that getAllProducts returns an empty List when the repository has no Product entries. This checks basic behavior when the data store is empty. + +Execution: + Arrange: Configure a mock ProductRepository so that findAll returns an empty List. + Act: Call getAllProducts on a ProductController instance wired with the mocked repository. + Assert: Use assertions such as assertNotNull on the returned value and assertTrue to confirm the list is empty (size equals 0). + +Validation: + Confirming an empty list ensures the controller method correctly delegates to productRepository.findAll and returns the repository’s result without modification. This behavior is important to avoid null handling issues on the client side and to signal “no data” cleanly. + + +*/ + +// ********RoostGPT******** + +package com.bootexample4.products.controller; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +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 com.bootexample4.products.model.Product; +import com.bootexample4.products.repository.ProductRepository; +import com.bootexample4.products.controller.ProductController; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@ExtendWith(MockitoExtension.class) +public class ProductControllerGetAllProductsTest { + + @Mock + private ProductRepository productRepository; + + @InjectMocks + private ProductController productController; + + @Test + @Tag("boundary") + public void testReturnsEmptyListWhenRepositoryHasNoProducts() { + List expected = new ArrayList<>(); + when(productRepository.findAll()).thenReturn(expected); + List actual = productController.getAllProducts(); + assertNotNull(actual, "Returned list should not be null"); + assertSame(expected, actual, "Controller should return the same list instance from repository"); + assertEquals((int) 0, (int) actual.size(), "Expected empty list when repository has no products"); + verify(productRepository, times(1)).findAll(); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("valid") + public void testReturnsListOfProductsWhenRepositoryHasEntries() { + Product product1 = new Product(); + Product product2 = new Product(); + // TODO: Set fields on product1 and product2 if needed for extended validations + List expected = Arrays.asList(product1, product2); + when(productRepository.findAll()).thenReturn(expected); + List actual = productController.getAllProducts(); + assertNotNull(actual, "Returned list should not be null"); + assertSame(expected, actual, "Controller should return the same list instance from repository"); + assertEquals((int) 2, (int) actual.size(), "Expected list size to match repository results"); + assertSame(product1, actual.get(0), "First product should match the repository result"); + assertSame(product2, actual.get(1), "Second product should match the repository result"); + verify(productRepository, times(1)).findAll(); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("invalid") + public void testPropagatesExceptionWhenRepositoryThrows() { + RuntimeException repoException = new RuntimeException("Repository failure"); + when(productRepository.findAll()).thenThrow(repoException); + RuntimeException thrown = assertThrows(RuntimeException.class, () -> productController.getAllProducts(), + "Expected exception to propagate from repository"); + assertSame(repoException, thrown, "The thrown exception instance should be the same as from repository"); + verify(productRepository, times(1)).findAll(); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("invalid") + public void testReturnsNullWhenRepositoryReturnsNull() { + when(productRepository.findAll()).thenReturn(null); + List actual = productController.getAllProducts(); + assertNull(actual, "Controller should return null if repository returns null"); + verify(productRepository, times(1)).findAll(); + verifyNoMoreInteractions(productRepository); + } + +} \ 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..a73e6b4c --- /dev/null +++ b/src/test/java/com/bootexample4/products/controller/ProductControllerGetProductByIdTest.java @@ -0,0 +1,365 @@ + +// ********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 + +Scenario 1: Return 200 OK with the Product in the response body when the Product exists + +Details: + TestName: returnsOkAndBodyWhenProductExists + Description: Verifies that getProductById returns a ResponseEntity with status code 200 and contains the exact Product instance in the body when ProductRepository.findById(id) returns a non-empty Optional. + +Execution: + Arrange: Configure a mock ProductRepository to return Optional.of(product) for a given valid id (e.g., 1L). Inject this mock into a ProductController instance. + Act: Invoke getProductById with the same id used in the mock setup. + Assert: Use JUnit assertions to confirm response.getStatusCodeValue() is 200 and response.getBody() is the exact same Product instance returned by the repository. + +Validation: + Confirming status 200 and the same object instance verifies the controller correctly maps a present Optional to ResponseEntity.ok().body(product). This test ensures the happy path works as intended and the body is not altered or replaced. + + +Scenario 2: Return 404 Not Found with no body when the Product is absent + +Details: + TestName: returnsNotFoundWhenProductDoesNotExist + Description: Ensures that getProductById responds with a 404 status and a null body when ProductRepository.findById(id) returns Optional.empty(). + +Execution: + Arrange: Configure a mock ProductRepository to return Optional.empty() for a given id (e.g., 999L). Inject this mock into ProductController. + Act: Invoke getProductById with that id. + Assert: Use JUnit assertions to verify response.getStatusCodeValue() equals 404 and response.getBody() is null. + +Validation: + This confirms the controller correctly translates an empty Optional into ResponseEntity.notFound().build(), signaling to clients that the requested resource does not exist. + + +Scenario 3: Propagate IllegalArgumentException when a null id is passed directly + +Details: + TestName: propagatesIllegalArgumentExceptionForNullId + Description: Validates that if getProductById is called directly with a null id (outside normal MVC path binding), and the repository throws IllegalArgumentException for findById(null), the exception is not caught and is propagated. + +Execution: + Arrange: Configure the mock ProductRepository so that findById(null) throws IllegalArgumentException. Inject it into ProductController. + Act: Call getProductById with null. + Assert: Use JUnit to expect an IllegalArgumentException to be thrown. + +Validation: + Since the method does not catch exceptions, this verifies transparent error propagation, allowing global exception handlers (if any) to manage such cases. + + +Scenario 4: Propagate unexpected RuntimeException thrown by the repository + +Details: + TestName: propagatesRuntimeExceptionFromRepository + Description: Confirms that if ProductRepository.findById(id) throws a RuntimeException (e.g., due to a data access error), getProductById does not swallow the exception and instead propagates it. + +Execution: + Arrange: Mock ProductRepository to throw a RuntimeException for a specific id. Inject the mock into ProductController. + Act: Invoke getProductById with that id. + Assert: Use JUnit to assert that a RuntimeException is thrown. + +Validation: + Ensures the controller method avoids masking repository failures, which is important for correct error handling and observability in the application. + + +Scenario 5: Return 404 Not Found for a negative id when no Product exists + +Details: + TestName: returnsNotFoundForNegativeId + Description: Verifies that a negative id (e.g., -1L) that does not correspond to any Product results in a 404 with a null body. + +Execution: + Arrange: Mock ProductRepository.findById(-1L) to return Optional.empty(). Inject this mock into the controller. + Act: Invoke getProductById(-1L). + Assert: Assert response.getStatusCodeValue() is 404 and response.getBody() is null. + +Validation: + Confirms consistent behavior for out-of-range identifiers, reinforcing correct 404 semantics for unfound resources. + + +Scenario 6: Return 404 Not Found for id equal to zero when no Product exists + +Details: + TestName: returnsNotFoundForZeroId + Description: Ensures that id = 0L (often invalid in many domains) yields 404 when no product is found. + +Execution: + Arrange: Mock ProductRepository.findById(0L) to return Optional.empty(). Inject the mock into the controller. + Act: Call getProductById(0L). + Assert: Verify 404 status and null body. + +Validation: + Demonstrates consistent not-found behavior for boundary values that do not map to existing data. + + +Scenario 7: Return 404 Not Found for a very large id (Long.MAX_VALUE) when no Product exists + +Details: + TestName: returnsNotFoundForVeryLargeId + Description: Checks that extremely large id values (e.g., Long.MAX_VALUE) that are absent in the repository correctly produce 404. + +Execution: + Arrange: Mock ProductRepository.findById(Long.MAX_VALUE) to return Optional.empty(). Inject the mock into the controller. + Act: Invoke getProductById(Long.MAX_VALUE). + Assert: Assert response.getStatusCodeValue() is 404 and response.getBody() is null. + +Validation: + Confirms robustness in handling upper boundary id values without errors and returning appropriate HTTP semantics. + + +Scenario 8: Invoke repository exactly once with the same id provided to the controller + +Details: + TestName: invokesFindByIdExactlyOnce + Description: Verifies interaction: the controller should call ProductRepository.findById(id) exactly once and with the exact id received. + +Execution: + Arrange: Prepare a mock ProductRepository to return Optional.empty() (or Optional.of(product)) for a given id. Inject into ProductController. + Act: Call getProductById with that id. + Assert: Use mock verification to assert findById was called once with the provided id, and no additional repository methods were invoked. + +Validation: + Ensures the controller does not perform unnecessary repository operations, maintaining efficiency and clarity of responsibility. + + +Scenario 9: Ensure the response body is null on not found + +Details: + TestName: bodyIsNullOnNotFound + Description: Confirms that when the product is not found, the ResponseEntity body is explicitly null rather than a placeholder object. + +Execution: + Arrange: Mock ProductRepository.findById(id) to return Optional.empty(). Inject into ProductController. + Act: Invoke getProductById(id). + Assert: Assert that response.getBody() is null and response.getStatusCodeValue() equals 404. + +Validation: + Guarantees correct REST semantics where a 404 response carries no body, avoiding misleading payloads. + + +Scenario 10: Ensure the exact Product instance from the repository is returned in the body + +Details: + TestName: returnsSameProductInstanceInBody + Description: Checks that the ResponseEntity contains the same Product instance reference as the one yielded by ProductRepository.findById(id), without copying or transforming it. + +Execution: + Arrange: Create a Product instance and mock ProductRepository.findById(id) to return Optional.of(thatInstance). Inject this mock into ProductController. + Act: Invoke getProductById(id). + Assert: Assert response.getStatusCodeValue() is 200 and response.getBody() == thatInstance (same reference). + +Validation: + Ensures that the controller passes through the repository result directly, which is important for preserving object identity and avoiding unexpected transformations. + + +Scenario 11: Confirm no unintended side effects on the repository during a read operation + +Details: + TestName: noUnexpectedRepositoryInteractions + Description: Ensures that getProductById only performs a read (findById) and does not call mutating methods such as save or delete. + +Execution: + Arrange: Create a mock ProductRepository and set findById behavior as needed (present or empty). Inject into ProductController. + Act: Call getProductById with a test id. + Assert: Verify that only findById was invoked and that no save or delete operations occurred on the repository mock. + +Validation: + Confirms proper adherence to the read-only contract of this endpoint, preventing accidental data changes. + + +Scenario 12: Use 200 OK status (not 201 or other codes) on successful retrieval + +Details: + TestName: uses200OkStatusOnSuccess + Description: Ensures that when a product is found, the HTTP status code is exactly 200, aligning with RESTful conventions for GET retrievals. + +Execution: + Arrange: Mock ProductRepository.findById(id) to return Optional.of(product). Inject mock into ProductController. + Act: Invoke getProductById(id). + Assert: Assert that response.getStatusCodeValue() equals 200 and that response.getBody() is not null. + +Validation: + Reinforces correct HTTP semantics for GET requests: successful resource retrieval should return 200 OK, enhancing API predictability and client compatibility. + +*/ + +// ********RoostGPT******** + +package com.bootexample4.products.controller; + +import java.util.Optional; +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 static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +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 ProductControllerGetProductByIdTest { + + @Mock + private ProductRepository productRepository; + + @InjectMocks + private ProductController productController; + + @Test + @Tag("valid") + public void testReturnsOkAndBodyWhenProductExists() { + Long id = Long.valueOf(1L); + Product product = new Product(); + when(productRepository.findById(id)).thenReturn(Optional.of(product)); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 200, (int) response.getStatusCodeValue()); + assertSame((Product) product, (Product) response.getBody()); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("invalid") + public void testReturnsNotFoundWhenProductDoesNotExist() { + Long id = Long.valueOf(999L); // TODO: Change to a non-existing id as per your + // dataset + when(productRepository.findById(id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Product) response.getBody()); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("invalid") + public void testPropagatesIllegalArgumentExceptionForNullId() { + 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 testPropagatesRuntimeExceptionFromRepository() { + Long id = Long.valueOf(123L); + when(productRepository.findById(id)).thenThrow(new RuntimeException()); + assertThrows(RuntimeException.class, () -> productController.getProductById(id)); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("boundary") + public void testReturnsNotFoundForNegativeId() { + Long id = Long.valueOf(-1L); + when(productRepository.findById(id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Product) response.getBody()); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("boundary") + public void testReturnsNotFoundForZeroId() { + Long id = Long.valueOf(0L); + when(productRepository.findById(id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Product) response.getBody()); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("boundary") + public void testReturnsNotFoundForVeryLargeId() { + Long id = Long.valueOf(Long.MAX_VALUE); + when(productRepository.findById(id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Product) response.getBody()); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("valid") + public void testInvokesFindByIdExactlyOnce() { + Long id = Long.valueOf(77L); + when(productRepository.findById(id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Product) response.getBody()); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("invalid") + public void testBodyIsNullOnNotFound() { + Long id = Long.valueOf(500L); + when(productRepository.findById(id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.getProductById(id); + assertNull((Product) response.getBody()); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("valid") + public void testReturnsSameProductInstanceInBody() { + Long id = Long.valueOf(2L); + Product sameInstance = new Product(); + when(productRepository.findById(id)).thenReturn(Optional.of(sameInstance)); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 200, (int) response.getStatusCodeValue()); + assertSame((Product) sameInstance, (Product) response.getBody()); + verify(productRepository, times(1)).findById(id); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("valid") + public void testNoUnexpectedRepositoryInteractions() { + Long id = Long.valueOf(300L); + when(productRepository.findById(id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 404, (int) response.getStatusCodeValue()); + assertNull((Product) response.getBody()); + verify(productRepository, times(1)).findById(id); + verify(productRepository, never()).save(any()); + verify(productRepository, never()).delete(any()); + verifyNoMoreInteractions(productRepository); + } + + @Test + @Tag("valid") + public void testUses200OkStatusOnSuccess() { + Long id = Long.valueOf(10L); + Product product = new Product(); + when(productRepository.findById(id)).thenReturn(Optional.of(product)); + ResponseEntity response = productController.getProductById(id); + assertEquals((int) 200, (int) response.getStatusCodeValue()); + assertNotNull((Product) 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/controller/ProductControllerUpdateProductTest.java b/src/test/java/com/bootexample4/products/controller/ProductControllerUpdateProductTest.java new file mode 100644 index 00000000..9e08dcd6 --- /dev/null +++ b/src/test/java/com/bootexample4/products/controller/ProductControllerUpdateProductTest.java @@ -0,0 +1,664 @@ + +// ********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 + +Scenario 1: Successful update when the product exists + +Details: + TestName: updateProductReturnsOkWithSavedEntity + Description: Verifies that when a product with the given id exists, the controller updates its fields (name, description, price), calls the repository to save the modified entity, and returns an HTTP 200 OK containing the instance returned by productRepository.save(...). + +Execution: + Arrange: Set up ProductRepository mock so that findById(id) returns Optional.of(existingProduct). Configure productRepository.save(existingProduct) to return updatedProduct (which may be the same or a different instance). Prepare an input Product with specific name, description, and price values. + Act: Invoke ProductController.updateProduct(id, inputProduct). + Assert: Confirm that the ResponseEntity status code is 200 OK and that the body is exactly the updatedProduct returned by productRepository.save(...). Verify productRepository.findById(id) was called once and productRepository.save(...) was called once with the existing product after its setters were invoked. + +Validation: + Ensures the controller correctly performs the update flow: reads by id, applies new values to the existing entity via setName, setDescription, setPrice, persists via save, and returns the saved entity in a 200 OK response. + +Scenario 2: Not found response when the product does not exist + +Details: + TestName: updateProductReturnsNotFoundWhenIdMissing + Description: Ensures that if no product is found for the given id, the controller does not attempt to save anything and returns an HTTP 404 Not Found. + +Execution: + Arrange: Configure productRepository.findById(id) to return Optional.empty(). + Act: Invoke ProductController.updateProduct(id, inputProduct). + Assert: Confirm that the ResponseEntity status code is 404 Not Found and that the response body is absent. Verify that productRepository.save(...) is never called. + +Validation: + Confirms proper handling of missing entities by returning 404 and avoiding unnecessary repository.save calls. + +Scenario 3: Null request body causes an exception + +Details: + TestName: updateProductThrowsWhenRequestBodyIsNull + Description: Validates behavior when a null Product is passed. Since the method dereferences product.getName(), this should result in a NullPointerException (or a similar runtime exception), propagating out of the method. + +Execution: + Arrange: Configure productRepository.findById(id) to return Optional.of(existingProduct). + Act: Invoke ProductController.updateProduct(id, null) and capture the thrown exception. + Assert: Use JUnit assertion to confirm that a NullPointerException (or compatible runtime exception) is thrown, and that productRepository.save(...) is not called. + +Validation: + Demonstrates that the controller does not guard against a null request body and relies on framework-layer validation; in a direct unit invocation a null body will cause an exception. + +Scenario 4: Incoming fields are null and are persisted as null + +Details: + TestName: updateProductAllowsNullFields + Description: Verifies that if the incoming Product has null name, description, and price, the controller sets those fields to null on the existing product and persists the change, returning 200 OK. + +Execution: + Arrange: Configure productRepository.findById(id) to return Optional.of(existingProduct). Create inputProduct with null name, null description, and null price. Configure productRepository.save(existingProduct) to return updatedProduct (with those nulls). + Act: Invoke ProductController.updateProduct(id, inputProduct). + Assert: Check that the response status is 200 OK and the response body is updatedProduct. Verify that setName(null), setDescription(null), and setPrice(null) were called on the existing product before save. + +Validation: + Confirms that the controller performs a direct field overwrite with nulls when provided, with no additional validation or defaults applied. + +Scenario 5: Negative price is accepted and saved + +Details: + TestName: updateProductPersistsNegativePrice + Description: Ensures a negative price value from the request is applied to the existing product and saved without validation, returning 200 OK. + +Execution: + Arrange: Configure productRepository.findById(id) to return Optional.of(existingProduct). Create inputProduct with a negative price and valid name/description. Configure productRepository.save(existingProduct) to return updatedProduct reflecting the negative price. + Act: Invoke ProductController.updateProduct(id, inputProduct). + Assert: Verify HTTP 200 OK and that the response body equals updatedProduct. Confirm that setPrice(negativeValue) was called and save(...) was invoked once. + +Validation: + Demonstrates the absence of business validation on price, ensuring the controller simply forwards values to persistence. + +Scenario 6: Very large price value is accepted and saved + +Details: + TestName: updateProductPersistsExtremePriceValue + Description: Verifies that extremely large numeric price values (e.g., boundary values for the type used in Product) are passed through and saved, returning 200 OK. + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). inputProduct has a very large price value. productRepository.save(existingProduct) returns updatedProduct with that value. + Act: Call ProductController.updateProduct(id, inputProduct). + Assert: Confirm 200 OK and that the returned body equals updatedProduct. Verify setPrice(largeValue) invocation occurred. + +Validation: + Confirms that no range checks are done by the controller and that large numeric values are persisted. + +Scenario 7: Empty strings for name and description are accepted + +Details: + TestName: updateProductAcceptsEmptyTextFields + Description: Ensures that empty strings for name and description are accepted, applied, saved, and returned with 200 OK. + +Execution: + Arrange: Mock productRepository.findById(id) to return Optional.of(existingProduct). Create inputProduct with empty name and empty description and a valid price. Configure productRepository.save(existingProduct) to return updatedProduct reflecting these values. + Act: Invoke updateProduct(id, inputProduct). + Assert: Verify 200 OK and body equals updatedProduct. Confirm setName("") and setDescription("") were called. + +Validation: + Confirms that the method does not enforce non-empty constraints on textual fields. + +Scenario 8: Repository findById throws a runtime exception + +Details: + TestName: updateProductPropagatesExceptionFromFindById + Description: Validates that if productRepository.findById(id) throws a runtime exception, the method does not swallow it and the exception propagates to the caller. + +Execution: + Arrange: Configure productRepository.findById(id) to throw a RuntimeException. + Act: Call ProductController.updateProduct(id, inputProduct) and capture the thrown exception. + Assert: Assert that the same type of RuntimeException is thrown and that productRepository.save(...) is never invoked. + +Validation: + Ensures proper fail-fast behavior when repository access fails, signaling error conditions to higher layers. + +Scenario 9: Repository save throws a runtime exception + +Details: + TestName: updateProductPropagatesExceptionFromSave + Description: Checks that if productRepository.save(existingProduct) throws a runtime exception, the method propagates the exception instead of returning a ResponseEntity. + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). productRepository.save(existingProduct) is set to throw a RuntimeException. + Act: Invoke updateProduct(id, inputProduct) and capture the thrown exception. + Assert: Confirm that a RuntimeException is thrown and that no ResponseEntity is produced. + +Validation: + Verifies that persistence errors during save result in exceptions bubbling up for appropriate error handling by the framework. + +Scenario 10: Save returns a different instance and the response uses that instance + +Details: + TestName: updateProductReturnsInstanceProvidedBySave + Description: Ensures that if productRepository.save(...) returns an instance different from the one passed in, the response body is exactly the instance returned by save. + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). Configure productRepository.save(existingProduct) to return a new updatedProduct instance. Prepare inputProduct with distinct field values. + Act: Invoke updateProduct(id, inputProduct). + Assert: Assert that ResponseEntity body is the same object as updatedProduct returned by save (using identity comparison in test). Verify HTTP 200 OK. + +Validation: + Confirms that the controller returns the persisted entity result from the repository, not merely the in-memory modified instance. + +Scenario 11: Save returns null leading to 200 OK with null body + +Details: + TestName: updateProductHandlesNullReturnFromSave + Description: Verifies behavior if productRepository.save(...) returns null. The controller should still return 200 OK with a null body because it passes the returned value directly to ResponseEntity.ok().body(...). + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). Configure productRepository.save(existingProduct) to return null. + Act: Call updateProduct(id, inputProduct). + Assert: Confirm that status is 200 OK and the ResponseEntity body is null. + +Validation: + Demonstrates the controller’s direct pass-through of the repository’s returned object, even if null. + +Scenario 12: No update attempted when not found (interaction verification) + +Details: + TestName: updateProductDoesNotCallSaveWhenNotFound + Description: Interaction-focused scenario to ensure that if findById returns empty, the controller does not call save at all. + +Execution: + Arrange: productRepository.findById(id) returns Optional.empty(). + Act: Invoke updateProduct(id, inputProduct). + Assert: Verify that productRepository.save(...) is never called and that the status is 404 Not Found. + +Validation: + Important to confirm the controller avoids unnecessary persistence operations when the entity is absent. + +Scenario 13: Id is null causing repository to fail + +Details: + TestName: updateProductThrowsWhenIdIsNull + Description: When invoked with a null id, productRepository.findById(null) will typically throw a runtime exception. The method should propagate this exception to the caller. + +Execution: + Arrange: Configure productRepository.findById(null) to throw a RuntimeException (e.g., IllegalArgumentException or a generic runtime error as appropriate for the repository mock). + Act: Call updateProduct(null, inputProduct) and capture the exception. + Assert: Assert that a RuntimeException is thrown and that productRepository.save(...) is not invoked. + +Validation: + Confirms that the method requires a non-null path variable id and does not handle null id values internally. + +Scenario 14: Fields are unchanged but save is still called + +Details: + TestName: updateProductSavesEvenIfValuesUnchanged + Description: Ensures that if the incoming values are identical to the existing entity’s values, the controller still invokes save and returns 200 OK. + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). inputProduct carries the same name, description, and price values that existingProduct already has. productRepository.save(existingProduct) returns updatedProduct. + Act: Invoke updateProduct(id, inputProduct). + Assert: Confirm 200 OK and response body equals updatedProduct. Verify productRepository.save(...) was called once. + +Validation: + Shows that the controller does not perform change detection and proceeds to save regardless of whether values differ. + +Scenario 15: Leading/trailing whitespace in text fields is preserved + +Details: + TestName: updateProductPreservesWhitespaceInTextFields + Description: Validates that the controller does not trim or alter string fields; it persists the strings exactly as provided. + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). inputProduct has name and description with leading/trailing spaces. productRepository.save(existingProduct) returns updatedProduct reflecting those exact strings. + Act: Call updateProduct(id, inputProduct). + Assert: Confirm 200 OK and that the returned body equals updatedProduct. Verify that setName and setDescription received strings with the exact whitespace. + +Validation: + Ensures no implicit normalization on input strings is performed by the controller. + +Scenario 16: Zero price is accepted and saved + +Details: + TestName: updateProductAcceptsZeroPrice + Description: Ensures that a zero price is treated as valid input and is persisted, returning 200 OK. + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). inputProduct has price set to zero. productRepository.save(existingProduct) returns updatedProduct with zero price. + Act: Invoke updateProduct(id, inputProduct). + Assert: Confirm 200 OK and that body equals updatedProduct. Verify setPrice(0) was called. + +Validation: + Confirms no minimum price constraint enforced by the controller. + +Scenario 17: Correct order of repository method calls + +Details: + TestName: updateProductCallsFindThenSaveInOrder + Description: Ensures that findById is called before save and that save is only called if findById returns a value. + +Execution: + Arrange: Use a mocking framework to track invocation order. Configure productRepository.findById(id) to return Optional.of(existingProduct). Configure save to return updatedProduct. + Act: Call updateProduct(id, inputProduct). + Assert: Verify that findById(id) is invoked exactly once before save(existingProduct) is called. Also verify status 200 OK. + +Validation: + Confirms proper call sequencing in the update workflow. + +Scenario 18: Response status is explicitly 200 OK on success + +Details: + TestName: updateProductReturnsHttp200OnSuccess + Description: Specifically checks that the response status code is 200 (OK) and not any other code like 201 or 204 when the update succeeds. + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). productRepository.save(existingProduct) returns updatedProduct. + Act: Invoke updateProduct(id, inputProduct). + Assert: Verify that the ResponseEntity status code is exactly 200 OK. + +Validation: + Aligns with REST semantics for updates, confirming the controller returns the correct success status. + +Scenario 19: Save is invoked exactly once on success + +Details: + TestName: updateProductCallsSaveOnceOnSuccess + Description: Ensures that, in the successful path, productRepository.save is called exactly once and no extra save calls occur. + +Execution: + Arrange: Configure productRepository.findById(id) to return Optional.of(existingProduct). Configure productRepository.save(existingProduct) to return updatedProduct. + Act: Call updateProduct(id, inputProduct). + Assert: Verify 200 OK and that productRepository.save(...) was invoked exactly once. + +Validation: + Confirms efficient and correct persistence behavior, minimizing unintended repeated writes. + +Scenario 20: Correctly propagates incoming values to existing entity via setters + +Details: + TestName: updateProductSetsAllMutableFieldsFromInput + Description: Verifies that the controller invokes setName, setDescription, and setPrice on the existing product with the corresponding values obtained from inputProduct.getName(), inputProduct.getDescription(), and inputProduct.getPrice(). + +Execution: + Arrange: productRepository.findById(id) returns Optional.of(existingProduct). Provide inputProduct with distinct values for each field. Configure productRepository.save(existingProduct) to return updatedProduct. + Act: Invoke updateProduct(id, inputProduct). + Assert: Verify that setName, setDescription, and setPrice were each called once with the exact values from inputProduct, then verify 200 OK and the body equals updatedProduct. + +Validation: + Confirms the core logic of the update method: transferring input values into the persisted entity through the appropriate setter methods. + +*/ + +// ********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.Assertions; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import java.util.Optional; +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 { + + @Mock + private ProductRepository productRepository; + + @InjectMocks + private ProductController productController; + + @Test + @Tag("valid") + public void updateProductReturnsOkWithSavedEntity() { + Long id = 100L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Updated Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Updated Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(123.45)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + InOrder inOrder = Mockito.inOrder(productRepository, existingProduct); + inOrder.verify(productRepository, Mockito.times(1)).findById((Long) id); + inOrder.verify(existingProduct, Mockito.times(1)).setName((String) "Updated Name"); + inOrder.verify(existingProduct, Mockito.times(1)).setDescription((String) "Updated Description"); + inOrder.verify(existingProduct, Mockito.times(1)).setPrice((Double) Double.valueOf(123.45)); + inOrder.verify(productRepository, Mockito.times(1)).save((Product) existingProduct); + } + + @Test + @Tag("invalid") + public void updateProductReturnsNotFoundWhenIdMissing() { + Long id = 101L; + Product inputProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 404, (int) response.getStatusCodeValue()); + Assertions.assertNull((Product) response.getBody()); + Mockito.verify(productRepository, Mockito.never()).save(Mockito.any(Product.class)); + } + + @Test + @Tag("invalid") + public void updateProductThrowsWhenRequestBodyIsNull() { + Long id = 102L; + Product existingProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Assertions.assertThrows(NullPointerException.class, + () -> productController.updateProduct((Long) id, (Product) null)); + Mockito.verify(productRepository, Mockito.never()).save(Mockito.any(Product.class)); + } + + @Test + @Tag("valid") + public void updateProductAllowsNullFields() { + Long id = 103L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn(null); + Mockito.when(inputProduct.getDescription()).thenReturn(null); + Mockito.when(inputProduct.getPrice()).thenReturn(null); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + Mockito.verify(existingProduct, Mockito.times(1)).setName(Mockito.isNull()); + Mockito.verify(existingProduct, Mockito.times(1)).setDescription(Mockito.isNull()); + Mockito.verify(existingProduct, Mockito.times(1)).setPrice(Mockito.isNull()); + } + + @Test + @Tag("boundary") + public void updateProductPersistsNegativePrice() { + Long id = 104L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Double negativePrice = Double.valueOf(-999.99); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(negativePrice); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + Mockito.verify(existingProduct, Mockito.times(1)).setPrice((Double) negativePrice); + Mockito.verify(productRepository, Mockito.times(1)).save((Product) existingProduct); + } + + @Test + @Tag("boundary") + public void updateProductPersistsExtremePriceValue() { + Long id = 105L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Double largePrice = Double.valueOf(Double.MAX_VALUE); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(largePrice); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + Mockito.verify(existingProduct, Mockito.times(1)).setPrice((Double) largePrice); + } + + @Test + @Tag("boundary") + public void updateProductAcceptsEmptyTextFields() { + Long id = 106L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn(""); + Mockito.when(inputProduct.getDescription()).thenReturn(""); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(10.0)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + Mockito.verify(existingProduct, Mockito.times(1)).setName((String) ""); + Mockito.verify(existingProduct, Mockito.times(1)).setDescription((String) ""); + } + + @Test + @Tag("invalid") + public void updateProductPropagatesExceptionFromFindById() { + Long id = 107L; + Product inputProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenThrow(new RuntimeException("Data access error")); + Assertions.assertThrows(RuntimeException.class, + () -> productController.updateProduct((Long) id, (Product) inputProduct)); + Mockito.verify(productRepository, Mockito.never()).save(Mockito.any(Product.class)); + } + + @Test + @Tag("invalid") + public void updateProductPropagatesExceptionFromSave() { + Long id = 108L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(5.0)); + Mockito.when(productRepository.save((Product) existingProduct)).thenThrow(new RuntimeException("Save failed")); + Assertions.assertThrows(RuntimeException.class, + () -> productController.updateProduct((Long) id, (Product) inputProduct)); + } + + @Test + @Tag("valid") + public void updateProductReturnsInstanceProvidedBySave() { + Long id = 109L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProductDifferent = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Different Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Different Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(77.7)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProductDifferent); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProductDifferent, (Product) response.getBody()); + } + + @Test + @Tag("valid") + public void updateProductHandlesNullReturnFromSave() { + Long id = 110L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(1.0)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(null); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertNull((Product) response.getBody()); + } + + @Test + @Tag("invalid") + public void updateProductDoesNotCallSaveWhenNotFound() { + Long id = 111L; + Product inputProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.empty()); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 404, (int) response.getStatusCodeValue()); + Mockito.verify(productRepository, Mockito.never()).save(Mockito.any(Product.class)); + } + + @Test + @Tag("invalid") + public void updateProductThrowsWhenIdIsNull() { + Long id = null; // TODO adjust if null IDs are handled differently in repository + Product inputProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenThrow(new RuntimeException("Null id")); + Assertions.assertThrows(RuntimeException.class, + () -> productController.updateProduct((Long) id, (Product) inputProduct)); + Mockito.verify(productRepository, Mockito.never()).save(Mockito.any(Product.class)); + } + + @Test + @Tag("valid") + public void updateProductSavesEvenIfValuesUnchanged() { + Long id = 112L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Same Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Same Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(50.0)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + Mockito.verify(productRepository, Mockito.times(1)).save((Product) existingProduct); + } + + @Test + @Tag("boundary") + public void updateProductPreservesWhitespaceInTextFields() { + Long id = 113L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + String nameWithSpaces = " Name With Spaces "; + String descriptionWithSpaces = " Description With Spaces "; + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn(nameWithSpaces); + Mockito.when(inputProduct.getDescription()).thenReturn(descriptionWithSpaces); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(15.0)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + Mockito.verify(existingProduct, Mockito.times(1)).setName((String) nameWithSpaces); + Mockito.verify(existingProduct, Mockito.times(1)).setDescription((String) descriptionWithSpaces); + } + + @Test + @Tag("boundary") + public void updateProductAcceptsZeroPrice() { + Long id = 114L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Double zeroPrice = Double.valueOf(0.0); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(zeroPrice); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + Mockito.verify(existingProduct, Mockito.times(1)).setPrice((Double) zeroPrice); + } + + @Test + @Tag("valid") + public void updateProductCallsFindThenSaveInOrder() { + Long id = 115L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Order Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Order Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(33.0)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + InOrder inOrder = Mockito.inOrder(productRepository, existingProduct); + inOrder.verify(productRepository).findById((Long) id); + inOrder.verify(existingProduct).setName((String) "Order Name"); + inOrder.verify(existingProduct).setDescription((String) "Order Description"); + inOrder.verify(existingProduct).setPrice((Double) Double.valueOf(33.0)); + inOrder.verify(productRepository).save((Product) existingProduct); + } + + @Test + @Tag("valid") + public void updateProductReturnsHttp200OnSuccess() { + Long id = 116L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(2.0)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((HttpStatus) HttpStatus.OK, (HttpStatus) response.getStatusCode()); + } + + @Test + @Tag("valid") + public void updateProductCallsSaveOnceOnSuccess() { + Long id = 117L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn("Name"); + Mockito.when(inputProduct.getDescription()).thenReturn("Description"); + Mockito.when(inputProduct.getPrice()).thenReturn(Double.valueOf(22.0)); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Mockito.verify(productRepository, Mockito.times(1)).save((Product) existingProduct); + } + + @Test + @Tag("valid") + public void updateProductSetsAllMutableFieldsFromInput() { + Long id = 118L; + Product existingProduct = Mockito.mock(Product.class); + Product inputProduct = Mockito.mock(Product.class); + Product updatedProduct = Mockito.mock(Product.class); + String newName = "New Product Name"; + String newDescription = "New Product Description"; + Double newPrice = Double.valueOf(123.0); + Mockito.when(productRepository.findById((Long) id)).thenReturn(Optional.of(existingProduct)); + Mockito.when(inputProduct.getName()).thenReturn(newName); + Mockito.when(inputProduct.getDescription()).thenReturn(newDescription); + Mockito.when(inputProduct.getPrice()).thenReturn(newPrice); + Mockito.when(productRepository.save((Product) existingProduct)).thenReturn(updatedProduct); + ResponseEntity response = productController.updateProduct((Long) id, (Product) inputProduct); + Assertions.assertEquals((int) 200, (int) response.getStatusCodeValue()); + Assertions.assertSame((Product) updatedProduct, (Product) response.getBody()); + Mockito.verify(existingProduct, Mockito.times(1)).setName((String) newName); + Mockito.verify(existingProduct, Mockito.times(1)).setDescription((String) newDescription); + Mockito.verify(existingProduct, Mockito.times(1)).setPrice((Double) newPrice); + } + +} \ 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..da6d41e1 --- /dev/null +++ b/src/test/java/com/bootexample4/products/model/ProductGetDescriptionTest.java @@ -0,0 +1,397 @@ + +// ********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 + +Scenario 1: Returns null when description is not initialized + +Details: + TestName: returnsNullWhenDescriptionNotInitialized + Description: Verifies that a newly constructed Product instance returns null from getDescription() when no description has been set. This checks the default state of the entity. + +Execution: + Arrange: Create a new Product instance using its no-argument constructor without invoking setDescription. + Act: Call getDescription() on the Product instance. + Assert: Use assertNull to confirm that the returned value is null. + +Validation: + This assertion verifies that the description field defaults to null, aligning with standard Java object behavior for uninitialized reference fields. It is significant because it ensures callers can distinguish between “no description provided” and any actual text value. + +Scenario 2: Returns the same description after being set to a typical value + +Details: + TestName: returnsDescriptionAfterSettingTypicalValue + Description: Ensures that getDescription() returns the exact string previously assigned via setDescription(String) for a normal, human-readable description. + +Execution: + Arrange: Create a Product and call setDescription with a typical value such as "A standard product description". + Act: Invoke getDescription(). + Assert: Use assertEquals to compare the returned string with the one that was set. + +Validation: + Confirms basic setter/getter correctness. This is fundamental for application behavior since many features (e.g., display layers, API responses) rely on accurate retrieval of product descriptions. + +Scenario 3: Returns empty string when description is set to empty + +Details: + TestName: returnsEmptyStringWhenDescriptionSetToEmpty + Description: Validates that setting the description to "" (an empty string) results in getDescription() returning an empty string, not null or a trimmed/altered value. + +Execution: + Arrange: Create a Product and call setDescription(""). + Act: Call getDescription(). + Assert: Use assertEquals with "" as the expected value. + +Validation: + Ensures that the method preserves empty string values as-is. This matters for UIs or APIs that differentiate between an empty description and the absence of one. + +Scenario 4: Preserves whitespace-only descriptions unchanged + +Details: + TestName: preservesWhitespaceOnlyDescription + Description: Checks that getDescription() preserves whitespace-only strings (e.g., spaces and tabs) without trimming or normalization. + +Execution: + Arrange: Create a Product and setDescription(" \t "). + Act: Call getDescription(). + Assert: Use assertEquals to ensure the exact whitespace pattern is returned. + +Validation: + Verifies there is no unintended trimming, which is important for cases where whitespace may be meaningful or where the application expects to handle normalization explicitly elsewhere. + +Scenario 5: Preserves leading and trailing spaces in the description + +Details: + TestName: preservesLeadingAndTrailingSpacesInDescription + Description: Ensures that leading and trailing spaces are returned exactly as set. + +Execution: + Arrange: Create a Product and setDescription(" Leading and trailing spaces "). + Act: Invoke getDescription(). + Assert: Use assertEquals to match the exact string including spaces. + +Validation: + Confirms that the getter does not modify content and that any formatting decisions remain the responsibility of upstream or downstream components. + +Scenario 6: Returns very long description unchanged + +Details: + TestName: returnsVeryLongDescriptionUnchanged + Description: Verifies that getDescription() can handle and return a very long string (e.g., thousands of characters) exactly as set. + +Execution: + Arrange: Construct a very long string (e.g., 10,000 'a' characters) and call setDescription with it. + Act: Call getDescription(). + Assert: Use assertEquals to compare the entire long string to the expected value. + +Validation: + Ensures robustness for large inputs, which may occur in rich product catalogs, and confirms there is no truncation or corruption. + +Scenario 7: Preserves special characters, newlines, and tabs + +Details: + TestName: preservesSpecialCharactersNewlinesAndTabs + Description: Validates that special characters (punctuation, newlines, tabs, symbols) are preserved exactly. + +Execution: + Arrange: setDescription with a string like "Line1\nLine2\t— ✓ #$%&". + Act: Invoke getDescription(). + Assert: Use assertEquals to verify the exact string including control characters. + +Validation: + Important for correct rendering and storage of formatted descriptions that may include line breaks or special symbols. + +Scenario 8: Preserves Unicode and non-Latin characters + +Details: + TestName: returnsUnicodeDescriptionUnchanged + Description: Ensures that getDescription() returns strings with Unicode characters (e.g., accents, emoji, non-Latin scripts) exactly as set. + +Execution: + Arrange: setDescription with "Café ☕ – пример مثال 你好". + Act: Call getDescription(). + Assert: Use assertEquals to ensure the exact Unicode content is returned. + +Validation: + Critical for internationalization and proper handling of multilingual product content. + +Scenario 9: Overwrites previous description with subsequent set + +Details: + TestName: overwritesPreviousDescriptionOnSubsequentSet + Description: Confirms that setting the description multiple times results in getDescription() returning the most recently assigned value. + +Execution: + Arrange: Create a Product; call setDescription("Old description"), then call setDescription("New description"). + Act: Invoke getDescription(). + Assert: Use assertEquals to verify the value is "New description". + +Validation: + Ensures expected mutability behavior where the latest setter call defines the current state, which is essential for updates in administrative flows. + +Scenario 10: Returns null after setting description to null + +Details: + TestName: returnsNullAfterSettingDescriptionToNull + Description: Verifies that explicitly setting the description to null produces a null result from getDescription(). + +Execution: + Arrange: Create a Product; call setDescription("Some description"); then call setDescription(null). + Act: Call getDescription(). + Assert: Use assertNull to confirm the result is null. + +Validation: + Confirms that the field can be cleared and that null values are handled without throwing exceptions or being coerced to another value. + +Scenario 11: Description is independent of name, id, and price when set + +Details: + TestName: descriptionUnaffectedByNameIdAndPriceChanges + Description: Ensures that changes to other fields (name, id, price) do not affect the value returned by getDescription() once set. + +Execution: + Arrange: Create a Product; setDescription("Primary description"); setName("Widget Pro"); setId(42L); setPrice(199.99). + Act: Invoke getDescription(). + Assert: Use assertEquals to confirm it returns "Primary description". + +Validation: + Verifies field isolation, ensuring that unrelated property changes do not inadvertently modify the description. + +Scenario 12: Description remains null when only other fields are set + +Details: + TestName: returnsNullWhenOnlyOtherFieldsAreSet + Description: Confirms that if description is never set, getDescription() remains null even when other fields are populated. + +Execution: + Arrange: Create a Product; setName("Widget"); setId(5L); setPrice(9.99), but do not call setDescription. + Act: Call getDescription(). + Assert: Use assertNull to verify the result is null. + +Validation: + Ensures there is no implicit initialization or side effect that populates description when unrelated fields are set. + +Scenario 13: Preserves case sensitivity in description + +Details: + TestName: preservesCaseSensitivityInDescription + Description: Validates that uppercase and lowercase characters are not altered. + +Execution: + Arrange: setDescription("Product Pro MAX edition"). + Act: Call getDescription(). + Assert: Use assertEquals to confirm exact case is preserved. + +Validation: + Important for brand/style correctness and any logic that may rely on exact casing. + +Scenario 14: Returns the same String reference that was assigned + +Details: + TestName: returnsSameStringReferenceAsAssigned + Description: Confirms that getDescription() returns the same String instance reference that was passed to setDescription, not a copy or new instance. + +Execution: + Arrange: Create a new String instance explicitly (e.g., using new String("RefValue")) and assign it via setDescription. + Act: Retrieve the value with getDescription(). + Assert: Use assertSame to verify the returned reference is the exact same object instance passed in. + +Validation: + Demonstrates that the setter stores the reference directly, which is expected for String fields and can be relevant for memory/reference-sensitive tests (even though Strings are immutable). + +Scenario 15: Description remains the last assigned value after multiple other field updates + +Details: + TestName: getDescriptionAfterMultipleFieldUpdatesRemainsLastAssigned + Description: Ensures that even after several updates to name, id, and price, and multiple calls to setDescription, getDescription() reflects the final assigned description. + +Execution: + Arrange: Create a Product; setDescription("First"); setName("Alpha"); setPrice(10.0); setId(1L); setDescription("Second"); setName("Beta"); setPrice(20.0); setId(2L); setDescription("Final value"). + Act: Call getDescription(). + Assert: Use assertEquals to confirm the result is "Final value". + +Validation: + Verifies cumulative update behavior and that the final state of the description is accurately retrievable regardless of other property changes. + +*/ + +// ********RoostGPT******** + +package com.bootexample4.products.model; + +import org.junit.jupiter.api.BeforeEach; +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 ProductGetDescriptionTest { + + private Product product; + + @BeforeEach + public void setUp() { + product = new Product(); + } + + @Test + @Tag("valid") + public void returnsNullWhenDescriptionNotInitialized() { + String actual = product.getDescription(); + assertNull((Object) actual); + } + + @Test + @Tag("valid") + public void returnsDescriptionAfterSettingTypicalValue() { + String expected = "A standard product description"; + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + public void returnsEmptyStringWhenDescriptionSetToEmpty() { + String expected = ""; + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + public void preservesWhitespaceOnlyDescription() { + String expected = " \t "; + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + public void preservesLeadingAndTrailingSpacesInDescription() { + String expected = " Leading and trailing spaces "; + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + public void returnsVeryLongDescriptionUnchanged() { + StringBuilder sb = new StringBuilder(); + int length = 10_000; // TODO adjust if needed based on performance constraints + for (int i = 0; i < length; i++) { + sb.append('a'); + } + String expected = sb.toString(); + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + assertEquals((int) expected.length(), (int) actual.length()); + } + + @Test + @Tag("valid") + public void preservesSpecialCharactersNewlinesAndTabs() { + String expected = "Line1\nLine2\t— ✓ #$%&"; + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + public void returnsUnicodeDescriptionUnchanged() { + String expected = "Café ☕ – пример مثال 你好"; + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + public void overwritesPreviousDescriptionOnSubsequentSet() { + product.setDescription("Old description"); + String expected = "New description"; + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + public void returnsNullAfterSettingDescriptionToNull() { + product.setDescription("Some description"); + product.setDescription(null); + String actual = product.getDescription(); + assertNull((Object) actual); + } + + @Test + @Tag("valid") + public void descriptionUnaffectedByNameIdAndPriceChanges() { + String expected = "Primary description"; + product.setDescription(expected); + product.setName("Widget Pro"); + product.setId(42L); + product.setPrice(199.99d); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + public void returnsNullWhenOnlyOtherFieldsAreSet() { + product.setName("Widget"); + product.setId(5L); + product.setPrice(9.99d); + String actual = product.getDescription(); + assertNull((Object) actual); + } + + @Test + @Tag("valid") + public void preservesCaseSensitivityInDescription() { + String expected = "Product Pro MAX edition"; + product.setDescription(expected); + String actual = product.getDescription(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + public void returnsSameStringReferenceAsAssigned() { + String expectedRef = new String("RefValue"); + product.setDescription(expectedRef); + String actualRef = product.getDescription(); + assertSame((Object) expectedRef, (Object) actualRef); + } + + @Test + @Tag("valid") + public void getDescriptionAfterMultipleFieldUpdatesRemainsLastAssigned() { + product.setDescription("First"); + product.setName("Alpha"); + product.setPrice(10.0d); + product.setId(1L); + product.setDescription("Second"); + product.setName("Beta"); + product.setPrice(20.0d); + product.setId(2L); + String expected = "Final value"; + product.setDescription(expected); + String actual = product.getDescription(); + 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..031eb7d5 --- /dev/null +++ b/src/test/java/com/bootexample4/products/model/ProductGetIdTest.java @@ -0,0 +1,103 @@ + +// ********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 + +Scenario 1: getId returns null for a newly constructed Product + +Details: + TestName: getIdReturnsNullByDefault + Description: Verify that a newly created Product instance has a null identifier before any assignment or persistence, aligning with JPA expectations for @GeneratedValue fields. + +Execution: + Arrange: Create a new Product instance using its default constructor without setting any fields. + Act: Invoke getId() on the newly created Product instance. + Assert: Use a JUnit assertion (e.g., assertNull) to confirm that the returned value is null. + +Validation: + This assertion verifies that the default state of id is null prior to any manual assignment or persistence-generated value. It is significant because JPA entities commonly initialize identifiers to null until persisted, and the getter should accurately reflect this state. + +*/ + +// ********RoostGPT******** + +package com.bootexample4.products.model; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.*; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +public class ProductGetIdTest { + + private Product product; + + @BeforeEach + public void setUp() { + product = new Product(); + } + + @Test + @Tag("valid") + public void testGetIdReturnsNullByDefault() { + Long value = product.getId(); + assertNull((Object) value); + } + + @Test + @Tag("valid") + public void testGetIdReturnsAssignedPositiveValue() { + Long expected = Long.valueOf(123L); // TODO change the expected ID value if + // business rules require a specific ID + product.setId(expected); + Long actual = product.getId(); + assertEquals((Long) expected, (Long) actual); + } + + @Test + @Tag("boundary") + public void testGetIdReturnsZeroWhenIdIsZero() { + Long expected = Long.valueOf(0L); + product.setId(expected); + Long actual = product.getId(); + assertEquals((Long) expected, (Long) actual); + } + + @Test + @Tag("boundary") + public void testGetIdHandlesMaxLongValue() { + Long expected = Long.valueOf(Long.MAX_VALUE); + product.setId(expected); + Long actual = product.getId(); + assertEquals((Long) expected, (Long) actual); + } + + @Test + @Tag("boundary") + public void testGetIdReturnsNullAfterSettingNull() { + product.setId(null); + Long actual = product.getId(); + assertNull((Object) actual); + } + + @Test + @Tag("valid") + public void testGetIdReflectsUpdatedValue() { + Long first = Long.valueOf(1L); + Long second = Long.valueOf(2L); + product.setId(first); + assertEquals((Long) first, (Long) product.getId()); + product.setId(second); + Long actual = product.getId(); + assertEquals((Long) second, (Long) actual); + } + +} \ 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..418a08c7 --- /dev/null +++ b/src/test/java/com/bootexample4/products/model/ProductGetNameTest.java @@ -0,0 +1,510 @@ + +// ********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 + +Scenario 1: Returns the assigned simple ASCII name + +Details: + TestName: returnsNameAfterSettingSimpleValue + Description: Verify that getName returns the same simple ASCII string that was previously assigned via setName on a Product instance. + +Execution: + Arrange: Create a new Product and call setName with a value like "Laptop". + Act: Invoke getName to retrieve the current name. + Assert: Use assertEquals("Laptop", actualName) to confirm the returned value matches the assigned value. + +Validation: + Confirm that the getter directly returns the stored field without modification. This validates the basic contract of a standard getter in a POJO and ensures correct behavior for typical inputs. + + +Scenario 2: Default name is null before any assignment + +Details: + TestName: defaultNameIsNullBeforeAnyAssignment + Description: Ensure that a newly created Product has a null name when setName has not been called. + +Execution: + Arrange: Instantiate a new Product without calling setName. + Act: Call getName to retrieve the default name. + Assert: Use assertNull(actualName) to verify the default is null. + +Validation: + Verifies that the default state of the name field is null as no initialization value is provided. This is important for handling optional fields and null checks in business logic. + + +Scenario 3: Returns null after explicitly setting name to null + +Details: + TestName: returnsNullAfterSettingNullName + Description: Confirm that getName returns null when setName(null) has been invoked. + +Execution: + Arrange: Create a Product and call setName(null). + Act: Invoke getName. + Assert: Use assertNull(actualName). + +Validation: + Ensures that null assignments are preserved and not converted to empty or default values. This reflects the absence of validation or transformation in the setter/getter pair. + + +Scenario 4: Returns an empty string when name is set to empty + +Details: + TestName: returnsEmptyStringWhenSetToEmpty + Description: Verify that an empty string assigned via setName("") is returned unchanged by getName. + +Execution: + Arrange: Instantiate Product and call setName(""). + Act: Call getName. + Assert: Use assertEquals("", actualName). + +Validation: + Confirms that empty values are preserved exactly, highlighting no trimming or fallback logic. This is critical if empty and null carry distinct meanings in the application. + + +Scenario 5: Preserves a whitespace-only name without trimming + +Details: + TestName: preservesWhitespaceOnlyName + Description: Ensure that a name consisting only of whitespace (e.g., " ") is returned exactly as set. + +Execution: + Arrange: Create Product and call setName(" "). + Act: Call getName. + Assert: Use assertEquals(" ", actualName). + +Validation: + Validates that whitespace-only strings are not automatically trimmed or altered, which can be significant for UI or formatting use cases. + + +Scenario 6: Preserves leading and trailing spaces in the name + +Details: + TestName: preservesLeadingAndTrailingSpacesInName + Description: Confirm that leading/trailing spaces (e.g., " Phone X ") remain unchanged when retrieved. + +Execution: + Arrange: Create Product and call setName(" Phone X "). + Act: Invoke getName. + Assert: Use assertEquals(" Phone X ", actualName). + +Validation: + Ensures no automatic whitespace stripping occurs in getName. This is important for scenarios where exact formatting matters. + + +Scenario 7: Handles a very long name string + +Details: + TestName: handlesVeryLongNameString + Description: Verify that getName correctly returns a very long string (e.g., thousands of characters) set via setName. + +Execution: + Arrange: Build a very long string (e.g., 10,000 characters) and call setName with it on a new Product. + Act: Call getName. + Assert: Use assertEquals(expectedLongString, actualName). + +Validation: + Confirms that the method has no artificial length constraints and does not truncate or alter large inputs, relevant for integrations importing verbose product titles. + + +Scenario 8: Returns non-Latin Unicode name correctly + +Details: + TestName: returnsUnicodeNonLatinName + Description: Ensure that names with non-Latin characters (e.g., "テレビ" or "مُنتَج") are preserved and returned exactly. + +Execution: + Arrange: Create a Product and call setName with a Unicode string using non-Latin characters. + Act: Invoke getName. + Assert: Use assertEquals(unicodeName, actualName). + +Validation: + Verifies proper handling of internationalized strings, critical for globalized applications. + + +Scenario 9: Returns emoji-containing name with surrogate pairs intact + +Details: + TestName: returnsEmojiNameWithSurrogatePairs + Description: Validate that getName returns names containing emojis (e.g., "Phone 📱 Pro") without corruption. + +Execution: + Arrange: Create Product and setName to a string containing emojis. + Act: Call getName. + Assert: Use assertEquals(expectedEmojiName, actualName). + +Validation: + Ensures that characters represented by surrogate pairs are stored and retrieved correctly, preventing data loss in modern text content. + + +Scenario 10: Returns name with combining characters accurately + +Details: + TestName: returnsNameWithCombiningCharacters + Description: Confirm that names using combining diacritics (e.g., "Café" with combining accent) are returned exactly as assigned. + +Execution: + Arrange: Create Product and call setName with a string that uses combining characters. + Act: Invoke getName. + Assert: Use assertEquals(expectedCombiningName, actualName). + +Validation: + Checks that the method does not normalize or alter Unicode combining sequences, preserving exact user-entered content. + + +Scenario 11: Returns name containing newline and tab characters + +Details: + TestName: returnsNameContainingNewlinesAndTabs + Description: Verify that control characters such as newline and tab (e.g., "Line1\nLine2\tTabbed") are returned unmodified. + +Execution: + Arrange: Create Product and call setName with a string containing newline and tab characters. + Act: Call getName. + Assert: Use assertEquals(expectedControlCharsName, actualName). + +Validation: + Confirms that getName does not sanitize or escape control characters, which may be necessary for multi-line display or data exports. + + +Scenario 12: Returns name containing the null character + +Details: + TestName: returnsNameContainingNullCharacter + Description: Ensure that a string containing the null character "\u0000" (e.g., "abc\u0000def") is returned exactly. + +Execution: + Arrange: Create Product and setName to a string with an embedded null character. + Act: Invoke getName. + Assert: Use assertEquals(expectedStringWithNull, actualName). + +Validation: + Demonstrates that the method does not strip or truncate at the null character, maintaining full data integrity. + + +Scenario 13: Returns the last assigned name after multiple updates + +Details: + TestName: returnsLastAssignedNameAfterMultipleUpdates + Description: Confirm that when setName is called multiple times with different values, getName returns the most recently assigned value. + +Execution: + Arrange: Create Product, call setName with "First", then "Second", then "Final". + Act: Invoke getName. + Assert: Use assertEquals("Final", actualName). + +Validation: + Verifies that the field reflects the last update, aligning with typical setter semantics and avoiding stale data issues. + + +Scenario 14: Name is unaffected by changes to other fields + +Details: + TestName: nameUnaffectedByOtherFieldChanges + Description: Ensure that modifications to id, description, and price do not alter the value returned by getName. + +Execution: + Arrange: Create Product, call setName("Gadget"), then modify setDescription, setPrice, and setId with arbitrary values. + Act: Call getName. + Assert: Use assertEquals("Gadget", actualName). + +Validation: + Confirms field independence: updates to other properties have no side-effects on name retrieval, supporting reliable encapsulation. + + +Scenario 15: Retrieving name multiple times returns the same value without side effects + +Details: + TestName: retrievingNameMultipleTimesReturnsSameValue + Description: Verify that repeated calls to getName return consistent results without mutating the state. + +Execution: + Arrange: Create Product and setName("Stable Name"). + Act: Invoke getName multiple times. + Assert: Use assertEquals("Stable Name", actualName) for each retrieval. + +Validation: + Ensures that getName is a pure accessor with no side effects, a key property for predictable getters. + + +Scenario 16: getName behavior is not influenced by persistence annotations + +Details: + TestName: nameIsUnchangedByJpaAnnotationsOrPersistenceContext + Description: Validate that JPA annotations on Product (e.g., @Entity, @Id) do not influence getName’s direct return value in a plain unit test context. + +Execution: + Arrange: Instantiate Product and setName("Annotated Product"). + Act: Call getName. + Assert: Use assertEquals("Annotated Product", actualName). + +Validation: + Confirms that annotations are metadata for persistence and do not affect core getter behavior, ensuring unit tests remain deterministic outside JPA. + + +Scenario 17: Returns exact case-sensitive name + +Details: + TestName: returnsExactCaseSensitiveName + Description: Ensure that case differences are preserved (e.g., "phone", "Phone", "PHONE" are distinct and returned exactly as set). + +Execution: + Arrange: Create Product and setName with a case-sensitive value such as "Phone". + Act: Invoke getName. + Assert: Use assertEquals("Phone", actualName). + +Validation: + Verifies that getName does not transform case, which is crucial if business logic requires case-sensitive identifiers or display names. + + +Scenario 18: External variable reassignment does not affect stored name + +Details: + TestName: externalVariableReassignmentDoesNotAffectStoredName + Description: Confirm that changing the local variable used to pass the value to setName after the call does not alter the stored name. + +Execution: + Arrange: Prepare a local string variable with value "Original", call setName with it, then reassign the local variable to "Changed". + Act: Invoke getName. + Assert: Use assertEquals("Original", actualName). + +Validation: + Demonstrates that Product stores the reference value present at the time of setName call and is unaffected by subsequent changes to external references, aligning with Java String immutability and expected setter behavior. + +*/ + +// ********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.extension.ExtendWith; +import static org.junit.jupiter.api.Assertions.*; +import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.jupiter.api.*; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@ExtendWith(MockitoExtension.class) +public class ProductGetNameTest { + + @Test + @Tag("valid") + @DisplayName("Scenario 1: Returns the assigned simple ASCII name") + public void returnsNameAfterSettingSimpleValue() { + Product product = new Product(); + String expected = "Laptop"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + @DisplayName("Scenario 2: Default name is null before any assignment") + public void defaultNameIsNullBeforeAnyAssignment() { + Product product = new Product(); + String actual = product.getName(); + assertNull((Object) actual); + } + + @Test + @Tag("boundary") + @DisplayName("Scenario 3: Returns null after explicitly setting name to null") + public void returnsNullAfterSettingNullName() { + Product product = new Product(); + product.setName(null); + String actual = product.getName(); + assertNull((Object) actual); + } + + @Test + @Tag("boundary") + @DisplayName("Scenario 4: Returns an empty string when name is set to empty") + public void returnsEmptyStringWhenSetToEmpty() { + Product product = new Product(); + String expected = ""; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + @DisplayName("Scenario 5: Preserves a whitespace-only name without trimming") + public void preservesWhitespaceOnlyName() { + Product product = new Product(); + String expected = " "; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + @DisplayName("Scenario 6: Preserves leading and trailing spaces in the name") + public void preservesLeadingAndTrailingSpacesInName() { + Product product = new Product(); + String expected = " Phone X "; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + @DisplayName("Scenario 7: Handles a very long name string") + public void handlesVeryLongNameString() { + Product product = new Product(); + StringBuilder sb = new StringBuilder(); + int length = 10_000; + for (int i = 0; i < length; i++) { + sb.append('A'); + } + String expected = sb.toString(); + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 8: Returns non-Latin Unicode name correctly") + public void returnsUnicodeNonLatinName() { + Product product = new Product(); + String expected = "テレビ"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 9: Returns emoji-containing name with surrogate pairs intact") + public void returnsEmojiNameWithSurrogatePairs() { + Product product = new Product(); + String expected = "Phone 📱 Pro"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 10: Returns name with combining characters accurately") + public void returnsNameWithCombiningCharacters() { + Product product = new Product(); + String expected = "Cafe\u0301"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 11: Returns name containing newline and tab characters") + public void returnsNameContainingNewlinesAndTabs() { + Product product = new Product(); + String expected = "Line1\nLine2\tTabbed"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("boundary") + @DisplayName("Scenario 12: Returns name containing the null character") + public void returnsNameContainingNullCharacter() { + Product product = new Product(); + String expected = "abc\u0000def"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 13: Returns the last assigned name after multiple updates") + public void returnsLastAssignedNameAfterMultipleUpdates() { + Product product = new Product(); + product.setName("First"); + product.setName("Second"); + String expected = "Final"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 14: Name is unaffected by changes to other fields") + public void nameUnaffectedByOtherFieldChanges() { + Product product = new Product(); + String expected = "Gadget"; + product.setName(expected); + product.setDescription("Description for gadget"); + product.setPrice(199.99d); + product.setId(Long.valueOf(42L)); // TODO: adjust id value if needed + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 15: Retrieving name multiple times returns the same value without side effects") + public void retrievingNameMultipleTimesReturnsSameValue() { + Product product = new Product(); + String expected = "Stable Name"; + product.setName(expected); + String actual1 = product.getName(); + String actual2 = product.getName(); + String actual3 = product.getName(); + assertEquals((String) expected, (String) actual1); + assertEquals((String) expected, (String) actual2); + assertEquals((String) expected, (String) actual3); + } + + @Test + @Tag("integration") + @DisplayName("Scenario 16: getName behavior is not influenced by persistence annotations") + public void nameIsUnchangedByJpaAnnotationsOrPersistenceContext() { + Product product = new Product(); + String expected = "Annotated Product"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 17: Returns exact case-sensitive name") + public void returnsExactCaseSensitiveName() { + Product product = new Product(); + String expected = "Phone"; + product.setName(expected); + String actual = product.getName(); + assertEquals((String) expected, (String) actual); + } + + @Test + @Tag("valid") + @DisplayName("Scenario 18: External variable reassignment does not affect stored name") + public void externalVariableReassignmentDoesNotAffectStoredName() { + Product product = new Product(); + String temp = "Original"; + product.setName(temp); + temp = "Changed"; + String actual = product.getName(); + String expected = "Original"; + assertEquals((String) expected, (String) actual); + } + +} \ 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..5cb7f908 --- /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 + +Scenario 1: Default price is zero for a newly created Product + +Details: + TestName: getPriceReturnsDefaultZeroForNewProduct + Description: Verify that a newly instantiated Product, without any explicit price assignment, returns a default price of 0.0 since the Java default for an uninitialized double field is 0.0. + +Execution: + Arrange: Create a new Product instance without setting any fields. + Act: Call getPrice on the Product. + Assert: Use JUnit assertEquals to check that the returned value equals 0.0 with a delta of 0.0 (e.g., assertEquals(0.0d, actual, 0.0d)). + +Validation: + Confirm that the getter reflects the default state of the double field. This ensures the object behaves predictably right after construction and before any setters are invoked. + +*/ + +// ********RoostGPT******** + +package com.bootexample4.products.model; + +import org.junit.jupiter.api.BeforeEach; +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 ProductGetPriceTest { + + private Product product; + + @BeforeEach + public void setUp() { + product = new Product(); + } + + @Test + @Tag("boundary") + public void testGetPriceReturnsDefaultZeroForNewProduct() { + double actual = (double) product.getPrice(); + Assertions.assertEquals((double) 0.0d, (double) actual, (double) 0.0d); + } + + @Test + @Tag("valid") + public void testGetPriceReturnsSetPositivePrice() { + double expected = (double) 25.5d; // TODO: adjust test value as required + product.setPrice((double) expected); + double actual = (double) product.getPrice(); + Assertions.assertEquals((double) expected, (double) actual, (double) 0.0d); + } + + @Test + @Tag("invalid") + public void testGetPriceReturnsSetNegativePrice() { + double expected = (double) -10.0d; // TODO: adjust test value if negative prices + // become disallowed + product.setPrice((double) expected); + double actual = (double) product.getPrice(); + Assertions.assertEquals((double) expected, (double) actual, (double) 0.0d); + } + + @Test + @Tag("boundary") + public void testGetPriceReturnsSetZeroPrice() { + double expected = (double) 0.0d; + product.setPrice((double) expected); + double actual = (double) product.getPrice(); + Assertions.assertEquals((double) expected, (double) actual, (double) 0.0d); + } + + @Test + @Tag("boundary") + public void testGetPriceReturnsMaxDoubleValue() { + double expected = (double) Double.MAX_VALUE; + product.setPrice((double) expected); + double actual = (double) product.getPrice(); + Assertions.assertEquals((double) expected, (double) actual, (double) 0.0d); + } + + @Test + @Tag("invalid") + public void testGetPriceReturnsNaNWhenSetNaN() { + double expected = (double) Double.NaN; + product.setPrice((double) expected); + double actual = (double) product.getPrice(); + Assertions.assertTrue(Double.isNaN((double) actual)); + } + + @Test + @Tag("boundary") + public void testGetPriceReturnsPositiveInfinity() { + double expected = (double) Double.POSITIVE_INFINITY; + product.setPrice((double) expected); + double actual = (double) product.getPrice(); + Assertions.assertEquals((double) expected, (double) actual, (double) 0.0d); + } + + @Test + @Tag("invalid") + public void testGetPriceReturnsNegativeInfinity() { + double expected = (double) Double.NEGATIVE_INFINITY; + product.setPrice((double) expected); + double actual = (double) product.getPrice(); + Assertions.assertEquals((double) expected, (double) actual, (double) 0.0d); + } + + @Test + @Tag("valid") + public void testGetPriceIndependenceBetweenInstances() { + Product firstProduct = new Product(); + Product secondProduct = new Product(); + double expectedFirst = (double) 15.0d; // TODO: adjust test value as required + firstProduct.setPrice((double) expectedFirst); + double actualFirst = (double) firstProduct.getPrice(); + double actualSecond = (double) secondProduct.getPrice(); + Assertions.assertEquals((double) expectedFirst, (double) actualFirst, (double) 0.0d); + Assertions.assertEquals((double) 0.0d, (double) actualSecond, (double) 0.0d); + } + + @Test + @Tag("valid") + public void testGetPriceAfterMultipleUpdatesReturnsLastSetValue() { + product.setPrice((double) 5.0d); + product.setPrice((double) 20.0d); + double expected = (double) 100.0d; // TODO: adjust test value as required + product.setPrice((double) expected); + double actual = (double) product.getPrice(); + Assertions.assertEquals((double) expected, (double) actual, (double) 0.0d); + } + +} \ No newline at end of file