Back to Repositories

Testing Configuration Export-Import Workflow in Apollo Framework

This test suite evaluates the ConfigsExportService and ConfigsImportService functionality in Apollo, focusing on namespace configuration export and import operations. The tests verify the system’s ability to handle configuration data transfer between environments with different item detail settings.

Test Coverage Overview

The test suite provides comprehensive coverage of configuration export and import scenarios in Apollo.

Key areas tested include:
  • Configuration export with different item detail settings
  • Namespace export and import operations
  • Multi-environment configuration handling
  • Application and cluster management
  • Error handling and edge cases

Implementation Analysis

The testing approach uses JUnit with extensive mocking via Mockito to isolate the export/import services.

Technical implementation includes:
  • Mock service interactions for AppService, ClusterService, and NamespaceService
  • File system operations testing with temporary directories
  • ZIP file handling for configuration packaging
  • Permission validation and user context simulation

Technical Details

Testing infrastructure includes:
  • JUnit test framework
  • Mockito mocking framework
  • Spring test context support
  • Temporary file system handling
  • ZIP stream processing
  • HTTP status code exception handling

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive mock setup for external dependencies
  • Proper test isolation and cleanup
  • Scenario-based testing with different configurations
  • Verification of both success and error paths
  • Clear test method organization and naming

apolloconfig/apollo

apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/ConfigsExportServiceTest.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.entity.App;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.portal.AbstractUnitTest;
import com.ctrip.framework.apollo.portal.component.PermissionValidator;
import com.ctrip.framework.apollo.portal.entity.bo.ItemBO;
import com.ctrip.framework.apollo.portal.entity.bo.NamespaceBO;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;

import org.assertj.core.util.Files;
import org.assertj.core.util.Lists;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.List;
import java.util.zip.ZipInputStream;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * @author lepdou 2021-08-30
 */
public class ConfigsExportServiceTest extends AbstractUnitTest {

  @Mock
  private AppService                appService;
  @Mock
  private ClusterService            clusterService;
  @Mock
  private NamespaceService          namespaceService;
  @Mock
  private PermissionValidator       permissionValidator;
  @Mock
  private UserInfoHolder            userInfoHolder;
  @Mock
  private AppNamespaceService       appNamespaceService;
  @InjectMocks
  private ConfigsExportService      configsExportService;
  @Mock
  private ItemService               itemService;
  @Mock
  private ApplicationEventPublisher applicationEventPublisher;
  @Mock
  private RoleInitializationService roleInitializationService;
  @InjectMocks
  private ConfigsImportService      configsImportService;

  @Test
  public void testNamespaceExportImport() throws FileNotFoundException {
    // Test with fillItemDetail = true
    testExportImportScenario(true);
  }

  @Test
  public void testNamespaceExportImportWithFillItemDetail() throws FileNotFoundException {
    // Test with fillItemDetail = false
    testExportImportScenario(false);
  }

  private void testExportImportScenario(boolean fillItemDetail) throws FileNotFoundException {

    File temporaryFolder = Files.newTemporaryFolder();
    temporaryFolder.deleteOnExit();
    String filePath = temporaryFolder + File.separator + "export.zip";

    //export config
    UserInfo userInfo = genUser();
    when(userInfoHolder.getUser()).thenReturn(userInfo);

    Env env = Env.DEV;
    String appId1 = "app1";
    String appId2 = "app2";

    App app1 = genApp(appId1, appId1, "org1", "org2");
    App app2 = genApp(appId2, appId2, "org1", "org2");

    List<App> exportApps = Lists.newArrayList(app1, app2);

    String appNamespaceName1 = "ns1";
    String appNamespaceName2 = "ns2";
    AppNamespace app1Namespace1 = genAppNamespace(appId1, appNamespaceName1, false);
    AppNamespace app1Namespace2 = genAppNamespace(appId1, appNamespaceName2, true);
    AppNamespace app2Namespace1 = genAppNamespace(appId2, appNamespaceName1, false);

    List<AppNamespace> appNamespaces = Lists.newArrayList(app1Namespace1, app1Namespace2, app2Namespace1);

    String clusterName1 = "c1";
    String clusterName2 = "c2";
    ClusterDTO app1Cluster1 = genCluster(clusterName1, appId1);
    ClusterDTO app1Cluster2 = genCluster(clusterName2, appId1);

    ClusterDTO app2Cluster1 = genCluster(clusterName1, appId2);
    ClusterDTO app2Cluster2 = genCluster(clusterName2, appId2);

    List<ClusterDTO>
        app1Clusters =
        Lists.newArrayList(app1Cluster1, app1Cluster2);
    List<ClusterDTO>
        app2Clusters =
        Lists.newArrayList(app2Cluster1, app2Cluster2);

    ItemBO item1 = genItem("k1", "v1");
    ItemBO item2 = genItem("k2", "v2");
    List<ItemBO> items = Lists.newArrayList(item1, item2);

    String namespaceName1 = "namespace1";
    String namespaceName2 = "namespace2";
    NamespaceBO app1Cluster1Namespace1 = genNamespace(app1, app1Cluster1, items, namespaceName1);
    NamespaceBO app1Cluster1Namespace2 = genNamespace(app1, app1Cluster1, items, namespaceName2);
    List<NamespaceBO> app1Cluster1Namespace = Lists.newArrayList(app1Cluster1Namespace1, app1Cluster1Namespace2);
    NamespaceBO app1Cluster2Namespace1 = genNamespace(app1, app1Cluster2, items, namespaceName1);
    List<NamespaceBO> app1Cluster2Namespace = Lists.newArrayList(app1Cluster2Namespace1);

    NamespaceBO app2Cluster1Namespace1 = genNamespace(app2, app1Cluster1, items, namespaceName1);
    List<NamespaceBO> app2Cluster1Namespace = Lists.newArrayList(app2Cluster1Namespace1);
    NamespaceBO app2Cluster2Namespace1 = genNamespace(app2, app1Cluster2, items, namespaceName1);
    NamespaceBO app2Cluster2Namespace2 = genNamespace(app2, app1Cluster2, items, namespaceName2);
    List<NamespaceBO> app2Cluster2Namespace = Lists.newArrayList(app2Cluster2Namespace1, app2Cluster2Namespace2);

    when(appService.findAll()).thenReturn(exportApps);
    when(appNamespaceService.findAll()).thenReturn(appNamespaces);
    when(permissionValidator.isAppAdmin(any())).thenReturn(true);
    when(clusterService.findClusters(env, appId1)).thenReturn(app1Clusters);
    when(clusterService.findClusters(env, appId2)).thenReturn(app2Clusters);
    when(namespaceService.findNamespaceBOs(appId1, Env.DEV, clusterName1, fillItemDetail, false)).thenReturn(app1Cluster1Namespace);
    when(namespaceService.findNamespaceBOs(appId1, Env.DEV, clusterName2, fillItemDetail, false)).thenReturn(app1Cluster2Namespace);
    when(namespaceService.findNamespaceBOs(appId2, Env.DEV, clusterName1, fillItemDetail, false)).thenReturn(app2Cluster1Namespace);
    when(namespaceService.findNamespaceBOs(appId2, Env.DEV, clusterName2, fillItemDetail, false)).thenReturn(app2Cluster2Namespace);

    FileOutputStream fileOutputStream = new FileOutputStream(filePath);

    configsExportService.exportData(fileOutputStream, Lists.newArrayList(Env.DEV));

    //import config
    when(appNamespaceService.findByAppIdAndName(any(), any())).thenReturn(null);
    when(appNamespaceService.importAppNamespaceInLocal(any())).thenReturn(app1Namespace1);
    when(appService.load(any())).thenReturn(null);
    when(appService.load(any(), any())).thenThrow(new RuntimeException());

    when(clusterService.loadCluster(any(), any(), any())).thenThrow(new RuntimeException());

    when(namespaceService.loadNamespaceBaseInfo(any(), any(), any(), any())).thenThrow(new RuntimeException());
    when(namespaceService.createNamespace(any(), any())).thenReturn(genNamespaceDTO(1));

    when(itemService.findItems(any(), any(), any(), any())).thenReturn(Lists.newArrayList());
    HttpStatusCodeException itemNotFoundException = new HttpClientErrorException(HttpStatus.NOT_FOUND);
    when(itemService.loadItem(any(), any(), any(), any(), anyString())).thenThrow(itemNotFoundException);

    FileInputStream fileInputStream = new FileInputStream(filePath);
    ZipInputStream zipInputStream = new ZipInputStream(fileInputStream);

    try {
      configsImportService.importDataFromZipFile(Lists.newArrayList(Env.DEV), zipInputStream, false);
    } catch (Exception e) {
      e.printStackTrace();
    }

    verify(appNamespaceService, times(3)).importAppNamespaceInLocal(any());
    verify(applicationEventPublisher, times(3)).publishEvent(any());

    verify(appService, times(2)).createAppInRemote(any(), any());

    verify(clusterService, times(4)).createCluster(any(), any());

    if(fillItemDetail){
      verify(namespaceService, times(6)).createNamespace(any(), any());
      verify(roleInitializationService,times(6)).initNamespaceRoles(any(), any(), anyString());
      verify(roleInitializationService,times(6)).initNamespaceEnvRoles(any(), any(), anyString());
      verify(itemService, times(12)).createItem(any(), any(), any(), any(), any());
    }
  }

  private App genApp(String name, String appId, String orgId, String orgName) {
    App app = new App();
    app.setAppId(appId);
    app.setName(name);
    app.setOrgName("apollo");
    app.setOrgId(orgId);
    app.setOrgName(orgName);
    return app;
  }

  private ClusterDTO genCluster(String name, String appId) {
    ClusterDTO clusterDTO = new ClusterDTO();
    clusterDTO.setAppId(appId);
    clusterDTO.setName(name);
    return clusterDTO;
  }

  private AppNamespace genAppNamespace(String appId, String name, boolean isPublic) {
    AppNamespace appNamespace = new AppNamespace();
    appNamespace.setAppId(appId);
    appNamespace.setPublic(isPublic);
    appNamespace.setName(name);
    appNamespace.setFormat(ConfigFileFormat.Properties.getValue());
    return appNamespace;
  }

  private NamespaceBO genNamespace(App app, ClusterDTO clusterDTO, List<ItemBO> itemBOS, String namespaceName) {
    NamespaceBO namespaceBO = new NamespaceBO();

    NamespaceDTO baseInfo = new NamespaceDTO();
    baseInfo.setNamespaceName(namespaceName);
    baseInfo.setAppId(app.getAppId());
    baseInfo.setClusterName(clusterDTO.getName());

    namespaceBO.setBaseInfo(baseInfo);
    namespaceBO.setFormat(ConfigFileFormat.Properties.getValue());
    namespaceBO.setItems(itemBOS);

    return namespaceBO;
  }

  private ItemBO genItem(String key, String value) {
    ItemBO itemBO = new ItemBO();

    ItemDTO itemDTO = new ItemDTO();
    itemDTO.setKey(key);
    itemDTO.setValue(value);

    itemBO.setItem(itemDTO);

    return itemBO;
  }

  private NamespaceDTO genNamespaceDTO(long id) {
    NamespaceDTO dto = new NamespaceDTO();
    dto.setId(id);
    return dto;
  }

  private UserInfo genUser() {
    UserInfo userInfo = new UserInfo();
    userInfo.setUserId("apollo");
    userInfo.setName("apollo");
    userInfo.setEmail("[email protected]");
    return userInfo;
  }
}