Back to Repositories

Testing Namespace Service Implementation in Apollo Configuration System

This test suite evaluates the NamespaceService component in the Apollo configuration system, focusing on namespace management operations including creation, deletion, and usage tracking. The tests verify core functionality for both private and public namespaces while ensuring proper handling of configurations and user permissions.

Test Coverage Overview

The test suite provides comprehensive coverage of namespace operations in Apollo:

  • Namespace finding and loading functionality
  • Private namespace deletion workflows
  • Namespace usage tracking across environments
  • Empty namespace handling
  • Public namespace association testing

Edge cases include error handling for malformed configurations and validation of namespace permissions.

Implementation Analysis

The testing approach utilizes JUnit with extensive mocking via Mockito to isolate namespace service functionality. The tests employ a structured setup with @Before initialization and detailed test scenarios that verify both successful operations and error conditions. Mock implementations simulate the behavior of dependent services like AdminServiceAPI, ReleaseService, and ItemService.

Technical Details

  • Testing Framework: JUnit
  • Mocking Framework: Mockito
  • Test Environment: Local unit tests
  • Key Classes: NamespaceService, NamespaceDTO, AppNamespace
  • Configuration Format: Properties and XML

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive mock setup for dependent services
  • Clear test method naming conventions
  • Thorough assertion checking
  • Proper test isolation
  • Effective error scenario coverage

apolloconfig/apollo

apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/NamespaceServiceTest.java

            
/*
 * Copyright 2024 Apollo Authors
 *
 * 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.
 *
 */
package com.ctrip.framework.apollo.portal.service;

import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.portal.component.PortalSettings;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceUsage;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.AbstractUnitTest;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.component.txtresolver.PropertyResolver;
import com.ctrip.framework.apollo.portal.entity.bo.NamespaceBO;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;

import org.assertj.core.util.Lists;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class NamespaceServiceTest extends AbstractUnitTest {

  @Mock
  private AdminServiceAPI.NamespaceAPI namespaceAPI;
  @Mock
  private ReleaseService releaseService;
  @Mock
  private ItemService itemService;
  @Mock
  private PropertyResolver resolver;
  @Mock
  private AppNamespaceService appNamespaceService;
  @Mock
  private InstanceService instanceService;
  @Mock
  private NamespaceBranchService branchService;
  @Mock
  private UserInfoHolder userInfoHolder;
  @Mock
  private AdditionalUserInfoEnrichService additionalUserInfoEnrichService;
  @Mock
  private PortalSettings portalSettings;
  @Mock
  private ClusterService clusterService;

  @InjectMocks
  private NamespaceService namespaceService;

  private String testAppId = "6666";
  private String testClusterName = "default";
  private String testNamespaceName = "application";
  private Env testEnv = Env.DEV;

  @Before
  public void setup() {
  }

  @Test
  public void testFindNamespace() {

    AppNamespace applicationAppNamespace = mock(AppNamespace.class);
    AppNamespace hermesAppNamespace = mock(AppNamespace.class);

    NamespaceDTO application = new NamespaceDTO();
    application.setId(1);
    application.setClusterName(testClusterName);
    application.setAppId(testAppId);
    application.setNamespaceName(testNamespaceName);

    NamespaceDTO hermes = new NamespaceDTO();
    hermes.setId(2);
    hermes.setClusterName("default");
    hermes.setAppId(testAppId);
    hermes.setNamespaceName("hermes");
    List<NamespaceDTO> namespaces = Arrays.asList(application, hermes);

    ReleaseDTO someRelease = new ReleaseDTO();
    someRelease.setConfigurations("{\"a\":\"123\",\"b\":\"123\"}");

    ItemDTO i1 = new ItemDTO("a", "123", "", 1);
    ItemDTO i2 = new ItemDTO("b", "1", "", 2);
    ItemDTO i3 = new ItemDTO("", "", "#dddd", 3);
    ItemDTO i4 = new ItemDTO("c", "1", "", 4);
    List<ItemDTO> someItems = Arrays.asList(i1, i2, i3, i4);

    when(applicationAppNamespace.getFormat()).thenReturn(ConfigFileFormat.Properties.getValue());
    when(hermesAppNamespace.getFormat()).thenReturn(ConfigFileFormat.XML.getValue());
    when(appNamespaceService.findByAppIdAndName(testAppId, testNamespaceName))
        .thenReturn(applicationAppNamespace);
    when(appNamespaceService.findPublicAppNamespace("hermes")).thenReturn(hermesAppNamespace);
    when(namespaceAPI.findNamespaceByCluster(testAppId, Env.DEV, testClusterName)).thenReturn(namespaces);
    when(releaseService.loadLatestRelease(testAppId, Env.DEV, testClusterName,
                                          testNamespaceName)).thenReturn(someRelease);
    when(releaseService.loadLatestRelease(testAppId, Env.DEV, testClusterName, "hermes")).thenReturn(someRelease);
    when(itemService.findItems(testAppId, Env.DEV, testClusterName, testNamespaceName)).thenReturn(someItems);

    List<NamespaceBO> namespaceVOs = namespaceService.findNamespaceBOs(testAppId, Env.DEV, testClusterName);
    assertEquals(2, namespaceVOs.size());

    when(namespaceAPI.findNamespaceByCluster(testAppId, Env.DEV, testClusterName)).thenReturn(Lists.list(application));
    namespaceVOs = namespaceService.findNamespaceBOs(testAppId, Env.DEV, testClusterName);
    assertEquals(1, namespaceVOs.size());
    NamespaceBO namespaceVO = namespaceVOs.get(0);
    assertEquals(4, namespaceVO.getItems().size());
    assertEquals("a", namespaceVO.getItems().get(0).getItem().getKey());
    assertEquals(2, namespaceVO.getItemModifiedCnt());
    assertEquals(testAppId, namespaceVO.getBaseInfo().getAppId());
    assertEquals(testClusterName, namespaceVO.getBaseInfo().getClusterName());
    assertEquals(testNamespaceName, namespaceVO.getBaseInfo().getNamespaceName());

    ReleaseDTO errorRelease = new ReleaseDTO();
    errorRelease.setConfigurations("\"a\":\"123\",\"b\":\"123\"");
    when(releaseService.loadLatestRelease(testAppId, Env.DEV, testClusterName, testNamespaceName)).thenReturn(errorRelease);
    assertThatExceptionOfType(RuntimeException.class)
        .isThrownBy(()-> namespaceService.findNamespaceBOs(testAppId, Env.DEV, testClusterName))
        .withMessageStartingWith("Parse namespaces error, expected: 1, but actual: 0, cannot get those namespaces: [application]");

  }

  @Test
  public void testDeletePrivateNamespace() {
    String operator = "user";
    AppNamespace privateNamespace = createAppNamespace(testAppId, testNamespaceName, false);

    when(appNamespaceService.findByAppIdAndName(testAppId, testNamespaceName)).thenReturn(privateNamespace);

    when(userInfoHolder.getUser()).thenReturn(createUser(operator));

    namespaceService.deleteNamespace(testAppId, testEnv, testClusterName, testNamespaceName);

    verify(namespaceAPI, times(1)).deleteNamespace(testEnv, testAppId, testClusterName, testNamespaceName, operator);
  }

  @Test
  public void testGetNamespaceUsage() {
    AppNamespace publicNamespace = createAppNamespace(testAppId, testNamespaceName, true);
    String branchName = "branch";

    NamespaceDTO branch = createNamespace(testAppId, branchName, testNamespaceName);

    when(portalSettings.getActiveEnvs()).thenReturn(Lists.newArrayList(testEnv));
    ClusterDTO cluster = new ClusterDTO();
    cluster.setName(testClusterName);
    cluster.setAppId(testAppId);
    when(clusterService.findClusters(testEnv, testAppId)).thenReturn(Lists.newArrayList(cluster));
    when(appNamespaceService.findByAppIdAndName(testAppId, testNamespaceName)).thenReturn(publicNamespace);
    when(instanceService.getInstanceCountByNamespace(testAppId, testEnv, testClusterName, testNamespaceName))
        .thenReturn(8);
    when(branchService.findBranchBaseInfo(testAppId, testEnv, testClusterName, testNamespaceName)).thenReturn(branch);
    when(instanceService.getInstanceCountByNamespace(testAppId, testEnv, branchName, testNamespaceName)).thenReturn(9);
    when(appNamespaceService.findPublicAppNamespace(testNamespaceName)).thenReturn(publicNamespace);

    when(namespaceAPI.countPublicAppNamespaceAssociatedNamespaces(testEnv, testNamespaceName)).thenReturn(10);

    List<NamespaceUsage> usages = namespaceService.getNamespaceUsageByAppId(testAppId, testNamespaceName);
    assertThat(usages).asList().hasSize(1);
    assertThat(usages.get(0).getInstanceCount()).isEqualTo(8);
    assertThat(usages.get(0).getBranchInstanceCount()).isEqualTo(9);
    assertThat(usages.get(0).getLinkedNamespaceCount()).isEqualTo(10);

    NamespaceUsage usage = namespaceService.getNamespaceUsageByEnv(testAppId, testNamespaceName, testEnv, testClusterName);
    assertThat(usage).isNotNull();
    assertThat(usage.getInstanceCount()).isEqualTo(8);
    assertThat(usage.getBranchInstanceCount()).isEqualTo(9);
    assertThat(usage.getLinkedNamespaceCount()).isEqualTo(0);
  }

  @Test
  public void testDeleteEmptyNamespace() {
    String branchName = "branch";
    String operator = "user";

    AppNamespace publicNamespace = createAppNamespace(testAppId, testNamespaceName, true);
    NamespaceDTO branch = createNamespace(testAppId, branchName, testNamespaceName);

    when(appNamespaceService.findByAppIdAndName(testAppId, testNamespaceName)).thenReturn(publicNamespace);
    when(instanceService.getInstanceCountByNamespace(testAppId, testEnv, testClusterName, testNamespaceName))
        .thenReturn(0);
    when(branchService.findBranchBaseInfo(testAppId, testEnv, testClusterName, testNamespaceName)).thenReturn(branch);
    when(instanceService.getInstanceCountByNamespace(testAppId, testEnv, branchName, testNamespaceName)).thenReturn(0);
    when(appNamespaceService.findPublicAppNamespace(testNamespaceName)).thenReturn(publicNamespace);

    NamespaceDTO namespace = createNamespace(testAppId, testClusterName, testNamespaceName);
    when(namespaceAPI.getPublicAppNamespaceAllNamespaces(testEnv, testNamespaceName, 0, 10)).thenReturn(
        Collections.singletonList(namespace));
    when(userInfoHolder.getUser()).thenReturn(createUser(operator));

    namespaceService.deleteNamespace(testAppId, testEnv, testClusterName, testNamespaceName);

    verify(namespaceAPI, times(1)).deleteNamespace(testEnv, testAppId, testClusterName, testNamespaceName, operator);

  }

  @Test
  public void testLoadNamespaceBO() {
    boolean fillItemDetail = true;
    NamespaceBO namespaceBO = loadNamespaceBO(fillItemDetail);

    List<String> namespaceKey2 = namespaceBO.getItems().stream().map(s -> s.getItem().getKey()).collect(Collectors.toList());
    assertThat(namespaceBO.getItemModifiedCnt()).isEqualTo(2);
    assertThat(namespaceBO.getItems().size()).isEqualTo(2);
    assertThat(namespaceKey2).isEqualTo(Arrays.asList("k1", "k2"));
  }

  @Test
  public void testLoadNamespaceBOWithoutItemDetail() {
    boolean fillItemDetail = false;
    NamespaceBO namespaceBO = loadNamespaceBO(fillItemDetail);

    assertThat(namespaceBO.getItems().size()).isEqualTo(0);
  }

  private NamespaceBO loadNamespaceBO(boolean fillItemDetail) {
    when(namespaceAPI.loadNamespace(any(), any(), any(), any())).thenReturn(createNamespace(testAppId, "branch", testNamespaceName));
    when(releaseService.loadLatestRelease(any(), any(), any(), any())).thenReturn(createReleaseDTO());
    when(itemService.findItems(any(), any(), any(), any())).thenReturn(createItems());
    when(itemService.findDeletedItems(any(), any(), any(), any())).thenReturn(createDeletedItems());

    NamespaceBO namespaceBO1 = namespaceService.loadNamespaceBO(testAppId, testEnv, testClusterName, testNamespaceName);
    List<String> namespaceKey1 = namespaceBO1.getItems().stream().map(s -> s.getItem().getKey()).collect(Collectors.toList());
    assertThat(namespaceBO1.getItemModifiedCnt()).isEqualTo(3);
    assertThat(namespaceBO1.getItems().size()).isEqualTo(3);
    assertThat(namespaceKey1).isEqualTo(Arrays.asList("k1", "k2", "k3"));

    return namespaceService.loadNamespaceBO(testAppId, testEnv, testClusterName, testNamespaceName, fillItemDetail, false);
  }

  @Test
  public void testFindPublicNamespaceForAssociatedNamespace() {
    when(namespaceAPI.findPublicNamespaceForAssociatedNamespace(any(), any(), any(), any())).thenReturn(createNamespace(testAppId, "branch", testNamespaceName));
    when(releaseService.loadLatestRelease(any(), any(), any(), any())).thenReturn(createReleaseDTO());
    when(itemService.findItems(any(), any(), any(), any())).thenReturn(createItems());
    when(itemService.findDeletedItems(any(), any(), any(), any())).thenReturn(createDeletedItems());

    NamespaceBO namespaceBO = namespaceService.findPublicNamespaceForAssociatedNamespace(testEnv, testAppId, testClusterName, testNamespaceName);

    List<String> namespaceKey2 = namespaceBO.getItems().stream().map(s -> s.getItem().getKey()).collect(Collectors.toList());
    assertThat(namespaceBO.getItemModifiedCnt()).isEqualTo(3);
    assertThat(namespaceBO.getItems().size()).isEqualTo(3);
    assertThat(namespaceKey2).isEqualTo(Arrays.asList("k1", "k2", "k3"));
  }

  private ReleaseDTO createReleaseDTO() {
    ReleaseDTO releaseDTO = new ReleaseDTO();
    releaseDTO.setConfigurations("{\"k1\":\"k1\",\"k2\":\"k2\", \"k3\":\"\"}");
    return releaseDTO;
  }

  private List<ItemDTO> createItems() {
    List<ItemDTO> itemDTOList = Lists.newArrayList();
    ItemDTO itemDTO1 = new ItemDTO();
    itemDTO1.setId(1);
    itemDTO1.setNamespaceId(1);
    itemDTO1.setKey("k1");
    itemDTO1.setValue(String.valueOf(1));
    itemDTOList.add(itemDTO1);

    ItemDTO itemDTO2 = new ItemDTO();
    itemDTO2.setId(2);
    itemDTO2.setNamespaceId(2);
    itemDTO2.setKey("k2");
    itemDTO2.setValue(String.valueOf(2));
    itemDTOList.add(itemDTO2);

    return itemDTOList;
  }

  private List<ItemDTO> createDeletedItems() {
    List<ItemDTO> deletedItemDTOList = Lists.newArrayList();
    ItemDTO deletedItemDTO = new ItemDTO();
    deletedItemDTO.setId(3);
    deletedItemDTO.setNamespaceId(3);
    deletedItemDTO.setKey("k3");
    deletedItemDTOList.add(deletedItemDTO);
    return deletedItemDTOList;
  }

  private AppNamespace createAppNamespace(String appId, String name, boolean isPublic) {
    AppNamespace instance = new AppNamespace();

    instance.setAppId(appId);
    instance.setName(name);
    instance.setPublic(isPublic);

    return instance;
  }

  private NamespaceDTO createNamespace(String appId, String clusterName, String namespaceName) {
    NamespaceDTO instance = new NamespaceDTO();

    instance.setAppId(appId);
    instance.setClusterName(clusterName);
    instance.setNamespaceName(namespaceName);

    return instance;
  }

  private UserInfo createUser(String userId) {
    UserInfo instance = new UserInfo();

    instance.setUserId(userId);

    return instance;
  }
}