content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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 org.apache.camel.quarkus.component.language.it;
  
  import java.nio.charset.StandardCharsets;
  import java.nio.file.Files;
  import java.nio.file.Paths;
  import java.nio.file.StandardCopyOption;
  import java.util.Collection;
  import java.util.Map;
  import java.util.stream.Collectors;
  
  import com.github.tomakehurst.wiremock.WireMockServer;
  import io.quarkus.test.common.QuarkusTestResource;
  import io.quarkus.test.junit.QuarkusTest;
  import io.restassured.RestAssured;
  import io.restassured.builder.RequestSpecBuilder;
  import io.restassured.http.ContentType;
  import org.apache.camel.quarkus.test.wiremock.MockServer;
  import org.hamcrest.Matchers;
  import org.junit.jupiter.api.AfterAll;
  import org.junit.jupiter.api.BeforeAll;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.params.ParameterizedTest;
  import org.junit.jupiter.params.provider.MethodSource;
  
  import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
  import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
  import static com.github.tomakehurst.wiremock.client.WireMock.request;
  import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
  import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
  import static org.junit.jupiter.api.Assertions.assertNotNull;
  
  @QuarkusTest
  @QuarkusTestResource(LanguageTestResource.class)
  class LanguageTest {
  
      @MockServer
      WireMockServer server;
  
      /**
       * Map endpoint names to objects containing exchange bodies and expected output
       */
      private static Map<String, TestResult> languageResultsMap = Map.ofEntries(
              /* Test Languages */
              Map.entry("languageBeanScript", new TestResult("Hello Bean", "HELLO BEAN")),
              Map.entry("languageConstantScript", new TestResult("Constant", "Hello from constant language script")),
              Map.entry("languageExchangePropertyScript",
                      new TestResult("ExProperty", "Hello ExProperty from exchangeProperty language script")),
              Map.entry("languageFileScript", new TestResult("", "File name is test-file.txt")),
              Map.entry("languageHeaderScript", new TestResult("Header", "Hello Header from header language script")),
              Map.entry("languageHl7terserScript", new TestResult(createHl7Message(), "Patient's surname is Smith")),
              Map.entry("languageJsonPathScript",
                      new TestResult("{\"message\": \"Hello from jsonpath\", \"user\": \"camel-quarkus\"}",
                              "Hello from jsonpath")),
              Map.entry("languageRefScript", new TestResult("Hello from Ref", "hello from ref")),
              Map.entry("languageSimpleScript", new TestResult("Simple", "Hello Simple from simple language script")),
              Map.entry("languageTokenizeScript", new TestResult("Hello,Tokenize", "Hello")),
              Map.entry("languageXpathScript", new TestResult("<message>Hello from Xpath</message>", "Hello from Xpath")),
              Map.entry("languageXqueryScript", new TestResult("<message>Hello from XQuery</message>", "HELLO FROM XQUERY")),
  
              /* Test Resource Loading */
              Map.entry("languageSimpleResource", new TestResult("SimpleRes", "Hello SimpleRes from simple language resource")),
              Map.entry("languageSimpleFile", new TestResult("SimpleFile", "Hello SimpleFile from simple language file")),
  
              /* Test transform=false option */
              Map.entry("languageSimpleTransform", new TestResult("SimpleTransform", "SimpleTransform"))
  
      );
  
      private static Collection<Object[]> getLanguageResultsMap() {
          return languageResultsMap.entrySet()
                  .stream()
                  .map((entry) -> new Object[] { entry.getKey(), entry.getValue() })
                  .collect(Collectors.toList());
      }
  
      private static final String OUTPUT_DIRECTORY = "target";
      private static final String TEST_FILE = "test-file.txt";
      private static final String SIMPLE_FILE = "hello.simple-file.txt";
  
      @BeforeAll
      public static void setupTestFiles() throws Exception {
          Files.createDirectories(Paths.get(OUTPUT_DIRECTORY));
          // Write the file used in the 'file' language test
          Files.writeString(Paths.get(OUTPUT_DIRECTORY, TEST_FILE), "Dummy text", StandardCharsets.UTF_8);
  
          // Copy the simple script from resources to the OUTPUT_DIRECTORY to test reading a script from a file: resource
          Files.copy(
                  LanguageTest.class.getClassLoader().getResourceAsStream(SIMPLE_FILE),
                  Paths.get(OUTPUT_DIRECTORY, SIMPLE_FILE),
                  StandardCopyOption.REPLACE_EXISTING);
      }
  
      @AfterAll
      public static void deleteTestFiles() throws Exception {
          Files.delete(Paths.get(OUTPUT_DIRECTORY, TEST_FILE));
          Files.delete(Paths.get(OUTPUT_DIRECTORY, SIMPLE_FILE));
      }
  
      /**
       * This test is called for each entry in {@link languageResultsMap}.
       * The body text is passed to the routeName endpoint, which is checked for successful completion
       * and that the returned body matches the expected result from {@link TestResult}
       *
       * @param routeName - The name of the endpoint exposed in {@link LanguageResource}
       * @param result    - The {@link TestResource} containing input body and expected result
       */
      @ParameterizedTest
      @MethodSource("getLanguageResultsMap")
      public void testLanguage(String routeName, TestResult result) {
          RestAssured.given()
                  .contentType(ContentType.TEXT)
                  .body(result.body)
                  .post("/language/route/" + routeName)
                  .then()
                  .statusCode(200)
                  .body(Matchers.is(result.expected));
      }
  
      /**
       * Tests whether scripts can be loaded over HTTP.
       *
       * @throws Exception
       */
      @Test
      public void testHttpResource() throws Exception {
          // Stub the WireMock server to return a simple script on the "/simple" endpoint
          assertNotNull(server);
          server.stubFor(request("GET", urlPathEqualTo("/simple"))
                  .willReturn(aResponse().withBody("Hello ${body} from simple language http")));
  
          // The heart of the test is very similar to the testLanguage method above, but pass in the
          // URL of the WireMock server as a query parameter.
          RestAssured.given()
                  .queryParam("baseUrl", server.baseUrl())
                  .contentType(ContentType.TEXT)
                  .body("SimpleHttp")
                  .post("/language/route/languageSimpleHttp")
                  .then()
                  .statusCode(200)
                  .body(Matchers.is("Hello SimpleHttp from simple language http"));
          server.verify(1, getRequestedFor(urlEqualTo("/simple")));
      }
  
      /**
       * Tests the contentCache option. The WireMock server is used to count how many times
       * the script endpoint was accessed during route execution.
       */
      @Test
      public void testContentCache() {
          // Create a WireMock stub to return a simple script
          assertNotNull(server);
          server.stubFor(request("GET", urlPathEqualTo("/simpleContentCache"))
                  .willReturn(aResponse().withBody("Hello ${body} from simple language http")));
  
          // Set up common request options
          RequestSpecBuilder builder = new RequestSpecBuilder();
          builder.addQueryParam("baseUrl", server.baseUrl());
          builder.setContentType(ContentType.TEXT);
          builder.setBody("SimpleHttp");
  
          // Call the route to load the script with caching enabled
          RestAssured.given().spec(builder.build())
                  .queryParam("contentCache", true)
                  .when().post("/language/route/languageSimpleContentCache")
                  .then()
                  .statusCode(200)
                  .body(Matchers.is("Hello SimpleHttp from simple language http"));
  
          // As this is the first time the route has been called, expect the script to have been loaded
          server.verify(1, getRequestedFor(urlEqualTo("/simpleContentCache")));
  
          // Call the endpoint again with caching enabled
          RestAssured.given().spec(builder.build())
                  .queryParam("contentCache", true)
                  .when().post("/language/route/languageSimpleContentCache")
                  .then()
                  .statusCode(200);
  
          // Expect the script to have been cached, so the request count should not have changed
          server.verify(1, getRequestedFor(urlEqualTo("/simpleContentCache")));
  
          // Call the route a third time without caching
          RestAssured.given().spec(builder.build())
                  .queryParam("contentCache", false)
                  .when().post("/language/route/languageSimpleContentCache")
                  .then()
                  .statusCode(200);
  
          // Expect the script to have been loaded a second time
          server.verify(2, getRequestedFor(urlEqualTo("/simpleContentCache")));
  
      }
  
      /**
       * Creates a health record for a fictional patient.
       *
       * @return
       */
      private static String createHl7Message() {
          return "MSH|^~\\&|||||20230221140012.386+0000||ADT^A01^ADT_A01|601|P|2.4\r"
                  + "PID|||1||Smith^John";
      }
  
      private static class TestResult {
          String body;
          String expected;
  
          public TestResult(String body, String expected) {
              this.body = body;
              this.expected = expected;
          }
      }
  
  }
  