Merge pull request #17 from usbharu/feature/ap-create

Feature/ap create
This commit is contained in:
usbharu 2023-06-03 10:55:03 +09:00 committed by GitHub
commit c581e563d9
32 changed files with 342 additions and 69 deletions

View File

@ -6,6 +6,6 @@ exposed_version=0.41.1
h2_version=2.1.214 h2_version=2.1.214
koin_version=3.3.1 koin_version=3.3.1
org.gradle.parallel=true org.gradle.parallel=true
#org.gradle.configureondemand=true org.gradle.configureondemand=true
org.gradle.caching=true org.gradle.caching=true
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
networkTimeout=10000 networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

4
gradlew vendored
View File

@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac

View File

@ -1,7 +1,12 @@
package dev.usbharu.hideout.domain.model.ap package dev.usbharu.hideout.domain.model.ap
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
open class Create : Object { open class Create : Object {
@JsonDeserialize(using = ObjectDeserializer::class)
var `object`: Object? = null var `object`: Object? = null
var to: List<String> = emptyList()
var cc: List<String> = emptyList()
protected constructor() : super() protected constructor() : super()
constructor( constructor(
@ -9,14 +14,18 @@ open class Create : Object {
name: String? = null, name: String? = null,
`object`: Object?, `object`: Object?,
actor: String? = null, actor: String? = null,
id: String? = null id: String? = null,
to: List<String> = emptyList(),
cc: List<String> = emptyList()
) : super( ) : super(
add(type, "Create"), type = add(type, "Create"),
name, name = name,
actor, actor = actor,
id id = id
) { ) {
this.`object` = `object` this.`object` = `object`
this.to = to
this.cc = cc
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -5,6 +5,9 @@ open class Note : Object {
var content: String? = null var content: String? = null
var published: String? = null var published: String? = null
var to: List<String> = emptyList() var to: List<String> = emptyList()
var cc: List<String> = emptyList()
var sensitive: Boolean = false
var inReplyTo: String? = null
protected constructor() : super() protected constructor() : super()
constructor( constructor(
@ -14,7 +17,10 @@ open class Note : Object {
attributedTo: String?, attributedTo: String?,
content: String?, content: String?,
published: String?, published: String?,
to: List<String> = emptyList() to: List<String> = emptyList(),
cc: List<String> = emptyList(),
sensitive: Boolean = false,
inReplyTo: String? = null
) : super( ) : super(
type = add(type, "Note"), type = add(type, "Note"),
name = name, name = name,
@ -24,6 +30,9 @@ open class Note : Object {
this.content = content this.content = content
this.published = published this.published = published
this.to = to this.to = to
this.cc = cc
this.sensitive = sensitive
this.inReplyTo = inReplyTo
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import dev.usbharu.hideout.service.activitypub.ActivityType import dev.usbharu.hideout.service.activitypub.ActivityVocabulary
class ObjectDeserializer : JsonDeserializer<Object>() { class ObjectDeserializer : JsonDeserializer<Object>() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object { override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): Object {
@ -22,21 +22,25 @@ class ObjectDeserializer : JsonDeserializer<Object>() {
val type = treeNode["type"] val type = treeNode["type"]
val activityType = if (type.isArray) { val activityType = if (type.isArray) {
type.firstNotNullOf { jsonNode: JsonNode -> type.firstNotNullOf { jsonNode: JsonNode ->
ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } ActivityVocabulary.values().firstOrNull { it.name.equals(jsonNode.asText(), true) }
} }
} else if (type.isValueNode) { } else if (type.isValueNode) {
ActivityType.values().first { it.name.equals(type.asText(), true) } ActivityVocabulary.values().first { it.name.equals(type.asText(), true) }
} else { } else {
TODO() TODO()
} }
return when (activityType) { return when (activityType) {
ActivityType.Follow -> { ActivityVocabulary.Follow -> {
val readValue = p.codec.treeToValue(treeNode, Follow::class.java) val readValue = p.codec.treeToValue(treeNode, Follow::class.java)
println(readValue) println(readValue)
readValue readValue
} }
ActivityVocabulary.Note -> {
p.codec.treeToValue(treeNode, Note::class.java)
}
else -> { else -> {
TODO() TODO()
} }

View File

@ -5,7 +5,7 @@ open class Person : Object {
var summary: String? = null var summary: String? = null
var inbox: String? = null var inbox: String? = null
var outbox: String? = null var outbox: String? = null
private var url: String? = null var url: String? = null
private var icon: Image? = null private var icon: Image? = null
var publicKey: Key? = null var publicKey: Key? = null

View File

@ -9,5 +9,7 @@ data class Post(
val visibility: Visibility, val visibility: Visibility,
val url: String, val url: String,
val repostId: Long? = null, val repostId: Long? = null,
val replyId: Long? = null val replyId: Long? = null,
val sensitive: Boolean = false,
val apId: String = url
) )

View File

@ -17,6 +17,8 @@ data class User(
val createdAt: Instant val createdAt: Instant
) { ) {
override fun toString(): String { override fun toString(): String {
return "User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description', password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey', privateKey=****, createdAt=$createdAt)" return "User(id=$id, name='$name', domain='$domain', screenName='$screenName', description='$description'," +
" password=****, inbox='$inbox', outbox='$outbox', url='$url', publicKey='$publicKey'," +
" privateKey=****, createdAt=$createdAt)"
} }
} }

View File

@ -5,6 +5,7 @@ import dev.usbharu.hideout.domain.model.hideout.entity.Post
interface IPostRepository { interface IPostRepository {
suspend fun generateId(): Long suspend fun generateId(): Long
suspend fun save(post: Post): Post suspend fun save(post: Post): Post
suspend fun findOneById(id: Long): Post suspend fun findOneById(id: Long): Post?
suspend fun findByUrl(url: String): Post?
suspend fun delete(id: Long) suspend fun delete(id: Long)
} }

View File

@ -16,6 +16,7 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
init { init {
transaction(database) { transaction(database) {
SchemaUtils.create(Posts) SchemaUtils.create(Posts)
SchemaUtils.createMissingTablesAndColumns(Posts)
} }
} }
@ -37,14 +38,22 @@ class PostRepositoryImpl(database: Database, private val idGenerateService: IdGe
it[url] = post.url it[url] = post.url
it[repostId] = post.repostId it[repostId] = post.repostId
it[replyId] = post.replyId it[replyId] = post.replyId
it[sensitive] = post.sensitive
it[apId] = post.apId
} }
return@query post return@query post
} }
} }
override suspend fun findOneById(id: Long): Post { override suspend fun findOneById(id: Long): Post? {
return query { return query {
Posts.select { Posts.id eq id }.single().toPost() Posts.select { Posts.id eq id }.singleOrNull()?.toPost()
}
}
override suspend fun findByUrl(url: String): Post? {
return query {
Posts.select { Posts.url eq url }.singleOrNull()?.toPost()
} }
} }
@ -65,6 +74,8 @@ object Posts : Table() {
val url = varchar("url", 500) val url = varchar("url", 500)
val repostId = long("repostId").references(id).nullable() val repostId = long("repostId").references(id).nullable()
val replyId = long("replyId").references(id).nullable() val replyId = long("replyId").references(id).nullable()
val sensitive = bool("sensitive").default(false)
val apId = varchar("ap_id", 100).uniqueIndex()
override val primaryKey: PrimaryKey = PrimaryKey(id) override val primaryKey: PrimaryKey = PrimaryKey(id)
} }
@ -78,6 +89,8 @@ fun ResultRow.toPost(): Post {
visibility = Visibility.values().first { visibility -> visibility.ordinal == this[Posts.visibility] }, visibility = Visibility.values().first { visibility -> visibility.ordinal == this[Posts.visibility] },
url = this[Posts.url], url = this[Posts.url],
repostId = this[Posts.repostId], repostId = this[Posts.repostId],
replyId = this[Posts.replyId] replyId = this[Posts.replyId],
sensitive = this[Posts.sensitive],
apId = this[Posts.apId]
) )
} }

View File

@ -0,0 +1,8 @@
package dev.usbharu.hideout.service.activitypub
import dev.usbharu.hideout.domain.model.ActivityPubResponse
import dev.usbharu.hideout.domain.model.ap.Create
interface ActivityPubCreateService {
suspend fun receiveCreate(create: Create): ActivityPubResponse
}

View File

@ -0,0 +1,25 @@
package dev.usbharu.hideout.service.activitypub
import dev.usbharu.hideout.domain.model.ActivityPubResponse
import dev.usbharu.hideout.domain.model.ActivityPubStringResponse
import dev.usbharu.hideout.domain.model.ap.Create
import dev.usbharu.hideout.domain.model.ap.Note
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import io.ktor.http.*
import org.koin.core.annotation.Single
@Single
class ActivityPubCreateServiceImpl(
private val activityPubNoteService: ActivityPubNoteService
) : ActivityPubCreateService {
override suspend fun receiveCreate(create: Create): ActivityPubResponse {
val value = create.`object` ?: throw IllegalActivityPubObjectException("object is null")
if (value.type.contains("Note").not()) {
throw IllegalActivityPubObjectException("object is not Note")
}
val note = value as Note
activityPubNoteService.fetchNote(note)
return ActivityPubStringResponse(HttpStatusCode.OK, "Created")
}
}

View File

@ -1,5 +1,6 @@
package dev.usbharu.hideout.service.activitypub package dev.usbharu.hideout.service.activitypub
import dev.usbharu.hideout.domain.model.ap.Note
import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Post
import dev.usbharu.hideout.domain.model.job.DeliverPostJob import dev.usbharu.hideout.domain.model.job.DeliverPostJob
import kjob.core.job.JobProps import kjob.core.job.JobProps
@ -8,4 +9,7 @@ interface ActivityPubNoteService {
suspend fun createNote(post: Post) suspend fun createNote(post: Post)
suspend fun createNoteJob(props: JobProps<DeliverPostJob>) suspend fun createNoteJob(props: JobProps<DeliverPostJob>)
suspend fun fetchNote(url: String, targetActor: String? = null): Note
suspend fun fetchNote(note: Note, targetActor: String? = null): Note
} }

View File

@ -5,11 +5,16 @@ import dev.usbharu.hideout.config.Config
import dev.usbharu.hideout.domain.model.ap.Create import dev.usbharu.hideout.domain.model.ap.Create
import dev.usbharu.hideout.domain.model.ap.Note import dev.usbharu.hideout.domain.model.ap.Note
import dev.usbharu.hideout.domain.model.hideout.entity.Post import dev.usbharu.hideout.domain.model.hideout.entity.Post
import dev.usbharu.hideout.domain.model.hideout.entity.Visibility
import dev.usbharu.hideout.domain.model.job.DeliverPostJob import dev.usbharu.hideout.domain.model.job.DeliverPostJob
import dev.usbharu.hideout.exception.ap.IllegalActivityPubObjectException
import dev.usbharu.hideout.plugins.getAp
import dev.usbharu.hideout.plugins.postAp import dev.usbharu.hideout.plugins.postAp
import dev.usbharu.hideout.repository.IPostRepository
import dev.usbharu.hideout.service.impl.IUserService import dev.usbharu.hideout.service.impl.IUserService
import dev.usbharu.hideout.service.job.JobQueueParentService import dev.usbharu.hideout.service.job.JobQueueParentService
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.*
import kjob.core.job.JobProps import kjob.core.job.JobProps
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -19,7 +24,9 @@ import java.time.Instant
class ActivityPubNoteServiceImpl( class ActivityPubNoteServiceImpl(
private val httpClient: HttpClient, private val httpClient: HttpClient,
private val jobQueueParentService: JobQueueParentService, private val jobQueueParentService: JobQueueParentService,
private val userService: IUserService private val userService: IUserService,
private val postRepository: IPostRepository,
private val activityPubUserService: ActivityPubUserService
) : ActivityPubNoteService { ) : ActivityPubNoteService {
private val logger = LoggerFactory.getLogger(this::class.java) private val logger = LoggerFactory.getLogger(this::class.java)
@ -46,7 +53,7 @@ class ActivityPubNoteServiceImpl(
attributedTo = actor, attributedTo = actor,
content = postEntity.text, content = postEntity.text,
published = Instant.ofEpochMilli(postEntity.createdAt).toString(), published = Instant.ofEpochMilli(postEntity.createdAt).toString(),
to = listOf("https://www.w3.org/ns/activitystreams#Public", actor + "/follower") to = listOf(public, actor + "/follower")
) )
val inbox = props[DeliverPostJob.inbox] val inbox = props[DeliverPostJob.inbox]
logger.debug("createNoteJob: actor={}, note={}, inbox={}", actor, postEntity, inbox) logger.debug("createNoteJob: actor={}, note={}, inbox={}", actor, postEntity, inbox)
@ -61,4 +68,82 @@ class ActivityPubNoteServiceImpl(
) )
) )
} }
override suspend fun fetchNote(url: String, targetActor: String?): Note {
val post = postRepository.findByUrl(url)
if (post != null) {
val user = userService.findById(post.userId)
val reply = post.replyId?.let { postRepository.findOneById(it) }
return Note(
name = "Post",
id = post.apId,
attributedTo = user.url,
content = post.text,
published = Instant.ofEpochMilli(post.createdAt).toString(),
to = listOf(public, user.url + "/follower"),
sensitive = post.sensitive,
cc = listOf(public, user.url + "/follower"),
inReplyTo = reply?.url
)
}
val response = httpClient.getAp(
url,
"$targetActor#pubkey"
)
val note = response.body<Note>()
return note(note, targetActor, url)
}
private suspend fun ActivityPubNoteServiceImpl.note(
note: Note,
targetActor: String?,
url: String
): Note {
val person = activityPubUserService.fetchPerson(
note.attributedTo ?: throw IllegalActivityPubObjectException("note.attributedTo is null"),
targetActor
)
val user =
userService.findByUrl(person.url ?: throw IllegalActivityPubObjectException("person.url is null"))
val visibility =
if (note.to.contains(public) && note.cc.contains(public)) {
Visibility.PUBLIC
} else if (note.to.find { it.endsWith("/followers") } != null && note.cc.contains(public)) {
Visibility.UNLISTED
} else if (note.to.find { it.endsWith("/followers") } != null) {
Visibility.FOLLOWERS
} else {
Visibility.DIRECT
}
val reply = note.inReplyTo?.let {
fetchNote(it, targetActor)
postRepository.findByUrl(it)
}
postRepository.save(
Post(
id = postRepository.generateId(),
userId = user.id,
overview = null,
text = note.content.orEmpty(),
createdAt = Instant.parse(note.published).toEpochMilli(),
visibility = visibility,
url = note.id ?: url,
repostId = null,
replyId = reply?.id,
sensitive = note.sensitive,
apId = note.id ?: url,
)
)
return note
}
override suspend fun fetchNote(note: Note, targetActor: String?): Note =
note(note, targetActor, note.id ?: throw IllegalArgumentException("note.id is null"))
companion object {
val public: String = "https://www.w3.org/ns/activitystreams#Public"
}
} }

View File

@ -43,3 +43,60 @@ enum class ActivityType {
View, View,
Other Other
} }
enum class ActivityVocabulary {
Object,
Link,
Activity,
IntransitiveActivity,
Collection,
OrderedCollection,
CollectionPage,
OrderedCollectionPage,
Accept,
Add,
Announce,
Arrive,
Block,
Create,
Delete,
Dislike,
Flag,
Follow,
Ignore,
Invite,
Join,
Leave,
Like,
Listen,
Move,
Offer,
Question,
Reject,
Read,
Remove,
TentativeReject,
TentativeAccept,
Travel,
Undo,
Update,
View,
Application,
Group,
Organization,
Person,
Service,
Article,
Audio,
Document,
Event,
Image,
Note,
Page,
Place,
Profile,
Relationship,
Tombstone,
Video,
Mention,
}

View File

@ -20,7 +20,8 @@ class ActivityPubServiceImpl(
private val activityPubReceiveFollowService: ActivityPubReceiveFollowService, private val activityPubReceiveFollowService: ActivityPubReceiveFollowService,
private val activityPubNoteService: ActivityPubNoteService, private val activityPubNoteService: ActivityPubNoteService,
private val activityPubUndoService: ActivityPubUndoService, private val activityPubUndoService: ActivityPubUndoService,
private val activityPubAcceptService: ActivityPubAcceptService private val activityPubAcceptService: ActivityPubAcceptService,
private val activityPubCreateService: ActivityPubCreateService
) : ActivityPubService { ) : ActivityPubService {
val logger: Logger = LoggerFactory.getLogger(this::class.java) val logger: Logger = LoggerFactory.getLogger(this::class.java)
@ -32,9 +33,9 @@ class ActivityPubServiceImpl(
} }
val type = readTree["type"] val type = readTree["type"]
if (type.isArray) { if (type.isArray) {
return type.mapNotNull { jsonNode: JsonNode -> return type.firstNotNullOf { jsonNode: JsonNode ->
ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) } ActivityType.values().firstOrNull { it.name.equals(jsonNode.asText(), true) }
}.first() }
} }
return ActivityType.values().first { it.name.equals(type.asText(), true) } return ActivityType.values().first { it.name.equals(type.asText(), true) }
} }
@ -51,6 +52,7 @@ class ActivityPubServiceImpl(
) )
) )
ActivityType.Create -> activityPubCreateService.receiveCreate(configData.objectMapper.readValue(json))
ActivityType.Undo -> activityPubUndoService.receiveUndo(configData.objectMapper.readValue(json)) ActivityType.Undo -> activityPubUndoService.receiveUndo(configData.objectMapper.readValue(json))
else -> { else -> {
@ -62,7 +64,10 @@ class ActivityPubServiceImpl(
override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) { override suspend fun <T : HideoutJob> processActivity(job: JobContextWithProps<T>, hideoutJob: HideoutJob) {
logger.debug("processActivity: ${hideoutJob.name}") logger.debug("processActivity: ${hideoutJob.name}")
when (hideoutJob) { when (hideoutJob) {
ReceiveFollowJob -> activityPubReceiveFollowService.receiveFollowJob(job.props as JobProps<ReceiveFollowJob>) ReceiveFollowJob -> activityPubReceiveFollowService.receiveFollowJob(
job.props as JobProps<ReceiveFollowJob>
)
DeliverPostJob -> activityPubNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>) DeliverPostJob -> activityPubNoteService.createNoteJob(job.props as JobProps<DeliverPostJob>)
} }
} }

View File

@ -9,6 +9,7 @@ import io.ktor.http.*
import org.koin.core.annotation.Single import org.koin.core.annotation.Single
@Single @Single
@Suppress("UnsafeCallOnNullableType")
class ActivityPubUndoServiceImpl( class ActivityPubUndoServiceImpl(
private val userService: IUserService, private val userService: IUserService,
private val activityPubUserService: ActivityPubUserService private val activityPubUserService: ActivityPubUserService

View File

@ -4,7 +4,7 @@
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="DEBUG"> <root level="TRACE">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
</root> </root>
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>

View File

@ -30,7 +30,8 @@ class UndoTest {
@Test @Test
fun Undoをデシリアライズ出来る() { fun Undoをデシリアライズ出来る() {
@Language("JSON") val json = """ @Language("JSON")
val json = """
{ {
"@context": [ "@context": [
"https://www.w3.org/ns/activitystreams", "https://www.w3.org/ns/activitystreams",

View File

@ -92,6 +92,18 @@ class ActivityPubKtTest {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun addFollowRequest(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun deleteFollowRequest(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean {
TODO("Not yet implemented")
}
override suspend fun nextId(): Long { override suspend fun nextId(): Long {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -88,6 +88,18 @@ class KtorKeyMapTest {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun addFollowRequest(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun deleteFollowRequest(id: Long, follower: Long) {
TODO("Not yet implemented")
}
override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean {
TODO("Not yet implemented")
}
override suspend fun nextId(): Long { override suspend fun nextId(): Long {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -41,11 +41,14 @@ class JwtRefreshTokenRepositoryImplTest {
@Test @Test
fun `save 存在しない場合はinsertする`() = runTest { fun `save 存在しない場合はinsertする`() = runTest {
val repository = JwtRefreshTokenRepositoryImpl(db, object : IdGenerateService { val repository = JwtRefreshTokenRepositoryImpl(
override suspend fun generateId(): Long { db,
TODO("Not yet implemented") object : IdGenerateService {
override suspend fun generateId(): Long {
TODO("Not yet implemented")
}
} }
}) )
val now = Instant.now(Clock.tickMillis(ZoneId.systemDefault())) val now = Instant.now(Clock.tickMillis(ZoneId.systemDefault()))
val expiresAt = now.plus(10, ChronoUnit.MINUTES) val expiresAt = now.plus(10, ChronoUnit.MINUTES)
@ -57,11 +60,14 @@ class JwtRefreshTokenRepositoryImplTest {
@Test @Test
fun `save 存在する場合はupdateする`() = runTest { fun `save 存在する場合はupdateする`() = runTest {
val repository = JwtRefreshTokenRepositoryImpl(db, object : IdGenerateService { val repository = JwtRefreshTokenRepositoryImpl(
override suspend fun generateId(): Long { db,
TODO("Not yet implemented") object : IdGenerateService {
override suspend fun generateId(): Long {
TODO("Not yet implemented")
}
} }
}) )
transaction { transaction {
JwtRefreshTokens.insert { JwtRefreshTokens.insert {
it[id] = 1L it[id] = 1L

View File

@ -28,6 +28,7 @@ class UserRepositoryTest {
transaction(db) { transaction(db) {
SchemaUtils.create(Users) SchemaUtils.create(Users)
SchemaUtils.create(UsersFollowers) SchemaUtils.create(UsersFollowers)
SchemaUtils.create(FollowRequests)
} }
} }
@ -35,6 +36,7 @@ class UserRepositoryTest {
fun tearDown() { fun tearDown() {
transaction(db) { transaction(db) {
SchemaUtils.drop(UsersFollowers) SchemaUtils.drop(UsersFollowers)
SchemaUtils.drop(FollowRequests)
SchemaUtils.drop(Users) SchemaUtils.drop(Users)
} }
} }
@ -96,9 +98,7 @@ class UserRepositoryTest {
) )
userRepository.createFollower(user.id, follower.id) userRepository.createFollower(user.id, follower.id)
userRepository.createFollower(user.id, follower2.id) userRepository.createFollower(user.id, follower2.id)
userRepository.findFollowersById(user.id).let { assertIterableEquals(listOf(follower, follower2), userRepository.findFollowersById(user.id))
assertIterableEquals(listOf(follower, follower2), it)
}
} }
@Test @Test

View File

@ -30,7 +30,7 @@ class ContentTypeRouteSelectorTest {
} }
} }
client.get("/test"){ client.get("/test") {
accept(ContentType.Text.Html) accept(ContentType.Text.Html)
}.apply { }.apply {
assertEquals("NG", bodyAsText()) assertEquals("NG", bodyAsText())
@ -60,7 +60,7 @@ class ContentTypeRouteSelectorTest {
} }
} }
client.get("/test"){ client.get("/test") {
accept(ContentType.Text.Html) accept(ContentType.Text.Html)
}.apply { }.apply {
assertEquals("NG", bodyAsText()) assertEquals("NG", bodyAsText())
@ -85,7 +85,7 @@ class ContentTypeRouteSelectorTest {
} }
} }
client.get("/test"){ client.get("/test") {
accept(ContentType.Text.Html) accept(ContentType.Text.Html)
}.apply { }.apply {
assertEquals("NG", bodyAsText()) assertEquals("NG", bodyAsText())
@ -114,18 +114,18 @@ class ContentTypeRouteSelectorTest {
} }
} }
client.get("/test"){ client.get("/test") {
accept(ContentType.Text.Html) accept(ContentType.Text.Html)
}.apply { }.apply {
assertEquals("OK", bodyAsText()) assertEquals("OK", bodyAsText())
} }
client.get("/test"){ client.get("/test") {
accept(ContentType.Application.Json) accept(ContentType.Application.Json)
}.apply { }.apply {
assertEquals("OK", bodyAsText()) assertEquals("OK", bodyAsText())
} }
client.get("/test"){ client.get("/test") {
accept(ContentType.Application.Xml) accept(ContentType.Application.Xml)
}.apply { }.apply {
assertEquals("NG", bodyAsText()) assertEquals("NG", bodyAsText())

View File

@ -158,8 +158,12 @@ class PostsTest {
config = ApplicationConfig("empty.conf") config = ApplicationConfig("empty.conf")
} }
val post = Post( val post = Post(
12345, 1234, text = "aaa", visibility = Visibility.PUBLIC, 12345,
createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" 1234,
text = "aaa",
visibility = Visibility.PUBLIC,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
) )
val postService = mock<IPostService> { val postService = mock<IPostService> {
onBlocking { findByIdForUser(any(), anyOrNull()) } doReturn post onBlocking { findByIdForUser(any(), anyOrNull()) } doReturn post
@ -185,8 +189,12 @@ class PostsTest {
config = ApplicationConfig("empty.conf") config = ApplicationConfig("empty.conf")
} }
val post = Post( val post = Post(
12345, 1234, text = "aaa", visibility = Visibility.FOLLOWERS, 12345,
createdAt = Instant.now().toEpochMilli(), url = "https://example.com/posts/1" 1234,
text = "aaa",
visibility = Visibility.FOLLOWERS,
createdAt = Instant.now().toEpochMilli(),
url = "https://example.com/posts/1"
) )
val postService = mock<IPostService> { val postService = mock<IPostService> {
onBlocking { findByIdForUser(any(), isNotNull()) } doReturn post onBlocking { findByIdForUser(any(), isNotNull()) } doReturn post
@ -247,7 +255,6 @@ class PostsTest {
} }
application { application {
authentication { authentication {
bearer(TOKEN_AUTH) { bearer(TOKEN_AUTH) {
authenticate { authenticate {
println("aaaaaaaaaaaa") println("aaaaaaaaaaaa")

View File

@ -290,7 +290,8 @@ class UsersTest {
"test User", "test User",
"https://example.com/test", "https://example.com/test",
Instant.now().toEpochMilli() Instant.now().toEpochMilli()
), UserResponse( ),
UserResponse(
1236, 1236,
"follower2", "follower2",
"example.com", "example.com",
@ -334,7 +335,8 @@ class UsersTest {
"test User", "test User",
"https://example.com/test", "https://example.com/test",
Instant.now().toEpochMilli() Instant.now().toEpochMilli()
), UserResponse( ),
UserResponse(
1236, 1236,
"follower2", "follower2",
"example.com", "example.com",
@ -378,7 +380,8 @@ class UsersTest {
"test User", "test User",
"https://example.com/test", "https://example.com/test",
Instant.now().toEpochMilli() Instant.now().toEpochMilli()
), UserResponse( ),
UserResponse(
1236, 1236,
"follower2", "follower2",
"example.com", "example.com",
@ -572,7 +575,8 @@ class UsersTest {
"test User", "test User",
"https://example.com/test", "https://example.com/test",
Instant.now().toEpochMilli() Instant.now().toEpochMilli()
), UserResponse( ),
UserResponse(
1236, 1236,
"follower2", "follower2",
"example.com", "example.com",
@ -616,7 +620,8 @@ class UsersTest {
"test User", "test User",
"https://example.com/test", "https://example.com/test",
Instant.now().toEpochMilli() Instant.now().toEpochMilli()
), UserResponse( ),
UserResponse(
1236, 1236,
"follower2", "follower2",
"example.com", "example.com",
@ -660,7 +665,8 @@ class UsersTest {
"test User", "test User",
"https://example.com/test", "https://example.com/test",
Instant.now().toEpochMilli() Instant.now().toEpochMilli()
), UserResponse( ),
UserResponse(
1236, 1236,
"follower2", "follower2",
"example.com", "example.com",

View File

@ -37,7 +37,6 @@ class MetaServiceImplTest {
@Test @Test
fun `updateMeta メタデータを保存できる`() = runTest { fun `updateMeta メタデータを保存できる`() = runTest {
val meta = Meta("1.0.1", Jwt(UUID.randomUUID(), "sdfsdjk", "adafda")) val meta = Meta("1.0.1", Jwt(UUID.randomUUID(), "sdfsdjk", "adafda"))
val metaRepository = mock<IMetaRepository> { val metaRepository = mock<IMetaRepository> {
onBlocking { save(any()) } doReturn Unit onBlocking { save(any()) } doReturn Unit

View File

@ -23,23 +23,23 @@ class ServerInitialiseServiceImplTest {
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository) val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository)
serverInitialiseServiceImpl.init() serverInitialiseServiceImpl.init()
verify(metaRepository,times(1)).save(any()) verify(metaRepository, times(1)).save(any())
} }
@Test @Test
fun `init メタデータが存在して同じバージョンのときは何もしない`() = runTest { fun `init メタデータが存在して同じバージョンのときは何もしない`() = runTest {
val meta = Meta(ServerUtil.getImplementationVersion(), Jwt(UUID.randomUUID(),"aaafafd","afafasdf")) val meta = Meta(ServerUtil.getImplementationVersion(), Jwt(UUID.randomUUID(), "aaafafd", "afafasdf"))
val metaRepository = mock<IMetaRepository> { val metaRepository = mock<IMetaRepository> {
onBlocking { get() } doReturn meta onBlocking { get() } doReturn meta
} }
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository) val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository)
serverInitialiseServiceImpl.init() serverInitialiseServiceImpl.init()
verify(metaRepository,times(0)).save(any()) verify(metaRepository, times(0)).save(any())
} }
@Test @Test
fun `init メタデータが存在して違うバージョンのときはバージョンを変更する`() = runTest { fun `init メタデータが存在して違うバージョンのときはバージョンを変更する`() = runTest {
val meta = Meta("1.0.0", Jwt(UUID.randomUUID(),"aaafafd","afafasdf")) val meta = Meta("1.0.0", Jwt(UUID.randomUUID(), "aaafafd", "afafasdf"))
val metaRepository = mock<IMetaRepository> { val metaRepository = mock<IMetaRepository> {
onBlocking { get() } doReturn meta onBlocking { get() } doReturn meta
onBlocking { save(any()) } doReturn Unit onBlocking { save(any()) } doReturn Unit
@ -47,10 +47,10 @@ class ServerInitialiseServiceImplTest {
val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository) val serverInitialiseServiceImpl = ServerInitialiseServiceImpl(metaRepository)
serverInitialiseServiceImpl.init() serverInitialiseServiceImpl.init()
verify(metaRepository,times(1)).save(any()) verify(metaRepository, times(1)).save(any())
argumentCaptor<Meta> { argumentCaptor<Meta> {
verify(metaRepository,times(1)).save(capture()) verify(metaRepository, times(1)).save(capture())
assertEquals(ServerUtil.getImplementationVersion(),firstValue.version) assertEquals(ServerUtil.getImplementationVersion(), firstValue.version)
} }
} }
} }

View File

@ -72,7 +72,8 @@ class ActivityPubNoteServiceImplTest {
onBlocking { findFollowersById(eq(1L)) } doReturn followers onBlocking { findFollowersById(eq(1L)) } doReturn followers
} }
val jobQueueParentService = mock<JobQueueParentService>() val jobQueueParentService = mock<JobQueueParentService>()
val activityPubNoteService = ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService) val activityPubNoteService =
ActivityPubNoteServiceImpl(mock(), jobQueueParentService, userService, mock(), mock())
val postEntity = Post( val postEntity = Post(
1L, 1L,
1L, 1L,
@ -95,7 +96,7 @@ class ActivityPubNoteServiceImplTest {
respondOk() respondOk()
} }
) )
val activityPubNoteService = ActivityPubNoteServiceImpl(httpClient, mock(), mock()) val activityPubNoteService = ActivityPubNoteServiceImpl(httpClient, mock(), mock(), mock(), mock())
activityPubNoteService.createNoteJob( activityPubNoteService.createNoteJob(
JobProps( JobProps(
data = mapOf<String, Any>( data = mapOf<String, Any>(

View File

@ -53,7 +53,7 @@ class PostServiceTest {
text, text,
Instant.now().toEpochMilli(), Instant.now().toEpochMilli(),
visibility, visibility,
"https://example.com" "https://example.com${(userId.toString() + text).hashCode()}"
) )
} }
@ -88,6 +88,8 @@ class PostServiceTest {
this[Posts.url] = it.url this[Posts.url] = it.url
this[Posts.replyId] = it.replyId this[Posts.replyId] = it.replyId
this[Posts.repostId] = it.repostId this[Posts.repostId] = it.repostId
this[Posts.sensitive] = it.sensitive
this[Posts.apId] = it.apId
} }
} }
@ -109,7 +111,7 @@ class PostServiceTest {
text, text,
Instant.now().toEpochMilli(), Instant.now().toEpochMilli(),
visibility, visibility,
"https://example.com" "https://example.com${(userId.toString() + text).hashCode()}"
) )
} }
@ -148,6 +150,8 @@ class PostServiceTest {
this[Posts.url] = it.url this[Posts.url] = it.url
this[Posts.replyId] = it.replyId this[Posts.replyId] = it.replyId
this[Posts.repostId] = it.repostId this[Posts.repostId] = it.repostId
this[Posts.sensitive] = it.sensitive
this[Posts.apId] = it.apId
} }
UsersFollowers.insert { UsersFollowers.insert {
it[id] = 100L it[id] = 100L