refactor: Ktorに関連するテストを削除

This commit is contained in:
usbharu 2023-09-22 23:59:42 +09:00
parent 9b68f1941f
commit b8e4d2dc3b
6 changed files with 0 additions and 2439 deletions

View File

@ -1,573 +0,0 @@
package dev.usbharu.hideout.plugins
import com.auth0.jwk.Jwk
import com.auth0.jwk.JwkProvider
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.config.ConfigData
import dev.usbharu.hideout.domain.model.hideout.dto.JwtToken
import dev.usbharu.hideout.domain.model.hideout.entity.Jwt
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.hideout.form.RefreshToken
import dev.usbharu.hideout.domain.model.hideout.form.UserLogin
import dev.usbharu.hideout.exception.InvalidRefreshTokenException
import dev.usbharu.hideout.exception.InvalidUsernameOrPasswordException
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.routing.api.internal.v1.auth
import dev.usbharu.hideout.service.api.UserAuthApiService
import dev.usbharu.hideout.service.auth.JwtService
import dev.usbharu.hideout.service.core.MetaService
import dev.usbharu.hideout.service.user.UserAuthService
import dev.usbharu.hideout.util.Base64Util
import dev.usbharu.hideout.util.JsonWebKeyUtil
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.ArgumentMatchers.anyString
import org.mockito.kotlin.*
import java.security.KeyPairGenerator
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.*
import kotlin.test.assertEquals
class SecurityKtTest {
@Test
fun `login ログイン出来るか`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
val jwtToken = JwtToken("Token", "RefreshToken")
val userAuthService = mock<UserAuthApiService> {
onBlocking { login(eq("testUser"), eq("password")) } doReturn jwtToken
}
val metaService = mock<MetaService>()
val userQueryService = mock<UserQueryService> {
onBlocking { findByNameAndDomain(eq("testUser"), eq("example.com")) } doReturn User.of(
id = 1L,
name = "testUser",
domain = "example.com",
screenName = "test",
description = "",
password = "hashedPassword",
inbox = "https://example.com/inbox",
outbox = "https://example.com/outbox",
url = "https://example.com/profile",
publicKey = "",
privateKey = "",
createdAt = Instant.now()
)
}
val jwkProvider = mock<JwkProvider>()
application {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(userAuthService)
}
}
client.post("/login") {
contentType(ContentType.Application.Json)
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("testUser", "password")))
}.apply {
assertEquals(HttpStatusCode.OK, call.response.status)
assertEquals(jwtToken, Config.configData.objectMapper.readValue(call.response.bodyAsText()))
}
}
@Test
fun `login 存在しないユーザーのログインに失敗する`() {
testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
mock<UserAuthService> {
onBlocking { verifyAccount(anyString(), anyString()) }.doReturn(false)
}
val metaService = mock<MetaService>()
mock<UserQueryService>()
mock<JwtService>()
val jwkProvider = mock<JwkProvider>()
val userAuthApiService = mock<UserAuthApiService> {
onBlocking { login(anyString(), anyString()) } doThrow InvalidUsernameOrPasswordException()
}
application {
configureStatusPages()
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(userAuthApiService)
}
}
client.post("/login") {
contentType(ContentType.Application.Json)
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("InvalidTtestUser", "password")))
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
}
@Test
fun `login 不正なパスワードのログインに失敗する`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
val metaService = mock<MetaService>()
val jwkProvider = mock<JwkProvider>()
val userAuthApiService = mock<UserAuthApiService> {
onBlocking { login(anyString(), eq("InvalidPassword")) } doThrow InvalidUsernameOrPasswordException()
}
application {
configureStatusPages()
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(userAuthApiService)
}
}
client.post("/login") {
contentType(ContentType.Application.Json)
setBody(Config.configData.objectMapper.writeValueAsString(UserLogin("TestUser", "InvalidPassword")))
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check Authorizedヘッダーが無いと401が帰ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock())
}
}
client.get("/auth-check").apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check Authorizedヘッダーの形式が間違っていると401が帰ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock())
}
}
client.get("/auth-check") {
header("Authorization", "Digest dsfjjhogalkjdfmlhaog")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check Authorizedヘッダーが空だと401が帰ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock())
}
}
client.get("/auth-check") {
header("Authorization", "")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check AuthorizedヘッダーがBeararで空だと401が帰ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
Config.configData = ConfigData(url = "http://example.com", objectMapper = jacksonObjectMapper())
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(mock())
}
}
client.get("/auth-check") {
header("Authorization", "Bearer ")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check 正当なJWTだとアクセスできる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer(Config.configData.url)
.withKeyId(kid.toString())
.withClaim("uid", 123456L)
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<MetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
application {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock())
}
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.OK, call.response.status)
assertEquals("Hello 123456", call.response.bodyAsText())
}
}
@Test
fun `auth-check 期限切れのトークンではアクセスできない`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer(Config.configData.url)
.withKeyId(kid.toString())
.withClaim("uid", 123345L)
.withExpiresAt(now.minus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<MetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
application {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock())
}
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check issuerが間違っているとアクセスできない`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer("https://example.com")
.withKeyId(kid.toString())
.withClaim("uid", 12345L)
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<MetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
application {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock())
}
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check usernameが空だと失敗する`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer(Config.configData.url)
.withKeyId(kid.toString())
.withClaim("uid", null as Long?)
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<MetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
application {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock())
}
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `auth-check usernameが存在しないと失敗する`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(2048)
val keyPair = keyPairGenerator.generateKeyPair()
val rsaPublicKey = keyPair.public as RSAPublicKey
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
val now = Instant.now()
val kid = UUID.randomUUID()
val token = JWT.create()
.withAudience("${Config.configData.url}/users/test")
.withIssuer(Config.configData.url)
.withKeyId(kid.toString())
.withExpiresAt(now.plus(30, ChronoUnit.MINUTES))
.sign(Algorithm.RSA256(rsaPublicKey, keyPair.private as RSAPrivateKey))
val metaService = mock<MetaService> {
onBlocking { getJwtMeta() }.doReturn(
Jwt(
kid,
Base64Util.encode(keyPair.private.encoded),
Base64Util.encode(rsaPublicKey.encoded)
)
)
}
val readValue = Config.configData.objectMapper.readerFor(Map::class.java)
.readValue<MutableMap<String, Any>?>(
JsonWebKeyUtil.publicKeyToJwk(
rsaPublicKey,
kid.toString()
)
)
val jwkProvider = mock<JwkProvider> {
onBlocking { get(anyString()) }.doReturn(
Jwk.fromValues(
(readValue["keys"] as List<Map<String, Any>>)[0]
)
)
}
application {
configureSerialization()
configureSecurity(jwkProvider, metaService)
routing {
auth(mock())
}
}
client.get("/auth-check") {
header("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.Unauthorized, call.response.status)
}
}
@Test
fun `refresh-token リフレッシュトークンが正当だとトークンを発行する`() = testApplication {
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
environment {
config = ApplicationConfig("empty.conf")
}
val jwtService = mock<UserAuthApiService> {
onBlocking { refreshToken(any()) }.doReturn(JwtToken("token", "refreshToken2"))
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(jwtService)
}
}
client.post("/refresh-token") {
header("Content-Type", "application/json")
setBody(Config.configData.objectMapper.writeValueAsString(RefreshToken("refreshToken")))
}.apply {
assertEquals(HttpStatusCode.OK, call.response.status)
}
}
@Test
fun `refresh-token リフレッシュトークンが不正だと失敗する`() = testApplication {
Config.configData = ConfigData(url = "https://localhost", objectMapper = jacksonObjectMapper())
environment {
config = ApplicationConfig("empty.conf")
}
val jwtService = mock<UserAuthApiService> {
onBlocking { refreshToken(any()) } doThrow InvalidRefreshTokenException("Invalid Refresh Token")
}
application {
configureStatusPages()
configureSerialization()
configureSecurity(mock(), mock())
routing {
auth(jwtService)
}
}
client.post("/refresh-token") {
header("Content-Type", "application/json")
setBody(Config.configData.objectMapper.writeValueAsString(RefreshToken("InvalidRefreshToken")))
}.apply {
assertEquals(HttpStatusCode.BadRequest, call.response.status)
}
}
}

View File

@ -1,134 +0,0 @@
package dev.usbharu.hideout.routing.activitypub
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.config.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class ContentTypeRouteSelectorTest {
@Test
fun `Content-Typeが一つでマッチする`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
application {
routing {
route("/test") {
createChild(ContentTypeRouteSelector(ContentType.Application.Json)).handle {
call.respondText("OK")
}
get {
call.respondText("NG")
}
}
}
}
client.get("/test") {
accept(ContentType.Text.Html)
}.apply {
assertEquals("NG", bodyAsText())
}
client.get("/test") {
accept(ContentType.Application.Json)
}.apply {
assertEquals("OK", bodyAsText())
}
}
@Test
fun `Content-Typeが一つのとき違うとマッチしない`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
application {
routing {
route("/test") {
createChild(ContentTypeRouteSelector(ContentType.Application.Json)).handle {
call.respondText("OK")
}
get {
call.respondText("NG")
}
}
}
}
client.get("/test") {
accept(ContentType.Text.Html)
}.apply {
assertEquals("NG", bodyAsText())
}
}
@Test
fun `Content-Typeがからのときマッチしない`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
application {
routing {
route("/test") {
createChild(ContentTypeRouteSelector()).handle {
call.respondText("OK")
}
get {
call.respondText("NG")
}
}
}
}
client.get("/test") {
accept(ContentType.Text.Html)
}.apply {
assertEquals("NG", bodyAsText())
}
client.get("/test").apply {
assertEquals("NG", bodyAsText())
}
}
@Test
fun `Content-Typeが複数指定されていてマッチする`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
application {
routing {
route("/test") {
createChild(ContentTypeRouteSelector(ContentType.Application.Json, ContentType.Text.Html)).handle {
call.respondText("OK")
}
get {
call.respondText("NG")
}
}
}
}
client.get("/test") {
accept(ContentType.Text.Html)
}.apply {
assertEquals("OK", bodyAsText())
}
client.get("/test") {
accept(ContentType.Application.Json)
}.apply {
assertEquals("OK", bodyAsText())
}
client.get("/test") {
accept(ContentType.Application.Xml)
}.apply {
assertEquals("NG", bodyAsText())
}
}
}

View File

@ -1,104 +0,0 @@
package dev.usbharu.hideout.routing.activitypub
import dev.usbharu.hideout.exception.JsonParseException
import dev.usbharu.hideout.plugins.configureSerialization
import dev.usbharu.hideout.plugins.configureStatusPages
import dev.usbharu.hideout.service.ap.APService
import dev.usbharu.hideout.service.ap.APUserService
import dev.usbharu.hideout.service.auth.HttpSignatureVerifyService
import dev.usbharu.hideout.service.user.UserService
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.server.config.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.mock
class InboxRoutingKtTest {
@Test
fun `sharedInboxにGETしたら405が帰ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
application {
configureSerialization()
routing {
inbox(mock(), mock())
}
}
client.get("/inbox").let {
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
}
}
@Test
fun `sharedInboxに空のリクエストボディでPOSTしたら400が帰ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {
on { verify(any()) } doReturn true
}
val apService = mock<APService> {
on { parseActivity(any()) } doThrow JsonParseException()
}
mock<UserService>()
mock<APUserService>()
application {
configureStatusPages()
configureSerialization()
routing {
inbox(httpSignatureVerifyService, apService)
}
}
client.post("/inbox").let {
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
}
}
@Test
fun `ユーザのinboxにGETしたら405が帰ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
application {
configureSerialization()
routing {
inbox(mock(), mock())
}
}
client.get("/users/test/inbox").let {
Assertions.assertEquals(HttpStatusCode.MethodNotAllowed, it.status)
}
}
@Test
fun `ユーザーのinboxに空のリクエストボディでPOSTしたら400が帰ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val httpSignatureVerifyService = mock<HttpSignatureVerifyService> {
on { verify(any()) } doReturn true
}
val apService = mock<APService> {
on { parseActivity(any()) } doThrow JsonParseException()
}
mock<UserService>()
mock<APUserService>()
application {
configureStatusPages()
configureSerialization()
routing {
inbox(httpSignatureVerifyService, apService)
}
}
client.post("/users/test/inbox").let {
Assertions.assertEquals(HttpStatusCode.BadRequest, it.status)
}
}
}

View File

@ -1,202 +0,0 @@
package dev.usbharu.hideout.routing.activitypub
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonSetter
import com.fasterxml.jackson.annotation.Nulls
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import dev.usbharu.hideout.domain.model.ap.Image
import dev.usbharu.hideout.domain.model.ap.Key
import dev.usbharu.hideout.domain.model.ap.Person
import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.plugins.configureSerialization
import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.service.ap.APUserService
import dev.usbharu.hideout.util.HttpUtil.Activity
import dev.usbharu.hideout.util.HttpUtil.JsonLd
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.ArgumentMatchers.anyString
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import utils.TestTransaction
import java.time.Instant
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class UsersAPTest {
@Test()
fun `ユーザのURLにAcceptヘッダーをActivityにしてアクセスしたときPersonが返ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val person = Person(
type = emptyList(),
name = "test",
id = "http://example.com/users/test",
preferredUsername = "test",
summary = "test user",
inbox = "http://example.com/users/test/inbox",
outbox = "http://example.com/users/test/outbox",
url = "http://example.com/users/test",
icon = Image(
type = emptyList(),
name = "http://example.com/users/test/icon.png",
mediaType = "image/png",
url = "http://example.com/users/test/icon.png"
),
publicKey = Key(
type = emptyList(),
name = "Public Key",
id = "http://example.com/users/test#pubkey",
owner = "https://example.com/users/test",
publicKeyPem = "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----"
)
)
person.context = listOf("https://www.w3.org/ns/activitystreams")
val apUserService = mock<APUserService> {
onBlocking { getPersonByName(anyString()) } doReturn person
}
application {
configureSerialization()
routing {
usersAP(apUserService, mock(), mock(), TestTransaction)
}
}
client.get("/users/test") {
accept(ContentType.Application.Activity)
}.let {
val objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper.configOverride(List::class.java).setSetterInfo(
JsonSetter.Value.forValueNulls(
Nulls.AS_EMPTY
)
)
val actual = it.bodyAsText()
val readValue = objectMapper.readValue<Person>(actual)
assertEquals(person, readValue)
}
}
// @Disabled
@Test()
fun `ユーザのURLにAcceptヘッダーをActivityとJson-LDにしてアクセスしたときPersonが返ってくる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val person = Person(
type = emptyList(),
name = "test",
id = "http://example.com/users/test",
preferredUsername = "test",
summary = "test user",
inbox = "http://example.com/users/test/inbox",
outbox = "http://example.com/users/test/outbox",
url = "http://example.com/users/test",
icon = Image(
type = emptyList(),
name = "http://example.com/users/test/icon.png",
mediaType = "image/png",
url = "http://example.com/users/test/icon.png"
),
publicKey = Key(
type = emptyList(),
name = "Public Key",
id = "http://example.com/users/test#pubkey",
owner = "https://example.com/users/test",
publicKeyPem = "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----"
)
)
person.context = listOf("https://www.w3.org/ns/activitystreams")
val apUserService = mock<APUserService> {
onBlocking { getPersonByName(anyString()) } doReturn person
}
application {
configureSerialization()
routing {
usersAP(apUserService, mock(), mock(), TestTransaction)
}
}
client.get("/users/test") {
accept(ContentType.Application.JsonLd)
accept(ContentType.Application.Activity)
}.let {
val objectMapper = jacksonObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper.configOverride(List::class.java).setSetterInfo(
JsonSetter.Value.forValueNulls(
Nulls.AS_EMPTY
)
)
val actual = it.bodyAsText()
val readValue = objectMapper.readValue<Person>(actual)
assertEquals(person, readValue)
}
}
// @Disabled
@Test
fun contentType_Test() {
assertTrue(ContentType.Application.Activity.match("application/activity+json"))
val listOf = listOf(ContentType.Application.JsonLd, ContentType.Application.Activity)
assertTrue(
listOf.find { contentType ->
contentType.match("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")
}.let { it != null }
)
assertTrue(
ContentType.Application.JsonLd.match(
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
)
)
}
@Test
fun ユーザーのURLにAcceptヘッダーをhtmlにしてアクセスしたときはただの文字を返す() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val userService = mock<UserQueryService> {
onBlocking { findByNameAndDomain(eq("test"), anyString()) } doReturn User.of(
1L,
"test",
"example.com",
"test",
"",
"hashedPassword",
"https://example.com/inbox",
"https://example.com/outbox",
"https://example.com",
"-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
"-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----",
Instant.now()
)
}
application {
routing {
usersAP(mock(), userService, mock(), TestTransaction)
}
}
client.get("/users/test") {
accept(ContentType.Text.Html)
}.let {
assertEquals(HttpStatusCode.OK, it.status)
assertTrue(it.contentType()?.match(ContentType.Text.Plain) == true)
}
}
}

View File

@ -1,734 +0,0 @@
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.PostResponse
import dev.usbharu.hideout.domain.model.hideout.dto.UserResponse
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.api.PostApiService
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 user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
)
val posts = listOf(
PostResponse(
id = "12345",
user = user,
text = "test1",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
),
PostResponse(
id = "123456",
user = user,
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
)
val postService = mock<PostApiService> {
onBlocking {
getAll(
since = anyOrNull(),
until = anyOrNull(),
minId = anyOrNull(),
maxId = anyOrNull(),
limit = anyOrNull(),
userId = isNull()
)
} doReturn posts
}
application {
configureSerialization()
configureSecurity(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<Claim> {
on { asLong() } doReturn 1234
}
val payload = mock<Payload> {
on { getClaim(eq("uid")) } doReturn claim
}
val user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
)
val posts = listOf(
PostResponse(
id = "12345",
user = user,
text = "test1",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
),
PostResponse(
id = "123456",
user = user,
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
),
PostResponse(
id = "1234567",
user = user,
text = "Followers only",
visibility = Visibility.FOLLOWERS,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/3"
)
)
val postService = mock<PostApiService> {
onBlocking {
getAll(
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 user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
)
val post = PostResponse(
id = "12345",
user = user,
text = "aaa",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
)
val postService = mock<PostApiService> {
onBlocking { getById(any(), anyOrNull()) } doReturn post
}
application {
configureSerialization()
configureSecurity(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 = PostResponse(
"12345",
UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
),
text = "aaa",
visibility = Visibility.FOLLOWERS,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
)
val postService = mock<PostApiService> {
onBlocking { getById(any(), isNotNull()) } doReturn post
}
val claim = mock<Claim> {
on { asLong() } doReturn 1234
}
val payload = mock<Payload> {
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<Claim> {
on { asLong() } doReturn 1234
}
val payload = mock<Payload> {
on { getClaim(eq("uid")) } doReturn claim
}
val postService = mock<PostApiService> {
onBlocking { createPost(any(), any()) } doAnswer {
val argument = it.getArgument<dev.usbharu.hideout.domain.model.hideout.form.Post>(0)
val userId = it.getArgument<Long>(1)
PostResponse(
id = "123",
user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
),
overview = null,
text = argument.text,
createdAt = Instant.now().toEpochMilli(),
visibility = Visibility.PUBLIC,
url = "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<dev.usbharu.hideout.domain.model.hideout.form.Post> {
verify(postService).createPost(capture(), any())
assertEquals(dev.usbharu.hideout.domain.model.hideout.form.Post("test"), firstValue)
}
}
@Test
fun `users userId postsにGETしたらユーザーのPUBLICな投稿一覧を取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
)
val posts = listOf(
PostResponse(
id = "12345",
user = user,
text = "test1",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
),
PostResponse(
id = "123456",
user = user,
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
)
val postService = mock<PostApiService> {
onBlocking {
getByUser(
nameOrId = any(),
since = anyOrNull(),
until = anyOrNull(),
minId = anyOrNull(),
maxId = anyOrNull(),
limit = anyOrNull(),
userId = anyOrNull()
)
} doReturn posts
}
application {
configureSerialization()
configureSecurity(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 user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
)
val posts = listOf(
PostResponse(
id = "12345",
user = user,
text = "test1",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
),
PostResponse(
id = "123456",
user = user,
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
)
val postService = mock<PostApiService> {
onBlocking {
getByUser(
nameOrId = eq("test1"),
since = anyOrNull(),
until = anyOrNull(),
minId = anyOrNull(),
maxId = anyOrNull(),
limit = anyOrNull(),
userId = anyOrNull()
)
} doReturn posts
}
application {
configureSerialization()
configureSecurity(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 user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
)
val posts = listOf(
PostResponse(
id = "12345",
user = user,
text = "test1",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
),
PostResponse(
id = "123456",
user = user,
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
)
val postService = mock<PostApiService> {
onBlocking {
getByUser(
nameOrId = eq("test1@example.com"),
since = anyOrNull(),
until = anyOrNull(),
minId = anyOrNull(),
maxId = anyOrNull(),
limit = anyOrNull(),
userId = anyOrNull()
)
} doReturn posts
}
application {
configureSerialization()
configureSecurity(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 user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
)
val posts = listOf(
PostResponse(
id = "12345",
user = user,
text = "test1",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
),
PostResponse(
id = "123456",
user = user,
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
)
val postService = mock<PostApiService> {
onBlocking {
getByUser(
nameOrId = eq("@test1@example.com"),
since = anyOrNull(),
until = anyOrNull(),
minId = anyOrNull(),
maxId = anyOrNull(),
limit = anyOrNull(),
userId = anyOrNull()
)
} doReturn posts
}
application {
configureSerialization()
configureSecurity(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 = PostResponse(
id = "123456",
user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
),
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
val postService = mock<PostApiService> {
onBlocking { getById(eq(12345L), anyOrNull()) } doReturn post
}
application {
configureSerialization()
configureSecurity(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 = PostResponse(
id = "123456",
user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
),
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
val postService = mock<PostApiService> {
onBlocking { getById(eq(12345L), anyOrNull()) } doReturn post
}
application {
configureSerialization()
configureSecurity(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 = PostResponse(
id = "123456",
user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
),
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
val postService = mock<PostApiService> {
onBlocking { getById(eq(12345L), anyOrNull()) } doReturn post
}
application {
configureSerialization()
configureSecurity(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 = PostResponse(
id = "123456",
user = UserResponse(
id = "54321",
name = "user1",
domain = "example.com",
screenName = "user 1",
description = "Test user",
url = "https://example.com/users/54321",
createdAt = Instant.now().toEpochMilli()
),
text = "test2",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/2"
)
val postService = mock<PostApiService> {
onBlocking { getById(eq(12345L), anyOrNull()) } doReturn post
}
application {
configureSerialization()
configureSecurity(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()))
}
}
}

View File

@ -1,692 +0,0 @@
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.Acct
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.TOKEN_AUTH
import dev.usbharu.hideout.plugins.configureSecurity
import dev.usbharu.hideout.plugins.configureSerialization
import dev.usbharu.hideout.service.api.UserApiService
import dev.usbharu.hideout.service.user.UserService
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.assertEquals
@Suppress("LargeClass")
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<UserApiService> {
onBlocking { findAll(anyOrNull(), anyOrNull()) } doReturn users
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), 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<UserService> {
onBlocking { usernameAlreadyUse(any()) } doReturn false
onBlocking { createLocalUser(any()) } doReturn User.of(
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())
routing {
route("/api/internal/v1") {
users(userService, mock())
}
}
}
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<UserService> {
onBlocking { usernameAlreadyUse(any()) } doReturn true
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(userService, mock())
}
}
}
client.post("/api/internal/v1/users") {
contentType(ContentType.Application.Json)
setBody(JsonObjectMapper.objectMapper.writeValueAsString(userCreateDto))
}.apply {
assertEquals(HttpStatusCode.BadRequest, status)
}
}
@Test
fun `users name にGETしたらユーザーを取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val userResponse = UserResponse(
"1234",
"test1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
val userApiService = mock<UserApiService> {
onBlocking { findByAcct(any()) } doReturn userResponse
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/test1").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users id にGETしたらユーザーを取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val userResponse = UserResponse(
"1234",
"test1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
val userApiService = mock<UserApiService> {
onBlocking { findById(any()) } doReturn userResponse
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/1234").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users name@domain にGETしたらユーザーを取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val userResponse = UserResponse(
"1234",
"test1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
val userApiService = mock<UserApiService> {
onBlocking { findByAcct(any()) } doReturn userResponse
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/test1").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users @name@domain にGETしたらユーザーを取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val userResponse = UserResponse(
"1234",
"test1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
val userApiService = mock<UserApiService> {
onBlocking { findByAcct(any()) } doReturn userResponse
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/test1").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(userResponse, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users name followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val followers = listOf(
UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
),
UserResponse(
"1236",
"follower2",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
)
val userApiService = mock<UserApiService> {
onBlocking { findFollowersByAcct(any()) } doReturn followers
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/test1/followers").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users name@domain followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val followers = listOf(
UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
),
UserResponse(
"1236",
"follower2",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
)
val userApiService = mock<UserApiService> {
onBlocking { findFollowersByAcct(any()) } doReturn followers
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/@test1@example.com/followers").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users id followers にGETしたらフォロワー一覧を取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val followers = listOf(
UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
),
UserResponse(
"1236",
"follower2",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
)
val userApiService = mock<UserApiService> {
onBlocking { findFollowers(any()) } doReturn followers
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/1234/followers").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users name followers に認証情報ありでGETしたらフォローできる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val claim = mock<Claim> {
on { asLong() } doReturn 1234
}
val payload = mock<Payload> {
on { getClaim(eq("uid")) } doReturn claim
}
val userApiService = mock<UserApiService> {
onBlocking { findByAcct(any()) } doReturn UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
onBlocking { follow(any<Acct>(), eq(1234)) } doReturn true
}
application {
configureSerialization()
authentication {
bearer(TOKEN_AUTH) {
authenticate {
JWTPrincipal(payload)
}
}
}
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.post("/api/internal/v1/users/test1/followers") {
header(HttpHeaders.Authorization, "Bearer test")
}.apply {
assertEquals(HttpStatusCode.OK, status)
}
}
@Test
fun `users name followers に認証情報ありでGETしたらフォロー処理受付になることもある`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val claim = mock<Claim> {
on { asLong() } doReturn 1234
}
val payload = mock<Payload> {
on { getClaim(eq("uid")) } doReturn claim
}
val userApiService = mock<UserApiService> {
onBlocking { findByAcct(any()) } doReturn UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
onBlocking { follow(any<Acct>(), eq(1234)) } doReturn false
}
application {
configureSerialization()
authentication {
bearer(TOKEN_AUTH) {
authenticate {
JWTPrincipal(payload)
}
}
}
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.post("/api/internal/v1/users/test1/followers") {
header(HttpHeaders.Authorization, "Bearer test")
}.apply {
assertEquals(HttpStatusCode.Accepted, status)
}
}
@Test
fun `users id followers に認証情報ありでGETしたらフォロー処理受付になることもある`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val claim = mock<Claim> {
on { asLong() } doReturn 1234
}
val payload = mock<Payload> {
on { getClaim(eq("uid")) } doReturn claim
}
val userApiService = mock<UserApiService> {
onBlocking { findById(any()) } doReturn UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
onBlocking { follow(eq(1235), eq(1234)) } doReturn false
}
application {
configureSerialization()
authentication {
bearer(TOKEN_AUTH) {
authenticate {
JWTPrincipal(payload)
}
}
}
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.post("/api/internal/v1/users/1235/followers") {
header(HttpHeaders.Authorization, "Bearer test")
}.apply {
assertEquals(HttpStatusCode.Accepted, status)
}
}
@Test
fun `users name following にGETしたらフォロイー一覧を取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val followers = listOf(
UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
),
UserResponse(
"1236",
"follower2",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
)
val userApiService = mock<UserApiService> {
onBlocking { findFollowingsByAcct(any()) } doReturn followers
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/test1/following").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users name@domain following にGETしたらフォロイー一覧を取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val followers = listOf(
UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
),
UserResponse(
"1236",
"follower2",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
)
val userApiService = mock<UserApiService> {
onBlocking { findFollowingsByAcct(any()) } doReturn followers
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/test1@domain/following").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
@Test
fun `users id following にGETしたらフォロイー一覧を取得できる`() = testApplication {
environment {
config = ApplicationConfig("empty.conf")
}
val followers = listOf(
UserResponse(
"1235",
"follower1",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
),
UserResponse(
"1236",
"follower2",
"example.com",
"test",
"test User",
"https://example.com/test",
Instant.now().toEpochMilli()
)
)
val userApiService = mock<UserApiService> {
onBlocking { findFollowings(any()) } doReturn followers
}
application {
configureSerialization()
configureSecurity(mock(), mock())
routing {
route("/api/internal/v1") {
users(mock(), userApiService)
}
}
}
client.get("/api/internal/v1/users/1234/following").apply {
assertEquals(HttpStatusCode.OK, status)
assertEquals(followers, JsonObjectMapper.objectMapper.readValue(bodyAsText()))
}
}
}