-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
599 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>io.quarkiverse.barcode</groupId> | ||
<artifactId>quarkus-qrcodegen-parent</artifactId> | ||
<version>999-SNAPSHOT</version> | ||
</parent> | ||
<artifactId>quarkus-qrcodegen-integration-tests</artifactId> | ||
<name>Quarkus QR Code Generator - Integration Tests</name> | ||
|
||
<properties> | ||
<skipITs>true</skipITs> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-rest</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-smallrye-openapi</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkiverse.barcode</groupId> | ||
<artifactId>quarkus-qrcodegen</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-junit5</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.rest-assured</groupId> | ||
<artifactId>rest-assured</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-maven-plugin</artifactId> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>build</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-failsafe-plugin</artifactId> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>integration-test</goal> | ||
<goal>verify</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
<configuration> | ||
<systemPropertyVariables> | ||
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path> | ||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> | ||
<maven.home>${maven.home}</maven.home> | ||
</systemPropertyVariables> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<profiles> | ||
<profile> | ||
<id>native-image</id> | ||
<activation> | ||
<property> | ||
<name>native</name> | ||
</property> | ||
</activation> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<configuration> | ||
<skipTests>${native.surefire.skip}</skipTests> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
<properties> | ||
<skipITs>false</skipITs> | ||
<quarkus.native.enabled>true</quarkus.native.enabled> | ||
</properties> | ||
</profile> | ||
</profiles> | ||
</project> |
33 changes: 33 additions & 0 deletions
33
...rcodegen/integration-tests/src/main/java/io/quarkiverse/barcode/it/BaseImageResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package io.quarkiverse.barcode.it; | ||
|
||
import jakarta.ws.rs.core.HttpHeaders; | ||
import jakarta.ws.rs.core.Response; | ||
|
||
public class BaseImageResource { | ||
public static final String CONTENT_DISPOSITION = "attachment;filename=%s"; | ||
public static final String PNG_MIME_TYPE = "image/png"; | ||
public static final String SVG_MIME_TYPE = "image/svg+xml"; | ||
|
||
/** | ||
* Builds a Response containing image data with appropriate headers. | ||
* | ||
* @param imageData The raw bytes of the image | ||
* @param mimeType The MIME type of the image (e.g. "image/png") | ||
* @param fileName The filename to use in the Content-Disposition header | ||
* @return A Response object configured with the image data and headers | ||
*/ | ||
protected Response buildImageResponse(byte[] imageData, String mimeType, String fileName) { | ||
final Response.ResponseBuilder response = Response.ok(imageData); | ||
response.header(HttpHeaders.CONTENT_DISPOSITION, CONTENT_DISPOSITION.formatted(fileName)); | ||
response.header(HttpHeaders.CONTENT_TYPE, mimeType); | ||
return response.build(); | ||
} | ||
|
||
protected String stripWhitespace(String svgContent) { | ||
// Remove unnecessary whitespace characters (spaces, tabs, newlines) from SVG content | ||
return svgContent | ||
.replaceAll(">\\s+<", "><") // Remove whitespace between tags | ||
.replaceAll("\\s{2,}", " ") // Replace multiple spaces with a single space | ||
.replaceAll("\\s+/>", "/>"); // Trim spaces before self-closing tags | ||
} | ||
} |
143 changes: 143 additions & 0 deletions
143
...rcodegen/integration-tests/src/main/java/io/quarkiverse/barcode/it/QrCodeGenResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/* | ||
* 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 io.quarkiverse.barcode.it; | ||
|
||
import java.awt.image.BufferedImage; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Objects; | ||
|
||
import javax.imageio.ImageIO; | ||
|
||
import jakarta.enterprise.context.ApplicationScoped; | ||
import jakarta.ws.rs.GET; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.core.MediaType; | ||
import jakarta.ws.rs.core.Response; | ||
|
||
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; | ||
import org.eclipse.microprofile.openapi.annotations.media.Content; | ||
import org.eclipse.microprofile.openapi.annotations.media.Schema; | ||
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; | ||
|
||
import io.nayuki.qrcodegen.QrCode; | ||
|
||
@Path("/qrcodegen") | ||
@ApplicationScoped | ||
public class QrCodeGenResource extends BaseImageResource { | ||
|
||
@GET | ||
@Path("qr/png") | ||
@APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) | ||
public Response qrCodePng() throws IOException { | ||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { | ||
QrCode qr = QrCode.encodeText("Quarkus Rocks!", QrCode.Ecc.MEDIUM); | ||
BufferedImage img = toImage(qr, 4, 10, 0xFFE0E0, 0x602020); | ||
ImageIO.write(img, "png", outputStream); | ||
|
||
// return the image | ||
return buildImageResponse(outputStream.toByteArray(), PNG_MIME_TYPE, "qrcodegen.png"); | ||
} | ||
} | ||
|
||
@GET | ||
@Path("qr/svg") | ||
@APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) | ||
public Response qrCodeSvg() { | ||
QrCode qr = QrCode.encodeText("Quarkus Rocks!", QrCode.Ecc.MEDIUM); | ||
String svg = stripWhitespace(toSvgString(qr, 4, "#FFFFFF", "#000000")); // Convert to SVG XML code | ||
|
||
// return the image | ||
return buildImageResponse(svg.getBytes(StandardCharsets.UTF_8), SVG_MIME_TYPE, "qrcodegen.svg"); | ||
} | ||
|
||
/** | ||
* Returns a raster image depicting the specified QR Code, with | ||
* the specified module scale, border modules, and module colors. | ||
* <p> | ||
* For example, scale=10 and border=4 means to pad the QR Code with 4 light border | ||
* modules on all four sides, and use 10×10 pixels to represent each module. | ||
* | ||
* @param qr the QR Code to render (not {@code null}) | ||
* @param scale the side length (measured in pixels, must be positive) of each module | ||
* @param border the number of border modules to add, which must be non-negative | ||
* @param lightColor the color to use for light modules, in 0xRRGGBB format | ||
* @param darkColor the color to use for dark modules, in 0xRRGGBB format | ||
* @return a new image representing the QR Code, with padding and scaling | ||
* @throws NullPointerException if the QR Code is {@code null} | ||
* @throws IllegalArgumentException if the scale or border is out of range, or if | ||
* {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE | ||
*/ | ||
private static BufferedImage toImage(QrCode qr, int scale, int border, int lightColor, int darkColor) { | ||
Objects.requireNonNull(qr); | ||
if (scale <= 0 || border < 0) | ||
throw new IllegalArgumentException("Value out of range"); | ||
if (border > Integer.MAX_VALUE / 2 || qr.size + border * 2L > Integer.MAX_VALUE / scale) | ||
throw new IllegalArgumentException("Scale or border too large"); | ||
|
||
BufferedImage result = new BufferedImage((qr.size + border * 2) * scale, (qr.size + border * 2) * scale, | ||
BufferedImage.TYPE_INT_RGB); | ||
for (int y = 0; y < result.getHeight(); y++) { | ||
for (int x = 0; x < result.getWidth(); x++) { | ||
boolean color = qr.getModule(x / scale - border, y / scale - border); | ||
result.setRGB(x, y, color ? darkColor : lightColor); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
/** | ||
* Returns a string of SVG code for an image depicting the specified QR Code, with the specified | ||
* number of border modules. The string always uses Unix newlines (\n), regardless of the platform. | ||
* | ||
* @param qr the QR Code to render (not {@code null}) | ||
* @param border the number of border modules to add, which must be non-negative | ||
* @param lightColor the color to use for light modules, in any format supported by CSS, not {@code null} | ||
* @param darkColor the color to use for dark modules, in any format supported by CSS, not {@code null} | ||
* @return a string representing the QR Code as an SVG XML document | ||
* @throws NullPointerException if any object is {@code null} | ||
* @throws IllegalArgumentException if the border is negative | ||
*/ | ||
private static String toSvgString(QrCode qr, int border, String lightColor, String darkColor) { | ||
Objects.requireNonNull(qr); | ||
Objects.requireNonNull(lightColor); | ||
Objects.requireNonNull(darkColor); | ||
if (border < 0) | ||
throw new IllegalArgumentException("Border must be non-negative"); | ||
StringBuilder sb = new StringBuilder() | ||
.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") | ||
.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n") | ||
.append(String.format( | ||
"<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 %1$d %1$d\" stroke=\"none\">\n", | ||
qr.size + (long) border * 2)) | ||
.append("\t<rect width=\"100%\" height=\"100%\" fill=\"").append(lightColor).append("\"/>\n") | ||
.append("\t<path d=\""); | ||
for (int y = 0; y < qr.size; y++) { | ||
for (int x = 0; x < qr.size; x++) { | ||
if (qr.getModule(x, y)) { | ||
if (x != 0 || y != 0) | ||
sb.append(" "); | ||
sb.append(String.format("M%d,%dh1v1h-1z", x + (long) border, y + (long) border)); | ||
} | ||
} | ||
} | ||
return sb.append("\" fill=\"").append(darkColor).append("\"/>\n") | ||
.append("</svg>\n") | ||
.toString(); | ||
} | ||
} |
Empty file.
7 changes: 7 additions & 0 deletions
7
...odegen/integration-tests/src/test/java/io/quarkiverse/barcode/it/QrCodeGenResourceIT.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package io.quarkiverse.barcode.it; | ||
|
||
import io.quarkus.test.junit.QuarkusIntegrationTest; | ||
|
||
@QuarkusIntegrationTest | ||
public class QrCodeGenResourceIT extends QrCodeGenResourceTest { | ||
} |
41 changes: 41 additions & 0 deletions
41
...egen/integration-tests/src/test/java/io/quarkiverse/barcode/it/QrCodeGenResourceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package io.quarkiverse.barcode.it; | ||
|
||
import static io.restassured.RestAssured.given; | ||
import static jakarta.ws.rs.core.HttpHeaders.CONTENT_LENGTH; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.hamcrest.Matchers.greaterThan; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.hamcrest.Matchers.notNullValue; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import io.quarkus.test.junit.QuarkusTest; | ||
|
||
@QuarkusTest | ||
public class QrCodeGenResourceTest { | ||
|
||
@Test | ||
public void testQrCodeGenPng() { | ||
given() | ||
.when().get("/qrcodegen/qr/png") | ||
.then() | ||
.statusCode(200) | ||
.contentType(equalTo(BaseImageResource.PNG_MIME_TYPE)) | ||
.header(CONTENT_LENGTH, Integer::parseInt, greaterThan(0)) | ||
.body(notNullValue()); | ||
} | ||
|
||
@Test | ||
public void testQrCodeGenSvg() { | ||
given() | ||
.when().get("/qrcodegen/qr/svg") | ||
.then() | ||
.statusCode(200) | ||
.contentType(equalTo(BaseImageResource.SVG_MIME_TYPE)) | ||
.header(CONTENT_LENGTH, Integer::parseInt, greaterThan(0)) | ||
.body(is( | ||
""" | ||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 29 29" stroke="none"><rect width="100%" height="100%" fill="#FFFFFF"/><path d="M4,4h1v1h-1z M5,4h1v1h-1z M6,4h1v1h-1z M7,4h1v1h-1z M8,4h1v1h-1z M9,4h1v1h-1z M10,4h1v1h-1z M12,4h1v1h-1z M15,4h1v1h-1z M18,4h1v1h-1z M19,4h1v1h-1z M20,4h1v1h-1z M21,4h1v1h-1z M22,4h1v1h-1z M23,4h1v1h-1z M24,4h1v1h-1z M4,5h1v1h-1z M10,5h1v1h-1z M12,5h1v1h-1z M13,5h1v1h-1z M16,5h1v1h-1z M18,5h1v1h-1z M24,5h1v1h-1z M4,6h1v1h-1z M6,6h1v1h-1z M7,6h1v1h-1z M8,6h1v1h-1z M10,6h1v1h-1z M13,6h1v1h-1z M15,6h1v1h-1z M18,6h1v1h-1z M20,6h1v1h-1z M21,6h1v1h-1z M22,6h1v1h-1z M24,6h1v1h-1z M4,7h1v1h-1z M6,7h1v1h-1z M7,7h1v1h-1z M8,7h1v1h-1z M10,7h1v1h-1z M12,7h1v1h-1z M13,7h1v1h-1z M14,7h1v1h-1z M18,7h1v1h-1z M20,7h1v1h-1z M21,7h1v1h-1z M22,7h1v1h-1z M24,7h1v1h-1z M4,8h1v1h-1z M6,8h1v1h-1z M7,8h1v1h-1z M8,8h1v1h-1z M10,8h1v1h-1z M13,8h1v1h-1z M14,8h1v1h-1z M15,8h1v1h-1z M18,8h1v1h-1z M20,8h1v1h-1z M21,8h1v1h-1z M22,8h1v1h-1z M24,8h1v1h-1z M4,9h1v1h-1z M10,9h1v1h-1z M13,9h1v1h-1z M18,9h1v1h-1z M24,9h1v1h-1z M4,10h1v1h-1z M5,10h1v1h-1z M6,10h1v1h-1z M7,10h1v1h-1z M8,10h1v1h-1z M9,10h1v1h-1z M10,10h1v1h-1z M12,10h1v1h-1z M14,10h1v1h-1z M16,10h1v1h-1z M18,10h1v1h-1z M19,10h1v1h-1z M20,10h1v1h-1z M21,10h1v1h-1z M22,10h1v1h-1z M23,10h1v1h-1z M24,10h1v1h-1z M12,11h1v1h-1z M4,12h1v1h-1z M6,12h1v1h-1z M7,12h1v1h-1z M9,12h1v1h-1z M10,12h1v1h-1z M11,12h1v1h-1z M14,12h1v1h-1z M18,12h1v1h-1z M21,12h1v1h-1z M23,12h1v1h-1z M24,12h1v1h-1z M5,13h1v1h-1z M8,13h1v1h-1z M9,13h1v1h-1z M12,13h1v1h-1z M13,13h1v1h-1z M19,13h1v1h-1z M20,13h1v1h-1z M21,13h1v1h-1z M22,13h1v1h-1z M23,13h1v1h-1z M24,13h1v1h-1z M5,14h1v1h-1z M7,14h1v1h-1z M8,14h1v1h-1z M10,14h1v1h-1z M12,14h1v1h-1z M15,14h1v1h-1z M18,14h1v1h-1z M23,14h1v1h-1z M24,14h1v1h-1z M5,15h1v1h-1z M6,15h1v1h-1z M11,15h1v1h-1z M13,15h1v1h-1z M14,15h1v1h-1z M15,15h1v1h-1z M16,15h1v1h-1z M19,15h1v1h-1z M21,15h1v1h-1z M6,16h1v1h-1z M8,16h1v1h-1z M10,16h1v1h-1z M11,16h1v1h-1z M13,16h1v1h-1z M16,16h1v1h-1z M17,16h1v1h-1z M19,16h1v1h-1z M12,17h1v1h-1z M14,17h1v1h-1z M16,17h1v1h-1z M18,17h1v1h-1z M19,17h1v1h-1z M20,17h1v1h-1z M21,17h1v1h-1z M23,17h1v1h-1z M24,17h1v1h-1z M4,18h1v1h-1z M5,18h1v1h-1z M6,18h1v1h-1z M7,18h1v1h-1z M8,18h1v1h-1z M9,18h1v1h-1z M10,18h1v1h-1z M12,18h1v1h-1z M13,18h1v1h-1z M15,18h1v1h-1z M17,18h1v1h-1z M18,18h1v1h-1z M19,18h1v1h-1z M20,18h1v1h-1z M21,18h1v1h-1z M4,19h1v1h-1z M10,19h1v1h-1z M12,19h1v1h-1z M14,19h1v1h-1z M15,19h1v1h-1z M17,19h1v1h-1z M21,19h1v1h-1z M22,19h1v1h-1z M24,19h1v1h-1z M4,20h1v1h-1z M6,20h1v1h-1z M7,20h1v1h-1z M8,20h1v1h-1z M10,20h1v1h-1z M13,20h1v1h-1z M14,20h1v1h-1z M15,20h1v1h-1z M16,20h1v1h-1z M19,20h1v1h-1z M22,20h1v1h-1z M23,20h1v1h-1z M4,21h1v1h-1z M6,21h1v1h-1z M7,21h1v1h-1z M8,21h1v1h-1z M10,21h1v1h-1z M12,21h1v1h-1z M13,21h1v1h-1z M14,21h1v1h-1z M16,21h1v1h-1z M18,21h1v1h-1z M19,21h1v1h-1z M23,21h1v1h-1z M4,22h1v1h-1z M6,22h1v1h-1z M7,22h1v1h-1z M8,22h1v1h-1z M10,22h1v1h-1z M12,22h1v1h-1z M14,22h1v1h-1z M15,22h1v1h-1z M16,22h1v1h-1z M17,22h1v1h-1z M18,22h1v1h-1z M4,23h1v1h-1z M10,23h1v1h-1z M16,23h1v1h-1z M17,23h1v1h-1z M18,23h1v1h-1z M19,23h1v1h-1z M20,23h1v1h-1z M24,23h1v1h-1z M4,24h1v1h-1z M5,24h1v1h-1z M6,24h1v1h-1z M7,24h1v1h-1z M8,24h1v1h-1z M9,24h1v1h-1z M10,24h1v1h-1z M12,24h1v1h-1z M13,24h1v1h-1z M17,24h1v1h-1z M18,24h1v1h-1z M22,24h1v1h-1z" fill="#000000"/></svg> | ||
""")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.