From d89c12adb16c86141a22eb5139ab2f557daba457 Mon Sep 17 00:00:00 2001 From: Pham Hoang Hung <84316773+phhung-axonivy@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:12:47 +0700 Subject: [PATCH 1/2] MARP-997 recent sorting should be based on first publish date (#255) --- .../constants/RequestMappingConstants.java | 2 +- .../market/controller/ProductController.java | 19 +++ .../com/axonivy/market/entity/Product.java | 1 + .../com/axonivy/market/enums/SortOption.java | 2 +- .../market/service/ProductService.java | 2 +- .../service/impl/ProductServiceImpl.java | 93 +++++++++++--- .../java/com/axonivy/market/BaseSetup.java | 1 + .../controller/ProductControllerTest.java | 28 +++++ .../service/impl/ProductServiceImplTest.java | 113 +++++++++++++++++- 9 files changed, 237 insertions(+), 24 deletions(-) diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java index 66b14b6d..30ac7fe3 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/RequestMappingConstants.java @@ -13,7 +13,7 @@ public class RequestMappingConstants { public static final String FEEDBACK = API + "/feedback"; public static final String IMAGE = API + "/image"; public static final String SYNC = "sync"; - public static final String SYNC_PRODUCT_VERSION = SYNC + "/product-version"; + public static final String SYNC_FIRST_PUBLISHED_DATE_ALL_PRODUCTS = SYNC + "/first-published-date"; public static final String SYNC_ONE_PRODUCT_BY_ID = "sync/{id}"; public static final String SWAGGER_URL = "/swagger-ui/index.html"; public static final String GIT_HUB_LOGIN = "/github/login"; diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java index 4598b808..44f767ba 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java @@ -145,6 +145,25 @@ public ResponseEntity syncOneProduct( return new ResponseEntity<>(message, HttpStatus.OK); } + @PutMapping(SYNC_FIRST_PUBLISHED_DATE_ALL_PRODUCTS) + @Operation(hidden = true) + public ResponseEntity syncFirstPublishedDateOfAllProducts( + @RequestHeader(value = AUTHORIZATION) String authorizationHeader) { + String token = AuthorizationUtils.getBearerToken(authorizationHeader); + gitHubService.validateUserInOrganizationAndTeam(token, GitHubConstants.AXONIVY_MARKET_ORGANIZATION_NAME, + GitHubConstants.AXONIVY_MARKET_TEAM_NAME); + + var message = new Message(); + var isSuccess = productService.syncFirstPublishedDateOfAllProducts(); + if (isSuccess) { + message.setHelpCode(ErrorCode.SUCCESSFUL.getCode()); + message.setMessageDetails("Sync successfully!"); + } else { + message.setMessageDetails("Sync unsuccessfully!"); + } + return new ResponseEntity<>(message, HttpStatus.OK); + } + @SuppressWarnings("unchecked") private ResponseEntity> generateEmptyPagedModel() { var emptyPagedModel = (PagedModel) pagedResourcesAssembler.toEmptyModel(Page.empty(), diff --git a/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java b/marketplace-service/src/main/java/com/axonivy/market/entity/Product.java index 85fc6292..bfe5eeac 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 @@ -64,6 +64,7 @@ public class Product implements Serializable { @Transient private int installationCount; private Date newestPublishedDate; + private Date firstPublishedDate; private String newestReleaseVersion; @Transient private ProductModuleContent productModuleContent; diff --git a/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java b/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java index 104e18b0..d46ba971 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java +++ b/marketplace-service/src/main/java/com/axonivy/market/enums/SortOption.java @@ -11,7 +11,7 @@ public enum SortOption { POPULARITY("popularity", "marketplaceData.installationCount", Sort.Direction.DESC), ALPHABETICALLY("alphabetically", "names", Sort.Direction.ASC), - RECENT("recent", "newestPublishedDate", Sort.Direction.DESC), + RECENT("recent", "firstPublishedDate", Sort.Direction.DESC), STANDARD("standard", "marketplaceData.customOrder", Sort.Direction.DESC), ID("id", "_id", Sort.Direction.ASC); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java index 3e88d59d..800b3743 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ProductService.java @@ -21,5 +21,5 @@ public interface ProductService { boolean syncOneProduct(String productId, String marketItemPath, Boolean overrideMarketItemPath); - void clearAllProductVersion(); + boolean syncFirstPublishedDateOfAllProducts(); } 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 24de8623..c26a41dd 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 @@ -41,6 +41,7 @@ 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.PageRequest; @@ -57,15 +58,7 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import static com.axonivy.market.constants.CommonConstants.SLASH; import static com.axonivy.market.constants.MavenConstants.*; @@ -370,8 +363,8 @@ private List syncProductsFromGitHubRepo(Boolean resetSync) { } else if (productRepo.findById(product.getId()).isPresent()) { continue; } - updateProductContentForNonStandardProduct(ghContentEntity.getValue(), product); + updateFirstPublishedDate(product); updateProductFromReleasedVersions(product); transferComputedDataFromDB(product); productMarketplaceDataRepo.checkAndInitProductMarketplaceDataIfNotExist(product.getId()); @@ -412,6 +405,53 @@ private String mapVendorImage(String productId, GHContent ghContent, String imag return EMPTY; } + private void updateFirstPublishedDate(Product product) { + try { + if (StringUtils.isNotBlank(product.getRepositoryName())) { + List gitHubTags = gitHubService.getRepositoryTags(product.getRepositoryName()); + Date firstTagPublishedDate = getFirstTagPublishedDate(gitHubTags); + product.setFirstPublishedDate(firstTagPublishedDate); + } + } catch (IOException e) { + log.error("Get GH Tags failed: ", e); + } + } + + private Date getFirstTagPublishedDate(List gitHubTags) { + Date firstTagPublishedDate = null; + try { + if (!CollectionUtils.isEmpty(gitHubTags)) { + List sortedTags = sortByTagCommitDate(gitHubTags); + GHCommit commit = sortedTags.get(0).getCommit(); + if (commit != null) { + firstTagPublishedDate = commit.getCommitDate(); + } + } + } catch (IOException e) { + log.error("Get first tag published date failed: ", e); + } + + return firstTagPublishedDate; + } + + private List sortByTagCommitDate(List gitHubTags) { + List sortedTags = new ArrayList<>(gitHubTags); + sortedTags.sort(Comparator.comparing(this::sortByCommitDate, Comparator.nullsLast(Comparator.naturalOrder()))); + return sortedTags; + } + + private Date sortByCommitDate(GHTag gitHubTag) { + Date commitDate = null; + try { + if (gitHubTag.getCommit() != null) { + commitDate = gitHubTag.getCommit().getCommitDate(); + } + } catch (IOException e) { + log.error("Get commit date of tag commit failed: ", e); + } + return commitDate; + } + private void updateProductFromReleasedVersions(Product product) { if (ObjectUtils.isEmpty(product.getArtifacts())) { return; @@ -536,7 +576,6 @@ private String createProductArtifactId(Artifact mavenArtifact) { : mavenArtifact.getArtifactId().concat(PRODUCT_ARTIFACT_POSTFIX); } - // Cover 3 cases after removing non-numeric characters (8, 11.1 and 10.0.2) @Override public String getCompatibilityFromOldestVersion(String oldestVersion) { @@ -628,6 +667,7 @@ public boolean syncOneProduct(String productId, String marketItemPath, Boolean o log.info("Update data of product {} from meta.json and logo files", productId); mappingMetaDataAndLogoFromGHContent(gitHubContents, product); updateProductContentForNonStandardProduct(gitHubContents, product); + updateFirstPublishedDate(product); updateProductFromReleasedVersions(product); productMarketplaceDataRepo.checkAndInitProductMarketplaceDataIfNotExist(productId); productRepo.save(product); @@ -640,13 +680,6 @@ public boolean syncOneProduct(String productId, String marketItemPath, Boolean o return false; } - @Override - public void clearAllProductVersion() { - metadataRepo.deleteAll(); - metadataSyncRepo.deleteAll(); - mavenArtifactVersionRepo.deleteAll(); - } - private Product renewProductById(String productId, String marketItemPath, Boolean overrideMarketItemPath) { Product product = new Product(); productRepo.findById(productId).ifPresent(foundProduct -> { @@ -692,4 +725,28 @@ private void updateProductContentForNonStandardProduct(List ghContent productModuleContentRepo.save(initialContent); } } + + @Override + public boolean syncFirstPublishedDateOfAllProducts() { + try { + List products = productRepo.findAll(); + if (!CollectionUtils.isEmpty(products)) { + for (Product product : products) { + if (product.getFirstPublishedDate() == null) { + log.info("sync FirstPublishedDate of product {} is starting ...", product.getId()); + updateFirstPublishedDate(product); + productRepo.save(product); + log.info("Sync FirstPublishedDate of product {} is finished!", product.getId()); + } else { + log.info("FirstPublishedDate of product {} is existing!", product.getId()); + } + } + } + log.info("sync FirstPublishedDate of all products is finished!"); + return true; + } catch (Exception e) { + log.error(e.getStackTrace()); + return false; + } + } } \ No newline at end of file diff --git a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java index 0e494e82..971097a6 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java +++ b/marketplace-service/src/test/java/com/axonivy/market/BaseSetup.java @@ -38,6 +38,7 @@ public class BaseSetup { protected static final String SAMPLE_PRODUCT_ID = "amazon-comprehend"; protected static final String SAMPLE_PRODUCT_PATH = "/market/connector/amazon-comprehend"; protected static final String SAMPLE_PRODUCT_NAME = "prody Comprehend"; + protected static final String SAMPLE_PRODUCT_REPOSITORY_NAME = "axonivy-market/amazon-comprehend"; protected static final Pageable PAGEABLE = PageRequest.of(0, 20, Sort.by(SortOption.ALPHABETICALLY.getOption()).descending()); protected static final String MOCK_PRODUCT_ID = "bpmn-statistic"; 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 e830e301..f46896c4 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 @@ -214,4 +214,32 @@ private Product createProductMock() { mockProduct.setTags(List.of("AI")); return mockProduct; } + + @Test + void testSyncFirstPublishedDateOfAllProductsInvalidToken() { + doThrow(new UnauthorizedException(ErrorCode.GITHUB_USER_UNAUTHORIZED.getCode(), + ErrorCode.GITHUB_USER_UNAUTHORIZED.getHelpText())).when(gitHubService) + .validateUserInOrganizationAndTeam(any(String.class), any(String.class), any(String.class)); + + UnauthorizedException exception = assertThrows(UnauthorizedException.class, + () -> productController.syncFirstPublishedDateOfAllProducts(INVALID_AUTHORIZATION_HEADER)); + + assertEquals(ErrorCode.GITHUB_USER_UNAUTHORIZED.getHelpText(), exception.getMessage()); + } + + @Test + void testSyncFirstPublishedDateOfAllProductsFailed() { + when(service.syncFirstPublishedDateOfAllProducts()).thenReturn(false); + var response = productController.syncFirstPublishedDateOfAllProducts(AUTHORIZATION_HEADER); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotEquals(ErrorCode.SUCCESSFUL.getCode(), response.getBody().getHelpCode()); + } + + @Test + void testSyncFirstPublishedDateOfAllProductsSuccess() { + when(service.syncFirstPublishedDateOfAllProducts()).thenReturn(true); + var response = productController.syncFirstPublishedDateOfAllProducts(AUTHORIZATION_HEADER); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(ErrorCode.SUCCESSFUL.getCode(), response.getBody().getHelpCode()); + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java index eb87febb..7896e389 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java @@ -40,12 +40,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.kohsuke.github.GHCommit; import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHTag; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.mockito.exceptions.base.MockitoException; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -56,6 +58,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -576,9 +579,8 @@ void testSyncOneProduct() throws IOException { var mockContents = mockMetaJsonAndLogoList(); when(marketRepoService.getMarketItemByPath(anyString())).thenReturn(mockContents); when(productRepo.save(any(Product.class))).thenReturn(mockProduct); - // Executes - var result = productService.syncOneProduct(SAMPLE_PRODUCT_ID, SAMPLE_PRODUCT_PATH, false); - assertTrue(result); + assertTrue(productService.syncOneProduct(SAMPLE_PRODUCT_ID, SAMPLE_PRODUCT_PATH, false)); + assertTrue(productService.syncOneProduct(SAMPLE_PRODUCT_ID, SAMPLE_PRODUCT_PATH, true)); } private List mockMetaJsonAndLogoList() throws IOException { @@ -590,6 +592,12 @@ private List mockMetaJsonAndLogoList() throws IOException { return new ArrayList<>(List.of(mockContent, mockContentLogo)); } + @Test + void testSyncOneProductFailed() { + when(marketRepoService.getMarketItemByPath(anyString())).thenThrow(new MockitoException("Sync a product failed!")); + assertFalse(productService.syncOneProduct(StringUtils.EMPTY, StringUtils.EMPTY, true)); + } + @Test void testSyncProductsAsUpdateMetaJSONFromGitHub_AddVendorLogo() throws IOException { // Start testing by adding new meta @@ -613,4 +621,103 @@ void testSyncProductsAsUpdateMetaJSONFromGitHub_AddVendorLogo() throws IOExcepti assertNotNull(result); assertTrue(result.isEmpty()); } + + @Test + void testSyncFirstPublishedDateOfAllProducts() throws IOException { + Product mockProduct = new Product(); + mockProduct.setId(SAMPLE_PRODUCT_ID); + mockProduct.setMarketDirectory(SAMPLE_PRODUCT_PATH); + mockProduct.setRepositoryName(SAMPLE_PRODUCT_REPOSITORY_NAME); + List products = Arrays.asList(mockProduct); + when(productRepo.findAll()).thenReturn(products); + when(productRepo.save(any(Product.class))).thenReturn(mockProduct); + GHTag ghTagVersionOne = new GHTag(); + GHTag ghTagVersionTwo = new GHTag(); + List tags = Arrays.asList(ghTagVersionOne, ghTagVersionTwo); + when(gitHubService.getRepositoryTags(SAMPLE_PRODUCT_REPOSITORY_NAME)).thenReturn(tags); + assertTrue(productService.syncFirstPublishedDateOfAllProducts()); + } + + @Test + void testNoSyncFirstPublishedDateForSyncedProducts() { + Product mockProduct = new Product(); + mockProduct.setId(SAMPLE_PRODUCT_ID); + mockProduct.setRepositoryName(SAMPLE_PRODUCT_REPOSITORY_NAME); + mockProduct.setFirstPublishedDate(new Date()); + when(productRepo.findAll()).thenReturn(Arrays.asList(mockProduct)); + assertTrue(productService.syncFirstPublishedDateOfAllProducts()); + } + + @Test + void testSyncFirstPublishedDateWithFindingAllProductsFailed() { + when(productRepo.findAll()).thenThrow(new MockitoException("Sync FirstPublishedDate of all products failed!")); + assertFalse(productService.syncFirstPublishedDateOfAllProducts()); + } + + @Test + void testSyncFirstPublishedDateForNoProduct() { + when(productRepo.findAll()).thenReturn(new ArrayList<>()); + assertTrue(productService.syncFirstPublishedDateOfAllProducts()); + } + + @Test + void testSyncFirstPublishedDateOfAllProductsFailed() throws IOException { + Product mockProduct = new Product(); + mockProduct.setRepositoryName(SAMPLE_PRODUCT_REPOSITORY_NAME); + List products = Arrays.asList(mockProduct); + when(productRepo.findAll()).thenReturn(products); + when(gitHubService.getRepositoryTags(SAMPLE_PRODUCT_REPOSITORY_NAME)).thenThrow( + new IOException("Mocked IOException")); + when(productRepo.save(mockProduct)).thenThrow( + new MockitoException("Mocked IOException")); + assertFalse(productService.syncFirstPublishedDateOfAllProducts()); + } + + @Test + void testSyncFirstPublishedDateOfAllProductsSuccess() throws IOException { + Product mockProduct = new Product(); + mockProduct.setId(SAMPLE_PRODUCT_ID); + mockProduct.setMarketDirectory(SAMPLE_PRODUCT_PATH); + mockProduct.setRepositoryName(SAMPLE_PRODUCT_REPOSITORY_NAME); + List products = Arrays.asList(mockProduct); + when(productRepo.findAll()).thenReturn(products); + when(productRepo.save(any(Product.class))).thenReturn(mockProduct); + GHTag ghTagVersionOne = mock(GHTag.class); + GHCommit commitOfTagVersionOne = mock(GHCommit.class); + GHTag ghTagVersionTwo = mock(GHTag.class); + GHCommit commitOfTagVersionTwo = mock(GHCommit.class); + List tags = Arrays.asList(ghTagVersionOne, ghTagVersionTwo); + when(ghTagVersionOne.getCommit()).thenReturn(commitOfTagVersionOne); + when(commitOfTagVersionOne.getCommitDate()).thenReturn(new Date()); + when(ghTagVersionTwo.getCommit()).thenReturn(commitOfTagVersionTwo); + when(commitOfTagVersionTwo.getCommitDate()).thenReturn(new Date()); + when(gitHubService.getRepositoryTags(SAMPLE_PRODUCT_REPOSITORY_NAME)).thenReturn(tags); + assertTrue(productService.syncFirstPublishedDateOfAllProducts()); + } + + @Test + void testSyncFirstPublishedDateWithGettingTagCommitFailed() throws IOException { + Product mockProduct = new Product(); + mockProduct.setId(SAMPLE_PRODUCT_ID); + mockProduct.setMarketDirectory(SAMPLE_PRODUCT_PATH); + mockProduct.setRepositoryName(SAMPLE_PRODUCT_REPOSITORY_NAME); + List products = Arrays.asList(mockProduct); + when(productRepo.findAll()).thenReturn(products); + GHTag ghTag = mock(GHTag.class); + List tags = Arrays.asList(ghTag); + GHCommit ghCommit = mock(GHCommit.class); + when(ghTag.getCommit()).thenReturn(ghCommit); + when(ghCommit.getCommitDate()).thenThrow( + new IOException("get commit date of tag commit failed!")); + when(gitHubService.getRepositoryTags(SAMPLE_PRODUCT_REPOSITORY_NAME)).thenReturn(tags); + assertTrue(productService.syncFirstPublishedDateOfAllProducts()); + + GHTag ghTag2 = mock(GHTag.class); + List secondTags = Arrays.asList(ghTag, ghTag2); + GHCommit ghCommit2 = mock(GHCommit.class); + when(ghTag2.getCommit()).thenReturn(ghCommit2); + when(ghCommit2.getCommitDate()).thenReturn(new Date()); + when(gitHubService.getRepositoryTags(SAMPLE_PRODUCT_REPOSITORY_NAME)).thenReturn(secondTags); + assertTrue(productService.syncFirstPublishedDateOfAllProducts()); + } } From 4439b5dc64e64156f050a8574c0e0150d9e0c157 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen <127725498+ntqdinh-axonivy@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:33:28 +0700 Subject: [PATCH 2/2] MARP-1642 Application Freezing due to missing product detail content (#258) --- .../product-detail/product-detail.component.html | 2 +- .../product-detail.component.spec.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html index 129825a0..a7a2515f 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.html @@ -160,7 +160,7 @@

[id]="displayedTab.value" role="tabpanel" [attr.aria-labelledby]="displayedTab.value + '-tab'"> - @if (displayedTab.value === 'dependency') { + @if (displayedTab.value === 'dependency' && productDetail().productModuleContent) { diff --git a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts index d379ea5d..b3e14d5a 100644 --- a/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts +++ b/marketplace-ui/src/app/modules/product/product-detail/product-detail.component.spec.ts @@ -808,4 +808,20 @@ describe('ProductDetailComponent', () => { expect(rateConnector.childNodes[0].nativeNode.textContent).toContain("common.feedback.rateFeedbackForUtilityBtnLabel"); expect(rateConnectorEmptyText.childNodes[0].nativeNode.textContent).toContain("common.feedback.noFeedbackForUtilityLabel"); }); + + it('maven tab should not display when product module content is missing', () => { + const event = { value: 'dependency' }; + component.onTabChange(event.value); + fixture.detectChanges(); + let mavenTab = fixture.debugElement.query( + By.css('app-product-detail-maven-content') + ); + expect(mavenTab).toBeTruthy(); + component.productModuleContent.set({} as any as ProductModuleContent); + fixture.detectChanges(); + mavenTab = fixture.debugElement.query( + By.css('app-product-detail-maven-content') + ); + expect(mavenTab).toBeFalsy(); + }); });