mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-11 22:51:04 +00:00
fix: stabilize skill promotion approval flow
This commit is contained in:
parent
0b85bf1e89
commit
df5883ec04
8 changed files with 198 additions and 23 deletions
|
|
@ -3,6 +3,8 @@ package com.iflytek.skillhub.service;
|
|||
import com.iflytek.skillhub.domain.namespace.NamespaceRepository;
|
||||
import com.iflytek.skillhub.domain.namespace.NamespaceStatus;
|
||||
import com.iflytek.skillhub.domain.namespace.NamespaceType;
|
||||
import com.iflytek.skillhub.domain.review.PromotionRequestRepository;
|
||||
import com.iflytek.skillhub.domain.review.ReviewTaskStatus;
|
||||
import com.iflytek.skillhub.domain.skill.Skill;
|
||||
import com.iflytek.skillhub.domain.skill.SkillRepository;
|
||||
import com.iflytek.skillhub.domain.skill.SkillVersion;
|
||||
|
|
@ -29,16 +31,19 @@ public class MySkillAppService {
|
|||
private final NamespaceRepository namespaceRepository;
|
||||
private final SkillVersionRepository skillVersionRepository;
|
||||
private final SkillStarRepository skillStarRepository;
|
||||
private final PromotionRequestRepository promotionRequestRepository;
|
||||
|
||||
public MySkillAppService(
|
||||
SkillRepository skillRepository,
|
||||
NamespaceRepository namespaceRepository,
|
||||
SkillVersionRepository skillVersionRepository,
|
||||
SkillStarRepository skillStarRepository) {
|
||||
SkillStarRepository skillStarRepository,
|
||||
PromotionRequestRepository promotionRequestRepository) {
|
||||
this.skillRepository = skillRepository;
|
||||
this.namespaceRepository = namespaceRepository;
|
||||
this.skillVersionRepository = skillVersionRepository;
|
||||
this.skillStarRepository = skillStarRepository;
|
||||
this.promotionRequestRepository = promotionRequestRepository;
|
||||
}
|
||||
|
||||
public List<SkillSummaryResponse> listMySkills(String userId) {
|
||||
|
|
@ -150,6 +155,12 @@ public class MySkillAppService {
|
|||
if (namespace.getStatus() != NamespaceStatus.ACTIVE || skill.getStatus() != com.iflytek.skillhub.domain.skill.SkillStatus.ACTIVE) {
|
||||
return false;
|
||||
}
|
||||
if (promotionRequestRepository.findBySourceSkillIdAndStatus(skill.getId(), ReviewTaskStatus.PENDING).isPresent()) {
|
||||
return false;
|
||||
}
|
||||
if (promotionRequestRepository.findBySourceSkillIdAndStatus(skill.getId(), ReviewTaskStatus.APPROVED).isPresent()) {
|
||||
return false;
|
||||
}
|
||||
return latestVersion != null && latestVersion.getStatus() == SkillVersionStatus.PUBLISHED;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package com.iflytek.skillhub.service;
|
|||
|
||||
import com.iflytek.skillhub.domain.namespace.Namespace;
|
||||
import com.iflytek.skillhub.domain.namespace.NamespaceRepository;
|
||||
import com.iflytek.skillhub.domain.review.PromotionRequest;
|
||||
import com.iflytek.skillhub.domain.review.PromotionRequestRepository;
|
||||
import com.iflytek.skillhub.domain.review.ReviewTaskStatus;
|
||||
import com.iflytek.skillhub.domain.skill.Skill;
|
||||
import com.iflytek.skillhub.domain.skill.SkillRepository;
|
||||
import com.iflytek.skillhub.domain.skill.SkillVersion;
|
||||
|
|
@ -43,11 +46,20 @@ class MySkillAppServiceTest {
|
|||
@Mock
|
||||
private SkillStarRepository skillStarRepository;
|
||||
|
||||
@Mock
|
||||
private PromotionRequestRepository promotionRequestRepository;
|
||||
|
||||
private MySkillAppService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new MySkillAppService(skillRepository, namespaceRepository, skillVersionRepository, skillStarRepository);
|
||||
service = new MySkillAppService(
|
||||
skillRepository,
|
||||
namespaceRepository,
|
||||
skillVersionRepository,
|
||||
skillStarRepository,
|
||||
promotionRequestRepository
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -134,6 +146,8 @@ class MySkillAppServiceTest {
|
|||
given(skillRepository.findByOwnerId("user-1")).willReturn(List.of(skill));
|
||||
given(skillVersionRepository.findBySkillIdIn(List.of(2L))).willReturn(List.of(publishedVersion));
|
||||
given(namespaceRepository.findByIdIn(List.of(101L))).willReturn(List.of(namespace));
|
||||
given(promotionRequestRepository.findBySourceSkillIdAndStatus(2L, ReviewTaskStatus.PENDING)).willReturn(Optional.empty());
|
||||
given(promotionRequestRepository.findBySourceSkillIdAndStatus(2L, ReviewTaskStatus.APPROVED)).willReturn(Optional.empty());
|
||||
|
||||
var skills = service.listMySkills("user-1");
|
||||
|
||||
|
|
@ -142,4 +156,30 @@ class MySkillAppServiceTest {
|
|||
assertThat(skills.get(0).latestVersionStatus()).isEqualTo("PUBLISHED");
|
||||
assertThat(skills.get(0).canSubmitPromotion()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listMySkills_hidesPromotionWhenPendingRequestExists() {
|
||||
Skill skill = new Skill(101L, "team-skill", "user-1", SkillVisibility.PUBLIC);
|
||||
skill.setDisplayName("Team Skill");
|
||||
ReflectionTestUtils.setField(skill, "id", 2L);
|
||||
|
||||
SkillVersion publishedVersion = new SkillVersion(2L, "1.2.0", "user-1");
|
||||
publishedVersion.setStatus(SkillVersionStatus.PUBLISHED);
|
||||
ReflectionTestUtils.setField(publishedVersion, "id", 22L);
|
||||
ReflectionTestUtils.setField(publishedVersion, "createdAt", LocalDateTime.of(2026, 3, 15, 10, 30));
|
||||
|
||||
Namespace namespace = new Namespace("team-ai", "Team AI", "user-1");
|
||||
ReflectionTestUtils.setField(namespace, "id", 101L);
|
||||
|
||||
given(skillRepository.findByOwnerId("user-1")).willReturn(List.of(skill));
|
||||
given(skillVersionRepository.findBySkillIdIn(List.of(2L))).willReturn(List.of(publishedVersion));
|
||||
given(namespaceRepository.findByIdIn(List.of(101L))).willReturn(List.of(namespace));
|
||||
given(promotionRequestRepository.findBySourceSkillIdAndStatus(2L, ReviewTaskStatus.PENDING))
|
||||
.willReturn(Optional.of(new PromotionRequest(2L, 22L, 999L, "user-1")));
|
||||
|
||||
var skills = service.listMySkills("user-1");
|
||||
|
||||
assertThat(skills).hasSize(1);
|
||||
assertThat(skills.get(0).canSubmitPromotion()).isFalse();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ public interface PromotionRequestRepository {
|
|||
PromotionRequest save(PromotionRequest request);
|
||||
Optional<PromotionRequest> findById(Long id);
|
||||
Optional<PromotionRequest> findBySourceVersionIdAndStatus(Long sourceVersionId, ReviewTaskStatus status);
|
||||
Optional<PromotionRequest> findBySourceSkillIdAndStatus(Long sourceSkillId, ReviewTaskStatus status);
|
||||
Page<PromotionRequest> findByStatus(ReviewTaskStatus status, Pageable pageable);
|
||||
int updateStatusWithVersion(Long id, ReviewTaskStatus status, String reviewedBy,
|
||||
String reviewComment, Long targetSkillId, Integer expectedVersion);
|
||||
|
|
|
|||
|
|
@ -85,10 +85,14 @@ public class PromotionService {
|
|||
throw new DomainBadRequestException("promotion.target_not_global", targetNamespaceId);
|
||||
}
|
||||
|
||||
promotionRequestRepository.findBySourceVersionIdAndStatus(sourceVersionId, ReviewTaskStatus.PENDING)
|
||||
promotionRequestRepository.findBySourceSkillIdAndStatus(sourceSkillId, ReviewTaskStatus.PENDING)
|
||||
.ifPresent(existing -> {
|
||||
throw new DomainBadRequestException("promotion.duplicate_pending", sourceVersionId);
|
||||
});
|
||||
promotionRequestRepository.findBySourceSkillIdAndStatus(sourceSkillId, ReviewTaskStatus.APPROVED)
|
||||
.ifPresent(existing -> {
|
||||
throw new DomainBadRequestException("promotion.already_promoted", sourceSkillId);
|
||||
});
|
||||
|
||||
PromotionRequest request = new PromotionRequest(sourceSkillId, sourceVersionId, targetNamespaceId, userId);
|
||||
return promotionRequestRepository.save(request);
|
||||
|
|
@ -127,10 +131,14 @@ public class PromotionService {
|
|||
throw new DomainBadRequestException("promotion.target_not_global", targetNamespaceId);
|
||||
}
|
||||
|
||||
promotionRequestRepository.findBySourceVersionIdAndStatus(sourceVersionId, ReviewTaskStatus.PENDING)
|
||||
promotionRequestRepository.findBySourceSkillIdAndStatus(sourceSkillId, ReviewTaskStatus.PENDING)
|
||||
.ifPresent(existing -> {
|
||||
throw new DomainBadRequestException("promotion.duplicate_pending", sourceVersionId);
|
||||
});
|
||||
promotionRequestRepository.findBySourceSkillIdAndStatus(sourceSkillId, ReviewTaskStatus.APPROVED)
|
||||
.ifPresent(existing -> {
|
||||
throw new DomainBadRequestException("promotion.already_promoted", sourceSkillId);
|
||||
});
|
||||
|
||||
PromotionRequest request = new PromotionRequest(sourceSkillId, sourceVersionId, targetNamespaceId, userId);
|
||||
return promotionRequestRepository.save(request);
|
||||
|
|
@ -156,14 +164,17 @@ public class PromotionService {
|
|||
throw new ConcurrentModificationException("Promotion request was modified concurrently");
|
||||
}
|
||||
|
||||
Skill sourceSkill = skillRepository.findById(request.getSourceSkillId())
|
||||
.orElseThrow(() -> new DomainNotFoundException("skill.not_found", request.getSourceSkillId()));
|
||||
PromotionRequest approvedRequest = promotionRequestRepository.findById(promotionId)
|
||||
.orElseThrow(() -> new DomainNotFoundException("promotion.not_found", promotionId));
|
||||
|
||||
SkillVersion sourceVersion = skillVersionRepository.findById(request.getSourceVersionId())
|
||||
.orElseThrow(() -> new DomainNotFoundException("skill_version.not_found", request.getSourceVersionId()));
|
||||
Skill sourceSkill = skillRepository.findById(approvedRequest.getSourceSkillId())
|
||||
.orElseThrow(() -> new DomainNotFoundException("skill.not_found", approvedRequest.getSourceSkillId()));
|
||||
|
||||
SkillVersion sourceVersion = skillVersionRepository.findById(approvedRequest.getSourceVersionId())
|
||||
.orElseThrow(() -> new DomainNotFoundException("skill_version.not_found", approvedRequest.getSourceVersionId()));
|
||||
|
||||
// Create new skill in global namespace
|
||||
Skill newSkill = new Skill(request.getTargetNamespaceId(), sourceSkill.getSlug(),
|
||||
Skill newSkill = new Skill(approvedRequest.getTargetNamespaceId(), sourceSkill.getSlug(),
|
||||
sourceSkill.getOwnerId(), SkillVisibility.PUBLIC);
|
||||
newSkill.setDisplayName(sourceSkill.getDisplayName());
|
||||
newSkill.setSummary(sourceSkill.getSummary());
|
||||
|
|
@ -189,7 +200,7 @@ public class PromotionService {
|
|||
skillRepository.save(newSkill);
|
||||
|
||||
// Copy file records (reuse storageKey)
|
||||
List<SkillFile> sourceFiles = skillFileRepository.findByVersionId(request.getSourceVersionId());
|
||||
List<SkillFile> sourceFiles = skillFileRepository.findByVersionId(approvedRequest.getSourceVersionId());
|
||||
Long newVersionId = newVersion.getId();
|
||||
List<SkillFile> copiedFiles = sourceFiles.stream()
|
||||
.map(f -> new SkillFile(newVersionId, f.getFilePath(), f.getFileSize(),
|
||||
|
|
@ -198,13 +209,13 @@ public class PromotionService {
|
|||
skillFileRepository.saveAll(copiedFiles);
|
||||
|
||||
// Update promotion request with target skill id
|
||||
request.setTargetSkillId(newSkill.getId());
|
||||
promotionRequestRepository.save(request);
|
||||
approvedRequest.setTargetSkillId(newSkill.getId());
|
||||
PromotionRequest savedRequest = promotionRequestRepository.save(approvedRequest);
|
||||
|
||||
eventPublisher.publishEvent(new SkillPublishedEvent(
|
||||
newSkill.getId(), newVersion.getId(), reviewerId));
|
||||
governanceNotificationService.notifyUser(
|
||||
request.getSubmittedBy(),
|
||||
approvedRequest.getSubmittedBy(),
|
||||
"PROMOTION",
|
||||
"PROMOTION_REQUEST",
|
||||
promotionId,
|
||||
|
|
@ -212,7 +223,7 @@ public class PromotionService {
|
|||
"{\"status\":\"APPROVED\"}"
|
||||
);
|
||||
|
||||
return request;
|
||||
return savedRequest;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.iflytek.skillhub.domain.namespace.NamespaceRepository;
|
|||
import com.iflytek.skillhub.domain.namespace.NamespaceRole;
|
||||
import com.iflytek.skillhub.domain.namespace.NamespaceStatus;
|
||||
import com.iflytek.skillhub.domain.namespace.NamespaceType;
|
||||
import com.iflytek.skillhub.domain.review.PromotionRequestRepository;
|
||||
import com.iflytek.skillhub.domain.review.ReviewTaskStatus;
|
||||
import com.iflytek.skillhub.domain.shared.exception.DomainBadRequestException;
|
||||
import com.iflytek.skillhub.domain.shared.exception.DomainForbiddenException;
|
||||
import com.iflytek.skillhub.domain.skill.*;
|
||||
|
|
@ -37,6 +39,7 @@ public class SkillQueryService {
|
|||
private final SkillTagRepository skillTagRepository;
|
||||
private final ObjectStorageService objectStorageService;
|
||||
private final VisibilityChecker visibilityChecker;
|
||||
private final PromotionRequestRepository promotionRequestRepository;
|
||||
|
||||
public SkillQueryService(
|
||||
NamespaceRepository namespaceRepository,
|
||||
|
|
@ -45,7 +48,8 @@ public class SkillQueryService {
|
|||
SkillFileRepository skillFileRepository,
|
||||
SkillTagRepository skillTagRepository,
|
||||
ObjectStorageService objectStorageService,
|
||||
VisibilityChecker visibilityChecker) {
|
||||
VisibilityChecker visibilityChecker,
|
||||
PromotionRequestRepository promotionRequestRepository) {
|
||||
this.namespaceRepository = namespaceRepository;
|
||||
this.skillRepository = skillRepository;
|
||||
this.skillVersionRepository = skillVersionRepository;
|
||||
|
|
@ -53,6 +57,7 @@ public class SkillQueryService {
|
|||
this.skillTagRepository = skillTagRepository;
|
||||
this.objectStorageService = objectStorageService;
|
||||
this.visibilityChecker = visibilityChecker;
|
||||
this.promotionRequestRepository = promotionRequestRepository;
|
||||
}
|
||||
|
||||
public record SkillDetailDTO(
|
||||
|
|
@ -501,6 +506,12 @@ public class SkillQueryService {
|
|||
if (latestVersionEntity == null || latestVersionEntity.getStatus() != SkillVersionStatus.PUBLISHED) {
|
||||
return false;
|
||||
}
|
||||
if (promotionRequestRepository.findBySourceSkillIdAndStatus(skill.getId(), ReviewTaskStatus.PENDING).isPresent()) {
|
||||
return false;
|
||||
}
|
||||
if (promotionRequestRepository.findBySourceSkillIdAndStatus(skill.getId(), ReviewTaskStatus.APPROVED).isPresent()) {
|
||||
return false;
|
||||
}
|
||||
return canManageRestrictedSkill(skill, currentUserId, userNsRoles);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,9 @@ class PromotionServiceTest {
|
|||
when(namespaceRepository.findById(sourceSkill.getNamespaceId())).thenReturn(Optional.of(createSourceNamespace()));
|
||||
when(permissionChecker.canSubmitPromotion(sourceSkill, USER_ID, Map.of())).thenReturn(true);
|
||||
when(namespaceRepository.findById(TARGET_NAMESPACE_ID)).thenReturn(Optional.of(globalNs));
|
||||
when(promotionRequestRepository.findBySourceVersionIdAndStatus(SOURCE_VERSION_ID, ReviewTaskStatus.PENDING))
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(SOURCE_SKILL_ID, ReviewTaskStatus.PENDING))
|
||||
.thenReturn(Optional.empty());
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(SOURCE_SKILL_ID, ReviewTaskStatus.APPROVED))
|
||||
.thenReturn(Optional.empty());
|
||||
when(promotionRequestRepository.save(any(PromotionRequest.class)))
|
||||
.thenAnswer(inv -> {
|
||||
|
|
@ -230,13 +232,33 @@ class PromotionServiceTest {
|
|||
when(namespaceRepository.findById(sourceSkill.getNamespaceId())).thenReturn(Optional.of(createSourceNamespace()));
|
||||
when(permissionChecker.canSubmitPromotion(sourceSkill, USER_ID, Map.of())).thenReturn(true);
|
||||
when(namespaceRepository.findById(TARGET_NAMESPACE_ID)).thenReturn(Optional.of(createGlobalNamespace()));
|
||||
when(promotionRequestRepository.findBySourceVersionIdAndStatus(SOURCE_VERSION_ID, ReviewTaskStatus.PENDING))
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(SOURCE_SKILL_ID, ReviewTaskStatus.PENDING))
|
||||
.thenReturn(Optional.of(createPendingPromotion()));
|
||||
|
||||
assertThrows(DomainBadRequestException.class,
|
||||
() -> promotionService.submitPromotion(SOURCE_SKILL_ID, SOURCE_VERSION_ID, TARGET_NAMESPACE_ID, USER_ID, Map.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenSkillAlreadyPromoted() {
|
||||
Skill sourceSkill = createSourceSkill();
|
||||
PromotionRequest approvedPromotion = createPendingPromotion();
|
||||
setField(approvedPromotion, "status", ReviewTaskStatus.APPROVED);
|
||||
|
||||
when(skillRepository.findById(SOURCE_SKILL_ID)).thenReturn(Optional.of(sourceSkill));
|
||||
when(skillVersionRepository.findById(SOURCE_VERSION_ID)).thenReturn(Optional.of(createPublishedVersion()));
|
||||
when(namespaceRepository.findById(sourceSkill.getNamespaceId())).thenReturn(Optional.of(createSourceNamespace()));
|
||||
when(permissionChecker.canSubmitPromotion(sourceSkill, USER_ID, Map.of())).thenReturn(true);
|
||||
when(namespaceRepository.findById(TARGET_NAMESPACE_ID)).thenReturn(Optional.of(createGlobalNamespace()));
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(SOURCE_SKILL_ID, ReviewTaskStatus.PENDING))
|
||||
.thenReturn(Optional.empty());
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(SOURCE_SKILL_ID, ReviewTaskStatus.APPROVED))
|
||||
.thenReturn(Optional.of(approvedPromotion));
|
||||
|
||||
assertThrows(DomainBadRequestException.class,
|
||||
() -> promotionService.submitPromotion(SOURCE_SKILL_ID, SOURCE_VERSION_ID, TARGET_NAMESPACE_ID, USER_ID, Map.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenSubmitterIsNotOwnerOrNamespaceAdmin() {
|
||||
Skill sourceSkill = createSourceSkill();
|
||||
|
|
@ -277,7 +299,9 @@ class PromotionServiceTest {
|
|||
Map.of(sourceSkill.getNamespaceId(), com.iflytek.skillhub.domain.namespace.NamespaceRole.ADMIN)))
|
||||
.thenReturn(true);
|
||||
when(namespaceRepository.findById(TARGET_NAMESPACE_ID)).thenReturn(Optional.of(globalNs));
|
||||
when(promotionRequestRepository.findBySourceVersionIdAndStatus(SOURCE_VERSION_ID, ReviewTaskStatus.PENDING))
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(SOURCE_SKILL_ID, ReviewTaskStatus.PENDING))
|
||||
.thenReturn(Optional.empty());
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(SOURCE_SKILL_ID, ReviewTaskStatus.APPROVED))
|
||||
.thenReturn(Optional.empty());
|
||||
when(promotionRequestRepository.save(any(PromotionRequest.class)))
|
||||
.thenAnswer(inv -> inv.getArgument(0));
|
||||
|
|
@ -362,11 +386,17 @@ class PromotionServiceTest {
|
|||
@Test
|
||||
void shouldApprovePromotionSuccessfully() {
|
||||
PromotionRequest pr = createPendingPromotion();
|
||||
PromotionRequest approvedPromotion = createPendingPromotion();
|
||||
setField(approvedPromotion, "status", ReviewTaskStatus.APPROVED);
|
||||
setField(approvedPromotion, "version", 2);
|
||||
setField(approvedPromotion, "reviewedBy", REVIEWER_ID);
|
||||
setField(approvedPromotion, "reviewComment", "LGTM");
|
||||
Skill sourceSkill = createSourceSkill();
|
||||
SkillVersion sourceVersion = createPublishedVersion();
|
||||
List<SkillFile> sourceFiles = createSourceFiles();
|
||||
|
||||
when(promotionRequestRepository.findById(PROMOTION_ID)).thenReturn(Optional.of(pr));
|
||||
when(promotionRequestRepository.findById(PROMOTION_ID))
|
||||
.thenReturn(Optional.of(pr), Optional.of(approvedPromotion));
|
||||
when(permissionChecker.canReviewPromotion(pr, REVIEWER_ID, Set.of("SKILL_ADMIN"))).thenReturn(true);
|
||||
when(promotionRequestRepository.updateStatusWithVersion(
|
||||
PROMOTION_ID, ReviewTaskStatus.APPROVED, REVIEWER_ID, "LGTM", null, pr.getVersion()))
|
||||
|
|
@ -385,6 +415,7 @@ class PromotionServiceTest {
|
|||
});
|
||||
when(skillFileRepository.findByVersionId(SOURCE_VERSION_ID)).thenReturn(sourceFiles);
|
||||
when(skillFileRepository.saveAll(anyList())).thenAnswer(inv -> inv.getArgument(0));
|
||||
when(promotionRequestRepository.save(approvedPromotion)).thenReturn(approvedPromotion);
|
||||
|
||||
PromotionRequest result = promotionService.approvePromotion(
|
||||
PROMOTION_ID, REVIEWER_ID, "LGTM", Set.of("SKILL_ADMIN"));
|
||||
|
|
@ -431,8 +462,8 @@ class PromotionServiceTest {
|
|||
assertEquals(REVIEWER_ID, event.publisherId());
|
||||
|
||||
// Verify targetSkillId updated on promotion request
|
||||
verify(promotionRequestRepository).save(pr);
|
||||
assertEquals(NEW_SKILL_ID, pr.getTargetSkillId());
|
||||
verify(promotionRequestRepository).save(approvedPromotion);
|
||||
assertEquals(NEW_SKILL_ID, approvedPromotion.getTargetSkillId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import com.iflytek.skillhub.domain.namespace.Namespace;
|
|||
import com.iflytek.skillhub.domain.namespace.NamespaceRepository;
|
||||
import com.iflytek.skillhub.domain.namespace.NamespaceRole;
|
||||
import com.iflytek.skillhub.domain.namespace.NamespaceStatus;
|
||||
import com.iflytek.skillhub.domain.review.PromotionRequestRepository;
|
||||
import com.iflytek.skillhub.domain.review.ReviewTaskStatus;
|
||||
import com.iflytek.skillhub.domain.shared.exception.DomainBadRequestException;
|
||||
import com.iflytek.skillhub.domain.shared.exception.DomainForbiddenException;
|
||||
import com.iflytek.skillhub.domain.skill.*;
|
||||
|
|
@ -46,6 +48,8 @@ class SkillQueryServiceTest {
|
|||
private ObjectStorageService objectStorageService;
|
||||
@Mock
|
||||
private VisibilityChecker visibilityChecker;
|
||||
@Mock
|
||||
private PromotionRequestRepository promotionRequestRepository;
|
||||
|
||||
private SkillQueryService service;
|
||||
|
||||
|
|
@ -58,7 +62,8 @@ class SkillQueryServiceTest {
|
|||
skillFileRepository,
|
||||
skillTagRepository,
|
||||
objectStorageService,
|
||||
visibilityChecker
|
||||
visibilityChecker,
|
||||
promotionRequestRepository
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -485,6 +490,8 @@ class SkillQueryServiceTest {
|
|||
when(skillRepository.findByNamespaceIdAndSlug(1L, skillSlug)).thenReturn(Optional.of(skill));
|
||||
when(visibilityChecker.canAccess(skill, userId, userNsRoles)).thenReturn(true);
|
||||
when(skillVersionRepository.findById(11L)).thenReturn(Optional.of(published));
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(1L, ReviewTaskStatus.PENDING)).thenReturn(Optional.empty());
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(1L, ReviewTaskStatus.APPROVED)).thenReturn(Optional.empty());
|
||||
|
||||
SkillQueryService.SkillDetailDTO result = service.getSkillDetail(namespaceSlug, skillSlug, userId, userNsRoles);
|
||||
|
||||
|
|
@ -492,6 +499,67 @@ class SkillQueryServiceTest {
|
|||
assertTrue(result.canSubmitPromotion());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSkillDetail_ShouldHidePromotionWhenPendingPromotionExists() throws Exception {
|
||||
String namespaceSlug = "team-ns";
|
||||
String skillSlug = "team-skill";
|
||||
String userId = "owner-1";
|
||||
Map<Long, NamespaceRole> userNsRoles = Map.of();
|
||||
|
||||
Namespace namespace = new Namespace(namespaceSlug, "Team NS", userId);
|
||||
setId(namespace, 1L);
|
||||
Skill skill = new Skill(1L, skillSlug, userId, SkillVisibility.PUBLIC);
|
||||
setId(skill, 1L);
|
||||
skill.setStatus(SkillStatus.ACTIVE);
|
||||
skill.setLatestVersionId(11L);
|
||||
|
||||
SkillVersion published = new SkillVersion(1L, "1.0.0", userId);
|
||||
setId(published, 11L);
|
||||
published.setStatus(SkillVersionStatus.PUBLISHED);
|
||||
|
||||
when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace));
|
||||
when(skillRepository.findByNamespaceIdAndSlug(1L, skillSlug)).thenReturn(Optional.of(skill));
|
||||
when(visibilityChecker.canAccess(skill, userId, userNsRoles)).thenReturn(true);
|
||||
when(skillVersionRepository.findById(11L)).thenReturn(Optional.of(published));
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(1L, ReviewTaskStatus.PENDING))
|
||||
.thenReturn(Optional.of(mock(com.iflytek.skillhub.domain.review.PromotionRequest.class)));
|
||||
|
||||
SkillQueryService.SkillDetailDTO result = service.getSkillDetail(namespaceSlug, skillSlug, userId, userNsRoles);
|
||||
|
||||
assertFalse(result.canSubmitPromotion());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSkillDetail_ShouldHidePromotionWhenSkillAlreadyPromoted() throws Exception {
|
||||
String namespaceSlug = "team-ns";
|
||||
String skillSlug = "team-skill";
|
||||
String userId = "owner-1";
|
||||
Map<Long, NamespaceRole> userNsRoles = Map.of();
|
||||
|
||||
Namespace namespace = new Namespace(namespaceSlug, "Team NS", userId);
|
||||
setId(namespace, 1L);
|
||||
Skill skill = new Skill(1L, skillSlug, userId, SkillVisibility.PUBLIC);
|
||||
setId(skill, 1L);
|
||||
skill.setStatus(SkillStatus.ACTIVE);
|
||||
skill.setLatestVersionId(11L);
|
||||
|
||||
SkillVersion published = new SkillVersion(1L, "1.0.0", userId);
|
||||
setId(published, 11L);
|
||||
published.setStatus(SkillVersionStatus.PUBLISHED);
|
||||
|
||||
when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace));
|
||||
when(skillRepository.findByNamespaceIdAndSlug(1L, skillSlug)).thenReturn(Optional.of(skill));
|
||||
when(visibilityChecker.canAccess(skill, userId, userNsRoles)).thenReturn(true);
|
||||
when(skillVersionRepository.findById(11L)).thenReturn(Optional.of(published));
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(1L, ReviewTaskStatus.PENDING)).thenReturn(Optional.empty());
|
||||
when(promotionRequestRepository.findBySourceSkillIdAndStatus(1L, ReviewTaskStatus.APPROVED))
|
||||
.thenReturn(Optional.of(mock(com.iflytek.skillhub.domain.review.PromotionRequest.class)));
|
||||
|
||||
SkillQueryService.SkillDetailDTO result = service.getSkillDetail(namespaceSlug, skillSlug, userId, userNsRoles);
|
||||
|
||||
assertFalse(result.canSubmitPromotion());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetSkillDetail_ShouldNotFlagLifecyclePermissionForRegularViewer() throws Exception {
|
||||
String namespaceSlug = "test-ns";
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ public interface PromotionRequestJpaRepository extends JpaRepository<PromotionRe
|
|||
|
||||
Optional<PromotionRequest> findBySourceVersionIdAndStatus(Long sourceVersionId, ReviewTaskStatus status);
|
||||
|
||||
Optional<PromotionRequest> findBySourceSkillIdAndStatus(Long sourceSkillId, ReviewTaskStatus status);
|
||||
|
||||
Page<PromotionRequest> findByStatus(ReviewTaskStatus status, Pageable pageable);
|
||||
|
||||
@Modifying
|
||||
@Modifying(clearAutomatically = true, flushAutomatically = true)
|
||||
@Query("""
|
||||
UPDATE PromotionRequest p
|
||||
SET p.status = :status,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue