diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java index a717a49d..701bc568 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java @@ -9,8 +9,10 @@ import p.packer.PackerWorkspaceService; import p.packer.events.PackerEventKind; import p.packer.events.PackerEventSink; import p.packer.events.PackerProgress; +import p.packer.dtos.PackerDiagnosticDTO; import p.packer.dtos.PackerPackSummaryAssetDTO; import p.packer.dtos.PackerPackSummaryDTO; +import p.packer.dtos.PackerPackValidationAssetDTO; import p.packer.messages.*; import p.packer.messages.assets.*; import p.packer.messages.diagnostics.PackerDiagnosticCategory; @@ -158,7 +160,20 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe @Override public ValidatePackWorkspaceResult validatePackWorkspace(ValidatePackWorkspaceRequest request) { - throw new UnsupportedOperationException("pack workspace validation is not implemented yet"); + final PackerProjectContext project = Objects.requireNonNull(request, "request").project(); + final PackerRuntimeSnapshot snapshot = runtimeRegistry.getOrLoad(project).snapshot(); + final List blockedAssets = snapshot.assets().stream() + .filter(runtimeAsset -> runtimeAsset.registryEntry().isPresent() && runtimeAsset.registryEntry().get().includedInBuild()) + .map(this::toPackValidationAssetDTO) + .filter(PackerPackValidationAssetDTO::blocked) + .toList(); + final PackerOperationStatus status = blockedAssets.isEmpty() + ? PackerOperationStatus.SUCCESS + : PackerOperationStatus.PARTIAL; + final String summary = blockedAssets.isEmpty() + ? "Pack validation completed without blocking diagnostics." + : "Pack validation found blocking diagnostics in " + blockedAssets.size() + " included assets."; + return new ValidatePackWorkspaceResult(status, summary, blockedAssets); } @Override @@ -677,6 +692,29 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe 0L); } + private PackerPackValidationAssetDTO toPackValidationAssetDTO(PackerRuntimeAsset runtimeAsset) { + final PackerRegistryEntry registryEntry = runtimeAsset.registryEntry() + .orElseThrow(() -> new IllegalArgumentException("pack validation asset must be registered")); + final PackerAssetDeclarationParseResult parsed = runtimeAsset.parsedDeclaration(); + final PackerAssetDeclaration declaration = parsed.declaration(); + final String assetName = declaration != null + ? declaration.name() + : runtimeAsset.assetRoot().getFileName().toString(); + final List blockingDiagnostics = Stream.of( + parsed.diagnostics(), + runtimeAsset.walkDiagnostics(), + identityMismatchDiagnostics(registryEntry, parsed, runtimeAsset.manifestPath())) + .flatMap(Collection::stream) + .filter(PackerDiagnostic::blocking) + .toList(); + return new PackerPackValidationAssetDTO( + registryEntry.assetId(), + assetName, + runtimeAsset.assetRoot(), + 0L, + PackerReadMessageMapper.toDiagnosticDTOs(blockingDiagnostics)); + } + private void saveRuntimeCache(PackerProjectContext project, PackerRuntimeSnapshot snapshot) { cacheRepository.save(project, snapshot.cacheState()); } diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java index 4131e12a..46fc87f2 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/services/FileSystemPackerWorkspaceServiceTest.java @@ -140,6 +140,61 @@ final class FileSystemPackerWorkspaceServiceTest { assertEquals(0L, result.packSummary().assets().getFirst().lastModified()); } + @Test + void packValidationReturnsGreenWhenIncludedRegisteredAssetsHaveNoBlockers() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("pack-validation-green")); + final FileSystemPackerWorkspaceService service = service(); + + final var result = service.validatePackWorkspace(new ValidatePackWorkspaceRequest(project(projectRoot))); + + assertEquals(PackerOperationStatus.SUCCESS, result.status()); + assertTrue(result.canPack()); + assertTrue(result.assets().isEmpty()); + } + + @Test + void packValidationReturnsBlockingDiagnosticsPerIncludedAsset() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("pack-validation-blocked")); + final Path manifestPath = projectRoot.resolve("assets/ui/atlas/asset.json"); + final ObjectNode manifest = (ObjectNode) MAPPER.readTree(manifestPath.toFile()); + manifest.remove("name"); + MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); + final FileSystemPackerWorkspaceService service = service(); + + final var result = service.validatePackWorkspace(new ValidatePackWorkspaceRequest(project(projectRoot))); + + assertEquals(PackerOperationStatus.PARTIAL, result.status()); + assertFalse(result.canPack()); + assertEquals(1, result.assets().size()); + assertEquals(1, result.assets().getFirst().assetId()); + assertEquals("atlas", result.assets().getFirst().assetName()); + assertEquals(projectRoot.resolve("assets/ui/atlas").toAbsolutePath().normalize(), result.assets().getFirst().assetPath()); + assertEquals(0L, result.assets().getFirst().lastModified()); + assertTrue(result.assets().getFirst().diagnostics().stream().allMatch(diagnostic -> diagnostic.blocking())); + assertTrue(result.assets().getFirst().diagnostics().stream() + .anyMatch(diagnostic -> diagnostic.message().contains("Field 'name' must be a non-blank string"))); + } + + @Test + void packValidationIgnoresRegisteredAssetsExcludedFromBuild() throws Exception { + final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("pack-validation-excluded")); + final Path registryPath = projectRoot.resolve("assets/.prometeu/index.json"); + final ObjectNode registry = (ObjectNode) MAPPER.readTree(registryPath.toFile()); + ((ObjectNode) registry.path("assets").get(0)).put("included_in_build", false); + MAPPER.writerWithDefaultPrettyPrinter().writeValue(registryPath.toFile(), registry); + final Path manifestPath = projectRoot.resolve("assets/ui/atlas/asset.json"); + final ObjectNode manifest = (ObjectNode) MAPPER.readTree(manifestPath.toFile()); + manifest.remove("name"); + MAPPER.writerWithDefaultPrettyPrinter().writeValue(manifestPath.toFile(), manifest); + final FileSystemPackerWorkspaceService service = service(); + + final var result = service.validatePackWorkspace(new ValidatePackWorkspaceRequest(project(projectRoot))); + + assertEquals(PackerOperationStatus.SUCCESS, result.status()); + assertTrue(result.canPack()); + assertTrue(result.assets().isEmpty()); + } + @Test void createsRegisteredAssetAndWritesManifest() throws Exception { final Path projectRoot = tempDir.resolve("created");