Back to Repositories

Testing AppNamespace Cache Management in Apollo Config

This test suite validates the AppNamespaceServiceWithCache functionality in Apollo Config Service, focusing on namespace caching, retrieval, and modification operations. It ensures proper handling of public and private namespaces with comprehensive cache management verification.

Test Coverage Overview

The test suite provides extensive coverage of namespace operations in Apollo’s configuration service.

Key areas tested include:
  • Namespace creation and retrieval for both public and private namespaces
  • Case-insensitive namespace lookups
  • Batch namespace operations
  • Cache rebuild intervals and timing
  • Namespace modifications and deletions

Implementation Analysis

The testing approach uses MockitoJUnitRunner for mocking dependencies and Awaitility for handling asynchronous operations.

Key patterns include:
  • Mock repository interactions
  • Asynchronous cache validation
  • Comprehensive state verification
  • Time-based cache updates testing

Technical Details

Testing tools and configuration:
  • JUnit 4 testing framework
  • Mockito for dependency mocking
  • Awaitility for async operations
  • Custom comparators for AppNamespace validation
  • Configurable scan intervals for cache testing

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through comprehensive verification of cache behavior.

Notable practices include:
  • Thorough setup and teardown management
  • Explicit timing control for async operations
  • Systematic state verification
  • Edge case handling for namespace modifications
  • Clear test organization and readability

apolloconfig/apollo

apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/AppNamespaceServiceWithCacheTest.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.configservice.service;

import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.repository.AppNamespaceRepository;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.awaitility.Awaitility;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static org.awaitility.Awaitility.await;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;

/**
 * @author Jason Song([email protected])
 */
@RunWith(MockitoJUnitRunner.Silent.class)
public class AppNamespaceServiceWithCacheTest {
  private AppNamespaceServiceWithCache appNamespaceServiceWithCache;
  @Mock
  private AppNamespaceRepository appNamespaceRepository;

  @Mock
  private BizConfig bizConfig;

  private int scanInterval;
  private TimeUnit scanIntervalTimeUnit;
  private Comparator<AppNamespace> appNamespaceComparator = (o1, o2) -> (int) (o1.getId() -
      o2.getId());

  @Before
  public void setUp() throws Exception {
    appNamespaceServiceWithCache = new AppNamespaceServiceWithCache(appNamespaceRepository, bizConfig);

    scanInterval = 50;
    scanIntervalTimeUnit = TimeUnit.MILLISECONDS;
    when(bizConfig.appNamespaceCacheRebuildInterval()).thenReturn(scanInterval);
    when(bizConfig.appNamespaceCacheRebuildIntervalTimeUnit()).thenReturn(scanIntervalTimeUnit);
    when(bizConfig.appNamespaceCacheScanInterval()).thenReturn(scanInterval);
    when(bizConfig.appNamespaceCacheScanIntervalTimeUnit()).thenReturn(scanIntervalTimeUnit);

    Awaitility.reset();
    Awaitility.setDefaultTimeout(scanInterval * 100, scanIntervalTimeUnit);
    Awaitility.setDefaultPollInterval(scanInterval, scanIntervalTimeUnit);

  }

  @Test
  public void testAppNamespace() throws Exception {
    String someAppId = "someAppId";
    String somePrivateNamespace = "somePrivateNamespace";
    String somePrivateNamespaceWithIncorrectCase = somePrivateNamespace.toUpperCase();
    long somePrivateNamespaceId = 1;
    String yetAnotherPrivateNamespace = "anotherPrivateNamespace";
    long yetAnotherPrivateNamespaceId = 4;
    String anotherPublicNamespace = "anotherPublicNamespace";
    long anotherPublicNamespaceId = 5;

    String somePublicAppId = "somePublicAppId";
    String somePublicNamespace = "somePublicNamespace";
    String somePublicNamespaceWithIncorrectCase = somePublicNamespace.toUpperCase();
    long somePublicNamespaceId = 2;
    String anotherPrivateNamespace = "anotherPrivateNamespace";
    long anotherPrivateNamespaceId = 3;

    AppNamespace somePrivateAppNamespace = assembleAppNamespace(somePrivateNamespaceId,
        someAppId, somePrivateNamespace, false);
    AppNamespace somePublicAppNamespace = assembleAppNamespace(somePublicNamespaceId,
        somePublicAppId, somePublicNamespace, true);
    AppNamespace anotherPrivateAppNamespace = assembleAppNamespace(anotherPrivateNamespaceId,
        somePublicAppId, anotherPrivateNamespace, false);
    AppNamespace yetAnotherPrivateAppNamespace = assembleAppNamespace
        (yetAnotherPrivateNamespaceId, someAppId, yetAnotherPrivateNamespace, false);
    AppNamespace anotherPublicAppNamespace = assembleAppNamespace(anotherPublicNamespaceId,
        someAppId, anotherPublicNamespace, true);

    Set<String> someAppIdNamespaces = Sets.newHashSet
        (somePrivateNamespace, yetAnotherPrivateNamespace, anotherPublicNamespace);
    Set<String> someAppIdNamespacesWithIncorrectCase = Sets.newHashSet
        (somePrivateNamespaceWithIncorrectCase, yetAnotherPrivateNamespace, anotherPublicNamespace);
    Set<String> somePublicAppIdNamespaces = Sets.newHashSet(somePublicNamespace,
        anotherPrivateNamespace);
    Set<String> publicNamespaces = Sets.newHashSet(somePublicNamespace, anotherPublicNamespace);
    Set<String> publicNamespacesWithIncorrectCase = Sets.newHashSet(somePublicNamespaceWithIncorrectCase,
        anotherPublicNamespace);

    List<Long> appNamespaceIds = Lists.newArrayList(somePrivateNamespaceId,
        somePublicNamespaceId, anotherPrivateNamespaceId, yetAnotherPrivateNamespaceId,
        anotherPublicNamespaceId);
    List<AppNamespace> allAppNamespaces = Lists.newArrayList(somePrivateAppNamespace,
        somePublicAppNamespace, anotherPrivateAppNamespace, yetAnotherPrivateAppNamespace,
        anotherPublicAppNamespace);

    // Test init
    appNamespaceServiceWithCache.afterPropertiesSet();

    // Should have no record now
    assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespace));
    assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespaceWithIncorrectCase));
    assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, yetAnotherPrivateNamespace));
    assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, anotherPublicNamespace));
    assertTrue(appNamespaceServiceWithCache.findByAppIdAndNamespaces(someAppId, someAppIdNamespaces).isEmpty());
    assertTrue(appNamespaceServiceWithCache.findByAppIdAndNamespaces(someAppId, someAppIdNamespacesWithIncorrectCase)
        .isEmpty());
    assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId, somePublicNamespace));
    assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId,
        somePublicNamespaceWithIncorrectCase));
    assertNull(appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId, anotherPrivateNamespace));
    assertTrue(appNamespaceServiceWithCache.findByAppIdAndNamespaces(somePublicAppId,
        somePublicAppIdNamespaces).isEmpty());
    assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespace));
    assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespaceWithIncorrectCase));
    assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(anotherPublicNamespace));
    assertTrue(appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespaces).isEmpty());
    assertTrue(appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespacesWithIncorrectCase).isEmpty());

    // Add 1 private namespace and 1 public namespace
    when(appNamespaceRepository.findFirst500ByIdGreaterThanOrderByIdAsc(0)).thenReturn(Lists
        .newArrayList(somePrivateAppNamespace, somePublicAppNamespace));
    when(appNamespaceRepository.findAllById(Lists.newArrayList(somePrivateNamespaceId,
        somePublicNamespaceId))).thenReturn(Lists.newArrayList(somePrivateAppNamespace,
        somePublicAppNamespace));

    await().untilAsserted(() -> {
      assertEquals(somePrivateAppNamespace,
          appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespace));
      assertEquals(somePrivateAppNamespace,
          appNamespaceServiceWithCache
              .findByAppIdAndNamespace(someAppId, somePrivateNamespaceWithIncorrectCase));
      check(Lists.newArrayList(somePrivateAppNamespace), appNamespaceServiceWithCache
          .findByAppIdAndNamespaces(someAppId, someAppIdNamespaces));
      check(Lists.newArrayList(somePrivateAppNamespace), appNamespaceServiceWithCache
          .findByAppIdAndNamespaces(someAppId, someAppIdNamespacesWithIncorrectCase));
      assertEquals(somePublicAppNamespace,
          appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId,
              somePublicNamespace));
      assertEquals(somePublicAppNamespace,
          appNamespaceServiceWithCache.findByAppIdAndNamespace(somePublicAppId,
              somePublicNamespaceWithIncorrectCase));
      check(Lists.newArrayList(somePublicAppNamespace), appNamespaceServiceWithCache
          .findByAppIdAndNamespaces(somePublicAppId, somePublicAppIdNamespaces));
      assertEquals(somePublicAppNamespace,
          appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespace));
      assertEquals(somePublicAppNamespace, appNamespaceServiceWithCache.findPublicNamespaceByName
          (somePublicNamespaceWithIncorrectCase));
      check(Lists.newArrayList(somePublicAppNamespace),
          appNamespaceServiceWithCache.findPublicNamespacesByNames
              (publicNamespaces));
      check(Lists.newArrayList(somePublicAppNamespace),
          appNamespaceServiceWithCache.findPublicNamespacesByNames
              (publicNamespacesWithIncorrectCase));
    });

    // Add 2 private namespaces and 1 public namespace
    when(appNamespaceRepository.findFirst500ByIdGreaterThanOrderByIdAsc(somePublicNamespaceId))
        .thenReturn(Lists.newArrayList(anotherPrivateAppNamespace, yetAnotherPrivateAppNamespace,
            anotherPublicAppNamespace));
    when(appNamespaceRepository.findAllById(appNamespaceIds)).thenReturn(allAppNamespaces);

    await().untilAsserted(() -> {
      check(Lists.newArrayList(somePrivateAppNamespace, yetAnotherPrivateAppNamespace,
          anotherPublicAppNamespace), Lists
          .newArrayList(
              appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespace),
              appNamespaceServiceWithCache
                  .findByAppIdAndNamespace(someAppId, yetAnotherPrivateNamespace),
              appNamespaceServiceWithCache
                  .findByAppIdAndNamespace(someAppId, anotherPublicNamespace)));
      check(Lists.newArrayList(somePrivateAppNamespace, yetAnotherPrivateAppNamespace,
          anotherPublicAppNamespace), appNamespaceServiceWithCache.findByAppIdAndNamespaces
          (someAppId, someAppIdNamespaces));
      check(Lists.newArrayList(somePublicAppNamespace, anotherPrivateAppNamespace),
          Lists.newArrayList(appNamespaceServiceWithCache
                  .findByAppIdAndNamespace(somePublicAppId, somePublicNamespace),
              appNamespaceServiceWithCache
                  .findByAppIdAndNamespace(somePublicAppId, anotherPrivateNamespace)));
      check(Lists.newArrayList(somePublicAppNamespace, anotherPrivateAppNamespace),
          appNamespaceServiceWithCache.findByAppIdAndNamespaces(somePublicAppId,
              somePublicAppIdNamespaces));
      check(Lists.newArrayList(somePublicAppNamespace, anotherPublicAppNamespace),
          Lists.newArrayList(
              appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespace),
              appNamespaceServiceWithCache.findPublicNamespaceByName(anotherPublicNamespace)));
      check(Lists.newArrayList(somePublicAppNamespace, anotherPublicAppNamespace),
          appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespaces));
    });

    // Update name
    String somePrivateNamespaceNew = "somePrivateNamespaceNew";
    AppNamespace somePrivateAppNamespaceNew = assembleAppNamespace(somePrivateAppNamespace.getId
        (), somePrivateAppNamespace.getAppId(), somePrivateNamespaceNew, somePrivateAppNamespace
        .isPublic());
    somePrivateAppNamespaceNew.setDataChangeLastModifiedTime(newDateWithDelta
        (somePrivateAppNamespace.getDataChangeLastModifiedTime(), 1));

    // Update appId
    String someAppIdNew = "someAppIdNew";
    AppNamespace yetAnotherPrivateAppNamespaceNew = assembleAppNamespace
        (yetAnotherPrivateAppNamespace.getId(), someAppIdNew, yetAnotherPrivateAppNamespace
            .getName(), false);
    yetAnotherPrivateAppNamespaceNew.setDataChangeLastModifiedTime(newDateWithDelta
        (yetAnotherPrivateAppNamespace.getDataChangeLastModifiedTime(), 1));

    // Update isPublic
    AppNamespace somePublicAppNamespaceNew = assembleAppNamespace(somePublicAppNamespace
            .getId(), somePublicAppNamespace.getAppId(), somePublicAppNamespace.getName(),
        !somePublicAppNamespace.isPublic());
    somePublicAppNamespaceNew.setDataChangeLastModifiedTime(newDateWithDelta
        (somePublicAppNamespace.getDataChangeLastModifiedTime(), 1));

    // Delete 1 private and 1 public

    // should prepare for the case after deleted first, or in 2 rebuild intervals, all will be deleted
    List<Long> appNamespaceIdsAfterDelete = Lists
        .newArrayList(somePrivateNamespaceId, somePublicNamespaceId, yetAnotherPrivateNamespaceId);
    when(appNamespaceRepository.findAllById(appNamespaceIdsAfterDelete)).thenReturn(Lists.newArrayList
        (somePrivateAppNamespaceNew, yetAnotherPrivateAppNamespaceNew, somePublicAppNamespaceNew));

    // do delete
    when(appNamespaceRepository.findAllById(appNamespaceIds)).thenReturn(Lists.newArrayList
        (somePrivateAppNamespaceNew, yetAnotherPrivateAppNamespaceNew, somePublicAppNamespaceNew));

    await().untilAsserted(() -> {
      assertNull(
          appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespace));
      assertNull(appNamespaceServiceWithCache
          .findByAppIdAndNamespace(someAppId, yetAnotherPrivateNamespace));
      assertNull(
          appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, anotherPublicNamespace));
      check(Collections.emptyList(), appNamespaceServiceWithCache
          .findByAppIdAndNamespaces(someAppId, someAppIdNamespaces));
      assertEquals(somePublicAppNamespaceNew,
          appNamespaceServiceWithCache
              .findByAppIdAndNamespace(somePublicAppId, somePublicNamespace));
      check(Lists.newArrayList(somePublicAppNamespaceNew),
          appNamespaceServiceWithCache.findByAppIdAndNamespaces(somePublicAppId,
              somePublicAppIdNamespaces));
      assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(somePublicNamespace));
      assertNull(appNamespaceServiceWithCache.findPublicNamespaceByName(anotherPublicNamespace));
      check(Collections.emptyList(),
          appNamespaceServiceWithCache.findPublicNamespacesByNames(publicNamespaces));

      assertEquals(somePrivateAppNamespaceNew,
          appNamespaceServiceWithCache.findByAppIdAndNamespace(someAppId, somePrivateNamespaceNew));
      check(Lists.newArrayList(somePrivateAppNamespaceNew), appNamespaceServiceWithCache
          .findByAppIdAndNamespaces(someAppId, Sets.newHashSet(somePrivateNamespaceNew)));
      assertEquals(yetAnotherPrivateAppNamespaceNew,
          appNamespaceServiceWithCache
              .findByAppIdAndNamespace(someAppIdNew, yetAnotherPrivateNamespace));
      check(Lists.newArrayList(yetAnotherPrivateAppNamespaceNew), appNamespaceServiceWithCache
          .findByAppIdAndNamespaces(someAppIdNew, Sets.newHashSet(yetAnotherPrivateNamespace)));
    });
  }

  private void check(List<AppNamespace> someList, List<AppNamespace> anotherList) {
    someList.sort(appNamespaceComparator);
    anotherList.sort(appNamespaceComparator);
    assertEquals(someList, anotherList);
  }

  private Date newDateWithDelta(Date date, int deltaInSeconds) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.SECOND, deltaInSeconds);

    return calendar.getTime();
  }

  private AppNamespace assembleAppNamespace(long id, String appId, String name, boolean isPublic) {
    AppNamespace appNamespace = new AppNamespace();
    appNamespace.setId(id);
    appNamespace.setAppId(appId);
    appNamespace.setName(name);
    appNamespace.setPublic(isPublic);
    appNamespace.setDataChangeLastModifiedTime(new Date());
    return appNamespace;
  }
}