static ClosableResolver<AwsEndpoint> compositeBootstrapResolver(
            final EurekaClientConfig clientConfig,
            final EurekaTransportConfig transportConfig,
            final TransportClientFactory transportClientFactory,
            final InstanceInfo myInstanceInfo,
            final ApplicationsResolver.ApplicationsSource applicationsSource,
            final EndpointRandomizer randomizer)
    {
        final ClusterResolver rootResolver = new ConfigClusterResolver(clientConfig, myInstanceInfo);

        final EurekaHttpResolver remoteResolver = new EurekaHttpResolver(
                clientConfig,
                transportConfig,
                rootResolver,
                transportClientFactory,
                transportConfig.getWriteClusterVip()
        );

        final ApplicationsResolver localResolver = new ApplicationsResolver(
                clientConfig,
                transportConfig,
                applicationsSource,
                transportConfig.getWriteClusterVip()
        );

        ClusterResolver<AwsEndpoint> compositeResolver = new ClusterResolver<AwsEndpoint>() {
            @Override
            public String getRegion() {
                return clientConfig.getRegion();
            }

            @Override
            public List<AwsEndpoint> getClusterEndpoints() {
                List<AwsEndpoint> result = localResolver.getClusterEndpoints();
                if (result.isEmpty()) {
                    result = remoteResolver.getClusterEndpoints();
                }

                return result;
            }
        };

        List<AwsEndpoint> initialValue = compositeResolver.getClusterEndpoints();
        if (initialValue.isEmpty()) {
            String msg = "Initial resolution of Eureka endpoints failed. Check ConfigClusterResolver logs for more info";
            logger.error(msg);
            failFastOnInitCheck(clientConfig, msg);
        }

        String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
        String myZone = InstanceInfo.getZone(availZones, myInstanceInfo);

        return new AsyncResolver<>(
                EurekaClientNames.BOOTSTRAP,
                new ZoneAffinityClusterResolver(compositeResolver, myZone, true, randomizer),
                initialValue,
                transportConfig.getAsyncExecutorThreadPoolSize(),
                transportConfig.getAsyncResolverRefreshIntervalMs()
        );
    }
@Test
    public void testCompositeBootstrapResolver() throws Exception {
        Applications applications = InstanceInfoGenerator.newBuilder(5, "eurekaWrite", "someOther").build().toApplications();
        Applications applications2 = InstanceInfoGenerator.newBuilder(2, "eurekaWrite", "someOther").build().toApplications();
        String vipAddress = applications.getRegisteredApplications("eurekaWrite").getInstances().get(0).getVIPAddress();

        // setup client config to use fixed root ips for testing
        when(clientConfig.shouldUseDnsForFetchingServiceUrls()).thenReturn(false);
        when(clientConfig.getEurekaServerServiceUrls(anyString())).thenReturn(Arrays.asList("http://foo:0"));  // can use anything here
        when(clientConfig.getRegion()).thenReturn("us-east-1");

        when(transportConfig.getWriteClusterVip()).thenReturn(vipAddress);
        when(transportConfig.getAsyncExecutorThreadPoolSize()).thenReturn(4);
        when(transportConfig.getAsyncResolverRefreshIntervalMs()).thenReturn(400);
        when(transportConfig.getAsyncResolverWarmUpTimeoutMs()).thenReturn(400);

        ApplicationsResolver.ApplicationsSource applicationsSource = mock(ApplicationsResolver.ApplicationsSource.class);
        when(applicationsSource.getApplications(anyInt(), eq(TimeUnit.SECONDS)))
                .thenReturn(null)  // first time
                .thenReturn(applications)  // second time
                .thenReturn(null);  // subsequent times

        EurekaHttpClient mockHttpClient = mock(EurekaHttpClient.class);
        when(mockHttpClient.getVip(eq(vipAddress)))
                .thenReturn(anEurekaHttpResponse(200, applications).build())
                .thenReturn(anEurekaHttpResponse(200, applications2).build());  // contains diff number of servers

        TransportClientFactory transportClientFactory = mock(TransportClientFactory.class);
        when(transportClientFactory.newClient(any(EurekaEndpoint.class))).thenReturn(mockHttpClient);

        ClosableResolver<AwsEndpoint> resolver = null;
        try {
            resolver = EurekaHttpClients.compositeBootstrapResolver(
                    clientConfig,
                    transportConfig,
                    transportClientFactory,
                    applicationInfoManager.getInfo(),
                    applicationsSource,
                    randomizer
            );

            List endpoints = resolver.getClusterEndpoints();
            assertThat(endpoints.size(), equalTo(applications.getInstancesByVirtualHostName(vipAddress).size()));

            // wait for the second cycle that hits the app source
            verify(applicationsSource, timeout(3000).times(2)).getApplications(anyInt(), eq(TimeUnit.SECONDS));
            endpoints = resolver.getClusterEndpoints();
            assertThat(endpoints.size(), equalTo(applications.getInstancesByVirtualHostName(vipAddress).size()));

            // wait for the third cycle that triggers the mock http client (which is the third resolver cycle)
            // for the third cycle we have mocked the application resolver to return null data so should fall back
            // to calling the remote resolver again (which should return applications2)
            verify(mockHttpClient, timeout(3000).times(3)).getVip(anyString());
            endpoints = resolver.getClusterEndpoints();
            assertThat(endpoints.size(), equalTo(applications2.getInstancesByVirtualHostName(vipAddress).size()));
        } finally {
            if (resolver != null) {
                resolver.shutdown();
            }
        }
    }