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:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@file:OptIn(ExperimentalCoroutinesApi::class) @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package dev.usbharu.hideout.service.ap
import dev.usbharu.hideout.config.ApplicationConfig
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.User
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
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.MediaQueryService
import dev.usbharu.hideout.query.PostQueryService
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.resource.APResourceResolveService
import dev.usbharu.hideout.service.core.TwitterSnowflakeIdGenerateService
import dev.usbharu.hideout.service.job.JobQueueParentService
import dev.usbharu.hideout.service.post.PostService
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
import org.mockito.kotlin.*
import utils.JsonObjectMapper.objectMapper
import utils.PostBuilder
import utils.TestTransaction
import utils.UserBuilder
import java.net.URL
import java.time.Instant
import kotlin.test.assertEquals
class APNoteServiceImplTest {
@ -58,8 +82,7 @@ class APNoteServiceImplTest {
publicKey = "",
createdAt = Instant.now(),
keyId = "a"
),
userBuilder.of(
), userBuilder.of(
3L,
"follower2",
"follower2.example.com",
@ -95,47 +118,303 @@ class APNoteServiceImplTest {
onBlocking { findFollowersById(eq(1L)) } doReturn followers
}
val jobQueueParentService = mock<JobQueueParentService>()
val activityPubNoteService =
APNoteServiceImpl(
jobQueueParentService = jobQueueParentService,
postRepository = mock(),
apUserService = mock(),
userQueryService = userQueryService,
followerQueryService = followerQueryService,
postQueryService = mock(),
mediaQueryService = mediaQueryService,
objectMapper = objectMapper,
postService = mock(),
apResourceResolveService = mock(),
postBuilder = postBuilder
)
val activityPubNoteService = APNoteServiceImpl(
jobQueueParentService = jobQueueParentService,
postRepository = mock(),
apUserService = mock(),
userQueryService = userQueryService,
followerQueryService = followerQueryService,
postQueryService = mock(),
mediaQueryService = mediaQueryService,
objectMapper = objectMapper,
postService = mock(),
apResourceResolveService = mock(),
postBuilder = postBuilder
)
val postEntity = postBuilder.of(
1L,
1L,
null,
"test text",
1L,
Visibility.PUBLIC,
"https://example.com"
1L, 1L, null, "test text", 1L, Visibility.PUBLIC, "https://example.com"
)
activityPubNoteService.createNote(postEntity)
verify(jobQueueParentService, times(2)).schedule(eq(DeliverPostJob), any())
}
}
@Test
fun `fetchNote(String,String) ートが既に存在する場合はDBから取得したものを返す`() = runTest {
val url = "https://example.com/note"
val post = PostBuilder.of()
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)
}
@Test
fun `fetchNote(String,String) ートがDBに存在しない場合リモートに取得しにいく`() = 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> {
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 mediaQueryService = mock<MediaQueryService> {
onBlocking { findByPostId(anyLong()) } doReturn emptyList()
}
val httpClient = HttpClient(
MockEngine { httpRequestData ->
assertEquals("https://follower.example.com/inbox", httpRequestData.url.toString())
respondOk()
}
)
val activityPubNoteService = ApNoteJobServiceImpl(
userQueryService = mock(),
@ -147,19 +426,15 @@ class APNoteServiceImplTest {
activityPubNoteService.createNoteJob(
JobProps(
data = mapOf<String, Any>(
DeliverPostJob.actor.name to "https://follower.example.com",
DeliverPostJob.post.name to """{
DeliverPostJob.actor.name to "https://follower.example.com", DeliverPostJob.post.name to """{
"id": 1,
"userId": 1,
"text": "test text",
"createdAt": 132525324,
"visibility": 0,
"url": "https://example.com"
}""",
DeliverPostJob.inbox.name to "https://follower.example.com/inbox",
DeliverPostJob.media.name to "[]"
),
json = Json
}""", DeliverPostJob.inbox.name to "https://follower.example.com/inbox", DeliverPostJob.media.name to "[]"
), json = Json
)
)
}