fix(compat): support canonical query slug for resolve/download

This commit is contained in:
huihan3 2026-04-03 12:22:23 +08:00
parent c0f790079d
commit bc6439c44d
2 changed files with 70 additions and 6 deletions

View file

@ -14,6 +14,7 @@ import com.iflytek.skillhub.controller.support.MultipartPackageExtractor;
import com.iflytek.skillhub.controller.support.ZipPackageExtractor;
import com.iflytek.skillhub.domain.audit.AuditLogService;
import com.iflytek.skillhub.domain.namespace.NamespaceRole;
import com.iflytek.skillhub.domain.shared.exception.DomainNotFoundException;
import com.iflytek.skillhub.domain.skill.SkillVersion;
import com.iflytek.skillhub.domain.skill.SkillVisibility;
import com.iflytek.skillhub.domain.skill.service.SkillPublishService;
@ -93,11 +94,11 @@ public class ClawHubCompatAppService {
String hash,
String userId,
Map<Long, NamespaceRole> userNsRoles) {
CompatSkillLookupService.CompatSkillContext context = compatSkillLookupService.findByLegacySlug(slug);
SkillCoordinate coord = resolveQueryCoordinate(slug);
SkillQueryService.ResolvedVersionDTO resolved = skillQueryService.resolveVersion(
context.namespace().getSlug(),
context.skill().getSlug(),
coord.namespace(),
coord.slug(),
"latest".equals(version) ? null : version,
"latest".equals(version) ? "latest" : null,
hash,
@ -132,10 +133,22 @@ public class ClawHubCompatAppService {
}
public String downloadLocationByQuery(String slug, String version) {
CompatSkillLookupService.CompatSkillContext context = compatSkillLookupService.findByLegacySlug(slug);
SkillCoordinate coord = resolveQueryCoordinate(slug);
return "latest".equals(version)
? "/api/v1/skills/" + context.namespace().getSlug() + "/" + context.skill().getSlug() + "/download"
: "/api/v1/skills/" + context.namespace().getSlug() + "/" + context.skill().getSlug() + "/versions/" + version + "/download";
? "/api/v1/skills/" + coord.namespace() + "/" + coord.slug() + "/download"
: "/api/v1/skills/" + coord.namespace() + "/" + coord.slug() + "/versions/" + version + "/download";
}
private SkillCoordinate resolveQueryCoordinate(String slug) {
if (slug != null && slug.contains("--")) {
return mapper.fromCanonical(slug);
}
try {
CompatSkillLookupService.CompatSkillContext context = compatSkillLookupService.findByLegacySlug(slug);
return new SkillCoordinate(context.namespace().getSlug(), context.skill().getSlug());
} catch (DomainNotFoundException ex) {
return mapper.fromCanonical(slug);
}
}
public ClawHubSkillListResponse listSkills(int page,

View file

@ -22,6 +22,7 @@ import java.util.Set;
import java.math.BigDecimal;
import java.time.Instant;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@ -105,6 +106,56 @@ class ClawHubCompatControllerTest {
.andExpect(jsonPath("$.latestVersion.version").value("latest"));
}
@Test
void resolve_query_with_canonical_slug_returns_correct_downloadUrl() throws Exception {
when(skillQueryService.resolveVersion("team-ai", "my-skill", null, "latest", null, null, java.util.Map.of()))
.thenReturn(new SkillQueryService.ResolvedVersionDTO(
1L, "team-ai", "my-skill", "latest", 2L, "sha", true, "/api/v1/skills/team-ai/my-skill/download"));
mockMvc.perform(get("/api/v1/resolve")
.param("slug", "team-ai--my-skill")
.param("version", "latest"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.match.version").value("latest"))
.andExpect(jsonPath("$.latestVersion.version").value("latest"));
verify(skillQueryService).resolveVersion("team-ai", "my-skill", null, "latest", null, null, java.util.Map.of());
}
@Test
void resolve_query_with_legacy_slug_keeps_legacy_lookup_behavior() throws Exception {
when(skillQueryService.resolveVersion("global", "my-skill", null, "latest", null, null, java.util.Map.of()))
.thenReturn(new SkillQueryService.ResolvedVersionDTO(
1L, "global", "my-skill", "latest", 2L, "sha", true, "/api/v1/skills/global/my-skill/download"));
mockMvc.perform(get("/api/v1/resolve")
.param("slug", "my-skill")
.param("version", "latest"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.match.version").value("latest"))
.andExpect(jsonPath("$.latestVersion.version").value("latest"));
verify(skillQueryService).resolveVersion("global", "my-skill", null, "latest", null, null, java.util.Map.of());
}
@Test
void download_query_with_canonical_slug_redirects_to_namespace_skill_download() throws Exception {
mockMvc.perform(get("/api/v1/download")
.param("slug", "team-ai--my-skill")
.param("version", "latest"))
.andExpect(status().isFound())
.andExpect(header().string("Location", "/api/v1/skills/team-ai/my-skill/download"));
}
@Test
void download_query_with_legacy_slug_keeps_legacy_lookup_behavior() throws Exception {
mockMvc.perform(get("/api/v1/download")
.param("slug", "my-skill")
.param("version", "latest"))
.andExpect(status().isFound())
.andExpect(header().string("Location", "/api/v1/skills/global/my-skill/download"));
}
@Test
void resolve_with_version_returns_specified_version() throws Exception {
when(skillQueryService.resolveVersion("global", "my-skill", "1.0.0", null, null, null, java.util.Map.of()))