fix(compat): prefer published legacy slug candidates

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-08-25 14:56:29 +08:00
parent 32d5de7c44
commit 46336a2576
2 changed files with 66 additions and 8 deletions

View file

@ -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.<Skill>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.<Skill>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<Long, Namespace> 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(

View file

@ -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;
}
}