From f317f2c0be88dacbc2ebfea6a2deea89cfb77170 Mon Sep 17 00:00:00 2001 From: Hoan Nguyen Date: Fri, 19 Jul 2024 08:48:57 +0700 Subject: [PATCH 1/2] MARP-704: Intergrade repo marketplace-ui into marketplace repo Remove unnecessary file --- marketplace-ui/.github/workflows/ci-build.yml | 57 ----- .../.github/workflows/dev-build.yml | 28 --- marketplace-ui/LICENSE | 201 ------------------ marketplace-ui/SECURITY.md | 25 --- 4 files changed, 311 deletions(-) delete mode 100644 marketplace-ui/.github/workflows/ci-build.yml delete mode 100644 marketplace-ui/.github/workflows/dev-build.yml delete mode 100644 marketplace-ui/LICENSE delete mode 100644 marketplace-ui/SECURITY.md diff --git a/marketplace-ui/.github/workflows/ci-build.yml b/marketplace-ui/.github/workflows/ci-build.yml deleted file mode 100644 index 8a437d1f4..000000000 --- a/marketplace-ui/.github/workflows/ci-build.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: CI Build -run-name: Build on branch ${{github.ref_name}} triggered by ${{github.actor}} - -on: - push: - branches-ignore: - - develop - - master - workflow_dispatch: - -jobs: - build: - name: Build - runs-on: self-hosted - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - name: Install Dependencies - run: npm install - - name: Build project - run: npm run build - - analysis: - name: Sonarqube - needs: build - runs-on: self-hosted - env: - SONAR_PROJECT_KEY: 'AxonIvy-Market-UI' - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - steps: - - name: Execute Tests - run: npm run test - - uses: sonarsource/sonarqube-scan-action@master - env: - SONAR_TOKEN: ${{ env.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }} - with: - args: - -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} - - name: SonarQube Quality Gate check - id: sonarqube-quality-gate-check - uses: sonarsource/sonarqube-quality-gate-action@master - timeout-minutes: 5 - env: - SONAR_TOKEN: ${{ env.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }} - - - name: Clean up - run: | - rm -rf * diff --git a/marketplace-ui/.github/workflows/dev-build.yml b/marketplace-ui/.github/workflows/dev-build.yml deleted file mode 100644 index cb34d8665..000000000 --- a/marketplace-ui/.github/workflows/dev-build.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Dev Build -run-name: Build and Deploy Marketplace-UI on branch ${{github.ref_name}} by ${{github.actor}} - -on: - push: - branches: [ "develop" ] - workflow_dispatch: - -jobs: - build: - name: Build and deploy new code to Deployment directory - runs-on: self-hosted - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '18' - cache: 'npm' - - name: Install Dependencies - run: npm install - - name: Build Angular app - run: npm run build -- --configuration production --output-path=dist - - name: Execute Tests - run: npm run test - - name: Copy files to Deployment directory - if: success() - run: sudo cp -r dist/* /var/www/marketplace-ui diff --git a/marketplace-ui/LICENSE b/marketplace-ui/LICENSE deleted file mode 100644 index 261eeb9e9..000000000 --- a/marketplace-ui/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/marketplace-ui/SECURITY.md b/marketplace-ui/SECURITY.md deleted file mode 100644 index 1d4c06f71..000000000 --- a/marketplace-ui/SECURITY.md +++ /dev/null @@ -1,25 +0,0 @@ -## Reporting a Vulnerability - -At Axon Ivy, we take security seriously. If you believe you've found a security vulnerability in our software, we encourage you to let us know right away. We investigate all reported vulnerabilities promptly. - -To report a vulnerability, please send an email to [security@axonivy.com](mailto:security@axonivy.com) with the following information: - -- Description of the vulnerability -- Steps to reproduce the vulnerability -- Any additional information or context that may be helpful - -Please refrain from publicly disclosing the vulnerability until it has been addressed by our team. - -## Response Time - -We strive to respond to security vulnerability reports as quickly as possible. Upon receiving your report, we will acknowledge it within 72 hours and we will release a patch as soon as possible depending on complexity, but historically within a few days. -Please report (suspected) security vulnerabilities at https://support.axonivy.com/. - - -## Responsible Disclosure - -We encourage responsible disclosure of security vulnerabilities. We believe that working together with security researchers and the broader community helps us improve the security of our software for everyone. - -## Contact - -For any questions or concerns regarding security, please contact us at [security@axonivy.com](mailto:security@axonivy.com). From 118e4ba80445747fc0414633263511d7d659efac Mon Sep 17 00:00:00 2001 From: Tu Thanh Nguyen <138571181+tutn-axonivy@users.noreply.github.com> Date: Fri, 19 Jul 2024 09:13:15 +0700 Subject: [PATCH 2/2] MARP-558 multilingualism for detail page description (#34) --- .../market/constants/GitHubConstants.java | 1 + .../market/constants/ReadmeConstants.java | 1 + .../com/axonivy/market/entity/Product.java | 28 ++-- .../market/entity/ProductModuleContent.java | 3 +- .../market/factory/ProductFactory.java | 44 +++---- .../impl/GHAxonIvyMarketRepoServiceImpl.java | 2 +- .../impl/GHAxonIvyProductRepoServiceImpl.java | 82 ++++++++---- .../market/model/MultilingualismValue.java | 17 --- .../axonivy/market/model/ProductModel.java | 19 +-- .../service/impl/ProductServiceImpl.java | 66 +++++----- .../controller/ProductControllerTest.java | 49 +++---- .../ProductDetailsControllerTest.java | 46 ++++--- .../market/factory/ProductFactoryTest.java | 18 ++- .../GHAxonIvyProductRepoServiceImplTest.java | 52 ++++---- .../service/ProductServiceImplTest.java | 120 +++++++++--------- .../service/VersionServiceImplTest.java | 4 + 16 files changed, 303 insertions(+), 249 deletions(-) delete mode 100644 marketplace-service/src/main/java/com/axonivy/market/model/MultilingualismValue.java diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java index 39d35a77c..6e81a25f9 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/GitHubConstants.java @@ -12,6 +12,7 @@ public class GitHubConstants { public static final String PRODUCT_JSON_FILE_PATH_FORMAT = "%s/product.json"; public static final String GITHUB_PROVIDER_NAME = "GitHub"; public static final String GITHUB_GET_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"; + public static final String README_FILE_LOCALE_REGEX = "_(..)"; @NoArgsConstructor(access = AccessLevel.PRIVATE) public static class Json { diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/ReadmeConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/ReadmeConstants.java index 6d3024e9f..2f4e4f946 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/ReadmeConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/ReadmeConstants.java @@ -7,6 +7,7 @@ public class ReadmeConstants { public static final String IMAGES = "images"; public static final String README_FILE = "README.md"; + public static final String README_FILE_NAME = "README"; public static final String DEMO_PART = "## Demo"; public static final String SETUP_PART = "## Setup"; } 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 a6d4c39df..ea1964863 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 @@ -1,23 +1,25 @@ package com.axonivy.market.entity; +import static com.axonivy.market.constants.EntityConstants.PRODUCT; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + import com.axonivy.market.github.model.MavenArtifact; -import com.axonivy.market.model.MultilingualismValue; import com.fasterxml.jackson.annotation.JsonProperty; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.springframework.data.annotation.Id; -import org.springframework.data.mongodb.core.mapping.Document; - -import java.io.Serializable; -import java.util.Date; -import java.util.List; - -import static com.axonivy.market.constants.EntityConstants.PRODUCT; @Getter @Setter @@ -31,10 +33,10 @@ public class Product implements Serializable { private String id; private String marketDirectory; @JsonProperty - private MultilingualismValue names; + private Map names; private String version; @JsonProperty - private MultilingualismValue shortDescriptions; + private Map shortDescriptions; private String logoUrl; private Boolean listed; private String type; diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java index d2b6d1145..f8df6160c 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java +++ b/marketplace-service/src/main/java/com/axonivy/market/entity/ProductModuleContent.java @@ -6,6 +6,7 @@ import lombok.Setter; import java.io.Serializable; +import java.util.Map; @Getter @Setter @@ -14,7 +15,7 @@ public class ProductModuleContent implements Serializable { private static final long serialVersionUID = 1L; private String tag; - private String description; + private Map description; private String setup; private String demo; private Boolean isDependency; 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 b7c4580ec..034f26594 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 @@ -1,29 +1,31 @@ package com.axonivy.market.factory; +import static com.axonivy.market.constants.CommonConstants.LOGO_FILE; +import static com.axonivy.market.constants.CommonConstants.SLASH; +import static com.axonivy.market.constants.MetaConstants.DEFAULT_VENDOR_NAME; +import static com.axonivy.market.constants.MetaConstants.DEFAULT_VENDOR_URL; +import static com.axonivy.market.constants.MetaConstants.META_FILE; +import static org.apache.commons.lang3.StringUtils.EMPTY; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.kohsuke.github.GHContent; +import org.springframework.util.CollectionUtils; + import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.Language; import com.axonivy.market.github.model.Meta; import com.axonivy.market.github.util.GitHubUtils; import com.axonivy.market.model.DisplayValue; -import com.axonivy.market.model.MultilingualismValue; import com.fasterxml.jackson.databind.ObjectMapper; + import lombok.AccessLevel; import lombok.NoArgsConstructor; 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.util.CollectionUtils; - -import java.io.IOException; -import java.util.List; - -import static com.axonivy.market.constants.CommonConstants.LOGO_FILE; -import static com.axonivy.market.constants.CommonConstants.SLASH; -import static com.axonivy.market.constants.MetaConstants.DEFAULT_VENDOR_NAME; -import static com.axonivy.market.constants.MetaConstants.DEFAULT_VENDOR_URL; -import static com.axonivy.market.constants.MetaConstants.META_FILE; -import static org.apache.commons.lang3.StringUtils.EMPTY; @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -76,15 +78,11 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent return product; } - private static MultilingualismValue mappingMultilingualismValueByMetaJSONFile(List list) { - MultilingualismValue value = new MultilingualismValue(); + private static Map mappingMultilingualismValueByMetaJSONFile(List list) { + Map value = new HashMap<>(); if (!CollectionUtils.isEmpty(list)) { for (DisplayValue name : list) { - if (Language.EN.getValue().equalsIgnoreCase(name.getLocale())) { - value.setEn(name.getValue()); - } else if (Language.DE.getValue().equalsIgnoreCase(name.getLocale())) { - value.setDe(name.getValue()); - } + value.put(name.getLocale(), name.getValue()); } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java index 1d3f286eb..c44a047bc 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java @@ -133,4 +133,4 @@ public GHRepository getRepository() { return repository; } -} +} \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java index 4c6516584..033503792 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java @@ -1,5 +1,24 @@ package com.axonivy.market.github.service.impl; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHOrganization; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHTag; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; import com.axonivy.market.constants.MavenConstants; @@ -8,31 +27,15 @@ import com.axonivy.market.constants.ReadmeConstants; import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductModuleContent; +import com.axonivy.market.enums.Language; import com.axonivy.market.github.model.MavenArtifact; import com.axonivy.market.github.service.GHAxonIvyProductRepoService; import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.github.util.GitHubUtils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.util.Strings; -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHOrganization; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHTag; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import lombok.extern.log4j.Log4j2; @Log4j2 @Service @@ -150,14 +153,17 @@ public ProductModuleContent getReadmeAndProductContentsFromTag(Product product, List contents = getProductFolderContents(product, ghRepository, tag); productModuleContent.setTag(tag); getDependencyContentsFromProductJson(productModuleContent, contents); - GHContent readmeFile = contents.stream().filter(GHContent::isFile) - .filter(content -> ReadmeConstants.README_FILE.equals(content.getName())).findFirst().orElse(null); - if (Objects.nonNull(readmeFile)) { - String readmeContents = new String(readmeFile.read().readAllBytes()); - if (hasImageDirectives(readmeContents)) { - readmeContents = updateImagesWithDownloadUrl(product, contents, readmeContents); + List readmeFiles = contents.stream().filter(GHContent::isFile) + .filter(content -> content.getName().startsWith(ReadmeConstants.README_FILE_NAME)).toList(); + if (!CollectionUtils.isEmpty(readmeFiles)) { + for (GHContent readmeFile : readmeFiles) { + String readmeContents = new String(readmeFile.read().readAllBytes()); + if (hasImageDirectives(readmeContents)) { + readmeContents = updateImagesWithDownloadUrl(product, contents, readmeContents); + } + String locale = getReadmeFileLocale(readmeFile.getName()); + getExtractedPartsOfReadme(productModuleContent, readmeContents, locale); } - getExtractedPartsOfReadme(productModuleContent, readmeContents); } } catch (Exception e) { log.error("Cannot get product.json and README file's content {}", e); @@ -166,6 +172,16 @@ public ProductModuleContent getReadmeAndProductContentsFromTag(Product product, return productModuleContent; } + private String getReadmeFileLocale(String readmeFile) { + String result = StringUtils.EMPTY; + Pattern pattern = Pattern.compile(GitHubConstants.README_FILE_LOCALE_REGEX); + Matcher matcher = pattern.matcher(readmeFile); + if (matcher.find()) { + result = matcher.group(1); + } + return result; + } + private void getDependencyContentsFromProductJson(ProductModuleContent productModuleContent, List contents) throws IOException { GHContent productJsonFile = getProductJsonFile(contents); @@ -223,7 +239,8 @@ private void getImagesFromImageFolder(Product product, List contents, // Cover some cases including when demo and setup parts switch positions or // missing one of them - public void getExtractedPartsOfReadme(ProductModuleContent productModuleContent, String readmeContents) { + public void getExtractedPartsOfReadme(ProductModuleContent productModuleContent, String readmeContents, + String locale) { String[] parts = readmeContents.split(DEMO_SETUP_TITLE); int demoIndex = readmeContents.indexOf(ReadmeConstants.DEMO_PART); int setupIndex = readmeContents.indexOf(ReadmeConstants.SETUP_PART); @@ -249,11 +266,22 @@ public void getExtractedPartsOfReadme(ProductModuleContent productModuleContent, setup = parts[1]; } - productModuleContent.setDescription(description.trim()); + setDescriptionWithLocale(productModuleContent, description.trim(), locale); productModuleContent.setDemo(demo.trim()); productModuleContent.setSetup(setup.trim()); } + private void setDescriptionWithLocale(ProductModuleContent productModuleContent, String description, String locale) { + if (productModuleContent.getDescription() == null) { + productModuleContent.setDescription(new HashMap<>()); + } + if (StringUtils.isEmpty(locale)) { + productModuleContent.getDescription().put(Language.EN.getValue(), description); + } else { + productModuleContent.getDescription().put(locale.toLowerCase(), description); + } + } + private List getProductFolderContents(Product product, GHRepository ghRepository, String tag) throws IOException { String productFolderPath = ghRepository.getDirectoryContent(CommonConstants.SLASH, tag).stream() diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/MultilingualismValue.java b/marketplace-service/src/main/java/com/axonivy/market/model/MultilingualismValue.java deleted file mode 100644 index 389c4832e..000000000 --- a/marketplace-service/src/main/java/com/axonivy/market/model/MultilingualismValue.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.axonivy.market.model; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.io.Serializable; - -@Getter -@Setter -@NoArgsConstructor -public class MultilingualismValue implements Serializable { - private static final long serialVersionUID = -4193508237020296419L; - - private String en; - private String de; -} diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java b/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java index 79ea5b5bf..fba99e845 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java +++ b/marketplace-service/src/main/java/com/axonivy/market/model/ProductModel.java @@ -1,16 +1,19 @@ package com.axonivy.market.model; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import java.util.List; +import java.util.Map; + import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.server.core.Relation; -import java.util.List; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Getter @Setter @@ -19,8 +22,8 @@ @JsonInclude(Include.NON_NULL) public class ProductModel extends RepresentationModel { private String id; - private MultilingualismValue names; - private MultilingualismValue shortDescriptions; + private Map names; + private Map shortDescriptions; private String logoUrl; private String type; private List tags; 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 4a8afd880..e87bf73ef 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 @@ -1,5 +1,37 @@ package com.axonivy.market.service.impl; +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.StringUtils.EMPTY; + +import java.io.IOException; +import java.net.URL; +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 org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.util.Strings; +import org.kohsuke.github.GHCommit; +import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHTag; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Order; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.GitHubConstants; import com.axonivy.market.entity.GitHubRepoMeta; @@ -19,38 +51,8 @@ import com.axonivy.market.service.ProductService; 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.apache.logging.log4j.util.Strings; -import org.kohsuke.github.GHCommit; -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHTag; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Order; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; -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.Random; - -import static java.util.Optional.ofNullable; -import static org.apache.commons.lang3.StringUtils.EMPTY; +import lombok.extern.log4j.Log4j2; @Log4j2 @Service @@ -70,7 +72,7 @@ public class ProductServiceImpl implements ProductService { private String installationCountPath; public static final String NON_NUMERIC_CHAR = "[^0-9.]"; - private final Random random = new Random(); + private final SecureRandom random = new SecureRandom(); public ProductServiceImpl(ProductRepository productRepository, GHAxonIvyMarketRepoService axonIvyMarketRepoService, GHAxonIvyProductRepoService axonIvyProductRepoService, GitHubRepoMetaRepository gitHubRepoMetaRepository, GitHubService gitHubService) { 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 079526960..fa5c2ff4e 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,12 +1,14 @@ package com.axonivy.market.controller; -import com.axonivy.market.assembler.ProductModelAssembler; -import com.axonivy.market.entity.Product; -import com.axonivy.market.enums.ErrorCode; -import com.axonivy.market.enums.SortOption; -import com.axonivy.market.enums.TypeOption; -import com.axonivy.market.model.MultilingualismValue; -import com.axonivy.market.service.ProductService; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -23,16 +25,17 @@ import org.springframework.hateoas.PagedModel.PageMetadata; import org.springframework.http.HttpStatus; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; +import com.axonivy.market.assembler.ProductModelAssembler; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.ErrorCode; +import com.axonivy.market.enums.Language; +import com.axonivy.market.enums.SortOption; +import com.axonivy.market.enums.TypeOption; +import com.axonivy.market.model.ProductRating; +import com.axonivy.market.service.ProductService; @ExtendWith(MockitoExtension.class) class ProductControllerTest { - private static final String PRODUCT_ID_SAMPLE = "amazon-comprehend"; private static final String PRODUCT_NAME_SAMPLE = "Amazon Comprehend"; private static final String PRODUCT_NAME_DE_SAMPLE = "Amazon Comprehend DE"; private static final String PRODUCT_DESC_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data."; @@ -85,8 +88,10 @@ void testFindProducts() { assertEquals(HttpStatus.OK, result.getStatusCode()); assertTrue(result.hasBody()); assertEquals(1, result.getBody().getContent().size()); - assertEquals(PRODUCT_NAME_SAMPLE, result.getBody().getContent().iterator().next().getNames().getEn()); - assertEquals(PRODUCT_NAME_DE_SAMPLE, result.getBody().getContent().iterator().next().getNames().getDe()); + assertEquals(PRODUCT_NAME_SAMPLE, + result.getBody().getContent().iterator().next().getNames().get(Language.EN.getValue())); + assertEquals(PRODUCT_NAME_DE_SAMPLE, + result.getBody().getContent().iterator().next().getNames().get(Language.DE.getValue())); } @Test @@ -100,13 +105,13 @@ void testSyncProducts() { private Product createProductMock() { Product mockProduct = new Product(); mockProduct.setId("amazon-comprehend"); - MultilingualismValue name = new MultilingualismValue(); - name.setEn(PRODUCT_NAME_SAMPLE); - name.setDe(PRODUCT_NAME_DE_SAMPLE); + Map name = new HashMap<>(); + name.put(Language.EN.getValue(), PRODUCT_NAME_SAMPLE); + name.put(Language.DE.getValue(), PRODUCT_NAME_DE_SAMPLE); mockProduct.setNames(name); - MultilingualismValue shortDescription = new MultilingualismValue(); - shortDescription.setEn(PRODUCT_DESC_SAMPLE); - shortDescription.setDe(PRODUCT_DESC_DE_SAMPLE); + Map shortDescription = new HashMap<>(); + shortDescription.put(Language.EN.getValue(), PRODUCT_DESC_SAMPLE); + shortDescription.put(Language.DE.getValue(), PRODUCT_DESC_DE_SAMPLE); mockProduct.setShortDescriptions(shortDescription); mockProduct.setType("connector"); mockProduct.setTags(List.of("AI")); 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 3551a82cf..e1163587e 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,12 +1,18 @@ package com.axonivy.market.controller; -import com.axonivy.market.assembler.ProductDetailModelAssembler; -import com.axonivy.market.entity.Product; -import com.axonivy.market.model.MavenArtifactVersionModel; -import com.axonivy.market.model.MultilingualismValue; -import com.axonivy.market.model.ProductDetailModel; -import com.axonivy.market.service.ProductService; -import com.axonivy.market.service.VersionService; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Objects; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,13 +23,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import java.util.List; -import java.util.Objects; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import com.axonivy.market.assembler.ProductDetailModelAssembler; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.Language; +import com.axonivy.market.model.MavenArtifactVersionModel; +import com.axonivy.market.model.ProductDetailModel; +import com.axonivy.market.service.ProductService; +import com.axonivy.market.service.VersionService; @ExtendWith(MockitoExtension.class) class ProductDetailsControllerTest { @@ -100,9 +106,9 @@ void testSyncInstallationCount() { private Product mockProduct() { Product mockProduct = new Product(); mockProduct.setId(DOCKER_CONNECTOR_ID); - MultilingualismValue name = new MultilingualismValue(); - name.setEn(PRODUCT_NAME_SAMPLE); - name.setDe(PRODUCT_NAME_DE_SAMPLE); + Map name = new HashMap<>(); + name.put(Language.EN.getValue(), PRODUCT_NAME_SAMPLE); + name.put(Language.DE.getValue(), PRODUCT_NAME_DE_SAMPLE); mockProduct.setNames(name); mockProduct.setLanguage("English"); return mockProduct; @@ -111,9 +117,9 @@ private Product mockProduct() { private ProductDetailModel createProductMockWithDetails() { ProductDetailModel mockProductDetail = new ProductDetailModel(); mockProductDetail.setId(DOCKER_CONNECTOR_ID); - MultilingualismValue name = new MultilingualismValue(); - name.setEn(PRODUCT_NAME_SAMPLE); - name.setDe(PRODUCT_NAME_DE_SAMPLE); + Map name = new HashMap<>(); + name.put(Language.EN.getValue(), PRODUCT_NAME_SAMPLE); + name.put(Language.DE.getValue(), PRODUCT_NAME_DE_SAMPLE); mockProductDetail.setNames(name); mockProductDetail.setType("connector"); mockProductDetail.setCompatibility("10.0+"); 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 2446c18c1..5b9f3727e 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 @@ -19,6 +19,20 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.IOException; +import java.io.InputStream; + +import com.axonivy.market.github.model.Meta; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kohsuke.github.GHContent; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.axonivy.market.constants.CommonConstants; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.Language; + @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"; @@ -34,8 +48,8 @@ void testMappingByGHContent() throws IOException { when(mockContent.read()).thenReturn(inputStream); result = ProductFactory.mappingByGHContent(product, mockContent); assertNotEquals(null, result); - assertEquals("Amazon Comprehend", result.getNames().getEn()); - assertEquals("Amazon Comprehend DE", result.getNames().getDe()); + assertEquals("Amazon Comprehend", result.getNames().get(Language.EN.getValue())); + assertEquals("Amazon Comprehend DE", result.getNames().get(Language.DE.getValue())); } @Test diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java index 1c7bdf164..52c01e218 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/GHAxonIvyProductRepoServiceImplTest.java @@ -1,13 +1,22 @@ package com.axonivy.market.service; -import com.axonivy.market.constants.CommonConstants; -import com.axonivy.market.constants.ProductJsonConstants; -import com.axonivy.market.constants.ReadmeConstants; -import com.axonivy.market.entity.Product; -import com.axonivy.market.github.model.MavenArtifact; -import com.axonivy.market.github.service.GitHubService; -import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; -import com.fasterxml.jackson.databind.JsonNode; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -22,22 +31,15 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.when; +import com.axonivy.market.constants.CommonConstants; +import com.axonivy.market.constants.ProductJsonConstants; +import com.axonivy.market.constants.ReadmeConstants; +import com.axonivy.market.entity.Product; +import com.axonivy.market.enums.Language; +import com.axonivy.market.github.model.MavenArtifact; +import com.axonivy.market.github.service.GitHubService; +import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; +import com.fasterxml.jackson.databind.JsonNode; @ExtendWith(MockitoExtension.class) class GHAxonIvyProductRepoServiceImplTest { @@ -214,7 +216,7 @@ void testGetReadmeAndProductContentsFromTag() throws IOException { assertEquals("com.axonivy.utils.bpmnstatistic", result.getGroupId()); assertEquals("bpmn-statistic", result.getArtifactId()); assertEquals("iar", result.getType()); - assertEquals("Test README", result.getDescription()); + assertEquals("Test README", result.getDescription().get(Language.EN.getValue())); assertEquals("Demo content", result.getDemo()); assertEquals("Setup content (https://raw.githubusercontent.com/image.png)", result.getSetup()); } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java index aa02a2c9f..1242e442f 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java @@ -1,21 +1,33 @@ package com.axonivy.market.service; -import com.axonivy.market.constants.GitHubConstants; -import com.axonivy.market.entity.GitHubRepoMeta; -import com.axonivy.market.entity.Product; -import com.axonivy.market.entity.ProductModuleContent; -import com.axonivy.market.enums.FileStatus; -import com.axonivy.market.enums.FileType; -import com.axonivy.market.enums.SortOption; -import com.axonivy.market.enums.TypeOption; -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.MultilingualismValue; -import com.axonivy.market.repository.GitHubRepoMetaRepository; -import com.axonivy.market.repository.ProductRepository; -import com.axonivy.market.service.impl.ProductServiceImpl; +import static com.axonivy.market.constants.CommonConstants.LOGO_FILE; +import static com.axonivy.market.constants.CommonConstants.SLASH; +import static com.axonivy.market.constants.MetaConstants.META_FILE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +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.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,33 +50,22 @@ import org.springframework.data.domain.Sort; import org.springframework.test.util.ReflectionTestUtils; -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.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.stream.Collectors; - -import static com.axonivy.market.constants.CommonConstants.LOGO_FILE; -import static com.axonivy.market.constants.CommonConstants.SLASH; -import static com.axonivy.market.constants.MetaConstants.META_FILE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -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 com.axonivy.market.constants.GitHubConstants; +import com.axonivy.market.entity.GitHubRepoMeta; +import com.axonivy.market.entity.Product; +import com.axonivy.market.entity.ProductModuleContent; +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.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.repository.GitHubRepoMetaRepository; +import com.axonivy.market.repository.ProductRepository; +import com.axonivy.market.service.impl.ProductServiceImpl; @ExtendWith(MockitoExtension.class) class ProductServiceImplTest { @@ -249,25 +250,26 @@ void testFindAllProductsWithKeyword() throws IOException { verify(productRepository).findAll(any(Pageable.class)); // Test has keyword - when(productRepository.searchByNameOrShortDescriptionRegex(any(), any(), any(Pageable.class))).thenReturn( - new PageImpl<>( - mockResultReturn.stream().filter(product -> product.getNames().getEn().equals(SAMPLE_PRODUCT_NAME)) - .collect(Collectors.toList()))); + when(productRepository.searchByNameOrShortDescriptionRegex(any(), any(), any(Pageable.class))) + .thenReturn(new PageImpl<>(mockResultReturn.stream() + .filter(product -> product.getNames().get(Language.EN.getValue()).equals(SAMPLE_PRODUCT_NAME)) + .collect(Collectors.toList()))); // Executes result = productService.findProducts(TypeOption.ALL.getOption(), SAMPLE_PRODUCT_NAME, langague, PAGEABLE); verify(productRepository).findAll(any(Pageable.class)); assertTrue(result.hasContent()); - assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().getEn()); + assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().get(Language.EN.getValue())); // Test has keyword and type is connector - when(productRepository.searchByKeywordAndType(any(), any(), any(), any(Pageable.class))).thenReturn(new PageImpl<>( - mockResultReturn.stream().filter( - product -> product.getNames().getEn().equals(SAMPLE_PRODUCT_NAME) && product.getType() - .equals(TypeOption.CONNECTORS.getCode())).collect(Collectors.toList()))); + when(productRepository.searchByKeywordAndType(any(), any(), any(), any(Pageable.class))) + .thenReturn(new PageImpl<>(mockResultReturn.stream() + .filter(product -> product.getNames().get(Language.EN.getValue()).equals(SAMPLE_PRODUCT_NAME) + && product.getType().equals(TypeOption.CONNECTORS.getCode())) + .collect(Collectors.toList()))); // Executes result = productService.findProducts(TypeOption.CONNECTORS.getOption(), SAMPLE_PRODUCT_NAME, langague, PAGEABLE); assertTrue(result.hasContent()); - assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().getEn()); + assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().get(Language.EN.getValue())); } @Test @@ -360,18 +362,18 @@ void testGetCompatibilityFromNumericTag() { private Page createPageProductsMock() { var mockProducts = new ArrayList(); - MultilingualismValue name = new MultilingualismValue(); + Map name = new HashMap<>(); Product mockProduct = new Product(); mockProduct.setId(SAMPLE_PRODUCT_ID); - name.setEn(SAMPLE_PRODUCT_NAME); + name.put(Language.EN.getValue(), SAMPLE_PRODUCT_NAME); mockProduct.setNames(name); mockProduct.setType("connector"); mockProducts.add(mockProduct); mockProduct = new Product(); mockProduct.setId("tel-search-ch-connector"); - name = new MultilingualismValue(); - name.setEn("Swiss phone directory"); + name = new HashMap<>(); + name.put(Language.EN.getValue(), "Swiss phone directory"); mockProduct.setNames(name); mockProduct.setType("util"); mockProducts.add(mockProduct); @@ -403,7 +405,9 @@ private ProductModuleContent mockReadmeProductContent() { ProductModuleContent productModuleContent = new ProductModuleContent(); productModuleContent.setTag("v10.0.2"); productModuleContent.setName("Amazon Comprehend"); - productModuleContent.setDescription("testDescription"); + Map description = new HashMap<>(); + description.put(Language.EN.getValue(), "testDescription"); + productModuleContent.setDescription(description); return productModuleContent; } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java index 60ffb9248..a5e98a854 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java @@ -235,6 +235,10 @@ void testGetVersionsFromArtifactDetails() { versionFromArtifact.add("10.0.19"); versionFromArtifact.add("10.0.20"); versionFromArtifact.add("10.0.21"); + versionFromArtifact.add("10.0.22"); + versionFromArtifact.add("10.0.23"); + versionFromArtifact.add("10.0.24"); + versionFromArtifact.add("10.0.25"); try (MockedStatic xmlUtils = Mockito.mockStatic(XmlReaderUtils.class)) { xmlUtils.when(() -> XmlReaderUtils.readXMLFromUrl(Mockito.anyString())).thenReturn(versionFromArtifact);