diff --git a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt index 402079b0..26b04f51 100644 --- a/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt +++ b/src/main/kotlin/dev/usbharu/hideout/domain/model/ActivityPubStringResponse.kt @@ -7,18 +7,78 @@ import io.ktor.http.* sealed class ActivityPubResponse( val httpStatusCode: HttpStatusCode, 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( httpStatusCode: HttpStatusCode = HttpStatusCode.OK, val message: String, 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( httpStatusCode: HttpStatusCode = HttpStatusCode.OK, val message: JsonLd, 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()}" + } + + +} diff --git a/src/test/kotlin/dev/usbharu/hideout/service/ap/APAcceptServiceImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/service/ap/APAcceptServiceImplTest.kt new file mode 100644 index 00000000..e4f0a851 --- /dev/null +++ b/src/test/kotlin/dev/usbharu/hideout/service/ap/APAcceptServiceImplTest.kt @@ -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 { + onBlocking { findByUrl(eq(actor)) } doReturn targetUser + onBlocking { findByUrl(eq(follower)) } doReturn followerUser + } + val followerQueryService = mock { + onBlocking { alreadyFollow(eq(targetUser.id), eq(followerUser.id)) } doReturn false + } + val userService = mock() + 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 { + onBlocking { findByUrl(eq(actor)) } doReturn targetUser + onBlocking { findByUrl(eq(follower)) } doReturn followerUser + } + val followerQueryService = mock { + onBlocking { alreadyFollow(eq(targetUser.id), eq(followerUser.id)) } doReturn true + } + val userService = mock() + 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 { + apAcceptServiceImpl.receiveAccept(accept) + } + } +} diff --git a/src/test/kotlin/utils/UserBuilder.kt b/src/test/kotlin/utils/UserBuilder.kt new file mode 100644 index 00000000..9d8116cd --- /dev/null +++ b/src/test/kotlin/utils/UserBuilder.kt @@ -0,0 +1,55 @@ +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 + + suspend 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 + ) + } + + private fun generateId(): Long = runBlocking { + idGenerator.generateId() + } +}