/**
 * Copyright 2017 Pivotal Software, Inc.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 io.micrometer.spring.autoconfigure.web.client;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.spring.autoconfigure.MetricsProperties;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.RestTemplate;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RestTemplateMetricsConfigurationTest.ClientApp.class, webEnvironment = WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {
    "management.port=-1", // Disable the entire Spring Boot actuator, so that it does not get needlessly instrumented
    "security.ignored=/**",
})
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class RestTemplateMetricsConfigurationTest {
    @Autowired
    private MeterRegistry registry;

    @Autowired
    private MetricsProperties metricsProperties;

    @LocalServerPort
    private int port;

    @Autowired
    private RestTemplateBuilder restTemplateBuilder;

    @Autowired
    private AsyncRestTemplate asyncClient;

    private String rootUri;

    private RestTemplate client;

    @Before
    public void before() {
        rootUri = "http://localhost:" + port;
        client = restTemplateBuilder
            .rootUri(rootUri)
            .build();
    }

    @Test
    public void restTemplatesCreatedWithBuilderAreInstrumented() {
        client.getForObject("/it/1", String.class);
        assertThat(registry.get("http.client.requests").meters()).hasSize(1);
    }

    @Test
    public void asyncRestTemplatesInContextAreInstrumented() throws Exception {
        // There's no way to inject local server port retrospectively into the autowired async template bean,
        // therefore a full absolute URI is used
        asyncClient.getForEntity(rootUri + "/it/2", String.class).get();
        assertThat(registry.get("http.client.requests").meters()).hasSize(1);
    }

    @Test
    public void afterMaxUrisReachedFurtherUrisAreDenied() {
        int maxUriTags = metricsProperties.getWeb().getClient().getMaxUriTags();
        for (int i = 0; i < maxUriTags + 10; i++) {
            client.getForObject("/it/" + i, String.class);
        }

        assertThat(registry.get("http.client.requests").meters()).hasSize(maxUriTags);
    }

    @SpringBootApplication(scanBasePackages = "ignore")
    @Import(SampleController.class)
    static class ClientApp {
        @Bean
        public MeterRegistry registry() {
            return new SimpleMeterRegistry();
        }

        @Bean
        public AsyncRestTemplate asyncRestTemplate() {
            final SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
            requestFactory.setTaskExecutor(new SimpleAsyncTaskExecutor());
            return new AsyncRestTemplate(requestFactory);
        }
    }

    @RestController
    static class SampleController {
        @GetMapping("/it/{id}")
        public String it(@PathVariable String id) {
            return id;
        }
    }
}

@@ -1490,16 +1490,64 @@
 ecutor;%0A
+import org.springframework.http.ResponseEntity;%0A
 import o
@@ -1854,24 +1854,85 @@
 ringRunner;%0A
+import org.springframework.util.concurrent.ListenableFuture;%0A
 import org.s
@@ -2212,16 +2212,60 @@
 plate;%0A%0A
+import java.util.concurrent.CyclicBarrier;%0A%0A
 import s
@@ -2313,16 +2313,81 @@
 ertThat;
+%0Aimport static org.assertj.core.api.AssertionsForClassTypes.fail;
 %0A%0A@RunWi
@@ -2569,16 +2569,20 @@
 ies = %7B%0A
+    
     %22man
@@ -2691,16 +2691,20 @@
 umented%0A
+    
     %22sec
@@ -3326,16 +3326,20 @@
         
+    
 .rootUri
@@ -3348,16 +3348,20 @@
 ootUri)%0A
+    
         
@@ -3700,224 +3700,569 @@
  // 
-T
+t
 here
-'s no way to inject local server port retrospectively into the autowired async template bean,%0A        // therefore a full absolute URI is used%0A        asyncClient.getForEntity(rootUri + %22/it/2%22, String.class).ge
+fore a full absolute URI is used%0A        ListenableFuture%3CResponseEntity%3CString%3E%3E future = asyncClient.getForEntity(rootUri + %22/it/2%22, String.class);%0A%0A        final CyclicBarrier barrier = new CyclicBarrier(2);%0A        future.addCallback(result -%3E %7B%0A                    try %7B%0A                        barrier.await();%0A                    %7D catch (Throwable e) %7B%0A                        fail(%22barrier broken%22, e);%0A                    %7D%0A                %7D,%0A                result -%3E fail(%22should not have failed%22));%0A%0A        future.get();%0A%0A        barrier.awai
 t();
@@ -4318,33 +4318,42 @@
 uests%22).
-meters()).hasSize
+timer().count()).isEqualTo
 (1);%0A   

Fix flaky async rest template test