diff --git a/api-gateway/src/main/resources/static/index.html b/api-gateway/src/main/resources/static/index.html index 34122ec4c2..046bfcb15d 100755 --- a/api-gateway/src/main/resources/static/index.html +++ b/api-gateway/src/main/resources/static/index.html @@ -150,6 +150,12 @@ + + + + + + diff --git a/api-gateway/src/main/resources/static/scripts/app.js b/api-gateway/src/main/resources/static/scripts/app.js index e98be79bda..19b24d4a77 100644 --- a/api-gateway/src/main/resources/static/scripts/app.js +++ b/api-gateway/src/main/resources/static/scripts/app.js @@ -10,9 +10,9 @@ const whiteList = new Set([ /* App Module */ const petClinicApp = angular.module('petClinicApp', [ 'ui.router', 'layoutNav', 'layoutFooter', 'layoutWelcome', 'ownerList', 'ownerDetails', 'ownerForm', 'ownerRegister', 'petRegister', 'petForm' - , 'visits', 'vetList','vetForm','vetDetails', 'visitList', 'billForm', 'billUpdateForm', 'loginForm', 'rolesDetails', 'signupForm', - 'billDetails', 'billsByOwnerId', 'billHistory','billsByVetId','inventoryList', 'inventoryForm', 'productForm','inventoryProductList', 'inventoryUpdateForm', 'productUpdateForm' - , 'verification' , 'adminPanel','resetPwdForm','forgotPwdForm','petTypeList']); + , 'visits', 'vetList','vetForm','vetDetails', 'visitList', 'billForm', 'billUpdateForm', 'loginForm', 'rolesDetails', 'signupForm', 'productDetailsInfo', + 'billDetails', 'billsByOwnerId', 'billHistory','billsByVetId','inventoryList', 'inventoryForm', 'productForm','inventoryProductList', 'inventoryUpdateForm', 'productUpdateForm', + 'verification' , 'adminPanel','resetPwdForm','forgotPwdForm','petTypeList']); diff --git a/api-gateway/src/main/resources/static/scripts/inventory-list/inventory-list.template.html b/api-gateway/src/main/resources/static/scripts/inventory-list/inventory-list.template.html index 8fbe712eb3..7434da746e 100644 --- a/api-gateway/src/main/resources/static/scripts/inventory-list/inventory-list.template.html +++ b/api-gateway/src/main/resources/static/scripts/inventory-list/inventory-list.template.html @@ -14,7 +14,7 @@

Inventory

Name Type Description - Update + diff --git a/api-gateway/src/main/resources/static/scripts/inventory-product-list/inventory-product-list.controller.js b/api-gateway/src/main/resources/static/scripts/inventory-product-list/inventory-product-list.controller.js index da64adf7ce..837df5dc3f 100644 --- a/api-gateway/src/main/resources/static/scripts/inventory-product-list/inventory-product-list.controller.js +++ b/api-gateway/src/main/resources/static/scripts/inventory-product-list/inventory-product-list.controller.js @@ -13,6 +13,8 @@ angular.module('inventoryProductList') // Handle if inventory is empty console.log("The inventory is empty!"); } + + }).catch(function (error) { if (error.status === 404) { console.clear() @@ -24,7 +26,7 @@ angular.module('inventoryProductList') }); - $scope.deleteProduct = function (product) { + $scope.deleteProduct = function (product) { let varIsConf = confirm('Are you sure you want to remove this product?'); if (varIsConf) { diff --git a/api-gateway/src/main/resources/static/scripts/inventory-product-list/inventory-product-list.template.html b/api-gateway/src/main/resources/static/scripts/inventory-product-list/inventory-product-list.template.html index 5c2bde164c..a7aa4e00f6 100644 --- a/api-gateway/src/main/resources/static/scripts/inventory-product-list/inventory-product-list.template.html +++ b/api-gateway/src/main/resources/static/scripts/inventory-product-list/inventory-product-list.template.html @@ -52,14 +52,27 @@

Inventory Products

{{product.productName}} - {{product.productId}} + {{product.productId}} {{product.productQuantity}} {{product.productPrice}} {{product.productDescription}} {{product.productSalePrice}} + + + + Get Product Details + + + + + - + + + + + Delete diff --git a/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.component.js b/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.component.js new file mode 100644 index 0000000000..462a4e59a6 --- /dev/null +++ b/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.component.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('productDetailsInfo') + .component('productDetailsInfo', { + templateUrl: 'scripts/product-details-info/product-details-info.template.html', + controller: 'ProductDetailsInfoController' + }); diff --git a/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.controller.js b/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.controller.js new file mode 100644 index 0000000000..a08c546281 --- /dev/null +++ b/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.controller.js @@ -0,0 +1,20 @@ +angular.module('productDetailsInfo') + .controller('ProductDetailsInfoController', ["$http", '$state', '$stateParams', '$scope', 'InventoryService', function ($http, $state, $stateParams, $scope, InventoryService) { + var self = this; + self.product = {}; // Initialize self.product + var inventoryId = InventoryService.getInventoryId(); + var productId = $stateParams.productId; + + $http.get('/api/gateway/inventory/' + inventoryId + '/products/' + productId) + .then(function (resp) { + // Handle the response data for the specific product + var product = resp.data; + console.log("Product found:", product); + self.product = product; // Update the product data in your controller + }) + .catch(function (error) { + // Handle errors if the product is not found or other issues + console.error("Error fetching product:", error); + }); + + }]); \ No newline at end of file diff --git a/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.js b/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.js new file mode 100644 index 0000000000..8610df5c6d --- /dev/null +++ b/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.js @@ -0,0 +1,17 @@ +'use strict'; + +angular.module('productDetailsInfo', ['ui.router']) + .config(['$stateProvider', function ($stateProvider) { + $stateProvider + .state('productDetails', { + parent: 'app', + url: '/inventory/:inventoryId/products/:productId', + template: '' + }) + + .state('products', { + parent: 'app', + url: '/inventory/:inventoryId/products', + template: '' + }) + }]); \ No newline at end of file diff --git a/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.template.html b/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.template.html new file mode 100644 index 0000000000..bae3b013ef --- /dev/null +++ b/api-gateway/src/main/resources/static/scripts/product-details-info/product-details-info.template.html @@ -0,0 +1,75 @@ + + + +

Product Details Info

+
+
+
+
+
+

Inventory Id:

+
+
+

{{$ctrl.product.inventoryId}}

+
+
+
+
+
+

Product Id:

+
+
+

{{$ctrl.product.productId}}

+
+
+
+
+
+
Product Description:
+
+
+
{{$ctrl.product.productDescription}}
+
+
+
+
+
+
Product Price:
+
+
+
{{$ctrl.product.productPrice}}
+
+ + + +
+
+
+
+
Product SalePrice:
+
+
+
{{$ctrl.product.productSalePrice}}
+
+ +
+ +
+
+
+
Product Quantity:
+
+
+
{{$ctrl.product.productQuantity}}
+
+
+
+
+
+
+ + Back to Products page + +
+
\ No newline at end of file diff --git a/api-gateway/src/main/resources/static/scripts/product-details/product-details.component.js b/api-gateway/src/main/resources/static/scripts/product-details/product-details.component.js new file mode 100644 index 0000000000..f9e7ae5e7a --- /dev/null +++ b/api-gateway/src/main/resources/static/scripts/product-details/product-details.component.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('productDetails') + .component('productDetails', { + templateUrl: 'scripts/product-details/product-details.template.html', + controller: 'ProductDetailsController' + }); diff --git a/api-gateway/src/main/resources/static/scripts/product-details/product-details.controller.js b/api-gateway/src/main/resources/static/scripts/product-details/product-details.controller.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api-gateway/src/main/resources/static/scripts/product-details/product-details.js b/api-gateway/src/main/resources/static/scripts/product-details/product-details.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api-gateway/src/main/resources/static/scripts/product-details/product-details.template.html b/api-gateway/src/main/resources/static/scripts/product-details/product-details.template.html new file mode 100644 index 0000000000..e1d6db055f --- /dev/null +++ b/api-gateway/src/main/resources/static/scripts/product-details/product-details.template.html @@ -0,0 +1,80 @@ + + + +

Product Details

+
+
+
+
+
+

Inventory Id:

+
+
+

{{$ctrl.product.inventoryId}}

+
+
+
+
+
+

Product Id:

+
+
+

{{$ctrl.product.productId}}

+
+
+
+
+
+
Product Description:
+
+
+
{{$ctrl.product.productDescription}}
+
+
+
+
+
+
Product Price:
+
+
+
{{$ctrl.product.productPrice}}
+
+ + + +
+
+
+
+
Product SalePrice:
+
+
+
{{$ctrl.product.productSalePrice}}
+
+ +
+ +
+
+
+
Product Quantity:
+
+
+
{{$ctrl.product.productQuantity}}
+
+
+
+
+
+
+ + Back to Products page + +
+
+ + + + + diff --git a/api-gateway/src/test/java/com/petclinic/bffapigateway/presentationlayer/ApiGatewayControllerTest.java b/api-gateway/src/test/java/com/petclinic/bffapigateway/presentationlayer/ApiGatewayControllerTest.java index 4613249181..9596947798 100755 --- a/api-gateway/src/test/java/com/petclinic/bffapigateway/presentationlayer/ApiGatewayControllerTest.java +++ b/api-gateway/src/test/java/com/petclinic/bffapigateway/presentationlayer/ApiGatewayControllerTest.java @@ -2769,8 +2769,36 @@ void updateInventory_withValidValue_shouldSucceed() { @Test - void getInventoryByInventoryId_ValidId_shouldSucceed() { + void GetProductByInventoryIdAndProductId_InsideInventory() { + ProductResponseDTO productResponseDTO = buildProductDTO(); + when(inventoryServiceClient.getProductByProductIdInInventory(productResponseDTO.getInventoryId(), productResponseDTO.getProductId())) + .thenReturn(Mono.just(productResponseDTO)); + client.get() + .uri("/api/gateway/inventory/{inventoryId}/products/{productId}", productResponseDTO.getInventoryId(), productResponseDTO.getProductId()) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectBody(ProductResponseDTO.class) + .value(dto -> { + assertNotNull(dto); + assertEquals(productResponseDTO.getInventoryId(), dto.getInventoryId()); + assertEquals(productResponseDTO.getProductId(), dto.getProductId()); + assertEquals(productResponseDTO.getProductName(), dto.getProductName()); + assertEquals(productResponseDTO.getProductDescription(), dto.getProductDescription()); + assertEquals(productResponseDTO.getProductPrice(), dto.getProductPrice()); + assertEquals(productResponseDTO.getProductQuantity(), dto.getProductQuantity()); + assertEquals(productResponseDTO.getProductSalePrice(), dto.getProductSalePrice()); + + }); + + verify(inventoryServiceClient, times(1)) + .getProductByProductIdInInventory(productResponseDTO.getInventoryId(), productResponseDTO.getProductId()); + } + + + @Test + void getInventoryByInventoryId_ValidId_shouldSucceed() { String validInventoryId = "inventoryId_1"; InventoryResponseDTO inventoryResponseDTO = InventoryResponseDTO.builder() .inventoryId(validInventoryId) @@ -2987,6 +3015,9 @@ void testAddProductToInventory_InvalidInventoryId_ShouldReturnNotFoundException( .addProductToInventory(eq(requestDTO), eq("invalidInventoryId")); } + + + private ProductResponseDTO buildProductDTO(){ return ProductResponseDTO.builder() .id("1") diff --git a/inventory-service/src/main/java/com/petclinic/inventoryservice/businesslayer/ProductInventoryServiceImpl.java b/inventory-service/src/main/java/com/petclinic/inventoryservice/businesslayer/ProductInventoryServiceImpl.java index 3ea908b267..4acfdf8675 100644 --- a/inventory-service/src/main/java/com/petclinic/inventoryservice/businesslayer/ProductInventoryServiceImpl.java +++ b/inventory-service/src/main/java/com/petclinic/inventoryservice/businesslayer/ProductInventoryServiceImpl.java @@ -313,21 +313,10 @@ public Mono deleteInventoryByInventoryId(String inventoryId) { @Override public Mono getProductByProductIdInInventory(String inventoryId, String productId) { - if(inventoryId == null ){ - return Mono.error(new NotFoundException("Inventory id not found:" + inventoryId )); - - } - else if (productId == null) { - return Mono.error(new NotFoundException("product id not found:" + productId )); - } - - return productRepository .findProductByInventoryIdAndProductId(inventoryId, productId) .map(EntityDTOUtil::toProductResponseDTO) .switchIfEmpty(Mono.error(new NotFoundException("Inventory id:" + inventoryId + "and product:" + productId + "are not found"))); - - } //delete all products and delete all inventory diff --git a/inventory-service/src/test/java/com/petclinic/inventoryservice/businesslayer/ProductInventoryServiceUnitTest.java b/inventory-service/src/test/java/com/petclinic/inventoryservice/businesslayer/ProductInventoryServiceUnitTest.java index 2ff91d77b4..8f7a13bcb8 100644 --- a/inventory-service/src/test/java/com/petclinic/inventoryservice/businesslayer/ProductInventoryServiceUnitTest.java +++ b/inventory-service/src/test/java/com/petclinic/inventoryservice/businesslayer/ProductInventoryServiceUnitTest.java @@ -226,6 +226,44 @@ public void deleteProduct_validProductAndInventory_ShouldSucceed() { + @Test + void getProductsByInventoryId_andProductId_withValidFields_shouldSucceed() { + String inventoryId = "1"; + String productId = "12345"; + + when(productRepository + .findProductByInventoryIdAndProductId(inventoryId, productId)) + .thenReturn(Mono.just(product)); + + Mono productResponseDTOMono = productInventoryService + .getProductByProductIdInInventory(inventoryId, productId); + + StepVerifier + .create(productResponseDTOMono) + .expectNextCount(1) + .verifyComplete(); + } + + + @Test + void getProductsByInventoryId_andProductId_withInvalidFields_shouldReturnEmptyFlux() { + String invalidInventoryId = "999"; // Invalid inventory ID + String invalidProductId = "invalid123"; // Invalid product ID + + when(productRepository + .findProductByInventoryIdAndProductId(invalidInventoryId, invalidProductId)) + .thenReturn(Mono.empty()); + + Mono productResponseDTOMono = productInventoryService + .getProductByProductIdInInventory(invalidInventoryId, invalidProductId); + + + StepVerifier.create(productResponseDTOMono) + .expectError(NotFoundException.class) + .verify(); + } + + @Test void getInventoryByInventoryId_ValidId_shouldSucceed(){ String inventoryId ="1"; @@ -255,6 +293,7 @@ void getInventoryByInventoryId_ValidId_shouldSucceed(){ + @Test void GetInventoryByInvalid_InventoryId_throwNotFound() { diff --git a/inventory-service/src/test/java/com/petclinic/inventoryservice/datalayer/Product/ProductRepositoryTest.java b/inventory-service/src/test/java/com/petclinic/inventoryservice/datalayer/Product/ProductRepositoryTest.java index d97e108d1d..7db3e80114 100644 --- a/inventory-service/src/test/java/com/petclinic/inventoryservice/datalayer/Product/ProductRepositoryTest.java +++ b/inventory-service/src/test/java/com/petclinic/inventoryservice/datalayer/Product/ProductRepositoryTest.java @@ -106,6 +106,21 @@ public void shouldGetTwoProductsByInventoryIdAndProductName(){ .expectNextCount(2) .verifyComplete(); } + + + @Test + public void shouldGetSingleProductByProductIdAndInventoryId(){ + StepVerifier + .create(productRepository.findProductByInventoryIdAndProductId( + product1.getInventoryId(), + product1.getProductId() + )) + .expectNextCount(1) + .verifyComplete(); + } + + + @Test public void ShouldDeleteAllProducts() { // Arrange diff --git a/inventory-service/src/test/java/com/petclinic/inventoryservice/presentationlayer/InventoryControllerIntegrationTest.java b/inventory-service/src/test/java/com/petclinic/inventoryservice/presentationlayer/InventoryControllerIntegrationTest.java index 4df45317b0..ed53e71422 100644 --- a/inventory-service/src/test/java/com/petclinic/inventoryservice/presentationlayer/InventoryControllerIntegrationTest.java +++ b/inventory-service/src/test/java/com/petclinic/inventoryservice/presentationlayer/InventoryControllerIntegrationTest.java @@ -52,10 +52,13 @@ class InventoryControllerIntegrationTest { .type("Sales") .build(); + + Inventory inventory1 = buildInventory("inventoryId_3", "internal", inventoryType1.getType(),"inventoryDescription_3"); Inventory inventory2 = buildInventory("inventoryId_4", "sales", inventoryType2.getType() ,"inventoryDescription_4"); + Product product1 = buildProduct("productId1", "inventoryId_3" , "drug" , "drug", 18.00, 30.00, 3); @BeforeEach public void dbSetup() { @@ -461,6 +464,43 @@ public void updateInventory_withValidId_ShouldSucceed() { }); } + + + @Test + void getProductInInventory_withInvalidInventoryId_invalidProductId_throwsNotFoundException() { + String invalidInventoryId = "123"; + String invalidProductId = "897"; + + webTestClient.get() + .uri("/inventory/{inventoryId}/products/{productId}", + invalidInventoryId, invalidProductId) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isNotFound() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() + .jsonPath("$.message").isEqualTo("Inventory id:" + invalidInventoryId + "and product:" + invalidProductId + "are not found"); + } + + + + @Test + public void getProductInInventory_byProductId_ShouldSucceed() { + + + webTestClient.get() + .uri("/inventory/{inventoryId}/products/{productId}", "1", "123F567C9") + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isOk() + .expectHeader().contentType(MediaType.APPLICATION_JSON) + .expectBody() + .jsonPath("$.productId").isEqualTo("123F567C9"); + } + + + + @Test public void updateInventory_withInvalidInventoryId() { String InvalidInventoryId = "inventoryId_234"; @@ -538,13 +578,14 @@ private Inventory buildInventory(String inventoryId, String name, String invento .build(); } - private Product buildProduct(String productId, String inventoryId, String productName, String productDescription, Double productPrice, Integer productQuantity) { + private Product buildProduct(String productId, String inventoryId, String productName, String productDescription, Double productPrice, Double SalePrice, Integer productQuantity) { return Product.builder() .productId(productId) .inventoryId(inventoryId) .productName(productName) .productDescription(productDescription) .productPrice(productPrice) + .productSalePrice(SalePrice) .productQuantity(productQuantity) .build(); } diff --git a/inventory-service/src/test/java/com/petclinic/inventoryservice/presentationlayer/InventoryControllerUnitTest.java b/inventory-service/src/test/java/com/petclinic/inventoryservice/presentationlayer/InventoryControllerUnitTest.java index d0153f1051..787d312547 100644 --- a/inventory-service/src/test/java/com/petclinic/inventoryservice/presentationlayer/InventoryControllerUnitTest.java +++ b/inventory-service/src/test/java/com/petclinic/inventoryservice/presentationlayer/InventoryControllerUnitTest.java @@ -14,6 +14,7 @@ import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; import java.util.Arrays; import java.util.List; @@ -303,6 +304,10 @@ void getProductsInInventory_withValidId_andValidProductPrice_shouldSucceed() { }); } + + + + @Test void getInventoryByInventoryId_ValidIdShouldSucceed(){ //arrange @@ -339,6 +344,74 @@ void getInventoryByInventoryId_ValidIdShouldSucceed(){ + + + + @Test + void getProductsInInventoryByInventoryIdAndProductId_ValidRequest_ShouldSucceed() { + // Arrange + String inventoryId = "1"; + String productId = "123F567C9"; + ProductResponseDTO productResponseDTO = ProductResponseDTO.builder() + .id("1") + .inventoryId(inventoryId) + .productId(productId) + .productName("Benzodiazepines") + .productDescription("Sedative Medication") + .productPrice(100.00) + .productQuantity(10) + .productSalePrice(15.99) + .build(); + + when(productInventoryService.getProductByProductIdInInventory(eq(inventoryId), eq(productId))) + .thenReturn(Mono.just(productResponseDTO)); + + // Act + Mono productResponseDTOMono = productInventoryService.getProductByProductIdInInventory(inventoryId, productId); + + // Assert + StepVerifier + .create(productResponseDTOMono) + .expectNextMatches(response -> { + assertNotNull(response); + assertEquals(productResponseDTO.getId(), response.getId()); + assertEquals(productResponseDTO.getInventoryId(), response.getInventoryId()); + assertEquals(productResponseDTO.getProductId(), response.getProductId()); + assertEquals(productResponseDTO.getProductName(), response.getProductName()); + assertEquals(productResponseDTO.getProductDescription(), response.getProductDescription()); + assertEquals(productResponseDTO.getProductPrice(), response.getProductPrice()); + assertEquals(productResponseDTO.getProductQuantity(), response.getProductQuantity()); + assertEquals(productResponseDTO.getProductSalePrice(), response.getProductSalePrice()); + return true; + }) + .verifyComplete(); + + verify(productInventoryService, times(1)) + .getProductByProductIdInInventory(eq(inventoryId), eq(productId)); + } + + + @Test + void getProductsInInventoryByInventoryIdAndProductId_InvalidRequest_ShouldFail() { + // Arrange + String invalidInventoryId = "invalidInventoryId"; + String invalidProductId = "invalidProductId"; + + when(productInventoryService.getProductByProductIdInInventory(eq(invalidInventoryId), eq(invalidProductId))) + .thenReturn(Mono.empty()); + + // Act + Mono productResponseDTOMono = productInventoryService.getProductByProductIdInInventory(invalidInventoryId, invalidProductId); + + // Assert + StepVerifier + .create(productResponseDTOMono) + .expectComplete() + .verify(); + + verify(productInventoryService, times(1)) + .getProductByProductIdInInventory(eq(invalidInventoryId), eq(invalidProductId)); + } @Test void getInventoryByInvalidInventoryId_ReturnNotFound() {