mirror of https://github.com/usbharu/Hideout.git
commit
89e1675611
|
@ -7,18 +7,72 @@ import io.ktor.http.*
|
||||||
sealed class ActivityPubResponse(
|
sealed class ActivityPubResponse(
|
||||||
val httpStatusCode: HttpStatusCode,
|
val httpStatusCode: HttpStatusCode,
|
||||||
val contentType: ContentType = ContentType.Application.Activity
|
val contentType: ContentType = ContentType.Application.Activity
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is ActivityPubResponse) return false
|
||||||
|
|
||||||
|
if (httpStatusCode != other.httpStatusCode) return false
|
||||||
|
if (contentType != other.contentType) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = httpStatusCode.hashCode()
|
||||||
|
result = 31 * result + contentType.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ActivityPubResponse(httpStatusCode=$httpStatusCode, contentType=$contentType)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ActivityPubStringResponse(
|
class ActivityPubStringResponse(
|
||||||
httpStatusCode: HttpStatusCode = HttpStatusCode.OK,
|
httpStatusCode: HttpStatusCode = HttpStatusCode.OK,
|
||||||
val message: String,
|
val message: String,
|
||||||
contentType: ContentType = ContentType.Application.Activity
|
contentType: ContentType = ContentType.Application.Activity
|
||||||
) :
|
) : ActivityPubResponse(httpStatusCode, contentType) {
|
||||||
ActivityPubResponse(httpStatusCode, contentType)
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is ActivityPubStringResponse) return false
|
||||||
|
|
||||||
|
if (message != other.message) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return message.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ActivityPubStringResponse(message='$message') ${super.toString()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ActivityPubObjectResponse(
|
class ActivityPubObjectResponse(
|
||||||
httpStatusCode: HttpStatusCode = HttpStatusCode.OK,
|
httpStatusCode: HttpStatusCode = HttpStatusCode.OK,
|
||||||
val message: JsonLd,
|
val message: JsonLd,
|
||||||
contentType: ContentType = ContentType.Application.Activity
|
contentType: ContentType = ContentType.Application.Activity
|
||||||
) :
|
) : ActivityPubResponse(httpStatusCode, contentType) {
|
||||||
ActivityPubResponse(httpStatusCode, contentType)
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is ActivityPubObjectResponse) return false
|
||||||
|
|
||||||
|
if (message != other.message) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return message.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ActivityPubObjectResponse(message=$message) ${super.toString()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -98,15 +98,16 @@ class APRequestServiceImpl(
|
||||||
|
|
||||||
override suspend fun <T : Object> apPost(url: String, body: T?, signer: User?): String {
|
override suspend fun <T : Object> apPost(url: String, body: T?, signer: User?): String {
|
||||||
logger.debug("START ActivityPub Request POST url: {}, signer: {}", url, signer?.url)
|
logger.debug("START ActivityPub Request POST url: {}, signer: {}", url, signer?.url)
|
||||||
if (body != null) {
|
val requestBody = if (body != null) {
|
||||||
val mutableListOf = mutableListOf<String>()
|
val mutableListOf = mutableListOf<String>()
|
||||||
mutableListOf.add("https://www.w3.org/ns/activitystreams")
|
mutableListOf.add("https://www.w3.org/ns/activitystreams")
|
||||||
mutableListOf.addAll(body.context)
|
mutableListOf.addAll(body.context)
|
||||||
body.context = mutableListOf
|
body.context = mutableListOf
|
||||||
|
objectMapper.writeValueAsString(body)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val requestBody = objectMapper.writeValueAsString(body)
|
|
||||||
|
|
||||||
logger.trace(
|
logger.trace(
|
||||||
"""
|
"""
|
||||||
|
|
|
|
||||||
|
@ -123,17 +124,19 @@ class APRequestServiceImpl(
|
||||||
|
|
||||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||||
|
|
||||||
val digest = Base64Util.encode(sha256.digest(requestBody.toByteArray()))
|
val digest = Base64Util.encode(sha256.digest(requestBody.orEmpty().toByteArray()))
|
||||||
|
|
||||||
val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT")))
|
val date = dateTimeFormatter.format(ZonedDateTime.now(ZoneId.of("GMT")))
|
||||||
val u = URL(url)
|
val u = URL(url)
|
||||||
if (signer?.privateKey == null) {
|
if (signer?.privateKey == null) {
|
||||||
val bodyAsText = httpClient.post(url) {
|
val bodyAsText = httpClient.post(url) {
|
||||||
header("Accept", ContentType.Application.Activity)
|
accept(ContentType.Application.Activity)
|
||||||
header("Date", date)
|
header("Date", date)
|
||||||
header("Digest", "sha-256=$digest")
|
header("Digest", "sha-256=$digest")
|
||||||
setBody(requestBody)
|
if (requestBody != null) {
|
||||||
contentType(ContentType.Application.Activity)
|
setBody(requestBody)
|
||||||
|
contentType(ContentType.Application.Activity)
|
||||||
|
}
|
||||||
}.bodyAsText()
|
}.bodyAsText()
|
||||||
logBody(bodyAsText, url)
|
logBody(bodyAsText, url)
|
||||||
return bodyAsText
|
return bodyAsText
|
||||||
|
|
|
@ -6,9 +6,6 @@ import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
import dev.usbharu.hideout.domain.model.ActivityPubResponse
|
||||||
import dev.usbharu.hideout.domain.model.ap.Follow
|
import dev.usbharu.hideout.domain.model.ap.Follow
|
||||||
import dev.usbharu.hideout.exception.JsonParseException
|
import dev.usbharu.hideout.exception.JsonParseException
|
||||||
import dev.usbharu.hideout.service.ap.job.APReceiveFollowJobService
|
|
||||||
import dev.usbharu.hideout.service.ap.job.ApNoteJobService
|
|
||||||
import dev.usbharu.hideout.service.ap.job.ApReactionJobService
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
|
@ -178,15 +175,16 @@ class APServiceImpl(
|
||||||
private val apAcceptService: APAcceptService,
|
private val apAcceptService: APAcceptService,
|
||||||
private val apCreateService: APCreateService,
|
private val apCreateService: APCreateService,
|
||||||
private val apLikeService: APLikeService,
|
private val apLikeService: APLikeService,
|
||||||
@Qualifier("activitypub") private val objectMapper: ObjectMapper,
|
@Qualifier("activitypub") private val objectMapper: ObjectMapper
|
||||||
private val apReceiveFollowJobService: APReceiveFollowJobService,
|
|
||||||
private val apNoteJobService: ApNoteJobService,
|
|
||||||
private val apReactionJobService: ApReactionJobService
|
|
||||||
) : APService {
|
) : APService {
|
||||||
|
|
||||||
val logger: Logger = LoggerFactory.getLogger(APServiceImpl::class.java)
|
val logger: Logger = LoggerFactory.getLogger(APServiceImpl::class.java)
|
||||||
override fun parseActivity(json: String): ActivityType {
|
override fun parseActivity(json: String): ActivityType {
|
||||||
val readTree = objectMapper.readTree(json)
|
val readTree = try {
|
||||||
|
objectMapper.readTree(json)
|
||||||
|
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
|
||||||
|
throw JsonParseException("Failed to parse json", e)
|
||||||
|
}
|
||||||
logger.trace(
|
logger.trace(
|
||||||
"""
|
"""
|
||||||
|
|
|
|
||||||
|
@ -204,11 +202,19 @@ class APServiceImpl(
|
||||||
}
|
}
|
||||||
val type = readTree["type"] ?: throw JsonParseException("Type is null")
|
val type = readTree["type"] ?: throw JsonParseException("Type is null")
|
||||||
if (type.isArray) {
|
if (type.isArray) {
|
||||||
return type.firstNotNullOf { jsonNode: JsonNode ->
|
try {
|
||||||
ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) }
|
return type.firstNotNullOf { jsonNode: JsonNode ->
|
||||||
|
ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) }
|
||||||
|
}
|
||||||
|
} catch (e: NoSuchElementException) {
|
||||||
|
throw IllegalArgumentException("No valid TYPE", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ActivityType.values().first { it.name.equals(type.asText(), true) }
|
try {
|
||||||
|
return ActivityType.values().first { it.name.equals(type.asText(), true) }
|
||||||
|
} catch (e: NoSuchElementException) {
|
||||||
|
throw IllegalArgumentException("No valid TYPE", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
|
@Suppress("CyclomaticComplexMethod", "NotImplementedDeclaration")
|
||||||
|
@ -219,6 +225,7 @@ class APServiceImpl(
|
||||||
ActivityType.Follow ->
|
ActivityType.Follow ->
|
||||||
apReceiveFollowService
|
apReceiveFollowService
|
||||||
.receiveFollow(objectMapper.readValue(json, Follow::class.java))
|
.receiveFollow(objectMapper.readValue(json, Follow::class.java))
|
||||||
|
|
||||||
ActivityType.Create -> apCreateService.receiveCreate(objectMapper.readValue(json))
|
ActivityType.Create -> apCreateService.receiveCreate(objectMapper.readValue(json))
|
||||||
ActivityType.Like -> apLikeService.receiveLike(objectMapper.readValue(json))
|
ActivityType.Like -> apLikeService.receiveLike(objectMapper.readValue(json))
|
||||||
ActivityType.Undo -> apUndoService.receiveUndo(objectMapper.readValue(json))
|
ActivityType.Undo -> apUndoService.receiveUndo(objectMapper.readValue(json))
|
||||||
|
|
|
@ -45,6 +45,7 @@ class APUndoServiceImpl(
|
||||||
val target = userQueryService.findByUrl(follow.`object`!!)
|
val target = userQueryService.findByUrl(follow.`object`!!)
|
||||||
userService.unfollow(target.id, follower.id)
|
userService.unfollow(target.id, follower.id)
|
||||||
}
|
}
|
||||||
|
return ActivityPubStringResponse(HttpStatusCode.OK, "Accept")
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
package dev.usbharu.hideout.service.ap
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Accept
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Follow
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Like
|
||||||
|
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
||||||
|
import dev.usbharu.hideout.query.FollowerQueryService
|
||||||
|
import dev.usbharu.hideout.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.service.user.UserService
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.TestTransaction
|
||||||
|
import utils.UserBuilder
|
||||||
|
|
||||||
|
class APAcceptServiceImplTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `receiveAccept 正常なAcceptを処理できる`() = runTest {
|
||||||
|
val actor = "https://example.com"
|
||||||
|
val follower = "https://follower.example.com"
|
||||||
|
val targetUser = UserBuilder.localUserOf()
|
||||||
|
val followerUser = UserBuilder.localUserOf()
|
||||||
|
val userQueryService = mock<UserQueryService> {
|
||||||
|
onBlocking { findByUrl(eq(actor)) } doReturn targetUser
|
||||||
|
onBlocking { findByUrl(eq(follower)) } doReturn followerUser
|
||||||
|
}
|
||||||
|
val followerQueryService = mock<FollowerQueryService> {
|
||||||
|
onBlocking { alreadyFollow(eq(targetUser.id), eq(followerUser.id)) } doReturn false
|
||||||
|
}
|
||||||
|
val userService = mock<UserService>()
|
||||||
|
val apAcceptServiceImpl =
|
||||||
|
APAcceptServiceImpl(userService, userQueryService, followerQueryService, TestTransaction)
|
||||||
|
|
||||||
|
val accept = Accept(
|
||||||
|
name = "Accept",
|
||||||
|
`object` = Follow(
|
||||||
|
name = "",
|
||||||
|
`object` = actor,
|
||||||
|
actor = follower
|
||||||
|
),
|
||||||
|
actor = actor
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val actual = apAcceptServiceImpl.receiveAccept(accept)
|
||||||
|
assertEquals(ActivityPubStringResponse(HttpStatusCode.OK, "accepted"), actual)
|
||||||
|
verify(userService, times(1)).follow(eq(targetUser.id), eq(followerUser.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `receiveAccept 既にフォローしている場合は無視する`() = runTest {
|
||||||
|
|
||||||
|
val actor = "https://example.com"
|
||||||
|
val follower = "https://follower.example.com"
|
||||||
|
val targetUser = UserBuilder.localUserOf()
|
||||||
|
val followerUser = UserBuilder.localUserOf()
|
||||||
|
val userQueryService = mock<UserQueryService> {
|
||||||
|
onBlocking { findByUrl(eq(actor)) } doReturn targetUser
|
||||||
|
onBlocking { findByUrl(eq(follower)) } doReturn followerUser
|
||||||
|
}
|
||||||
|
val followerQueryService = mock<FollowerQueryService> {
|
||||||
|
onBlocking { alreadyFollow(eq(targetUser.id), eq(followerUser.id)) } doReturn true
|
||||||
|
}
|
||||||
|
val userService = mock<UserService>()
|
||||||
|
val apAcceptServiceImpl =
|
||||||
|
APAcceptServiceImpl(userService, userQueryService, followerQueryService, TestTransaction)
|
||||||
|
|
||||||
|
val accept = Accept(
|
||||||
|
name = "Accept",
|
||||||
|
`object` = Follow(
|
||||||
|
name = "",
|
||||||
|
`object` = actor,
|
||||||
|
actor = follower
|
||||||
|
),
|
||||||
|
actor = actor
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val actual = apAcceptServiceImpl.receiveAccept(accept)
|
||||||
|
assertEquals(ActivityPubStringResponse(HttpStatusCode.OK, "accepted"), actual)
|
||||||
|
verify(userService, times(0)).follow(eq(targetUser.id), eq(followerUser.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `revieveAccept AcceptのobjectのtypeがFollow以外の場合IllegalActivityPubObjectExceptionがthrowされる`() =
|
||||||
|
runTest {
|
||||||
|
val accept = Accept(
|
||||||
|
name = "Accept",
|
||||||
|
`object` = Like(
|
||||||
|
name = "Like",
|
||||||
|
actor = "actor",
|
||||||
|
id = "https://example.com",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
content = "aaaa"
|
||||||
|
),
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
val apAcceptServiceImpl = APAcceptServiceImpl(mock(), mock(), mock(), TestTransaction)
|
||||||
|
|
||||||
|
assertThrows<IllegalActivityPubObjectException> {
|
||||||
|
apAcceptServiceImpl.receiveAccept(accept)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package dev.usbharu.hideout.service.ap
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Create
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Like
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Note
|
||||||
|
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.TestTransaction
|
||||||
|
|
||||||
|
class APCreateServiceImplTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `receiveCreate 正常なCreateを処理できる`() = runTest {
|
||||||
|
val create = Create(
|
||||||
|
name = "Create",
|
||||||
|
`object` = Note(
|
||||||
|
name = "Note",
|
||||||
|
id = "https://example.com/note",
|
||||||
|
attributedTo = "https://example.com/actor",
|
||||||
|
content = "Hello World",
|
||||||
|
published = "Date: Wed, 21 Oct 2015 07:28:00 GMT"
|
||||||
|
),
|
||||||
|
actor = "https://example.com/actor",
|
||||||
|
id = "https://example.com/create",
|
||||||
|
)
|
||||||
|
|
||||||
|
val apNoteService = mock<APNoteService>()
|
||||||
|
val apCreateServiceImpl = APCreateServiceImpl(apNoteService, TestTransaction)
|
||||||
|
|
||||||
|
val actual = ActivityPubStringResponse(HttpStatusCode.OK, "Created")
|
||||||
|
|
||||||
|
val receiveCreate = apCreateServiceImpl.receiveCreate(create)
|
||||||
|
verify(apNoteService, times(1)).fetchNote(any<Note>(), anyOrNull())
|
||||||
|
assertEquals(actual, receiveCreate)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `reveiveCreate CreateのobjectのtypeがNote以外の場合IllegalActivityPubObjectExceptionがthrowされる`() = runTest {
|
||||||
|
val create = Create(
|
||||||
|
name = "Create",
|
||||||
|
`object` = Like(
|
||||||
|
name = "Like",
|
||||||
|
id = "https://example.com/note",
|
||||||
|
actor = "https://example.com/actor",
|
||||||
|
`object` = "https://example.com/create",
|
||||||
|
content = "aaa"
|
||||||
|
),
|
||||||
|
actor = "https://example.com/actor",
|
||||||
|
id = "https://example.com/create",
|
||||||
|
)
|
||||||
|
|
||||||
|
val apCreateServiceImpl = APCreateServiceImpl(mock(), TestTransaction)
|
||||||
|
assertThrows<IllegalActivityPubObjectException> {
|
||||||
|
apCreateServiceImpl.receiveCreate(create)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package dev.usbharu.hideout.service.ap
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Like
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Note
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Person
|
||||||
|
import dev.usbharu.hideout.exception.ap.FailedToGetActivityPubResourceException
|
||||||
|
import dev.usbharu.hideout.query.PostQueryService
|
||||||
|
import dev.usbharu.hideout.service.reaction.ReactionService
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.PostBuilder
|
||||||
|
import utils.TestTransaction
|
||||||
|
import utils.UserBuilder
|
||||||
|
|
||||||
|
|
||||||
|
class APLikeServiceImplTest {
|
||||||
|
@Test
|
||||||
|
fun `receiveLike 正常なLikeを処理できる`() = runTest {
|
||||||
|
val actor = "https://example.com/actor"
|
||||||
|
val note = "https://example.com/note"
|
||||||
|
val like = Like(
|
||||||
|
name = "Like", actor = actor, id = "htps://example.com", `object` = note, content = "aaa"
|
||||||
|
)
|
||||||
|
|
||||||
|
val user = UserBuilder.localUserOf()
|
||||||
|
val apUserService = mock<APUserService> {
|
||||||
|
onBlocking { fetchPersonWithEntity(eq(actor), anyOrNull()) } doReturn (Person(
|
||||||
|
name = "TestUser",
|
||||||
|
id = "https://example.com",
|
||||||
|
preferredUsername = "Test user",
|
||||||
|
summary = "test user",
|
||||||
|
inbox = "https://example.com/inbox",
|
||||||
|
outbox = "https://example.com/outbox",
|
||||||
|
url = "https://example.com/",
|
||||||
|
icon = null,
|
||||||
|
publicKey = null,
|
||||||
|
followers = null,
|
||||||
|
following = null
|
||||||
|
) to user)
|
||||||
|
}
|
||||||
|
val apNoteService = mock<APNoteService> {
|
||||||
|
on { fetchNoteAsync(eq(note), anyOrNull()) } doReturn async {
|
||||||
|
Note(
|
||||||
|
name = "Note",
|
||||||
|
id = "https://example.com/note",
|
||||||
|
attributedTo = "https://example.com/actor",
|
||||||
|
content = "Hello World",
|
||||||
|
published = "Date: Wed, 21 Oct 2015 07:28:00 GMT",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val post = PostBuilder.of()
|
||||||
|
val postQueryService = mock<PostQueryService> {
|
||||||
|
onBlocking { findByUrl(eq(note)) } doReturn post
|
||||||
|
}
|
||||||
|
val reactionService = mock<ReactionService>()
|
||||||
|
val apLikeServiceImpl = APLikeServiceImpl(
|
||||||
|
reactionService, apUserService, apNoteService, postQueryService, TestTransaction
|
||||||
|
)
|
||||||
|
|
||||||
|
val actual = apLikeServiceImpl.receiveLike(like)
|
||||||
|
|
||||||
|
verify(reactionService, times(1)).receiveReaction(eq("aaa"), eq("example.com"), eq(user.id), eq(post.id))
|
||||||
|
assertEquals(ActivityPubStringResponse(HttpStatusCode.OK, ""), actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `recieveLike Likeのobjectのurlが取得できないとき何もしない`() = runTest {
|
||||||
|
val actor = "https://example.com/actor"
|
||||||
|
val note = "https://example.com/note"
|
||||||
|
val like = Like(
|
||||||
|
name = "Like", actor = actor, id = "htps://example.com", `object` = note, content = "aaa"
|
||||||
|
)
|
||||||
|
|
||||||
|
val user = UserBuilder.localUserOf()
|
||||||
|
val apUserService = mock<APUserService> {
|
||||||
|
onBlocking { fetchPersonWithEntity(eq(actor), anyOrNull()) } doReturn (Person(
|
||||||
|
name = "TestUser",
|
||||||
|
id = "https://example.com",
|
||||||
|
preferredUsername = "Test user",
|
||||||
|
summary = "test user",
|
||||||
|
inbox = "https://example.com/inbox",
|
||||||
|
outbox = "https://example.com/outbox",
|
||||||
|
url = "https://example.com/",
|
||||||
|
icon = null,
|
||||||
|
publicKey = null,
|
||||||
|
followers = null,
|
||||||
|
following = null
|
||||||
|
) to user)
|
||||||
|
}
|
||||||
|
val apNoteService = mock<APNoteService> {
|
||||||
|
on { fetchNoteAsync(eq(note), anyOrNull()) } doThrow FailedToGetActivityPubResourceException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val reactionService = mock<ReactionService>()
|
||||||
|
val apLikeServiceImpl = APLikeServiceImpl(
|
||||||
|
reactionService, apUserService, apNoteService, mock(), TestTransaction
|
||||||
|
)
|
||||||
|
|
||||||
|
val actual = apLikeServiceImpl.receiveLike(like)
|
||||||
|
|
||||||
|
verify(reactionService, times(0)).receiveReaction(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
|
||||||
|
assertEquals(ActivityPubStringResponse(HttpStatusCode.OK, ""), actual)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,47 +118,303 @@ 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(),
|
userQueryService = userQueryService,
|
||||||
userQueryService = userQueryService,
|
followerQueryService = followerQueryService,
|
||||||
followerQueryService = followerQueryService,
|
postQueryService = mock(),
|
||||||
postQueryService = mock(),
|
mediaQueryService = mediaQueryService,
|
||||||
mediaQueryService = mediaQueryService,
|
objectMapper = objectMapper,
|
||||||
objectMapper = objectMapper,
|
postService = mock(),
|
||||||
postService = mock(),
|
apResourceResolveService = mock(),
|
||||||
apResourceResolveService = mock(),
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
fun `createPostJob 新しい投稿のJob`() {
|
fun `createPostJob 新しい投稿のJob`() {
|
||||||
runTest {
|
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(
|
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
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package dev.usbharu.hideout.service.ap
|
||||||
|
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Reaction
|
||||||
|
import dev.usbharu.hideout.domain.model.job.DeliverReactionJob
|
||||||
|
import dev.usbharu.hideout.domain.model.job.DeliverRemoveReactionJob
|
||||||
|
import dev.usbharu.hideout.query.FollowerQueryService
|
||||||
|
import dev.usbharu.hideout.query.PostQueryService
|
||||||
|
import dev.usbharu.hideout.service.core.TwitterSnowflakeIdGenerateService
|
||||||
|
import dev.usbharu.hideout.service.job.JobQueueParentService
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.JsonObjectMapper.objectMapper
|
||||||
|
import utils.PostBuilder
|
||||||
|
import utils.UserBuilder
|
||||||
|
|
||||||
|
class APReactionServiceImplTest {
|
||||||
|
@Test
|
||||||
|
fun `reaction リアクションするとフォロワーの数だけ配送ジョブが作成される`() = runTest {
|
||||||
|
|
||||||
|
val user = UserBuilder.localUserOf()
|
||||||
|
val post = PostBuilder.of()
|
||||||
|
|
||||||
|
val postQueryService = mock<PostQueryService> {
|
||||||
|
onBlocking { findById(eq(post.id)) } doReturn post
|
||||||
|
}
|
||||||
|
val followerQueryService = mock<FollowerQueryService> {
|
||||||
|
onBlocking { findFollowersById(eq(user.id)) } doReturn listOf(
|
||||||
|
UserBuilder.localUserOf(),
|
||||||
|
UserBuilder.localUserOf(),
|
||||||
|
UserBuilder.localUserOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val jobQueueParentService = mock<JobQueueParentService>()
|
||||||
|
val apReactionServiceImpl = APReactionServiceImpl(
|
||||||
|
jobQueueParentService = jobQueueParentService,
|
||||||
|
userQueryService = mock(),
|
||||||
|
followerQueryService = followerQueryService,
|
||||||
|
postQueryService = postQueryService,
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
apReactionServiceImpl.reaction(
|
||||||
|
Reaction(
|
||||||
|
id = TwitterSnowflakeIdGenerateService.generateId(),
|
||||||
|
emojiId = 0,
|
||||||
|
postId = post.id,
|
||||||
|
userId = user.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(jobQueueParentService, times(3)).schedule(eq(DeliverReactionJob), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeReaction リアクションを削除するとフォロワーの数だけ配送ジョブが作成される`() = runTest {
|
||||||
|
|
||||||
|
val user = UserBuilder.localUserOf()
|
||||||
|
val post = PostBuilder.of()
|
||||||
|
|
||||||
|
val postQueryService = mock<PostQueryService> {
|
||||||
|
onBlocking { findById(eq(post.id)) } doReturn post
|
||||||
|
}
|
||||||
|
val followerQueryService = mock<FollowerQueryService> {
|
||||||
|
onBlocking { findFollowersById(eq(user.id)) } doReturn listOf(
|
||||||
|
UserBuilder.localUserOf(),
|
||||||
|
UserBuilder.localUserOf(),
|
||||||
|
UserBuilder.localUserOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val jobQueueParentService = mock<JobQueueParentService>()
|
||||||
|
val apReactionServiceImpl = APReactionServiceImpl(
|
||||||
|
jobQueueParentService = jobQueueParentService,
|
||||||
|
userQueryService = mock(),
|
||||||
|
followerQueryService = followerQueryService,
|
||||||
|
postQueryService = postQueryService,
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
apReactionServiceImpl.removeReaction(
|
||||||
|
Reaction(
|
||||||
|
id = TwitterSnowflakeIdGenerateService.generateId(),
|
||||||
|
emojiId = 0,
|
||||||
|
postId = post.id,
|
||||||
|
userId = user.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(jobQueueParentService, times(3)).schedule(eq(DeliverRemoveReactionJob), any())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,348 @@
|
||||||
|
package dev.usbharu.hideout.service.ap
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Follow
|
||||||
|
import dev.usbharu.hideout.util.Base64Util
|
||||||
|
import dev.usbharu.httpsignature.common.HttpHeaders
|
||||||
|
import dev.usbharu.httpsignature.common.HttpMethod
|
||||||
|
import dev.usbharu.httpsignature.common.HttpRequest
|
||||||
|
import dev.usbharu.httpsignature.sign.HttpSignatureSigner
|
||||||
|
import dev.usbharu.httpsignature.sign.Signature
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.engine.mock.*
|
||||||
|
import io.ktor.util.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
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.eq
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import utils.JsonObjectMapper.objectMapper
|
||||||
|
import utils.UserBuilder
|
||||||
|
import java.net.URL
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
class APRequestServiceImplTest {
|
||||||
|
@Test
|
||||||
|
fun `apGet signerがnullのとき署名なしリクエストをする`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(
|
||||||
|
HttpClient(MockEngine {
|
||||||
|
assertTrue(it.headers.contains("Date"))
|
||||||
|
assertTrue(it.headers.contains("Accept"))
|
||||||
|
assertFalse(it.headers.contains("Signature"))
|
||||||
|
assertDoesNotThrow {
|
||||||
|
dateTimeFormatter.parse(it.headers["Date"])
|
||||||
|
}
|
||||||
|
respond("{}")
|
||||||
|
}),
|
||||||
|
objectMapper,
|
||||||
|
mock(),
|
||||||
|
dateTimeFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
val responseClass = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
apRequestServiceImpl.apGet("https://example.com", responseClass = responseClass::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apGet signerがnullではないがprivateKeyがnullのとき署名なしリクエストをする`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(
|
||||||
|
HttpClient(MockEngine {
|
||||||
|
assertTrue(it.headers.contains("Date"))
|
||||||
|
assertTrue(it.headers.contains("Accept"))
|
||||||
|
assertFalse(it.headers.contains("Signature"))
|
||||||
|
assertDoesNotThrow {
|
||||||
|
dateTimeFormatter.parse(it.headers["Date"])
|
||||||
|
}
|
||||||
|
respond("{}")
|
||||||
|
}),
|
||||||
|
objectMapper,
|
||||||
|
mock(),
|
||||||
|
dateTimeFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
val responseClass = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
apRequestServiceImpl.apGet(
|
||||||
|
"https://example.com",
|
||||||
|
UserBuilder.remoteUserOf(),
|
||||||
|
responseClass = responseClass::class.java
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apGet signerとprivatekeyがnullではないとき署名付きリクエストをする`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val httpSignatureSigner = mock<HttpSignatureSigner> {
|
||||||
|
onBlocking {
|
||||||
|
sign(
|
||||||
|
any(),
|
||||||
|
any(),
|
||||||
|
eq(listOf("(request-target)", "date", "host", "accept"))
|
||||||
|
)
|
||||||
|
} doReturn Signature(
|
||||||
|
HttpRequest(URL("https://example.com"), HttpHeaders(mapOf()), HttpMethod.GET), "", ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(
|
||||||
|
HttpClient(MockEngine {
|
||||||
|
assertTrue(it.headers.contains("Date"))
|
||||||
|
assertTrue(it.headers.contains("Accept"))
|
||||||
|
assertTrue(it.headers.contains("Signature"))
|
||||||
|
assertDoesNotThrow {
|
||||||
|
dateTimeFormatter.parse(it.headers["Date"])
|
||||||
|
}
|
||||||
|
respond("{}")
|
||||||
|
}),
|
||||||
|
objectMapper,
|
||||||
|
httpSignatureSigner,
|
||||||
|
dateTimeFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
val responseClass = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
apRequestServiceImpl.apGet(
|
||||||
|
"https://example.com",
|
||||||
|
UserBuilder.localUserOf(
|
||||||
|
privateKey = "-----BEGIN PRIVATE KEY-----\n" +
|
||||||
|
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJhNETcFVoZW36\n" +
|
||||||
|
"pDiaaUDa1FsWGqULUa6jDWYbMXFirbbceJEfvaasac+E8VUQ3krrEhYBArntB1do\n" +
|
||||||
|
"1Zq/MpI97WaQefwrBmjJwjYglB8AHF1RRqFlJ0aABMBvuHiIzuTPv4dLS4+pJQWl\n" +
|
||||||
|
"iE9TKsxXgUrEdWLmpSukZpyiWnrgFtJ8322LXRuL9+O4ivns1JfozbrHTprI4ohe\n" +
|
||||||
|
"6taZJX1mhGBXQT+U/UrEILk+z70P2rrwxwerdO7s6nkkC3ieJWdi924/AopDlg12\n" +
|
||||||
|
"8udubLPbpWVVrHbSKviUr3VKBKGe4xmvO7hqpGwKmctaXRVPjh/ue2mCIzv3qyxQ\n" +
|
||||||
|
"3n2Xyhb3AgMBAAECggEAGddiSC/bg+ud0spER+i/XFBm7cq052KuFlKdiVcpxxGn\n" +
|
||||||
|
"pVYApiVXvjxDVDTuR5950/MZxz9mQDL0zoi1s1b00eQjhttdrta/kT/KWRslboo0\n" +
|
||||||
|
"nTuFbsc+jyQM2Ua6jjCZvto8qzchUPtiYfu80Floor/9qnuzFwiPNCHEbD1WDG4m\n" +
|
||||||
|
"fLuH+INnGY6eRF+pgly1dykGs18DaR3vC9CWOqR9PWH+p/myksVymR5adKauMc+l\n" +
|
||||||
|
"gjLaeB1YjnzXnHYLqwtCgh053kedPG/xZZwq48YNP5npSBIHsd9g8JIPVNOOc6+s\n" +
|
||||||
|
"bbFqD9aQQxG/WaA5hxHRupLkKGjE6lw4SnVYzKMZIQKBgQDryFa3qzJIBrCQQa0r\n" +
|
||||||
|
"6YlmZeeCQ8mQL8d0gY0Ixo9Gm2/9J71m/oBkhOqnS6Z5e5UHS5iVaqM7sIOZ2Ony\n" +
|
||||||
|
"kPADAtxUsk71Il+z+JgyN3OQ+DROLREi2TIWS523hbtN7e/fRFs7KoN6cH7IeF13\n" +
|
||||||
|
"3pphg9+WWRGX7y1zMd1puY/gSwKBgQDazFrAt/oZbnDhkX350OdIybz62OHNyuZv\n" +
|
||||||
|
"UX9fFl9i93SF+UhOpJ8YvDJtfLEJUkwO+V3TB+we1OlOYMTqir5M8GFn6YDotwxB\n" +
|
||||||
|
"r6eT886UpJgtJwswwwW2yaXo7zXaeg3ovRE8RJ4y++Mhuqeq3ajIo7xlhQjzBDEf\n" +
|
||||||
|
"ZAqasSWwhQKBgQC0VbUlo1XAywUOQH0/oc4KOJS6CDjJBBIsZM3G0X9SBJ7B5Dwz\n" +
|
||||||
|
"4yG2QAbtT6oTLldMjiA036vbgmUVLVe5w+sekniMexhy2wiRsOhPOCQ20+/Ffyil\n" +
|
||||||
|
"G7P4Y3tMm4cn0n1tqW2RsjF/Wz1M/OqYPPSc8uz2pEcVisSbX582Nsv5QwKBgEuy\n" +
|
||||||
|
"vAtFG6BE14UTIzSVFA/YzCs1choTAtqspZauVN4WoxffASdESU7zfbbnlxCUin/7\n" +
|
||||||
|
"wnxKl2SrYPSfAkHrMp/H4stivBjHi9QGA8JqbaR7tbKZeYOrVYTCC0alzEoERF+r\n" +
|
||||||
|
"WhUx4FHfV9vJikzRV53jGEE/X7NEVgJ4SDrw4wtJAoGAAMJ2kOIL3HSQPd8csXeU\n" +
|
||||||
|
"nkxLNzBsFpF76LVmLdzJttlr8HWBjLP/EJFQZFzuf5Hd38cLUOWWD3FRZVw0dUcN\n" +
|
||||||
|
"RSqfIYT4yDc/9GSRb6rOkdmBUWpTsrZjXBo0MC3p1QE6sNO8JfvmxHTSAe8apBh/\n" +
|
||||||
|
"gaYuQGh0lNa23HwwFoJxuoc=\n" +
|
||||||
|
"-----END PRIVATE KEY-----"
|
||||||
|
),
|
||||||
|
responseClass = responseClass::class.java
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apPost bodyがnullでないときcontextにactivitystreamのURLを追加する`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
|
||||||
|
val readValue = objectMapper.readValue<Follow>(it.body.toByteArray())
|
||||||
|
|
||||||
|
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
|
respondOk("{}")
|
||||||
|
}), objectMapper, mock(), dateTimeFormatter)
|
||||||
|
|
||||||
|
val body = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
apRequestServiceImpl.apPost("https://example.com", body, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apPost bodyがnullのときリクエストボディは空`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
|
||||||
|
|
||||||
|
assertEquals(0, it.body.toByteArray().size)
|
||||||
|
|
||||||
|
respondOk("{}")
|
||||||
|
}), objectMapper, mock(), dateTimeFormatter)
|
||||||
|
|
||||||
|
apRequestServiceImpl.apPost("https://example.com", null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apPost signerがnullのとき署名なしリクエストをする`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
|
||||||
|
val src = it.body.toByteArray()
|
||||||
|
val readValue = objectMapper.readValue<Follow>(src)
|
||||||
|
|
||||||
|
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
|
val map = it.headers.toMap()
|
||||||
|
assertThat(map).containsKey("Date")
|
||||||
|
.containsKey("Digest")
|
||||||
|
.containsKey("Accept")
|
||||||
|
.doesNotContainKey("Signature")
|
||||||
|
|
||||||
|
assertDoesNotThrow {
|
||||||
|
dateTimeFormatter.parse(it.headers["Date"])
|
||||||
|
}
|
||||||
|
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val digest = Base64Util.encode(messageDigest.digest(src))
|
||||||
|
|
||||||
|
assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last())
|
||||||
|
|
||||||
|
respondOk("{}")
|
||||||
|
}), objectMapper, mock(), dateTimeFormatter)
|
||||||
|
|
||||||
|
val body = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
apRequestServiceImpl.apPost("https://example.com", body, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apPost signerがnullではないがprivatekeyがnullのとき署名なしリクエストをする`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
|
||||||
|
val src = it.body.toByteArray()
|
||||||
|
val readValue = objectMapper.readValue<Follow>(src)
|
||||||
|
|
||||||
|
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
|
val map = it.headers.toMap()
|
||||||
|
assertThat(map).containsKey("Date")
|
||||||
|
.containsKey("Digest")
|
||||||
|
.containsKey("Accept")
|
||||||
|
.doesNotContainKey("Signature")
|
||||||
|
|
||||||
|
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val digest = Base64Util.encode(messageDigest.digest(src))
|
||||||
|
|
||||||
|
assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last())
|
||||||
|
|
||||||
|
respondOk("{}")
|
||||||
|
}), objectMapper, mock(), dateTimeFormatter)
|
||||||
|
|
||||||
|
val body = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
apRequestServiceImpl.apPost("https://example.com", body, UserBuilder.remoteUserOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apPost signerがnullではないとき署名付きリクエストをする`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val httpSignatureSigner = mock<HttpSignatureSigner> {
|
||||||
|
onBlocking {
|
||||||
|
sign(
|
||||||
|
any(),
|
||||||
|
any(),
|
||||||
|
eq(listOf("(request-target)", "date", "host", "digest"))
|
||||||
|
)
|
||||||
|
} doReturn Signature(
|
||||||
|
HttpRequest(URL("https://example.com"), HttpHeaders(mapOf()), HttpMethod.POST), "", ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
|
||||||
|
val src = it.body.toByteArray()
|
||||||
|
val readValue = objectMapper.readValue<Follow>(src)
|
||||||
|
|
||||||
|
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
|
val map = it.headers.toMap()
|
||||||
|
assertThat(map).containsKey("Date")
|
||||||
|
.containsKey("Digest")
|
||||||
|
.containsKey("Accept")
|
||||||
|
.containsKey("Signature")
|
||||||
|
|
||||||
|
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val digest = Base64Util.encode(messageDigest.digest(src))
|
||||||
|
|
||||||
|
assertEquals(digest, it.headers["Digest"].orEmpty().split("256=").last())
|
||||||
|
|
||||||
|
respondOk("{}")
|
||||||
|
}), objectMapper, httpSignatureSigner, dateTimeFormatter)
|
||||||
|
|
||||||
|
val body = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
apRequestServiceImpl.apPost(
|
||||||
|
"https://example.com", body, UserBuilder.localUserOf(
|
||||||
|
privateKey = "-----BEGIN PRIVATE KEY-----\n" +
|
||||||
|
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1+pj+/t5WwU6P\n" +
|
||||||
|
"OiaAKfOHCUVMdOR5e2Jp0BUYfAFpim27pLsHRXVjdzs+D4gvDnQWC0FMltPyBldk\n" +
|
||||||
|
"gjisNMtTKgTTsYhlLlSi+yRDZvIQyH4b7xSX0hCeflTrTkt18ZldBRPfMHE0KSho\n" +
|
||||||
|
"mm3Lc7ubF32YzGoo3A3qEVDAR9dVQOnt/GXLiN4RHoStX+y5UiP6B4s49nyEwuLm\n" +
|
||||||
|
"+HE4ph3Loqn0dTEL4cEuI8ZX51J3mTKT3rmMo0wCXXOm8gD2Fu7hYEdr9ulWF8GO\n" +
|
||||||
|
"yVe7Miu9prbBlY/r4skdXc5o6uE8tsPT88Ly9lSr3xqbmn1/EhyqBRdcyoj28C65\n" +
|
||||||
|
"cThO38jvAgMBAAECggEAFbOaXkJ3smHgI/17zOnz1EU7QehovMIFlPfPJDnZk0QC\n" +
|
||||||
|
"XQ/CjBXw71kvM/H3PCFdn6lc8qzD/sdZ0a8j4glzu+m1ZKd1zBcv2bXYd79Fm9HF\n" +
|
||||||
|
"FEC5NHfFKpmHN/6AykJzFyA9Y+7reRx1aLAN6ubU1ySAgmHSQSgo8qJ4/k0y9UQS\n" +
|
||||||
|
"EbjxQL5ziXuxRBMn7InLUGLl5UfCC0V1R8MZQAe+fApKDXMQ0LHSJUg1A365PyhV\n" +
|
||||||
|
"seotqvhurHH3UVHf5n0/sFeqp2hI4ymR3cs4kd8IuNIXE7afh+89IyuVKMvJh+iQ\n" +
|
||||||
|
"ZGO1RL0v0mNtUpI81agSrrQ4LRBjSkP+5s5PdXTrSQKBgQD2lwMXLylhQzhRyhLx\n" +
|
||||||
|
"sSPRf9mKDUcretwA5Fh9GuAurKOz7SvIdzrUPFYUTUKSTwk8mVRRamkFtJ8IOB7Z\n" +
|
||||||
|
"MLenlFqxs4XrNGBcZxut5cPv68xn2F00Y4HwX9xmEi+vniNVrDpdVLxEoVfm1pBk\n" +
|
||||||
|
"02ZHCcfYVN0t8dnvXvlL+eJSqQKBgQC87GMoMvFnWgT23wdXtQH+F+gQAMUrkMWz\n" +
|
||||||
|
"Ld2uRwuSVQArgp+YgnwWMlYlFp/QIW90t7UVmf6bHIplO5bL2OwayIO1r/WxD1eN\n" +
|
||||||
|
"RLrFIeDbtCZWQTHUypnWtl+9lrh/RrCjZo/sZFl07OSIKgGM37j9taG6Nv6fV7gv\n" +
|
||||||
|
"T0q6eDCV1wKBgGh3CUQlIq6lv5JGvUfO95GlTA+EGIZ/Af0Ov74gSKD9Wky7STUf\n" +
|
||||||
|
"7bhD52OqZ218NjmJ64KiReO45TaiL89rKCLCYrmtiCpgggIjXEKLeDqH9ox3yOSM\n" +
|
||||||
|
"01t2APTs926629VLpV4sq6WXhJmyhHFybX3i0tr++MSiFOWnoo1hS1QhAoGAfVY6\n" +
|
||||||
|
"ppW9kDqppnrqrSZ6Lu//VnacWL3QW4JnWtLpe2iHF1auuQiAeF1mx25OEk/MWNvz\n" +
|
||||||
|
"+GPVBWUW7/hrn8vHQDGdJ/GYB6LNC/z4CAbk3f2TnY/dFnZfP5J4zBftSQtF7vIB\n" +
|
||||||
|
"M+yTaL4tE6UCqEpYuYFBzX/kxyP0Hvb09eb9HLsCgYEArFSgWpaLbADcWd+ygWls\n" +
|
||||||
|
"LNfch1Yl2bnqXKz1Dnw3J4l2gbVNcABXQLrB6upjtkytxj4ae66Sio7nf+dB5yJ6\n" +
|
||||||
|
"NVY7i4C0JrniY2OvLnuz2bKpaTgMPJxyZqGQ6Vu2b3x9WhcpiI83SCuCUgBKxjh/\n" +
|
||||||
|
"qEGv2ZqFfnNVrz5RXLHBoG4=\n" +
|
||||||
|
"-----END PRIVATE KEY-----"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `apPost responseClassを指定した場合はjsonでシリアライズされる`() = runTest {
|
||||||
|
val dateTimeFormatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
|
||||||
|
val apRequestServiceImpl = APRequestServiceImpl(HttpClient(MockEngine {
|
||||||
|
val src = it.body.toByteArray()
|
||||||
|
val readValue = objectMapper.readValue<Follow>(src)
|
||||||
|
|
||||||
|
assertThat(readValue.context).contains("https://www.w3.org/ns/activitystreams")
|
||||||
|
|
||||||
|
respondOk(src.decodeToString())
|
||||||
|
}), objectMapper, mock(), dateTimeFormatter)
|
||||||
|
|
||||||
|
val body = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com",
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
val actual = apRequestServiceImpl.apPost("https://example.com", body, null, body::class.java)
|
||||||
|
|
||||||
|
assertThat(body).isEqualTo(actual)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package dev.usbharu.hideout.service.ap
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Follow
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.dto.SendFollowDto
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.eq
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.times
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import utils.UserBuilder
|
||||||
|
|
||||||
|
class APSendFollowServiceImplTest {
|
||||||
|
@Test
|
||||||
|
fun `sendFollow フォローするユーザーのinboxにFollowオブジェクトが送られる`() = runTest {
|
||||||
|
val apRequestService = mock<APRequestService>()
|
||||||
|
val apSendFollowServiceImpl = APSendFollowServiceImpl(apRequestService)
|
||||||
|
|
||||||
|
val sendFollowDto = SendFollowDto(
|
||||||
|
UserBuilder.localUserOf(),
|
||||||
|
UserBuilder.remoteUserOf()
|
||||||
|
)
|
||||||
|
apSendFollowServiceImpl.sendFollow(sendFollowDto)
|
||||||
|
|
||||||
|
val value = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = sendFollowDto.followTargetUserId.url,
|
||||||
|
actor = sendFollowDto.userId.url
|
||||||
|
)
|
||||||
|
verify(apRequestService, times(1)).apPost(
|
||||||
|
eq(sendFollowDto.followTargetUserId.inbox),
|
||||||
|
eq(value),
|
||||||
|
eq(sendFollowDto.userId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
package dev.usbharu.hideout.service.ap
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.exception.JsonParseException
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import utils.JsonObjectMapper.objectMapper
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class APServiceImplTest {
|
||||||
|
@Test
|
||||||
|
fun `parseActivity 正常なActivityをパースできる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
val activityType = apServiceImpl.parseActivity("""{"type": "Follow"}""")
|
||||||
|
|
||||||
|
assertEquals(ActivityType.Follow, activityType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity Typeが配列のActivityをパースできる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
val activityType = apServiceImpl.parseActivity("""{"type": ["Follow"]}""")
|
||||||
|
|
||||||
|
assertEquals(ActivityType.Follow, activityType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity Typeが配列で関係ない物が入っていてもパースできる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
val activityType = apServiceImpl.parseActivity("""{"type": ["Hello","Follow"]}""")
|
||||||
|
|
||||||
|
assertEquals(ActivityType.Follow, activityType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity jsonとして解釈できない場合JsonParseExceptionがthrowされる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
assertThrows<JsonParseException> {
|
||||||
|
apServiceImpl.parseActivity("""hoge""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity 空の場合JsonParseExceptionがthrowされる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
assertThrows<JsonParseException> {
|
||||||
|
apServiceImpl.parseActivity("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity jsonにtypeプロパティがない場合JsonParseExceptionがthrowされる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
assertThrows<JsonParseException> {
|
||||||
|
apServiceImpl.parseActivity("""{"actor": "https://example.com"}""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity typeが配列でないときtypeが未定義の場合IllegalArgumentExceptionがthrowされる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
apServiceImpl.parseActivity("""{"type": "Hoge"}""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity typeが配列のとき定義済みのtypeを見つけられなかった場合IllegalArgumentExceptionがthrowされる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
apServiceImpl.parseActivity("""{"type": ["Hoge","Fuga"]}""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity typeが空の場合IllegalArgumentExceptionがthrowされる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
apServiceImpl.parseActivity("""{"type": ""}""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity typeに指定されている文字の判定がcase-insensitiveで行われる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
val activityType = apServiceImpl.parseActivity("""{"type": "FoLlOw"}""")
|
||||||
|
|
||||||
|
assertEquals(ActivityType.Follow, activityType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity typeが配列のとき指定されている文字の判定がcase-insensitiveで行われる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
val activityType = apServiceImpl.parseActivity("""{"type": ["HoGE","fOllOw"]}""")
|
||||||
|
|
||||||
|
assertEquals(ActivityType.Follow, activityType)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity activityがarrayのときJsonParseExceptionがthrowされる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
assertThrows<JsonParseException> {
|
||||||
|
apServiceImpl.parseActivity("""[{"type": "Follow"},{"type": "Accept"}]""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parseActivity activityがvalueのときJsonParseExceptionがthrowされる`() {
|
||||||
|
val apServiceImpl = APServiceImpl(
|
||||||
|
apReceiveFollowService = mock(),
|
||||||
|
apUndoService = mock(),
|
||||||
|
apAcceptService = mock(),
|
||||||
|
apCreateService = mock(),
|
||||||
|
apLikeService = mock(),
|
||||||
|
objectMapper = objectMapper
|
||||||
|
)
|
||||||
|
|
||||||
|
//language=JSON
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
apServiceImpl.parseActivity(""""hoge"""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package dev.usbharu.hideout.service.ap
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Follow
|
||||||
|
import dev.usbharu.hideout.domain.model.ap.Undo
|
||||||
|
import dev.usbharu.hideout.query.UserQueryService
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.eq
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import utils.TestTransaction
|
||||||
|
import utils.UserBuilder
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class APUndoServiceImplTest {
|
||||||
|
@Test
|
||||||
|
fun `receiveUndo FollowのUndoを処理できる`() = runTest {
|
||||||
|
|
||||||
|
val userQueryService = mock<UserQueryService> {
|
||||||
|
onBlocking { findByUrl(eq("https://follower.example.com/actor")) } doReturn UserBuilder.remoteUserOf()
|
||||||
|
onBlocking { findByUrl(eq("https://example.com/actor")) } doReturn UserBuilder.localUserOf()
|
||||||
|
}
|
||||||
|
val apUndoServiceImpl = APUndoServiceImpl(
|
||||||
|
userService = mock(),
|
||||||
|
apUserService = mock(),
|
||||||
|
userQueryService = userQueryService,
|
||||||
|
transaction = TestTransaction
|
||||||
|
)
|
||||||
|
|
||||||
|
val undo = Undo(
|
||||||
|
name = "Undo",
|
||||||
|
actor = "https://follower.example.com/actor",
|
||||||
|
id = "https://follower.example.com/undo/follow",
|
||||||
|
`object` = Follow(
|
||||||
|
name = "Follow",
|
||||||
|
`object` = "https://example.com/actor",
|
||||||
|
actor = "https://follower.example.com/actor"
|
||||||
|
),
|
||||||
|
published = Instant.now()
|
||||||
|
)
|
||||||
|
val activityPubResponse = apUndoServiceImpl.receiveUndo(undo)
|
||||||
|
assertEquals(ActivityPubStringResponse(HttpStatusCode.OK, "Accept"), activityPubResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.config.CharacterLimit
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Post
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
|
||||||
|
import dev.usbharu.hideout.service.core.TwitterSnowflakeIdGenerateService
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
object PostBuilder {
|
||||||
|
|
||||||
|
private val postBuilder = Post.PostBuilder(CharacterLimit())
|
||||||
|
|
||||||
|
private val idGenerator = TwitterSnowflakeIdGenerateService
|
||||||
|
|
||||||
|
fun of(
|
||||||
|
id: Long = generateId(),
|
||||||
|
userId: Long = generateId(),
|
||||||
|
overview: String? = null,
|
||||||
|
text: String = "Hello World",
|
||||||
|
createdAt: Long = Instant.now().toEpochMilli(),
|
||||||
|
visibility: Visibility = Visibility.PUBLIC,
|
||||||
|
url: String = "https://example.com/users/$userId/posts/$id"
|
||||||
|
): Post {
|
||||||
|
return postBuilder.of(
|
||||||
|
id = id,
|
||||||
|
userId = userId,
|
||||||
|
overview = overview,
|
||||||
|
text = text,
|
||||||
|
createdAt = createdAt,
|
||||||
|
visibility = visibility,
|
||||||
|
url = url,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateId(): Long = runBlocking {
|
||||||
|
idGenerator.generateId()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.config.ApplicationConfig
|
||||||
|
import dev.usbharu.hideout.config.CharacterLimit
|
||||||
|
import dev.usbharu.hideout.domain.model.hideout.entity.User
|
||||||
|
import dev.usbharu.hideout.service.core.TwitterSnowflakeIdGenerateService
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.net.URL
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
object UserBuilder {
|
||||||
|
private val userBuilder = User.UserBuilder(CharacterLimit(), ApplicationConfig(URL("https://example.com")))
|
||||||
|
|
||||||
|
private val idGenerator = TwitterSnowflakeIdGenerateService
|
||||||
|
|
||||||
|
fun localUserOf(
|
||||||
|
id: Long = generateId(),
|
||||||
|
name: String = "test-user-$id",
|
||||||
|
domain: String = "example.com",
|
||||||
|
screenName: String = name,
|
||||||
|
description: String = "This user is test user.",
|
||||||
|
password: String = "password-$id",
|
||||||
|
inbox: String = "https://$domain/$id/inbox",
|
||||||
|
outbox: String = "https://$domain/$id/outbox",
|
||||||
|
url: String = "https://$domain/$id/",
|
||||||
|
publicKey: String = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
|
||||||
|
privateKey: String = "-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----",
|
||||||
|
createdAt: Instant = Instant.now(),
|
||||||
|
keyId: String = "https://$domain/$id#pubkey",
|
||||||
|
followers: String = "https://$domain/$id/followers",
|
||||||
|
following: String = "https://$domain/$id/following"
|
||||||
|
): User {
|
||||||
|
return userBuilder.of(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
domain = domain,
|
||||||
|
screenName = screenName,
|
||||||
|
description = description,
|
||||||
|
password = password,
|
||||||
|
inbox = inbox,
|
||||||
|
outbox = outbox,
|
||||||
|
url = url,
|
||||||
|
publicKey = publicKey,
|
||||||
|
privateKey = privateKey,
|
||||||
|
createdAt = createdAt,
|
||||||
|
keyId = keyId,
|
||||||
|
followers = following,
|
||||||
|
following = followers
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remoteUserOf(
|
||||||
|
id: Long = generateId(),
|
||||||
|
name: String = "test-user-$id",
|
||||||
|
domain: String = "remote.example.com",
|
||||||
|
screenName: String = name,
|
||||||
|
description: String = "This user is test user.",
|
||||||
|
inbox: String = "https://$domain/$id/inbox",
|
||||||
|
outbox: String = "https://$domain/$id/outbox",
|
||||||
|
url: String = "https://$domain/$id/",
|
||||||
|
publicKey: String = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
|
||||||
|
createdAt: Instant = Instant.now(),
|
||||||
|
keyId: String = "https://$domain/$id#pubkey",
|
||||||
|
followers: String = "https://$domain/$id/followers",
|
||||||
|
following: String = "https://$domain/$id/following"
|
||||||
|
): User {
|
||||||
|
return userBuilder.of(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
domain = domain,
|
||||||
|
screenName = screenName,
|
||||||
|
description = description,
|
||||||
|
password = null,
|
||||||
|
inbox = inbox,
|
||||||
|
outbox = outbox,
|
||||||
|
url = url,
|
||||||
|
publicKey = publicKey,
|
||||||
|
privateKey = null,
|
||||||
|
createdAt = createdAt,
|
||||||
|
keyId = keyId,
|
||||||
|
followers = following,
|
||||||
|
following = followers
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateId(): Long = runBlocking {
|
||||||
|
idGenerator.generateId()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue