fixes Add SpringBoot integration test to showcase cp configuration
ADD
asciidoctorj-springboot-integration-test/build.gradle
+jar.enabled = false
+
+dependencies {
+ testCompileOnly project(':asciidoctorj')
+ testCompile "junit:junit:$junitVersion"
+ testCompile "org.assertj:assertj-core:$assertjVersion"
+ testCompile "commons-io:commons-io:$commonsioVersion"
+ testCompile "org.awaitility:awaitility:4.0.3"
+ testCompile "com.squareup.okhttp3:okhttp:4.9.1"
+ testCompile "com.google.code.gson:gson:2.8.6"
+}
+
+test.dependsOn(':asciidoctorj-springboot-integration-test:springboot-app:assemble')

ADD
asciidoctorj-springboot-integration-test/springboot-app/build.gradle
+plugins {
+ id 'org.springframework.boot' version '2.4.2'
+ id 'io.spring.dependency-management' version '1.0.11.RELEASE'
+ id 'java'
+}
+
+group = 'org.asciidoctor.it'
+version = '0.0.1-SNAPSHOT'
+sourceCompatibility = '1.8'
+
+repositories {
+ mavenCentral()
+}
+
+ext {
+ asciidoctorjVersion = '2.4.2'
+}
+
+dependencies {
+ implementation project(':asciidoctorj')
+ implementation 'org.springframework.boot:spring-boot-starter-web'
+ implementation 'org.springframework.boot:spring-boot-starter-actuator'
+
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
+}
+
+bootJar {
+ requiresUnpack '**/jruby-complete-*.jar'
+ requiresUnpack '**/asciidoctorj-*.jar'
+}
+
+test {
+ useJUnitPlatform()
+}

ADD
asciidoctorj-springboot-integration-test/springboot-app/settings.gradle
+rootProject.name = 'springboot-app'

ADD
asciidoctorj-springboot-integration-test/springboot-app/src/main/java/org/asciidoctor/it/springboot/AsciidoctorService.java
+package org.asciidoctor.it.springboot;
+
+import org.asciidoctor.Asciidoctor;
+import org.asciidoctor.OptionsBuilder;
+import org.asciidoctor.SafeMode;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AsciidoctorService {
+
+ private final Asciidoctor asciidoctor = Asciidoctor.Factory.create();
+
+
+ public String convert(String source) {
+ return asciidoctor.convert(source, defaultOptions());
+ }
+
+ private OptionsBuilder defaultOptions() {
+ return OptionsBuilder.options()
+ .backend("html5")
+ .safe(SafeMode.SAFE)
+ .headerFooter(true)
+ .toFile(false);
+ }
+}

ADD
asciidoctorj-springboot-integration-test/springboot-app/src/main/java/org/asciidoctor/it/springboot/ConvertedResource.java
+package org.asciidoctor.it.springboot;
+
+import java.beans.ConstructorProperties;
+
+public class ConvertedResource {
+
+ private final String content;
+ private final String contentType;
+
+ @ConstructorProperties({"content", "contentType"})
+ public ConvertedResource(String content, String contentType) {
+ this.content = content;
+ this.contentType = contentType;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+}

ADD
asciidoctorj-springboot-integration-test/springboot-app/src/main/java/org/asciidoctor/it/springboot/ConverterController.java
+package org.asciidoctor.it.springboot;
+
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+
+/**
+ * Simple Spring MVC Rest Controller to convert AsciiDoc sources to HTML.
+ */
+@RestController
+public class ConverterController {
+
+ private final AsciidoctorService asciidoctorService;
+
+ public ConverterController(AsciidoctorService asciidoctorService) {
+ this.asciidoctorService = asciidoctorService;
+ }
+
+
+ @PostMapping("/asciidoc")
+ public ConvertedResource convert(@RequestBody SourceContent content) {
+ byte[] decodedContent = Base64.getDecoder().decode(content.getData());
+ String converted = asciidoctorService.convert(new String(decodedContent));
+
+ String encodedConvertedContent = Base64.getEncoder().encodeToString(converted.getBytes(StandardCharsets.UTF_8));
+ return new ConvertedResource(encodedConvertedContent, MediaType.TEXT_HTML_VALUE);
+ }
+}

ADD
asciidoctorj-springboot-integration-test/springboot-app/src/main/java/org/asciidoctor/it/springboot/SourceContent.java
+package org.asciidoctor.it.springboot;
+
+public class SourceContent {
+
+ /**
+ * Base64 encoded
+ */
+ private String data;
+
+
+ public void setData(String data) {
+ this.data = data;
+ }
+
+ public String getData() {
+ return data;
+ }
+}

ADD
asciidoctorj-springboot-integration-test/springboot-app/src/main/java/org/asciidoctor/it/springboot/SpringbootAppApplication.java
+package org.asciidoctor.it.springboot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * This is a test application for testing purposes.
+ * It does not in any represent the recommended way to implement an
+ * Asciidoctorj conversion service with SpringBoot.
+ */
+@SpringBootApplication
+public class SpringbootAppApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringbootAppApplication.class, args);
+ }
+
+}

ADD
asciidoctorj-springboot-integration-test/springboot-app/src/main/resources/application.properties
+spring.main.banner-mode=off
+management.endpoint.health.probes.enabled=true
+info.app.java.source=${java.version}
+info.app.java.target=${java.version}

ADD
asciidoctorj-springboot-integration-test/springboot-app/src/test/java/org/asciidoctor/it/springboot/SpringbootAppApplicationTests.java
+package org.asciidoctor.it.springboot;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class SpringbootAppApplicationTests {
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Test
+ void should_convert_document() {
+
+ final SourceContent request = new SourceContent();
+ String asciidocDocument = "= Title\n" +
+ "\n" +
+ "== First chapter\n" +
+ "first chapter\n" +
+ "\n" +
+ "== Second chapter\n" +
+ "second chapter";
+ request.setData(base64Encode(asciidocDocument));
+
+ ResponseEntity<ConvertedResource> responseEntity = restTemplate.postForEntity("/asciidoc", request, ConvertedResource.class);
+
+ assertThat(responseEntity.getStatusCodeValue())
+ .isEqualTo(HttpStatus.OK.value());
+ final ConvertedResource response = responseEntity.getBody();
+ assertThat(response.getContentType())
+ .isEqualTo(MediaType.TEXT_HTML_VALUE);
+
+ assertThat(base64Decode(response))
+ .contains("<h1>Title</h1>")
+ .contains("<p>first chapter</p>")
+ .contains("<h2 id=\"_second_chapter\">Second chapter</h2>");
+ }
+
+ private String base64Encode(String asciidocDocument) {
+ return new String(Base64.getEncoder().encode(asciidocDocument.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ private String base64Decode(ConvertedResource response) {
+ return new String(Base64.getDecoder().decode(response.getContent()));
+ }
+}

ADD
asciidoctorj-springboot-integration-test/src/test/java/org/asciidoctor/it/springboot/ProcessHelper.java
+package org.asciidoctor.it.springboot;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class ProcessHelper {
+
+ static ProcessOutput run(Map<String, String> env, String... command) throws IOException, InterruptedException {
+ ProcessBuilder processBuilder = new ProcessBuilder(command)
+ .directory(new File("springboot-app"));
+
+ processBuilder.environment().clear();
+ String javaHome = System.getProperty("java.home");
+ processBuilder.environment().put("JAVA_HOME", javaHome);
+ processBuilder.environment().put("PATH", javaHome + "/bin");
+ processBuilder.environment().putAll(env);
+
+ Process process = processBuilder.start();
+
+ final InputStream is = process.getInputStream();
+ final InputStream es = process.getErrorStream();
+ boolean status = process.waitFor(10l, TimeUnit.SECONDS);
+ if (!status) {
+ byte[] buffer = new byte[1800];
+ IOUtils.read(is, buffer);
+ return new ProcessOutput(process, new String(buffer), new String());
+ }
+
+ return new ProcessOutput(IOUtils.toString(es), IOUtils.toString(is));
+ }
+
+
+ static class ProcessOutput {
+ final Process process;
+ final String out, err;
+
+ public ProcessOutput(String sto, String err) {
+ this(null, sto, err);
+ }
+
+ public ProcessOutput(Process process, String sto, String err) {
+ this.process = process;
+ this.out = sto;
+ this.err = err;
+ }
+ }
+}

ADD
asciidoctorj-springboot-integration-test/src/test/java/org/asciidoctor/it/springboot/SpringBootTest.java
+package org.asciidoctor.it.springboot;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import kotlin.collections.ArrayDeque;
+import okhttp3.*;
+import org.awaitility.Awaitility;
+import org.junit.After;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonMap;
+import static org.asciidoctor.it.springboot.ProcessHelper.run;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SpringBootTest {
+
+ final List<Process> testProcesses = new ArrayDeque<>();
+
+
+ @Test
+ public void should_start_SpringBoot_service_with_requiresUnpack() throws IOException, InterruptedException {
+
+ int serverPort = 8000 + ThreadLocalRandom.current().nextInt(1000);
+ startSpringBootApp(serverPort);
+
+ String asciidocDocument = "= Title\n" +
+ "\n" +
+ "== First chapter\n" +
+ "first chapter\n" +
+ "\n" +
+ "== Second chapter\n" +
+ "second chapter";
+ Map<String, String> jsonResponse = convertDocument(asciidocDocument, serverPort);
+
+ assertThat(jsonResponse.get("contentType"))
+ .isEqualTo("text/html");
+ assertThat(base64Decode(jsonResponse.get("content")))
+ .contains("<h1>Title</h1>")
+ .contains("<p>first chapter</p>")
+ .contains("<h2 id=\"_second_chapter\">Second chapter</h2>");
+ }
+
+ @After
+ public void cleanup() {
+ testProcesses.forEach(Process::destroy);
+ }
+
+ private Map<String, String> convertDocument(String asciidocDocument, int serverPort) throws IOException {
+ String body = "{\"data\":\"" + base64Encode(asciidocDocument) + "\"}";
+
+ Request request = new Request.Builder()
+ .url("http://localhost:" + serverPort + "/asciidoc")
+ .post(RequestBody.create(body, MediaType.parse("application/json")))
+ .build();
+ Response response = new OkHttpClient()
+ .newCall(request)
+ .execute();
+
+ return new Gson()
+ .fromJson(response.body().string(), new TypeToken<Map<String, String>>() {}.getType());
+ }
+
+ private String base64Encode(String asciidocDocument) {
+ return new String(Base64.getEncoder().encode(asciidocDocument.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ private String base64Decode(String value) {
+ return new String(Base64.getDecoder().decode(value));
+ }
+
+ private void startSpringBootApp(int serverPort) throws IOException, InterruptedException {
+
+ ProcessHelper.ProcessOutput run = run(singletonMap("SERVER_PORT", String.valueOf(serverPort)),
+ "java", "-jar", "build/libs/springboot-app-0.0.1-SNAPSHOT.jar");
+
+ testProcesses.add(run.process);
+
+ Awaitility.await()
+ .atMost(30, TimeUnit.SECONDS)
+ .pollInterval(3, TimeUnit.SECONDS)
+ .until(() -> {
+ Request request = new Request.Builder()
+ .url("http://localhost:" + serverPort + "/actuator/health/readiness")
+ .build();
+ Response response = new OkHttpClient()
+ .newCall(request)
+ .execute();
+ return response.isSuccessful() && response.body().string().contains("UP");
+ });
+ }
+}

MODIFY
build.gradle
build.gradle
@@ -66,6 +66,7 @@ ext {
jrubyVersion = '9.2.14.0'
jsoupVersion = '1.12.1'
junitVersion = '4.13.1'
+ assertjVersion = '3.19.0'
nettyVersion = '4.1.58.Final'
saxonVersion = '9.9.0-2'
xmlMatchersVersion = '1.0-RC1'
@@ -135,7 +136,7 @@ subprojects {
configFile = rootProject.file('config/codenarc/codenarc.groovy')
}
- if (it.name != 'asciidoctorj-wildfly-integration-test') {
+ if (!isIntegrationTestProject(it)) {
dependencies {
testCompile "junit:junit:$junitVersion"
@@ -178,10 +179,16 @@ subprojects {
}
-configure(subprojects.findAll { !it.isDistribution() && it.name != 'asciidoctorj-api' && it.name != 'asciidoctorj-documentation' && it.name != 'asciidoctorj-test-support' && it.name != 'asciidoctorj-arquillian-extension' && it.name != 'asciidoctorj-wildfly-integration-test' }) {
+configure(subprojects.findAll { !it.isDistribution() && it.name != 'asciidoctorj-api' && it.name != 'asciidoctorj-documentation' && it.name != 'asciidoctorj-test-support' && it.name != 'asciidoctorj-arquillian-extension' && !isIntegrationTestProject(it) }) {
apply from: rootProject.file('gradle/versioncheck.gradle')
}
+boolean isIntegrationTestProject(def project) {
+ project.name in ['asciidoctorj-wildfly-integration-test',
+ 'asciidoctorj-springboot-integration-test',
+ 'springboot-app']
+}
+
// apply JRuby and sources/javadocs packaging stuff for all subprojects except the distribution
configure(subprojects.findAll { !it.isDistribution() }) {

MODIFY
settings.gradle
settings.gradle
@@ -7,6 +7,8 @@ include \
'asciidoctorj-distribution',
'asciidoctorj-documentation',
'asciidoctorj-wildfly-integration-test',
+ 'asciidoctorj-springboot-integration-test',
+ 'asciidoctorj-springboot-integration-test:springboot-app',
'asciidoctorj-test-support'
project(':asciidoctorj').projectDir = file('asciidoctorj-core')

fixes #973 Add SpringBoot integration test to showcase cp configuration