mirror of https://github.com/usbharu/Hideout.git
Merge branch 'develop' into usbharu-patch-3
This commit is contained in:
commit
60bd0951fd
|
@ -13,6 +13,8 @@ on:
|
|||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
@ -50,7 +52,12 @@ jobs:
|
|||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Gradle Build Action
|
||||
- name: Run JUnit
|
||||
uses: gradle/gradle-build-action@v2.8.1
|
||||
with:
|
||||
arguments: test
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v2
|
||||
if: always()
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
|
|
|
@ -16,6 +16,7 @@ plugins {
|
|||
id("org.springframework.boot") version "3.1.3"
|
||||
kotlin("plugin.spring") version "1.8.21"
|
||||
id("org.openapi.generator") version "7.0.1"
|
||||
id("org.jetbrains.kotlinx.kover") version "0.7.4"
|
||||
// id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10"
|
||||
}
|
||||
|
||||
|
@ -31,6 +32,11 @@ tasks.withType<Test> {
|
|||
val cpus = Runtime.getRuntime().availableProcessors()
|
||||
maxParallelForks = max(1, cpus - 1)
|
||||
setForkEvery(4)
|
||||
doFirst {
|
||||
jvmArgs = arrayOf(
|
||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED"
|
||||
).toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
|
||||
|
@ -128,6 +134,7 @@ dependencies {
|
|||
implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version")
|
||||
testImplementation("org.springframework.boot:spring-boot-test-autoconfigure")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.springframework.security:spring-security-test")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
implementation("org.springframework.security:spring-security-oauth2-jose")
|
||||
|
@ -158,7 +165,8 @@ dependencies {
|
|||
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
|
||||
testImplementation("org.mockito:mockito-inline:5.2.0")
|
||||
|
||||
testImplementation("nl.jqno.equalsverifier:equalsverifier:3.15.3")
|
||||
testImplementation("com.jparams:to-string-verifier:1.4.8")
|
||||
|
||||
implementation("org.drewcarlson:kjob-core:0.6.0")
|
||||
implementation("org.drewcarlson:kjob-mongo:0.6.0")
|
||||
|
@ -198,3 +206,34 @@ configurations.matching { it.name == "detekt" }.all {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
project.gradle.taskGraph.whenReady {
|
||||
println(this.allTasks)
|
||||
this.allTasks.map { println(it.name) }
|
||||
if (this.hasTask(":koverGenerateArtifact")) {
|
||||
println("has task")
|
||||
val task = this.allTasks.find { it.name == "test" }
|
||||
val verificationTask = task as VerificationTask
|
||||
verificationTask.ignoreFailures = true
|
||||
}
|
||||
}
|
||||
|
||||
kover {
|
||||
|
||||
excludeSourceSets {
|
||||
names("aot")
|
||||
}
|
||||
}
|
||||
|
||||
koverReport {
|
||||
filters {
|
||||
excludes {
|
||||
packages(
|
||||
"dev.usbharu.hideout.controller.mastodon.generated",
|
||||
"dev.usbharu.hideout.domain.mastodon.model.generated"
|
||||
)
|
||||
packages("org.springframework")
|
||||
packages("org.jetbrains")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ open class Delete : Object {
|
|||
|
||||
constructor(
|
||||
type: List<String> = emptyList(),
|
||||
name: String = "Delete",
|
||||
name: String? = "Delete",
|
||||
actor: String,
|
||||
id: String,
|
||||
`object`: Object,
|
||||
|
|
|
@ -19,10 +19,17 @@ open class JsonLd {
|
|||
@JsonSerialize(include = JsonSerialize.Inclusion.NON_EMPTY, using = ContextSerializer::class)
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
var context: List<String> = emptyList()
|
||||
set(value) {
|
||||
field = value.filterNotNull().filter { it.isNotBlank() }
|
||||
}
|
||||
|
||||
@JsonCreator
|
||||
constructor(context: List<String>) {
|
||||
this.context = context
|
||||
constructor(context: List<String?>?) {
|
||||
if (context != null) {
|
||||
this.context = context.filterNotNull().filter { it.isNotBlank() }
|
||||
} else {
|
||||
this.context = emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
protected constructor()
|
||||
|
@ -40,25 +47,34 @@ open class JsonLd {
|
|||
}
|
||||
|
||||
class ContextDeserializer : JsonDeserializer<String>() {
|
||||
|
||||
|
||||
override fun deserialize(
|
||||
p0: com.fasterxml.jackson.core.JsonParser?,
|
||||
p1: com.fasterxml.jackson.databind.DeserializationContext?
|
||||
): String {
|
||||
val readTree: JsonNode = p0?.codec?.readTree(p0) ?: return ""
|
||||
if (readTree.isObject) {
|
||||
return ""
|
||||
if (readTree.isValueNode) {
|
||||
return readTree.textValue()
|
||||
}
|
||||
return readTree.asText()
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
class ContextSerializer : JsonSerializer<List<String>>() {
|
||||
|
||||
override fun isEmpty(value: List<String>?): Boolean = value.isNullOrEmpty()
|
||||
override fun isEmpty(value: List<String>?): Boolean {
|
||||
return value.isNullOrEmpty()
|
||||
}
|
||||
|
||||
override fun isEmpty(provider: SerializerProvider?, value: List<String>?): Boolean {
|
||||
return value.isNullOrEmpty()
|
||||
}
|
||||
|
||||
override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider) {
|
||||
|
||||
override fun serialize(value: List<String>?, gen: JsonGenerator?, serializers: SerializerProvider?) {
|
||||
if (value.isNullOrEmpty()) {
|
||||
gen?.writeNull()
|
||||
serializers.defaultSerializeNull(gen)
|
||||
return
|
||||
}
|
||||
if (value.size == 1) {
|
||||
|
|
|
@ -9,18 +9,22 @@ import dev.usbharu.hideout.activitypub.domain.model.JsonLd
|
|||
open class Object : JsonLd {
|
||||
@JsonSerialize(using = TypeSerializer::class)
|
||||
var type: List<String> = emptyList()
|
||||
set(value) {
|
||||
field = value.filter { it.isNotBlank() }
|
||||
}
|
||||
var name: String? = null
|
||||
var actor: String? = null
|
||||
var id: String? = null
|
||||
|
||||
protected constructor()
|
||||
constructor(type: List<String>, name: String? = null, actor: String? = null, id: String? = null) : super() {
|
||||
this.type = type
|
||||
this.type = type.filter { it.isNotBlank() }
|
||||
this.name = name
|
||||
this.actor = actor
|
||||
this.id = id
|
||||
}
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Object) return false
|
||||
|
@ -29,7 +33,9 @@ open class Object : JsonLd {
|
|||
if (type != other.type) return false
|
||||
if (name != other.name) return false
|
||||
if (actor != other.actor) return false
|
||||
return id == other.id
|
||||
if (id != other.id) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
|
@ -41,7 +47,9 @@ open class Object : JsonLd {
|
|||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String = "Object(type=$type, name=$name, actor=$actor, id=$id) ${super.toString()}"
|
||||
override fun toString(): String {
|
||||
return "Object(type=$type, name=$name, actor=$actor, id=$id) ${super.toString()}"
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
|
|
@ -2,6 +2,7 @@ package dev.usbharu.hideout.activitypub.interfaces.api.actor
|
|||
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Person
|
||||
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
@ -9,7 +10,11 @@ import org.springframework.web.bind.annotation.RestController
|
|||
@RestController
|
||||
class UserAPControllerImpl(private val apUserService: APUserService) : UserAPController {
|
||||
override suspend fun userAp(username: String): ResponseEntity<Person> {
|
||||
val person = apUserService.getPersonByName(username)
|
||||
val person = try {
|
||||
apUserService.getPersonByName(username)
|
||||
} catch (e: FailedToGetResourcesException) {
|
||||
return ResponseEntity.notFound().build()
|
||||
}
|
||||
person.context += listOf("https://www.w3.org/ns/activitystreams")
|
||||
return ResponseEntity(person, HttpStatus.OK)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package dev.usbharu.hideout.activitypub.interfaces.api.inbox
|
||||
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
|
@ -16,7 +15,7 @@ interface InboxController {
|
|||
"application/activity+json",
|
||||
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
||||
],
|
||||
method = [RequestMethod.GET, RequestMethod.POST]
|
||||
method = [RequestMethod.POST]
|
||||
)
|
||||
suspend fun inbox(@RequestBody string: String): ResponseEntity<Unit> = ResponseEntity(HttpStatus.ACCEPTED)
|
||||
suspend fun inbox(@RequestBody string: String): ResponseEntity<Unit>
|
||||
}
|
||||
|
|
|
@ -2,15 +2,12 @@ package dev.usbharu.hideout.activitypub.interfaces.api.note
|
|||
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Note
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.core.annotation.CurrentSecurityContext
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
|
||||
interface NoteApController {
|
||||
@GetMapping("/users/*/posts/{postId}")
|
||||
suspend fun postsAp(
|
||||
@PathVariable("postId") postId: Long,
|
||||
@CurrentSecurityContext context: SecurityContext
|
||||
@PathVariable("postId") postId: Long
|
||||
): ResponseEntity<Note>
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@ import dev.usbharu.hideout.activitypub.domain.model.Note
|
|||
import dev.usbharu.hideout.activitypub.service.objects.note.NoteApApiService
|
||||
import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.core.annotation.CurrentSecurityContext
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
@ -14,8 +13,8 @@ import org.springframework.web.bind.annotation.RestController
|
|||
class NoteApControllerImpl(private val noteApApiService: NoteApApiService) : NoteApController {
|
||||
override suspend fun postsAp(
|
||||
@PathVariable(value = "postId") postId: Long,
|
||||
@CurrentSecurityContext context: SecurityContext
|
||||
): ResponseEntity<Note> {
|
||||
val context = SecurityContextHolder.getContext()
|
||||
val userId =
|
||||
if (context.authentication is PreAuthenticatedAuthenticationToken &&
|
||||
context.authentication.details is HttpSignatureUser
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package dev.usbharu.hideout.activitypub.interfaces.api.outbox
|
||||
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestMethod
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
@ -10,5 +8,5 @@ import org.springframework.web.bind.annotation.RestController
|
|||
@RestController
|
||||
interface OutboxController {
|
||||
@RequestMapping("/outbox", "/users/{username}/outbox", method = [RequestMethod.POST, RequestMethod.GET])
|
||||
suspend fun outbox(@RequestBody string: String): ResponseEntity<Unit> = ResponseEntity(HttpStatus.ACCEPTED)
|
||||
suspend fun outbox(): ResponseEntity<Unit>
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@ package dev.usbharu.hideout.activitypub.interfaces.api.outbox
|
|||
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
class OutboxControllerImpl : OutboxController {
|
||||
override suspend fun outbox(@RequestBody string: String): ResponseEntity<Unit> =
|
||||
override suspend fun outbox(): ResponseEntity<Unit> =
|
||||
ResponseEntity(HttpStatus.NOT_IMPLEMENTED)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package dev.usbharu.hideout.activitypub.interfaces.api.webfinger
|
|||
import dev.usbharu.hideout.activitypub.domain.model.webfinger.WebFinger
|
||||
import dev.usbharu.hideout.activitypub.service.webfinger.WebFingerApiService
|
||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import dev.usbharu.hideout.util.AcctUtil
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.slf4j.LoggerFactory
|
||||
|
@ -26,8 +27,11 @@ class WebFingerController(
|
|||
logger.warn("FAILED Parse acct.", e)
|
||||
return@runBlocking ResponseEntity.badRequest().build()
|
||||
}
|
||||
val user =
|
||||
val user = try {
|
||||
webFingerApiService.findByNameAndDomain(acct.username, acct.domain ?: applicationConfig.url.host)
|
||||
} catch (_: FailedToGetResourcesException) {
|
||||
return@runBlocking ResponseEntity.notFound().build()
|
||||
}
|
||||
val webFinger = WebFinger(
|
||||
"acct:${user.name}@${user.domain}",
|
||||
listOf(
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package dev.usbharu.hideout.application.config
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.annotation.JsonSetter
|
||||
import com.fasterxml.jackson.annotation.Nulls
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
|
@ -21,7 +24,11 @@ class ActivityPubConfig {
|
|||
val objectMapper = jacksonObjectMapper()
|
||||
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
|
||||
.setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY))
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
.configure(JsonParser.Feature.ALLOW_COMMENTS, true)
|
||||
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
|
||||
.configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true)
|
||||
return objectMapper
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package dev.usbharu.hideout.core.service.reaction
|
||||
|
||||
import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
|
||||
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||
import dev.usbharu.hideout.core.query.ReactionQueryService
|
||||
|
@ -26,18 +27,26 @@ class ReactionServiceImpl(
|
|||
}
|
||||
|
||||
override suspend fun sendReaction(name: String, userId: Long, postId: Long) {
|
||||
if (reactionQueryService.reactionAlreadyExist(postId, userId, 0)) {
|
||||
// delete
|
||||
reactionQueryService.deleteByPostIdAndUserId(postId, userId)
|
||||
} else {
|
||||
try {
|
||||
val findByPostIdAndUserIdAndEmojiId =
|
||||
reactionQueryService.findByPostIdAndUserIdAndEmojiId(postId, userId, 0)
|
||||
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId)
|
||||
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId)
|
||||
} catch (_: FailedToGetResourcesException) {
|
||||
}
|
||||
val reaction = Reaction(reactionRepository.generateId(), 0, postId, userId)
|
||||
reactionRepository.save(reaction)
|
||||
apReactionService.reaction(reaction)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeReaction(userId: Long, postId: Long) {
|
||||
reactionQueryService.deleteByPostIdAndUserId(postId, userId)
|
||||
try {
|
||||
val findByPostIdAndUserIdAndEmojiId =
|
||||
reactionQueryService.findByPostIdAndUserIdAndEmojiId(postId, userId, 0)
|
||||
reactionRepository.delete(findByPostIdAndUserIdAndEmojiId)
|
||||
apReactionService.removeReaction(findByPostIdAndUserIdAndEmojiId)
|
||||
} catch (e: FailedToGetResourcesException) {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package dev.usbharu.hideout
|
||||
|
||||
import com.jparams.verifier.tostring.ToStringVerifier
|
||||
import nl.jqno.equalsverifier.EqualsVerifier
|
||||
import nl.jqno.equalsverifier.Warning
|
||||
import nl.jqno.equalsverifier.internal.reflection.PackageScanner
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.lang.reflect.Modifier
|
||||
import kotlin.test.assertFails
|
||||
|
||||
class EqualsAndToStringTest {
|
||||
@Test
|
||||
fun equalsTest() {
|
||||
assertFails {
|
||||
EqualsVerifier
|
||||
.simple()
|
||||
.suppress(Warning.INHERITED_DIRECTLY_FROM_OBJECT)
|
||||
.forPackage("dev.usbharu.hideout", true)
|
||||
.verify()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toStringTest() {
|
||||
|
||||
PackageScanner.getClassesIn("dev.usbharu.hideout", null, true)
|
||||
.filter {
|
||||
it != null && !it.isEnum && !it.isInterface && !Modifier.isAbstract(it.modifiers)
|
||||
}
|
||||
.forEach {
|
||||
try {
|
||||
ToStringVerifier.forClass(it).verify()
|
||||
} catch (e: AssertionError) {
|
||||
println(it.name)
|
||||
e.printStackTrace()
|
||||
} catch (e: Exception) {
|
||||
println(it.name)
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package dev.usbharu.hideout.activitypub.domain.model
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class DeleteSerializeTest {
|
||||
@Test
|
||||
fun Misskeyの発行するJSONをデシリアライズできる() {
|
||||
@Language("JSON") val json = """{
|
||||
"@context" : [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", {
|
||||
"manuallyApprovesFollowers" : "as:manuallyApprovesFollowers",
|
||||
"sensitive" : "as:sensitive",
|
||||
"Hashtag" : "as:Hashtag",
|
||||
"quoteUrl" : "as:quoteUrl",
|
||||
"toot" : "http://joinmastodon.org/ns#",
|
||||
"Emoji" : "toot:Emoji",
|
||||
"featured" : "toot:featured",
|
||||
"discoverable" : "toot:discoverable",
|
||||
"schema" : "http://schema.org#",
|
||||
"PropertyValue" : "schema:PropertyValue",
|
||||
"value" : "schema:value",
|
||||
"misskey" : "https://misskey-hub.net/ns#",
|
||||
"_misskey_content" : "misskey:_misskey_content",
|
||||
"_misskey_quote" : "misskey:_misskey_quote",
|
||||
"_misskey_reaction" : "misskey:_misskey_reaction",
|
||||
"_misskey_votes" : "misskey:_misskey_votes",
|
||||
"isCat" : "misskey:isCat",
|
||||
"vcard" : "http://www.w3.org/2006/vcard/ns#"
|
||||
} ],
|
||||
"type" : "Delete",
|
||||
"actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||
"object" : {
|
||||
"id" : "https://misskey.usbharu.dev/notes/9lkwqnwqk9",
|
||||
"type" : "Tombstone"
|
||||
},
|
||||
"published" : "2023-11-02T15:30:34.160Z",
|
||||
"id" : "https://misskey.usbharu.dev/4b5b6ed5-9269-45f3-8403-cba1e74b4b69"
|
||||
}
|
||||
"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<Delete>(json)
|
||||
|
||||
val expected = Delete(
|
||||
name = null,
|
||||
actor = "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||
id = "https://misskey.usbharu.dev/4b5b6ed5-9269-45f3-8403-cba1e74b4b69",
|
||||
`object` = Tombstone(
|
||||
id = "https://misskey.usbharu.dev/notes/9lkwqnwqk9",
|
||||
),
|
||||
published = "2023-11-02T15:30:34.160Z",
|
||||
)
|
||||
expected.context = listOf("https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", "")
|
||||
assertEquals(expected, readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun シリアライズできる() {
|
||||
val delete = Delete(
|
||||
name = null,
|
||||
actor = "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||
id = "https://misskey.usbharu.dev/4b5b6ed5-9269-45f3-8403-cba1e74b4b69",
|
||||
`object` = Tombstone(
|
||||
id = "https://misskey.usbharu.dev/notes/9lkwqnwqk9",
|
||||
),
|
||||
published = "2023-11-02T15:30:34.160Z",
|
||||
)
|
||||
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val actual = objectMapper.writeValueAsString(delete)
|
||||
val expected =
|
||||
"""{"type":"Delete","actor":"https://misskey.usbharu.dev/users/97ws8y3rj6","id":"https://misskey.usbharu.dev/4b5b6ed5-9269-45f3-8403-cba1e74b4b69","object":{"type":"Tombstone","name":"Tombstone","id":"https://misskey.usbharu.dev/notes/9lkwqnwqk9"},"published":"2023-11-02T15:30:34.160Z"}"""
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package dev.usbharu.hideout.activitypub.domain.model
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class JsonLdSerializeTest {
|
||||
@Test
|
||||
fun contextが文字列のときデシリアライズできる() {
|
||||
//language=JSON
|
||||
val json = """{"@context":"https://example.com"}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<JsonLd>(json)
|
||||
|
||||
assertEquals(JsonLd(listOf("https://example.com")), readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextが文字列の配列のときデシリアライズできる() {
|
||||
//language=JSON
|
||||
val json = """{"@context":["https://example.com","https://www.w3.org/ns/activitystreams"]}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<JsonLd>(json)
|
||||
|
||||
assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextがnullのとき空のlistとして解釈してデシリアライズする() {
|
||||
//language=JSON
|
||||
val json = """{"@context":null}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<JsonLd>(json)
|
||||
|
||||
assertEquals(JsonLd(emptyList()), readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextがnullを含む文字列の配列のときnullを無視してデシリアライズできる() {
|
||||
//language=JSON
|
||||
val json = """{"@context":["https://example.com",null,"https://www.w3.org/ns/activitystreams"]}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<JsonLd>(json)
|
||||
|
||||
assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextがオブジェクトのとき無視してデシリアライズする() {
|
||||
//language=JSON
|
||||
val json = """{"@context":{"hoge": "fuga"}}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<JsonLd>(json)
|
||||
|
||||
assertEquals(JsonLd(emptyList()), readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextがオブジェクトを含む文字列の配列のときオブジェクトを無視してデシリアライズする() {
|
||||
//language=JSON
|
||||
val json = """{"@context":["https://example.com",{"hoge": "fuga"},"https://www.w3.org/ns/activitystreams"]}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<JsonLd>(json)
|
||||
|
||||
assertEquals(JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams")), readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextが配列の配列のとき無視してデシリアライズする() {
|
||||
//language=JSON
|
||||
val json = """{"@context":[["a","b"],["c","d"]]}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<JsonLd>(json)
|
||||
|
||||
assertEquals(JsonLd(emptyList()), readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextが空のとき無視してシリアライズする() {
|
||||
val jsonLd = JsonLd(emptyList())
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val actual = objectMapper.writeValueAsString(jsonLd)
|
||||
|
||||
assertEquals("{}", actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextがnullのとき無視してシリアライズする() {
|
||||
val jsonLd = JsonLd(listOf(null))
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val actual = objectMapper.writeValueAsString(jsonLd)
|
||||
|
||||
assertEquals("{}", actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextが文字列のとき文字列としてシリアライズされる() {
|
||||
val jsonLd = JsonLd(listOf("https://example.com"))
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val actual = objectMapper.writeValueAsString(jsonLd)
|
||||
|
||||
assertEquals("""{"@context":"https://example.com"}""", actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun contextが文字列の配列のとき配列としてシリアライズされる() {
|
||||
val jsonLd = JsonLd(listOf("https://example.com", "https://www.w3.org/ns/activitystreams"))
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val actual = objectMapper.writeValueAsString(jsonLd)
|
||||
|
||||
assertEquals("""{"@context":["https://example.com","https://www.w3.org/ns/activitystreams"]}""", actual)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package dev.usbharu.hideout.activitypub.domain.model
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl.Companion.public
|
||||
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class NoteSerializeTest {
|
||||
@Test
|
||||
fun Noteのシリアライズができる() {
|
||||
val note = Note(
|
||||
name = "Note",
|
||||
id = "https://example.com",
|
||||
attributedTo = "https://example.com/actor",
|
||||
content = "Hello",
|
||||
published = "2023-05-20T10:28:17.308Z",
|
||||
)
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val writeValueAsString = objectMapper.writeValueAsString(note)
|
||||
|
||||
assertEquals(
|
||||
"{\"type\":\"Note\",\"name\":\"Note\",\"id\":\"https://example.com\",\"attributedTo\":\"https://example.com/actor\",\"content\":\"Hello\",\"published\":\"2023-05-20T10:28:17.308Z\",\"sensitive\":false}",
|
||||
writeValueAsString
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun Noteのデシリアライズができる() {
|
||||
//language=JSON
|
||||
val json = """{
|
||||
"id": "https://misskey.usbharu.dev/notes/9f2i9cm88e",
|
||||
"type": "Note",
|
||||
"attributedTo": "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||
"content": "<p><a href=\"https://calckey.jp/@trapezial\" class=\"u-url mention\">@trapezial@calckey.jp</a><span> いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…</span></p>",
|
||||
"_misskey_content": "@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…",
|
||||
"source": {
|
||||
"content": "@trapezial@calckey.jp いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…",
|
||||
"mediaType": "text/x.misskeymarkdown"
|
||||
},
|
||||
"published": "2023-05-22T14:26:53.600Z",
|
||||
"to": [
|
||||
"https://misskey.usbharu.dev/users/97ws8y3rj6/followers"
|
||||
],
|
||||
"cc": [
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
"https://calckey.jp/users/9bu1xzwjyb"
|
||||
],
|
||||
"inReplyTo": "https://calckey.jp/notes/9f2i7ymf1d",
|
||||
"attachment": [],
|
||||
"sensitive": false,
|
||||
"tag": [
|
||||
{
|
||||
"type": "Mention",
|
||||
"href": "https://calckey.jp/users/9bu1xzwjyb",
|
||||
"name": "@trapezial@calckey.jp"
|
||||
}
|
||||
]
|
||||
}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<Note>(json)
|
||||
|
||||
val note = Note(
|
||||
name = "",
|
||||
id = "https://misskey.usbharu.dev/notes/9f2i9cm88e",
|
||||
type = listOf("Note"),
|
||||
attributedTo = "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||
content = "<p><a href=\"https://calckey.jp/@trapezial\" class=\"u-url mention\">@trapezial@calckey.jp</a><span> いやそういうことじゃなくて、連合先と自インスタンスで状態が狂うことが多いのでどっちに合わせるべきかと…</span></p>",
|
||||
published = "2023-05-22T14:26:53.600Z",
|
||||
to = listOf("https://misskey.usbharu.dev/users/97ws8y3rj6/followers"),
|
||||
cc = listOf(public, "https://calckey.jp/users/9bu1xzwjyb"),
|
||||
sensitive = false,
|
||||
inReplyTo = "https://calckey.jp/notes/9f2i7ymf1d",
|
||||
attachment = emptyList()
|
||||
)
|
||||
note.name = null
|
||||
assertEquals(note, readValue)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package dev.usbharu.hideout.activitypub.domain.model.objects
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ObjectSerializeTest {
|
||||
@Test
|
||||
fun typeが文字列のときデシリアライズできる() {
|
||||
//language=JSON
|
||||
val json = """{"type": "Object"}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<Object>(json)
|
||||
|
||||
val expected = Object(
|
||||
listOf("Object"),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
assertEquals(expected, readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun typeが文字列の配列のときデシリアライズできる() {
|
||||
//language=JSON
|
||||
val json = """{"type": ["Hoge","Object"]}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<Object>(json)
|
||||
|
||||
val expected = Object(
|
||||
listOf("Hoge", "Object"),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
assertEquals(expected, readValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun typeが空のとき無視してデシリアライズする() {
|
||||
//language=JSON
|
||||
val json = """{"type": ""}"""
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<Object>(json)
|
||||
|
||||
val expected = Object(
|
||||
emptyList(),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
assertEquals(expected, readValue)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package dev.usbharu.hideout.activitypub.interfaces.api.actor
|
||||
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Image
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Key
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Person
|
||||
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
|
||||
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.doThrow
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.post
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class UserAPControllerImplTest {
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@Mock
|
||||
private lateinit var apUserService: APUserService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var userAPControllerImpl: UserAPControllerImpl
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(userAPControllerImpl).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `userAp 存在するユーザーにGETするとPersonが返ってくる`(): Unit = runTest {
|
||||
val person = Person(
|
||||
name = "Hoge",
|
||||
id = "https://example.com/users/hoge",
|
||||
preferredUsername = "hoge",
|
||||
summary = "fuga",
|
||||
inbox = "https://example.com/users/hoge/inbox",
|
||||
outbox = "https://example.com/users/hoge/outbox",
|
||||
url = "https://example.com/users/hoge",
|
||||
icon = Image(
|
||||
name = "icon",
|
||||
mediaType = "image/jpeg",
|
||||
url = "https://example.com/users/hoge/icon.jpg"
|
||||
),
|
||||
publicKey = Key(
|
||||
name = "Public Key",
|
||||
id = "https://example.com/users/hoge#pubkey",
|
||||
owner = "https://example.com/users/hoge",
|
||||
publicKeyPem = "-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----",
|
||||
type = emptyList()
|
||||
),
|
||||
endpoints = mapOf("sharedInbox" to "https://example.com/inbox"),
|
||||
followers = "https://example.com/users/hoge/followers",
|
||||
following = "https://example.com/users/hoge/following"
|
||||
)
|
||||
whenever(apUserService.getPersonByName(eq("hoge"))).doReturn(person)
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
mockMvc
|
||||
.get("/users/hoge")
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { this.json(objectMapper.writeValueAsString(person)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `userAP 存在しないユーザーにGETすると404が返ってくる`() = runTest {
|
||||
whenever(apUserService.getPersonByName(eq("fuga"))).doThrow(FailedToGetResourcesException::class)
|
||||
|
||||
mockMvc
|
||||
.get("/users/fuga")
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isNotFound() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `userAP POSTすると405が返ってくる`() {
|
||||
mockMvc
|
||||
.post("/users/hoge")
|
||||
.andExpect { status { isMethodNotAllowed() } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package dev.usbharu.hideout.activitypub.interfaces.api.inbox
|
||||
|
||||
import dev.usbharu.hideout.activitypub.domain.exception.JsonParseException
|
||||
import dev.usbharu.hideout.activitypub.interfaces.api.common.ActivityPubStringResponse
|
||||
import dev.usbharu.hideout.activitypub.service.common.APService
|
||||
import dev.usbharu.hideout.activitypub.service.common.ActivityType
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import io.ktor.http.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.doThrow
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.post
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class InboxControllerImplTest {
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@Mock
|
||||
private lateinit var apService: APService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var inboxController: InboxControllerImpl
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(inboxController).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `inbox 正常なPOSTリクエストをしたときAcceptが返ってくる`() = runTest {
|
||||
|
||||
|
||||
val json = """{"type":"Follow"}"""
|
||||
whenever(apService.parseActivity(eq(json))).doReturn(ActivityType.Follow)
|
||||
whenever(apService.processActivity(eq(json), eq(ActivityType.Follow))).doReturn(
|
||||
ActivityPubStringResponse(
|
||||
HttpStatusCode.Accepted, ""
|
||||
)
|
||||
)
|
||||
|
||||
mockMvc
|
||||
.post("/inbox") {
|
||||
content = json
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect {
|
||||
status { isAccepted() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `inbox parseActivityに失敗したときAcceptが返ってくる`() = runTest {
|
||||
val json = """{"type":"Hoge"}"""
|
||||
whenever(apService.parseActivity(eq(json))).doThrow(JsonParseException::class)
|
||||
|
||||
mockMvc
|
||||
.post("/inbox") {
|
||||
content = json
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect {
|
||||
status { isAccepted() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `inbox processActivityに失敗したときAcceptが返ってくる`() = runTest {
|
||||
val json = """{"type":"Follow"}"""
|
||||
whenever(apService.parseActivity(eq(json))).doReturn(ActivityType.Follow)
|
||||
whenever(
|
||||
apService.processActivity(
|
||||
eq(json),
|
||||
eq(ActivityType.Follow)
|
||||
)
|
||||
).doThrow(FailedToGetResourcesException::class)
|
||||
|
||||
mockMvc
|
||||
.post("/inbox") {
|
||||
content = json
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect {
|
||||
status { isAccepted() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `inbox GETリクエストには405を返す`() {
|
||||
mockMvc.get("/inbox").andExpect { status { isMethodNotAllowed() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `user-inbox 正常なPOSTリクエストをしたときAcceptが返ってくる`() = runTest {
|
||||
|
||||
|
||||
val json = """{"type":"Follow"}"""
|
||||
whenever(apService.parseActivity(eq(json))).doReturn(ActivityType.Follow)
|
||||
whenever(apService.processActivity(eq(json), eq(ActivityType.Follow))).doReturn(
|
||||
ActivityPubStringResponse(
|
||||
HttpStatusCode.Accepted, ""
|
||||
)
|
||||
)
|
||||
|
||||
mockMvc
|
||||
.post("/users/hoge/inbox") {
|
||||
content = json
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect {
|
||||
status { isAccepted() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `user-inbox parseActivityに失敗したときAcceptが返ってくる`() = runTest {
|
||||
val json = """{"type":"Hoge"}"""
|
||||
whenever(apService.parseActivity(eq(json))).doThrow(JsonParseException::class)
|
||||
|
||||
mockMvc
|
||||
.post("/users/hoge/inbox") {
|
||||
content = json
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect {
|
||||
status { isAccepted() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `user-inbox processActivityに失敗したときAcceptが返ってくる`() = runTest {
|
||||
val json = """{"type":"Follow"}"""
|
||||
whenever(apService.parseActivity(eq(json))).doReturn(ActivityType.Follow)
|
||||
whenever(
|
||||
apService.processActivity(
|
||||
eq(json),
|
||||
eq(ActivityType.Follow)
|
||||
)
|
||||
).doThrow(FailedToGetResourcesException::class)
|
||||
|
||||
mockMvc
|
||||
.post("/users/hoge/inbox") {
|
||||
content = json
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect {
|
||||
status { isAccepted() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `user-inbox GETリクエストには405を返す`() {
|
||||
mockMvc.get("/users/hoge/inbox").andExpect { status { isMethodNotAllowed() } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package dev.usbharu.hideout.activitypub.interfaces.api.note
|
||||
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Note
|
||||
import dev.usbharu.hideout.activitypub.service.objects.note.NoteApApiService
|
||||
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||
import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUser
|
||||
import dev.usbharu.httpsignature.common.HttpHeaders
|
||||
import dev.usbharu.httpsignature.common.HttpMethod
|
||||
import dev.usbharu.httpsignature.common.HttpRequest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.*
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
|
||||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
import org.springframework.security.web.FilterChainProxy
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder
|
||||
import java.net.URL
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class NoteApControllerImplTest {
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@Mock
|
||||
private lateinit var noteApApiService: NoteApApiService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var noteApControllerImpl: NoteApControllerImpl
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(noteApControllerImpl)
|
||||
.apply<StandaloneMockMvcBuilder>(
|
||||
springSecurity(
|
||||
FilterChainProxy(
|
||||
DefaultSecurityFilterChain(
|
||||
AnyRequestMatcher.INSTANCE
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `postAP 匿名で取得できる`() = runTest {
|
||||
|
||||
val note = Note(
|
||||
name = "Note",
|
||||
id = "https://example.com/users/hoge/posts/1234",
|
||||
attributedTo = "https://example.com/users/hoge",
|
||||
content = "Hello",
|
||||
published = "2023-11-02T15:30:34.160Z"
|
||||
)
|
||||
whenever(noteApApiService.getNote(eq(1234), isNull())).doReturn(
|
||||
note
|
||||
)
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
mockMvc
|
||||
.get("/users/hoge/posts/1234") {
|
||||
with(anonymous())
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(note)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `postAP 存在しない場合は404`() = runTest {
|
||||
whenever(noteApApiService.getNote(eq(123), isNull())).doReturn(null)
|
||||
|
||||
mockMvc
|
||||
.get("/users/hoge/posts/123") {
|
||||
with(anonymous())
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isNotFound() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `postAP 認証に成功している場合userIdがnullでない`() = runTest {
|
||||
val note = Note(
|
||||
name = "Note",
|
||||
id = "https://example.com/users/hoge/posts/1234",
|
||||
attributedTo = "https://example.com/users/hoge",
|
||||
content = "Hello",
|
||||
published = "2023-11-02T15:30:34.160Z"
|
||||
)
|
||||
whenever(noteApApiService.getNote(eq(1234), isNotNull())).doReturn(note)
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
val preAuthenticatedAuthenticationToken = PreAuthenticatedAuthenticationToken(
|
||||
"", HttpRequest(
|
||||
URL("https://follower.example.com"),
|
||||
HttpHeaders(
|
||||
mapOf()
|
||||
), HttpMethod.GET
|
||||
)
|
||||
).apply { details = HttpSignatureUser("fuga", "follower.example.com", 123, true, true, mutableListOf()) }
|
||||
SecurityContextHolder.getContext().authentication = preAuthenticatedAuthenticationToken
|
||||
|
||||
mockMvc.get("/users/hoge/posts/1234") {
|
||||
with(
|
||||
authentication(
|
||||
preAuthenticatedAuthenticationToken
|
||||
)
|
||||
)
|
||||
}.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(note)) } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package dev.usbharu.hideout.activitypub.interfaces.api.outbox
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.post
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class OutboxControllerImplTest {
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var outboxController: OutboxControllerImpl
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc =
|
||||
MockMvcBuilders.standaloneSetup(outboxController).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `outbox GETに501を返す`() {
|
||||
mockMvc
|
||||
.get("/outbox")
|
||||
.asyncDispatch()
|
||||
.andDo { print() }
|
||||
.andExpect { status { isNotImplemented() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `user-outbox GETに501を返す`() {
|
||||
mockMvc
|
||||
.get("/users/hoge/outbox")
|
||||
.asyncDispatch()
|
||||
.andDo { print() }
|
||||
.andExpect { status { isNotImplemented() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `outbox POSTに501を返す`() {
|
||||
mockMvc
|
||||
.post("/outbox")
|
||||
.asyncDispatch()
|
||||
.andDo { print() }
|
||||
.andExpect { status { isNotImplemented() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `user-outbox POSTに501を返す`() {
|
||||
mockMvc
|
||||
.post("/users/hoge/outbox")
|
||||
.asyncDispatch()
|
||||
.andDo { print() }
|
||||
.andExpect { status { isNotImplemented() } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package dev.usbharu.hideout.activitypub.interfaces.api.webfinger
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import dev.usbharu.hideout.activitypub.domain.model.webfinger.WebFinger
|
||||
import dev.usbharu.hideout.activitypub.service.webfinger.WebFingerApiService
|
||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.doThrow
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import utils.UserBuilder
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class WebFingerControllerTest {
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@Mock
|
||||
private lateinit var webFingerApiService: WebFingerApiService
|
||||
|
||||
@Mock
|
||||
private lateinit var applicationConfig: ApplicationConfig
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var webFingerController: WebFingerController
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
this.mockMvc = MockMvcBuilders.standaloneSetup(webFingerController).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `webfinger 存在するacctを指定したとき200 OKでWebFingerのレスポンスが返ってくる`() = runTest {
|
||||
|
||||
val user = UserBuilder.localUserOf()
|
||||
whenever(
|
||||
webFingerApiService.findByNameAndDomain(
|
||||
eq("hoge"),
|
||||
eq("example.com")
|
||||
)
|
||||
).doReturn(user)
|
||||
|
||||
val contentAsString = mockMvc.perform(get("/.well-known/webfinger?resource=acct:hoge@example.com"))
|
||||
.andDo(print())
|
||||
.andExpect(status().isOk())
|
||||
.andReturn()
|
||||
.response
|
||||
.contentAsString
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
val readValue = objectMapper.readValue<WebFinger>(contentAsString)
|
||||
|
||||
val expected = WebFinger(
|
||||
subject = "acct:${user.name}@${user.domain}",
|
||||
listOf(
|
||||
WebFinger.Link(
|
||||
"self",
|
||||
"application/activity+json",
|
||||
user.url
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assertThat(readValue).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `webfinger 存在しないacctを指定したとき404 Not Foundが返ってくる`() = runTest {
|
||||
whenever(webFingerApiService.findByNameAndDomain(eq("fuga"), eq("example.com"))).doThrow(
|
||||
FailedToGetResourcesException::class
|
||||
)
|
||||
|
||||
mockMvc.perform(get("/.well-known/webfinger?resource=acct:fuga@example.com"))
|
||||
.andDo(print())
|
||||
.andExpect(status().isNotFound)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `webfinger acctとして解釈できない場合は400 Bad Requestが返ってくる`() {
|
||||
mockMvc.perform(get("/.well-known/webfinger?resource=@hello@aa@aab@aaa"))
|
||||
.andDo(print())
|
||||
.andExpect(status().isBadRequest)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package dev.usbharu.hideout.activitypub.service.activity.create
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Note
|
||||
import dev.usbharu.hideout.activitypub.query.NoteQueryService
|
||||
import dev.usbharu.hideout.activitypub.service.objects.note.APNoteServiceImpl
|
||||
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.core.external.job.DeliverPostJob
|
||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
||||
import dev.usbharu.hideout.core.query.UserQueryService
|
||||
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Spy
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.*
|
||||
import utils.PostBuilder
|
||||
import utils.UserBuilder
|
||||
import java.net.URL
|
||||
import java.time.Instant
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class ApSendCreateServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var followerQueryService: FollowerQueryService
|
||||
|
||||
@Spy
|
||||
private val objectMapper: ObjectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
@Mock
|
||||
private lateinit var jobQueueParentService: JobQueueParentService
|
||||
|
||||
@Mock
|
||||
private lateinit var userQueryService: UserQueryService
|
||||
|
||||
@Mock
|
||||
private lateinit var noteQueryService: NoteQueryService
|
||||
|
||||
@Spy
|
||||
private val applicationConfig: ApplicationConfig = ApplicationConfig(URL("https://example.com"))
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var apSendCreateServiceImpl: ApSendCreateServiceImpl
|
||||
|
||||
@Test
|
||||
fun `createNote 正常なPostでCreateのジョブを発行できる`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
val user = UserBuilder.localUserOf(id = post.userId)
|
||||
val note = Note(
|
||||
name = "Post",
|
||||
id = post.apId,
|
||||
attributedTo = user.url,
|
||||
content = post.text,
|
||||
published = Instant.ofEpochMilli(post.createdAt).toString(),
|
||||
to = listOfNotNull(APNoteServiceImpl.public, user.followers),
|
||||
sensitive = post.sensitive,
|
||||
cc = listOfNotNull(APNoteServiceImpl.public, user.followers),
|
||||
inReplyTo = null
|
||||
)
|
||||
val followers = listOf(
|
||||
UserBuilder.remoteUserOf(),
|
||||
UserBuilder.remoteUserOf(),
|
||||
UserBuilder.remoteUserOf()
|
||||
)
|
||||
|
||||
whenever(followerQueryService.findFollowersById(eq(post.userId))).doReturn(followers)
|
||||
whenever(userQueryService.findById(eq(post.userId))).doReturn(user)
|
||||
whenever(noteQueryService.findById(eq(post.id))).doReturn(note to post)
|
||||
|
||||
apSendCreateServiceImpl.createNote(post)
|
||||
|
||||
verify(jobQueueParentService, times(followers.size)).schedule(eq(DeliverPostJob), any())
|
||||
}
|
||||
}
|
|
@ -70,8 +70,8 @@ class APReceiveFollowServiceImplTest {
|
|||
"type": "Follow",
|
||||
"name": "Follow",
|
||||
"actor": "https://follower.example.com",
|
||||
"object": "https://example.com",
|
||||
"@context": null
|
||||
"object": "https://example.com"
|
||||
|
||||
}"""
|
||||
),
|
||||
Json.parseToJsonElement(follow)
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
package dev.usbharu.hideout.core.service.post
|
||||
|
||||
import dev.usbharu.hideout.activitypub.service.activity.create.ApSendCreateService
|
||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
||||
import dev.usbharu.hideout.core.query.PostQueryService
|
||||
import dev.usbharu.hideout.core.service.timeline.TimelineService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mockStatic
|
||||
import org.mockito.Spy
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.*
|
||||
import org.springframework.dao.DuplicateKeyException
|
||||
import utils.PostBuilder
|
||||
import utils.UserBuilder
|
||||
import java.time.Instant
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class PostServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var postRepository: PostRepository
|
||||
|
||||
@Mock
|
||||
private lateinit var userRepository: UserRepository
|
||||
|
||||
@Mock
|
||||
private lateinit var timelineService: TimelineService
|
||||
|
||||
@Mock
|
||||
private lateinit var postQueryService: PostQueryService
|
||||
|
||||
@Spy
|
||||
private var postBuilder: Post.PostBuilder = Post.PostBuilder(CharacterLimit())
|
||||
|
||||
@Mock
|
||||
private lateinit var apSendCreateService: ApSendCreateService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var postServiceImpl: PostServiceImpl
|
||||
|
||||
@Test
|
||||
fun `createLocal 正常にpostを作成できる`() = runTest {
|
||||
|
||||
val now = Instant.now()
|
||||
val post = PostBuilder.of(createdAt = now.toEpochMilli())
|
||||
|
||||
whenever(postRepository.save(eq(post))).doReturn(true)
|
||||
whenever(postRepository.generateId()).doReturn(post.id)
|
||||
whenever(userRepository.findById(eq(post.userId))).doReturn(UserBuilder.localUserOf(id = post.userId))
|
||||
whenever(timelineService.publishTimeline(eq(post), eq(true))).doReturn(Unit)
|
||||
|
||||
mockStatic(Instant::class.java, Mockito.CALLS_REAL_METHODS).use {
|
||||
|
||||
it.`when`<Instant>(Instant::now).doReturn(now)
|
||||
val createLocal = postServiceImpl.createLocal(
|
||||
PostCreateDto(
|
||||
post.text,
|
||||
post.overview,
|
||||
post.visibility,
|
||||
post.repostId,
|
||||
post.replyId,
|
||||
post.userId,
|
||||
post.mediaIds
|
||||
)
|
||||
)
|
||||
|
||||
assertThat(createLocal).isEqualTo(post)
|
||||
}
|
||||
|
||||
verify(postRepository, times(1)).save(eq(post))
|
||||
verify(timelineService, times(1)).publishTimeline(eq(post), eq(true))
|
||||
verify(apSendCreateService, times(1)).createNote(eq(post))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createRemote 正常にリモートのpostを作成できる`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
|
||||
whenever(postRepository.save(eq(post))).doReturn(true)
|
||||
whenever(timelineService.publishTimeline(eq(post), eq(false))).doReturn(Unit)
|
||||
|
||||
val createLocal = postServiceImpl.createRemote(post)
|
||||
|
||||
assertThat(createLocal).isEqualTo(post)
|
||||
|
||||
|
||||
verify(postRepository, times(1)).save(eq(post))
|
||||
verify(timelineService, times(1)).publishTimeline(eq(post), eq(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createRemote 既に作成されていた場合はそのまま帰す`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
|
||||
whenever(postRepository.save(eq(post))).doReturn(false)
|
||||
|
||||
val createLocal = postServiceImpl.createRemote(post)
|
||||
|
||||
assertThat(createLocal).isEqualTo(post)
|
||||
|
||||
verify(postRepository, times(1)).save(eq(post))
|
||||
verify(timelineService, times(0)).publishTimeline(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createRemote 既に作成されていることを検知できず例外が発生した場合はDBから取得して返す`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
|
||||
whenever(postRepository.save(eq(post))).doAnswer { throw ExposedSQLException(null, emptyList(), mock()) }
|
||||
whenever(postQueryService.findByApId(eq(post.apId))).doReturn(post)
|
||||
|
||||
val createLocal = postServiceImpl.createRemote(post)
|
||||
|
||||
assertThat(createLocal).isEqualTo(post)
|
||||
|
||||
verify(postRepository, times(1)).save(eq(post))
|
||||
verify(timelineService, times(0)).publishTimeline(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createRemote 既に作成されていることを検知出来ずタイムラインにpush出来なかった場合何もしない`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
|
||||
whenever(postRepository.save(eq(post))).doReturn(true)
|
||||
whenever(timelineService.publishTimeline(eq(post), eq(false))).doThrow(DuplicateKeyException::class)
|
||||
|
||||
val createLocal = postServiceImpl.createRemote(post)
|
||||
|
||||
assertThat(createLocal).isEqualTo(post)
|
||||
|
||||
verify(postRepository, times(1)).save(eq(post))
|
||||
verify(timelineService, times(1)).publishTimeline(eq(post), eq(false))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package dev.usbharu.hideout.core.service.reaction
|
||||
|
||||
|
||||
import dev.usbharu.hideout.activitypub.service.activity.like.APReactionService
|
||||
import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import dev.usbharu.hideout.core.domain.model.reaction.Reaction
|
||||
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||
import dev.usbharu.hideout.core.query.ReactionQueryService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.*
|
||||
import utils.PostBuilder
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class ReactionServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var reactionRepository: ReactionRepository
|
||||
|
||||
@Mock
|
||||
private lateinit var apReactionService: APReactionService
|
||||
|
||||
@Mock
|
||||
private lateinit var reactionQueryService: ReactionQueryService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var reactionServiceImpl: ReactionServiceImpl
|
||||
|
||||
@Test
|
||||
fun `receiveReaction リアクションが存在しないとき保存する`() = runTest {
|
||||
|
||||
val post = PostBuilder.of()
|
||||
|
||||
whenever(reactionQueryService.reactionAlreadyExist(eq(post.id), eq(post.userId), eq(0))).doReturn(false)
|
||||
val generateId = TwitterSnowflakeIdGenerateService.generateId()
|
||||
whenever(reactionRepository.generateId()).doReturn(generateId)
|
||||
|
||||
reactionServiceImpl.receiveReaction("❤", "example.com", post.userId, post.id)
|
||||
|
||||
verify(reactionRepository, times(1)).save(eq(Reaction(generateId, 0, post.id, post.userId)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `receiveReaction リアクションが既に作成されていることを検知出来ずに例外が発生した場合は何もしない`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
|
||||
whenever(reactionQueryService.reactionAlreadyExist(eq(post.id), eq(post.userId), eq(0))).doReturn(false)
|
||||
val generateId = TwitterSnowflakeIdGenerateService.generateId()
|
||||
whenever(
|
||||
reactionRepository.save(
|
||||
eq(
|
||||
Reaction(
|
||||
id = generateId,
|
||||
emojiId = 0,
|
||||
postId = post.id,
|
||||
userId = post.userId
|
||||
)
|
||||
)
|
||||
)
|
||||
).doAnswer {
|
||||
throw ExposedSQLException(
|
||||
null,
|
||||
emptyList(), mock()
|
||||
)
|
||||
}
|
||||
whenever(reactionRepository.generateId()).doReturn(generateId)
|
||||
|
||||
reactionServiceImpl.receiveReaction("❤", "example.com", post.userId, post.id)
|
||||
|
||||
verify(reactionRepository, times(1)).save(eq(Reaction(generateId, 0, post.id, post.userId)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `receiveReaction リアクションが既に作成されている場合は何もしない`() = runTest() {
|
||||
val post = PostBuilder.of()
|
||||
whenever(reactionQueryService.reactionAlreadyExist(eq(post.id), eq(post.userId), eq(0))).doReturn(true)
|
||||
|
||||
reactionServiceImpl.receiveReaction("❤", "example.com", post.userId, post.id)
|
||||
|
||||
verify(reactionRepository, never()).save(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendReaction リアクションが存在しないとき保存して配送する`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
whenever(reactionQueryService.findByPostIdAndUserIdAndEmojiId(eq(post.id), eq(post.userId), eq(0))).doThrow(
|
||||
FailedToGetResourcesException::class
|
||||
)
|
||||
val generateId = TwitterSnowflakeIdGenerateService.generateId()
|
||||
whenever(reactionRepository.generateId()).doReturn(generateId)
|
||||
|
||||
reactionServiceImpl.sendReaction("❤", post.userId, post.id)
|
||||
|
||||
verify(reactionRepository, times(1)).save(eq(Reaction(generateId, 0, post.id, post.userId)))
|
||||
verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, 0, post.id, post.userId)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendReaction リアクションが存在するときは削除して保存して配送する`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
val id = TwitterSnowflakeIdGenerateService.generateId()
|
||||
whenever(reactionQueryService.findByPostIdAndUserIdAndEmojiId(eq(post.id), eq(post.userId), eq(0))).doReturn(
|
||||
Reaction(id, 0, post.id, post.userId)
|
||||
)
|
||||
val generateId = TwitterSnowflakeIdGenerateService.generateId()
|
||||
whenever(reactionRepository.generateId()).doReturn(generateId)
|
||||
|
||||
reactionServiceImpl.sendReaction("❤", post.userId, post.id)
|
||||
|
||||
|
||||
verify(reactionRepository, times(1)).delete(eq(Reaction(id, 0, post.id, post.userId)))
|
||||
verify(reactionRepository, times(1)).save(eq(Reaction(generateId, 0, post.id, post.userId)))
|
||||
verify(apReactionService, times(1)).removeReaction(eq(Reaction(id, 0, post.id, post.userId)))
|
||||
verify(apReactionService, times(1)).reaction(eq(Reaction(generateId, 0, post.id, post.userId)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `removeReaction リアクションが存在する場合削除して配送`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
whenever(reactionQueryService.findByPostIdAndUserIdAndEmojiId(eq(post.id), eq(post.userId), eq(0))).doReturn(
|
||||
Reaction(0, 0, post.id, post.userId)
|
||||
)
|
||||
|
||||
reactionServiceImpl.removeReaction(post.userId, post.id)
|
||||
|
||||
verify(reactionRepository, times(1)).delete(eq(Reaction(0, 0, post.id, post.userId)))
|
||||
verify(apReactionService, times(1)).removeReaction(eq(Reaction(0, 0, post.id, post.userId)))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package dev.usbharu.hideout.core.service.timeline
|
||||
|
||||
import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
import dev.usbharu.hideout.core.domain.model.timeline.Timeline
|
||||
import dev.usbharu.hideout.core.domain.model.timeline.TimelineRepository
|
||||
import dev.usbharu.hideout.core.domain.model.user.User
|
||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
||||
import dev.usbharu.hideout.core.query.UserQueryService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.*
|
||||
import utils.PostBuilder
|
||||
import utils.UserBuilder
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class TimelineServiceTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var followerQueryService: FollowerQueryService
|
||||
|
||||
@Mock
|
||||
private lateinit var userQueryService: UserQueryService
|
||||
|
||||
@Mock
|
||||
private lateinit var timelineRepository: TimelineRepository
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var timelineService: TimelineService
|
||||
|
||||
@Captor
|
||||
private lateinit var captor: ArgumentCaptor<List<Timeline>>
|
||||
|
||||
@Test
|
||||
fun `publishTimeline ローカルの投稿はローカルのフォロワーと投稿者のタイムラインに追加される`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
val listOf = listOf<User>(UserBuilder.localUserOf(), UserBuilder.localUserOf())
|
||||
val localUserOf = UserBuilder.localUserOf(id = post.userId)
|
||||
|
||||
whenever(followerQueryService.findFollowersById(eq(post.userId))).doReturn(listOf)
|
||||
whenever(userQueryService.findById(eq(post.userId))).doReturn(localUserOf)
|
||||
whenever(timelineRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId())
|
||||
|
||||
|
||||
timelineService.publishTimeline(post, true)
|
||||
|
||||
verify(timelineRepository).saveAll(capture(captor))
|
||||
val timelineList = captor.value
|
||||
|
||||
assertThat(timelineList).hasSize(4).anyMatch { it.userId == post.userId }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `publishTimeline リモートの投稿はローカルのフォロワーのタイムラインに追加される`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
val listOf = listOf<User>(UserBuilder.localUserOf(), UserBuilder.localUserOf())
|
||||
|
||||
whenever(followerQueryService.findFollowersById(eq(post.userId))).doReturn(listOf)
|
||||
whenever(timelineRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId())
|
||||
|
||||
|
||||
timelineService.publishTimeline(post, false)
|
||||
|
||||
verify(timelineRepository).saveAll(capture(captor))
|
||||
val timelineList = captor.value
|
||||
|
||||
assertThat(timelineList).hasSize(3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `publishTimeline パブリック投稿はパブリックタイムラインにも追加される`() = runTest {
|
||||
val post = PostBuilder.of()
|
||||
val listOf = listOf<User>(UserBuilder.localUserOf(), UserBuilder.localUserOf())
|
||||
|
||||
whenever(followerQueryService.findFollowersById(eq(post.userId))).doReturn(listOf)
|
||||
whenever(timelineRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId())
|
||||
|
||||
|
||||
timelineService.publishTimeline(post, false)
|
||||
|
||||
verify(timelineRepository).saveAll(capture(captor))
|
||||
val timelineList = captor.value
|
||||
|
||||
assertThat(timelineList).hasSize(3).anyMatch { it.userId == 0L }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `publishTimeline パブリック投稿ではない場合はローカルのフォロワーのみに追加される`() = runTest {
|
||||
val post = PostBuilder.of(visibility = Visibility.UNLISTED)
|
||||
val listOf = listOf<User>(UserBuilder.localUserOf(), UserBuilder.localUserOf())
|
||||
|
||||
whenever(followerQueryService.findFollowersById(eq(post.userId))).doReturn(listOf)
|
||||
whenever(timelineRepository.generateId()).doReturn(TwitterSnowflakeIdGenerateService.generateId())
|
||||
|
||||
|
||||
timelineService.publishTimeline(post, false)
|
||||
|
||||
verify(timelineRepository).saveAll(capture(captor))
|
||||
val timelineList = captor.value
|
||||
|
||||
assertThat(timelineList).hasSize(2).noneMatch { it.userId == 0L }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package dev.usbharu.hideout.mastodon.interfaces.api.account
|
||||
|
||||
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.CredentialAccount
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.CredentialAccountSource
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Role
|
||||
import dev.usbharu.hideout.mastodon.service.account.AccountApiService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Spy
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.oauth2.jwt.Jwt
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.post
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import utils.TestTransaction
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class MastodonAccountApiControllerTest {
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@Spy
|
||||
private lateinit var testTransaction: TestTransaction
|
||||
|
||||
@Mock
|
||||
private lateinit var accountApiService: AccountApiService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var mastodonAccountApiController: MastodonAccountApiController
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(mastodonAccountApiController).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1AccountsVerifyCredentialsGet JWTで認証時に200が返ってくる`() = runTest {
|
||||
|
||||
val createEmptyContext = SecurityContextHolder.createEmptyContext()
|
||||
createEmptyContext.authentication = JwtAuthenticationToken(
|
||||
Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build()
|
||||
)
|
||||
SecurityContextHolder.setContext(createEmptyContext)
|
||||
val credentialAccount = CredentialAccount(
|
||||
id = "",
|
||||
username = "",
|
||||
acct = "",
|
||||
url = "",
|
||||
displayName = "",
|
||||
note = "",
|
||||
avatar = "",
|
||||
avatarStatic = "",
|
||||
header = "",
|
||||
headerStatic = "",
|
||||
locked = false,
|
||||
fields = emptyList(),
|
||||
emojis = emptyList(),
|
||||
bot = false,
|
||||
group = false,
|
||||
discoverable = true,
|
||||
createdAt = "",
|
||||
lastStatusAt = "",
|
||||
statusesCount = 0,
|
||||
followersCount = 0,
|
||||
source = CredentialAccountSource(
|
||||
note = "",
|
||||
fields = emptyList(),
|
||||
privacy = CredentialAccountSource.Privacy.public,
|
||||
sensitive = false,
|
||||
followRequestsCount = 0
|
||||
),
|
||||
noindex = false,
|
||||
moved = false,
|
||||
suspendex = false,
|
||||
limited = false,
|
||||
followingCount = 0,
|
||||
role = Role(0, "ADMIN", "", 0, false)
|
||||
)
|
||||
whenever(accountApiService.verifyCredentials(eq(1234))).doReturn(credentialAccount)
|
||||
|
||||
val objectMapper = ActivityPubConfig().objectMapper()
|
||||
|
||||
mockMvc
|
||||
.get("/api/v1/accounts/verify_credentials")
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(credentialAccount)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1AccountsVerifyCredentialsGet POSTは405が返ってくる`() {
|
||||
mockMvc.post("/api/v1/accounts/verify_credentials")
|
||||
.andExpect { status { isMethodNotAllowed() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1AccountsPost GETは405が返ってくる`() {
|
||||
mockMvc.get("/api/v1/accounts")
|
||||
.andExpect { status { isMethodNotAllowed() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1AccountsPost アカウント作成成功時302とアカウントのurlが返ってくる`() {
|
||||
mockMvc
|
||||
.post("/api/v1/accounts") {
|
||||
contentType = MediaType.APPLICATION_FORM_URLENCODED
|
||||
param("username", "hoge")
|
||||
param("password", "very_secure_password")
|
||||
param("email", "email@example.com")
|
||||
param("agreement", "true")
|
||||
param("locale", "true")
|
||||
}.asyncDispatch()
|
||||
.andExpect { header { string("location", "/users/hoge") } }
|
||||
.andExpect { status { isFound() } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package dev.usbharu.hideout.mastodon.interfaces.api.apps
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Application
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.AppsRequest
|
||||
import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor
|
||||
import dev.usbharu.hideout.mastodon.service.app.AppApiService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.converter.HttpMessageConverter
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.post
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class MastodonAppsApiControllerTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var appApiService: AppApiService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var mastodonAppsApiController: MastodonAppsApiController
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(mastodonAppsApiController).setCustomArgumentResolvers(
|
||||
JsonOrFormModelMethodProcessor(
|
||||
ModelAttributeMethodProcessor(false), RequestResponseBodyMethodProcessor(
|
||||
mutableListOf<HttpMessageConverter<*>>(
|
||||
MappingJackson2HttpMessageConverter()
|
||||
)
|
||||
)
|
||||
)
|
||||
).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1AppsPost JSONで作成に成功したら200が返ってくる`() = runTest {
|
||||
|
||||
val appsRequest = AppsRequest(
|
||||
"test",
|
||||
"https://example.com",
|
||||
"write",
|
||||
null
|
||||
)
|
||||
val application = Application(
|
||||
"test",
|
||||
"",
|
||||
null,
|
||||
"safdash;",
|
||||
"aksdhgoa"
|
||||
)
|
||||
|
||||
whenever(appApiService.createApp(eq(appsRequest))).doReturn(application)
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
val writeValueAsString = objectMapper.writeValueAsString(appsRequest)
|
||||
|
||||
mockMvc
|
||||
.post("/api/v1/apps") {
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
content = writeValueAsString
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(application)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1AppsPost FORMで作成に成功したら200が返ってくる`() = runTest {
|
||||
|
||||
val appsRequest = AppsRequest(
|
||||
"test",
|
||||
"https://example.com",
|
||||
"write",
|
||||
null
|
||||
)
|
||||
val application = Application(
|
||||
"test",
|
||||
"",
|
||||
null,
|
||||
"safdash;",
|
||||
"aksdhgoa"
|
||||
)
|
||||
|
||||
whenever(appApiService.createApp(eq(appsRequest))).doReturn(application)
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
mockMvc
|
||||
.post("/api/v1/apps") {
|
||||
contentType = MediaType.APPLICATION_FORM_URLENCODED
|
||||
param("client_name", "test")
|
||||
param("redirect_uris", "https://example.com")
|
||||
param("scopes", "write")
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(application)) } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package dev.usbharu.hideout.mastodon.interfaces.api.instance
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.*
|
||||
import dev.usbharu.hideout.mastodon.service.instance.InstanceApiService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.post
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class MastodonInstanceApiControllerTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var instanceApiService: InstanceApiService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var mastodonInstanceApiController: MastodonInstanceApiController
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(mastodonInstanceApiController).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1InstanceGet GETしたら200が返ってくる`() = runTest {
|
||||
|
||||
val v1Instance = V1Instance(
|
||||
uri = "https://example.com",
|
||||
title = "hideout",
|
||||
shortDescription = "test",
|
||||
description = "test instance",
|
||||
email = "test@example.com",
|
||||
version = "0.0.1",
|
||||
urls = V1InstanceUrls(streamingApi = "https://example.com/atreaming"),
|
||||
stats = V1InstanceStats(userCount = 1, statusCount = 0, domainCount = 0),
|
||||
thumbnail = "https://example.com",
|
||||
languages = emptyList(),
|
||||
registrations = false,
|
||||
approvalRequired = false,
|
||||
invitesEnabled = false,
|
||||
configuration = V1InstanceConfiguration(
|
||||
accounts = V1InstanceConfigurationAccounts(0),
|
||||
V1InstanceConfigurationStatuses(100, 4, 23),
|
||||
V1InstanceConfigurationMediaAttachments(emptyList(), 100, 100, 100, 100, 100),
|
||||
V1InstanceConfigurationPolls(
|
||||
10, 10, 10, 10
|
||||
)
|
||||
),
|
||||
contactAccount = Account(
|
||||
id = "",
|
||||
username = "",
|
||||
acct = "",
|
||||
url = "",
|
||||
displayName = "",
|
||||
note = "",
|
||||
avatar = "",
|
||||
avatarStatic = "",
|
||||
header = "",
|
||||
headerStatic = "",
|
||||
locked = false,
|
||||
fields = emptyList(),
|
||||
emojis = emptyList(),
|
||||
bot = false,
|
||||
group = false,
|
||||
discoverable = true,
|
||||
createdAt = "",
|
||||
lastStatusAt = "",
|
||||
statusesCount = 0,
|
||||
followersCount = 0,
|
||||
noindex = false,
|
||||
moved = false,
|
||||
suspendex = false,
|
||||
limited = false,
|
||||
followingCount = 0
|
||||
),
|
||||
emptyList()
|
||||
)
|
||||
whenever(instanceApiService.v1Instance()).doReturn(v1Instance)
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
mockMvc
|
||||
.get("/api/v1/instance")
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(objectMapper)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1InstanceGet POSTしたら405が返ってくる`() {
|
||||
mockMvc
|
||||
.post("/api/v1/instance")
|
||||
.andExpect { status { isMethodNotAllowed() } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package dev.usbharu.hideout.mastodon.interfaces.api.media
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.MediaAttachment
|
||||
import dev.usbharu.hideout.mastodon.service.media.MediaApiService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.springframework.mock.web.MockMultipartFile
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.multipart
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class MastodonMediaApiControllerTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var mediaApiService: MediaApiService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var mastodonMediaApiController: MastodonMediaApiController
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(mastodonMediaApiController).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1MediaPost ファイルとサムネイルをアップロードできる`() = runTest {
|
||||
|
||||
val mediaAttachment = MediaAttachment(
|
||||
id = "1234",
|
||||
type = MediaAttachment.Type.image,
|
||||
url = "https://example.com",
|
||||
previewUrl = "https://example.com",
|
||||
remoteUrl = "https://example.com",
|
||||
description = "pngImageStream",
|
||||
blurhash = "",
|
||||
textUrl = "https://example.com"
|
||||
)
|
||||
whenever(mediaApiService.postMedia(any())).doReturn(mediaAttachment)
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
mockMvc
|
||||
.multipart("/api/v1/media") {
|
||||
file(MockMultipartFile("file", "test.png", "image/png", "jpgImageStream".toByteArray()))
|
||||
file(MockMultipartFile("thumbnail", "thumbnail.png", "image/png", "pngImageStream".toByteArray()))
|
||||
param("description", "jpgImage")
|
||||
param("focus", "")
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(mediaAttachment)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1MediaPost ファイルだけをアップロードできる`() = runTest {
|
||||
|
||||
val mediaAttachment = MediaAttachment(
|
||||
id = "1234",
|
||||
type = MediaAttachment.Type.image,
|
||||
url = "https://example.com",
|
||||
previewUrl = "https://example.com",
|
||||
remoteUrl = "https://example.com",
|
||||
description = "pngImageStream",
|
||||
blurhash = "",
|
||||
textUrl = "https://example.com"
|
||||
)
|
||||
whenever(mediaApiService.postMedia(any())).doReturn(mediaAttachment)
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
mockMvc
|
||||
.multipart("/api/v1/media") {
|
||||
file(MockMultipartFile("file", "test.png", "image/png", "jpgImageStream".toByteArray()))
|
||||
param("description", "jpgImage")
|
||||
param("focus", "")
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(mediaAttachment)) } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package dev.usbharu.hideout.mastodon.interfaces.api.status
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Account
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||
import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor
|
||||
import dev.usbharu.hideout.mastodon.service.status.StatusesApiService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.eq
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.converter.HttpMessageConverter
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.oauth2.jwt.Jwt
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.post
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class MastodonStatusesApiControllerTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var statusesApiService: StatusesApiService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var mastodonStatusesApiController: MastodonStatusesApiContoller
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(mastodonStatusesApiController).setCustomArgumentResolvers(
|
||||
JsonOrFormModelMethodProcessor(
|
||||
ModelAttributeMethodProcessor(false), RequestResponseBodyMethodProcessor(
|
||||
mutableListOf<HttpMessageConverter<*>>(
|
||||
MappingJackson2HttpMessageConverter()
|
||||
)
|
||||
)
|
||||
)
|
||||
).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1StatusesPost JWT認証時POSTすると投稿できる`() = runTest {
|
||||
val createEmptyContext = SecurityContextHolder.createEmptyContext()
|
||||
createEmptyContext.authentication = JwtAuthenticationToken(
|
||||
Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build()
|
||||
)
|
||||
SecurityContextHolder.setContext(createEmptyContext)
|
||||
val status = Status(
|
||||
id = "",
|
||||
uri = "",
|
||||
createdAt = "",
|
||||
account = Account(
|
||||
id = "",
|
||||
username = "",
|
||||
acct = "",
|
||||
url = "",
|
||||
displayName = "",
|
||||
note = "",
|
||||
avatar = "",
|
||||
avatarStatic = "",
|
||||
header = "",
|
||||
headerStatic = "",
|
||||
locked = false,
|
||||
fields = emptyList(),
|
||||
emojis = emptyList(),
|
||||
bot = false,
|
||||
group = false,
|
||||
discoverable = true,
|
||||
createdAt = "",
|
||||
lastStatusAt = "",
|
||||
statusesCount = 0,
|
||||
followersCount = 0,
|
||||
noindex = false,
|
||||
moved = false,
|
||||
suspendex = false,
|
||||
limited = false,
|
||||
followingCount = 0
|
||||
),
|
||||
content = "",
|
||||
visibility = Status.Visibility.public,
|
||||
sensitive = false,
|
||||
spoilerText = "",
|
||||
mediaAttachments = emptyList(),
|
||||
mentions = emptyList(),
|
||||
tags = emptyList(),
|
||||
emojis = emptyList(),
|
||||
reblogsCount = 0,
|
||||
favouritesCount = 0,
|
||||
repliesCount = 0,
|
||||
url = "https://example.com",
|
||||
inReplyToId = null,
|
||||
inReplyToAccountId = null,
|
||||
language = "ja_JP",
|
||||
text = "Test",
|
||||
editedAt = null
|
||||
|
||||
)
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
val statusesRequest = StatusesRequest()
|
||||
|
||||
statusesRequest.status = "hello"
|
||||
|
||||
whenever(statusesApiService.postStatus(eq(statusesRequest), eq(1234))).doReturn(status)
|
||||
|
||||
mockMvc
|
||||
.post("/api/v1/statuses") {
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
content = objectMapper.writeValueAsString(statusesRequest)
|
||||
}
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(status)) } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
package dev.usbharu.hideout.mastodon.interfaces.api.timeline
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Account
|
||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Status
|
||||
import dev.usbharu.hideout.mastodon.service.timeline.TimelineApiService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.*
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.oauth2.jwt.Jwt
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.post
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class MastodonTimelineApiControllerTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var timelineApiService: TimelineApiService
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var mastodonTimelineApiController: MastodonTimelineApiController
|
||||
|
||||
private lateinit var mockMvc: MockMvc
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(mastodonTimelineApiController).build()
|
||||
}
|
||||
|
||||
val statusList = listOf<Status>(
|
||||
Status(
|
||||
id = "",
|
||||
uri = "",
|
||||
createdAt = "",
|
||||
account = Account(
|
||||
id = "",
|
||||
username = "",
|
||||
acct = "",
|
||||
url = "",
|
||||
displayName = "",
|
||||
note = "",
|
||||
avatar = "",
|
||||
avatarStatic = "",
|
||||
header = "",
|
||||
headerStatic = "",
|
||||
locked = false,
|
||||
fields = emptyList(),
|
||||
emojis = emptyList(),
|
||||
bot = false,
|
||||
group = false,
|
||||
discoverable = true,
|
||||
createdAt = "",
|
||||
lastStatusAt = "",
|
||||
statusesCount = 0,
|
||||
followersCount = 0,
|
||||
noindex = false,
|
||||
moved = false,
|
||||
suspendex = false,
|
||||
limited = false,
|
||||
followingCount = 0
|
||||
),
|
||||
content = "",
|
||||
visibility = Status.Visibility.public,
|
||||
sensitive = false,
|
||||
spoilerText = "",
|
||||
mediaAttachments = emptyList(),
|
||||
mentions = emptyList(),
|
||||
tags = emptyList(),
|
||||
emojis = emptyList(),
|
||||
reblogsCount = 0,
|
||||
favouritesCount = 0,
|
||||
repliesCount = 0,
|
||||
url = "https://example.com",
|
||||
inReplyToId = null,
|
||||
inReplyToAccountId = null,
|
||||
language = "ja_JP",
|
||||
text = "Test",
|
||||
editedAt = null
|
||||
|
||||
),
|
||||
Status(
|
||||
id = "",
|
||||
uri = "",
|
||||
createdAt = "",
|
||||
account = Account(
|
||||
id = "",
|
||||
username = "",
|
||||
acct = "",
|
||||
url = "",
|
||||
displayName = "",
|
||||
note = "",
|
||||
avatar = "",
|
||||
avatarStatic = "",
|
||||
header = "",
|
||||
headerStatic = "",
|
||||
locked = false,
|
||||
fields = emptyList(),
|
||||
emojis = emptyList(),
|
||||
bot = false,
|
||||
group = false,
|
||||
discoverable = true,
|
||||
createdAt = "",
|
||||
lastStatusAt = "",
|
||||
statusesCount = 0,
|
||||
followersCount = 0,
|
||||
noindex = false,
|
||||
moved = false,
|
||||
suspendex = false,
|
||||
limited = false,
|
||||
followingCount = 0
|
||||
),
|
||||
content = "",
|
||||
visibility = Status.Visibility.public,
|
||||
sensitive = false,
|
||||
spoilerText = "",
|
||||
mediaAttachments = emptyList(),
|
||||
mentions = emptyList(),
|
||||
tags = emptyList(),
|
||||
emojis = emptyList(),
|
||||
reblogsCount = 0,
|
||||
favouritesCount = 0,
|
||||
repliesCount = 0,
|
||||
url = "https://example.com",
|
||||
inReplyToId = null,
|
||||
inReplyToAccountId = null,
|
||||
language = "ja_JP",
|
||||
text = "Test",
|
||||
editedAt = null
|
||||
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `apiV1TimelineHogeGet JWT認証でログインじ200が返ってくる`() = runTest {
|
||||
|
||||
val createEmptyContext = SecurityContextHolder.createEmptyContext()
|
||||
createEmptyContext.authentication = JwtAuthenticationToken(
|
||||
Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build()
|
||||
)
|
||||
SecurityContextHolder.setContext(createEmptyContext)
|
||||
|
||||
whenever(
|
||||
timelineApiService.homeTimeline(
|
||||
eq(1234),
|
||||
eq(123456),
|
||||
eq(54321),
|
||||
eq(1234567),
|
||||
eq(20)
|
||||
)
|
||||
).doReturn(statusList)
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
mockMvc
|
||||
.get("/api/v1/timelines/home?max_id=123456&since_id=1234567&min_id=54321&limit=20")
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(statusList)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1TimelineHomeGet パラメーターがなくても取得できる`() = runTest {
|
||||
val createEmptyContext = SecurityContextHolder.createEmptyContext()
|
||||
createEmptyContext.authentication = JwtAuthenticationToken(
|
||||
Jwt.withTokenValue("a").header("alg", "RS236").claim("uid", "1234").build()
|
||||
)
|
||||
SecurityContextHolder.setContext(createEmptyContext)
|
||||
|
||||
whenever(
|
||||
timelineApiService.homeTimeline(
|
||||
eq(1234),
|
||||
isNull(),
|
||||
isNull(),
|
||||
isNull(),
|
||||
eq(20)
|
||||
)
|
||||
).doReturn(statusList)
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
mockMvc
|
||||
.get("/api/v1/timelines/home")
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(statusList)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1TimelineHomeGet POSTには405を返す`() {
|
||||
mockMvc
|
||||
.post("/api/v1/timelines/home?max_id=123456&since_id=1234567&min_id=54321&limit=20")
|
||||
.andExpect { status { isMethodNotAllowed() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1TimelinePublicGet GETで200が返ってくる`() = runTest {
|
||||
whenever(
|
||||
timelineApiService.publicTimeline(
|
||||
localOnly = eq(false),
|
||||
remoteOnly = eq(true),
|
||||
mediaOnly = eq(false),
|
||||
maxId = eq(1234),
|
||||
minId = eq(4321),
|
||||
sinceId = eq(12345),
|
||||
limit = eq(20)
|
||||
)
|
||||
).doAnswer {
|
||||
println(it.arguments.joinToString())
|
||||
statusList
|
||||
}
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
mockMvc
|
||||
.get("/api/v1/timelines/public?local=false&remote=true&only_media=false&max_id=1234&since_id=12345&min_id=4321&limit=20")
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(statusList)) } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1TimelinePublicGet POSTで405が返ってくる`() {
|
||||
mockMvc.post("/api/v1/timelines/public")
|
||||
.andExpect { status { isMethodNotAllowed() } }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `apiV1TimelinePublicGet パラメーターがなくても取得できる`() = runTest {
|
||||
whenever(
|
||||
timelineApiService.publicTimeline(
|
||||
localOnly = eq(false),
|
||||
remoteOnly = eq(false),
|
||||
mediaOnly = eq(false),
|
||||
maxId = isNull(),
|
||||
minId = isNull(),
|
||||
sinceId = isNull(),
|
||||
limit = eq(20)
|
||||
)
|
||||
).doAnswer {
|
||||
println(it.arguments.joinToString())
|
||||
statusList
|
||||
}
|
||||
|
||||
val objectMapper = jacksonObjectMapper()
|
||||
|
||||
mockMvc
|
||||
.get("/api/v1/timelines/public")
|
||||
.asyncDispatch()
|
||||
.andExpect { status { isOk() } }
|
||||
.andExpect { content { json(objectMapper.writeValueAsString(statusList)) } }
|
||||
}
|
||||
}
|
|
@ -20,15 +20,15 @@ object UserBuilder {
|
|||
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/",
|
||||
inbox: String = "https://$domain/users/$id/inbox",
|
||||
outbox: String = "https://$domain/users/$id/outbox",
|
||||
url: String = "https://$domain/users/$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"
|
||||
keyId: String = "https://$domain/users/$id#pubkey",
|
||||
followers: String = "https://$domain/users/$id/followers",
|
||||
following: String = "https://$domain/users/$id/following"
|
||||
): User {
|
||||
return userBuilder.of(
|
||||
id = id,
|
||||
|
|
Loading…
Reference in New Issue