/*
 * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
 */

package ai.vespa.metricsproxy.http.application;

import ai.vespa.metricsproxy.http.application.NodeMetricsClient.Node;
import ai.vespa.metricsproxy.metric.model.MetricsPacket;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.yahoo.test.ManualClock;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.List;

import static ai.vespa.metricsproxy.TestUtil.getFileContents;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.Assert.assertEquals;

/**
 * @author gjoranv
 */
public class NodeMetricsClientTest {

    private static final String TEST_FILE = "generic-sample.json";
    private static final String RESPONSE = getFileContents(TEST_FILE);
    private static final int PORT = getAvailablePort();

    private static final CloseableHttpClient httpClient = HttpClients.createDefault();

    private final Node node = new Node("id", "localhost", PORT);
    private ManualClock clock;
    private NodeMetricsClient nodeMetricsClient;

    @Before
    public void setup() {
        clock = new ManualClock();
        nodeMetricsClient = new NodeMetricsClient(httpClient, node, clock);
    }

    @Rule
    public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));

    @Test
    public void metrics_are_not_retrieved_until_first_request() {
        assertEquals(0, nodeMetricsClient.snapshotsRetrieved());
    }

    @Test
    public void metrics_are_retrieved_upon_first_request() {
        stubFor(get(urlEqualTo(node.metricsUri.getPath()))
                        .willReturn(aResponse().withBody(RESPONSE)));

        List<MetricsPacket.Builder> metrics = nodeMetricsClient.getMetrics();
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());
        assertEquals(4, metrics.size());
   }

    @Test
    public void cached_metrics_are_used_when_ttl_has_not_expired() {
        stubFor(get(urlEqualTo(node.metricsUri.getPath()))
                        .willReturn(aResponse().withBody(RESPONSE)));

        nodeMetricsClient.getMetrics();
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());

        clock.advance(NodeMetricsClient.METRICS_TTL.minusMillis(1));
        nodeMetricsClient.getMetrics();
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());
    }

    @Test
    public void metrics_are_refreshed_when_ttl_has_expired() {
        stubFor(get(urlEqualTo(node.metricsUri.getPath()))
                        .willReturn(aResponse().withBody(RESPONSE)));

        nodeMetricsClient.getMetrics();
        assertEquals(1, nodeMetricsClient.snapshotsRetrieved());

        clock.advance(NodeMetricsClient.METRICS_TTL.plusMillis(1));
        nodeMetricsClient.getMetrics();
        assertEquals(2, nodeMetricsClient.snapshotsRetrieved());
    }

    private static int getAvailablePort() {
        try (ServerSocket socket = new ServerSocket(0)) {
            socket.setReuseAddress(true);
            return socket.getLocalPort();
        } catch (IOException e) {
            throw new RuntimeException("Could not find available port: ", e);
        }
    }

}

@@ -339,24 +339,29 @@
 nit.WireMock
+Class
 Rule;%0Aimport
@@ -535,20 +535,27 @@
 g.junit.
-Rule
+BeforeClass
 ;%0Aimport
@@ -569,73 +569,43 @@
 nit.
-Test
+ClassRule
 ;%0A
-%0A
 import 
-java.io.IOException;%0Aimport java.net.ServerSocke
+org.junit.Tes
 t;%0A
+%0A
 impo
@@ -830,79 +830,8 @@
 et;%0A
-import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;%0A
 impo
@@ -1032,16 +1032,236 @@
 s;%0A%0A/**%0A
+ * Two optimizations worth noting:%0A *%0A * 1. Using a ClassRule for the wire mocking means it is reused between test methods.%0A * 2. Configuring stubs on the rule is faster than using the static WireMock.stubFor method.%0A *%0A
  * @auth
@@ -1480,272 +1480,581 @@
 nal 
-int PORT = getAvailablePort();%0A%0A    private static fin
+CloseableHttpClient httpClient = HttpClients.createDefault();%0A%0A    private static Node node;%0A%0A    private Manu
 al
- 
 Clo
-seableHttpClient httpClient = HttpClients.createDefault();%0A%0A    private final Node node = new Node(%22id%22, %22localhost%22, PORT);%0A    private ManualClock clock;%0A    private NodeM
+ck clock;%0A    private NodeMetricsClient nodeMetricsClient;%0A%0A    @ClassRule%0A    public static WireMockClassRule wireMockRule = new WireMockClassRule(options().dynamicPort());%0A%0A    @BeforeClass%0A    public static void setupWireMock() %7B%0A        node = new Node(%22id%22, %22localhost%22, wireMockRule.port());%0A        wireMockRule.stubFor(get(urlEqualTo(node.m
 etrics
-Client nodeMetricsClient;
+Uri.getPath()))%0A                                     .willReturn(aResponse().withBody(RESPONSE)));%0A    %7D
 %0A%0A  
@@ -2084,16 +2084,22 @@
 id setup
+Client
 () %7B%0A   
@@ -2217,98 +2217,8 @@
  %7D%0A%0A
-    @Rule%0A    public WireMockRule wireMockRule = new WireMockRule(options().port(PORT));%0A%0A
     
@@ -2436,138 +2436,8 @@
 ) %7B%0A
-        stubFor(get(urlEqualTo(node.metricsUri.getPath()))%0A                        .willReturn(aResponse().withBody(RESPONSE)));%0A%0A
     
@@ -2705,138 +2705,8 @@
 ) %7B%0A
-        stubFor(get(urlEqualTo(node.metricsUri.getPath()))%0A                        .willReturn(aResponse().withBody(RESPONSE)));%0A%0A
     
@@ -3065,138 +3065,8 @@
 ) %7B%0A
-        stubFor(get(urlEqualTo(node.metricsUri.getPath()))%0A                        .willReturn(aResponse().withBody(RESPONSE)));%0A%0A
     
@@ -3351,321 +3351,6 @@
  %7D%0A%0A
-    private static int getAvailablePort() %7B%0A        try (ServerSocket socket = new ServerSocket(0)) %7B%0A            socket.setReuseAddress(true);%0A            return socket.getLocalPort();%0A        %7D catch (IOException e) %7B%0A            throw new RuntimeException(%22Could not find available port: %22, e);%0A        %7D%0A    %7D%0A%0A
 %7D%0A

Optimize NodeMetricsClientTest