From 46336a25760123e2a129128fa7a705c0663efbd8 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:56:29 +0800 Subject: [PATCH] fix(compat): prefer published legacy slug candidates Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- .../compat/CompatSkillLookupService.java | 24 ++++++--- .../compat/CompatSkillLookupServiceTest.java | 50 +++++++++++++++++++ 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/compat/CompatSkillLookupService.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/compat/CompatSkillLookupService.java index 5729fa06..36d7d7b0 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/compat/CompatSkillLookupService.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/compat/CompatSkillLookupService.java @@ -27,6 +27,10 @@ import org.springframework.stereotype.Service; @Service public class CompatSkillLookupService { + private static final int LEGACY_SLUG_PUBLISHED_SCORE = 1_000; + private static final int LEGACY_SLUG_PUBLIC_SCORE = 100; + private static final int LEGACY_SLUG_GLOBAL_SCORE = 10; + private final SkillRepository skillRepository; private final NamespaceRepository namespaceRepository; private final SkillVersionRepository skillVersionRepository; @@ -60,14 +64,8 @@ public class CompatSkillLookupService { : namespaceRepository.findByIdIn(namespaceIds).stream() .collect(Collectors.toMap(Namespace::getId, Function.identity())); Skill skill = skills.stream() - .min(Comparator.comparingInt(s -> { - Namespace ns = namespacesById.get(s.getNamespaceId()); - int score = 0; - if (s.getVisibility() == SkillVisibility.PUBLIC) score += 100; - if (ns != null && ns.getType() == NamespaceType.GLOBAL) score += 50; - if (s.getLatestVersionId() != null) score += 10; - return -score; - }).thenComparing(Skill::getId)) + .min(Comparator.comparingInt(s -> -legacySlugCandidateScore(s, namespacesById)) + .thenComparing(Skill::getId)) .orElse(skills.get(0)); Namespace namespace = namespacesById.get(skill.getNamespaceId()); if (namespace == null) { @@ -115,6 +113,16 @@ public class CompatSkillLookupService { return skillVersionRepository.findById(skill.getLatestVersionId()); } + private static int legacySlugCandidateScore(Skill skill, Map namespacesById) { + Namespace namespace = namespacesById.get(skill.getNamespaceId()); + int score = 0; + // Bare-slug CLI resolution should prefer installable candidates before namespace defaults. + if (skill.getLatestVersionId() != null) score += LEGACY_SLUG_PUBLISHED_SCORE; + if (skill.getVisibility() == SkillVisibility.PUBLIC) score += LEGACY_SLUG_PUBLIC_SCORE; + if (namespace != null && namespace.getType() == NamespaceType.GLOBAL) score += LEGACY_SLUG_GLOBAL_SCORE; + return score; + } + private Skill resolveVisibleSkill(Long namespaceId, String slug, String currentUserId) { try { return skillSlugResolutionService.resolve( diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/CompatSkillLookupServiceTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/CompatSkillLookupServiceTest.java index 3500ce04..b8722391 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/CompatSkillLookupServiceTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/compat/CompatSkillLookupServiceTest.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.when; 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.NamespaceType; import com.iflytek.skillhub.domain.shared.exception.DomainNotFoundException; import com.iflytek.skillhub.domain.skill.Skill; import com.iflytek.skillhub.domain.skill.SkillRepository; @@ -15,6 +16,7 @@ import com.iflytek.skillhub.domain.skill.SkillVersionRepository; import com.iflytek.skillhub.domain.skill.SkillVisibility; import com.iflytek.skillhub.domain.skill.VisibilityChecker; import com.iflytek.skillhub.domain.skill.service.SkillSlugResolutionService; +import java.util.List; import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -36,6 +38,40 @@ class CompatSkillLookupServiceTest { visibilityChecker ); + @Test + void findByLegacySlug_prefersPublicGlobalPublishedCandidate() { + Skill privateTeamSkill = skill(11L, 1L, "demo", SkillVisibility.PRIVATE, 110L); + Skill publicTeamSkill = skill(12L, 1L, "demo", SkillVisibility.PUBLIC, 120L); + Skill publicGlobalSkill = skill(13L, 2L, "demo", SkillVisibility.PUBLIC, 130L); + Namespace teamNamespace = namespace(1L, "team-a", NamespaceType.TEAM); + Namespace globalNamespace = namespace(2L, "global", NamespaceType.GLOBAL); + + when(skillRepository.findBySlug("demo")) + .thenReturn(List.of(privateTeamSkill, publicTeamSkill, publicGlobalSkill)); + when(namespaceRepository.findByIdIn(List.of(1L, 2L))).thenReturn(List.of(teamNamespace, globalNamespace)); + + CompatSkillLookupService.CompatSkillContext result = service.findByLegacySlug("demo"); + + assertThat(result.skill().getId()).isEqualTo(13L); + assertThat(result.namespace().getSlug()).isEqualTo("global"); + } + + @Test + void findByLegacySlug_prefersPublishedCandidateOverGlobalDraft() { + Skill publicGlobalDraft = skill(21L, 2L, "demo", SkillVisibility.PUBLIC, null); + Skill publicTeamPublished = skill(22L, 1L, "demo", SkillVisibility.PUBLIC, 220L); + Namespace teamNamespace = namespace(1L, "team-a", NamespaceType.TEAM); + Namespace globalNamespace = namespace(2L, "global", NamespaceType.GLOBAL); + + when(skillRepository.findBySlug("demo")).thenReturn(List.of(publicGlobalDraft, publicTeamPublished)); + when(namespaceRepository.findByIdIn(List.of(2L, 1L))).thenReturn(List.of(globalNamespace, teamNamespace)); + + CompatSkillLookupService.CompatSkillContext result = service.findByLegacySlug("demo"); + + assertThat(result.skill().getId()).isEqualTo(22L); + assertThat(result.namespace().getSlug()).isEqualTo("team-a"); + } + @Test void resolveVisible_throwsNotFoundWhenCallerCannotAccessSkill() { Namespace namespace = new Namespace("team-a", "Team A", "owner-1"); @@ -75,4 +111,18 @@ class CompatSkillLookupServiceTest { assertThat(result.skill().getId()).isEqualTo(7L); } + + private static Skill skill(Long id, Long namespaceId, String slug, SkillVisibility visibility, Long latestVersionId) { + Skill skill = new Skill(namespaceId, slug, "owner-1", visibility); + ReflectionTestUtils.setField(skill, "id", id); + skill.setLatestVersionId(latestVersionId); + return skill; + } + + private static Namespace namespace(Long id, String slug, NamespaceType type) { + Namespace namespace = new Namespace(slug, slug, "owner-1"); + ReflectionTestUtils.setField(namespace, "id", id); + namespace.setType(type); + return namespace; + } }