fix: allow republish after review withdrawal

This commit is contained in:
yun-zhi-ztl 2026-03-17 11:16:57 +08:00
parent b01bedf392
commit 96dcf08b0e
2 changed files with 84 additions and 2 deletions

View file

@ -227,8 +227,13 @@ public class SkillPublishService {
}
// 7. Check version doesn't already exist
if (skillVersionRepository.findBySkillIdAndVersion(skill.getId(), metadata.version()).isPresent()) {
throw new DomainBadRequestException("error.skill.version.exists", metadata.version());
java.util.Optional<SkillVersion> existingVersion = skillVersionRepository.findBySkillIdAndVersion(skill.getId(), metadata.version());
if (existingVersion.isPresent()) {
SkillVersion matchedVersion = existingVersion.get();
if (matchedVersion.getStatus() == SkillVersionStatus.PUBLISHED) {
throw new DomainBadRequestException("error.skill.version.exists", metadata.version());
}
deleteReplaceableVersionArtifacts(skill, matchedVersion);
}
// 8. Create SkillVersion
@ -333,6 +338,27 @@ public class SkillPublishService {
return new PublishResult(skill.getId(), skill.getSlug(), version);
}
private void deleteReplaceableVersionArtifacts(Skill skill, SkillVersion version) {
if (version.getStatus() == SkillVersionStatus.PUBLISHED) {
throw new DomainBadRequestException("error.skill.version.exists", version.getVersion());
}
reviewTaskRepository.findBySkillVersionIdAndStatus(version.getId(), ReviewTaskStatus.PENDING)
.ifPresent(reviewTaskRepository::delete);
List<SkillFile> files = skillFileRepository.findByVersionId(version.getId());
if (!files.isEmpty()) {
objectStorageService.deleteObjects(files.stream().map(SkillFile::getStorageKey).toList());
}
objectStorageService.deleteObject(String.format("packages/%d/%d/bundle.zip", skill.getId(), version.getId()));
skillFileRepository.deleteByVersionId(version.getId());
skillVersionRepository.delete(version);
if (version.getId().equals(skill.getLatestVersionId())) {
skill.setLatestVersionId(null);
}
}
private String resolveNamespaceSlug(Long namespaceId) {
return namespaceRepository.findById(namespaceId)
.orElseThrow(() -> new DomainBadRequestException("error.namespace.notFound", namespaceId))

View file

@ -142,6 +142,62 @@ class SkillPublishServiceTest {
verify(eventPublisher, never()).publishEvent(any());
}
@Test
void testPublishFromEntries_ShouldReplaceDraftVersionWithSameVersion() throws Exception {
String namespaceSlug = "test-ns";
String publisherId = "user-100";
String skillMdContent = "---\nname: test-skill\ndescription: Test\nversion: 1.0.0\n---\nBody";
PackageEntry skillMd = new PackageEntry("SKILL.md", skillMdContent.getBytes(), skillMdContent.length(), "text/markdown");
List<PackageEntry> entries = List.of(skillMd);
Namespace namespace = new Namespace(namespaceSlug, "Test NS", "user-1");
setId(namespace, 1L);
NamespaceMember member = mock(NamespaceMember.class);
SkillMetadata metadata = new SkillMetadata("test-skill", "Test", "1.0.0", "Body", Map.of());
Skill skill = new Skill(1L, "test-skill", publisherId, SkillVisibility.PUBLIC);
setId(skill, 1L);
SkillVersion draftVersion = new SkillVersion(1L, "1.0.0", publisherId);
draftVersion.setStatus(SkillVersionStatus.DRAFT);
setId(draftVersion, 8L);
SkillFile oldFile = new SkillFile(8L, "SKILL.md", (long) skillMdContent.length(), "text/markdown", "abc", "skills/1/8/SKILL.md");
when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace));
when(namespaceMemberRepository.findByNamespaceIdAndUserId(any(), eq(publisherId))).thenReturn(Optional.of(member));
when(skillPackageValidator.validate(entries)).thenReturn(ValidationResult.pass());
when(skillMetadataParser.parse(skillMdContent)).thenReturn(metadata);
when(prePublishValidator.validate(any())).thenReturn(ValidationResult.pass());
when(skillRepository.findByNamespaceIdAndSlug(any(), eq("test-skill"))).thenReturn(List.of(skill));
when(skillRepository.findByNamespaceIdAndSlugAndOwnerId(any(), eq("test-skill"), eq(publisherId))).thenReturn(Optional.of(skill));
when(skillVersionRepository.findBySkillIdAndStatus(1L, SkillVersionStatus.PENDING_REVIEW)).thenReturn(List.of());
when(skillVersionRepository.findBySkillIdAndVersion(1L, "1.0.0")).thenReturn(Optional.of(draftVersion));
when(skillFileRepository.findByVersionId(8L)).thenReturn(List.of(oldFile));
when(skillVersionRepository.save(any(SkillVersion.class))).thenAnswer(invocation -> {
SkillVersion saved = invocation.getArgument(0);
if (saved.getId() == null) {
setId(saved, 10L);
}
return saved;
});
when(skillRepository.save(any())).thenReturn(skill);
SkillPublishService.PublishResult result = service.publishFromEntries(
namespaceSlug,
entries,
publisherId,
SkillVisibility.PUBLIC,
Set.of()
);
assertEquals("1.0.0", result.version().getVersion());
assertEquals(SkillVersionStatus.PENDING_REVIEW, result.version().getStatus());
verify(skillFileRepository).deleteByVersionId(8L);
verify(skillVersionRepository).delete(draftVersion);
verify(objectStorageService).deleteObjects(List.of("skills/1/8/SKILL.md"));
verify(objectStorageService).deleteObject("packages/1/8/bundle.zip");
}
@Test
void testPublishFromEntries_ShouldSlugifyNameBeforeLookupAndResponse() throws Exception {
String namespaceSlug = "test-ns";