Skip to content

Commit 7fedf56

Browse files
committed
✅ Add tests for metadata import and export
1 parent 0b4bdfa commit 7fedf56

File tree

3 files changed

+86
-7
lines changed

3 files changed

+86
-7
lines changed

backend/src/main/kotlin/xyz/poeschl/roborush/service/MapImportExportService.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ class MapImportExportService {
3737
private val LOGGER = LoggerFactory.getLogger(MapImportExportService::class.java)
3838

3939
private const val XMP_URI = "https://github.com/Poeschl/RoboRush"
40-
private const val XMP_MAP_SOLAR_CHARGE_RATE_KEY = "SolarChargeRate"
41-
private const val XMP_MAP_MAX_ROBOT_FUEL_KEY = "MaxRobotFuel"
40+
private const val XMP_PREFIX = "rr:"
41+
private const val XMP_MAP_SOLAR_CHARGE_RATE_KEY = "${XMP_PREFIX}solarChargeRate"
42+
private const val XMP_MAP_MAX_ROBOT_FUEL_KEY = "${XMP_PREFIX}maxRobotFuel"
4243
}
4344

4445
fun exportMap(map: Map): ByteArray {
@@ -205,6 +206,7 @@ class MapImportExportService {
205206

206207
return ByteArrayOutputStream().use {
207208
val pngParams = PngImagingParameters()
209+
pngParams.isForceTrueColor = true
208210
pngParams.xmpXml = xmpString
209211
PngWriter().writeImage(imageInput, it, pngParams, null)
210212
return@use it.toByteArray()
@@ -214,16 +216,20 @@ class MapImportExportService {
214216
} catch (ex: SAXException) {
215217
LOGGER.warn("Couldn't write metadata!", ex)
216218
}
219+
220+
// In case of error write image without metadata
217221
return ByteArrayOutputStream().use {
218-
PngWriter().writeImage(imageInput, it, null, null)
222+
val pngParams = PngImagingParameters()
223+
pngParams.isForceTrueColor = true
224+
PngWriter().writeImage(imageInput, it, pngParams, null)
219225
return@use it.toByteArray()
220226
}
221227
}
222228

223229
private fun getMapMetadata(imageInput: InputStream): MapMetadata? {
224230
val xmpString = Imaging.getXmpXml(imageInput.readAllBytes())
225231
if (xmpString != null && xmpString.isNotEmpty() && xmpString.contains(XMP_URI)) {
226-
val xmpMetadata = XMPParser.parseXMP(StreamSource(xmpString.byteInputStream()))
232+
val xmpMetadata = XMPParser.parseXMP(StreamSource(xmpString.byteInputStream().buffered()))
227233

228234
val solarChargeProp = Optional.ofNullable(xmpMetadata.getProperty(XMP_URI, XMP_MAP_SOLAR_CHARGE_RATE_KEY)).getOrNull()
229235
val maxRobotFuelProp = Optional.ofNullable(xmpMetadata.getProperty(XMP_URI, XMP_MAP_MAX_ROBOT_FUEL_KEY)).getOrNull()

backend/src/test/kotlin/xyz/poeschl/roborush/service/MapImportExportServiceTest.kt

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package xyz.poeschl.roborush.service
22

3+
import org.apache.commons.imaging.Imaging
4+
import org.apache.commons.imaging.formats.png.PngImagingParameters
5+
import org.apache.commons.imaging.formats.png.PngWriter
6+
import org.apache.xmlgraphics.xmp.XMPParser
37
import org.assertj.core.api.Assertions.assertThat
48
import org.assertj.core.api.Assertions.assertThatThrownBy
59
import org.junit.jupiter.api.Test
610
import org.junit.jupiter.params.ParameterizedTest
711
import org.junit.jupiter.params.provider.Arguments
812
import org.junit.jupiter.params.provider.MethodSource
13+
import org.springframework.core.io.ByteArrayResource
914
import org.springframework.core.io.ClassPathResource
1015
import xyz.poeschl.roborush.exceptions.NoStartingPosition
1116
import xyz.poeschl.roborush.exceptions.NoTargetPosition
@@ -19,10 +24,14 @@ import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Map`
1924
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Position`
2025
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Size`
2126
import xyz.poeschl.roborush.test.utils.builder.GameLogicBuilder.Companion.`$Tile`
27+
import xyz.poeschl.roborush.test.utils.builder.NativeTypes.Companion.`$Double`
28+
import xyz.poeschl.roborush.test.utils.builder.NativeTypes.Companion.`$Int`
2229
import xyz.poeschl.roborush.test.utils.builder.NativeTypes.Companion.`$String`
2330
import java.io.ByteArrayInputStream
31+
import java.io.ByteArrayOutputStream
2432
import java.util.stream.Stream
2533
import javax.imageio.ImageIO
34+
import javax.xml.transform.stream.StreamSource
2635

2736
class MapImportExportServiceTest {
2837

@@ -34,6 +43,17 @@ class MapImportExportServiceTest {
3443
Arguments.of(TileType.FUEL_TILE, 1, Color(1, 1, 255)),
3544
Arguments.of(TileType.DEFAULT_TILE, 100, Color(100, 100, 100))
3645
)
46+
47+
private val XMP_META_TEMPLATE = """
48+
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/">
49+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
50+
<rdf:Description xmlns:rr="https://github.com/Poeschl/RoboRush" rdf:about="">
51+
<rr:solarChargeRate>%s</rr:solarChargeRate>
52+
<rr:maxRobotFuel>%s</rr:maxRobotFuel>
53+
</rdf:Description>
54+
</rdf:RDF>
55+
</x:xmpmeta>
56+
""".trimIndent()
3757
}
3858

3959
private val importExportService = MapImportExportService()
@@ -75,6 +95,30 @@ class MapImportExportServiceTest {
7595
assertThat(Color.fromColorInt(resultImage.getRGB(0, 0))).isEqualTo(expectedColor)
7696
}
7797

98+
@Test
99+
fun exportMap_metadata() {
100+
// WHEN
101+
val expectedMaxRobotFuel = a(`$Int`(10, 500))
102+
val expectedSolarChargeRate = a(`$Double`(.1, 1.0))
103+
val map = a(
104+
`$Map`()
105+
.withMaxRobotFuel(expectedMaxRobotFuel)
106+
.withSolarChargeRate(expectedSolarChargeRate)
107+
)
108+
map.addTile(a(`$Tile`()))
109+
110+
// THEN
111+
val byteResult = importExportService.exportMap(map)
112+
113+
// VERIFY
114+
val xmpMetadataString = Imaging.getXmpXml(byteResult)
115+
val xmpMetadata = XMPParser.parseXMP(StreamSource(xmpMetadataString.byteInputStream().buffered()))
116+
assertThat(xmpMetadata.getProperty("https://github.com/Poeschl/RoboRush", "rr:solarChargeRate").value)
117+
.isEqualTo(expectedSolarChargeRate.toString())
118+
assertThat(xmpMetadata.getProperty("https://github.com/Poeschl/RoboRush", "rr:maxRobotFuel").value)
119+
.isEqualTo(expectedMaxRobotFuel.toString())
120+
}
121+
78122
@Test
79123
fun importMap_correct() {
80124
// WHEN
@@ -196,4 +240,33 @@ class MapImportExportServiceTest {
196240
)
197241
)
198242
}
243+
244+
@Test
245+
fun importMap_readMetadata() {
246+
// WHEN
247+
val inputImage = ClassPathResource("/maps/with-fuel.png")
248+
val expectedMaxRobotFuel = a(`$Int`(10, 500))
249+
val expectedSolarChargeRate = a(`$Double`(.1, 1.0))
250+
val xmpString = XMP_META_TEMPLATE.format(expectedSolarChargeRate, expectedMaxRobotFuel)
251+
val name = a(`$String`("name"))
252+
253+
// Put metadata in image, since the png optimizer removes it from the file
254+
val imageWithMetadata = ByteArrayResource(
255+
ByteArrayOutputStream().use {
256+
val pngParams = PngImagingParameters()
257+
pngParams.isForceTrueColor = true
258+
pngParams.xmpXml = xmpString
259+
PngWriter().writeImage(ImageIO.read(inputImage.inputStream.buffered()), it, pngParams, null)
260+
return@use it.toByteArray()
261+
}
262+
)
263+
264+
// THEN
265+
val genResult = importExportService.importMap(name, imageWithMetadata)
266+
267+
// VERIFY
268+
assertThat(genResult.errors).isEmpty()
269+
assertThat(genResult.map.solarChargeRate).isEqualTo(expectedSolarChargeRate)
270+
assertThat(genResult.map.maxRobotFuel).isEqualTo(expectedMaxRobotFuel)
271+
}
199272
}

backend/src/test/kotlin/xyz/poeschl/roborush/test/utils/builder/GameLogicBuilder.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ class GameLogicBuilder {
5555
fun `$Tile`() = TileBuilder()
5656
.withId(null)
5757
.withPosition(a(`$Position`()))
58-
.withHeight(a(`$Int`()))
58+
.withHeight(a(`$Int`(1, 254)))
5959
.withType(TileType.DEFAULT_TILE)
6060

6161
fun `$Size`() = SizeBuilder()
62-
.withHeight(a(`$Int`()))
63-
.withWidth(a(`$Int`()))
62+
.withHeight(a(`$Int`(1, 512)))
63+
.withWidth(a(`$Int`(1, 512)))
6464

6565
fun `$Position`() = PositionBuilder()
6666
.withX(a(`$Int`(0, Int.MAX_VALUE)))

0 commit comments

Comments
 (0)