diff --git a/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductDetailModelAssembler.java b/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductDetailModelAssembler.java index 726b40dca..9c3747d9a 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductDetailModelAssembler.java +++ b/marketplace-service/src/main/java/com/axonivy/market/assembler/ProductDetailModelAssembler.java @@ -1,18 +1,15 @@ package com.axonivy.market.assembler; -import com.axonivy.market.constants.CommonConstants; -import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.constants.RequestMappingConstants; import com.axonivy.market.controller.ProductDetailsController; import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductModuleContent; -import com.axonivy.market.enums.NonStandardProduct; import com.axonivy.market.model.ProductDetailModel; import org.apache.commons.lang3.StringUtils; import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; -import java.util.List; import java.util.Optional; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @@ -30,32 +27,36 @@ public ProductDetailModelAssembler(ProductModelAssembler productModelAssembler) @Override public ProductDetailModel toModel(Product product) { - return createModel(product, StringUtils.EMPTY); + return createModel(product, StringUtils.EMPTY, StringUtils.EMPTY); } - public ProductDetailModel toModel(Product product, String version) { - String productId = Optional.ofNullable(product).map(Product::getId).orElse(StringUtils.EMPTY); - return createModel(product, convertVersionToTag(productId, version)); + public ProductDetailModel toModel(Product product, String requestPath) { + return createModel(product, StringUtils.EMPTY, requestPath); } - private ProductDetailModel createModel(Product product, String tag) { - if (product == null) { - return new ProductDetailModel(); - } + public ProductDetailModel toModel(Product product, String version, String requestPath) { + return createModel(product, version, requestPath); + } + + private ProductDetailModel createModel(Product product, String version, String requestPath) { ResponseEntity selfLinkWithTag; ProductDetailModel model = instantiateModel(product); productModelAssembler.createResource(model, product); - if (StringUtils.isBlank(tag)) { - selfLinkWithTag = methodOn(ProductDetailsController.class).findProductDetails(product.getId()); - } else { - selfLinkWithTag = methodOn(ProductDetailsController.class).findProductDetailsByVersion(product.getId(), tag); - } + String productId = Optional.of(product).map(Product::getId).orElse(StringUtils.EMPTY); + selfLinkWithTag = switch (requestPath) { + case RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION -> + methodOn(ProductDetailsController.class).findBestMatchProductDetailsByVersion(productId, version); + case RequestMappingConstants.BY_ID_AND_VERSION -> + methodOn(ProductDetailsController.class).findProductDetailsByVersion(productId, version); + default -> + methodOn(ProductDetailsController.class).findProductDetails(productId); + }; model.add(linkTo(selfLinkWithTag).withSelfRel()); - createDetailResource(model, product, tag); + createDetailResource(model, product); return model; } - private void createDetailResource(ProductDetailModel model, Product product, String tag) { + private void createDetailResource(ProductDetailModel model, Product product) { model.setVendor(product.getVendor()); model.setNewestReleaseVersion(product.getNewestReleaseVersion()); model.setPlatformReview(product.getPlatformReview()); @@ -67,28 +68,6 @@ private void createDetailResource(ProductDetailModel model, Product product, Str model.setContactUs(product.getContactUs()); model.setCost(product.getCost()); model.setInstallationCount(product.getInstallationCount()); - - if (StringUtils.isBlank(tag) && StringUtils.isNotBlank(product.getNewestReleaseVersion())) { - tag = product.getNewestReleaseVersion(); - } - ProductModuleContent content = getProductModuleContentByTag(product.getProductModuleContents(), tag); - model.setProductModuleContent(content); - } - - private ProductModuleContent getProductModuleContentByTag(List contents, String tag) { - return contents.stream().filter(content -> StringUtils.equals(content.getTag(), tag)).findAny().orElse(null); - } - - public String convertVersionToTag(String productId, String version) { - if (StringUtils.isBlank(version)) { - return version; - } - String[] versionParts = version.split(CommonConstants.SPACE_SEPARATOR); - String versionNumber = versionParts[versionParts.length - 1]; - NonStandardProduct product = NonStandardProduct.findById(productId); - if (product.isVersionTagNumberOnly()) { - return versionNumber; - } - return GitHubConstants.STANDARD_TAG_PREFIX.concat(versionNumber); + model.setProductModuleContent(CollectionUtils.firstElement(product.getProductModuleContents())); } } 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 new file mode 100644 index 000000000..2915af56a --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/MongoDBConstants.java @@ -0,0 +1,20 @@ +package com.axonivy.market.constants; + +public class MongoDBConstants { + private MongoDBConstants() { + } + + public static final String ID ="_id"; + public static final String ADD_FIELD ="$addFields"; + public static final String PRODUCT_MODULE_CONTENTS ="productModuleContents"; + public static final String PRODUCT_MODULE_CONTENT ="productModuleContent"; + public static final String PRODUCT_MODULE_CONTENT_QUERY ="$productModuleContents"; + public static final String FILTER ="$filter"; + public static final String INPUT ="input"; + public static final String AS ="as"; + public static final String CONDITION ="cond"; + public static final String EQUAL ="$eq"; + public static final String PRODUCT_MODULE_CONTENT_TAG ="$$productModuleContent.tag"; + public static final String PRODUCT_COLLECTION ="Product"; + public static final String NEWEST_RELEASED_VERSION_QUERY = "$newestReleaseVersion"; +} 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 42f466de4..5cf0a1170 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 @@ -15,7 +15,8 @@ public class RequestMappingConstants { public static final String GIT_HUB_LOGIN = "/github/login"; public static final String AUTH = "/auth"; public static final String BY_ID = "/{id}"; - public static final String BY_ID_AND_TAG = "/{id}/{tag}"; + public static final String BY_ID_AND_VERSION = "/{id}/{version}"; + public static final String BEST_MATCH_BY_ID_AND_VERSION = "/{id}/{version}/bestmatch"; 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"; diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestParamConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestParamConstants.java index 0ce0935b6..46b2b3fcf 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestParamConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestParamConstants.java @@ -16,4 +16,5 @@ public class RequestParamConstants { public static final String RESET_SYNC = "resetSync"; public static final String SHOW_DEV_VERSION = "isShowDevVersion"; public static final String DESIGNER_VERSION = "designerVersion"; + public static final String VERSION = "version"; } 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 6242e5589..a50230331 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 @@ -1,14 +1,16 @@ package com.axonivy.market.controller; import static com.axonivy.market.constants.RequestMappingConstants.BY_ID; -import static com.axonivy.market.constants.RequestMappingConstants.BY_ID_AND_TAG; +import static com.axonivy.market.constants.RequestMappingConstants.BY_ID_AND_VERSION; import static com.axonivy.market.constants.RequestMappingConstants.INSTALLATION_COUNT_BY_ID; import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DETAILS; import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_BY_ID; +import static com.axonivy.market.constants.RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION; import static com.axonivy.market.constants.RequestParamConstants.DESIGNER_VERSION; import static com.axonivy.market.constants.RequestParamConstants.ID; import static com.axonivy.market.constants.RequestParamConstants.SHOW_DEV_VERSION; -import static com.axonivy.market.constants.RequestParamConstants.TAG; +import static com.axonivy.market.constants.RequestParamConstants.VERSION; + import java.util.List; @@ -47,20 +49,29 @@ public ProductDetailsController(VersionService versionService, ProductService pr this.detailModelAssembler = detailModelAssembler; } - @GetMapping(BY_ID_AND_TAG) - @Operation(summary = "Find product detail by product id and release tag.", description = "get product detail by it product id and release tag") + @GetMapping(BY_ID_AND_VERSION) + @Operation(summary = "Find product detail by product id and release version.", description = "get product detail by it product id and release version") public ResponseEntity findProductDetailsByVersion( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String id, - @PathVariable(TAG) @Parameter(description = "Release tag (from git hub repo tags)", example = "v10.0.20", in = ParameterIn.PATH) String tag) { - var productDetail = productService.fetchProductDetail(id); - return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, tag), HttpStatus.OK); + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id, + @PathVariable(VERSION) @Parameter(description = "Release version (from maven metadata.xml)", example = "10.0.20", in = ParameterIn.PATH) String version) { + var productDetail = productService.fetchProductDetailByIdAndVersion(id, version); + return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BY_ID_AND_VERSION), HttpStatus.OK); + } + + @GetMapping(BEST_MATCH_BY_ID_AND_VERSION) + @Operation(summary = "Find best match product detail by product id and version.", description = "get product detail by it product id and version") + public ResponseEntity findBestMatchProductDetailsByVersion( + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id, + @PathVariable(VERSION) @Parameter(description = "Version", example = "10.0.20", in = ParameterIn.PATH) String version) { + var productDetail = productService.fetchBestMatchProductDetail(id,version); + return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BEST_MATCH_BY_ID_AND_VERSION), HttpStatus.OK); } @CrossOrigin(originPatterns = "*") @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 = "adobe-acrobat-connector", in = ParameterIn.PATH) String productId) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String productId) { int result = productService.updateInstallationCountForProduct(productId); return new ResponseEntity<>(result, HttpStatus.OK); } @@ -68,9 +79,9 @@ public ResponseEntity syncInstallationCount( @GetMapping(BY_ID) @Operation(summary = "increase installation count by 1", description = "update installation count when click download product files by users") public ResponseEntity findProductDetails( - @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String id) { + @PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id) { var productDetail = productService.fetchProductDetail(id); - return new ResponseEntity<>(detailModelAssembler.toModel(productDetail), HttpStatus.OK); + return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, BY_ID), HttpStatus.OK); } @GetMapping(VERSIONS_BY_ID) 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 b567a353c..6cd687032 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 @@ -60,6 +60,7 @@ public class Product implements Serializable { private List artifacts; private Boolean synchronizedInstallationCount; private Integer customOrder; + private List releasedVersions; @Override public int hashCode() { 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 224de4e86..e6f5b7580 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 @@ -14,6 +14,7 @@ import org.springframework.util.CollectionUtils; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -73,6 +74,7 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent product.setCompatibility(meta.getCompatibility()); extractSourceUrl(product, meta); product.setArtifacts(meta.getMavenArtifacts()); + product.setReleasedVersions(new ArrayList<>()); return product; } 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 new file mode 100644 index 000000000..3cd980656 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/CustomProductRepository.java @@ -0,0 +1,17 @@ +package com.axonivy.market.repository; + +import com.axonivy.market.entity.Product; + +import java.util.List; + +public interface CustomProductRepository { + Product getProductByIdAndTag(String id, String tag); + + Product getProductById(String id); + + List getReleasedVersionsById(String id); + + int updateInitialCount(String productId, int initialCount); + + int increaseInstallationCount(String productId); +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java index fef338ef6..c138cadf4 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductRepository.java @@ -6,7 +6,7 @@ import com.axonivy.market.entity.Product; @Repository -public interface ProductRepository extends MongoRepository, ProductSearchRepository { +public interface ProductRepository extends MongoRepository, ProductSearchRepository, CustomProductRepository { Product findByLogoUrl(String logoUrl); 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 new file mode 100644 index 000000000..c43502185 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/CustomProductRepositoryImpl.java @@ -0,0 +1,102 @@ +package com.axonivy.market.repository.impl; + +import com.axonivy.market.constants.MongoDBConstants; +import com.axonivy.market.entity.Product; +import com.axonivy.market.repository.CustomProductRepository; +import org.bson.Document; +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.AggregationOperation; +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 java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + + +public class CustomProductRepositoryImpl implements CustomProductRepository { + private final MongoTemplate mongoTemplate; + + public CustomProductRepositoryImpl(MongoTemplate mongoTemplate) { + this.mongoTemplate = mongoTemplate; + } + + private AggregationOperation createIdMatchOperation(String id) { + return Aggregation.match(Criteria.where(MongoDBConstants.ID).is(id)); + } + + public Document createDocumentFilterProductModuleContentByTag(String tag) { + Document isProductModuleContentOfCurrentTag = new Document(MongoDBConstants.EQUAL, + Arrays.asList(MongoDBConstants.PRODUCT_MODULE_CONTENT_TAG, tag)); + Document loopOverProductModuleContents = new Document(MongoDBConstants.INPUT, + MongoDBConstants.PRODUCT_MODULE_CONTENT_QUERY) + .append(MongoDBConstants.AS, MongoDBConstants.PRODUCT_MODULE_CONTENT); + return loopOverProductModuleContents.append(MongoDBConstants.CONDITION, isProductModuleContentOfCurrentTag); + } + + private AggregationOperation createReturnFirstModuleContentOperation() { + return context -> new Document(MongoDBConstants.ADD_FIELD, + new Document(MongoDBConstants.PRODUCT_MODULE_CONTENTS, + new Document(MongoDBConstants.FILTER, createDocumentFilterProductModuleContentByTag(MongoDBConstants.NEWEST_RELEASED_VERSION_QUERY)))); + } + + private AggregationOperation createReturnFirstMatchTagModuleContentOperation(String tag) { + return context -> new Document(MongoDBConstants.ADD_FIELD, + new Document(MongoDBConstants.PRODUCT_MODULE_CONTENTS, + new Document(MongoDBConstants.FILTER, createDocumentFilterProductModuleContentByTag(tag)))); + } + + public Product queryProductByAggregation(Aggregation aggregation) { + return Optional.of(mongoTemplate.aggregate(aggregation, MongoDBConstants.PRODUCT_COLLECTION, Product.class)) + .map(AggregationResults::getUniqueMappedResult).orElse(null); + } + + @Override + public Product getProductByIdAndTag(String id, String tag) { + // Create the aggregation pipeline + Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id), createReturnFirstMatchTagModuleContentOperation(tag)); + return queryProductByAggregation(aggregation); + } + + @Override + public Product getProductById(String id) { + Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id), createReturnFirstModuleContentOperation()); + return queryProductByAggregation(aggregation); + } + + @Override + public List getReleasedVersionsById(String id) { + Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id)); + Product product = queryProductByAggregation(aggregation); + if (Objects.isNull(product)) { + return Collections.emptyList(); + } + return product.getReleasedVersions(); + + } + + public int updateInitialCount(String productId, int initialCount) { + Update update = new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true); + mongoTemplate.updateFirst(createQueryById(productId), update, Product.class); + return Optional.ofNullable(getProductById(productId)).map(Product::getInstallationCount).orElse(0); + } + + @Override + public int increaseInstallationCount(String productId) { + Update update = new Update().inc("InstallationCount", 1); + // Find and modify the document, then return the updated InstallationCount field + Product updatedProduct = mongoTemplate.findAndModify(createQueryById(productId), update, + FindAndModifyOptions.options().returnNew(true), Product.class); + return updatedProduct != null ? updatedProduct.getInstallationCount() : 0; + } + + private Query createQueryById(String id) { + return new Query(Criteria.where(MongoDBConstants.ID).is(id)); + } +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java index 68a86856f..538f8f447 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImpl.java @@ -34,6 +34,7 @@ public ProductSearchRepositoryImpl(MongoTemplate mongoTemplate) { this.mongoTemplate = mongoTemplate; } + @Override public Page searchByCriteria(ProductSearchCriteria searchCriteria, Pageable pageable) { return getResultAsPageable(pageable, buildCriteriaSearch(searchCriteria)); 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 5a6581a90..f63495616 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 @@ -20,4 +20,9 @@ public interface ProductService { void clearAllProducts(); 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/VersionService.java b/marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java index 19755be9c..66d0d6661 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/VersionService.java @@ -6,8 +6,6 @@ public interface VersionService { - List getVersionsToDisplay(Boolean isShowDevVersion, String designerVersion); - List getVersionsFromArtifactDetails(String repoUrl, String groupId, String artifactId); String buildMavenMetadataUrlFromArtifact(String repoUrl, String groupId, String artifactId); 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 6a21963ec..6c57569ae 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 @@ -11,15 +11,14 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.security.SecureRandom; - import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Objects; - +import java.util.Optional; +import com.axonivy.market.util.VersionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -143,17 +142,19 @@ public boolean syncLatestDataFromMarketRepo() { @Override public int updateInstallationCountForProduct(String key) { - return productRepository.findById(key).map(product -> { - log.info("updating installation count for product {}", key); - if (!BooleanUtils.isTrue(product.getSynchronizedInstallationCount())) { - syncInstallationCountWithProduct(product); - } - product.setInstallationCount(product.getInstallationCount() + 1); - return productRepository.save(product); - }).map(Product::getInstallationCount).orElse(0); + Product product= productRepository.getProductById(key); + if (Objects.isNull(product)){ + return 0; + } + log.info("updating installation count for product {}", key); + if (BooleanUtils.isTrue(product.getSynchronizedInstallationCount())) { + return productRepository.increaseInstallationCount(key); + } + syncInstallationCountWithProduct(product); + return productRepository.updateInitialCount(key, product.getInstallationCount() + 1); } - private void syncInstallationCountWithProduct(Product product) { + public void syncInstallationCountWithProduct(Product product) { log.info("synchronizing installation count for product {}", product.getId()); try { String installationCounts = Files.readString(Paths.get(installationCountPath)); @@ -370,6 +371,11 @@ private void updateProductFromReleaseTags(Product product, GHRepository productR ProductModuleContent productModuleContent = axonIvyProductRepoService.getReadmeAndProductContentsFromTag(product, productRepo, ghTag.getName()); productModuleContents.add(productModuleContent); + String versionFromTag = VersionUtils.convertTagToVersion(ghTag.getName()); + if (Objects.isNull(product.getReleasedVersions())) { + product.setReleasedVersions(new ArrayList<>()); + } + product.getReleasedVersions().add(versionFromTag); } product.setProductModuleContents(productModuleContents); } @@ -424,16 +430,39 @@ public String getCompatibilityFromOldestTag(String oldestTag) { @Override public Product fetchProductDetail(String id) { - Product product = productRepository.findById(id).orElse(null); + Product product = productRepository.getProductById(id); return Optional.ofNullable(product).map(productItem -> { - if (!BooleanUtils.isTrue(productItem.getSynchronizedInstallationCount())) { - syncInstallationCountWithProduct(productItem); - return productRepository.save(productItem); - } + updateProductInstallationCount(id, productItem); + return productItem; + }).orElse(null); + } + + + @Override + public Product fetchBestMatchProductDetail(String id, String version) { + List releasedVersions = productRepository.getReleasedVersionsById(id); + String bestMatchVersion = VersionUtils.getBestMatchVersion(releasedVersions, version); + String bestMatchTag = VersionUtils.convertVersionToTag(id,bestMatchVersion); + Product product = StringUtils.isBlank(bestMatchTag) ? productRepository.getProductById(id) : productRepository.getProductByIdAndTag(id, bestMatchTag); + return Optional.ofNullable(product).map(productItem -> { + updateProductInstallationCount(id, productItem); return productItem; }).orElse(null); } + public void updateProductInstallationCount(String id, Product productItem) { + if (!BooleanUtils.isTrue(productItem.getSynchronizedInstallationCount())) { + syncInstallationCountWithProduct(productItem); + int persistedInitialCount = productRepository.updateInitialCount(id, productItem.getInstallationCount()); + productItem.setInstallationCount(persistedInitialCount); + } + } + + @Override + public Product fetchProductDetailByIdAndVersion(String id, String version) { + return productRepository.getProductByIdAndTag(id, VersionUtils.convertVersionToTag(id, version)); + } + @Override public void clearAllProducts() { gitHubRepoMetaRepository.deleteAll(); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java index 75c28b2fe..2aa875ddb 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/VersionServiceImpl.java @@ -17,10 +17,10 @@ import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.VersionService; +import com.axonivy.market.util.VersionUtils; import com.axonivy.market.util.XmlReaderUtils; import lombok.Getter; import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.GHContent; import org.springframework.stereotype.Service; @@ -36,7 +36,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; @Log4j2 @Service @@ -82,7 +81,7 @@ public List getArtifactsAndVersionToDisplay(String pr this.productId = productId; artifactsFromMeta = getProductMetaArtifacts(productId); - List versionsToDisplay = getVersionsToDisplay(isShowDevVersion, designerVersion); + List versionsToDisplay = VersionUtils.getVersionsToDisplay(getVersionsFromMavenArtifacts(), isShowDevVersion, designerVersion); proceedDataCache = mavenArtifactVersionRepository.findById(productId).orElse(new MavenArtifactVersion(productId)); metaProductArtifact = artifactsFromMeta.stream() .filter(artifact -> artifact.getArtifactId().endsWith(MavenConstants.PRODUCT_ARTIFACT_POSTFIX)).findAny() @@ -142,20 +141,6 @@ public void sanitizeMetaArtifactBeforeHandle() { }); } - @Override - public List getVersionsToDisplay(Boolean isShowDevVersion, String designerVersion) { - List versions = getVersionsFromMavenArtifacts(); - Stream versionStream = versions.stream(); - if (BooleanUtils.isTrue(isShowDevVersion)) { - return versionStream.filter(version -> isOfficialVersionOrUnReleasedDevVersion(versions, version)) - .sorted(new LatestVersionComparator()).toList(); - } - if (StringUtils.isNotBlank(designerVersion)) { - return versionStream.filter(version -> isMatchWithDesignerVersion(version, designerVersion)).toList(); - } - return versions.stream().filter(this::isReleasedVersion).sorted(new LatestVersionComparator()).toList(); - } - public List getVersionsFromMavenArtifacts() { Set versions = new HashSet<>(); for (MavenArtifact artifact : artifactsFromMeta) { @@ -191,52 +176,6 @@ public String buildMavenMetadataUrlFromArtifact(String repoUrl, String groupId, return String.format(MavenConstants.METADATA_URL_FORMAT, repoUrl, groupId, artifactID); } - public String getBugfixVersion(String version) { - - if (isSnapshotVersion(version)) { - version = version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY); - } else if (isSprintVersion(version)) { - version = version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]; - } - String[] segments = version.split("\\."); - if (segments.length >= 3) { - segments[2] = segments[2].split(CommonConstants.DASH_SEPARATOR)[0]; - return segments[0] + CommonConstants.DOT_SEPARATOR + segments[1] + CommonConstants.DOT_SEPARATOR + segments[2]; - } - return version; - } - - public boolean isOfficialVersionOrUnReleasedDevVersion(List versions, String version) { - if (isReleasedVersion(version)) { - return true; - } - String bugfixVersion; - if (isSnapshotVersion(version)) { - bugfixVersion = getBugfixVersion(version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY)); - } else { - bugfixVersion = getBugfixVersion(version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]); - } - return versions.stream().noneMatch( - currentVersion -> !currentVersion.equals(version) && isReleasedVersion(currentVersion) && getBugfixVersion( - currentVersion).equals(bugfixVersion)); - } - - public boolean isSnapshotVersion(String version) { - return version.endsWith(MavenConstants.SNAPSHOT_RELEASE_POSTFIX); - } - - public boolean isSprintVersion(String version) { - return version.contains(MavenConstants.SPRINT_RELEASE_POSTFIX); - } - - public boolean isReleasedVersion(String version) { - return !(isSprintVersion(version) || isSnapshotVersion(version)); - } - - public boolean isMatchWithDesignerVersion(String version, String designerVersion) { - return isReleasedVersion(version) && version.startsWith(designerVersion); - } - public List getProductJsonByVersion(String version) { List result = new ArrayList<>(); String versionTag = getVersionTag(version); diff --git a/marketplace-service/src/main/java/com/axonivy/market/util/VersionUtils.java b/marketplace-service/src/main/java/com/axonivy/market/util/VersionUtils.java new file mode 100644 index 000000000..ebfac0d7a --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/util/VersionUtils.java @@ -0,0 +1,112 @@ +package com.axonivy.market.util; + +import com.axonivy.market.comparator.LatestVersionComparator; +import com.axonivy.market.constants.CommonConstants; +import com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.constants.MavenConstants; +import com.axonivy.market.enums.NonStandardProduct; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public class VersionUtils { + private VersionUtils() { + } + public static List getVersionsToDisplay(List versions, Boolean isShowDevVersion, String designerVersion) { + Stream versionStream = versions.stream(); + if (StringUtils.isNotBlank(designerVersion)) { + return versionStream.filter(version -> isMatchWithDesignerVersion(version, designerVersion)).toList(); + } + if (BooleanUtils.isTrue(isShowDevVersion)) { + return versionStream.filter(version -> isOfficialVersionOrUnReleasedDevVersion(versions, version)) + .sorted(new LatestVersionComparator()).toList(); + } + return versions.stream().filter(VersionUtils::isReleasedVersion).sorted(new LatestVersionComparator()).toList(); + } + + public static String getBestMatchVersion(List versions, String designerVersion) { + String bestMatchVersion = versions.stream().filter(version -> StringUtils.equals(version, designerVersion)).findAny().orElse(null); + if(StringUtils.isBlank(bestMatchVersion)){ + LatestVersionComparator comparator = new LatestVersionComparator(); + bestMatchVersion = versions.stream().filter(version -> comparator.compare(version, designerVersion) > 0 && isReleasedVersion(version)).findAny().orElse(null); + } + if (StringUtils.isBlank(bestMatchVersion)) { + bestMatchVersion = versions.stream().filter(VersionUtils::isReleasedVersion).findAny().orElse(CollectionUtils.firstElement(versions)); + } + return bestMatchVersion; + } + + public static boolean isOfficialVersionOrUnReleasedDevVersion(List versions, String version) { + if (isReleasedVersion(version)) { + return true; + } + String bugfixVersion; + if (isSnapshotVersion(version)) { + bugfixVersion = getBugfixVersion(version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY)); + } else { + bugfixVersion = getBugfixVersion(version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]); + } + return versions.stream().noneMatch( + currentVersion -> !currentVersion.equals(version) && isReleasedVersion(currentVersion) && getBugfixVersion( + currentVersion).equals(bugfixVersion)); + } + + public static boolean isSnapshotVersion(String version) { + return version.endsWith(MavenConstants.SNAPSHOT_RELEASE_POSTFIX); + } + + public static boolean isSprintVersion(String version) { + return version.contains(MavenConstants.SPRINT_RELEASE_POSTFIX); + } + + public static boolean isReleasedVersion(String version) { + return !(isSprintVersion(version) || isSnapshotVersion(version)); + } + + public static boolean isMatchWithDesignerVersion(String version, String designerVersion) { + return isReleasedVersion(version) && version.startsWith(designerVersion); + } + + public static String getBugfixVersion(String version) { + + if (isSnapshotVersion(version)) { + version = version.replace(MavenConstants.SNAPSHOT_RELEASE_POSTFIX, StringUtils.EMPTY); + } else if (isSprintVersion(version)) { + version = version.split(MavenConstants.SPRINT_RELEASE_POSTFIX)[0]; + } + String[] segments = version.split("\\."); + if (segments.length >= 3) { + segments[2] = segments[2].split(CommonConstants.DASH_SEPARATOR)[0]; + return segments[0] + CommonConstants.DOT_SEPARATOR + segments[1] + CommonConstants.DOT_SEPARATOR + segments[2]; + } + return version; + } + + public static String convertTagToVersion (String tag){ + if(StringUtils.isBlank(tag) || !StringUtils.startsWith(tag, GitHubConstants.STANDARD_TAG_PREFIX)){ + return tag; + } + return tag.substring(1); + } + + public static List convertTagsToVersions (List tags){ + Objects.requireNonNull(tags); + return tags.stream().map(VersionUtils::convertTagToVersion).toList(); + } + + public static String convertVersionToTag(String productId, String version) { + if (StringUtils.isBlank(version)) { + return version; + } + NonStandardProduct product = NonStandardProduct.findById(productId); + if (product.isVersionTagNumberOnly()) { + return version; + } + return GitHubConstants.STANDARD_TAG_PREFIX.concat(version); + } + +} diff --git a/marketplace-service/src/test/java/com/axonivy/market/assembler/ProductDetailModelAssemblerTest.java b/marketplace-service/src/test/java/com/axonivy/market/assembler/ProductDetailModelAssemblerTest.java index 7c1a8dc3d..8e99449fa 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/assembler/ProductDetailModelAssemblerTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/assembler/ProductDetailModelAssemblerTest.java @@ -1,36 +1,55 @@ package com.axonivy.market.assembler; -import com.axonivy.market.enums.NonStandardProduct; -import org.apache.commons.lang3.StringUtils; +import com.axonivy.market.constants.RequestMappingConstants; +import com.axonivy.market.entity.Product; +import com.axonivy.market.model.ProductDetailModel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; -import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(SpringExtension.class) +@ExtendWith(MockitoExtension.class) class ProductDetailModelAssemblerTest { + private static final String ID = "portal"; + private static final String VERSION = "10.0.19"; + private static final String SELF_RELATION = "self"; + + Product mockProduct; @InjectMocks - private ProductDetailModelAssembler assembler; + private ProductDetailModelAssembler productDetailModelAssembler; @BeforeEach void setup() { - assembler = new ProductDetailModelAssembler(new ProductModelAssembler()); + productDetailModelAssembler = new ProductDetailModelAssembler(new ProductModelAssembler()); + mockProduct = new Product(); + mockProduct.setId(ID); } @Test - void testConvertVersionToTag() { - - String rawVersion = StringUtils.EMPTY; - Assertions.assertEquals(rawVersion, assembler.convertVersionToTag(StringUtils.EMPTY, rawVersion)); + void testToModel() { + ProductDetailModel model = productDetailModelAssembler.toModel(mockProduct); + Assertions.assertEquals(ID, model.getId()); + Assertions.assertFalse(model.getLinks().isEmpty()); + Assertions.assertTrue(model.getLink(SELF_RELATION).get().getHref().endsWith("/api/product-details/portal")); + } - rawVersion = "Version 11.0.0"; - String targetVersion = "11.0.0"; - Assertions.assertEquals(targetVersion, assembler.convertVersionToTag(NonStandardProduct.PORTAL.getId(), rawVersion)); + @Test + void testToModelWithRequestPath() { + ProductDetailModel model = productDetailModelAssembler.toModel(mockProduct, RequestMappingConstants.BY_ID); + Assertions.assertTrue(model.getLink(SELF_RELATION).get().getHref().endsWith("/api/product-details/portal")); + } - targetVersion = "v11.0.0"; - Assertions.assertEquals(targetVersion, assembler.convertVersionToTag(NonStandardProduct.GRAPHQL_DEMO.getId(), rawVersion)); + @Test + void testToModelWithRequestPathAndVersion() { + ProductDetailModel model = productDetailModelAssembler.toModel(mockProduct, VERSION, RequestMappingConstants.BY_ID_AND_VERSION); + Assertions.assertTrue(model.getLink(SELF_RELATION).get().getHref().endsWith("/api/product-details/portal/10.0.19")); + } + + @Test + void testToModelWithRequestPathAndBestMatchVersion() { + ProductDetailModel model = productDetailModelAssembler.toModel(mockProduct, VERSION, RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION); + Assertions.assertTrue(model.getLink(SELF_RELATION).get().getHref().endsWith("/api/product-details/portal/10.0.19/bestmatch")); } -} +} \ No newline at end of file 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 7518fc918..686e291f4 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 @@ -1,6 +1,7 @@ package com.axonivy.market.controller; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -11,6 +12,7 @@ import java.util.HashMap; import java.util.Map; +import com.axonivy.market.constants.RequestMappingConstants; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -50,7 +52,7 @@ class ProductDetailsControllerTest { @Test void testProductDetails() { Mockito.when(productService.fetchProductDetail(Mockito.anyString())).thenReturn(mockProduct()); - Mockito.when(detailModelAssembler.toModel(mockProduct())).thenReturn(createProductMockWithDetails()); + Mockito.when(detailModelAssembler.toModel(mockProduct(), RequestMappingConstants.BY_ID)).thenReturn(createProductMockWithDetails()); ResponseEntity mockExpectedResult = new ResponseEntity<>(createProductMockWithDetails(), HttpStatus.OK); @@ -60,13 +62,32 @@ void testProductDetails() { assertEquals(result, mockExpectedResult); verify(productService, times(1)).fetchProductDetail(DOCKER_CONNECTOR_ID); - verify(detailModelAssembler, times(1)).toModel(mockProduct()); + verify(detailModelAssembler, times(1)).toModel(mockProduct(), RequestMappingConstants.BY_ID); + assertTrue(result.hasBody()); + assertEquals(DOCKER_CONNECTOR_ID, Objects.requireNonNull(result.getBody()).getId()); + } + + + @Test + void testFindBestMatchProductDetailsByVersion() { + Mockito.when(productService.fetchBestMatchProductDetail(Mockito.anyString(), Mockito.anyString())).thenReturn(mockProduct()); + Mockito.when(detailModelAssembler.toModel(mockProduct(), TAG, RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION)).thenReturn(createProductMockWithDetails()); + ResponseEntity mockExpectedResult = new ResponseEntity<>(createProductMockWithDetails(), + HttpStatus.OK); + + ResponseEntity result = productDetailsController.findBestMatchProductDetailsByVersion(DOCKER_CONNECTOR_ID, TAG); + + assertEquals(HttpStatus.OK, result.getStatusCode()); + assertEquals(result, mockExpectedResult); + + verify(productService, times(1)).fetchBestMatchProductDetail(DOCKER_CONNECTOR_ID, TAG); + verify(detailModelAssembler, times(1)).toModel(mockProduct(), TAG, RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION); } @Test void testProductDetailsWithVersion() { - Mockito.when(productService.fetchProductDetail(Mockito.anyString())).thenReturn(mockProduct()); - Mockito.when(detailModelAssembler.toModel(mockProduct(), TAG)).thenReturn(createProductMockWithDetails()); + Mockito.when(productService.fetchProductDetailByIdAndVersion(Mockito.anyString(), Mockito.anyString())).thenReturn(mockProduct()); + Mockito.when(detailModelAssembler.toModel(mockProduct(), TAG, RequestMappingConstants.BY_ID_AND_VERSION)).thenReturn(createProductMockWithDetails()); ResponseEntity mockExpectedResult = new ResponseEntity<>(createProductMockWithDetails(), HttpStatus.OK); @@ -76,7 +97,7 @@ void testProductDetailsWithVersion() { assertEquals(HttpStatus.OK, result.getStatusCode()); assertEquals(result, mockExpectedResult); - verify(productService, times(1)).fetchProductDetail(DOCKER_CONNECTOR_ID); + verify(productService, times(1)).fetchProductDetailByIdAndVersion(DOCKER_CONNECTOR_ID, TAG); } @Test 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 new file mode 100644 index 000000000..671690f41 --- /dev/null +++ b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/CustomProductRepositoryImplTest.java @@ -0,0 +1,145 @@ +package com.axonivy.market.repository.impl; + +import com.axonivy.market.BaseSetup; +import com.axonivy.market.constants.MongoDBConstants; +import com.axonivy.market.entity.Product; +import org.bson.Document; +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.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.eq; + +@ExtendWith(MockitoExtension.class) +class CustomProductRepositoryImplTest extends BaseSetup { + private static final String ID = "bmpn-statistic"; + private static final String TAG = "v10.0.21"; + private Product mockProduct; + private Aggregation mockAggregation; + + @Mock + private MongoTemplate mongoTemplate; + + @InjectMocks + private CustomProductRepositoryImpl repo; + + @Test + void testQueryProductByAggregation_WhenResultIsPresent() { + setUpMockAggregateResult(); + Product actualProduct = repo.queryProductByAggregation(mockAggregation); + assertNotNull(actualProduct); + assertEquals(mockProduct, actualProduct); + } + + private void setUpMockAggregateResult() { + mockAggregation = mock(Aggregation.class); + AggregationResults aggregationResults = mock(AggregationResults.class); + + when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), eq(Product.class))).thenReturn(aggregationResults); + mockProduct = new Product(); + mockProduct.setId(ID); + when(aggregationResults.getUniqueMappedResult()).thenReturn(mockProduct); + } + + @Test + void testQueryProductByAggregation_WhenResultIsNull() { + Aggregation aggregation = mock(Aggregation.class); + AggregationResults aggregationResults = mock(AggregationResults.class); + when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), eq(Product.class))).thenReturn(aggregationResults); + when(aggregationResults.getUniqueMappedResult()).thenReturn(null); + + Product actualProduct = repo.queryProductByAggregation(aggregation); + + assertNull(actualProduct); + } + + @Test + void testReleasedVersionsById_WhenResultIsNull() { + AggregationResults aggregationResults = mock(AggregationResults.class); + + when(mongoTemplate.aggregate(any(Aggregation.class), eq(MongoDBConstants.PRODUCT_COLLECTION), eq(Product.class))).thenReturn(aggregationResults); + when(aggregationResults.getUniqueMappedResult()).thenReturn(null); + + List results = repo.getReleasedVersionsById(ID); + assertEquals(0, results.size()); + } + + @Test + void testGetProductById() { + setUpMockAggregateResult(); + Product actualProduct = repo.getProductById(ID); + assertEquals(mockProduct, actualProduct); + } + + @Test + void testGetProductByIdAndTag() { + setUpMockAggregateResult(); + Product actualProduct = repo.getProductByIdAndTag(ID, TAG); + assertEquals(mockProduct, actualProduct); + } + + @Test + void testCreateDocumentFilterProductModuleContentByTag() { + Document expectedCondition = new Document(MongoDBConstants.EQUAL, + Arrays.asList(MongoDBConstants.PRODUCT_MODULE_CONTENT_TAG, TAG)); + Document expectedLoop = new Document(MongoDBConstants.INPUT, MongoDBConstants.PRODUCT_MODULE_CONTENT_QUERY) + .append(MongoDBConstants.AS, MongoDBConstants.PRODUCT_MODULE_CONTENT) + .append(MongoDBConstants.CONDITION, expectedCondition); + + Document result = repo.createDocumentFilterProductModuleContentByTag(TAG); + + assertEquals(expectedLoop, result, "The created Document does not match the expected structure."); + } + + @Test + void testGetReleasedVersionsById() { + setUpMockAggregateResult(); + List actualReleasedVersions = repo.getReleasedVersionsById(ID); + assertEquals(mockProduct.getReleasedVersions(), actualReleasedVersions); + } + + @Test + void testIncreaseInstallationCount() { + String productId = "testProductId"; + Product product = new Product(); + product.setId(productId); + product.setInstallationCount(5); + when(mongoTemplate.findAndModify(any(Query.class), any(Update.class), any(FindAndModifyOptions.class), eq(Product.class))).thenReturn(product); + int updatedCount = repo.increaseInstallationCount(productId); + 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(ID); + assertEquals(0, updatedCount); + } + + @Test + void testUpdateInitialCount() { + setUpMockAggregateResult(); + int initialCount = 10; + repo.updateInitialCount(ID, initialCount); + verify(mongoTemplate).updateFirst(any(Query.class), eq(new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true)), eq(Product.class)); + } +} diff --git a/marketplace-service/src/test/java/com/axonivy/market/repository/ProductSearchRepositoryImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java similarity index 96% rename from marketplace-service/src/test/java/com/axonivy/market/repository/ProductSearchRepositoryImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java index 676b84d76..bdcfeb12e 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/repository/ProductSearchRepositoryImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/repository/impl/ProductSearchRepositoryImplTest.java @@ -1,4 +1,4 @@ -package com.axonivy.market.repository; +package com.axonivy.market.repository.impl; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -24,7 +24,6 @@ import com.axonivy.market.entity.Product; import com.axonivy.market.enums.DocumentField; import com.axonivy.market.enums.Language; -import com.axonivy.market.repository.impl.ProductSearchRepositoryImpl; @ExtendWith(MockitoExtension.class) class ProductSearchRepositoryImplTest extends BaseSetup { diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/FeedbackServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/FeedbackServiceImplTest.java similarity index 99% rename from marketplace-service/src/test/java/com/axonivy/market/service/FeedbackServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/FeedbackServiceImplTest.java index 2b53637f8..b2f6cfded 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/FeedbackServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/FeedbackServiceImplTest.java @@ -1,4 +1,4 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import com.axonivy.market.entity.Feedback; import com.axonivy.market.entity.Product; @@ -11,7 +11,6 @@ import com.axonivy.market.repository.FeedbackRepository; import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.repository.UserRepository; -import com.axonivy.market.service.impl.FeedbackServiceImpl; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyMarketRepoServiceImplTest.java similarity index 99% rename from marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyMarketRepoServiceImplTest.java index 3cf65bafe..fc3406146 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyMarketRepoServiceImplTest.java @@ -1,4 +1,4 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.github.service.impl.GHAxonIvyMarketRepoServiceImpl; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java similarity index 99% rename from marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java index cad800a8a..d4cc7d0d8 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java @@ -1,4 +1,4 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.ProductJsonConstants; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GitHubServiceImplTest.java similarity index 98% rename from marketplace-service/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/GitHubServiceImplTest.java index c50ace4f8..5dd3ce8b6 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GitHubServiceImplTest.java @@ -1,4 +1,4 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import com.axonivy.market.github.service.impl.GitHubServiceImpl; import org.junit.jupiter.api.BeforeEach; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/JwtServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/JwtServiceImplTest.java similarity index 96% rename from marketplace-service/src/test/java/com/axonivy/market/service/JwtServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/JwtServiceImplTest.java index d5c2b7457..923aae407 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/JwtServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/JwtServiceImplTest.java @@ -1,7 +1,6 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import com.axonivy.market.entity.User; -import com.axonivy.market.service.impl.JwtServiceImpl; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import org.junit.jupiter.api.BeforeEach; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java similarity index 86% rename from marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java index 9f2e99a82..28328b2e2 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java @@ -1,4 +1,4 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import static com.axonivy.market.constants.CommonConstants.LOGO_FILE; import static com.axonivy.market.constants.CommonConstants.SLASH; @@ -11,19 +11,18 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; + import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -32,7 +31,6 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.stream.Collectors; import com.axonivy.market.criteria.ProductSearchCriteria; import org.junit.jupiter.api.BeforeEach; @@ -46,8 +44,6 @@ import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -80,7 +76,6 @@ import com.axonivy.market.repository.GitHubRepoMetaRepository; import com.axonivy.market.repository.ProductCustomSortRepository; import com.axonivy.market.repository.ProductRepository; -import com.axonivy.market.service.impl.ProductServiceImpl; @ExtendWith(MockitoExtension.class) class ProductServiceImplTest extends BaseSetup { @@ -90,6 +85,8 @@ class ProductServiceImplTest extends BaseSetup { Sort.by(SortOption.ALPHABETICALLY.getOption()).descending()); private static final String SHA1_SAMPLE = "35baa89091b2452b77705da227f1a964ecabc6c8"; public static final String RELEASE_TAG = "v10.0.2"; + private static final String INSTALLATION_FILE_PATH = "src/test/resources/installationCount.json"; + private String keyword; private String language; private Page mockResultReturn; @@ -135,42 +132,41 @@ public void setup() { } @Test - void testUpdateInstallationCount() { - // prepare - Mockito.when(productRepository.findById("google-maps-connector")).thenReturn(Optional.of(mockProduct())); + void testUpdateInstallationCountForProduct() { + int result = productService.updateInstallationCountForProduct(null); + assertEquals(0, result); + + Product product = mockProduct(); + when(productRepository.getProductById(product.getId())).thenReturn(product); + when(productRepository.increaseInstallationCount(product.getId())).thenReturn(31); + result = productService.updateInstallationCountForProduct(product.getId()); + assertEquals(31,result); + } - // exercise - productService.updateInstallationCountForProduct("google-maps-connector"); + @Test + void testSyncInstallationCountWithNewProduct() { + Product product = new Product(); + product.setSynchronizedInstallationCount(null); + product.setId("portal"); + ReflectionTestUtils.setField(productService, "installationCountPath", INSTALLATION_FILE_PATH); - // Verify - verify(productRepository).save(argumentCaptor.capture()); - int updatedInstallationCount = argumentCaptor.getValue().getInstallationCount(); + productService.syncInstallationCountWithProduct(product); - assertEquals(1, updatedInstallationCount); - verify(productRepository, times(1)).findById(Mockito.anyString()); - verify(productRepository, times(1)).save(Mockito.any()); + assertTrue(product.getInstallationCount() >= 20 && product.getInstallationCount() <= 50); + assertTrue(product.getSynchronizedInstallationCount()); } @Test - void testSyncInstallationCountWithProduct() throws Exception { - // Mock data - ReflectionTestUtils.setField(productService, "installationCountPath", "path/to/installationCount.json"); + void testSyncInstallationCountWithProduct() { + ReflectionTestUtils.setField(productService, "installationCountPath", INSTALLATION_FILE_PATH); Product product = mockProduct(); product.setSynchronizedInstallationCount(false); - Mockito.when(productRepository.findById("google-maps-connector")).thenReturn(Optional.of(product)); - Mockito.when(productRepository.save(any())).thenReturn(product); - // Mock the behavior of Files.readString and ObjectMapper.readValue - String installationCounts = "{\"google-maps-connector\": 10}"; - try (MockedStatic filesMockedStatic = mockStatic(Files.class)) { - when(Files.readString(Paths.get("path/to/installationCount.json"))).thenReturn(installationCounts); - // Call the method - int result = productService.updateInstallationCountForProduct("google-maps-connector"); - - // Verify the results - assertEquals(11, result); - assertEquals(true, product.getSynchronizedInstallationCount()); - assertTrue(product.getSynchronizedInstallationCount()); - } + + productService.syncInstallationCountWithProduct(product); + + assertEquals(40, product.getInstallationCount()); + assertEquals(true, product.getSynchronizedInstallationCount()); + assertTrue(product.getSynchronizedInstallationCount()); } private Product mockProduct() { @@ -402,10 +398,32 @@ void testFetchProductDetail() { String id = "amazon-comprehend"; Product mockProduct = mockResultReturn.getContent().get(0); mockProduct.setSynchronizedInstallationCount(true); - when(productRepository.findById(id)).thenReturn(Optional.of(mockProduct)); + when(productRepository.getProductById(id)).thenReturn(mockProduct); Product result = productService.fetchProductDetail(id); assertEquals(mockProduct, result); - verify(productRepository, times(1)).findById(id); + verify(productRepository, times(1)).getProductById(id); + } + + @Test + void testFetchProductDetailByIdAndVersion() { + String id = "amazon-comprehend"; + Product mockProduct = mockResultReturn.getContent().get(0); + when(productRepository.getProductByIdAndTag(id, RELEASE_TAG)).thenReturn(mockProduct); + Product result = productService.fetchProductDetailByIdAndVersion(id, "10.0.2"); + assertEquals(mockProduct, result); + verify(productRepository, times(1)).getProductByIdAndTag(id, RELEASE_TAG); + } + + @Test + void testFetchBestMatchProductDetailByIdAndVersion() { + String id = "amazon-comprehend"; + Product mockProduct = mockResultReturn.getContent().get(0); + mockProduct.setSynchronizedInstallationCount(true); + when(productRepository.getReleasedVersionsById(id)).thenReturn(List.of("10.0.2", "10.0.1")); + when(productRepository.getProductByIdAndTag(id, RELEASE_TAG)).thenReturn(mockProduct); + Product result = productService.fetchBestMatchProductDetail(id, "10.0.2"); + assertEquals(mockProduct, result); + verify(productRepository, times(1)).getProductByIdAndTag(id, RELEASE_TAG); } @Test @@ -476,6 +494,20 @@ void testAddCustomSortProduct() throws InvalidParamException { assertEquals(1, capturedProducts.get(0).getCustomOrder()); } + @Test + void testUpdateProductInstallationCountWhenNotSynchronized() { + Product product = mockProduct(); + product.setSynchronizedInstallationCount(false); + String id = product.getId(); + ReflectionTestUtils.setField(productService, "installationCountPath", INSTALLATION_FILE_PATH); + + when(productRepository.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"); diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/SchedulingTasksTest.java similarity index 96% rename from marketplace-service/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/SchedulingTasksTest.java index 474c25ed1..04cf00ce3 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/SchedulingTasksTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/SchedulingTasksTest.java @@ -1,4 +1,4 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/UserServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/UserServiceImplTest.java similarity index 96% rename from marketplace-service/src/test/java/com/axonivy/market/service/UserServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/UserServiceImplTest.java index 475c4ef6e..389c87f3c 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/UserServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/UserServiceImplTest.java @@ -1,10 +1,9 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import com.axonivy.market.entity.User; import com.axonivy.market.enums.ErrorCode; import com.axonivy.market.exceptions.model.NotFoundException; import com.axonivy.market.repository.UserRepository; -import com.axonivy.market.service.impl.UserServiceImpl; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/VersionServiceImplTest.java similarity index 82% rename from marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java rename to marketplace-service/src/test/java/com/axonivy/market/service/impl/VersionServiceImplTest.java index a5e98a854..aa47d5e1d 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/VersionServiceImplTest.java @@ -1,4 +1,4 @@ -package com.axonivy.market.service; +package com.axonivy.market.service.impl; import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.entity.MavenArtifactModel; @@ -10,7 +10,6 @@ import com.axonivy.market.model.MavenArtifactVersionModel; import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.ProductRepository; -import com.axonivy.market.service.impl.VersionServiceImpl; import com.axonivy.market.util.XmlReaderUtils; import org.apache.commons.lang3.StringUtils; import org.assertj.core.api.Fail; @@ -89,7 +88,7 @@ void testGetArtifactsAndVersionToDisplay() { String targetVersion = "10.0.10"; setUpArtifactFromMeta(); when(versionService.getProductMetaArtifacts(Mockito.anyString())).thenReturn(artifactsFromMeta); - when(versionService.getVersionsToDisplay(Mockito.anyBoolean(), Mockito.anyString())).thenReturn( + when(versionService.getVersionsFromMavenArtifacts()).thenReturn( List.of(targetVersion)); when(mavenArtifactVersionRepository.findById(Mockito.anyString())).thenReturn(Optional.empty()); ArrayList artifactsInVersion = new ArrayList<>(); @@ -177,23 +176,6 @@ void testSanitizeMetaArtifactBeforeHandle() { Assertions.assertEquals(archivedArtifact1, archivedArtifactsMap.get(artifactId).get(0)); } - @Test - void testGetVersionsToDisplay() { - String repoUrl = "https://maven.axonivy.com"; - String groupId = "com.axonivy.connector.adobe.acrobat.sign"; - String artifactId = "adobe-acrobat-sign-connector"; - artifactsFromMeta.add(new MavenArtifact(repoUrl, null, groupId, artifactId, null, null, null, null)); - ArrayList versionFromArtifact = new ArrayList<>(); - versionFromArtifact.add("10.0.6"); - versionFromArtifact.add("10.0.5"); - versionFromArtifact.add("10.0.4"); - versionFromArtifact.add("10.0.3-SNAPSHOT"); - when(versionService.getVersionsFromArtifactDetails(repoUrl, groupId, artifactId)).thenReturn(versionFromArtifact); - Assertions.assertEquals(versionFromArtifact, versionService.getVersionsToDisplay(true, null)); - Assertions.assertEquals(List.of("10.0.5"), versionService.getVersionsToDisplay(null, "10.0.5")); - versionFromArtifact.remove("10.0.3-SNAPSHOT"); - Assertions.assertEquals(versionFromArtifact, versionService.getVersionsToDisplay(null, null)); - } @Test void getVersionsFromMavenArtifacts() { @@ -262,81 +244,9 @@ void testBuildMavenMetadataUrlFromArtifact() { versionService.buildMavenMetadataUrlFromArtifact(repoUrl, groupId, artifactId)); } - @Test - void testIsReleasedVersionOrUnReleaseDevVersion() { - String releasedVersion = "10.0.20"; - String snapshotVersion = "10.0.20-SNAPSHOT"; - String sprintVersion = "10.0.20-m1234"; - String minorSprintVersion = "10.0.20.1-m1234"; - String unreleasedSprintVersion = "10.0.21-m1235"; - List versions = List.of(releasedVersion, snapshotVersion, sprintVersion, unreleasedSprintVersion); - Assertions.assertTrue(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, releasedVersion)); - Assertions.assertFalse(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, sprintVersion)); - Assertions.assertFalse(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, snapshotVersion)); - Assertions.assertFalse(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, minorSprintVersion)); - Assertions.assertTrue(versionService.isOfficialVersionOrUnReleasedDevVersion(versions, unreleasedSprintVersion)); - } - - @Test - void testGetBugfixVersion() { - String releasedVersion = "10.0.20"; - String snapshotVersion = "10.0.20-SNAPSHOT"; - String sprintVersion = "10.0.20-m1234"; - String minorSprintVersion = "10.0.20.1-m1234"; - Assertions.assertEquals(releasedVersion, versionService.getBugfixVersion(releasedVersion)); - Assertions.assertEquals(releasedVersion, versionService.getBugfixVersion(snapshotVersion)); - Assertions.assertEquals(releasedVersion, versionService.getBugfixVersion(sprintVersion)); - Assertions.assertEquals(releasedVersion, versionService.getBugfixVersion(minorSprintVersion)); - } - @Test - void testIsSnapshotVersion() { - String targetVersion = "10.0.21-SNAPSHOT"; - Assertions.assertTrue(versionService.isSnapshotVersion(targetVersion)); - targetVersion = "10.0.21-m1234"; - Assertions.assertFalse(versionService.isSnapshotVersion(targetVersion)); - targetVersion = "10.0.21"; - Assertions.assertFalse(versionService.isSnapshotVersion(targetVersion)); - } - - @Test - void testIsSprintVersion() { - String targetVersion = "10.0.21-m1234"; - Assertions.assertTrue(versionService.isSprintVersion(targetVersion)); - - targetVersion = "10.0.21-SNAPSHOT"; - Assertions.assertFalse(versionService.isSprintVersion(targetVersion)); - - targetVersion = "10.0.21"; - Assertions.assertFalse(versionService.isSprintVersion(targetVersion)); - } - - @Test - void testIsReleasedVersion() { - String targetVersion = "10.0.21"; - Assertions.assertTrue(versionService.isReleasedVersion(targetVersion)); - - targetVersion = "10.0.21-SNAPSHOT"; - Assertions.assertFalse(versionService.isReleasedVersion(targetVersion)); - - targetVersion = "10.0.21-m1231"; - Assertions.assertFalse(versionService.isReleasedVersion(targetVersion)); - } - - @Test - void testIsMatchWithDesignerVersion() { - String designerVersion = "10.0.21"; - String targetVersion = "10.0.21.2"; - Assertions.assertTrue(versionService.isMatchWithDesignerVersion(targetVersion, designerVersion)); - - targetVersion = "10.0.21-SNAPSHOT"; - Assertions.assertFalse(versionService.isMatchWithDesignerVersion(targetVersion, designerVersion)); - - targetVersion = "10.0.19"; - Assertions.assertFalse(versionService.isMatchWithDesignerVersion(targetVersion, designerVersion)); - } @Test void testGetProductJsonByVersion() { diff --git a/marketplace-service/src/test/java/com/axonivy/market/util/VersionUtilsTest.java b/marketplace-service/src/test/java/com/axonivy/market/util/VersionUtilsTest.java new file mode 100644 index 000000000..c4fd418fb --- /dev/null +++ b/marketplace-service/src/test/java/com/axonivy/market/util/VersionUtilsTest.java @@ -0,0 +1,150 @@ +package com.axonivy.market.util; + +import com.axonivy.market.enums.NonStandardProduct; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; + +@ExtendWith(MockitoExtension.class) + +class VersionUtilsTest { + @InjectMocks + private VersionUtils versionUtils; + + @Test + void testIsSnapshotVersion() { + String targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertTrue(VersionUtils.isSnapshotVersion(targetVersion)); + + targetVersion = "10.0.21-m1234"; + Assertions.assertFalse(VersionUtils.isSnapshotVersion(targetVersion)); + + targetVersion = "10.0.21"; + Assertions.assertFalse(VersionUtils.isSnapshotVersion(targetVersion)); + } + + @Test + void testIsSprintVersion() { + String targetVersion = "10.0.21-m1234"; + Assertions.assertTrue(VersionUtils.isSprintVersion(targetVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(VersionUtils.isSprintVersion(targetVersion)); + + targetVersion = "10.0.21"; + Assertions.assertFalse(VersionUtils.isSprintVersion(targetVersion)); + } + + @Test + void testIsReleasedVersion() { + String targetVersion = "10.0.21"; + Assertions.assertTrue(VersionUtils.isReleasedVersion(targetVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(VersionUtils.isReleasedVersion(targetVersion)); + + targetVersion = "10.0.21-m1231"; + Assertions.assertFalse(VersionUtils.isReleasedVersion(targetVersion)); + } + + @Test + void testIsMatchWithDesignerVersion() { + String designerVersion = "10.0.21"; + String targetVersion = "10.0.21.2"; + Assertions.assertTrue(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); + + targetVersion = "10.0.21-SNAPSHOT"; + Assertions.assertFalse(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); + + targetVersion = "10.0.19"; + Assertions.assertFalse(VersionUtils.isMatchWithDesignerVersion(targetVersion, designerVersion)); + } + + @Test + void testConvertVersionToTag() { + + String rawVersion = StringUtils.EMPTY; + Assertions.assertEquals(rawVersion, VersionUtils.convertVersionToTag(StringUtils.EMPTY, rawVersion)); + + rawVersion = "11.0.0"; + String tag = "11.0.0"; + Assertions.assertEquals(tag, VersionUtils.convertVersionToTag(NonStandardProduct.PORTAL.getId(), rawVersion)); + + tag = "v11.0.0"; + Assertions.assertEquals(tag, VersionUtils.convertVersionToTag(NonStandardProduct.GRAPHQL_DEMO.getId(), rawVersion)); + } + + @Test + void testGetVersionsToDisplay() { + ArrayList versionFromArtifact = new ArrayList<>(); + versionFromArtifact.add("10.0.6"); + versionFromArtifact.add("10.0.5"); + versionFromArtifact.add("10.0.4"); + versionFromArtifact.add("10.0.3-SNAPSHOT"); + Assertions.assertEquals(versionFromArtifact, VersionUtils.getVersionsToDisplay(versionFromArtifact, true, null)); + Assertions.assertEquals(List.of("10.0.5"), VersionUtils.getVersionsToDisplay(versionFromArtifact, null, "10.0.5")); + versionFromArtifact.remove("10.0.3-SNAPSHOT"); + Assertions.assertEquals(versionFromArtifact, VersionUtils.getVersionsToDisplay(versionFromArtifact, null, null)); + } + + + @Test + void testIsReleasedVersionOrUnReleaseDevVersion() { + String releasedVersion = "10.0.20"; + String snapshotVersion = "10.0.20-SNAPSHOT"; + String sprintVersion = "10.0.20-m1234"; + String minorSprintVersion = "10.0.20.1-m1234"; + String unreleasedSprintVersion = "10.0.21-m1235"; + List versions = List.of(releasedVersion, snapshotVersion, sprintVersion, unreleasedSprintVersion); + Assertions.assertTrue(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, releasedVersion)); + Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, sprintVersion)); + Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, snapshotVersion)); + Assertions.assertFalse(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, minorSprintVersion)); + Assertions.assertTrue(VersionUtils.isOfficialVersionOrUnReleasedDevVersion(versions, unreleasedSprintVersion)); + } + + @Test + void testGetBugfixVersion() { + String releasedVersion = "10.0.20"; + String shortReleasedVersion = "10.0"; + String snapshotVersion = "10.0.20-SNAPSHOT"; + String sprintVersion = "10.0.20-m1234"; + String minorSprintVersion = "10.0.20.1-m1234"; + Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(releasedVersion)); + Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(snapshotVersion)); + Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(sprintVersion)); + Assertions.assertEquals(releasedVersion, VersionUtils.getBugfixVersion(minorSprintVersion)); + Assertions.assertEquals(shortReleasedVersion, VersionUtils.getBugfixVersion(shortReleasedVersion)); + + } + + @Test + void testGetBestMatchVersion() { + List releasedVersions = List.of("10.0.21-SNAPSHOT", "10.0.21", "10.0.19", "10.0.17"); + Assertions.assertEquals("10.0.19", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.19")); + Assertions.assertEquals("10.0.21", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.22")); + Assertions.assertEquals("10.0.17", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.18")); + Assertions.assertEquals("10.0.21", VersionUtils.getBestMatchVersion(releasedVersions, "10.0.16")); + } + + @Test + void testConvertTagToVersion() { + Assertions.assertEquals("10.0.19", VersionUtils.convertTagToVersion("10.0.19")); + Assertions.assertEquals("10.0.19", VersionUtils.convertTagToVersion("v10.0.19")); + Assertions.assertEquals("", VersionUtils.convertTagToVersion("")); + } + + @Test + void testConvertTagsToVersions() { + List results = VersionUtils.convertTagsToVersions(List.of("10.0.1", "v10.0.2")); + Assertions.assertEquals(2, results.size()); + Assertions.assertEquals("10.0.1", results.get(0)); + Assertions.assertEquals("10.0.2", results.get(1)); + } +} diff --git a/marketplace-service/src/test/resources/installationCount.json b/marketplace-service/src/test/resources/installationCount.json new file mode 100644 index 000000000..a0c8a3fbb --- /dev/null +++ b/marketplace-service/src/test/resources/installationCount.json @@ -0,0 +1,3 @@ +{ + "google-maps-connector": 40 +} \ No newline at end of file diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts index 446f77694..3592068d0 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.ts @@ -148,7 +148,7 @@ export class ProductDetailComponent { if (!targetVersion) { return this.productService.getProductDetails(productId); } - return this.productService.getProductDetailsWithVersion( + return this.productService.getBestMatchProductDetailsWithVersion( productId, targetVersion ); @@ -182,9 +182,10 @@ export class ProductDetailComponent { } loadDetailTabs(selectedVersion: string) { - const tag = selectedVersion || this.productDetail().newestReleaseVersion; + let version = selectedVersion || this.productDetail().newestReleaseVersion; + version = version.replace("Version ","") this.productService - .getProductDetailsWithVersion(this.productDetail().id, tag) + .getProductDetailsWithVersion(this.productDetail().id, version) .subscribe(updatedProductDetail => { this.productModuleContent.set( updatedProductDetail.productModuleContent diff --git a/marketplace-ui/src/app/modules/product/product.service.ts b/marketplace-ui/src/app/modules/product/product.service.ts index 8804bcf4b..723304134 100644 --- a/marketplace-ui/src/app/modules/product/product.service.ts +++ b/marketplace-ui/src/app/modules/product/product.service.ts @@ -43,6 +43,15 @@ export class ProductService { ); } + getBestMatchProductDetailsWithVersion( + productId: string, + tag: string + ): Observable { + return this.httpClient.get( + `api/product-details/${productId}/${tag}/bestmatch` + ); + } + getProductDetails(productId: string): Observable { return this.httpClient.get( `api/product-details/${productId}` diff --git a/marketplace-ui/src/app/shared/mocks/mock-services.ts b/marketplace-ui/src/app/shared/mocks/mock-services.ts index ac771ae50..1d19b8f48 100644 --- a/marketplace-ui/src/app/shared/mocks/mock-services.ts +++ b/marketplace-ui/src/app/shared/mocks/mock-services.ts @@ -32,4 +32,11 @@ export class MockProductService { ): Observable { return of(MOCK_PRODUCT_DETAIL_BY_VERSION); } + + getBestMatchProductDetailsWithVersion( + productId: string, + version: string + ): Observable { + return of(MOCK_PRODUCT_DETAIL_BY_VERSION); + } }