diff --git a/src/intTest/kotlin/mastodon/timelines/TimelineApiTest.kt b/src/intTest/kotlin/mastodon/timelines/TimelineApiTest.kt new file mode 100644 index 00000000..37fe48d8 --- /dev/null +++ b/src/intTest/kotlin/mastodon/timelines/TimelineApiTest.kt @@ -0,0 +1,115 @@ +package mastodon.timelines + +import dev.usbharu.hideout.SpringApplication +import org.flywaydb.core.Flyway +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.test.context.support.WithAnonymousUser +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors +import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers +import org.springframework.test.context.jdbc.Sql +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.context.WebApplicationContext + +@SpringBootTest(classes = [SpringApplication::class]) +@Transactional +@Sql("/sql/test-user.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) +class TimelineApiTest { + @Autowired + private lateinit var context: WebApplicationContext + + private lateinit var mockMvc: MockMvc + + @BeforeEach + fun beforeEach() { + mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(SecurityMockMvcConfigurers.springSecurity()) + .build() + } + + @Test + fun `apiV1TimelinesHomeGetにreadでアクセスできる`() { + mockMvc + .get("/api/v1/timelines/home") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1TimelinesHomeGetにread statusesでアクセスできる`() { + mockMvc + .get("/api/v1/timelines/home") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:statuses")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + @WithAnonymousUser + fun apiV1TimelineHomeGetに匿名でアクセスすると401() { + mockMvc + .get("/api/v1/timelines/home") + .andExpect { status { isUnauthorized() } } + } + + @Test + fun apiV1TimelinesPublicGetにreadでアクセスできる() { + mockMvc + .get("/api/v1/timelines/public") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + fun `apiV1TimelinesPublicGetにread statusesでアクセスできる`() { + mockMvc + .get("/api/v1/timelines/public") { + with( + SecurityMockMvcRequestPostProcessors.jwt() + .jwt { it.claim("uid", "1") }.authorities(SimpleGrantedAuthority("SCOPE_read:statuses")) + ) + } + .asyncDispatch() + .andExpect { status { isOk() } } + } + + @Test + @WithAnonymousUser + fun apiV1TimeinesPublicGetに匿名でアクセスできる() { + mockMvc + .get("/api/v1/timelines/public") + .asyncDispatch() + .andExpect { status { isOk() } } + } + + companion object { + @JvmStatic + @AfterAll + fun dropDatabase(@Autowired flyway: Flyway) { + flyway.clean() + flyway.migrate() + } + } +} diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt index dadee6f4..08059d2d 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt @@ -204,6 +204,9 @@ class SecurityConfig { authorize(POST, "/api/v1/media", hasAnyScope("write", "write:media")) authorize(POST, "/api/v1/statuses", hasAnyScope("write", "write:statuses")) + authorize(GET, "/api/v1/timelines/public", permitAll) + authorize(GET, "/api/v1/timelines/home", hasAnyScope("read", "read:statuses")) + authorize(anyRequest, authenticated) }