// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.testrunner;

import ai.vespa.hosted.api.TestDescriptor;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.test.json.JsonTestHelper;
import com.yahoo.vespa.testrunner.legacy.LegacyTestRunner;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * @author mortent
 */
public class TestRunnerHandlerTest {

    private static final Instant testInstant = Instant.ofEpochMilli(1598432151660L);
    private static TestRunnerHandler testRunnerHandler;

    @BeforeAll
    public static void setup() {
        List<LogRecord> logRecords = List.of(logRecord("Tests started"));
        Throwable exception = new RuntimeException("org.junit.ComparisonFailure: expected:<foo> but was:<bar>");
        exception.setStackTrace(new StackTraceElement[]{new StackTraceElement("Foo", "bar", "Foo.java", 1123)});
        TestReport testReport = TestReport.builder()
                .withSuccessCount(1)
                .withFailedCount(2)
                .withIgnoredCount(3)
                .withAbortedCount(4)
                .withTotalCount(10)
                .withFailures(List.of(new TestReport.Failure("Foo.bar()", exception)))
                .withLogs(logRecords).build();

        testRunnerHandler = new TestRunnerHandler(
                Executors.newSingleThreadExecutor(),
                new MockJunitRunner(LegacyTestRunner.Status.SUCCESS, testReport),
                null);
    }

    @Test
    public void createsCorrectTestReport() throws IOException {
        HttpResponse response = testRunnerHandler.handle(HttpRequest.createTestRequest("http://localhost:1234/tester/v1/report", GET));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        response.render(out);
        JsonTestHelper.assertJsonEquals(out.toString(UTF_8), "{\"summary\":{\"total\":10,\"success\":1,\"failed\":2,\"ignored\":3,\"aborted\":4,\"failures\":[{\"testName\":\"Foo.bar()\",\"testError\":\"org.junit.ComparisonFailure: expected:<foo> but was:<bar>\",\"exception\":\"java.lang.RuntimeException: org.junit.ComparisonFailure: expected:<foo> but was:<bar>\\n\\tat Foo.bar(Foo.java:1123)\\n\"}]},\"output\":[\"Tests started\"]}");

    }

    @Test
    public void returnsCorrectLog() throws IOException {
        HttpResponse response = testRunnerHandler.handle(HttpRequest.createTestRequest("http://localhost:1234/tester/v1/log", GET));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        response.render(out);
        JsonTestHelper.assertJsonEquals(out.toString(UTF_8), "{\"logRecords\":[{\"id\":0,\"at\":1598432151660,\"type\":\"info\",\"message\":\"Tests started\"}]}");

        // Should not get old log
        response = testRunnerHandler.handle(HttpRequest.createTestRequest("http://localhost:1234/tester/v1/log?after=0", GET));
        out = new ByteArrayOutputStream();
        response.render(out);
        assertEquals("{\"logRecords\":[]}", out.toString(UTF_8));

    }

    @Test
    public void returnsEmptyLogWhenReportNotReady() throws IOException {
        TestRunner testRunner = mock(TestRunner.class);
        when(testRunner.isSupported()).thenReturn(true);
        when(testRunner.getReport()).thenReturn(null);
        testRunnerHandler = new TestRunnerHandler(
                Executors.newSingleThreadExecutor(),
                testRunner, null);

        HttpResponse response = testRunnerHandler.handle(HttpRequest.createTestRequest("http://localhost:1234/tester/v1/log", GET));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        response.render(out);
        assertEquals("{\"logRecords\":[]}", out.toString(UTF_8));
    }

    @Test
    public void usesLegacyTestRunnerWhenNotSupported() throws IOException {
        TestRunner testRunner = mock(TestRunner.class);
        when(testRunner.isSupported()).thenReturn(false);
        LegacyTestRunner legacyTestRunner = mock(LegacyTestRunner.class);
        when(legacyTestRunner.getLog(anyLong())).thenReturn(List.of(logRecord("Legacy log message")));

        testRunnerHandler = new TestRunnerHandler(
                Executors.newSingleThreadExecutor(),
                testRunner, legacyTestRunner);

        HttpResponse response = testRunnerHandler.handle(HttpRequest.createTestRequest("http://localhost:1234/tester/v1/log", GET));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        response.render(out);
        JsonTestHelper.assertJsonEquals(out.toString(UTF_8), "{\"logRecords\":[{\"id\":0,\"at\":1598432151660,\"type\":\"info\",\"message\":\"Legacy log message\"}]}");
    }

    /* Creates a LogRecord that has a known instant and sequence number to get predictable serialization format */
    private static LogRecord logRecord(String logMessage) {
        LogRecord logRecord = new LogRecord(Level.INFO, logMessage);
        logRecord.setInstant(testInstant);
        logRecord.setSequenceNumber(0);
        return logRecord;
    }

    private static class MockJunitRunner implements TestRunner {

        private final LegacyTestRunner.Status status;
        private final TestReport testReport;

        public MockJunitRunner(LegacyTestRunner.Status status, TestReport testReport) {

            this.status = status;
            this.testReport = testReport;
        }

        @Override
        public void executeTests(TestDescriptor.TestCategory category, byte[] testConfig) {
        }

        @Override
        public boolean isSupported() {
            return true;
        }

        @Override
        public LegacyTestRunner.Status getStatus() {
            return status;
        }

        @Override
        public TestReport getReport() {
            return testReport;
        }

    }
}

@@ -3680,19 +3680,25 @@
 rnsEmpty
-Log
+Responses
 WhenRepo
@@ -4035,24 +4035,38 @@
 er, null);%0A%0A
+        %7B%0A    
         Http
@@ -4186,32 +4186,36 @@
  GET));%0A        
+    
 ByteArrayOutputS
@@ -4255,32 +4255,36 @@
 ream();%0A        
+    
 response.render(
@@ -4289,32 +4289,36 @@
 r(out);%0A        
+ 
+   
 assertEquals(%22%7B%5C
@@ -4355,24 +4355,351 @@
 ng(UTF_8));%0A
+        %7D%0A%0A        %7B%0A            HttpResponse response = testRunnerHandler.handle(HttpRequest.createTestRequest(%22http://localhost:1234/tester/v1/report%22, GET));%0A            ByteArrayOutputStream out = new ByteArrayOutputStream();%0A            response.render(out);%0A            assertEquals(%22%7B%7D%22, out.toString(UTF_8));%0A        %7D%0A
     %7D%0A%0A    @

Verify response for empty report