mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-09 03:17:52 +00:00
fix(review): handle promotion target skill conflicts
This commit is contained in:
parent
e35e99ae59
commit
082d97a69d
4 changed files with 51 additions and 1 deletions
|
|
@ -167,3 +167,4 @@ validation.auth.password.reset.email.invalid=Email format is invalid
|
|||
validation.auth.password.reset.code.notBlank=Verification code cannot be blank
|
||||
validation.auth.password.reset.code.invalid=Verification code must be 6 digits
|
||||
validation.auth.password.reset.newPassword.notBlank=New password cannot be blank
|
||||
promotion.target_skill_conflict=The target global skill "{0}" already exists
|
||||
|
|
|
|||
|
|
@ -167,3 +167,4 @@ validation.auth.password.reset.email.invalid=邮箱格式不正确
|
|||
validation.auth.password.reset.code.notBlank=验证码不能为空
|
||||
validation.auth.password.reset.code.invalid=验证码必须为 6 位数字
|
||||
validation.auth.password.reset.newPassword.notBlank=新密码不能为空
|
||||
promotion.target_skill_conflict=目标全局技能“{0}”已存在
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.iflytek.skillhub.domain.shared.exception.DomainNotFoundException;
|
|||
import com.iflytek.skillhub.domain.skill.*;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -207,6 +208,8 @@ public class PromotionService {
|
|||
SkillVersion sourceVersion = skillVersionRepository.findById(approvedRequest.getSourceVersionId())
|
||||
.orElseThrow(() -> new DomainNotFoundException("skill_version.not_found", approvedRequest.getSourceVersionId()));
|
||||
|
||||
assertTargetSkillNotExists(approvedRequest, sourceSkill);
|
||||
|
||||
// Create new skill in global namespace
|
||||
Skill newSkill = new Skill(approvedRequest.getTargetNamespaceId(), sourceSkill.getSlug(),
|
||||
sourceSkill.getOwnerId(), SkillVisibility.PUBLIC);
|
||||
|
|
@ -215,7 +218,11 @@ public class PromotionService {
|
|||
newSkill.setSourceSkillId(sourceSkill.getId());
|
||||
newSkill.setCreatedBy(reviewerId);
|
||||
newSkill.setUpdatedBy(reviewerId);
|
||||
newSkill = skillRepository.save(newSkill);
|
||||
try {
|
||||
newSkill = skillRepository.save(newSkill);
|
||||
} catch (DataIntegrityViolationException ex) {
|
||||
throw duplicateTargetSkillConflict(sourceSkill.getSlug(), ex);
|
||||
}
|
||||
|
||||
// Create new version copying metadata from source
|
||||
SkillVersion newVersion = new SkillVersion(newSkill.getId(), sourceVersion.getVersion(),
|
||||
|
|
@ -264,6 +271,24 @@ public class PromotionService {
|
|||
return savedRequest;
|
||||
}
|
||||
|
||||
private void assertTargetSkillNotExists(PromotionRequest approvedRequest, Skill sourceSkill) {
|
||||
skillRepository.findByNamespaceIdAndSlugAndOwnerId(
|
||||
approvedRequest.getTargetNamespaceId(),
|
||||
sourceSkill.getSlug(),
|
||||
sourceSkill.getOwnerId()
|
||||
).ifPresent(existing -> {
|
||||
throw duplicateTargetSkillConflict(sourceSkill.getSlug(), null);
|
||||
});
|
||||
}
|
||||
|
||||
private DomainBadRequestException duplicateTargetSkillConflict(String slug, Exception cause) {
|
||||
DomainBadRequestException ex = new DomainBadRequestException("promotion.target_skill_conflict", slug);
|
||||
if (cause != null) {
|
||||
ex.initCause(cause);
|
||||
}
|
||||
return ex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects a pending promotion request without changing the source skill.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import org.mockito.ArgumentCaptor;
|
|||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
|
|
@ -511,6 +512,28 @@ class PromotionServiceTest {
|
|||
() -> promotionService.approvePromotion(PROMOTION_ID, REVIEWER_ID, "ok", Set.of("SKILL_ADMIN")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTranslateDuplicateTargetSkillIntoBadRequest() {
|
||||
PromotionRequest pr = createPendingPromotion();
|
||||
Skill sourceSkill = createSourceSkill();
|
||||
SkillVersion sourceVersion = createPublishedVersion();
|
||||
|
||||
when(promotionRequestRepository.findById(PROMOTION_ID)).thenReturn(Optional.of(pr));
|
||||
when(permissionChecker.canReviewPromotion(pr, REVIEWER_ID, Set.of("SKILL_ADMIN"))).thenReturn(true);
|
||||
when(promotionRequestRepository.updateStatusWithVersion(
|
||||
PROMOTION_ID, ReviewTaskStatus.APPROVED, REVIEWER_ID, "ok", null, pr.getVersion()))
|
||||
.thenReturn(1);
|
||||
when(skillRepository.findById(SOURCE_SKILL_ID)).thenReturn(Optional.of(sourceSkill));
|
||||
when(skillVersionRepository.findById(SOURCE_VERSION_ID)).thenReturn(Optional.of(sourceVersion));
|
||||
when(skillRepository.save(any(Skill.class)))
|
||||
.thenThrow(new DataIntegrityViolationException("duplicate key value violates unique constraint"));
|
||||
|
||||
DomainBadRequestException ex = assertThrows(DomainBadRequestException.class,
|
||||
() -> promotionService.approvePromotion(PROMOTION_ID, REVIEWER_ID, "ok", Set.of("SKILL_ADMIN")));
|
||||
|
||||
assertEquals("promotion.target_skill_conflict", ex.messageCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCopyDisplayNameAndSummaryToNewSkill() {
|
||||
PromotionRequest pr = createPendingPromotion();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue