From a0e498850456bf4751e9b005cf7f04e539142d49 Mon Sep 17 00:00:00 2001 From: Thuy Nguyen <145430420+nntthuy-axonivy@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:20:23 +0700 Subject: [PATCH] MARP-1094 Refactor product table (#227) --- .../marketplace_script_SNAPSHOT.sql | 23 +++ .../market/constants/EntityConstants.java | 1 + .../market/constants/MongoDBConstants.java | 3 + .../constants/RequestMappingConstants.java | 3 +- .../market/controller/ProductController.java | 18 -- .../controller/ProductDetailsController.java | 13 -- .../ProductMarketplaceDataController.java | 65 +++++++ .../com/axonivy/market/entity/Product.java | 9 + .../market/entity/ProductMarketplaceData.java | 30 +++ .../com/axonivy/market/enums/SortOption.java | 4 +- .../market/factory/ProductFactory.java | 3 - ...ustomProductMarketplaceDataRepository.java | 12 ++ .../repository/CustomProductRepository.java | 8 - .../ProductMarketplaceDataRepository.java | 9 + ...mProductMarketplaceDataRepositoryImpl.java | 60 ++++++ .../impl/CustomProductRepositoryImpl.java | 56 +----- .../ProductMarketplaceDataService.java | 14 ++ .../market/service/ProductService.java | 6 - .../ProductMarketplaceDataServiceImpl.java | 140 ++++++++++++++ .../service/impl/ProductServiceImpl.java | 145 +++------------ .../java/com/axonivy/market/BaseSetup.java | 7 + .../controller/ProductControllerTest.java | 27 +-- .../ProductDetailsControllerTest.java | 9 - .../ProductMarketplaceDataControllerTest.java | 59 ++++++ .../market/factory/ProductFactoryTest.java | 12 +- ...ductMarketplaceDataRepositoryImplTest.java | 84 +++++++++ .../impl/CustomProductRepositoryImplTest.java | 52 ------ .../impl/ProductSearchRepositoryImplTest.java | 21 ++- ...ProductMarketplaceDataServiceImplTest.java | 171 ++++++++++++++++++ .../service/impl/ProductServiceImplTest.java | 151 ++-------------- .../modules/product/product.service.spec.ts | 2 +- .../app/modules/product/product.service.ts | 2 +- .../src/app/shared/constants/api.constant.ts | 4 +- 33 files changed, 766 insertions(+), 457 deletions(-) create mode 100644 marketplace-migration/marketplace_script_SNAPSHOT.sql create mode 100644 marketplace-service/src/main/java/com/axonivy/market/controller/ProductMarketplaceDataController.java create mode 100644 marketplace-service/src/main/java/com/axonivy/market/entity/ProductMarketplaceData.java create mode 100644 marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductMarketplaceDataRepository.java create mode 100644 marketplace-service/src/main/java/com/axonivy/market/repository/ProductMarketplaceDataRepository.java create mode 100644 marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductMarketplaceDataRepositoryImpl.java create mode 100644 marketplace-service/src/main/java/com/axonivy/market/service/ProductMarketplaceDataService.java create mode 100644 marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductMarketplaceDataServiceImpl.java create mode 100644 marketplace-service/src/test/java/com/axonivy/market/controller/ProductMarketplaceDataControllerTest.java create mode 100644 marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductMarketplaceDataRepositoryImplTest.java create mode 100644 marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductMarketplaceDataServiceImplTest.java diff --git a/marketplace-migration/marketplace_script_SNAPSHOT.sql b/marketplace-migration/marketplace_script_SNAPSHOT.sql new file mode 100644 index 000000000..6c319e559 --- /dev/null +++ b/marketplace-migration/marketplace_script_SNAPSHOT.sql @@ -0,0 +1,23 @@ +function moveProductFieldsAndCleanUp() { + const products = db.getCollection("Product").find({}, { + _id: 1, + installationCount: 1, + synchronizedInstallationCount: 1, + customOrder: 1 + }).toArray(); + + if (products.length > 0) { + db.getCollection("ProductMarketplaceData").insertMany(products); + print("Fields successfully moved to ProductMarketplaceData."); + } else { + print("No fields to move."); + } + + const result = db.getCollection("Product").updateMany( + {}, + { $unset: { installationCount: "", synchronizedInstallationCount: "", customOrder: "" } } + ); + print(`Fields removed from ${result.modifiedCount} documents in the Product collection.`); +} + +moveProductFieldsAndCleanUp(); \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java index 264971cde..1e518bdfe 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/EntityConstants.java @@ -18,4 +18,5 @@ public class EntityConstants { public static final String IMAGE = "Image"; public static final String MAVEN_ARTIFACT_VERSION = "MavenArtifactVersion"; public static final String EXTERNAL_DOCUMENT_META = "ExternalDocumentMeta"; + public static final String PRODUCT_MARKETPLACE_DATA = "ProductMarketplaceData"; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java index 69b726c86..a36717a5b 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java @@ -7,6 +7,9 @@ public class MongoDBConstants { public static final String ID = "_id"; public static final String PRODUCT_COLLECTION = "Product"; + public static final String PRODUCT_MARKETPLACE_COLLECTION = "ProductMarketplaceData"; + public static final String MARKETPLACE_DATA = "marketplaceData"; + public static final String MARKETPLACE_DATA_CUSTOM_ORDER = "marketplaceData.customOrder"; public static final String INSTALLATION_COUNT = "InstallationCount"; public static final String SYNCHRONIZED_INSTALLATION_COUNT = "SynchronizedInstallationCount"; public static final String PRODUCT_ID = "productId"; diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java index 61aa1627d..66b14b6d4 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -24,11 +24,12 @@ public class RequestMappingConstants { public static final String VERSIONS_BY_ID = "/{id}/versions"; public static final String PRODUCT_BY_ID = "/product/{id}"; public static final String PRODUCT_RATING_BY_ID = "/product/{id}/rating"; - public static final String INSTALLATION_COUNT_BY_ID = "/installationcount/{id}"; + public static final String INSTALLATION_COUNT_BY_ID = "/installation-count/{id}"; public static final String PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION = "/{id}/{version}/json"; public static final String VERSIONS_IN_DESIGNER = "/{id}/designerversions"; public static final String DESIGNER_INSTALLATION_BY_ID = "/installation/{id}/designer"; public static final String CUSTOM_SORT = "custom-sort"; public static final String LATEST_ARTIFACT_DOWNLOAD_URL_BY_ID = "/{id}/artifact"; public static final String EXTERNAL_DOCUMENT = API + "/externaldocument"; + public static final String PRODUCT_MARKETPLACE_DATA = API + "/product-marketplace-data"; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java index e6d730477..666488327 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java @@ -7,7 +7,6 @@ import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.model.Message; -import com.axonivy.market.model.ProductCustomSortRequest; import com.axonivy.market.model.ProductModel; import com.axonivy.market.service.MetadataService; import com.axonivy.market.service.ProductService; @@ -17,7 +16,6 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -33,9 +31,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -177,20 +173,6 @@ public ResponseEntity syncOneProduct( return new ResponseEntity<>(message, HttpStatus.OK); } - @PostMapping(CUSTOM_SORT) - @Operation(hidden = true) - public ResponseEntity createCustomSortProducts( - @RequestHeader(value = AUTHORIZATION) String authorizationHeader, - @RequestBody @Valid ProductCustomSortRequest productCustomSortRequest) { - String token = AuthorizationUtils.getBearerToken(authorizationHeader); - gitHubService.validateUserInOrganizationAndTeam(token, GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME, - GitHubConstants.AXONIVY_MARKET_TEAM_NAME); - productService.addCustomSortProduct(productCustomSortRequest); - var message = new Message(ErrorCode.SUCCESSFUL.getCode(), ErrorCode.SUCCESSFUL.getHelpText(), - "Custom product sort order added successfully"); - return new ResponseEntity<>(message, HttpStatus.OK); - } - @SuppressWarnings("unchecked") private ResponseEntity> generateEmptyPagedModel() { var emptyPagedModel = (PagedModel) pagedResourcesAssembler.toEmptyModel(Page.empty(), diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java index 9167c0930..6d915d5da 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductDetailsController.java @@ -16,7 +16,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -73,18 +72,6 @@ public ResponseEntity findBestMatchProductDetailsByVersion( HttpStatus.OK); } - @PutMapping(INSTALLATION_COUNT_BY_ID) - @Operation(summary = "Update installation count of product", - description = "By default, increase installation count when click download product files by users") - public ResponseEntity syncInstallationCount( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", - in = ParameterIn.PATH) String productId, - @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, - example = "v10.0.20") String designerVersion) { - int result = productService.updateInstallationCountForProduct(productId, designerVersion); - return new ResponseEntity<>(result, HttpStatus.OK); - } - @GetMapping(BY_ID) @Operation(summary = "get product detail by ID", description = "Return product detail by product id (from meta.json)") public ResponseEntity findProductDetails( diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductMarketplaceDataController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductMarketplaceDataController.java new file mode 100644 index 000000000..728251b33 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductMarketplaceDataController.java @@ -0,0 +1,65 @@ +package com.axonivy.market.controller; + +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.model.Message; +import com.axonivy.market.model.ProductCustomSortRequest; +import com.axonivy.market.service.ProductMarketplaceDataService; +import com.axonivy.market.util.AuthorizationUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static com.axonivy.market.constants.RequestMappingConstants.*; +import static com.axonivy.market.constants.RequestParamConstants.DESIGNER_VERSION; +import static com.axonivy.market.constants.RequestParamConstants.ID; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; + +@RestController +@RequestMapping(PRODUCT_MARKETPLACE_DATA) +@Tag(name = "Product Marketplace Data Controller", description = "API collection to get product marketplace data") +@AllArgsConstructor +public class ProductMarketplaceDataController { + private final GitHubService gitHubService; + private final ProductMarketplaceDataService productMarketplaceDataService; + + @PostMapping(CUSTOM_SORT) + @Operation(hidden = true) + public ResponseEntity createCustomSortProducts( + @RequestHeader(value = AUTHORIZATION) String authorizationHeader, + @RequestBody @Valid ProductCustomSortRequest productCustomSortRequest) { + String token = AuthorizationUtils.getBearerToken(authorizationHeader); + gitHubService.validateUserInOrganizationAndTeam(token, GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME, + GitHubConstants.AXONIVY_MARKET_TEAM_NAME); + productMarketplaceDataService.addCustomSortProduct(productCustomSortRequest); + var message = new Message(ErrorCode.SUCCESSFUL.getCode(), ErrorCode.SUCCESSFUL.getHelpText(), + "Custom product sort order added successfully"); + return new ResponseEntity<>(message, HttpStatus.OK); + } + + @PutMapping(INSTALLATION_COUNT_BY_ID) + @Operation(summary = "Update installation count of product", + description = "By default, increase installation count when click download product files by users") + public ResponseEntity syncInstallationCount( + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", + in = ParameterIn.PATH) String productId, + @RequestParam(name = DESIGNER_VERSION, required = false) @Parameter(in = ParameterIn.QUERY, + example = "v10.0.20") String designerVersion) { + int result = productMarketplaceDataService.updateInstallationCountForProduct(productId, designerVersion); + return new ResponseEntity<>(result, HttpStatus.OK); + } +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java b/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java index b62f89397..9fe2edbb1 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java @@ -61,13 +61,22 @@ public class Product implements Serializable { private String compatibility; private Boolean validate; private Boolean contactUs; + @Transient private int installationCount; private Date newestPublishedDate; private String newestReleaseVersion; @Transient private ProductModuleContent productModuleContent; private List artifacts; + /** + * @deprecated + */ + @Deprecated(forRemoval = true, since = "1.6.0") private Boolean synchronizedInstallationCount; + /** + * @deprecated + */ + @Deprecated(forRemoval = true, since = "1.6.0") private Integer customOrder; private List releasedVersions; @Transient diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductMarketplaceData.java b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductMarketplaceData.java new file mode 100644 index 000000000..a791a6e6d --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductMarketplaceData.java @@ -0,0 +1,30 @@ +package com.axonivy.market.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.io.Serial; +import java.io.Serializable; + +import static com.axonivy.market.constants.EntityConstants.PRODUCT_MARKETPLACE_DATA; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Document(PRODUCT_MARKETPLACE_DATA) +public class ProductMarketplaceData implements Serializable { + @Serial + private static final long serialVersionUID = -8770801879877277456L; + @Id + private String id; + private int installationCount; + private Boolean synchronizedInstallationCount; + private Integer customOrder; +} \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java b/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java index 1fac93267..104e18b0a 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java +++ b/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java @@ -9,10 +9,10 @@ @Getter @AllArgsConstructor public enum SortOption { - POPULARITY("popularity", "installationCount", Sort.Direction.DESC), + POPULARITY("popularity", "marketplaceData.installationCount", Sort.Direction.DESC), ALPHABETICALLY("alphabetically", "names", Sort.Direction.ASC), RECENT("recent", "newestPublishedDate", Sort.Direction.DESC), - STANDARD("standard", "customOrder", Sort.Direction.DESC), + STANDARD("standard", "marketplaceData.customOrder", Sort.Direction.DESC), ID("id", "_id", Sort.Direction.ASC); private final String option; diff --git a/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java b/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java index b234adc86..9ed2374d3 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java +++ b/marketplace-service/src/main/java/com/axonivy/market/factory/ProductFactory.java @@ -85,9 +85,6 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent public static void transferComputedPersistedDataToProduct(Product persisted, Product product) { product.setMarketDirectory(persisted.getMarketDirectory()); - product.setCustomOrder(persisted.getCustomOrder()); - product.setInstallationCount(persisted.getInstallationCount()); - product.setSynchronizedInstallationCount(persisted.getSynchronizedInstallationCount()); } private static Map mappingMultilingualismValueByMetaJSONFile(List list) { diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductMarketplaceDataRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductMarketplaceDataRepository.java new file mode 100644 index 000000000..2bcd28403 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductMarketplaceDataRepository.java @@ -0,0 +1,12 @@ +package com.axonivy.market.repository; + +public interface CustomProductMarketplaceDataRepository { + + int updateInitialCount(String productId, int initialCount); + + int increaseInstallationCount(String productId); + + void increaseInstallationCountForProductByDesignerVersion(String productId, String designerVersion); + + void checkAndInitProductMarketplaceDataIfNotExist(String productId); +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java index d00ff2af1..9b9dda311 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java @@ -10,18 +10,10 @@ public interface CustomProductRepository { Product getProductByIdAndVersion(String id, String version); - Product getProductWithModuleContent(String id); - Product findProductById(String id); List getReleasedVersionsById(String id); - int updateInitialCount(String productId, int initialCount); - - int increaseInstallationCount(String productId); - - void increaseInstallationCountForProductByDesignerVersion(String productId, String designerVersion); - List getAllProductsWithIdAndReleaseTagAndArtifact(); Page searchByCriteria(ProductSearchCriteria criteria, Pageable pageable); diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductMarketplaceDataRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductMarketplaceDataRepository.java new file mode 100644 index 000000000..f3ab86203 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductMarketplaceDataRepository.java @@ -0,0 +1,9 @@ +package com.axonivy.market.repository; + +import com.axonivy.market.entity.ProductMarketplaceData; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductMarketplaceDataRepository extends MongoRepository, CustomProductMarketplaceDataRepository { +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductMarketplaceDataRepositoryImpl.java b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductMarketplaceDataRepositoryImpl.java new file mode 100644 index 000000000..ce22b05a2 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductMarketplaceDataRepositoryImpl.java @@ -0,0 +1,60 @@ +package com.axonivy.market.repository.impl; + +import com.axonivy.market.constants.MongoDBConstants; +import com.axonivy.market.entity.ProductDesignerInstallation; +import com.axonivy.market.entity.ProductMarketplaceData; +import com.axonivy.market.repository.CustomProductMarketplaceDataRepository; +import com.axonivy.market.repository.CustomRepository; +import lombok.AllArgsConstructor; +import lombok.Builder; +import org.springframework.data.mongodb.core.FindAndModifyOptions; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +@Builder +@AllArgsConstructor +public class CustomProductMarketplaceDataRepositoryImpl extends CustomRepository implements CustomProductMarketplaceDataRepository { + + final MongoTemplate mongoTemplate; + + @Override + public int updateInitialCount(String productId, int initialCount) { + Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, initialCount).set( + MongoDBConstants.SYNCHRONIZED_INSTALLATION_COUNT, true); + ProductMarketplaceData updatedProductMarketplaceData = mongoTemplate.findAndModify(createQueryById(productId), + update, FindAndModifyOptions.options().returnNew(true), ProductMarketplaceData.class); + return updatedProductMarketplaceData != null ? updatedProductMarketplaceData.getInstallationCount() : 0; + } + + @Override + public int increaseInstallationCount(String productId) { + Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, 1); + ProductMarketplaceData updatedProduct = mongoTemplate.findAndModify(createQueryById(productId), update, + FindAndModifyOptions.options().returnNew(true), ProductMarketplaceData.class); + return updatedProduct != null ? updatedProduct.getInstallationCount() : 0; + } + + @Override + public void increaseInstallationCountForProductByDesignerVersion(String productId, String designerVersion) { + Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, 1); + mongoTemplate.upsert(createQueryByProductIdAndDesignerVersion(productId, designerVersion), + update, ProductDesignerInstallation.class); + } + + @Override + public void checkAndInitProductMarketplaceDataIfNotExist(String productId) { + Query query = new Query(Criteria.where(MongoDBConstants.ID).is(productId)); + if (!mongoTemplate.exists(query, ProductMarketplaceData.class)) { + ProductMarketplaceData productMarketplaceData = new ProductMarketplaceData(); + productMarketplaceData.setId(productId); + mongoTemplate.insert(productMarketplaceData); + } + } + + private Query createQueryByProductIdAndDesignerVersion(String productId, String designerVersion) { + return new Query(Criteria.where(MongoDBConstants.PRODUCT_ID).is(productId) + .andOperator(Criteria.where(MongoDBConstants.DESIGNER_VERSION).is(designerVersion))); + } +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java index aaeb2ba5d..40e823701 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java @@ -4,14 +4,12 @@ import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.criteria.ProductSearchCriteria; import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductDesignerInstallation; import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.enums.DocumentField; import com.axonivy.market.enums.Language; import com.axonivy.market.enums.TypeOption; import com.axonivy.market.repository.CustomProductRepository; import com.axonivy.market.repository.CustomRepository; -import com.axonivy.market.repository.ProductJsonContentRepository; import com.axonivy.market.repository.ProductModuleContentRepository; import lombok.AllArgsConstructor; import lombok.Builder; @@ -20,13 +18,11 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; -import org.springframework.data.mongodb.core.FindAndModifyOptions; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; import org.springframework.util.CollectionUtils; import java.util.ArrayList; @@ -46,7 +42,6 @@ public class CustomProductRepositoryImpl extends CustomRepository implements Cus final MongoTemplate mongoTemplate; final ProductModuleContentRepository contentRepository; - final ProductJsonContentRepository jsonContentRepository; public Product queryProductByAggregation(Aggregation aggregation) { return Optional.of(mongoTemplate.aggregate(aggregation, EntityConstants.PRODUCT, Product.class)) @@ -74,17 +69,6 @@ public Product findProductById(String id) { return queryProductByAggregation(aggregation); } - @Override - public Product getProductWithModuleContent(String id) { - Product result = findProductById(id); - if (!Objects.isNull(result)) { - ProductModuleContent content = contentRepository.findByVersionAndProductId( - result.getNewestReleaseVersion(), id); - result.setProductModuleContent(content); - } - return result; - } - @Override public List getReleasedVersionsById(String id) { Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id)); @@ -95,40 +79,12 @@ public List getReleasedVersionsById(String id) { return product.getReleasedVersions(); } - public int updateInitialCount(String productId, int initialCount) { - Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, initialCount).set( - MongoDBConstants.SYNCHRONIZED_INSTALLATION_COUNT, true); - mongoTemplate.updateFirst(createQueryById(productId), update, Product.class); - return Optional.ofNullable(getProductWithModuleContent(productId)).map(Product::getInstallationCount).orElse(0); - } - - @Override - public int increaseInstallationCount(String productId) { - Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, 1); - Product updatedProduct = mongoTemplate.findAndModify(createQueryById(productId), update, - FindAndModifyOptions.options().returnNew(true), Product.class); - return updatedProduct != null ? updatedProduct.getInstallationCount() : 0; - } - - - @Override - public void increaseInstallationCountForProductByDesignerVersion(String productId, String designerVersion) { - Update update = new Update().inc(MongoDBConstants.INSTALLATION_COUNT, 1); - mongoTemplate.upsert(createQueryByProductIdAndDesignerVersion(productId, designerVersion), - update, ProductDesignerInstallation.class); - } - @Override public List getAllProductsWithIdAndReleaseTagAndArtifact() { return queryProductsByAggregation( createProjectIdAndReleasedVersionsAndArtifactsAggregation()); } - private Query createQueryByProductIdAndDesignerVersion(String productId, String designerVersion) { - return new Query(Criteria.where(MongoDBConstants.PRODUCT_ID).is(productId) - .andOperator(Criteria.where(MongoDBConstants.DESIGNER_VERSION).is(designerVersion))); - } - protected Aggregation createProjectIdAndReleasedVersionsAndArtifactsAggregation() { return Aggregation.newAggregation( Aggregation.project(MongoDBConstants.ID, MongoDBConstants.ARTIFACTS, MongoDBConstants.RELEASED_VERSIONS) @@ -155,9 +111,15 @@ public List findAllProductsHaveDocument() { } private Page getResultAsPageable(Pageable pageable, Criteria criteria) { - var query = new Query(criteria); - query.with(pageable); - List entities = mongoTemplate.find(query, Product.class); + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(criteria), + Aggregation.lookup(MongoDBConstants.PRODUCT_MARKETPLACE_COLLECTION, MongoDBConstants.ID, MongoDBConstants.ID, + MongoDBConstants.MARKETPLACE_DATA), + Aggregation.sort(pageable.getSort()) + ); + + List entities = mongoTemplate.aggregate(aggregation, MongoDBConstants.PRODUCT_COLLECTION, + Product.class).getMappedResults(); long count = mongoTemplate.count(new Query(criteria), Product.class); return new PageImpl<>(entities, pageable, count); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ProductMarketplaceDataService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductMarketplaceDataService.java new file mode 100644 index 000000000..508c72914 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ProductMarketplaceDataService.java @@ -0,0 +1,14 @@ +package com.axonivy.market.service; + +import com.axonivy.market.entity.ProductMarketplaceData; +import com.axonivy.market.model.ProductCustomSortRequest; + +public interface ProductMarketplaceDataService { + void addCustomSortProduct(ProductCustomSortRequest customSort); + + int updateInstallationCountForProduct(String key, String designerVersion); + + int updateProductInstallationCount(String id); + + ProductMarketplaceData getProductMarketplaceData(String id); +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java index 0c7321acb..3e88d59d9 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java @@ -1,8 +1,6 @@ package com.axonivy.market.service; import com.axonivy.market.entity.Product; -import com.axonivy.market.exceptions.model.InvalidParamException; -import com.axonivy.market.model.ProductCustomSortRequest; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -13,14 +11,10 @@ public interface ProductService { List syncLatestDataFromMarketRepo(Boolean resetSync); - int updateInstallationCountForProduct(String key, String designerVersion); - Product fetchProductDetail(String id, Boolean isShowDevVersion); String getCompatibilityFromOldestVersion(String oldestVersion); - void addCustomSortProduct(ProductCustomSortRequest customSort) throws InvalidParamException; - Product fetchBestMatchProductDetail(String id, String version); Product fetchProductDetailByIdAndVersion(String id, String version); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductMarketplaceDataServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductMarketplaceDataServiceImpl.java new file mode 100644 index 000000000..1debedc32 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductMarketplaceDataServiceImpl.java @@ -0,0 +1,140 @@ +package com.axonivy.market.service.impl; + +import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.entity.ProductCustomSort; +import com.axonivy.market.entity.ProductMarketplaceData; +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.enums.SortOption; +import com.axonivy.market.exceptions.model.NotFoundException; +import com.axonivy.market.model.ProductCustomSortRequest; +import com.axonivy.market.repository.ProductCustomSortRepository; +import com.axonivy.market.repository.ProductMarketplaceDataRepository; +import com.axonivy.market.repository.ProductRepository; +import com.axonivy.market.service.ProductMarketplaceDataService; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@Log4j2 +public class ProductMarketplaceDataServiceImpl implements ProductMarketplaceDataService { + private final ProductMarketplaceDataRepository productMarketplaceDataRepo; + private final ProductCustomSortRepository productCustomSortRepo; + private final MongoTemplate mongoTemplate; + private final ProductRepository productRepo; + private final ObjectMapper mapper = new ObjectMapper(); + private final SecureRandom random = new SecureRandom(); + @Value("${market.legacy.installation.counts.path}") + private String legacyInstallationCountPath; + + public ProductMarketplaceDataServiceImpl(ProductMarketplaceDataRepository productMarketplaceDataRepo, + ProductCustomSortRepository productCustomSortRepo, MongoTemplate mongoTemplate, ProductRepository productRepo) { + this.productMarketplaceDataRepo = productMarketplaceDataRepo; + this.productCustomSortRepo = productCustomSortRepo; + this.mongoTemplate = mongoTemplate; + this.productRepo = productRepo; + } + + @Override + public void addCustomSortProduct(ProductCustomSortRequest customSort) { + SortOption.of(customSort.getRuleForRemainder()); + + ProductCustomSort productCustomSort = new ProductCustomSort(customSort.getRuleForRemainder()); + productCustomSortRepo.deleteAll(); + removeFieldFromAllProductDocuments(ProductJsonConstants.CUSTOM_ORDER); + productCustomSortRepo.save(productCustomSort); + productMarketplaceDataRepo.saveAll(refineOrderedListOfProductsInCustomSort(customSort.getOrderedListOfProducts())); + } + + public List refineOrderedListOfProductsInCustomSort(List orderedListOfProducts) { + List productEntries = new ArrayList<>(); + + int descendingOrder = orderedListOfProducts.size(); + for (String productId : orderedListOfProducts) { + validateProductExists(productId); + ProductMarketplaceData productMarketplaceData = getProductMarketplaceData(productId); + + productMarketplaceData.setCustomOrder(descendingOrder--); + productEntries.add(productMarketplaceData); + } + return productEntries; + } + + public void removeFieldFromAllProductDocuments(String fieldName) { + Update update = new Update().unset(fieldName); + mongoTemplate.updateMulti(new Query(), update, ProductMarketplaceData.class); + } + + @Override + public int updateInstallationCountForProduct(String productId, String designerVersion) { + validateProductExists(productId); + ProductMarketplaceData productMarketplaceData = getProductMarketplaceData(productId); + + log.info("Increase installation count for product {} By Designer Version {}", productId, designerVersion); + if (StringUtils.isNotBlank(designerVersion)) { + productMarketplaceDataRepo.increaseInstallationCountForProductByDesignerVersion(productId, designerVersion); + } + + log.info("updating installation count for product {}", productId); + if (BooleanUtils.isTrue(productMarketplaceData.getSynchronizedInstallationCount())) { + return productMarketplaceDataRepo.increaseInstallationCount(productId); + } + int installationCount = getInstallationCountFromFileOrInitializeRandomly(productId); + return productMarketplaceDataRepo.updateInitialCount(productId, installationCount + 1); + } + + public int getInstallationCountFromFileOrInitializeRandomly(String productId) { + log.info("synchronizing installation count for product {}", productId); + int result = 0; + try { + String installationCounts = Files.readString(Paths.get(legacyInstallationCountPath)); + Map mapping = mapper.readValue(installationCounts, + new TypeReference>() { + }); + List keyList = mapping.keySet().stream().toList(); + result = keyList.contains(productId) ? mapping.get(productId) : random.nextInt(20, 50); + log.info("synchronized installation count for product {} successfully", productId); + } catch (IOException ex) { + log.error("Could not read the marketplace-installation file to synchronize", ex); + } + return result; + } + + @Override + public int updateProductInstallationCount(String id) { + ProductMarketplaceData productMarketplaceData = getProductMarketplaceData(id); + if (BooleanUtils.isNotTrue(productMarketplaceData.getSynchronizedInstallationCount())) { + return productMarketplaceDataRepo.updateInitialCount(id, + getInstallationCountFromFileOrInitializeRandomly(id)); + } + return productMarketplaceData.getInstallationCount(); + } + + @Override + public ProductMarketplaceData getProductMarketplaceData(String productId) { + return productMarketplaceDataRepo.findById(productId).orElse( + ProductMarketplaceData.builder().id(productId).build()); + } + + private void validateProductExists(String productId) throws NotFoundException { + if (productRepo.findById(productId).isEmpty()) { + throw new NotFoundException(ErrorCode.PRODUCT_NOT_FOUND, "Not found product with id: " + productId); + } + } +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java index 836ed1da2..24de86232 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java @@ -5,7 +5,7 @@ import com.axonivy.market.constants.GitHubConstants; import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.constants.MetaConstants; -import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.criteria.ProductSearchCriteria; import com.axonivy.market.entity.GitHubRepoMeta; import com.axonivy.market.entity.Image; @@ -13,38 +13,26 @@ import com.axonivy.market.entity.ProductCustomSort; import com.axonivy.market.entity.ProductJsonContent; import com.axonivy.market.entity.ProductModuleContent; -import com.axonivy.market.enums.ErrorCode; import com.axonivy.market.enums.FileType; import com.axonivy.market.enums.Language; import com.axonivy.market.enums.SortOption; import com.axonivy.market.enums.TypeOption; -import com.axonivy.market.exceptions.model.InvalidParamException; import com.axonivy.market.factory.ProductFactory; import com.axonivy.market.github.model.GitHubFile; import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; import com.axonivy.market.github.service.GHAxonIvyProductRepoService; import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.github.util.GitHubUtils; -import com.axonivy.market.model.ProductCustomSortRequest; -import com.axonivy.market.repository.GitHubRepoMetaRepository; -import com.axonivy.market.repository.ImageRepository; -import com.axonivy.market.repository.MavenArtifactVersionRepository; -import com.axonivy.market.repository.MetadataRepository; -import com.axonivy.market.repository.MetadataSyncRepository; -import com.axonivy.market.repository.ProductCustomSortRepository; -import com.axonivy.market.repository.ProductJsonContentRepository; -import com.axonivy.market.repository.ProductModuleContentRepository; -import com.axonivy.market.repository.ProductRepository; +import com.axonivy.market.repository.*; import com.axonivy.market.service.ExternalDocumentService; import com.axonivy.market.service.ImageService; import com.axonivy.market.service.MetadataService; import com.axonivy.market.service.ProductContentService; +import com.axonivy.market.service.ProductMarketplaceDataService; import com.axonivy.market.service.ProductService; import com.axonivy.market.util.MavenUtils; import com.axonivy.market.util.MetadataReaderUtils; import com.axonivy.market.util.VersionUtils; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; @@ -59,9 +47,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.w3c.dom.Document; @@ -69,9 +54,6 @@ import java.io.IOException; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.SecureRandom; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; @@ -113,28 +95,25 @@ public class ProductServiceImpl implements ProductService { private final ProductJsonContentRepository productJsonContentRepo; private final ImageRepository imageRepo; private final ImageService imageService; - private final MongoTemplate mongoTemplate; private final ProductContentService productContentService; private final ExternalDocumentService externalDocumentService; - private final ObjectMapper mapper = new ObjectMapper(); - private final SecureRandom random = new SecureRandom(); private final MetadataService metadataService; + private final ProductMarketplaceDataService productMarketplaceDataService; + private final ProductMarketplaceDataRepository productMarketplaceDataRepo; private GHCommit lastGHCommit; private GitHubRepoMeta marketRepoMeta; - @Value("${market.legacy.installation.counts.path}") - private String legacyInstallationCountPath; @Value("${market.github.market.branch}") private String marketRepoBranch; - public ProductServiceImpl(ProductRepository productRepo, - ProductModuleContentRepository productModuleContentRepo, + public ProductServiceImpl(ProductRepository productRepo, ProductModuleContentRepository productModuleContentRepo, GHAxonIvyMarketRepoService axonIvyMarketRepoService, GHAxonIvyProductRepoService axonIvyProductRepoService, GitHubRepoMetaRepository gitHubRepoMetaRepo, GitHubService gitHubService, ProductCustomSortRepository productCustomSortRepo, MavenArtifactVersionRepository mavenArtifactVersionRepo, ProductJsonContentRepository productJsonContentRepo, ImageRepository imageRepo, MetadataSyncRepository metadataSyncRepo, MetadataRepository metadataRepo, ImageService imageService, - MongoTemplate mongoTemplate, ProductContentService productContentService, - ExternalDocumentService externalDocumentService, MetadataService metadataService) { + ProductContentService productContentService, MetadataService metadataService, + ProductMarketplaceDataService productMarketplaceDataService, ExternalDocumentService externalDocumentService, + ProductMarketplaceDataRepository productMarketplaceDataRepo) { this.productRepo = productRepo; this.productModuleContentRepo = productModuleContentRepo; this.axonIvyMarketRepoService = axonIvyMarketRepoService; @@ -143,15 +122,16 @@ public ProductServiceImpl(ProductRepository productRepo, this.gitHubService = gitHubService; this.productCustomSortRepo = productCustomSortRepo; this.mavenArtifactVersionRepo = mavenArtifactVersionRepo; - this.productJsonContentRepo = productJsonContentRepo; this.metadataSyncRepo = metadataSyncRepo; this.metadataRepo = metadataRepo; + this.productJsonContentRepo = productJsonContentRepo; this.imageRepo = imageRepo; this.imageService = imageService; - this.mongoTemplate = mongoTemplate; + this.metadataService = metadataService; this.productContentService = productContentService; + this.productMarketplaceDataService = productMarketplaceDataService; this.externalDocumentService = externalDocumentService; - this.metadataService = metadataService; + this.productMarketplaceDataRepo = productMarketplaceDataRepo; } @Override @@ -195,44 +175,6 @@ public List syncLatestDataFromMarketRepo(Boolean resetSync) { return syncedProductIds.stream().filter(StringUtils::isNotBlank).toList(); } - @Override - public int updateInstallationCountForProduct(String key, String designerVersion) { - Product product = productRepo.getProductWithModuleContent(key); - if (Objects.isNull(product)) { - return 0; - } - - log.info("Increase installation count for product {} By Designer Version {}", key, designerVersion); - if (StringUtils.isNotBlank(designerVersion)) { - productRepo.increaseInstallationCountForProductByDesignerVersion(key, designerVersion); - } - - log.info("updating installation count for product {}", key); - if (BooleanUtils.isTrue(product.getSynchronizedInstallationCount())) { - return productRepo.increaseInstallationCount(key); - } - syncInstallationCountWithProduct(product); - return productRepo.updateInitialCount(key, product.getInstallationCount() + 1); - } - - public void syncInstallationCountWithProduct(Product product) { - log.info("synchronizing installation count for product {}", product.getId()); - try { - String installationCounts = Files.readString(Paths.get(legacyInstallationCountPath)); - Map mapping = mapper.readValue(installationCounts, - new TypeReference>() { - }); - List keyList = mapping.keySet().stream().toList(); - int currentInstallationCount = keyList.contains(product.getId()) - ? mapping.get(product.getId()) : random.nextInt(20, 50); - product.setInstallationCount(currentInstallationCount); - product.setSynchronizedInstallationCount(true); - log.info("synchronized installation count for product {} successfully", product.getId()); - } catch (IOException ex) { - log.error("Could not read the marketplace-installation file to synchronize", ex); - } - } - private void syncRepoMetaDataStatus() { if (lastGHCommit == null) { return; @@ -365,6 +307,9 @@ private Pageable refinePagination(String language, Pageable pageable) { } public Order createOrder(SortOption sortOption, String language) { + if (SortOption.STANDARD.equals(sortOption)) { + return new Order(sortOption.getDirection(), MongoDBConstants.MARKETPLACE_DATA_CUSTOM_ORDER); + } return new Order(sortOption.getDirection(), sortOption.getCode(language)); } @@ -429,6 +374,7 @@ private List syncProductsFromGitHubRepo(Boolean resetSync) { updateProductContentForNonStandardProduct(ghContentEntity.getValue(), product); updateProductFromReleasedVersions(product); transferComputedDataFromDB(product); + productMarketplaceDataRepo.checkAndInitProductMarketplaceDataIfNotExist(product.getId()); syncedProductIds.add(productRepo.save(product).getId()); } return syncedProductIds; @@ -524,7 +470,7 @@ private void updateContentsFromMavenXML(Product product, String metadataContent, if (ObjectUtils.isEmpty(mavenVersions)) { return; } - + Date lastUpdated = getLastUpdatedDate(document); if (ObjectUtils.isEmpty(product.getNewestPublishedDate()) || lastUpdated.after(product.getNewestPublishedDate())) { String latestVersion = MetadataReaderUtils.getElementValue(document, MavenConstants.LATEST_VERSION_TAG); @@ -612,7 +558,8 @@ public String getCompatibilityFromOldestVersion(String oldestVersion) { public Product fetchProductDetail(String id, Boolean isShowDevVersion) { Product product = getProductByIdWithNewestReleaseVersion(id, isShowDevVersion); return Optional.ofNullable(product).map(productItem -> { - updateProductInstallationCount(id, productItem); + int installationCount = productMarketplaceDataService.updateProductInstallationCount(id); + productItem.setInstallationCount(installationCount); return productItem; }).orElse(null); } @@ -622,11 +569,12 @@ public Product fetchBestMatchProductDetail(String id, String version) { List installableVersions = VersionUtils.getInstallableVersionsFromMetadataList( metadataRepo.findByProductId(id)); String bestMatchVersion = VersionUtils.getBestMatchVersion(installableVersions, version); - // Cover exception case of employee onboarding without any product.json file + // Cover exception case of employee onboarding without any product.json file Product product = StringUtils.isBlank(bestMatchVersion) ? getProductByIdWithNewestReleaseVersion(id, false) : productRepo.getProductByIdAndVersion(id, bestMatchVersion); return Optional.ofNullable(product).map(productItem -> { - updateProductInstallationCount(id, productItem); + int installationCount = productMarketplaceDataService.updateProductInstallationCount(id); + productItem.setInstallationCount(installationCount); productItem.setBestMatchVersion(bestMatchVersion); return productItem; }).orElse(null); @@ -657,55 +605,11 @@ public Product getProductByIdWithNewestReleaseVersion(String id, Boolean isShowD return product; } - public void updateProductInstallationCount(String id, Product productItem) { - if (!BooleanUtils.isTrue(productItem.getSynchronizedInstallationCount())) { - syncInstallationCountWithProduct(productItem); - int persistedInitialCount = productRepo.updateInitialCount(id, productItem.getInstallationCount()); - productItem.setInstallationCount(persistedInitialCount); - } - } - @Override public Product fetchProductDetailByIdAndVersion(String id, String version) { return productRepo.getProductByIdAndVersion(id, version); } - @Override - public void addCustomSortProduct(ProductCustomSortRequest customSort) throws InvalidParamException { - SortOption.of(customSort.getRuleForRemainder()); - - ProductCustomSort productCustomSort = new ProductCustomSort(customSort.getRuleForRemainder()); - productCustomSortRepo.deleteAll(); - removeFieldFromAllProductDocuments(ProductJsonConstants.CUSTOM_ORDER); - productCustomSortRepo.save(productCustomSort); - productRepo.saveAll(refineOrderedListOfProductsInCustomSort(customSort.getOrderedListOfProducts())); - } - - public List refineOrderedListOfProductsInCustomSort(List orderedListOfProducts) - throws InvalidParamException { - List productEntries = new ArrayList<>(); - - int descendingOrder = orderedListOfProducts.size(); - for (String productId : orderedListOfProducts) { - Optional productOptional = productRepo.findById(productId); - - if (productOptional.isEmpty()) { - throw new InvalidParamException(ErrorCode.PRODUCT_NOT_FOUND, "Not found product with id: " + productId); - } - Product product = productOptional.get(); - product.setCustomOrder(descendingOrder--); - productRepo.save(product); - productEntries.add(product); - } - - return productEntries; - } - - public void removeFieldFromAllProductDocuments(String fieldName) { - Update update = new Update().unset(fieldName); - mongoTemplate.updateMulti(new Query(), update, Product.class); - } - public void transferComputedDataFromDB(Product product) { productRepo.findById(product.getId()).ifPresent(persistedData -> ProductFactory.transferComputedPersistedDataToProduct(persistedData, product) @@ -725,6 +629,7 @@ public boolean syncOneProduct(String productId, String marketItemPath, Boolean o mappingMetaDataAndLogoFromGHContent(gitHubContents, product); updateProductContentForNonStandardProduct(gitHubContents, product); updateProductFromReleasedVersions(product); + productMarketplaceDataRepo.checkAndInitProductMarketplaceDataIfNotExist(productId); productRepo.save(product); log.info("Sync product {} is finished!", productId); return true; @@ -787,4 +692,4 @@ private void updateProductContentForNonStandardProduct(List ghContent productModuleContentRepo.save(initialContent); } } -} +} \ No newline at end of file diff --git a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java index 834e0e56c..2b470f035 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java +++ b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java @@ -8,6 +8,7 @@ import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductDesignerInstallation; import com.axonivy.market.entity.ProductJsonContent; +import com.axonivy.market.entity.ProductMarketplaceData; import com.axonivy.market.enums.Language; import com.axonivy.market.enums.SortOption; import com.axonivy.market.model.MavenArtifactModel; @@ -33,6 +34,7 @@ @Log4j2 public class BaseSetup { + protected static final String AUTHORIZATION_HEADER = "Bearer valid_token"; protected static final String SAMPLE_PRODUCT_ID = "amazon-comprehend"; protected static final String SAMPLE_PRODUCT_PATH = "/market/connector/amazon-comprehend"; protected static final String SAMPLE_PRODUCT_NAME = "prody Comprehend"; @@ -69,6 +71,7 @@ public class BaseSetup { protected static final String LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME = "legacyInstallationCountPath"; protected static final String MOCK_IMAGE_URL = "https://raw.githubusercontent" + ".com/amazon-comprehend-connector-product/images/comprehend-demo-sentiment.png"; + protected static final String INSTALLATION_FILE_PATH = "src/test/resources/installationCount.json"; protected Page createPageProductsMock() { var mockProducts = new ArrayList(); @@ -242,4 +245,8 @@ protected static ProductJsonContent getMockProductJsonContentContainMavenDropins result.setContent(getContentFromTestResourcePath(MOCK_PRODUCT_JSON_WITH_DROPINS_FILE_PATH)); return result; } + + protected ProductMarketplaceData getMockProductMarketplaceData() { + return ProductMarketplaceData.builder().id(MOCK_PRODUCT_ID).installationCount(3).build(); + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java index 9d21e2b52..2d7cabd4b 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductControllerTest.java @@ -1,5 +1,6 @@ package com.axonivy.market.controller; +import com.axonivy.market.BaseSetup; import com.axonivy.market.assembler.ProductModelAssembler; import com.axonivy.market.entity.Product; import com.axonivy.market.enums.ErrorCode; @@ -9,10 +10,8 @@ import com.axonivy.market.exceptions.model.UnauthorizedException; import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; import com.axonivy.market.github.service.GitHubService; -import com.axonivy.market.model.ProductCustomSortRequest; import com.axonivy.market.service.MetadataService; import com.axonivy.market.service.ProductService; -import com.axonivy.market.service.VersionService; import com.axonivy.market.util.AuthorizationUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,7 +40,7 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class ProductControllerTest { +class ProductControllerTest extends BaseSetup { private static final String PRODUCT_ID_SAMPLE = "a-trust"; private static final String PRODUCT_PATH_SAMPLE = "market/connector/a-trust"; private static final String PRODUCT_NAME_SAMPLE = "Amazon Comprehend"; @@ -50,7 +49,6 @@ class ProductControllerTest { "uncover information in unstructured data."; private static final String PRODUCT_DESC_DE_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning " + "to uncover information in unstructured data. DE"; - private static final String AUTHORIZATION_HEADER = "Bearer valid_token"; private static final String INVALID_AUTHORIZATION_HEADER = "Bearer invalid_token"; @Mock @@ -71,9 +69,6 @@ class ProductControllerTest { @Mock private MetadataService metadataService; - @Mock - private VersionService versionService; - @Mock private GHAxonIvyMarketRepoService axonIvyMarketRepoService; @@ -218,17 +213,6 @@ void testSyncOneProductInvalidToken() { assertEquals(ErrorCode.GITHUB_USER_UNAUTHORIZED.getHelpText(), exception.getMessage()); } - @Test - void testCreateCustomSortProductsSuccess() { - ProductCustomSortRequest mockProductCustomSortRequest = createProductCustomSortRequestMock(); - var response = productController.createCustomSortProducts(AUTHORIZATION_HEADER, mockProductCustomSortRequest); - - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertTrue(response.hasBody()); - assertEquals(ErrorCode.SUCCESSFUL.getCode(), Objects.requireNonNull(response.getBody()).getHelpCode()); - assertTrue(response.getBody().getMessageDetails().contains("Custom product sort order added successfully")); - } - @Test void testGetBearerTokenWithValidHeader() { String token = AuthorizationUtils.getBearerToken(AUTHORIZATION_HEADER); @@ -256,11 +240,4 @@ private Product createProductMock() { mockProduct.setTags(List.of("AI")); return mockProduct; } - - private ProductCustomSortRequest createProductCustomSortRequestMock() { - List productIds = new ArrayList<>(); - productIds.add("a-trust"); - productIds.add("approval-decision-utils"); - return new ProductCustomSortRequest(productIds, "recently"); - } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java index 570d765a2..249df32a6 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductDetailsControllerTest.java @@ -162,15 +162,6 @@ void testFindProductVersionsById() { Assertions.assertEquals(models, result.getBody()); } - @Test - void testSyncInstallationCount() { - when(productService.updateInstallationCountForProduct("google-maps-connector", "10.0.20")).thenReturn(1); - - var result = productDetailsController.syncInstallationCount("google-maps-connector", "10.0.20"); - - assertEquals(1, result.getBody()); - } - @Test void findProductVersionsById() { when(versionService.getVersionsForDesigner("google-maps-connector")).thenReturn(mockVersionAndUrlModels()); diff --git a/marketplace-service/src/test/java/com/axonivy/market/controller/ProductMarketplaceDataControllerTest.java b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductMarketplaceDataControllerTest.java new file mode 100644 index 000000000..81c5d83ce --- /dev/null +++ b/marketplace-service/src/test/java/com/axonivy/market/controller/ProductMarketplaceDataControllerTest.java @@ -0,0 +1,59 @@ +package com.axonivy.market.controller; + +import com.axonivy.market.BaseSetup; +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.model.ProductCustomSortRequest; +import com.axonivy.market.service.ProductMarketplaceDataService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ProductMarketplaceDataControllerTest extends BaseSetup { + @Mock + private ProductMarketplaceDataService productMarketplaceDataService; + @Mock + private GitHubService gitHubService; + @InjectMocks + private ProductMarketplaceDataController productMarketplaceDataController; + + @Test + void testCreateCustomSortProducts() { + ProductCustomSortRequest mockProductCustomSortRequest = createProductCustomSortRequestMock(); + var response = productMarketplaceDataController.createCustomSortProducts(AUTHORIZATION_HEADER, + mockProductCustomSortRequest); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.hasBody()); + assertEquals(ErrorCode.SUCCESSFUL.getCode(), Objects.requireNonNull(response.getBody()).getHelpCode()); + assertTrue(response.getBody().getMessageDetails().contains("Custom product sort order added successfully")); + } + + @Test + void testSyncInstallationCount() { + when(productMarketplaceDataService.updateInstallationCountForProduct(MOCK_PRODUCT_ID, + MOCK_RELEASED_VERSION)).thenReturn(1); + var result = productMarketplaceDataController.syncInstallationCount(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); + + assertEquals(1, result.getBody()); + } + + private ProductCustomSortRequest createProductCustomSortRequestMock() { + List productIds = new ArrayList<>(); + productIds.add("a-trust"); + productIds.add("approval-decision-utils"); + return new ProductCustomSortRequest(productIds, "recently"); + } +} \ No newline at end of file diff --git a/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java b/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java index 24a4a854b..b630add3e 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java @@ -1,5 +1,6 @@ package com.axonivy.market.factory; +import com.axonivy.market.BaseSetup; import com.axonivy.market.constants.ProductJsonConstants; import com.axonivy.market.entity.Product; import com.axonivy.market.enums.Language; @@ -22,10 +23,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class ProductFactoryTest { - private static final String DUMMY_LOGO_URL = "https://raw.githubusercontent" + - ".com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; - +class ProductFactoryTest extends BaseSetup { @Test void testMappingByGHContent() throws IOException { Product product = new Product(); @@ -81,11 +79,9 @@ void testExtractSourceUrl() { void testTransferComputedData() { Product product = new Product(); Product persistedData = new Product(); - persistedData.setCustomOrder(1); - persistedData.setInstallationCount(300); + persistedData.setMarketDirectory(SAMPLE_PRODUCT_PATH); ProductFactory.transferComputedPersistedDataToProduct(persistedData, product); - assertEquals(1, product.getCustomOrder()); - assertEquals(300, product.getInstallationCount()); + assertEquals(SAMPLE_PRODUCT_PATH, product.getMarketDirectory()); } } \ No newline at end of file diff --git a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductMarketplaceDataRepositoryImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductMarketplaceDataRepositoryImplTest.java new file mode 100644 index 000000000..e35fa1061 --- /dev/null +++ b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductMarketplaceDataRepositoryImplTest.java @@ -0,0 +1,84 @@ +package com.axonivy.market.repository.impl; + +import com.axonivy.market.BaseSetup; +import com.axonivy.market.constants.MongoDBConstants; +import com.axonivy.market.entity.ProductDesignerInstallation; +import com.axonivy.market.entity.ProductMarketplaceData; +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.data.mongodb.core.FindAndModifyOptions; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CustomProductMarketplaceDataRepositoryImplTest extends BaseSetup { + @Mock + private MongoTemplate mongoTemplate; + @InjectMocks + private CustomProductMarketplaceDataRepositoryImpl repo; + + @Test + void testIncreaseInstallationCount() { + ProductMarketplaceData mockProductMarketplaceData = getMockProductMarketplaceData(); + when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), + eq(ProductMarketplaceData.class))).thenReturn(mockProductMarketplaceData); + + int updatedCount = repo.increaseInstallationCount(MOCK_PRODUCT_ID); + + assertEquals(3, updatedCount); + } + + @Test + void testIncreaseInstallationCount_NullProduct() { + when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), + eq(ProductMarketplaceData.class))).thenReturn(null); + int updatedCount = repo.increaseInstallationCount(MOCK_PRODUCT_ID); + assertEquals(0, updatedCount); + } + + @Test + void testUpdateInitialCount() { + int initialCount = 10; + + ProductMarketplaceData updatedProductMarketplaceData = new ProductMarketplaceData(); + updatedProductMarketplaceData.setId(MOCK_PRODUCT_ID); + updatedProductMarketplaceData.setInstallationCount(11); + + when(mongoTemplate.findAndModify(any(Query.class), + eq(new Update().inc(MongoDBConstants.INSTALLATION_COUNT, initialCount) + .set(MongoDBConstants.SYNCHRONIZED_INSTALLATION_COUNT, true)), + any(FindAndModifyOptions.class), + eq(ProductMarketplaceData.class)) + ).thenReturn(updatedProductMarketplaceData); + + int updatedCount = repo.updateInitialCount(MOCK_PRODUCT_ID, initialCount); + + assertEquals(11, updatedCount); + } + + @Test + void testIncreaseInstallationCountForProductByDesignerVersion() { + repo.increaseInstallationCountForProductByDesignerVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); + verify(mongoTemplate).upsert(any(Query.class), any(Update.class), eq(ProductDesignerInstallation.class)); + } + + @Test + void testCheckAndInitProductMarketplaceDataIfNotExist(){ + Query query = new Query(Criteria.where(MongoDBConstants.ID).is(MOCK_PRODUCT_ID)); + when(mongoTemplate.exists(query, ProductMarketplaceData.class)).thenReturn(true); + repo.checkAndInitProductMarketplaceDataIfNotExist(MOCK_PRODUCT_ID); + verify(mongoTemplate, never()).insert(any(ProductMarketplaceData.class)); + + when(mongoTemplate.exists(query, ProductMarketplaceData.class)).thenReturn(false); + repo.checkAndInitProductMarketplaceDataIfNotExist(MOCK_PRODUCT_ID); + verify(mongoTemplate, times(1)).insert(any(ProductMarketplaceData.class)); + } +} \ No newline at end of file diff --git a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java index d04be35be..2744fedfc 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java @@ -3,22 +3,17 @@ import com.axonivy.market.BaseSetup; import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductDesignerInstallation; import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.MetadataRepository; -import com.axonivy.market.repository.ProductJsonContentRepository; import com.axonivy.market.repository.ProductModuleContentRepository; 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.data.mongodb.core.FindAndModifyOptions; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; import java.util.List; @@ -28,15 +23,12 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class CustomProductRepositoryImplTest extends BaseSetup { @Mock ProductModuleContentRepository contentRepo; - @Mock - ProductJsonContentRepository jsonContentRepo; private Product mockProduct; private Aggregation mockAggregation; @Mock @@ -92,13 +84,6 @@ void testReleasedVersionsById_WhenResultIsNull() { assertEquals(0, results.size()); } - @Test - void testGetProductById() { - setUpMockAggregateResult(); - Product actualProduct = repo.getProductWithModuleContent(MOCK_PRODUCT_ID); - assertEquals(mockProduct, actualProduct); - } - @Test void testGetProductByIdAndVersion() { setUpMockAggregateResult(); @@ -112,41 +97,4 @@ void testGetReleasedVersionsById() { List actualReleasedVersions = repo.getReleasedVersionsById(MOCK_PRODUCT_ID); assertEquals(mockProduct.getReleasedVersions(), actualReleasedVersions); } - - @Test - void testIncreaseInstallationCount() { - Product product = new Product(); - product.setId(MOCK_PRODUCT_ID); - product.setInstallationCount(5); - when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), - eq(Product.class))).thenReturn(product); - int updatedCount = repo.increaseInstallationCount(MOCK_PRODUCT_ID); - assertEquals(5, updatedCount); - verify(mongoTemplate).findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), - eq(Product.class)); - } - - @Test - void testIncreaseInstallationCount_NullProduct() { - when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), - eq(Product.class))).thenReturn(null); - int updatedCount = repo.increaseInstallationCount(MOCK_PRODUCT_ID); - assertEquals(0, updatedCount); - } - - @Test - void testUpdateInitialCount() { - setUpMockAggregateResult(); - int initialCount = 10; - repo.updateInitialCount(MOCK_PRODUCT_ID, initialCount); - verify(mongoTemplate).updateFirst(any(Query.class), - eq(new Update().inc(MongoDBConstants.INSTALLATION_COUNT, initialCount).set(MongoDBConstants.SYNCHRONIZED_INSTALLATION_COUNT, true)), - eq(Product.class)); - } - - @Test - void testIncreaseInstallationCountForProductByDesignerVersion() { - repo.increaseInstallationCountForProductByDesignerVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); - verify(mongoTemplate).upsert(any(Query.class), any(Update.class), eq(ProductDesignerInstallation.class)); - } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java index 199bb1dab..8ba14fbe5 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java @@ -1,6 +1,7 @@ package com.axonivy.market.repository.impl; import com.axonivy.market.BaseSetup; +import com.axonivy.market.constants.MongoDBConstants; import com.axonivy.market.criteria.ProductSearchCriteria; import com.axonivy.market.entity.Product; import com.axonivy.market.enums.DocumentField; @@ -13,12 +14,16 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Query; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -41,13 +46,19 @@ public void setup() { @Test void testSearchByCriteria() { - when(mongoTemplate.find(any(), eq(Product.class))).thenReturn(mockResultReturn.getContent()); - var result = productListedRepository.searchByCriteria(searchCriteria, PAGEABLE); + AggregationResults mockAggregationResults = mock(AggregationResults.class); + when(mockAggregationResults.getMappedResults()).thenReturn(mockResultReturn.getContent()); + when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), eq(Product.class))) + .thenReturn(mockAggregationResults); + when(mongoTemplate.count(any(Query.class), eq(Product.class))).thenReturn((long) mockResultReturn.getSize()); + + Page result = productListedRepository.searchByCriteria(searchCriteria, PAGEABLE); + assertFalse(result.isEmpty(), "Result is empty"); - assertTrue(result.isFirst(), "Result is not in first page"); - assertEquals(2, result.getContent().size()); + assertTrue(result.isFirst(), "Result is not on the first page"); + assertEquals(2, result.getContent().size(), "Unexpected number of products"); assertTrue(result.getContent().get(0).getNames().containsValue(SAMPLE_PRODUCT_NAME), - "No Product has name " + SAMPLE_PRODUCT_NAME); + "Expected product name not found in the result"); } @Test diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductMarketplaceDataServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductMarketplaceDataServiceImplTest.java new file mode 100644 index 000000000..969022357 --- /dev/null +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductMarketplaceDataServiceImplTest.java @@ -0,0 +1,171 @@ +package com.axonivy.market.service.impl; + +import com.axonivy.market.BaseSetup; +import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.entity.Product; +import com.axonivy.market.entity.ProductCustomSort; +import com.axonivy.market.entity.ProductMarketplaceData; +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.enums.SortOption; +import com.axonivy.market.exceptions.model.InvalidParamException; +import com.axonivy.market.exceptions.model.NotFoundException; +import com.axonivy.market.model.ProductCustomSortRequest; +import com.axonivy.market.repository.ProductCustomSortRepository; +import com.axonivy.market.repository.ProductMarketplaceDataRepository; +import com.axonivy.market.repository.ProductRepository; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ProductMarketplaceDataServiceImplTest extends BaseSetup { + @Mock + private MongoTemplate mongoTemplate; + @Mock + private ProductRepository productRepo; + @Mock + private ProductCustomSortRepository productCustomSortRepo; + @Mock + private ProductMarketplaceDataRepository productMarketplaceDataRepo; + @InjectMocks + private ProductMarketplaceDataServiceImpl productMarketplaceDataService; + @Captor + ArgumentCaptor> productListArgumentCaptor; + + @Test + void testRemoveFieldFromAllProductDocuments() { + productMarketplaceDataService.removeFieldFromAllProductDocuments(ProductJsonConstants.CUSTOM_ORDER); + + verify(mongoTemplate).updateMulti(ArgumentMatchers.any(Query.class), ArgumentMatchers.any(Update.class), + eq(ProductMarketplaceData.class)); + } + + @Test + void testAddCustomSortProduct() throws InvalidParamException { + List orderedListOfProducts = List.of(SAMPLE_PRODUCT_ID); + ProductCustomSortRequest customSortRequest = new ProductCustomSortRequest(); + customSortRequest.setOrderedListOfProducts(orderedListOfProducts); + customSortRequest.setRuleForRemainder(SortOption.ALPHABETICALLY.getOption()); + + ProductMarketplaceData mockProductMarketplaceData = new ProductMarketplaceData(); + mockProductMarketplaceData.setId(SAMPLE_PRODUCT_ID); + when(productRepo.findById(anyString())).thenReturn(Optional.of(getMockProduct())); + + productMarketplaceDataService.addCustomSortProduct(customSortRequest); + + verify(productCustomSortRepo).deleteAll(); + verify(mongoTemplate).updateMulti(any(Query.class), any(Update.class), eq(ProductMarketplaceData.class)); + verify(productCustomSortRepo).save(any(ProductCustomSort.class)); + verify(productMarketplaceDataRepo).saveAll(productListArgumentCaptor.capture()); + + List capturedProducts = productListArgumentCaptor.getValue(); + assertEquals(1, capturedProducts.size()); + assertEquals(1, capturedProducts.get(0).getCustomOrder()); + } + + @Test + void testRefineOrderedListOfProductsInCustomSort() throws InvalidParamException { + List orderedListOfProducts = List.of(SAMPLE_PRODUCT_ID); + when(productRepo.findById(anyString())).thenReturn(Optional.of(getMockProduct())); + + List refinedProducts = + productMarketplaceDataService.refineOrderedListOfProductsInCustomSort(orderedListOfProducts); + + assertEquals(1, refinedProducts.size()); + assertEquals(1, refinedProducts.get(0).getCustomOrder()); + verify(productMarketplaceDataRepo).findById(SAMPLE_PRODUCT_ID); + } + + @Test + void testRefineOrderedListOfProductsInCustomSort_ProductNotFound() { + List orderedListOfProducts = List.of(SAMPLE_PRODUCT_ID); + when(productRepo.findById(SAMPLE_PRODUCT_ID)).thenReturn(Optional.empty()); + + NotFoundException exception = assertThrows(NotFoundException.class, + () -> productMarketplaceDataService.refineOrderedListOfProductsInCustomSort(orderedListOfProducts)); + assertEquals(ErrorCode.PRODUCT_NOT_FOUND.getCode(), exception.getCode()); + } + + @Test + void testUpdateProductInstallationCountWhenNotSynchronized() { + ProductMarketplaceData mockProductMarketplaceData = getMockProductMarketplaceData(); + mockProductMarketplaceData.setSynchronizedInstallationCount(false); + ReflectionTestUtils.setField(productMarketplaceDataService, LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME, + INSTALLATION_FILE_PATH); + + when(productMarketplaceDataRepo.findById(SAMPLE_PRODUCT_ID)).thenReturn(Optional.of(mockProductMarketplaceData)); + when(productMarketplaceDataRepo.updateInitialCount(eq(SAMPLE_PRODUCT_ID), anyInt())).thenReturn(10); + + int result = productMarketplaceDataService.updateProductInstallationCount(SAMPLE_PRODUCT_ID); + + assertEquals(10, result); + verify(productMarketplaceDataRepo).updateInitialCount(eq(SAMPLE_PRODUCT_ID), anyInt()); + } + + @Test + void testUpdateInstallationCountForProduct() { + ProductMarketplaceData mockProductMarketplaceData = getMockProductMarketplaceData(); + mockProductMarketplaceData.setSynchronizedInstallationCount(true); + ReflectionTestUtils.setField(productMarketplaceDataService, LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME, + INSTALLATION_FILE_PATH); + + when(productRepo.findById(SAMPLE_PRODUCT_ID)).thenReturn(Optional.of(new Product())); + when(productMarketplaceDataRepo.findById(SAMPLE_PRODUCT_ID)).thenReturn(Optional.of(mockProductMarketplaceData)); + when(productMarketplaceDataRepo.increaseInstallationCount(SAMPLE_PRODUCT_ID)).thenReturn(4); + + int result = productMarketplaceDataService.updateInstallationCountForProduct(SAMPLE_PRODUCT_ID, + MOCK_RELEASED_VERSION); + assertEquals(4, result); + + result = productMarketplaceDataService.updateInstallationCountForProduct(SAMPLE_PRODUCT_ID, StringUtils.EMPTY); + assertEquals(4, result); + } + + @Test + void testSyncInstallationCountWithNewProduct() { + ProductMarketplaceData mockProductMarketplaceData = ProductMarketplaceData.builder().id(MOCK_PRODUCT_ID).build(); + ReflectionTestUtils.setField(productMarketplaceDataService, LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME, + INSTALLATION_FILE_PATH); + + int installationCount = productMarketplaceDataService.getInstallationCountFromFileOrInitializeRandomly( + mockProductMarketplaceData.getId()); + + assertTrue(installationCount >= 20 && installationCount <= 50); + } + + @Test + void testGetInstallationCountFromFileOrInitializeRandomly() { + ReflectionTestUtils.setField(productMarketplaceDataService, LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME, + INSTALLATION_FILE_PATH); + ProductMarketplaceData mockProductMarketplaceData = getMockProductMarketplaceData(); + + int installationCount = productMarketplaceDataService.getInstallationCountFromFileOrInitializeRandomly( + mockProductMarketplaceData.getId()); + + assertEquals(40, installationCount); + } +} \ No newline at end of file diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java index 0dd20f6bc..f4aa90868 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java @@ -8,33 +8,31 @@ import com.axonivy.market.entity.MavenArtifactVersion; import com.axonivy.market.entity.Metadata; import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductCustomSort; +import com.axonivy.market.entity.ProductMarketplaceData; import com.axonivy.market.entity.ProductModuleContent; -import com.axonivy.market.enums.ErrorCode; import com.axonivy.market.enums.FileStatus; import com.axonivy.market.enums.FileType; import com.axonivy.market.enums.Language; import com.axonivy.market.enums.SortOption; import com.axonivy.market.enums.TypeOption; -import com.axonivy.market.exceptions.model.InvalidParamException; import com.axonivy.market.github.model.GitHubFile; import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; import com.axonivy.market.github.service.GHAxonIvyProductRepoService; import com.axonivy.market.github.service.GitHubService; -import com.axonivy.market.model.ProductCustomSortRequest; import com.axonivy.market.repository.GitHubRepoMetaRepository; import com.axonivy.market.repository.ImageRepository; import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.MetadataRepository; import com.axonivy.market.repository.MetadataSyncRepository; -import com.axonivy.market.repository.ProductCustomSortRepository; import com.axonivy.market.repository.ProductJsonContentRepository; +import com.axonivy.market.repository.ProductMarketplaceDataRepository; import com.axonivy.market.repository.ProductModuleContentRepository; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.ExternalDocumentService; import com.axonivy.market.service.ImageService; import com.axonivy.market.service.MetadataService; import com.axonivy.market.service.ProductContentService; +import com.axonivy.market.service.ProductMarketplaceDataService; import com.axonivy.market.util.MavenUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -42,7 +40,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.kohsuke.github.GHCommit; import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHRepository; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; @@ -55,10 +52,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; -import org.springframework.test.util.ReflectionTestUtils; import java.io.IOException; import java.io.InputStream; @@ -84,7 +77,6 @@ class ProductServiceImplTest extends BaseSetup { private static final Pageable PAGEABLE = PageRequest.of(0, 20, Sort.by(SortOption.ALPHABETICALLY.getOption()).descending()); private static final String SHA1_SAMPLE = "35baa89091b2452b77705da227f1a964ecabc6c8"; - private static final String INSTALLATION_FILE_PATH = "src/test/resources/installationCount.json"; private static final String EMPTY_SOURCE_URL_META_JSON_FILE = "/emptySourceUrlMeta.json"; private static final String META_JSON_FILE_WITH_VENDOR_INFORMATION = "/meta-with-vendor-information.json"; @Captor @@ -94,17 +86,13 @@ class ProductServiceImplTest extends BaseSetup { @Captor ArgumentCaptor argumentCaptorProductModuleContent; @Captor - ArgumentCaptor> productListArgumentCaptor; - @Captor ArgumentCaptor productSearchCriteriaArgumentCaptor; + @Captor + ArgumentCaptor argumentCaptorProductMarketplaceData; private String keyword; private String language; private Page mockResultReturn; @Mock - private MongoTemplate mongoTemplate; - @Mock - private GHRepository ghRepository; - @Mock private ProductRepository productRepo; @Mock private ProductModuleContentRepository productModuleContentRepo; @@ -123,8 +111,6 @@ class ProductServiceImplTest extends BaseSetup { @Mock private MetadataRepository metadataRepo; @Mock - private ProductCustomSortRepository productCustomSortRepo; - @Mock private ImageService imageService; @Mock private ExternalDocumentService externalDocumentService; @@ -136,6 +122,10 @@ class ProductServiceImplTest extends BaseSetup { private GHAxonIvyProductRepoService axonIvyProductRepoService; @Mock private MetadataSyncRepository metadataSyncRepo; + @Mock + private ProductMarketplaceDataService productMarketplaceDataService; + @Mock + private ProductMarketplaceDataRepository productMarketplaceDataRepo; @InjectMocks private ProductServiceImpl productService; @@ -144,48 +134,6 @@ public void setup() { mockResultReturn = createPageProductsMock(); } - @Test - void testUpdateInstallationCountForProduct() { - int result = productService.updateInstallationCountForProduct(null, MOCK_RELEASED_VERSION); - assertEquals(0, result); - - Product product = getMockProduct(); - product.setSynchronizedInstallationCount(true); - when(productRepo.getProductWithModuleContent(MOCK_PRODUCT_ID)).thenReturn(product); - when(productRepo.increaseInstallationCount(MOCK_PRODUCT_ID)).thenReturn(31); - - result = productService.updateInstallationCountForProduct(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); - assertEquals(31, result); - - result = productService.updateInstallationCountForProduct(MOCK_PRODUCT_ID, StringUtils.EMPTY); - assertEquals(31, result); - } - - @Test - void testSyncInstallationCountWithNewProduct() { - Product product = new Product(); - product.setSynchronizedInstallationCount(null); - product.setId(MOCK_PRODUCT_ID); - ReflectionTestUtils.setField(productService, LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME, INSTALLATION_FILE_PATH); - - productService.syncInstallationCountWithProduct(product); - - assertTrue(product.getInstallationCount() >= 20 && product.getInstallationCount() <= 50); - assertTrue(product.getSynchronizedInstallationCount()); - } - - @Test - void testSyncInstallationCountWithProduct() { - ReflectionTestUtils.setField(productService, LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME, INSTALLATION_FILE_PATH); - Product product = getMockProduct(); - product.setSynchronizedInstallationCount(false); - - productService.syncInstallationCountWithProduct(product); - - assertEquals(40, product.getInstallationCount()); - assertTrue(product.getSynchronizedInstallationCount()); - } - @Test void testFindProducts() { language = "en"; @@ -315,8 +263,7 @@ void testSyncProductsFirstTime() throws IOException { when(marketRepoService.getLastCommit(anyLong())).thenReturn(mockCommit); when(repoMetaRepo.findByRepoName(anyString())).thenReturn(null); when(productContentService.getReadmeAndProductContentsFromVersion(any(), anyString(), anyString(), - any(), anyString())).thenReturn( - mockReadmeProductContent()); + any(), anyString())).thenReturn(mockReadmeProductContent()); Map> mockGHContentMap = new HashMap<>(); mockGHContentMap.put(SAMPLE_PRODUCT_ID, mockMetaJsonAndLogoList()); @@ -327,6 +274,7 @@ void testSyncProductsFirstTime() throws IOException { when(productRepo.save(any(Product.class))).thenReturn(new Product()); // Executes productService.syncLatestDataFromMarketRepo(false); + verify(productModuleContentRepo).saveAll(argumentCaptorProductModuleContents.capture()); verify(productRepo).save(argumentCaptor.capture()); @@ -444,11 +392,9 @@ void testSearchProducts() { @Test void testFetchProductDetail() { MavenArtifactVersion mockMavenArtifactVersion = getMockMavenArtifactVersionWithData(); - Product mockProduct = getMockProduct(); when(mavenArtifactVersionRepo.findById(MOCK_PRODUCT_ID)).thenReturn( Optional.ofNullable(mockMavenArtifactVersion)); when(productRepo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_SNAPSHOT_VERSION)).thenReturn(null); - mockProduct.setSynchronizedInstallationCount(true); Product result = productService.fetchProductDetail(MOCK_PRODUCT_ID, true); assertNull(result); } @@ -493,12 +439,14 @@ void testFetchProductDetailByIdAndVersion() { @Test void testFetchBestMatchProductDetailByIdAndVersion() { - ReflectionTestUtils.setField(productService, LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME, INSTALLATION_FILE_PATH); Product mockProduct = getMockProduct(); Metadata mockMetadata = getMockMetadataWithVersions(); + ProductMarketplaceData mockProductMarketplaceData = getMockProductMarketplaceData(); mockMetadata.setArtifactId(MOCK_PRODUCT_ARTIFACT_ID); when(metadataRepo.findByProductId(MOCK_PRODUCT_ID)).thenReturn(List.of(mockMetadata)); when(productRepo.getProductByIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION)).thenReturn(mockProduct); + when(productMarketplaceDataService.updateProductInstallationCount(MOCK_PRODUCT_ID)).thenReturn( + mockProductMarketplaceData.getInstallationCount()); Product result = productService.fetchBestMatchProductDetail(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION); assertEquals(mockProduct, result); } @@ -516,75 +464,6 @@ void testGetCompatibilityFromNumericVersion() { assertEquals("11.2+", result); } - @Test - void testRemoveFieldFromAllProductDocuments() { - productService.removeFieldFromAllProductDocuments("customOrder"); - - verify(mongoTemplate).updateMulti(any(Query.class), any(Update.class), eq(Product.class)); - } - - @Test - void testRefineOrderedListOfProductsInCustomSort() throws InvalidParamException { - // prepare - List orderedListOfProducts = List.of(SAMPLE_PRODUCT_ID); - Product mockProduct = new Product(); - mockProduct.setId(SAMPLE_PRODUCT_ID); - when(productRepo.findById(SAMPLE_PRODUCT_ID)).thenReturn(Optional.of(mockProduct)); - - List refinedProducts = productService.refineOrderedListOfProductsInCustomSort(orderedListOfProducts); - - assertEquals(1, refinedProducts.size()); - assertEquals(1, refinedProducts.get(0).getCustomOrder()); - verify(productRepo).findById(SAMPLE_PRODUCT_ID); - } - - @Test - void testRefineOrderedListOfProductsInCustomSort_ProductNotFound() { - List orderedListOfProducts = List.of(SAMPLE_PRODUCT_ID); - when(productRepo.findById(SAMPLE_PRODUCT_ID)).thenReturn(Optional.empty()); - - InvalidParamException exception = assertThrows(InvalidParamException.class, - () -> productService.refineOrderedListOfProductsInCustomSort(orderedListOfProducts)); - assertEquals(ErrorCode.PRODUCT_NOT_FOUND.getCode(), exception.getCode()); - } - - @Test - void testAddCustomSortProduct() throws InvalidParamException { - List orderedListOfProducts = List.of(SAMPLE_PRODUCT_ID); - ProductCustomSortRequest customSortRequest = new ProductCustomSortRequest(); - customSortRequest.setOrderedListOfProducts(orderedListOfProducts); - customSortRequest.setRuleForRemainder(SortOption.ALPHABETICALLY.getOption()); - - Product mockProduct = new Product(); - mockProduct.setId(SAMPLE_PRODUCT_ID); - when(productRepo.findById(SAMPLE_PRODUCT_ID)).thenReturn(Optional.of(mockProduct)); - - productService.addCustomSortProduct(customSortRequest); - - verify(productCustomSortRepo).deleteAll(); - verify(mongoTemplate).updateMulti(any(Query.class), any(Update.class), eq(Product.class)); - verify(productCustomSortRepo).save(any(ProductCustomSort.class)); - verify(productRepo).saveAll(productListArgumentCaptor.capture()); - - List capturedProducts = productListArgumentCaptor.getValue(); - assertEquals(1, capturedProducts.size()); - assertEquals(1, capturedProducts.get(0).getCustomOrder()); - } - - @Test - void testUpdateProductInstallationCountWhenNotSynchronized() { - Product product = getMockProduct(); - product.setSynchronizedInstallationCount(false); - String id = product.getId(); - ReflectionTestUtils.setField(productService, LEGACY_INSTALLATION_COUNT_PATH_FIELD_NAME, INSTALLATION_FILE_PATH); - - when(productRepo.updateInitialCount(eq(id), anyInt())).thenReturn(10); - - productService.updateProductInstallationCount(id, product); - - assertEquals(10, product.getInstallationCount()); - } - @Test void testCreateOrder() { Sort.Order order = productService.createOrder(SortOption.ALPHABETICALLY, "en"); @@ -699,7 +578,7 @@ void testSyncOneProduct() throws IOException { when(marketRepoService.getMarketItemByPath(anyString())).thenReturn(mockContents); when(productRepo.save(any(Product.class))).thenReturn(mockProduct); // Executes - var result = productService.syncOneProduct(SAMPLE_PRODUCT_PATH, SAMPLE_PRODUCT_ID, false); + var result = productService.syncOneProduct(SAMPLE_PRODUCT_ID, SAMPLE_PRODUCT_PATH, false); assertTrue(result); } diff --git a/marketplace-ui/src/app/modules/product/product.service.spec.ts b/marketplace-ui/src/app/modules/product/product.service.spec.ts index 4bd6c7c15..ab17ec697 100644 --- a/marketplace-ui/src/app/modules/product/product.service.spec.ts +++ b/marketplace-ui/src/app/modules/product/product.service.spec.ts @@ -222,7 +222,7 @@ describe('ProductService', () => { expect(response).toBe(3); }); - const req = httpMock.expectOne(`${API_URI.PRODUCT_DETAILS}/installationcount/${productId}?designerVersion=${designerVersion}`); + const req = httpMock.expectOne(`${API_URI.PRODUCT_MARKETPLACE_DATA}/installation-count/${productId}?designerVersion=${designerVersion}`); expect(req.request.method).toBe('PUT'); req.flush(3); }); diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index addb2af84..22ee095b1 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -82,7 +82,7 @@ export class ProductService { } sendRequestToUpdateInstallationCount(productId: string, designerVersion: string) { - const url = `${API_URI.PRODUCT_DETAILS}/installationcount/${productId}`; + const url = `${API_URI.PRODUCT_MARKETPLACE_DATA}/installation-count/${productId}`; const params = new HttpParams().append('designerVersion', designerVersion); return this.httpClient.put(url, null, { params }); } diff --git a/marketplace-ui/src/app/shared/constants/api.constant.ts b/marketplace-ui/src/app/shared/constants/api.constant.ts index 2f6527b59..41842a7fa 100644 --- a/marketplace-ui/src/app/shared/constants/api.constant.ts +++ b/marketplace-ui/src/app/shared/constants/api.constant.ts @@ -1,4 +1,3 @@ - const API = 'api'; export const API_URI = { @@ -7,4 +6,5 @@ export const API_URI = { PRODUCT_DETAILS: `${API}/product-details`, EXTERNAL_DOCUMENT: `${API}/externaldocument`, FEEDBACK: `${API}/feedback`, -} \ No newline at end of file + PRODUCT_MARKETPLACE_DATA: `${API}/product-marketplace-data` +};