test: APNoteServiceImplのテストを追加

This commit is contained in:
usbharu 2023-10-30 11:57:18 +09:00
parent f2fe870888
commit 8e4a0d6584
1 changed files with 320 additions and 45 deletions

View File

@ -1,34 +1,58 @@
@file:OptIn(ExperimentalCoroutinesApi::class) @file:OptIn(ExperimentalCoroutinesApi::class) @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package dev.usbharu.hideout.service.ap package dev.usbharu.hideout.service.ap
import dev.usbharu.hideout.config.ApplicationConfig import dev.usbharu.hideout.config.ApplicationConfig
import dev.usbharu.hideout.config.CharacterLimit import dev.usbharu.hideout.config.CharacterLimit
import dev.usbharu.hideout.domain.model.ap.Image
import dev.usbharu.hideout.domain.model.ap.Key
import dev.usbharu.hideout.domain.model.ap.Note
import dev.usbharu.hideout.domain.model.ap.Person
import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Post
import dev.usbharu.hideout.domain.model.hideout.entity.User import dev.usbharu.hideout.domain.model.hideout.entity.User
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
import dev.usbharu.hideout.domain.model.job.DeliverPostJob import dev.usbharu.hideout.domain.model.job.DeliverPostJob
import dev.usbharu.hideout.exception.FailedToGetResourcesException
import dev.usbharu.hideout.exception.ap.FailedToGetActivityPubResourceException
import dev.usbharu.hideout.query.FollowerQueryService import dev.usbharu.hideout.query.FollowerQueryService
import dev.usbharu.hideout.query.MediaQueryService import dev.usbharu.hideout.query.MediaQueryService
import dev.usbharu.hideout.query.PostQueryService
import dev.usbharu.hideout.query.UserQueryService import dev.usbharu.hideout.query.UserQueryService
import dev.usbharu.hideout.repository.PostRepository
import dev.usbharu.hideout.service.ap.APNoteServiceImpl.Companion.public
import dev.usbharu.hideout.service.ap.job.ApNoteJobServiceImpl import dev.usbharu.hideout.service.ap.job.ApNoteJobServiceImpl
import dev.usbharu.hideout.service.ap.resource.APResourceResolveService
import dev.usbharu.hideout.service.core.TwitterSnowflakeIdGenerateService
import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.post.PostService
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.mock.* import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.utils.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.util.*
import io.ktor.util.date.*
import kjob.core.job.JobProps import kjob.core.job.JobProps
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
import org.mockito.kotlin.* import org.mockito.kotlin.*
import utils.JsonObjectMapper.objectMapper import utils.JsonObjectMapper.objectMapper
import utils.PostBuilder
import utils.TestTransaction import utils.TestTransaction
import utils.UserBuilder
import java.net.URL import java.net.URL
import java.time.Instant import java.time.Instant
import kotlin.test.assertEquals
class APNoteServiceImplTest { class APNoteServiceImplTest {
@ -58,8 +82,7 @@ class APNoteServiceImplTest {
publicKey = "", publicKey = "",
createdAt = Instant.now(), createdAt = Instant.now(),
keyId = "a" keyId = "a"
), ), userBuilder.of(
userBuilder.of(
3L, 3L,
"follower2", "follower2",
"follower2.example.com", "follower2.example.com",
@ -95,8 +118,7 @@ class APNoteServiceImplTest {
onBlocking { findFollowersById(eq(1L)) } doReturn followers onBlocking { findFollowersById(eq(1L)) } doReturn followers
} }
val jobQueueParentService = mock<JobQueueParentService>() val jobQueueParentService = mock<JobQueueParentService>()
val activityPubNoteService = val activityPubNoteService = APNoteServiceImpl(
APNoteServiceImpl(
jobQueueParentService = jobQueueParentService, jobQueueParentService = jobQueueParentService,
postRepository = mock(), postRepository = mock(),
apUserService = mock(), apUserService = mock(),
@ -110,13 +132,7 @@ class APNoteServiceImplTest {
postBuilder = postBuilder postBuilder = postBuilder
) )
val postEntity = postBuilder.of( val postEntity = postBuilder.of(
1L, 1L, 1L, null, "test text", 1L, Visibility.PUBLIC, "https://example.com"
1L,
null,
"test text",
1L,
Visibility.PUBLIC,
"https://example.com"
) )
activityPubNoteService.createNote(postEntity) activityPubNoteService.createNote(postEntity)
verify(jobQueueParentService, times(2)).schedule(eq(DeliverPostJob), any()) verify(jobQueueParentService, times(2)).schedule(eq(DeliverPostJob), any())
@ -124,18 +140,281 @@ class APNoteServiceImplTest {
} }
@Test @Test
fun `createPostJob 新しい投稿のJob`() { fun `fetchNote(String,String) ートが既に存在する場合はDBから取得したものを返す`() = runTest {
runTest { val url = "https://example.com/note"
val mediaQueryService = mock<MediaQueryService> { val post = PostBuilder.of()
onBlocking { findByPostId(anyLong()) } doReturn emptyList()
val postQueryService = mock<PostQueryService> {
onBlocking { findByUrl(eq(url)) } doReturn post
}
val user = UserBuilder.localUserOf(id = post.userId)
val userQueryService = mock<UserQueryService> {
onBlocking { findById(eq(post.userId)) } doReturn user
}
val apNoteServiceImpl = APNoteServiceImpl(
jobQueueParentService = mock(),
postRepository = mock(),
apUserService = mock(),
userQueryService = userQueryService,
followerQueryService = mock(),
postQueryService = postQueryService,
mediaQueryService = mock(),
objectMapper = objectMapper,
postService = mock(),
apResourceResolveService = mock(),
postBuilder = Post.PostBuilder(CharacterLimit())
)
val actual = apNoteServiceImpl.fetchNote(url)
val expected = Note(
name = "Post",
id = post.apId,
attributedTo = user.url,
content = post.text,
published = Instant.ofEpochMilli(post.createdAt).toString(),
to = listOfNotNull(public, user.followers),
sensitive = post.sensitive,
cc = listOfNotNull(public, user.followers),
inReplyTo = null
)
assertEquals(expected, actual)
} }
val httpClient = HttpClient( @Test
MockEngine { httpRequestData -> fun `fetchNote(String,String) ートがDBに存在しない場合リモートに取得しにいく`() = runTest {
assertEquals("https://follower.example.com/inbox", httpRequestData.url.toString()) val url = "https://example.com/note"
respondOk() val post = PostBuilder.of()
val postQueryService = mock<PostQueryService> {
onBlocking { findByUrl(eq(url)) } doThrow FailedToGetResourcesException()
onBlocking { findByApId(eq(post.apId)) } doReturn post
} }
val user = UserBuilder.localUserOf(id = post.userId)
val userQueryService = mock<UserQueryService> {
onBlocking { findById(eq(post.userId)) } doReturn user
}
val note = Note(
name = "Post",
id = post.apId,
attributedTo = user.url,
content = post.text,
published = Instant.ofEpochMilli(post.createdAt).toString(),
to = listOfNotNull(public, user.followers),
sensitive = post.sensitive,
cc = listOfNotNull(public, user.followers),
inReplyTo = null
) )
val apResourceResolveService = mock<APResourceResolveService> {
onBlocking { resolve<Note>(eq(url), any(), isNull<Long>()) } doReturn note
}
val apNoteServiceImpl = APNoteServiceImpl(
jobQueueParentService = mock(),
postRepository = mock(),
apUserService = mock(),
userQueryService = userQueryService,
followerQueryService = mock(),
postQueryService = postQueryService,
mediaQueryService = mock(),
objectMapper = objectMapper,
postService = mock(),
apResourceResolveService = apResourceResolveService,
postBuilder = Post.PostBuilder(CharacterLimit())
)
val actual = apNoteServiceImpl.fetchNote(url)
assertEquals(note, actual)
}
@OptIn(InternalAPI::class)
@Test
fun `fetchNote(String,String) ートをリモートから取得した際にエラーが返ってきたらFailedToGetActivityPubResourceExceptionがthrowされる`() =
runTest {
val url = "https://example.com/note"
val post = PostBuilder.of()
val postQueryService = mock<PostQueryService> {
onBlocking { findByUrl(eq(url)) } doThrow FailedToGetResourcesException()
onBlocking { findByApId(eq(post.apId)) } doReturn post
}
val user = UserBuilder.localUserOf(id = post.userId)
val userQueryService = mock<UserQueryService> {
onBlocking { findById(eq(post.userId)) } doReturn user
}
val note = Note(
name = "Post",
id = post.apId,
attributedTo = user.url,
content = post.text,
published = Instant.ofEpochMilli(post.createdAt).toString(),
to = listOfNotNull(public, user.followers),
sensitive = post.sensitive,
cc = listOfNotNull(public, user.followers),
inReplyTo = null
)
val apResourceResolveService = mock<APResourceResolveService> {
val responseData = HttpResponseData(
HttpStatusCode.BadRequest,
GMTDate(),
Headers.Empty,
HttpProtocolVersion.HTTP_1_1,
NullBody,
Dispatchers.IO
)
onBlocking { resolve<Note>(eq(url), any(), isNull<Long>()) } doThrow ClientRequestException(
DefaultHttpResponse(
HttpClientCall(
HttpClient(), HttpRequestData(
Url("http://example.com"),
HttpMethod.Get,
Headers.Empty,
EmptyContent,
Job(null),
Attributes()
), responseData
), responseData
), ""
)
}
val apNoteServiceImpl = APNoteServiceImpl(
jobQueueParentService = mock(),
postRepository = mock(),
apUserService = mock(),
userQueryService = userQueryService,
followerQueryService = mock(),
postQueryService = postQueryService,
mediaQueryService = mock(),
objectMapper = objectMapper,
postService = mock(),
apResourceResolveService = apResourceResolveService,
postBuilder = Post.PostBuilder(CharacterLimit())
)
assertThrows<FailedToGetActivityPubResourceException> { apNoteServiceImpl.fetchNote(url) }
}
@Test
fun `fetchNote(Note,String) DBに無いNoteは保存される`() = runTest {
val user = UserBuilder.localUserOf()
val generateId = TwitterSnowflakeIdGenerateService.generateId()
val post = PostBuilder.of(id = generateId, userId = user.id)
val postQueryService = mock<PostQueryService> {
onBlocking { findByApId(eq(post.apId)) } doThrow FailedToGetResourcesException()
}
val postRepository = mock<PostRepository> {
onBlocking { generateId() } doReturn generateId
}
val person = Person(
name = user.name,
id = user.url,
preferredUsername = user.name,
summary = user.name,
inbox = user.inbox,
outbox = user.outbox,
url = user.url,
icon = Image(
name = user.url + "/icon.png", mediaType = "image/png", url = user.url + "/icon.png"
),
publicKey = Key(
type = emptyList(),
name = "Public Key",
id = user.keyId,
owner = user.url,
publicKeyPem = user.publicKey
),
endpoints = mapOf("sharedInbox" to "https://example.com/inbox"),
following = user.following,
followers = user.followers
)
val apUserService = mock<APUserService> {
onBlocking { fetchPersonWithEntity(eq(user.url), anyOrNull()) } doReturn (person to user)
}
val postService = mock<PostService>()
val apNoteServiceImpl = APNoteServiceImpl(
jobQueueParentService = mock(),
postRepository = postRepository,
apUserService = apUserService,
userQueryService = mock(),
followerQueryService = mock(),
postQueryService = postQueryService,
mediaQueryService = mock(),
objectMapper = objectMapper,
postService = postService,
apResourceResolveService = mock(),
postBuilder = postBuilder
)
val note = Note(
name = "Post",
id = post.apId,
attributedTo = user.url,
content = post.text,
published = Instant.ofEpochMilli(post.createdAt).toString(),
to = listOfNotNull(public, user.followers),
sensitive = post.sensitive,
cc = listOfNotNull(public, user.followers),
inReplyTo = null
)
val fetchNote = apNoteServiceImpl.fetchNote(note, null)
verify(postService, times(1)).createRemote(
eq(
PostBuilder.of(
id = generateId, userId = user.id, createdAt = post.createdAt
)
)
)
assertEquals(note, fetchNote)
}
@Test
fun `fetchNote DBに存在する場合取得して返す`() = runTest {
val user = UserBuilder.localUserOf()
val post = PostBuilder.of(userId = user.id)
val postQueryService = mock<PostQueryService> {
onBlocking { findByApId(eq(post.apId)) } doReturn post
}
val userQueryService = mock<UserQueryService> {
onBlocking { findById(eq(user.id)) } doReturn user
}
val apNoteServiceImpl = APNoteServiceImpl(
jobQueueParentService = mock(),
postRepository = mock(),
apUserService = mock(),
userQueryService = userQueryService,
followerQueryService = mock(),
postQueryService = postQueryService,
mediaQueryService = mock(),
objectMapper = objectMapper,
postService = mock(),
apResourceResolveService = mock(),
postBuilder = postBuilder
)
val note = Note(
name = "Post",
id = post.apId,
attributedTo = user.url,
content = post.text,
published = Instant.ofEpochMilli(post.createdAt).toString(),
to = listOfNotNull(public, user.followers),
sensitive = post.sensitive,
cc = listOfNotNull(public, user.followers),
inReplyTo = null
)
val fetchNote = apNoteServiceImpl.fetchNote(note, null)
assertEquals(note, fetchNote)
}
@Test
fun `createPostJob 新しい投稿のJob`() {
runTest {
val activityPubNoteService = ApNoteJobServiceImpl( val activityPubNoteService = ApNoteJobServiceImpl(
userQueryService = mock(), userQueryService = mock(),
@ -147,19 +426,15 @@ class APNoteServiceImplTest {
activityPubNoteService.createNoteJob( activityPubNoteService.createNoteJob(
JobProps( JobProps(
data = mapOf<String, Any>( data = mapOf<String, Any>(
DeliverPostJob.actor.name to "https://follower.example.com", DeliverPostJob.actor.name to "https://follower.example.com", DeliverPostJob.post.name to """{
DeliverPostJob.post.name to """{
"id": 1, "id": 1,
"userId": 1, "userId": 1,
"text": "test text", "text": "test text",
"createdAt": 132525324, "createdAt": 132525324,
"visibility": 0, "visibility": 0,
"url": "https://example.com" "url": "https://example.com"
}""", }""", DeliverPostJob.inbox.name to "https://follower.example.com/inbox", DeliverPostJob.media.name to "[]"
DeliverPostJob.inbox.name to "https://follower.example.com/inbox", ), json = Json
DeliverPostJob.media.name to "[]"
),
json = Json
) )
) )
} }