This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 0e8cd2ced57 CAMEL-20943: camel-jbang - Add openapi generate integration test (#14744) 0e8cd2ced57 is described below commit 0e8cd2ced5769434fb2c3484597f1100e85d2082 Author: Marco Carletti <mcarl...@redhat.com> AuthorDate: Fri Jul 5 16:42:12 2024 +0200 CAMEL-20943: camel-jbang - Add openapi generate integration test (#14744) --- .../apache/camel/dsl/jbang/it/OpenApiITCase.java | 146 +++++++++++++++++++++ .../dsl/jbang/it/support/JBangTestSupport.java | 67 ++++++++++ 2 files changed, 213 insertions(+) diff --git a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/OpenApiITCase.java b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/OpenApiITCase.java new file mode 100644 index 00000000000..a2ba0d2915b --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/OpenApiITCase.java @@ -0,0 +1,146 @@ +/* + * 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.dsl.jbang.it; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.apache.camel.dsl.jbang.it.support.InVersion; +import org.apache.camel.dsl.jbang.it.support.JBangTestSupport; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +public class OpenApiITCase extends JBangTestSupport { + + final HttpClient httpClient = HttpClient.newHttpClient(); + + @Test + public void runOpenApiOnExistingImplementation() { + final String openApiImpl + = "https://raw.githubusercontent.com/apache/camel-kamelets-examples/main/jbang/open-api/Greetings.java"; + final String openApiUrl + = "https://raw.githubusercontent.com/apache/camel-kamelets-examples/main/jbang/open-api/greetings-api.json"; + + downloadFile(openApiImpl); + downloadFile(openApiUrl); + executeBackground("run --open-api greetings-api.json Greetings.java"); + checkLogContains("HTTP endpoints summary"); + HttpResponse<String> response = executeHttpRequest("/camel/greetings/jack", false); + Assertions.assertThat(response.statusCode()).isEqualTo(200); + Assertions.assertThat(response.body()).isEqualTo("Hello from jack"); + } + + @Test + public void runOpenApiUsingContractFirstApproach() throws IOException { + final String openApiUrl + = "https://raw.githubusercontent.com/apache/camel-kamelets-examples/main/jbang/open-api-contract-first/petstore-v3.json"; + final String openApiConfig + = "https://raw.githubusercontent.com/apache/camel-kamelets-examples/main/jbang/open-api-contract-first/petstore.camel.yaml"; + + downloadFile(openApiUrl); + downloadFile(openApiConfig); + containerService.executeGenericCommand("mkdir -p camel-mock/pet"); + downloadFile( + "https://raw.githubusercontent.com/apache/camel-kamelets-examples/main/jbang/open-api-contract-first/camel-mock/pet/123.json"); + containerService.executeGenericCommand("mv 123.json camel-mock/pet/"); + executeBackground("run petstore-v3.json petstore.camel.yaml"); + checkLogContains("HTTP endpoints summary"); + + //verify mock + HttpResponse<String> response = executeHttpRequest("/api/v3/pet/123", true); + Assertions.assertThat(response.statusCode()).isEqualTo(200); + Assertions.assertThat(response.body()).contains("donald the dock"); + + //verify sample response + response = executeHttpRequest("/api/v3/pet/" + new Random().nextInt(124, 500), true); + Assertions.assertThat(response.statusCode()).isEqualTo(200); + Assertions.assertThat(response.body()).contains("jack the cat"); + + //verify api-doc + response = executeHttpRequest("/api-doc", true); + Assertions.assertThat(response.statusCode()).isEqualTo(200); + final ObjectMapper objectMapper = new ObjectMapper(); + Map expectedDoc = objectMapper.readValue(new URL( + openApiUrl), + Map.class); + Map actualDoc = objectMapper.readValue(response.body(), Map.class); + Assertions.assertThat(((Map) actualDoc.get("paths")).size()) + .as("check api doc exposed paths size") + .isEqualTo(((Map) expectedDoc.get("paths")).size()); + } + + @Test + public void exportOpenApiUsingContractFirstApproach() { + final String openApiUrl + = "https://raw.githubusercontent.com/apache/camel-kamelets-examples/main/jbang/open-api-contract-first/petstore-v3.json"; + + downloadFile(openApiUrl); + final String generatedPath = mountPoint() + "/petstore"; + generateProperties(Map.of("camel.jbang.runtime", "spring-boot", "camel.jbang.gav", "example:petstore:1.0-SNAPSHOT", + "camel.jbang.exportDir", generatedPath, "camel.jbang.open-api", + DEFAULT_ROUTE_FOLDER + "/petstore-v3.json")); + execute("export"); + assertFileInDataFolderExists("petstore"); + assertFileInDataFolderExists("petstore/src/main/resources/petstore-v3.json"); + assertFileInDataFolderExists("petstore/src/main/resources/camel/generated-openapi.yaml"); + } + + @Test + @InVersion(from = "4.7.00") + public void generateOpenApiWithDtoUsingContractFirstApproach() { + final String openApiUrl + = "https://raw.githubusercontent.com/apache/camel-kamelets-examples/main/jbang/open-api-contract-first/petstore-v3.json"; + + downloadFile(openApiUrl); + + execute("plugin add generate"); + execute("plugin get"); + execute("generate rest --dto --input=petstore-v3.json --output=rest-dsl.yaml --runtime=spring-boot --routes"); + List<String> generatedDTO = containerService.listDirectory(DEFAULT_ROUTE_FOLDER + "/model").toList(); + Assertions.assertThat(generatedDTO).as("check generated DTO number").hasSize(8); + assertFileInContainerExists(DEFAULT_ROUTE_FOLDER + "/rest-dsl.yaml"); + } + + private HttpResponse<String> executeHttpRequest(final String ctxUrl, boolean acceptJson) { + try { + final HttpRequest.Builder builder = HttpRequest + .newBuilder( + new URI(String.format("http://localhost:%s%s", containerService.getDevConsolePort(), ctxUrl))) + .timeout(Duration.ofSeconds(5)) + .GET(); + if (acceptJson) { + builder.headers("Accept", "application/json"); + } + return httpClient.send(builder + .build(), + HttpResponse.BodyHandlers.ofString()); + } catch (IOException | InterruptedException | URISyntaxException e) { + Assertions.fail("unable to execute the request"); + throw new RuntimeException(e); + } + } +} diff --git a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/support/JBangTestSupport.java b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/support/JBangTestSupport.java index 11b5d9e048e..6e6f0164426 100644 --- a/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/support/JBangTestSupport.java +++ b/dsl/camel-jbang/camel-jbang-it/src/test/java/org/apache/camel/dsl/jbang/it/support/JBangTestSupport.java @@ -20,6 +20,13 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -28,7 +35,9 @@ import java.nio.file.StandardCopyOption; import java.nio.file.attribute.PosixFilePermission; import java.time.Duration; import java.util.EnumSet; +import java.util.Map; import java.util.Optional; +import java.util.Properties; import java.util.concurrent.TimeUnit; import org.apache.camel.test.infra.cli.common.CliProperties; @@ -254,4 +263,62 @@ public abstract class JBangTestSupport { protected String makeTheFileWriteable(String containerPath) { return containerService.executeGenericCommand("chmod 777 " + containerPath); } + + protected String downloadNewFileInDataFolder(String downloadUrl) { + try { + return this.downloadNewFileInDataFolder(new URL(downloadUrl), null); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + protected String downloadNewFileInDataFolder(URL downloadUrl, String fileName) { + final String fName = fileName == null ? Paths.get(downloadUrl.getPath().toString()).getFileName().toString() : fileName; + + final StringWriter sw = new StringWriter(); + try (ReadableByteChannel channel = Channels.newChannel(downloadUrl.openStream()); + Reader reader = Channels.newReader(channel, Charset.defaultCharset())) { + reader.transferTo(sw); + sw.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.newFileInDataFolder(fName, sw.toString()); + return fName; + } + + protected String downloadFile(String downloadUrl) { + String fileName = this.downloadNewFileInDataFolder(downloadUrl); + containerService.copyFileInternally(mountPoint() + "/" + fileName, DEFAULT_ROUTE_FOLDER); + return fileName; + } + + protected void generateProperties(Map<String, String> properties) { + this.generateProperties("application.properties", properties, false); + } + + protected void generateProperties(String fileName, Map<String, String> properties, boolean inDataFolder) { + final Properties prop = new Properties(); + prop.putAll(properties); + final StringWriter contentWriter = new StringWriter(); + try { + prop.store(contentWriter, ""); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.newFileInDataFolder(fileName, contentWriter.toString()); + if (!inDataFolder) { + containerService.executeGenericCommand( + String.format("mv %s/%s %s/%s", mountPoint(), fileName, DEFAULT_ROUTE_FOLDER, fileName)); + } + } + + protected void assertFileInContainerExists(String fileAbsolutePath) { + String fileName = Path.of(fileAbsolutePath).getFileName().toFile().getName(); + Assertions.assertThat(containerService.listDirectory(Path.of(fileAbsolutePath).getParent().toAbsolutePath().toString()) + .anyMatch(child -> fileName.equals(child))) + .as("check if file " + fileAbsolutePath + " exists") + .isTrue(); + } + }