diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/UserResponse.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/UserResponse.kt new file mode 100644 index 00000000..7e73a81f --- /dev/null +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/hideout/dto/UserResponse.kt @@ -0,0 +1,11 @@ +package dev.usbharu.hideout.domain.model.hideout.dto + +data class UserResponse( + val id: Long, + val name: String, + val domain: String, + val screenName: String, + val description: String = "", + val url: String, + val createdAt: Long +) diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Posts.kt b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Posts.kt index db1b3f70..772ad8cf 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Posts.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Posts.kt @@ -46,7 +46,7 @@ fun Route.posts(postService: IPostService) { val minId = call.request.queryParameters["minId"]?.toLong() val maxId = call.request.queryParameters["maxId"]?.toLong() val limit = call.request.queryParameters["limit"]?.toInt() - call.respond(postService.findAll(since, until, minId, maxId, limit, userId)) + call.respond(HttpStatusCode.OK, postService.findAll(since, until, minId, maxId, limit, userId)) } get("/{id}") { val userId = call.principal()?.payload?.getClaim("uid")?.asLong() @@ -84,7 +84,6 @@ fun Route.posts(postService: IPostService) { ?: throw ParameterNotExistException("Parameter(name='postsId' does not exist.") val post = (postService.findByIdForUser(id, userId) ?: throw PostNotFoundException("$id was not found or is not authorized.")) - call.response.header("Content-Location", post.url) call.respond(post) } } diff --git a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt index 13917837..a4bea389 100644 --- a/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt +++ b/src/main/kotlin/dev/usbharu/hideout/routing/api/internal/v1/Users.kt @@ -18,7 +18,7 @@ import io.ktor.server.routing.* fun Route.users(userService: IUserService) { route("/users") { get { - call.respond(userService.findAll()) + call.respond(userService.findAllForUser()) } post { val userCreate = call.receive() @@ -34,7 +34,7 @@ fun Route.users(userService: IUserService) { ) ) call.response.header("Location", "${Config.configData.url}/api/internal/v1/users/${user.name}") - call.respond(HttpStatusCode.OK) + call.respond(HttpStatusCode.Created) } route("/{name}") { diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt index ab726ac1..1fc6922e 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/IUserService.kt @@ -2,12 +2,15 @@ package dev.usbharu.hideout.service.impl import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto +import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse import dev.usbharu.hideout.domain.model.hideout.entity.User @Suppress("TooManyFunctions") interface IUserService { suspend fun findAll(limit: Int? = 100, offset: Long? = 0): List + suspend fun findAllForUser(limit: Int? = 100, offset: Long? = 0): List + suspend fun findById(id: Long): User suspend fun findByIds(ids: List): List diff --git a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt index 490c3859..b4f3157c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/service/impl/UserService.kt @@ -3,6 +3,7 @@ package dev.usbharu.hideout.service.impl import dev.usbharu.hideout.config.Config import dev.usbharu.hideout.domain.model.hideout.dto.RemoteUserCreateDto import dev.usbharu.hideout.domain.model.hideout.dto.UserCreateDto +import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.exception.UserNotFoundException import dev.usbharu.hideout.repository.IUserRepository @@ -23,6 +24,10 @@ class UserService(private val userRepository: IUserRepository, private val userA ) } + override suspend fun findAllForUser(limit: Int?, offset: Long?): List { + TODO("Not yet implemented") + } + override suspend fun findById(id: Long): User = userRepository.findById(id) ?: throw UserNotFoundException("$id was not found.") diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsKtTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsKtTest.kt deleted file mode 100644 index a5143505..00000000 --- a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsKtTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -package dev.usbharu.hideout.routing.api.internal.v1 - -import com.auth0.jwt.interfaces.Claim -import com.auth0.jwt.interfaces.Payload -import dev.usbharu.hideout.config.Config -import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto -import dev.usbharu.hideout.domain.model.hideout.entity.Visibility -import dev.usbharu.hideout.domain.model.hideout.form.Post -import dev.usbharu.hideout.plugins.TOKEN_AUTH -import dev.usbharu.hideout.plugins.configureSerialization -import dev.usbharu.hideout.service.IPostService -import io.ktor.client.request.* -import io.ktor.http.* -import io.ktor.server.auth.* -import io.ktor.server.auth.jwt.* -import io.ktor.server.config.* -import io.ktor.server.routing.* -import io.ktor.server.testing.* -import org.junit.jupiter.api.Test -import org.mockito.kotlin.* -import java.time.Instant -import kotlin.test.assertEquals - -class PostsKtTest { - - - @Test - fun `posts-post postsにpostしたら投稿できる`() = testApplication { - environment { - config = ApplicationConfig("empty.conf") - } - val claim = mock { - on { asLong() } doReturn 1234 - } - val payload = mock { - on { getClaim(eq("uid")) } doReturn claim - } - val postService = mock { - onBlocking { create(any()) } doAnswer { - val argument = it.getArgument(0) - dev.usbharu.hideout.domain.model.hideout.entity.Post( - 123L, - argument.userId, - null, - argument.text, - Instant.now().toEpochMilli(), - Visibility.PUBLIC, - "https://example.com" - ) - } - } - application { - authentication { - - bearer(TOKEN_AUTH) { - authenticate { - println("aaaaaaaaaaaa") - JWTPrincipal(payload) - } - } - } - routing { - route("/api/internal/v1") { - posts(postService) - } - } - configureSerialization() - } - - val post = Post("test") - client.post("/api/internal/v1/posts") { - header("Authorization", "Bearer asdkaf") - contentType(ContentType.Application.Json) - setBody(Config.configData.objectMapper.writeValueAsString(post)) - }.apply { - assertEquals(HttpStatusCode.OK, status) - assertEquals("https://example.com", headers["Location"]) - } - argumentCaptor { - verify(postService).create(capture()) - assertEquals(PostCreateDto("test", userId = 1234), firstValue) - } - } -} diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsTest.kt new file mode 100644 index 00000000..901acc89 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/PostsTest.kt @@ -0,0 +1,619 @@ +package dev.usbharu.hideout.routing.api.internal.v1 + +import com.auth0.jwt.interfaces.Claim +import com.auth0.jwt.interfaces.Payload +import com.fasterxml.jackson.module.kotlin.readValue +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.hideout.dto.PostCreateDto +import dev.usbharu.hideout.domain.model.hideout.entity.Post +import dev.usbharu.hideout.domain.model.hideout.entity.Visibility +import dev.usbharu.hideout.plugins.TOKEN_AUTH +import dev.usbharu.hideout.plugins.configureSecurity +import dev.usbharu.hideout.plugins.configureSerialization +import dev.usbharu.hideout.service.IPostService +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import io.ktor.server.config.* +import io.ktor.server.routing.* +import io.ktor.server.testing.* +import org.junit.jupiter.api.Test +import org.mockito.kotlin.* +import utils.JsonObjectMapper +import java.time.Instant +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class PostsTest { + + @Test + fun 認証情報無しでpostsにGETしたらPUBLICな投稿一覧が返ってくる() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val posts = listOf( + Post( + id = 12345, + userId = 4321, + text = "test1", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/1" + ), + Post( + id = 123456, + userId = 4322, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + ) + val postService = mock { + onBlocking { + findAll( + since = anyOrNull(), + until = anyOrNull(), + minId = anyOrNull(), + maxId = anyOrNull(), + limit = anyOrNull(), + userId = isNull() + ) + } doReturn posts + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/posts").apply { + assertEquals(HttpStatusCode.OK, status) + assertContentEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun 認証情報ありでpostsにGETすると権限のある投稿が返ってくる() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val claim = mock { + on { asLong() } doReturn 1234 + } + val payload = mock { + on { getClaim(eq("uid")) } doReturn claim + } + + val posts = listOf( + Post( + id = 12345, + userId = 4321, + text = "test1", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/1" + ), + Post( + id = 123456, + userId = 4322, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ), + Post( + id = 1234567, + userId = 4333, + text = "Followers only", + visibility = Visibility.FOLLOWERS, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/3" + ) + ) + + val postService = mock { + onBlocking { + findAll( + since = anyOrNull(), + until = anyOrNull(), + minId = anyOrNull(), + maxId = anyOrNull(), + limit = anyOrNull(), + userId = isNotNull() + ) + } doReturn posts + } + application { + authentication { + bearer(TOKEN_AUTH) { + authenticate { + JWTPrincipal(payload) + } + } + } + configureSerialization() + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + client.get("/api/internal/v1/posts") { + header("Authorization", "Bearer asdkaf") + }.apply { + assertEquals(HttpStatusCode.OK, status) + } + } + + @Test + fun `posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val post = Post( + 12345, 1234, text = "aaa", visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" + ) + val postService = mock { + onBlocking { findByIdForUser(any(), anyOrNull()) } doReturn post + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + client.get("/api/internal/v1/posts/1").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `認証情報ありでposts id にGETしたら権限のある投稿を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val post = Post( + 12345, 1234, text = "aaa", visibility = Visibility.FOLLOWERS, + createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" + ) + val postService = mock { + onBlocking { findByIdForUser(any(), isNotNull()) } doReturn post + } + val claim = mock { + on { asLong() } doReturn 1234 + } + val payload = mock { + on { getClaim(eq("uid")) } doReturn claim + } + application { + configureSerialization() + authentication { + bearer(TOKEN_AUTH) { + authenticate { + JWTPrincipal(payload) + } + } + } + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + client.get("/api/internal/v1/posts/1") { + header("Authorization", "Bearer asdkaf") + }.apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `posts-post postsにpostしたら投稿できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val claim = mock { + on { asLong() } doReturn 1234 + } + val payload = mock { + on { getClaim(eq("uid")) } doReturn claim + } + val postService = mock { + onBlocking { create(any()) } doAnswer { + val argument = it.getArgument(0) + Post( + 123L, + argument.userId, + null, + argument.text, + Instant.now().toEpochMilli(), + Visibility.PUBLIC, + "https://example.com" + ) + } + } + application { + authentication { + + bearer(TOKEN_AUTH) { + authenticate { + println("aaaaaaaaaaaa") + JWTPrincipal(payload) + } + } + } + routing { + route("/api/internal/v1") { + posts(postService) + } + } + configureSerialization() + } + + val post = dev.usbharu.hideout.domain.model.hideout.form.Post("test") + client.post("/api/internal/v1/posts") { + header("Authorization", "Bearer asdkaf") + contentType(ContentType.Application.Json) + setBody(Config.configData.objectMapper.writeValueAsString(post)) + }.apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals("https://example.com", headers["Location"]) + } + argumentCaptor { + verify(postService).create(capture()) + assertEquals(PostCreateDto("test", userId = 1234), firstValue) + } + } + + @Test + fun `users userId postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val posts = listOf( + Post( + id = 12345, + userId = 1, + text = "test1", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/1" + ), + Post( + id = 123456, + userId = 1, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + ) + val postService = mock { + onBlocking { + findByUserIdForUser( + userId = any(), + since = anyOrNull(), + until = anyOrNull(), + minId = anyOrNull(), + maxId = anyOrNull(), + limit = anyOrNull(), + forUserId = anyOrNull() + ) + } doReturn posts + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/users/1/posts").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `users username postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val posts = listOf( + Post( + id = 12345, + userId = 1, + text = "test1", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/1" + ), + Post( + id = 123456, + userId = 1, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + ) + val postService = mock { + onBlocking { + findByUserNameAndDomainForUser( + userName = eq("test1"), + domain = eq(Config.configData.domain), + since = anyOrNull(), + until = anyOrNull(), + minId = anyOrNull(), + maxId = anyOrNull(), + limit = anyOrNull(), + forUserId = anyOrNull() + ) + } doReturn posts + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/users/test1/posts").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `users username@domain postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val posts = listOf( + Post( + id = 12345, + userId = 1, + text = "test1", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/1" + ), + Post( + id = 123456, + userId = 1, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + ) + val postService = mock { + onBlocking { + findByUserNameAndDomainForUser( + userName = eq("test1"), + domain = eq("example.com"), + since = anyOrNull(), + until = anyOrNull(), + minId = anyOrNull(), + maxId = anyOrNull(), + limit = anyOrNull(), + forUserId = anyOrNull() + ) + } doReturn posts + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/users/test1@example.com/posts").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `users @username@domain postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val posts = listOf( + Post( + id = 12345, + userId = 1, + text = "test1", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/1" + ), + Post( + id = 123456, + userId = 1, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + ) + val postService = mock { + onBlocking { + findByUserNameAndDomainForUser( + userName = eq("test1"), + domain = eq("example.com"), + since = anyOrNull(), + until = anyOrNull(), + minId = anyOrNull(), + maxId = anyOrNull(), + limit = anyOrNull(), + forUserId = anyOrNull() + ) + } doReturn posts + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/users/@test1@example.com/posts").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(posts, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `users name posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val post = Post( + id = 123456, + userId = 1, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + val postService = mock { + onBlocking { findByIdForUser(eq(12345L), anyOrNull()) } doReturn post + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/users/test/posts/12345").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `users id posts id にGETしたらPUBLICな投稿を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val post = Post( + id = 123456, + userId = 1, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + val postService = mock { + onBlocking { findByIdForUser(eq(12345L), anyOrNull()) } doReturn post + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/users/1/posts/12345").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `users name posts id にGETしたらUserIdが間違っててもPUBLICな投稿を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val post = Post( + id = 123456, + userId = 1, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + val postService = mock { + onBlocking { findByIdForUser(eq(12345L), anyOrNull()) } doReturn post + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/users/423827849732847/posts/12345").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `users name posts id にGETしたらuserNameが間違っててもPUBLICな投稿を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val post = Post( + id = 123456, + userId = 1, + text = "test2", + visibility = Visibility.PUBLIC, + createdAt = Instant.now().toEpochMilli(), + url = "https://example.com/posts/2" + ) + val postService = mock { + onBlocking { findByIdForUser(eq(12345L), anyOrNull()) } doReturn post + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + posts(postService) + } + } + } + + client.get("/api/internal/v1/users/invalidUserName/posts/12345").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(post, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } +} diff --git a/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt new file mode 100644 index 00000000..7e411c49 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/routing/api/internal/v1/UsersTest.kt @@ -0,0 +1,142 @@ +package dev.usbharu.hideout.routing.api.internal.v1 + +import com.fasterxml.jackson.module.kotlin.readValue +import dev.usbharu.hideout.config.Config +import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse +import dev.usbharu.hideout.domain.model.hideout.entity.User +import dev.usbharu.hideout.domain.model.hideout.form.UserCreate +import dev.usbharu.hideout.plugins.configureSecurity +import dev.usbharu.hideout.plugins.configureSerialization +import dev.usbharu.hideout.service.impl.IUserService +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.server.config.* +import io.ktor.server.routing.* +import io.ktor.server.testing.* +import org.junit.jupiter.api.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import utils.JsonObjectMapper +import java.time.Instant +import kotlin.test.assertEquals + +class UsersTest { + @Test + fun `users にGETするとユーザー一覧を取得できる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + + val users = listOf( + UserResponse( + 12345, + "test1", + "example.com", + "test", + "", + "https://example.com/test", + Instant.now().toEpochMilli() + ), + UserResponse( + 12343, + "tes2", + "example.com", + "test", + "", + "https://example.com/tes2", + Instant.now().toEpochMilli() + ), + ) + val userService = mock { + onBlocking { findAllForUser(anyOrNull(), anyOrNull()) } doReturn users + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + users(userService) + } + } + } + client.get("/api/internal/v1/users").apply { + assertEquals(HttpStatusCode.OK, status) + assertEquals(users, JsonObjectMapper.objectMapper.readValue(bodyAsText())) + } + } + + @Test + fun `users にPOSTすると新規ユーザー作成ができる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val userCreateDto = UserCreate("test", "XXXXXXX") + val userService = mock { + onBlocking { usernameAlreadyUse(any()) } doReturn false + onBlocking { createLocalUser(any()) } doReturn User( + id = 12345, + name = "test", + domain = "example.com", + screenName = "testUser", + description = "test user", + password = "XXXXXXX", + inbox = "https://example.com/inbox", + outbox = "https://example.com/outbox", + url = "https://example.com", + publicKey = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----", + privateKey = "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----", + createdAt = Instant.now() + ) + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + users(userService) + } + } + } + + client.post("/api/internal/v1/users") { + contentType(ContentType.Application.Json) + setBody(JsonObjectMapper.objectMapper.writeValueAsString(userCreateDto)) + }.apply { + assertEquals(HttpStatusCode.Created, status) + assertEquals( + "${Config.configData.url}/api/internal/v1/users/${userCreateDto.username}", + headers["Location"] + ) + } + } + + @Test + fun `users 既にユーザー名が使用されているときはBadRequestが帰ってくる`() = testApplication { + environment { + config = ApplicationConfig("empty.conf") + } + val userCreateDto = UserCreate("test", "XXXXXXX") + val userService = mock { + onBlocking { usernameAlreadyUse(any()) } doReturn true + } + application { + configureSerialization() + configureSecurity(mock(), mock(), mock(), mock(), mock()) + routing { + route("/api/internal/v1") { + users(userService) + } + } + } + + client.post("/api/internal/v1/users") { + contentType(ContentType.Application.Json) + setBody(JsonObjectMapper.objectMapper.writeValueAsString(userCreateDto)) + }.apply { + assertEquals(HttpStatusCode.BadRequest, status) + } + } +}