diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillReviewAppService.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillReviewAppService.java index 4c33b921..49fba830 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillReviewAppService.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/service/SkillReviewAppService.java @@ -8,6 +8,7 @@ import com.iflytek.skillhub.domain.shared.exception.DomainForbiddenException; import com.iflytek.skillhub.domain.shared.exception.DomainNotFoundException; import com.iflytek.skillhub.domain.skill.Skill; import com.iflytek.skillhub.domain.skill.SkillRepository; +import com.iflytek.skillhub.domain.skill.SkillStatus; import com.iflytek.skillhub.domain.skill.SkillVersionRepository; import com.iflytek.skillhub.domain.skill.SkillVersionStatus; import com.iflytek.skillhub.domain.skill.VisibilityChecker; @@ -142,7 +143,7 @@ public class SkillReviewAppService { && skillVersionRepository.findById(skill.getLatestVersionId()) .map(version -> version.getStatus() == SkillVersionStatus.PUBLISHED) .orElse(false); - if (!published) { + if (skill.getStatus() != SkillStatus.ACTIVE || !published) { throw new DomainBadRequestException("error.skillReview.notInteractable"); } return skill; diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SkillReviewAppServiceTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SkillReviewAppServiceTest.java index eb8ff6ed..be792eed 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SkillReviewAppServiceTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/service/SkillReviewAppServiceTest.java @@ -13,6 +13,7 @@ import com.iflytek.skillhub.domain.shared.exception.DomainBadRequestException; import com.iflytek.skillhub.domain.shared.exception.DomainForbiddenException; import com.iflytek.skillhub.domain.skill.Skill; import com.iflytek.skillhub.domain.skill.SkillRepository; +import com.iflytek.skillhub.domain.skill.SkillStatus; import com.iflytek.skillhub.domain.skill.SkillVersion; import com.iflytek.skillhub.domain.skill.SkillVersionRepository; import com.iflytek.skillhub.domain.skill.SkillVersionStatus; @@ -125,6 +126,23 @@ class SkillReviewAppServiceTest { verify(ratingService, never()).upsertReview(any(), any(), any(Short.class), any()); } + @Test + void archivedSkillReviewMutationIsRejectedServerSide() { + Skill skill = publishedSkill(SkillVisibility.PUBLIC); + skill.setStatus(SkillStatus.ARCHIVED); + SkillVersion published = new SkillVersion(10L, "1.0.0", "owner"); + published.setStatus(SkillVersionStatus.PUBLISHED); + when(skillRepository.findById(10L)).thenReturn(Optional.of(skill)); + when(skillVersionRepository.findById(100L)).thenReturn(Optional.of(published)); + + assertThatThrownBy(() -> service.upsert( + 10L, "owner", (short) 5, "archived", Map.of(), Set.of())) + .isInstanceOf(DomainBadRequestException.class) + .hasMessage("error.skillReview.notInteractable"); + + verify(ratingService, never()).upsertReview(any(), any(), any(Short.class), any()); + } + @Test void authorCanClearReviewAfterSkillStopsBeingInteractable() { SkillRating review = new SkillRating(10L, "author", (short) 4); diff --git a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillQueryServiceTest.java b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillQueryServiceTest.java index ac49f1d8..67d34fc4 100644 --- a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillQueryServiceTest.java +++ b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/skill/service/SkillQueryServiceTest.java @@ -1241,6 +1241,28 @@ class SkillQueryServiceTest { assertFalse(result.canInteract()); } + @Test + void testGetSkillDetail_ShouldKeepInteractionForActiveSkillWithoutHeadlineVersion() throws Exception { + String namespaceSlug = "test-ns"; + String skillSlug = "test-skill"; + String ownerId = "owner-1"; + + Namespace namespace = new Namespace(namespaceSlug, "Test NS", ownerId); + setId(namespace, 1L); + Skill skill = new Skill(1L, skillSlug, ownerId, SkillVisibility.PUBLIC); + setId(skill, 1L); + skill.setStatus(SkillStatus.ACTIVE); + + when(namespaceRepository.findBySlug(namespaceSlug)).thenReturn(Optional.of(namespace)); + when(skillRepository.findByNamespaceIdAndSlug(1L, skillSlug)).thenReturn(List.of(skill)); + + SkillQueryService.SkillDetailDTO result = service.getSkillDetail( + namespaceSlug, skillSlug, ownerId, Map.of()); + + assertNull(result.headlineVersion()); + assertTrue(result.canInteract()); + } + @Test void testGetSkillDetail_ShouldIncludeRejectedOwnerPreviewComment() throws Exception { String namespaceSlug = "test-ns";