package p.packer.services; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import p.packer.messages.assets.AssetFamilyCatalog; import p.packer.messages.assets.OutputCodecCatalog; import p.packer.messages.diagnostics.PackerDiagnosticCategory; import p.packer.testing.PackerFixtureLocator; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; final class PackerAssetDeclarationParserTest { private final PackerAssetDeclarationParser parser = new PackerAssetDeclarationParser(new ObjectMapper()); @TempDir Path tempDir; @Test void parsesValidDeclarationFixture() { final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/managed-basic/assets/ui/atlas/asset.json")); assertTrue(result.valid()); assertNotNull(result.declaration()); assertEquals(1, result.declaration().schemaVersion()); assertEquals("fixture-uuid-1", result.declaration().assetUuid()); assertEquals("ui_atlas", result.declaration().name()); assertEquals(AssetFamilyCatalog.GLYPH_BANK, result.declaration().assetFamily()); assertEquals("GLYPH/indexed_v1", result.declaration().outputFormat().displayName()); assertEquals(OutputCodecCatalog.NONE, result.declaration().outputCodec()); assertEquals(Map.of(), result.declaration().outputPipelineMetadata()); assertTrue(result.declaration().preloadEnabled()); } @Test void parsesPipelineMetadataAsDedicatedOutputObject() throws Exception { final Path manifest = tempDir.resolve("asset.json"); Files.writeString(manifest, """ { "schema_version": 1, "asset_uuid": "uuid-pipeline", "name": "pipeline_asset", "type": "sound_bank", "output": { "format": "SOUND/v1", "codec": "NONE", "pipeline": { "samples": { "1": { "offset": 0, "length": 128 } }, "normalized": true } }, "preload": { "enabled": true } } """); final var result = parser.parse(manifest); assertTrue(result.valid()); assertEquals(2, result.declaration().outputPipelineMetadata().size()); assertTrue(result.declaration().outputPipelineMetadata().get("normalized").asBoolean()); assertEquals(128, result.declaration().outputPipelineMetadata().get("samples").path("1").path("length").asInt()); } @Test void parsesSceneBankDeclaration() throws Exception { final Path manifest = tempDir.resolve("scene-asset.json"); Files.writeString(manifest, """ { "schema_version": 1, "asset_uuid": "uuid-scene", "name": "overworld_scene", "type": "scene_bank", "output": { "format": "SCENE/tilemap_v1", "codec": "NONE" }, "preload": { "enabled": false } } """); final var result = parser.parse(manifest); assertTrue(result.valid()); assertEquals(AssetFamilyCatalog.SCENE_BANK, result.declaration().assetFamily()); assertEquals("SCENE/tilemap_v1", result.declaration().outputFormat().displayName()); assertEquals(OutputCodecCatalog.NONE, result.declaration().outputCodec()); } @Test void parsesLegacySceneBankDeclarationFormatAsTilemapContract() throws Exception { final Path manifest = tempDir.resolve("legacy-scene-asset.json"); Files.writeString(manifest, """ { "schema_version": 1, "asset_uuid": "uuid-scene-legacy", "name": "overworld_scene", "type": "scene_bank", "output": { "format": "SCENE/tiled_v1", "codec": "NONE" }, "preload": { "enabled": false } } """); final var result = parser.parse(manifest); assertTrue(result.valid()); assertEquals(AssetFamilyCatalog.SCENE_BANK, result.declaration().assetFamily()); assertEquals("SCENE/tilemap_v1", result.declaration().outputFormat().displayName()); assertEquals(OutputCodecCatalog.NONE, result.declaration().outputCodec()); } @Test void rejectsNonObjectPipelineMetadata() throws Exception { final Path manifest = tempDir.resolve("asset.json"); Files.writeString(manifest, """ { "schema_version": 1, "asset_uuid": "uuid-pipeline", "name": "pipeline_asset", "type": "glyph_bank", "output": { "format": "GLYPH/indexed_v1", "codec": "NONE", "pipeline": [] }, "preload": { "enabled": true } } """); final var result = parser.parse(manifest); assertFalse(result.valid()); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("output.pipeline"))); } @Test void parsesPaletteDeclarationsUsingExplicitIndexContract() throws Exception { final Path manifest = tempDir.resolve("asset.json"); Files.writeString(manifest, """ { "schema_version": 1, "asset_uuid": "uuid-palette-contract", "name": "palette_asset", "type": "glyph_bank", "output": { "format": "GLYPH/indexed_v1", "codec": "NONE", "pipeline": { "palettes": [ { "index": 1, "palette": { "originalArgb8888": [4294901760], "convertedRgb565": [63488] } } ] } }, "preload": { "enabled": true } } """); final var result = parser.parse(manifest); assertTrue(result.valid()); assertTrue(result.declaration().outputPipelineMetadata().get("palettes").isArray()); assertEquals(1, result.declaration().outputPipelineMetadata().get("palettes").get(0).path("index").asInt()); assertEquals(63488, result.declaration().outputPipelineMetadata().get("palettes").get(0).path("palette").path("convertedRgb565").get(0).asInt()); } @Test void rejectsPaletteDeclarationsWithDuplicateIndices() throws Exception { final Path manifest = tempDir.resolve("asset.json"); Files.writeString(manifest, """ { "schema_version": 1, "asset_uuid": "uuid-palette-duplicate", "name": "palette_asset", "type": "glyph_bank", "output": { "format": "GLYPH/indexed_v1", "codec": "NONE", "pipeline": { "palettes": [ { "index": 0, "palette": { "originalArgb8888": [4294901760], "convertedRgb565": [63488] } }, { "index": 0, "palette": { "originalArgb8888": [4278255360], "convertedRgb565": [2016] } } ] } }, "preload": { "enabled": true } } """); final var result = parser.parse(manifest); assertFalse(result.valid()); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("duplicate palette index 0"))); } @Test void rejectsMalformedJsonWithStructuralDiagnostic() { final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/invalid-malformed/assets/bad/asset.json")); assertFalse(result.valid()); assertNull(result.declaration()); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.category() == PackerDiagnosticCategory.STRUCTURAL)); } @Test void rejectsMissingRequiredFields() { final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/invalid-missing-fields/assets/bad/asset.json")); assertFalse(result.valid()); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("name"))); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("asset_uuid"))); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("format"))); } @Test void rejectsUnsupportedSchemaVersion() { final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/invalid-version/assets/bad/asset.json")); assertFalse(result.valid()); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.category() == PackerDiagnosticCategory.VERSIONING)); } @Test void rejectsLegacyInputsField() throws Exception { final Path manifest = tempDir.resolve("asset.json"); Files.writeString(manifest, """ { "schema_version": 1, "asset_uuid": "uuid-outside", "name": "bad_asset", "type": "glyph_bank", "inputs": { "sprites": ["../outside.png"] }, "output": { "format": "GLYPH/indexed_v1", "codec": "NONE" }, "preload": { "enabled": true } } """); final var result = parser.parse(manifest); assertFalse(result.valid()); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("no longer supported"))); } @Test void rejectsUnknownAssetFamily() throws Exception { final Path manifest = tempDir.resolve("asset.json"); Files.writeString(manifest, """ { "schema_version": 1, "asset_uuid": "uuid-video", "name": "bad_asset", "type": "video_bank", "output": { "format": "GLYPH/indexed_v1", "codec": "NONE" }, "preload": { "enabled": true } } """); final var result = parser.parse(manifest); assertFalse(result.valid()); assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("Field 'type' must be one of"))); } }