From 726eeac8b24dc85a6e98276738d211ef92551d58 Mon Sep 17 00:00:00 2001 From: dongmucat <1127093059@qq.com> Date: Tue, 28 Jul 2026 13:52:26 +0800 Subject: [PATCH] test(auth): cover token replay and private search (#605) Signed-off-by: dongmucat <1127093059@qq.com> --- ...ictedReadAuthorizationIntegrationTest.java | 29 +++++++++ ...TokenLifecycleSecurityIntegrationTest.java | 62 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliRestrictedReadAuthorizationIntegrationTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliRestrictedReadAuthorizationIntegrationTest.java index 0424e373..5a29cffa 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliRestrictedReadAuthorizationIntegrationTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliRestrictedReadAuthorizationIntegrationTest.java @@ -11,6 +11,8 @@ import com.iflytek.skillhub.domain.skill.SkillVersionStatus; import com.iflytek.skillhub.domain.skill.SkillVisibility; import com.iflytek.skillhub.domain.user.UserAccount; import com.iflytek.skillhub.domain.user.UserAccountRepository; +import com.iflytek.skillhub.infra.jpa.SkillSearchDocumentEntity; +import com.iflytek.skillhub.infra.jpa.SkillSearchDocumentJpaRepository; import java.time.Instant; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; @@ -23,6 +25,9 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -38,6 +43,7 @@ class CliRestrictedReadAuthorizationIntegrationTest { @Autowired NamespaceRepository namespaceRepository; @Autowired SkillRepository skillRepository; @Autowired SkillVersionRepository skillVersionRepository; + @Autowired SkillSearchDocumentJpaRepository skillSearchDocumentRepository; private String namespaceSlug; private String skillSlug; @@ -76,6 +82,29 @@ class CliRestrictedReadAuthorizationIntegrationTest { skillRepository.save(skill); skillRepository.flush(); skillVersionRepository.flush(); + skillSearchDocumentRepository.saveAndFlush(new SkillSearchDocumentEntity( + skill.getId(), + namespace.getId(), + namespaceSlug, + ownerId, + skillSlug, + "Private skill search fixture", + "private", + skillSlug, + "", + SkillVisibility.PRIVATE.name(), + skill.getStatus().name())); + } + + @Test + void outsiderSearchOmitsPersistedPrivateSkill() throws Exception { + mockMvc.perform(withBearer( + get("/api/cli/v1/skills/search").param("limit", "20"), + outsiderToken)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", aMapWithSize(5))) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.items[*].slug", not(hasItem(skillSlug)))); } @Test diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliTokenLifecycleSecurityIntegrationTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliTokenLifecycleSecurityIntegrationTest.java index 04b19de5..e351a7e5 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliTokenLifecycleSecurityIntegrationTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/cli/CliTokenLifecycleSecurityIntegrationTest.java @@ -29,8 +29,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.nullValue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -205,6 +208,65 @@ class CliTokenLifecycleSecurityIntegrationTest { verifyNoInteractions(cliSkillAppService); } + @Test + void sameRawTokenIsRejectedByAllEndpointsAfterValidUseAndRevocation() throws Exception { + ApiTokenService.TokenCreateResult token = createToken(); + String rawToken = token.rawToken(); + + assertSuccessEnvelope(withBearer(get("/api/cli/v1/auth/whoami"), rawToken)) + .andExpect(jsonPath("$.data.handle").value(userId)); + assertSuccessEnvelope(withBearer( + get("/api/cli/v1/skills/search").param("q", "demo").param("limit", "20"), + rawToken)); + assertSuccessEnvelope(withBearer( + get("/api/cli/v1/skills/global/demo/resolve"), rawToken)); + mockMvc.perform(withBearer( + get("/api/cli/v1/skills/global/demo/download"), rawToken)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")); + mockMvc.perform(withBearer( + get("/api/cli/v1/skills/global/demo/versions/1.0.0/download"), rawToken)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/zip")); + + apiTokenService.revokeToken(token.entity().getId(), userId); + clearInvocations(cliSkillAppService); + + assertUnauthorizedEnvelope(withBearer(get("/api/cli/v1/auth/whoami"), rawToken)); + assertUnauthorizedEnvelope(withBearer( + get("/api/cli/v1/skills/search").param("q", "demo").param("limit", "20"), + rawToken)); + assertUnauthorizedEnvelope(withBearer( + get("/api/cli/v1/skills/global/demo/resolve"), rawToken)); + assertUnauthorizedEnvelope(withBearer( + get("/api/cli/v1/skills/global/demo/download"), rawToken)); + assertUnauthorizedEnvelope(withBearer( + get("/api/cli/v1/skills/global/demo/versions/1.0.0/download"), rawToken)); + verifyNoInteractions(cliSkillAppService); + } + + private ResultActions assertSuccessEnvelope(MockHttpServletRequestBuilder request) throws Exception { + return mockMvc.perform(request) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", aMapWithSize(5))) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.msg").isString()) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.timestamp").isString()) + .andExpect(jsonPath("$.requestId").isString()); + } + + private void assertUnauthorizedEnvelope(MockHttpServletRequestBuilder request) throws Exception { + mockMvc.perform(request) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$", aMapWithSize(5))) + .andExpect(jsonPath("$.code").value(401)) + .andExpect(jsonPath("$.msg").isString()) + .andExpect(jsonPath("$.data").value(nullValue())) + .andExpect(jsonPath("$.timestamp").isString()) + .andExpect(jsonPath("$.requestId").isString()); + } + private MockHttpServletRequestBuilder withInvalidBearer( MockHttpServletRequestBuilder request, InvalidCredentialState state) {