Skip to content

Commit 62e8510

Browse files
committed
improve image asserts
in addition to just "data:*base64" prefix, also 1. check image size 2. verify that the base64 part is a valid parseable image data 3. validate XML format in case of SVG images
1 parent e63a834 commit 62e8510

File tree

5 files changed

+132
-28
lines changed

5 files changed

+132
-28
lines changed

src/main/java/net/datafaker/providers/base/Image.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33

44
import javax.imageio.ImageIO;
5-
import java.awt.Color;
65
import java.awt.*;
6+
import java.awt.Color;
77
import java.awt.image.BufferedImage;
88
import java.io.ByteArrayOutputStream;
99
import java.io.IOException;
1010
import java.util.Base64;
1111

1212
import static java.awt.Color.WHITE;
13+
import static java.util.Objects.requireNonNull;
1314
import static net.datafaker.providers.base.Image.ImageType.BMP;
1415
import static net.datafaker.providers.base.Image.ImageType.GIF;
1516
import static net.datafaker.providers.base.Image.ImageType.JPEG;
@@ -106,11 +107,7 @@ public Base64ImageRuleConfig build() {
106107
}
107108

108109
public ImageBuilder type(ImageType imageType) {
109-
if(imageType == null) {
110-
throw new IllegalArgumentException("Type cannot be null");
111-
}
112-
113-
this.imageType = imageType;
110+
this.imageType = requireNonNull(imageType, "Image type cannot be null");
114111
return this;
115112
}
116113

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package net.datafaker.assertions;
2+
3+
import net.datafaker.providers.base.Image.ImageType;
4+
import org.assertj.core.api.AbstractAssert;
5+
import org.opentest4j.AssertionFailedError;
6+
import org.w3c.dom.Document;
7+
import org.w3c.dom.Node;
8+
import org.w3c.dom.NodeList;
9+
10+
import javax.imageio.ImageIO;
11+
import javax.xml.parsers.DocumentBuilder;
12+
import javax.xml.parsers.DocumentBuilderFactory;
13+
import java.awt.image.BufferedImage;
14+
import java.io.ByteArrayInputStream;
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.util.Base64;
18+
import java.util.regex.Pattern;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
public class ImageAssert extends AbstractAssert<ImageAssert, ImageData> {
23+
private static final Pattern RE = Pattern.compile("data:image/.+;base64,(.+)");
24+
25+
ImageAssert(ImageData actual) {
26+
super(actual, ImageAssert.class);
27+
}
28+
29+
public ImageAssert is(ImageType type, int width, int height) {
30+
assertImageDataUrl(type);
31+
return switch (type) {
32+
case SVG -> verifySvg(actual, width, height);
33+
case BMP, GIF, JPEG, PNG, TIFF -> verifyImage(actual, width, height);
34+
};
35+
}
36+
37+
private void assertImageDataUrl(ImageType type) {
38+
String prefix = "data:%s;base64,".formatted(type.getMimeType());
39+
40+
if (!actual.url().startsWith(prefix)) {
41+
String message = "Expected image data URL starting with \"%s\", but received: \"%s\"".formatted(prefix, actual.url());
42+
throw new AssertionFailedError(message, prefix, actual.url());
43+
}
44+
45+
assertThat(actual.url().substring(actual.url().indexOf(",") + 1))
46+
.isNotBlank()
47+
.isBase64();
48+
}
49+
50+
private ImageAssert verifyImage(ImageData image, int width, int height) {
51+
String content = RE.matcher(image.url()).replaceFirst("$1");
52+
byte[] bytes = Base64.getDecoder().decode(content);
53+
try (InputStream in = new ByteArrayInputStream(bytes)) {
54+
BufferedImage i = ImageIO.read(in);
55+
assertThat(i.getWidth()).isEqualTo(width);
56+
assertThat(i.getHeight()).isEqualTo(height);
57+
} catch (IOException e) {
58+
throw new RuntimeException("Failed to parse image " + image.url(), e);
59+
}
60+
return this;
61+
}
62+
63+
private ImageAssert verifySvg(ImageData image, int width, int height) {
64+
String content = RE.matcher(image.url()).replaceFirst("$1");
65+
byte[] bytes = Base64.getDecoder().decode(content);
66+
try {
67+
String xml = new String(bytes);
68+
assertThat(xml).matches("<svg xmlns=\".+\" width=\"256\" height=\"256\">.*");
69+
70+
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
71+
Document doc = builder.parse(new ByteArrayInputStream(bytes));
72+
NodeList childNodes = doc.getChildNodes();
73+
assertThat(childNodes.getLength()).isEqualTo(1);
74+
75+
Node svg = childNodes.item(0);
76+
assertThat(svg.getNodeName()).isEqualTo("svg");
77+
78+
int boxSize = width / 8;
79+
int rectCount = (width / boxSize) * (height / boxSize);
80+
assertThat(svg.getChildNodes().getLength()).isEqualTo(rectCount);
81+
assertThat(svg.getChildNodes().item(0).getNodeName()).isEqualTo("rect");
82+
} catch (Exception e) {
83+
throw new RuntimeException("Failed to parse image " + image.url(), e);
84+
}
85+
return this;
86+
}
87+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package net.datafaker.assertions;
2+
3+
import org.assertj.core.api.Assertions;
4+
5+
public class ImageAssertions extends Assertions {
6+
public static ImageAssert assertThatImage(String imageDataUrl) {
7+
return assertThat(new ImageData(imageDataUrl));
8+
}
9+
10+
public static ImageAssert assertThat(ImageData actual) {
11+
return new ImageAssert(actual);
12+
}
13+
}
14+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package net.datafaker.assertions;
2+
3+
public record ImageData(String url) {
4+
}

src/test/java/net/datafaker/providers/base/ImageTest.java

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,83 +6,85 @@
66
import org.junit.jupiter.params.ParameterizedTest;
77
import org.junit.jupiter.params.provider.EnumSource;
88

9-
import static org.assertj.core.api.Assertions.assertThat;
9+
import static net.datafaker.assertions.ImageAssertions.assertThatImage;
10+
import static net.datafaker.providers.base.Image.ImageType.BMP;
11+
import static net.datafaker.providers.base.Image.ImageType.GIF;
12+
import static net.datafaker.providers.base.Image.ImageType.JPEG;
13+
import static net.datafaker.providers.base.Image.ImageType.PNG;
14+
import static net.datafaker.providers.base.Image.ImageType.SVG;
15+
import static net.datafaker.providers.base.Image.ImageType.TIFF;
16+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
1017
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
1118

1219
class ImageTest {
1320
private final Faker faker = new Faker();
1421

1522
@Test
1623
void bmp() {
17-
assertThat(faker.image().base64BMP()).startsWith("data:image/bmp;base64,");
24+
assertThatImage(faker.image().base64BMP()).is(BMP, 256, 256);
1825
}
1926

2027
@Test
2128
void gif() {
22-
assertThat(faker.image().base64GIF()).startsWith("data:image/gif;base64,");
29+
assertThatImage(faker.image().base64GIF()).is(GIF, 256, 256);
2330
}
2431

2532
@Test
2633
void png() {
27-
assertThat(faker.image().base64PNG()).startsWith("data:image/png;base64,");
34+
assertThatImage(faker.image().base64PNG()).is(PNG, 256, 256);
2835
}
2936

3037
@Test
3138
void jpg() {
32-
assertThat(faker.image().base64JPG()).startsWith("data:image/jpeg;base64,");
39+
assertThatImage(faker.image().base64JPG()).is(JPEG, 256, 256);
3340
}
3441

3542
@Test
3643
void jpeg() {
37-
assertThat(faker.image().base64JPEG()).startsWith("data:image/jpeg;base64,");
44+
assertThatImage(faker.image().base64JPEG()).is(JPEG, 256, 256);
3845
}
3946

4047
@Test
4148
void svg() {
42-
assertThat(faker.image().base64SVG()).startsWith("data:image/svg+xml;base64,");
49+
assertThatImage(faker.image().base64SVG()).is(SVG, 256, 256);
4350
}
4451

4552
@Test
4653
void tiff() {
47-
assertThat(faker.image().base64TIFF()).startsWith("data:image/tiff;base64,");
54+
assertThatImage(faker.image().base64TIFF()).is(TIFF, 256, 256);
4855
}
4956

5057
@ParameterizedTest
5158
@EnumSource(ImageType.class)
5259
void base64(ImageType imageType) {
5360
String base64Image = faker.image().base64(new Image.Base64ImageRuleConfig(imageType, 1000, 1000));
5461

55-
assertThat(base64Image)
56-
.startsWith("data:" + imageType.getMimeType() + ";base64,");
57-
assertThat(base64Image.substring(base64Image.indexOf(",") + 1))
58-
.isNotBlank()
59-
.isBase64();
62+
assertThatImage(base64Image).is(imageType, 1000, 1000);
6063
}
6164

6265
@Test
63-
void defaultBuilder() {
64-
String image = faker.image().base64(Image.ImageBuilder.builder()
65-
.build());
66-
assertThat(image).startsWith("data:image/");
66+
void defaultBuilder_generatesPngImage() {
67+
String image = faker.image().base64(Image.ImageBuilder.builder().build());
68+
assertThatImage(image).is(PNG, 256, 256);
6769
}
6870

6971
@Test
7072
void customBase64builder() {
7173
String gif = faker.image().base64(Image.ImageBuilder.builder()
7274
.type(ImageType.GIF)
7375
.build());
74-
assertThat(gif).startsWith("data:image/gif;base64,");
76+
assertThatImage(gif).is(GIF, 256, 256);
7577
}
7678

7779
@Test
7880
void tinyBase64builder() {
7981
String tiny = faker.image().base64(Image.ImageBuilder.builder()
8082
.height(1)
8183
.width(1)
82-
.type(ImageType.PNG)
84+
.type(PNG)
8385
.build());
8486

85-
assertThat(tiny).startsWith("data:image/png;base64,");
87+
assertThatImage(tiny).is(PNG, 1, 1);
8688
}
8789

8890
@Test
@@ -92,12 +94,12 @@ void largeBase64builder() {
9294
.width(2000)
9395
.type(ImageType.BMP)
9496
.build());
95-
assertThat(large).startsWith("data:image/bmp;base64,");
97+
assertThatImage(large).is(BMP, 2000, 1000);
9698
}
9799

98100
@Test
99101
void shouldErrorOnIllegalType() {
100-
assertThatIllegalArgumentException().isThrownBy(() -> Image.ImageBuilder.builder().type(null).build());
102+
assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> Image.ImageBuilder.builder().type(null).build());
101103
}
102104

103105
@Test

0 commit comments

Comments
 (0)