From 6880dacbe5fc18822049fb99683e3b28db2e14b8 Mon Sep 17 00:00:00 2001 From: Pham Hoang Hung <84316773+phhung-axonivy@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:22:06 +0700 Subject: [PATCH] MARP-463 Multilingualism for Website - landing page (#22) --- .../assembler/ProductModelAssembler.java | 4 +- .../market/constants/GitHubConstants.java | 11 +-- .../market/controller/ProductController.java | 5 +- .../market/entity/MavenArtifactModel.java | 10 +-- .../market/entity/MavenArtifactVersion.java | 4 +- .../com/axonivy/market/entity/Product.java | 9 +- .../com/axonivy/market/enums/Language.java | 12 +++ .../com/axonivy/market/enums/SortOption.java | 8 +- .../market/factory/ProductFactory.java | 24 ++++- .../com/axonivy/market/github/model/Meta.java | 5 +- .../market/github/service/GitHubService.java | 4 +- .../impl/GHAxonIvyMarketRepoServiceImpl.java | 5 +- .../service/impl/GitHubServiceImpl.java | 8 +- .../axonivy/market/model/DisplayValue.java | 44 +++++++++ .../market/model/MultilingualismValue.java | 17 ++++ .../axonivy/market/model/ProductModel.java | 4 +- .../market/repository/ProductRepository.java | 8 +- .../market/service/ProductService.java | 2 +- .../service/impl/ProductServiceImpl.java | 17 ++-- .../controller/ProductControllerTest.java | 24 +++-- .../market/factory/ProductFactoryTest.java | 90 ++++++++++--------- .../GHAxonIvyMarketRepoServiceImplTest.java | 2 +- .../market/service/GitHubServiceImplTest.java | 6 +- .../service/ProductServiceImplTest.java | 49 +++++----- .../service/VersionServiceImplTest.java | 45 ++++++---- src/test/resources/meta.json | 22 ++++- 26 files changed, 297 insertions(+), 142 deletions(-) create mode 100644 src/main/java/com/axonivy/market/enums/Language.java create mode 100644 src/main/java/com/axonivy/market/model/DisplayValue.java create mode 100644 src/main/java/com/axonivy/market/model/MultilingualismValue.java diff --git a/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java b/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java index 6b36a9d07..bd9c948cd 100644 --- a/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java +++ b/src/main/java/com/axonivy/market/assembler/ProductModelAssembler.java @@ -27,8 +27,8 @@ public ProductModel toModel(Product product) { private ProductModel createResource(ProductModel model, Product product) { model.setId(product.getId()); - model.setName(product.getName()); - model.setShortDescription(product.getShortDescription()); + model.setNames(product.getNames()); + model.setShortDescriptions(product.getShortDescriptions()); model.setType(product.getType()); model.setTags(product.getTags()); model.setLogoUrl(product.getLogoUrl()); diff --git a/src/main/java/com/axonivy/market/constants/GitHubConstants.java b/src/main/java/com/axonivy/market/constants/GitHubConstants.java index c03c69f65..33c84df88 100644 --- a/src/main/java/com/axonivy/market/constants/GitHubConstants.java +++ b/src/main/java/com/axonivy/market/constants/GitHubConstants.java @@ -5,8 +5,9 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class GitHubConstants { - public static final String AXONIVY_MARKET_ORGANIZATION_NAME = "axonivy-market"; - public static final String AXONIVY_MARKETPLACE_REPO_NAME = "market"; - public static final String AXONIVY_MARKETPLACE_PATH = "market"; - public static final String PRODUCT_JSON_FILE_PATH_FORMAT = "%s/product.json"; -} \ No newline at end of file + public static final String AXONIVY_MARKET_ORGANIZATION_NAME = "axonivy-market"; + public static final String AXONIVY_MARKETPLACE_REPO_NAME = "market"; + public static final String AXONIVY_MARKETPLACE_PATH = "market"; + public static final String DEFAULT_BRANCH = "feature/MARP-463-Multilingualism-for-Website"; + public static final String PRODUCT_JSON_FILE_PATH_FORMAT = "%s/product.json"; +} diff --git a/src/main/java/com/axonivy/market/controller/ProductController.java b/src/main/java/com/axonivy/market/controller/ProductController.java index 434beb202..55d0444ff 100644 --- a/src/main/java/com/axonivy/market/controller/ProductController.java +++ b/src/main/java/com/axonivy/market/controller/ProductController.java @@ -45,8 +45,9 @@ public ProductController(ProductService service, ProductModelAssembler assembler @GetMapping() public ResponseEntity> findProducts( @RequestParam(required = true, name = "type") String type, - @RequestParam(required = false, name = "keyword") String keyword, Pageable pageable) { - Page results = service.findProducts(type, keyword, pageable); + @RequestParam(required = false, name = "keyword") String keyword, + @RequestParam(required = true, name = "language") String language, Pageable pageable) { + Page results = service.findProducts(type, keyword, language, pageable); if (results.isEmpty()) { return generateEmptyPagedModel(); } diff --git a/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java b/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java index f3f8977d5..6b5328e26 100644 --- a/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java +++ b/src/main/java/com/axonivy/market/entity/MavenArtifactModel.java @@ -1,14 +1,14 @@ package com.axonivy.market.entity; -import com.axonivy.market.github.model.MavenArtifact; +import java.io.Serializable; +import java.util.Objects; + +import org.springframework.data.annotation.Transient; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.springframework.data.annotation.Transient; - -import java.io.Serializable; -import java.util.Objects; @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java b/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java index f92be46fc..8c8820579 100644 --- a/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java +++ b/src/main/java/com/axonivy/market/entity/MavenArtifactVersion.java @@ -21,7 +21,9 @@ @NoArgsConstructor @Document(MAVEN_ARTIFACT_VERSION) public class MavenArtifactVersion implements Serializable { - @Id + private static final long serialVersionUID = -6492612804634492078L; + + @Id private String productId; private List versions = new ArrayList<>(); private Map> productArtifactWithVersionReleased = new HashMap<>(); diff --git a/src/main/java/com/axonivy/market/entity/Product.java b/src/main/java/com/axonivy/market/entity/Product.java index f38748c41..9d89381f7 100644 --- a/src/main/java/com/axonivy/market/entity/Product.java +++ b/src/main/java/com/axonivy/market/entity/Product.java @@ -12,6 +12,9 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import com.axonivy.market.model.MultilingualismValue; +import com.fasterxml.jackson.annotation.JsonProperty; + import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -28,9 +31,11 @@ public class Product implements Serializable { @Id private String id; private String marketDirectory; - private String name; + @JsonProperty + private MultilingualismValue names; private String version; - private String shortDescription; + @JsonProperty + private MultilingualismValue shortDescriptions; private String logoUrl; private Boolean listed; private String type; diff --git a/src/main/java/com/axonivy/market/enums/Language.java b/src/main/java/com/axonivy/market/enums/Language.java new file mode 100644 index 000000000..f1115c234 --- /dev/null +++ b/src/main/java/com/axonivy/market/enums/Language.java @@ -0,0 +1,12 @@ +package com.axonivy.market.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum Language { + EN("en"), DE("de"); + + private String value; +} diff --git a/src/main/java/com/axonivy/market/enums/SortOption.java b/src/main/java/com/axonivy/market/enums/SortOption.java index 308a01bd3..59914ab90 100644 --- a/src/main/java/com/axonivy/market/enums/SortOption.java +++ b/src/main/java/com/axonivy/market/enums/SortOption.java @@ -10,7 +10,7 @@ @Getter @AllArgsConstructor public enum SortOption { - POPULARITY("popularity", "installationCount"), ALPHABETICALLY("alphabetically", "name"), + POPULARITY("popularity", "installationCount"), ALPHABETICALLY("alphabetically", "names"), RECENT("recent", "newestPublishedDate"); private String option; @@ -25,4 +25,10 @@ public static SortOption of(String option) { } throw new InvalidParamException(ErrorCode.PRODUCT_SORT_INVALID, "SortOption: " + option); } + + public String getCode(String language) { + return StringUtils.isNotBlank(language) && ALPHABETICALLY.option.equalsIgnoreCase(option) + ? String.format("%s.%s", ALPHABETICALLY.code, language) + : code; + } } diff --git a/src/main/java/com/axonivy/market/factory/ProductFactory.java b/src/main/java/com/axonivy/market/factory/ProductFactory.java index 8ec75e239..4cd0f6971 100644 --- a/src/main/java/com/axonivy/market/factory/ProductFactory.java +++ b/src/main/java/com/axonivy/market/factory/ProductFactory.java @@ -6,13 +6,18 @@ import static org.apache.commons.lang3.StringUtils.EMPTY; import java.io.IOException; +import java.util.List; 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; @@ -49,13 +54,13 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent } product.setId(meta.getId()); - product.setName(meta.getName()); + product.setNames(mappingMultilingualismValueByMetaJSONFile(meta.getNames())); product.setMarketDirectory(extractParentDirectory(ghContent)); product.setListed(meta.getListed()); product.setType(meta.getType()); product.setTags(meta.getTags()); product.setVersion(meta.getVersion()); - product.setShortDescription(meta.getDescription()); + product.setShortDescriptions(mappingMultilingualismValueByMetaJSONFile(meta.getDescriptions())); product.setVendor(meta.getVendor()); product.setVendorImage(meta.getVendorImage()); product.setVendorUrl(meta.getVendorUrl()); @@ -68,6 +73,21 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent return product; } + private static MultilingualismValue mappingMultilingualismValueByMetaJSONFile(List list) { + MultilingualismValue value = new MultilingualismValue(); + 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()); + } + } + } + + return value; + } + private static String extractParentDirectory(GHContent ghContent) { var path = StringUtils.defaultIfEmpty(ghContent.getPath(), EMPTY); return path.replace(ghContent.getName(), EMPTY); diff --git a/src/main/java/com/axonivy/market/github/model/Meta.java b/src/main/java/com/axonivy/market/github/model/Meta.java index 4ef988820..ee80857de 100644 --- a/src/main/java/com/axonivy/market/github/model/Meta.java +++ b/src/main/java/com/axonivy/market/github/model/Meta.java @@ -2,6 +2,7 @@ import java.util.List; +import com.axonivy.market.model.DisplayValue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -19,8 +20,8 @@ public class Meta { @JsonProperty("$schema") private String schema; private String id; - private String name; - private String description; + private List names; + private List descriptions; private String type; private String platformReview; private String sourceUrl; diff --git a/src/main/java/com/axonivy/market/github/service/GitHubService.java b/src/main/java/com/axonivy/market/github/service/GitHubService.java index 2f4fb7e16..7dd5009db 100644 --- a/src/main/java/com/axonivy/market/github/service/GitHubService.java +++ b/src/main/java/com/axonivy/market/github/service/GitHubService.java @@ -16,7 +16,7 @@ public interface GitHubService { public GHRepository getRepository(String repositoryPath) throws IOException; - public List getDirectoryContent(GHRepository ghRepository, String path) throws IOException; + public List getDirectoryContent(GHRepository ghRepository, String path, String ref) throws IOException; - public GHContent getGHContent(GHRepository ghRepository, String path) throws IOException; + public GHContent getGHContent(GHRepository ghRepository, String path, String ref) throws IOException; } diff --git a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java index 0d5202c18..6c96bee26 100644 --- a/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java +++ b/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyMarketRepoServiceImpl.java @@ -29,7 +29,6 @@ @Log4j2 @Service public class GHAxonIvyMarketRepoServiceImpl implements GHAxonIvyMarketRepoService { - private static final String DEFAULT_BRANCH = "master"; private static final LocalDateTime INITIAL_COMMIT_DATE = LocalDateTime.of(2020, 10, 30, 0, 0); private GHOrganization organization; private GHRepository repository; @@ -45,7 +44,7 @@ public Map> fetchAllMarketItems() { Map> ghContentMap = new HashMap<>(); try { List directoryContent = gitHubService.getDirectoryContent(getRepository(), - GitHubConstants.AXONIVY_MARKETPLACE_PATH); + GitHubConstants.AXONIVY_MARKETPLACE_PATH, GitHubConstants.DEFAULT_BRANCH); for (var content : directoryContent) { extractFileInDirectoryContent(content, ghContentMap); } @@ -85,7 +84,7 @@ public GHCommit getLastCommit(long lastCommitTime) { } private GHCommitQueryBuilder createQueryCommitsBuilder(long lastCommitTime) { - return getRepository().queryCommits().since(lastCommitTime).from(DEFAULT_BRANCH); + return getRepository().queryCommits().since(lastCommitTime).from(GitHubConstants.DEFAULT_BRANCH); } @Override diff --git a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java index b1a069ed5..62bbd9181 100644 --- a/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java +++ b/src/main/java/com/axonivy/market/github/service/impl/GitHubServiceImpl.java @@ -34,9 +34,9 @@ public GHOrganization getOrganization(String orgName) throws IOException { } @Override - public List getDirectoryContent(GHRepository ghRepository, String path) throws IOException { + public List getDirectoryContent(GHRepository ghRepository, String path, String ref) throws IOException { Assert.notNull(ghRepository, "Repository must not be null"); - return ghRepository.getDirectoryContent(path); + return ghRepository.getDirectoryContent(path, ref); } @Override @@ -45,8 +45,8 @@ public GHRepository getRepository(String repositoryPath) throws IOException { } @Override - public GHContent getGHContent(GHRepository ghRepository, String path) throws IOException { + public GHContent getGHContent(GHRepository ghRepository, String path, String ref) throws IOException { Assert.notNull(ghRepository, "Repository must not be null"); - return ghRepository.getFileContent(path); + return ghRepository.getFileContent(path, ref); } } \ No newline at end of file diff --git a/src/main/java/com/axonivy/market/model/DisplayValue.java b/src/main/java/com/axonivy/market/model/DisplayValue.java new file mode 100644 index 000000000..cd0bf4abf --- /dev/null +++ b/src/main/java/com/axonivy/market/model/DisplayValue.java @@ -0,0 +1,44 @@ +package com.axonivy.market.model; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class DisplayValue { + + private String locale; + private String value; + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DisplayValue)) { + return false; + } + DisplayValue other = (DisplayValue) obj; + EqualsBuilder builder = new EqualsBuilder(); + builder.append(value, other.getValue()); + builder.append(locale, other.locale); + return builder.isEquals(); + } + + @Override + public int hashCode() { + HashCodeBuilder builder = new HashCodeBuilder(); + builder.append(getValue()); + builder.append(getLocale()); + return builder.hashCode(); + } +} diff --git a/src/main/java/com/axonivy/market/model/MultilingualismValue.java b/src/main/java/com/axonivy/market/model/MultilingualismValue.java new file mode 100644 index 000000000..58431cf8e --- /dev/null +++ b/src/main/java/com/axonivy/market/model/MultilingualismValue.java @@ -0,0 +1,17 @@ +package com.axonivy.market.model; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class MultilingualismValue implements Serializable { + private static final long serialVersionUID = -4193508237020296419L; + + private String en; + private String de; +} diff --git a/src/main/java/com/axonivy/market/model/ProductModel.java b/src/main/java/com/axonivy/market/model/ProductModel.java index f11a62244..7ba586438 100644 --- a/src/main/java/com/axonivy/market/model/ProductModel.java +++ b/src/main/java/com/axonivy/market/model/ProductModel.java @@ -21,8 +21,8 @@ @JsonInclude(Include.NON_NULL) public class ProductModel extends RepresentationModel { private String id; - private String name; - private String shortDescription; + private MultilingualismValue names; + private MultilingualismValue shortDescriptions; private String logoUrl; private String type; private List tags; diff --git a/src/main/java/com/axonivy/market/repository/ProductRepository.java b/src/main/java/com/axonivy/market/repository/ProductRepository.java index 1b18bad04..25ff5e794 100644 --- a/src/main/java/com/axonivy/market/repository/ProductRepository.java +++ b/src/main/java/com/axonivy/market/repository/ProductRepository.java @@ -22,9 +22,9 @@ public interface ProductRepository extends MongoRepository { @Query("{'marketDirectory': {$regex : ?0, $options: 'i'}}") Product findByMarketDirectoryRegex(String search); - @Query("{ $and: [ { $or: [ { 'name': { $regex: ?0, $options: 'i' } }, { 'shortDescription': { $regex: ?0, $options: 'i' } } ] }, { 'type': ?1 } ] }") - Page searchByKeywordAndType(String keyword, String type, Pageable unifiedPageabe); + @Query("{ $and: [ { $or: [ { 'names.?': { $regex: ?0, $options: 'i' } }, { 'shortDescriptions.?': { $regex: ?0, $options: 'i' } } ] }, { 'type': ?1 } ] }") + Page searchByKeywordAndType(String keyword, String type, String language, Pageable unifiedPageabe); - @Query("{ $or: [ { 'name': { $regex: ?0, $options: 'i' } }, { 'shortDescription': { $regex: ?0, $options: 'i' } } ] }") - Page searchByNameOrShortDescriptionRegex(String keyword, Pageable unifiedPageabe); + @Query("{ $or: [ { 'names.?1': { $regex: ?0, $options: 'i' } }, { 'shortDescriptions.?1': { $regex: ?0, $options: 'i' } } ] }") + Page searchByNameOrShortDescriptionRegex(String keyword, String language, Pageable unifiedPageabe); } diff --git a/src/main/java/com/axonivy/market/service/ProductService.java b/src/main/java/com/axonivy/market/service/ProductService.java index 0a3f5529f..5e35368de 100644 --- a/src/main/java/com/axonivy/market/service/ProductService.java +++ b/src/main/java/com/axonivy/market/service/ProductService.java @@ -6,7 +6,7 @@ import com.axonivy.market.entity.Product; public interface ProductService { - Page findProducts(String type, String keyword, Pageable pageable); + Page findProducts(String type, String keyword, String language, Pageable pageable); boolean syncLatestDataFromMarketRepo(); } diff --git a/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java b/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java index 44de63b60..70f7a3a16 100644 --- a/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java +++ b/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java @@ -63,23 +63,23 @@ public ProductServiceImpl(ProductRepository productRepository, GHAxonIvyMarketRe } @Override - public Page findProducts(String type, String keyword, Pageable pageable) { + public Page findProducts(String type, String keyword, String language, Pageable pageable) { final var typeOption = TypeOption.of(type); - final var searchPageable = refinePagination(pageable); + final var searchPageable = refinePagination(language, pageable); Page result = Page.empty(); switch (typeOption) { case ALL: if (StringUtils.isBlank(keyword)) { result = productRepository.findAll(searchPageable); } else { - result = productRepository.searchByNameOrShortDescriptionRegex(keyword, searchPageable); + result = productRepository.searchByNameOrShortDescriptionRegex(keyword, language, searchPageable); } break; case CONNECTORS, UTILITIES, SOLUTIONS: if (StringUtils.isBlank(keyword)) { result = productRepository.findByType(typeOption.getCode(), searchPageable); } else { - result = productRepository.searchByKeywordAndType(keyword, typeOption.getCode(), searchPageable); + result = productRepository.searchByKeywordAndType(keyword, typeOption.getCode(), language, searchPageable); } break; default: @@ -136,7 +136,8 @@ private void updateLatestChangeToProductsFromGithubRepo() { Product product = new Product(); GHContent fileContent; try { - fileContent = gitHubService.getGHContent(axonIvyMarketRepoService.getRepository(), file.getFileName()); + fileContent = gitHubService.getGHContent(axonIvyMarketRepoService.getRepository(), file.getFileName(), + GitHubConstants.DEFAULT_BRANCH); } catch (IOException e) { log.error("Get GHContent failed: ", e); continue; @@ -187,13 +188,13 @@ private void modifyProductByMetaContent(GitHubFile file, Product product) { } } - private Pageable refinePagination(Pageable pageable) { + private Pageable refinePagination(String language, Pageable pageable) { PageRequest pageRequest = (PageRequest) pageable; - if (pageable != null && pageable.getSort() != null) { + if (pageable != null) { List orders = new ArrayList<>(); for (var sort : pageable.getSort()) { final var sortOption = SortOption.of(sort.getProperty()); - Order order = new Order(sort.getDirection(), sortOption.getCode()); + Order order = new Order(sort.getDirection(), sortOption.getCode(language)); orders.add(order); } pageRequest = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(orders)); diff --git a/src/test/java/com/axonivy/market/controller/ProductControllerTest.java b/src/test/java/com/axonivy/market/controller/ProductControllerTest.java index a2c6f3ec7..00417f662 100644 --- a/src/test/java/com/axonivy/market/controller/ProductControllerTest.java +++ b/src/test/java/com/axonivy/market/controller/ProductControllerTest.java @@ -28,12 +28,15 @@ 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; @ExtendWith(MockitoExtension.class) class ProductControllerTest { 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."; + private static final String PRODUCT_DESC_DE_SAMPLE = "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data. DE"; @Mock private ProductService service; @@ -59,9 +62,9 @@ void setup() { void testFindProductsAsEmpty() { PageRequest pageable = PageRequest.of(0, 20); Page mockProducts = new PageImpl(List.of(), pageable, 0); - when(service.findProducts(any(), any(), any())).thenReturn(mockProducts); + when(service.findProducts(any(), any(), any(), any())).thenReturn(mockProducts); when(pagedResourcesAssembler.toEmptyModel(any(), any())).thenReturn(PagedModel.empty()); - var result = productController.findProducts(TypeOption.ALL.getOption(), null, pageable); + var result = productController.findProducts(TypeOption.ALL.getOption(), null, "en", pageable); assertEquals(HttpStatus.OK, result.getStatusCode()); assertTrue(result.hasBody()); assertEquals(0, result.getBody().getContent().size()); @@ -73,16 +76,17 @@ void testFindProducts() { Product mockProduct = createProductMock(); Page mockProducts = new PageImpl(List.of(mockProduct), pageable, 1); - when(service.findProducts(any(), any(), any())).thenReturn(mockProducts); + when(service.findProducts(any(), any(), any(), any())).thenReturn(mockProducts); assembler = new ProductModelAssembler(); var mockProductModel = assembler.toModel(mockProduct); var mockPagedModel = PagedModel.of(List.of(mockProductModel), new PageMetadata(1, 0, 1)); when(pagedResourcesAssembler.toModel(any(), any(ProductModelAssembler.class))).thenReturn(mockPagedModel); - var result = productController.findProducts(TypeOption.ALL.getOption(), null, pageable); + var result = productController.findProducts(TypeOption.ALL.getOption(), "", "en", pageable); assertEquals(HttpStatus.OK, result.getStatusCode()); assertTrue(result.hasBody()); assertEquals(1, result.getBody().getContent().size()); - assertEquals(PRODUCT_NAME_SAMPLE, result.getBody().getContent().iterator().next().getName()); + assertEquals(PRODUCT_NAME_SAMPLE, result.getBody().getContent().iterator().next().getNames().getEn()); + assertEquals(PRODUCT_NAME_DE_SAMPLE, result.getBody().getContent().iterator().next().getNames().getDe()); } @Test @@ -96,8 +100,14 @@ void testSyncProducts() { private Product createProductMock() { Product mockProduct = new Product(); mockProduct.setId("amazon-comprehend"); - mockProduct.setName(PRODUCT_NAME_SAMPLE); - mockProduct.setShortDescription(PRODUCT_DESC_SAMPLE); + MultilingualismValue name = new MultilingualismValue(); + name.setEn(PRODUCT_NAME_SAMPLE); + name.setDe(PRODUCT_NAME_DE_SAMPLE); + mockProduct.setNames(name); + MultilingualismValue shortDescription = new MultilingualismValue(); + shortDescription.setEn(PRODUCT_DESC_SAMPLE); + shortDescription.setDe(PRODUCT_DESC_DE_SAMPLE); + mockProduct.setShortDescriptions(shortDescription); mockProduct.setType("connector"); mockProduct.setTags(List.of("AI")); return mockProduct; diff --git a/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java b/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java index 96d27d5bf..0221c4ce6 100644 --- a/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java +++ b/src/test/java/com/axonivy/market/factory/ProductFactoryTest.java @@ -22,54 +22,56 @@ @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"; + private static final String DUMMY_LOGO_URL = + "https://raw.githubusercontent.com/axonivy-market/market/master/market/connector/amazon-comprehend-connector/logo.png"; - @Test - void testMappingByGHContent() throws IOException { - Product product = new Product(); - GHContent mockContent = mock(GHContent.class); - var result = ProductFactory.mappingByGHContent(product, null); - assertEquals(product, result); - when(mockContent.getName()).thenReturn(CommonConstants.META_FILE); - InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); - when(mockContent.read()).thenReturn(inputStream); - result = ProductFactory.mappingByGHContent(product, mockContent); - assertNotEquals(null, result); - assertEquals("Amazon Comprehend", result.getName()); - } + @Test + void testMappingByGHContent() throws IOException { + Product product = new Product(); + GHContent mockContent = mock(GHContent.class); + var result = ProductFactory.mappingByGHContent(product, null); + assertEquals(product, result); + when(mockContent.getName()).thenReturn(CommonConstants.META_FILE); + InputStream inputStream = this.getClass().getResourceAsStream(SLASH.concat(META_FILE)); + 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()); + } - @Test - void testMappingLogo() throws IOException { - Product product = new Product(); - GHContent content = mock(GHContent.class); - when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); - var result = ProductFactory.mappingByGHContent(product, content); - assertNotEquals(null, result); + @Test + void testMappingLogo() throws IOException { + Product product = new Product(); + GHContent content = mock(GHContent.class); + when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); + var result = ProductFactory.mappingByGHContent(product, content); + assertNotEquals(null, result); - when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); - when(content.getDownloadUrl()).thenReturn(DUMMY_LOGO_URL); - result = ProductFactory.mappingByGHContent(product, content); - assertNotEquals(null, result); - } + when(content.getName()).thenReturn(CommonConstants.LOGO_FILE); + when(content.getDownloadUrl()).thenReturn(DUMMY_LOGO_URL); + result = ProductFactory.mappingByGHContent(product, content); + assertNotEquals(null, result); + } - @Test - void testExtractSourceUrl() throws IOException { - Product product = new Product(); - Meta meta = new Meta(); - ProductFactory.extractSourceUrl(product, meta); - Assertions.assertNull(product.getRepositoryName()); - Assertions.assertNull(product.getSourceUrl()); + @Test + void testExtractSourceUrl() throws IOException { + Product product = new Product(); + Meta meta = new Meta(); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertNull(product.getRepositoryName()); + Assertions.assertNull(product.getSourceUrl()); - String sourceUrl = "https://github.com/axonivy-market/alfresco-connector"; - meta.setSourceUrl(sourceUrl); - ProductFactory.extractSourceUrl(product, meta); - Assertions.assertEquals("axonivy-market/alfresco-connector", product.getRepositoryName()); - Assertions.assertEquals(sourceUrl, product.getSourceUrl()); + String sourceUrl = "https://github.com/axonivy-market/alfresco-connector"; + meta.setSourceUrl(sourceUrl); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertEquals("axonivy-market/alfresco-connector", product.getRepositoryName()); + Assertions.assertEquals(sourceUrl, product.getSourceUrl()); - sourceUrl = "portal"; - meta.setSourceUrl(sourceUrl); - ProductFactory.extractSourceUrl(product, meta); - Assertions.assertEquals(sourceUrl, product.getRepositoryName()); - Assertions.assertEquals(sourceUrl, product.getSourceUrl()); - } + sourceUrl = "portal"; + meta.setSourceUrl(sourceUrl); + ProductFactory.extractSourceUrl(product, meta); + Assertions.assertEquals(sourceUrl, product.getRepositoryName()); + Assertions.assertEquals(sourceUrl, product.getSourceUrl()); + } } diff --git a/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java b/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java index d8b06b074..fa74bc54a 100644 --- a/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/GHAxonIvyMarketRepoServiceImplTest.java @@ -73,7 +73,7 @@ void testFetchAllMarketItems() throws IOException { mockGhContents.add(mockGHContent); when(mockGHFileContent.isFile()).thenReturn(true); when(pagedGHContent.toList()).thenReturn(List.of(mockGHFileContent)); - when(gitHubService.getDirectoryContent(any(), any())).thenReturn(mockGhContents); + when(gitHubService.getDirectoryContent(any(), any(), any())).thenReturn(mockGhContents); ghContentMap = axonIvyMarketRepoServiceImpl.fetchAllMarketItems(); assertEquals(1, ghContentMap.values().size()); diff --git a/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java b/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java index ea3bd0fe7..e26226c6b 100644 --- a/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/GitHubServiceImplTest.java @@ -41,14 +41,14 @@ void testGetGithubContent() throws IOException { var mockGHContent = mock(GHContent.class); final String dummryURL = DUMMY_API_URL.concat("/dummry-content"); when(mockGHContent.getUrl()).thenReturn(dummryURL); - when(ghRepository.getFileContent(any())).thenReturn(mockGHContent); - var result = gitHubService.getGHContent(ghRepository, ""); + when(ghRepository.getFileContent(any(), any())).thenReturn(mockGHContent); + var result = gitHubService.getGHContent(ghRepository, "", ""); assertEquals(dummryURL, result.getUrl()); } @Test void testGetDirectoryContent() throws IOException { - var result = gitHubService.getDirectoryContent(ghRepository, ""); + var result = gitHubService.getDirectoryContent(ghRepository, "", ""); assertEquals(0, result.size()); } diff --git a/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java b/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java index 5671a21c5..bcebfd4e8 100644 --- a/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/ProductServiceImplTest.java @@ -46,6 +46,7 @@ import com.axonivy.market.github.model.GitHubFile; import com.axonivy.market.github.service.GHAxonIvyMarketRepoService; 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; @@ -60,6 +61,7 @@ class ProductServiceImplTest { Sort.by(SortOption.ALPHABETICALLY.getOption()).descending()); private static final String SHA1_SAMPLE = "35baa89091b2452b77705da227f1a964ecabc6c8"; private String keyword; + private String langague; private Page mockResultReturn; @Mock @@ -84,21 +86,22 @@ public void setup() { @Test void testFindProducts() { + langague = "en"; // Start testing by All when(productRepository.findAll(any(Pageable.class))).thenReturn(mockResultReturn); // Executes - var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, PAGEABLE); + var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, langague, PAGEABLE); assertEquals(mockResultReturn, result); // Start testing by Connector when(productRepository.findByType(any(), any(Pageable.class))).thenReturn(mockResultReturn); // Executes - result = productService.findProducts(TypeOption.CONNECTORS.getOption(), keyword, PAGEABLE); + result = productService.findProducts(TypeOption.CONNECTORS.getOption(), keyword, langague, PAGEABLE); assertEquals(mockResultReturn, result); // Start testing by Other // Executes - result = productService.findProducts(TypeOption.DEMOS.getOption(), keyword, PAGEABLE); + result = productService.findProducts(TypeOption.DEMOS.getOption(), keyword, langague, PAGEABLE); assertEquals(0, result.getSize()); } @@ -116,7 +119,7 @@ void testSyncProductsAsUpdateMetaJSONFromGitHub() throws IOException { mockGithubFile.setStatus(FileStatus.ADDED); when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGithubFile)); var mockGHContent = mockGHContentAsMetaJSON(); - when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + when(gitHubService.getGHContent(any(), anyString(), anyString())).thenReturn(mockGHContent); // Executes var result = productService.syncLatestDataFromMarketRepo(); @@ -147,7 +150,7 @@ void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { mockGitHubFile.setStatus(FileStatus.ADDED); when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); var mockGHContent = mockGHContentAsMetaJSON(); - when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + when(gitHubService.getGHContent(any(), anyString(), anyString())).thenReturn(mockGHContent); // Executes var result = productService.syncLatestDataFromMarketRepo(); @@ -157,7 +160,7 @@ void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { when(mockCommit.getSHA1()).thenReturn(UUID.randomUUID().toString()); mockGitHubFile.setStatus(FileStatus.REMOVED); when(marketRepoService.fetchMarketItemsBySHA1Range(any(), any())).thenReturn(List.of(mockGitHubFile)); - when(gitHubService.getGHContent(any(), anyString())).thenReturn(mockGHContent); + when(gitHubService.getGHContent(any(), anyString(), anyString())).thenReturn(mockGHContent); when(productRepository.findByLogoUrl(any())).thenReturn(new Product()); // Executes @@ -167,30 +170,31 @@ void testSyncProductsAsUpdateLogoFromGitHub() throws IOException { @Test void testFindAllProductsWithKeyword() throws IOException { + langague = "en"; when(productRepository.findAll(any(Pageable.class))).thenReturn(mockResultReturn); // Executes - var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, PAGEABLE); + var result = productService.findProducts(TypeOption.ALL.getOption(), keyword, langague, PAGEABLE); assertEquals(mockResultReturn, result); verify(productRepository).findAll(any(Pageable.class)); // Test has keyword - when(productRepository.searchByNameOrShortDescriptionRegex(any(), any(Pageable.class))) + when(productRepository.searchByNameOrShortDescriptionRegex(any(), any(), any(Pageable.class))) .thenReturn(new PageImpl<>(mockResultReturn.stream() - .filter(product -> product.getName().equals(SAMPLE_PRODUCT_NAME)).collect(Collectors.toList()))); + .filter(product -> product.getNames().getEn().equals(SAMPLE_PRODUCT_NAME)).collect(Collectors.toList()))); // Executes - result = productService.findProducts(TypeOption.ALL.getOption(), SAMPLE_PRODUCT_NAME, PAGEABLE); + 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).getName()); + assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().getEn()); // Test has keyword and type is connector - when(productRepository.searchByKeywordAndType(any(), any(), any(Pageable.class))).thenReturn( - new PageImpl<>(mockResultReturn.stream().filter(product -> product.getName().equals(SAMPLE_PRODUCT_NAME) + 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()))); // Executes - result = productService.findProducts(TypeOption.CONNECTORS.getOption(), SAMPLE_PRODUCT_NAME, PAGEABLE); + result = productService.findProducts(TypeOption.CONNECTORS.getOption(), SAMPLE_PRODUCT_NAME, langague, PAGEABLE); assertTrue(result.hasContent()); - assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getName()); + assertEquals(SAMPLE_PRODUCT_NAME, result.getContent().get(0).getNames().getEn()); } @Test @@ -230,24 +234,29 @@ void testSearchProducts() { var simplePageable = PageRequest.of(0, 20); String type = TypeOption.ALL.getOption(); keyword = "on"; - when(productRepository.searchByNameOrShortDescriptionRegex(keyword, simplePageable)).thenReturn(mockResultReturn); + langague = "en"; + when(productRepository.searchByNameOrShortDescriptionRegex(keyword, langague, simplePageable)).thenReturn(mockResultReturn); - var result = productService.findProducts(type, keyword, simplePageable); + var result = productService.findProducts(type, keyword, langague, simplePageable); assertEquals(result, mockResultReturn); - verify(productRepository).searchByNameOrShortDescriptionRegex(keyword, simplePageable); + verify(productRepository).searchByNameOrShortDescriptionRegex(keyword, langague, simplePageable); } private Page createPageProductsMock() { var mockProducts = new ArrayList(); + MultilingualismValue name = new MultilingualismValue(); Product mockProduct = new Product(); mockProduct.setId(SAMPLE_PRODUCT_ID); - mockProduct.setName(SAMPLE_PRODUCT_NAME); + name.setEn(SAMPLE_PRODUCT_NAME); + mockProduct.setNames(name); mockProduct.setType("connector"); mockProducts.add(mockProduct); mockProduct = new Product(); mockProduct.setId("tel-search-ch-connector"); - mockProduct.setName("Swiss phone directory"); + name = new MultilingualismValue(); + name.setEn("Swiss phone directory"); + mockProduct.setNames(name); mockProduct.setType("util"); mockProducts.add(mockProduct); return new PageImpl<>(mockProducts); diff --git a/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java b/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java index 932b7a864..f9f75b439 100644 --- a/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java +++ b/src/test/java/com/axonivy/market/service/VersionServiceImplTest.java @@ -1,5 +1,31 @@ package com.axonivy.market.service; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.api.Fail; +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.kohsuke.github.GHContent; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + import com.axonivy.market.constants.MavenConstants; import com.axonivy.market.constants.NonStandardProductPackageConstants; import com.axonivy.market.entity.MavenArtifactModel; @@ -13,26 +39,7 @@ import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.impl.VersionServiceImpl; import com.axonivy.market.utils.XmlReaderUtils; -import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.api.Fail; -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.kohsuke.github.GHContent; -import org.mockito.*; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; - -import java.io.IOException; -import java.util.*; -@Log4j2 @ExtendWith(MockitoExtension.class) class VersionServiceImplTest { private String repoName; diff --git a/src/test/resources/meta.json b/src/test/resources/meta.json index 90fb8f309..d46c28424 100644 --- a/src/test/resources/meta.json +++ b/src/test/resources/meta.json @@ -1,8 +1,26 @@ { "$schema": "https://json-schema.axonivy.com/market/10.0.0/meta.json", "id": "amazon-comprehend", - "name": "Amazon Comprehend", - "description": "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data.", + "names": [ + { + "locale":"en", + "value": "Amazon Comprehend" + }, + { + "locale":"de", + "value": "Amazon Comprehend DE" + } + ], + "descriptions": [ + { + "locale":"en", + "value": "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data." + }, + { + "locale":"de", + "value": "Amazon Comprehend is a AI service that uses machine learning to uncover information in unstructured data. DE" + } + ], "type": "connector", "platformReview": "4.5", "sourceUrl": "https://github.com/axonivy-market/amazon-comprehend-connector",