diff --git a/gradle.properties b/gradle.properties index 936dc96e..d345cf66 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.caching=true -org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC --add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED \ No newline at end of file +org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC --add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED +kotlin.compiler.preciseCompilationResultsBackup=true \ No newline at end of file diff --git a/hideout-core/build.gradle.kts b/hideout-core/build.gradle.kts index 88b2bca3..d184d19c 100644 --- a/hideout-core/build.gradle.kts +++ b/hideout-core/build.gradle.kts @@ -132,6 +132,8 @@ dependencies { testImplementation(libs.ktor.client.mock) testImplementation(libs.h2db) testImplementation(libs.mockito.kotlin) + testImplementation("org.assertj:assertj-db:2.0.2") + testImplementation("com.ninja-squad:DbSetup-kotlin:2.1.0") } detekt { diff --git a/hideout-core/gradle.properties b/hideout-core/gradle.properties index e628db63..0e81c240 100644 --- a/hideout-core/gradle.properties +++ b/hideout-core/gradle.properties @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # -kotlin.code.style=official org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.caching=true -org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC \ No newline at end of file +org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC --add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED +kotlin.compiler.preciseCompilationResultsBackup=true \ No newline at end of file diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/MigrationLocalActorApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/MigrationLocalActorApplicationService.kt index ea9b8313..24a36249 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/MigrationLocalActorApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/actor/MigrationLocalActorApplicationService.kt @@ -56,7 +56,7 @@ class MigrationLocalActorApplicationService( val canAccountMigration = localActorMigrationCheckDomainService.canAccountMigration(userDetail, fromActor, toActor) if (canAccountMigration.canMigration) { - fromActor.moveTo = toActorId + fromActor.setMoveTo(toActorId) actorRepository.save(fromActor) } else { when (canAccountMigration) { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostDetailApplicationService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostDetailApplicationService.kt index 278c0afe..89dd080e 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostDetailApplicationService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/application/post/GetPostDetailApplicationService.kt @@ -40,7 +40,7 @@ class GetPostDetailApplicationService( val iconMedia = actor.icon?.let { mediaRepository.findById(it) } - val mediaList = mediaRepository.findByIds(post.mediaIds) + val mediaList = mediaRepository.findByIdIn(post.mediaIds) val reactions = reactionsQueryService.findAllByPostId(post.id) @@ -82,7 +82,7 @@ class GetPostDetailApplicationService( actor to iconMedia } - val mediaList = mediaRepository.findByIds(post.mediaIds) + val mediaList = mediaRepository.findByIdIn(post.mediaIds) return PostDetail.of( post = post, actor = first, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt index 8fd41710..684c3474 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor.kt @@ -85,31 +85,40 @@ class Actor( } var alsoKnownAs = alsoKnownAs - set(value) { - require(value.none { it == id }) - field = value - } + private set + + fun setAlsoKnownAs(alsoKnownAs: Set) { + require(alsoKnownAs.none { it == id }) + this.alsoKnownAs = alsoKnownAs + } var moveTo = moveTo - set(value) { - require(value != id) - addDomainEvent(ActorDomainEventFactory(this).createEvent(MOVE)) - field = value - } + private set + + fun setMoveTo(moveTo: ActorId?) { + require(moveTo != id) + addDomainEvent(ActorDomainEventFactory(this).createEvent(MOVE)) + this.moveTo = moveTo + } var emojis = emojiIds private set var description = description - set(value) { - addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE)) - field = value - } + private set + + fun setDescription(description: ActorDescription) { + addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE)) + this.description = description + } + var screenName = screenName - set(value) { - addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE)) - field = value - } + private set + + fun setScreenName(screenName: ActorScreenName) { + addDomainEvent(ActorDomainEventFactory(this).createEvent(UPDATE)) + this.screenName = screenName + } var deleted = deleted private set @@ -135,4 +144,48 @@ class Actor( fun checkUpdate() { addDomainEvent(ActorDomainEventFactory(this).createEvent(CHECK_UPDATE)) } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Actor + + return id == other.id + } + + override fun hashCode(): Int = id.hashCode() + + override fun toString(): String { + return "Actor(" + + "id=$id, " + + "name=$name, " + + "domain=$domain, " + + "inbox=$inbox, " + + "outbox=$outbox, " + + "url=$url, " + + "publicKey=$publicKey, " + + "privateKey=$privateKey, " + + "createdAt=$createdAt, " + + "keyId=$keyId, " + + "followersEndpoint=$followersEndpoint, " + + "followingEndpoint=$followingEndpoint, " + + "instance=$instance, " + + "locked=$locked, " + + "followersCount=$followersCount, " + + "followingCount=$followingCount, " + + "postsCount=$postsCount, " + + "lastPostAt=$lastPostAt, " + + "lastUpdateAt=$lastUpdateAt, " + + "banner=$banner, " + + "icon=$icon, " + + "suspend=$suspend, " + + "alsoKnownAs=$alsoKnownAs, " + + "moveTo=$moveTo, " + + "emojis=$emojis, " + + "description=$description, " + + "screenName=$screenName, " + + "deleted=$deleted" + + ")" + } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescription.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescription.kt index 84c60eff..55cb6dc7 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescription.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorDescription.kt @@ -19,6 +19,17 @@ package dev.usbharu.hideout.core.domain.model.actor class ActorDescription(description: String) { val description: String = description.take(LENGTH) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ActorDescription + + return description == other.description + } + + override fun hashCode(): Int = description.hashCode() + companion object { const val LENGTH = 10000 val empty = ActorDescription("") diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenName.kt index 57866d77..4c594d73 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenName.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorScreenName.kt @@ -20,6 +20,17 @@ class ActorScreenName(screenName: String) { val screenName: String = screenName.take(LENGTH) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ActorScreenName + + return screenName == other.screenName + } + + override fun hashCode(): Int = screenName.hashCode() + companion object { const val LENGTH = 300 val empty = ActorScreenName("") diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt index 333eb8ae..6e5822fd 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterKeyword.kt @@ -4,4 +4,17 @@ class FilterKeyword( val id: FilterKeywordId, var keyword: FilterKeywordKeyword, val mode: FilterMode -) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FilterKeyword + + return id == other.id + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt index bd6596f3..2e517ceb 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/filter/FilterName.kt @@ -4,6 +4,19 @@ class FilterName(name: String) { val name = name.take(LENGTH) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FilterName + + return name == other.name + } + + override fun hashCode(): Int { + return name.hashCode() + } + companion object { const val LENGTH = 300 } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaRepository.kt index f762af7b..1237d2bc 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/media/MediaRepository.kt @@ -19,6 +19,6 @@ package dev.usbharu.hideout.core.domain.model.media interface MediaRepository { suspend fun save(media: Media): Media suspend fun findById(id: MediaId): Media? - suspend fun findByIds(ids: List): List + suspend fun findByIdIn(ids: List): List suspend fun delete(media: Media) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt index ca6cebcd..09f1bc3a 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/relationship/RelationshipRepository.kt @@ -34,18 +34,4 @@ interface RelationshipRepository { targetIds: List, following: Boolean ): List - - suspend fun findByTargetId( - targetId: ActorId, - option: FindRelationshipOption? = null, - inverseOption: FindRelationshipOption? = null - ): List } - -data class FindRelationshipOption( - val follow: Boolean? = null, - val block: Boolean? = null, - val mute: Boolean? = null, - val followRequest: Boolean? = null, - val muteFollowRequest: Boolean? = null -) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt index 9b713d8f..ca513707 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timeline/Timeline.kt @@ -26,6 +26,17 @@ class Timeline( addDomainEvent(TimelineEventFactory(this).createEvent(TimelineEvent.CHANGE_VISIBILITY)) } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Timeline + + return id == other.id + } + + override fun hashCode(): Int = id.hashCode() + companion object { fun create( id: TimelineId, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorQueryMapper.kt index e3bcab78..5e3c96c8 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorQueryMapper.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorQueryMapper.kt @@ -35,7 +35,7 @@ class ActorQueryMapper(private val actorResultRowMapper: ResultRowMapper) .first() .let(actorResultRowMapper::map) .apply { - alsoKnownAs = buildAlsoKnownAs(it) + setAlsoKnownAs(buildAlsoKnownAs(it)) clearDomainEvents() } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorResultRowMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorResultRowMapper.kt index c552c541..d4daa3d4 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorResultRowMapper.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/ActorResultRowMapper.kt @@ -24,7 +24,6 @@ import dev.usbharu.hideout.core.domain.model.support.domain.Domain import dev.usbharu.hideout.core.infrastructure.exposedrepository.Actors import org.jetbrains.exposed.sql.ResultRow import org.springframework.stereotype.Component -import java.net.URI @Component class ActorResultRowMapper : ResultRowMapper { @@ -35,15 +34,15 @@ class ActorResultRowMapper : ResultRowMapper { domain = Domain(resultRow[Actors.domain]), screenName = ActorScreenName(resultRow[Actors.screenName]), description = ActorDescription(resultRow[Actors.description]), - inbox = URI.create(resultRow[Actors.inbox]), - outbox = URI.create(resultRow[Actors.outbox]), - url = URI.create(resultRow[Actors.url]), + inbox = resultRow[Actors.inbox], + outbox = resultRow[Actors.outbox], + url = resultRow[Actors.url], publicKey = ActorPublicKey(resultRow[Actors.publicKey]), privateKey = resultRow[Actors.privateKey]?.let { ActorPrivateKey(it) }, createdAt = resultRow[Actors.createdAt], keyId = ActorKeyId(resultRow[Actors.keyId]), - followersEndpoint = resultRow[Actors.followers]?.let { URI.create(it) }, - followingEndpoint = resultRow[Actors.following]?.let { URI.create(it) }, + followersEndpoint = resultRow[Actors.followers], + followingEndpoint = resultRow[Actors.following], instance = InstanceId(resultRow[Actors.instance]), locked = resultRow[Actors.locked], followersCount = resultRow[Actors.followersCount]?.let { ActorRelationshipCount(it) }, diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt index 5b6039c4..f0c2814d 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/FilterQueryMapper.kt @@ -33,7 +33,7 @@ class FilterQueryMapper(private val filterResultRowMapper: ResultRowMapper FilterKeyword( diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UriColumnType.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UriColumnType.kt new file mode 100644 index 00000000..06f2388b --- /dev/null +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposed/UriColumnType.kt @@ -0,0 +1,22 @@ +package dev.usbharu.hideout.core.infrastructure.exposed + +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.ColumnType +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.vendors.currentDialect +import java.net.URI + +class UriColumnType(val colLength: Int) : ColumnType() { + override fun sqlType(): String = currentDialect.dataTypeProvider.varcharType(colLength) + + override fun valueFromDB(value: Any): URI? = when (value) { + is URI -> value + is String -> URI(value) + is CharSequence -> URI(value.toString()) + else -> error("Unexpected value of type String: $value of ${value::class.qualifiedName}") + } + + override fun notNullValueToDB(value: URI): Any = value.toString() +} + +fun Table.uri(name: String, colLength: Int): Column = registerColumn(name, UriColumnType(colLength)) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt index 6bc068ff..f63a2e53 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedquery/ExposedUserTimelineQueryService.kt @@ -90,9 +90,9 @@ class ExposedUserTimelineQueryService : UserTimelineQueryService, AbstractReposi name = it[Actors.name], domain = it[Actors.domain], screenName = it[Actors.screenName], - url = URI.create(it[Actors.url]), + url = it[Actors.url], locked = it[Actors.locked], - icon = it.getOrNull(iconMedia[Media.url])?.let { URI.create(it) } + icon = it.getOrNull(iconMedia[Media.url]) ), overview = it[authorizedQuery[Posts.overview]], text = it[authorizedQuery[Posts.text]], diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt index 76d80efd..74c71f07 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepository.kt @@ -75,6 +75,7 @@ class ExposedActorInstanceRelationshipRepository(override val domainEventPublish ActorInstanceRelationships.actorId eq actorId.id and (ActorInstanceRelationships.instanceId eq instanceId.instanceId) } + .limit(1) .singleOrNull() ?.toActorInstanceRelationship() } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepository.kt index 8de778e1..821d4f73 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepository.kt @@ -5,6 +5,7 @@ import dev.usbharu.hideout.core.domain.model.support.domain.Domain import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository import dev.usbharu.hideout.core.infrastructure.exposed.QueryMapper +import dev.usbharu.hideout.core.infrastructure.exposed.uri import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.javatime.timestamp @@ -30,15 +31,15 @@ class ExposedActorRepository( it[domain] = actor.domain.domain it[screenName] = actor.screenName.screenName it[description] = actor.description.description - it[inbox] = actor.inbox.toString() - it[outbox] = actor.outbox.toString() - it[url] = actor.url.toString() + it[inbox] = actor.inbox + it[outbox] = actor.outbox + it[url] = actor.url it[publicKey] = actor.publicKey.publicKey it[privateKey] = actor.privateKey?.privateKey it[createdAt] = actor.createdAt it[keyId] = actor.keyId.keyId - it[following] = actor.followingEndpoint?.toString() - it[followers] = actor.followersEndpoint?.toString() + it[following] = actor.followingEndpoint + it[followers] = actor.followersEndpoint it[instance] = actor.instance.instanceId it[locked] = actor.locked it[followingCount] = actor.followingCount?.relationshipCount @@ -69,8 +70,8 @@ class ExposedActorRepository( override suspend fun delete(actor: Actor) { query { - Actors.deleteWhere { id eq actor.id.id } ActorsAlsoKnownAs.deleteWhere { actorId eq actor.id.id } + Actors.deleteWhere { id eq actor.id.id } onComplete { update(actor) } @@ -126,15 +127,15 @@ object Actors : Table("actors") { val domain = varchar("domain", Domain.LENGTH) val screenName = varchar("screen_name", ActorScreenName.LENGTH) val description = varchar("description", ActorDescription.LENGTH) - val inbox = varchar("inbox", 1000).uniqueIndex() - val outbox = varchar("outbox", 1000).uniqueIndex() - val url = varchar("url", 1000).uniqueIndex() + val inbox = uri("inbox", 1000).uniqueIndex() + val outbox = uri("outbox", 1000).uniqueIndex() + val url = uri("url", 1000).uniqueIndex() val publicKey = varchar("public_key", 10000) val privateKey = varchar("private_key", 100000).nullable() val createdAt = timestamp("created_at") val keyId = varchar("key_id", 1000) - val following = varchar("following", 1000).nullable() - val followers = varchar("followers", 1000).nullable() + val following = uri("following", 1000).nullable() + val followers = uri("followers", 1000).nullable() val instance = long("instance").references(Instance.id) val locked = bool("locked") val followingCount = integer("following_count").nullable() diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedCustomEmojiRepository.kt similarity index 91% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedCustomEmojiRepository.kt index e3ca9079..7f1a0b50 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/CustomEmojiRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedCustomEmojiRepository.kt @@ -31,8 +31,7 @@ import org.springframework.stereotype.Repository import java.net.URI @Repository -class CustomEmojiRepositoryImpl : CustomEmojiRepository, - AbstractRepository() { +class ExposedCustomEmojiRepository : CustomEmojiRepository, AbstractRepository() { override val logger: Logger get() = Companion.logger @@ -50,7 +49,12 @@ class CustomEmojiRepositoryImpl : CustomEmojiRepository, } override suspend fun findById(id: Long): CustomEmoji? = query { - return@query CustomEmojis.selectAll().where { CustomEmojis.id eq id }.singleOrNull()?.toCustomEmoji() + CustomEmojis + .selectAll() + .where { CustomEmojis.id eq id } + .limit(1) + .singleOrNull() + ?.toCustomEmoji() } override suspend fun delete(customEmoji: CustomEmoji): Unit = query { @@ -58,7 +62,7 @@ class CustomEmojiRepositoryImpl : CustomEmojiRepository, } override suspend fun findByNamesAndDomain(names: List, domain: String): List = query { - return@query CustomEmojis + CustomEmojis .selectAll() .where { CustomEmojis.name inList names and (CustomEmojis.domain eq domain) @@ -67,7 +71,7 @@ class CustomEmojiRepositoryImpl : CustomEmojiRepository, } override suspend fun findByIds(ids: List): List = query { - return@query CustomEmojis + CustomEmojis .selectAll() .where { CustomEmojis.id inList ids @@ -76,7 +80,7 @@ class CustomEmojiRepositoryImpl : CustomEmojiRepository, } companion object { - private val logger = LoggerFactory.getLogger(CustomEmojiRepositoryImpl::class.java) + private val logger = LoggerFactory.getLogger(ExposedCustomEmojiRepository::class.java) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt index 765641f1..837ccee9 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepository.kt @@ -63,18 +63,20 @@ class ExposedFilterRepository(private val filterQueryMapper: QueryMapper val filterId = FilterKeywords .selectAll() .where { FilterKeywords.id eq filterKeywordId.id } + .limit(1) .firstOrNull()?.get(FilterKeywords.filterId) ?: return@query null - val where = Filters.selectAll().where { Filters.id eq filterId } + val where = Filters.leftJoin(FilterKeywords).selectAll().where { Filters.id eq filterId } return@query filterQueryMapper.map(where).firstOrNull() } override suspend fun findByFilterId(filterId: FilterId): Filter? = query { - val where = Filters.selectAll().where { Filters.id eq filterId.id } + val where = Filters.leftJoin(FilterKeywords).selectAll().where { Filters.id eq filterId.id } return@query filterQueryMapper.map(where).firstOrNull() } override suspend fun findByUserDetailId(userDetailId: UserDetailId): List = query { - return@query Filters.selectAll().where { Filters.userId eq userDetailId.id }.let(filterQueryMapper::map) + return@query Filters.leftJoin(FilterKeywords).selectAll().where { Filters.userId eq userDetailId.id } + .let(filterQueryMapper::map) } companion object { diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/InstanceRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedInstanceRepository.kt similarity index 82% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/InstanceRepositoryImpl.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedInstanceRepository.kt index de57ff44..1f4e35f4 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/InstanceRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedInstanceRepository.kt @@ -17,6 +17,7 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository import dev.usbharu.hideout.core.domain.model.instance.* +import dev.usbharu.hideout.core.infrastructure.exposed.uri import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.javatime.timestamp @@ -27,7 +28,7 @@ import java.net.URI import dev.usbharu.hideout.core.domain.model.instance.Instance as InstanceEntity @Repository -class InstanceRepositoryImpl : InstanceRepository, +class ExposedInstanceRepository : InstanceRepository, AbstractRepository() { override val logger: Logger get() = Companion.logger @@ -37,9 +38,9 @@ class InstanceRepositoryImpl : InstanceRepository, it[id] = instance.id.instanceId it[name] = instance.name.name it[description] = instance.description.description - it[url] = instance.url.toString() - it[iconUrl] = instance.iconUrl.toString() - it[sharedInbox] = instance.sharedInbox?.toString() + it[url] = instance.url + it[iconUrl] = instance.iconUrl + it[sharedInbox] = instance.sharedInbox it[software] = instance.software.software it[version] = instance.version.version it[isBlocked] = instance.isBlocked @@ -52,7 +53,7 @@ class InstanceRepositoryImpl : InstanceRepository, } override suspend fun findById(id: InstanceId): InstanceEntity? = query { - return@query Instance.selectAll().where { Instance.id eq id.instanceId } + return@query Instance.selectAll().where { Instance.id eq id.instanceId }.limit(1) .singleOrNull()?.toInstance() } @@ -61,11 +62,11 @@ class InstanceRepositoryImpl : InstanceRepository, } override suspend fun findByUrl(url: URI): dev.usbharu.hideout.core.domain.model.instance.Instance? = query { - return@query Instance.selectAll().where { Instance.url eq url.toString() }.singleOrNull()?.toInstance() + return@query Instance.selectAll().where { Instance.url eq url }.limit(1).singleOrNull()?.toInstance() } companion object { - private val logger = LoggerFactory.getLogger(InstanceRepositoryImpl::class.java) + private val logger = LoggerFactory.getLogger(ExposedInstanceRepository::class.java) } } @@ -74,9 +75,9 @@ fun ResultRow.toInstance(): InstanceEntity { id = InstanceId(this[Instance.id]), name = InstanceName(this[Instance.name]), description = InstanceDescription(this[Instance.description]), - url = URI.create(this[Instance.url]), - iconUrl = URI.create(this[Instance.iconUrl]), - sharedInbox = this[Instance.sharedInbox]?.let { URI.create(it) }, + url = this[Instance.url], + iconUrl = this[Instance.iconUrl], + sharedInbox = this[Instance.sharedInbox], software = InstanceSoftware(this[Instance.software]), version = InstanceVersion(this[Instance.version]), isBlocked = this[Instance.isBlocked], @@ -90,9 +91,9 @@ object Instance : Table("instance") { val id = long("id") val name = varchar("name", 1000) val description = varchar("description", 5000) - val url = varchar("url", 255).uniqueIndex() - val iconUrl = varchar("icon_url", 255) - val sharedInbox = varchar("shared_inbox", 255).nullable().uniqueIndex() + val url = uri("url", 255).uniqueIndex() + val iconUrl = uri("icon_url", 255) + val sharedInbox = uri("shared_inbox", 255).nullable().uniqueIndex() val software = varchar("software", 255) val version = varchar("version", 255) val isBlocked = bool("is_blocked") diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedMediaRepository.kt similarity index 79% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedMediaRepository.kt index 81fddc23..03502b72 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/MediaRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedMediaRepository.kt @@ -18,16 +18,16 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository import dev.usbharu.hideout.core.domain.model.actor.ActorId import dev.usbharu.hideout.core.domain.model.media.* +import dev.usbharu.hideout.core.infrastructure.exposed.uri import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository -import java.net.URI import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia @Repository -class MediaRepositoryImpl : MediaRepository, AbstractRepository() { +class ExposedMediaRepository : MediaRepository, AbstractRepository() { override val logger: Logger get() = Companion.logger @@ -35,9 +35,9 @@ class MediaRepositoryImpl : MediaRepository, AbstractRepository() { Media.upsert { it[id] = media.id.id it[name] = media.name.name - it[url] = media.url.toString() - it[remoteUrl] = media.remoteUrl?.toString() - it[thumbnailUrl] = media.thumbnailUrl?.toString() + it[url] = media.url + it[remoteUrl] = media.remoteUrl + it[thumbnailUrl] = media.thumbnailUrl it[type] = media.type.name it[blurhash] = media.blurHash?.hash it[mimeType] = media.mimeType.type + "/" + media.mimeType.subtype @@ -51,12 +51,13 @@ class MediaRepositoryImpl : MediaRepository, AbstractRepository() { return query { return@query Media .selectAll().where { Media.id eq id.id } + .limit(1) .singleOrNull() ?.toMedia() } } - override suspend fun findByIds(ids: List): List { + override suspend fun findByIdIn(ids: List): List { return query { return@query Media .selectAll() @@ -72,7 +73,7 @@ class MediaRepositoryImpl : MediaRepository, AbstractRepository() { } companion object { - private val logger = LoggerFactory.getLogger(MediaRepositoryImpl::class.java) + private val logger = LoggerFactory.getLogger(ExposedMediaRepository::class.java) } } @@ -82,9 +83,9 @@ fun ResultRow.toMedia(): EntityMedia { return EntityMedia( id = MediaId(this[Media.id]), name = MediaName(this[Media.name]), - url = URI.create(this[Media.url]), - remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) }, - thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) }, + url = this[Media.url], + remoteUrl = this[Media.remoteUrl], + thumbnailUrl = this[Media.thumbnailUrl], type = fileType, blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) }, mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType), @@ -99,9 +100,9 @@ fun ResultRow.toMediaOrNull(): EntityMedia? { return EntityMedia( id = MediaId(this.getOrNull(Media.id) ?: return null), name = MediaName(this.getOrNull(Media.name) ?: return null), - url = URI.create(this.getOrNull(Media.url) ?: return null), - remoteUrl = this[Media.remoteUrl]?.let { URI.create(it) }, - thumbnailUrl = this[Media.thumbnailUrl]?.let { URI.create(it) }, + url = this.getOrNull(Media.url) ?: return null, + remoteUrl = this[Media.remoteUrl], + thumbnailUrl = this[Media.thumbnailUrl], type = FileType.valueOf(this[Media.type]), blurHash = this[Media.blurhash]?.let { MediaBlurHash(it) }, mimeType = MimeType(mimeType.substringBefore("/"), mimeType.substringAfter("/"), fileType), @@ -113,9 +114,9 @@ fun ResultRow.toMediaOrNull(): EntityMedia? { object Media : Table("media") { val id = long("id") val name = varchar("name", 255) - val url = varchar("url", 255).uniqueIndex() - val remoteUrl = varchar("remote_url", 255).uniqueIndex().nullable() - val thumbnailUrl = varchar("thumbnail_url", 255).uniqueIndex().nullable() + val url = uri("url", 255).uniqueIndex() + val remoteUrl = uri("remote_url", 255).uniqueIndex().nullable() + val thumbnailUrl = uri("thumbnail_url", 255).uniqueIndex().nullable() val type = varchar("type", 100) val blurhash = varchar("blurhash", 255).nullable() val mimeType = varchar("mime_type", 255) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt index 37a476b1..7bbe3782 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepository.kt @@ -163,6 +163,8 @@ class ExposedPostRepository( override suspend fun findById(id: PostId): Post? = query { Posts .leftJoin(PostsMedia) + .leftJoin(PostsEmojis) + .leftJoin(PostsVisibleActors) .selectAll() .where { Posts.id eq id.id @@ -174,6 +176,9 @@ class ExposedPostRepository( override suspend fun findAllById(ids: List): List { return query { Posts + .leftJoin(PostsMedia) + .leftJoin(PostsEmojis) + .leftJoin(PostsVisibleActors) .selectAll() .where { Posts.id inList ids.map { it.id } @@ -182,21 +187,43 @@ class ExposedPostRepository( } } - override suspend fun findByActorId(id: ActorId, page: Page?): PaginationList = PaginationList( - query { - Posts + override suspend fun findByActorId(id: ActorId, page: Page?): PaginationList { + val postList = query { + val query = Posts .selectAll() .where { - actorId eq actorId + actorId eq id.id } - .let(postQueryMapper::map) - }, - null, - null - ) + + page(page, query) + + query.let(postQueryMapper::map) + } + + val posts = if (page?.minId != null) { + postList.reversed() + } else { + postList + } + + return PaginationList( + posts, + posts.lastOrNull()?.id, + posts.firstOrNull()?.id + ) + } override suspend fun delete(post: Post) { query { + PostsMedia.deleteWhere { + postId eq post.id.id + } + PostsEmojis.deleteWhere { + postId eq post.id.id + } + PostsVisibleActors.deleteWhere { + postId eq post.id.id + } Posts.deleteWhere { id eq post.id.id } @@ -218,17 +245,7 @@ class ExposedPostRepository( Posts.actorId eq actorId.id and (visibility inList visibilityList.map { it.name }) } - if (of?.minId != null) { - query.orderBy(Posts.createdAt, SortOrder.ASC) - of.minId?.let { query.andWhere { Posts.id greater it } } - of.maxId?.let { query.andWhere { Posts.id less it } } - } else { - query.orderBy(Posts.createdAt, SortOrder.DESC) - of?.sinceId?.let { query.andWhere { Posts.id greater it } } - of?.maxId?.let { query.andWhere { Posts.id less it } } - } - - of?.limit?.let { query.limit(it) } + page(of, query) query.let(postQueryMapper::map) } @@ -246,6 +263,23 @@ class ExposedPostRepository( ) } + private fun page( + page: Page?, + query: Query + ) { + if (page?.minId != null) { + query.orderBy(createdAt, SortOrder.ASC) + page.minId!!.let { query.andWhere { id greater it } } + page.maxId?.let { query.andWhere { id less it } } + } else { + query.orderBy(createdAt, SortOrder.DESC) + page?.sinceId?.let { query.andWhere { id greater it } } + page?.maxId?.let { query.andWhere { id less it } } + } + + page?.limit?.let { query.limit(it) } + } + companion object { private val logger = LoggerFactory.getLogger(ExposedPostRepository::class.java) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt index 73675308..b4827a4f 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepository.kt @@ -17,7 +17,6 @@ package dev.usbharu.hideout.core.infrastructure.exposedrepository import dev.usbharu.hideout.core.domain.model.actor.ActorId -import dev.usbharu.hideout.core.domain.model.relationship.FindRelationshipOption import dev.usbharu.hideout.core.domain.model.relationship.Relationship import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher @@ -69,7 +68,7 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve override suspend fun findByActorIdAndTargetId(actorId: ActorId, targetId: ActorId): Relationship? = query { Relationships.selectAll().where { Relationships.actorId eq actorId.id and (Relationships.targetActorId eq targetId.id) - }.singleOrNull()?.toRelationships() + }.limit(1).singleOrNull()?.toRelationships() } override suspend fun findByActorIdsAndTargetIdAndBlocking( @@ -78,7 +77,9 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve blocking: Boolean ): List = query { Relationships.selectAll().where { - Relationships.actorId inList actorIds.map { it.id } and (Relationships.targetActorId eq targetId.id) + Relationships.actorId inList actorIds.map { + it.id + } and (Relationships.targetActorId eq targetId.id) and (Relationships.blocking eq blocking) }.map { it.toRelationships() } } @@ -88,49 +89,19 @@ class ExposedRelationshipRepository(override val domainEventPublisher: DomainEve following: Boolean ): List = query { Relationships.selectAll().where { - Relationships.actorId eq actorId.id and (Relationships.targetActorId inList targetIds.map { it.id }) + Relationships.actorId eq actorId.id and ( + Relationships.targetActorId inList targetIds.map { + it.id + } + ) and (Relationships.following eq following) }.map { it.toRelationships() } } - override suspend fun findByTargetId( - targetId: ActorId, - option: FindRelationshipOption?, - inverseOption: FindRelationshipOption? - ): List { - val query1 = Relationships.selectAll().where { Relationships.actorId eq targetId.id } - inverseOption.apply(query1) - // todo 逆のほうがいいかも - val query = query1.alias("INV").selectAll().where { - Relationships.targetActorId eq targetId.id - } - option.apply(query) - - return query.map(ResultRow::toRelationships) - } - companion object { private val logger = LoggerFactory.getLogger(ExposedRelationshipRepository::class.java) } } -fun FindRelationshipOption?.apply(query: Query) { - if (this?.follow != null) { - query.andWhere { Relationships.following eq this@apply.follow } - } - if (this?.mute != null) { - query.andWhere { Relationships.muting eq this@apply.mute } - } - if (this?.block != null) { - query.andWhere { Relationships.blocking eq this@apply.block } - } - if (this?.followRequest != null) { - query.andWhere { Relationships.followRequesting eq this@apply.followRequest } - } - if (this?.muteFollowRequest != null) { - query.andWhere { Relationships.mutingFollowRequest eq this@apply.muteFollowRequest } - } -} - fun ResultRow.toRelationships(): Relationship = Relationship( actorId = ActorId(this[Relationships.actorId]), targetActorId = ActorId(this[Relationships.targetActorId]), diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt index 9fe9f694..8f5f8c65 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepository.kt @@ -54,7 +54,7 @@ class ExposedTimelineRepository(override val domainEventPublisher: DomainEventPu override suspend fun findById(id: TimelineId): Timeline? { return query { - Timelines.selectAll().where { Timelines.id eq id.value }.firstOrNull()?.toTimeline() + Timelines.selectAll().where { Timelines.id eq id.value }.limit(1).firstOrNull()?.toTimeline() } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedUserDetailRepository.kt similarity index 83% rename from hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt rename to hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedUserDetailRepository.kt index 8251ec08..cacaa922 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/UserDetailRepositoryImpl.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedUserDetailRepository.kt @@ -32,31 +32,27 @@ import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository @Repository -class UserDetailRepositoryImpl(override val domainEventPublisher: DomainEventPublisher) : +class ExposedUserDetailRepository(override val domainEventPublisher: DomainEventPublisher) : UserDetailRepository, AbstractRepository(), DomainEventPublishableRepository { override val logger: Logger get() = Companion.logger - override suspend fun save(userDetail: UserDetail): UserDetail { - val userDetail1 = query { - UserDetails.upsert { - it[id] = userDetail.id.id - it[actorId] = userDetail.actorId.id - it[password] = userDetail.password.password - it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest - it[lastMigration] = userDetail.lastMigration - it[homeTimelineId] = userDetail.homeTimelineId?.value - } - - onComplete { - update(userDetail) - } - userDetail + override suspend fun save(userDetail: UserDetail): UserDetail = query { + UserDetails.upsert { + it[id] = userDetail.id.id + it[actorId] = userDetail.actorId.id + it[password] = userDetail.password.password + it[autoAcceptFolloweeFollowRequest] = userDetail.autoAcceptFolloweeFollowRequest + it[lastMigration] = userDetail.lastMigration + it[homeTimelineId] = userDetail.homeTimelineId?.value } - return userDetail1 + onComplete { + update(userDetail) + } + userDetail } override suspend fun delete(userDetail: UserDetail) { @@ -71,6 +67,7 @@ class UserDetailRepositoryImpl(override val domainEventPublisher: DomainEventPub override suspend fun findByActorId(actorId: Long): UserDetail? = query { return@query UserDetails .selectAll().where { UserDetails.actorId eq actorId } + .limit(1) .singleOrNull() ?.let { userDetail(it) @@ -80,6 +77,7 @@ class UserDetailRepositoryImpl(override val domainEventPublisher: DomainEventPub override suspend fun findById(id: UserDetailId): UserDetail? = query { UserDetails .selectAll().where { UserDetails.id eq id.id } + .limit(1) .singleOrNull() ?.let { userDetail(it) @@ -107,7 +105,7 @@ class UserDetailRepositoryImpl(override val domainEventPublisher: DomainEventPub ) companion object { - private val logger = LoggerFactory.getLogger(UserDetailRepositoryImpl::class.java) + private val logger = LoggerFactory.getLogger(ExposedUserDetailRepository::class.java) } } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStore.kt index c435bc85..e25751cd 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStore.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStore.kt @@ -158,7 +158,7 @@ open class DefaultTimelineStore( actorRepository.findAllById(actorIds).associateBy { it.id } override suspend fun getMedias(mediaIds: List): Map = - mediaRepository.findByIds(mediaIds).associateBy { it.id } + mediaRepository.findByIdIn(mediaIds).associateBy { it.id } override suspend fun getReactions(postIds: List): Map> = reactionsQueryService.findAllByPostIdIn(postIds).groupBy { PostId(it.postId) } diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorsTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorsTest.kt index d4f55ff9..e98a7113 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorsTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorsTest.kt @@ -34,7 +34,7 @@ class ActorsTest { val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) val actorIds = setOf(ActorId(100), ActorId(200)) - actor.alsoKnownAs = actorIds + actor.setAlsoKnownAs(actorIds) assertEquals(actorIds, actor.alsoKnownAs) } @@ -44,7 +44,7 @@ class ActorsTest { val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) - actor.moveTo = ActorId(100) + actor.setMoveTo(ActorId(100)) assertContainsEvent(actor, ActorEvent.MOVE.eventName) } @@ -54,7 +54,7 @@ class ActorsTest { val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) assertThrows { - actor.alsoKnownAs = setOf(actor.id) + actor.setAlsoKnownAs(setOf(actor.id)) } } @@ -63,7 +63,7 @@ class ActorsTest { val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) assertThrows { - actor.moveTo = actor.id + actor.setMoveTo(actor.id) } } @@ -71,7 +71,7 @@ class ActorsTest { fun descriptionが更新されたときupdateイベントが発生する() { val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) - actor.description = ActorDescription("hoge fuga") + actor.setDescription(ActorDescription("hoge fuga")) assertContainsEvent(actor, ActorEvent.UPDATE.eventName) } @@ -80,7 +80,7 @@ class ActorsTest { fun screenNameが更新されたときupdateイベントが発生する() { val actor = TestActorFactory.create(publicKey = ActorPublicKey("")) - actor.screenName = ActorScreenName("fuga hoge") + actor.setScreenName(ActorScreenName("fuga hoge")) assertContainsEvent(actor, ActorEvent.UPDATE.eventName) } diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/TestActorFactory.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/TestActorFactory.kt index 4f81a194..c39e8818 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/TestActorFactory.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/actor/TestActorFactory.kt @@ -2,6 +2,7 @@ package dev.usbharu.hideout.core.domain.model.actor import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.domain.model.support.domain.Domain import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService import kotlinx.coroutines.runBlocking @@ -32,12 +33,14 @@ object TestActorFactory { followingCount: Int = 0, postCount: Int = 0, lastPostDate: Instant? = null, + lastUpdateAt: Instant = createdAt, suspend: Boolean = false, alsoKnownAs: Set = emptySet(), moveTo: Long? = null, emojiIds: Set = emptySet(), deleted: Boolean = false, - roles: Set = emptySet(), + icon: Long? = null, + banner: Long? = null, ): Actor { return runBlocking { Actor( @@ -61,13 +64,14 @@ object TestActorFactory { followingCount = ActorRelationshipCount(followingCount), postsCount = ActorPostsCount(postCount), lastPostAt = lastPostDate, + lastUpdateAt = lastUpdateAt, suspend = suspend, alsoKnownAs = alsoKnownAs, moveTo = moveTo?.let { ActorId(it) }, emojiIds = emojiIds, deleted = deleted, - icon = null, - banner = null, + icon = icon?.let { MediaId(it) }, + banner = banner?.let { MediaId(it) }, ) } diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/TestPostFactory.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/TestPostFactory.kt index 388aaca0..8dea9878 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/TestPostFactory.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/domain/model/post/TestPostFactory.kt @@ -1,6 +1,7 @@ package dev.usbharu.hideout.core.domain.model.post import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId import dev.usbharu.hideout.core.domain.model.instance.InstanceId import dev.usbharu.hideout.core.domain.model.media.MediaId import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService @@ -29,13 +30,14 @@ object TestPostFactory { visibleActors: List = emptyList(), hide: Boolean = false, moveTo: Long? = null, + emojiIds: List = emptyList(), ): Post { return Post( PostId(id), ActorId(actorId), instanceId = InstanceId(instanceId), overview = overview?.let { PostOverview(it) }, - content = PostContent(content, content, emptyList()), + content = PostContent(content, content, emojiIds.map { CustomEmojiId(it) }), createdAt = createdAt, visibility = visibility, url = url, diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepositoryTest.kt new file mode 100644 index 00000000..6ed40eb6 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorInstanceRelationshipRepositoryTest.kt @@ -0,0 +1,289 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup.Operations +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationship +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import kotlinx.coroutines.test.runTest +import org.assertj.db.api.Assertions.assertThat +import org.jetbrains.exposed.sql.transactions.TransactionManager +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.times +import org.mockito.kotlin.verify +import utils.AbstractRepositoryTest +import utils.columns +import utils.disableReferenceIntegrityConstraints +import utils.isEqualTo +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@ExtendWith(MockitoExtension::class) +class ExposedActorInstanceRelationshipRepositoryTest : AbstractRepositoryTest(ActorInstanceRelationships) { + + @InjectMocks + lateinit var repository: ExposedActorInstanceRelationshipRepository + + @Mock + lateinit var domainEventPublisher: DomainEventPublisher + + @Test + fun save_idが同じレコードがない場合はinsert() = runTest { + dbSetup(to = dataSource) { + execute(Operations.sql("SET REFERENTIAL_INTEGRITY FALSE")) + insertInto(Instance.tableName) { + columns( + Instance.columns + ) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + insertInto("public.actors") { + columns( + Actors.columns + ) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + }.launch() + + + val actorInstanceRelationship = ActorInstanceRelationship( + actorId = ActorId(1), instanceId = InstanceId(1), blocking = false, muting = false, doNotSendPrivate = false + ) + + repository.save(actorInstanceRelationship) + + dbSetup(to = dataSource) { + execute(Operations.sql("SET REFERENTIAL_INTEGRITY TRUE")) + } + + assertThat(assertTable).row(0).isEqualTo(ActorInstanceRelationships.actorId, 1) + .isEqualTo(ActorInstanceRelationships.actorId, 1).isEqualTo(ActorInstanceRelationships.blocking, false) + .isEqualTo(ActorInstanceRelationships.muting, false) + .isEqualTo(ActorInstanceRelationships.doNotSendPrivate, false) + } + + @Test + fun save_idが同じレコードがある場合はupdate() = runTest { + dbSetup(to = dataSource) { + execute(Operations.sql("SET REFERENTIAL_INTEGRITY FALSE")) + insertInto(Instance.tableName) { + columns( + Instance.columns + ) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + insertInto("public.actors") { + columns( + Actors.columns + ) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(ActorInstanceRelationships.tableName) { + columns(ActorInstanceRelationships.columns) + values(1, 1, true, true, true) + } + }.launch() + + + val actorInstanceRelationship = ActorInstanceRelationship( + actorId = ActorId(1), instanceId = InstanceId(1), blocking = false, muting = false, doNotSendPrivate = false + ) + + repository.save(actorInstanceRelationship) + + dbSetup(to = dataSource) { + execute(Operations.sql("SET REFERENTIAL_INTEGRITY TRUE")) + } + + assertThat(assertTable).row(0).isEqualTo(ActorInstanceRelationships.actorId, 1) + .isEqualTo(ActorInstanceRelationships.actorId, 1).isEqualTo(ActorInstanceRelationships.blocking, false) + .isEqualTo(ActorInstanceRelationships.muting, false) + .isEqualTo(ActorInstanceRelationships.doNotSendPrivate, false) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(Operations.sql("SET REFERENTIAL_INTEGRITY FALSE")) + insertInto(ActorInstanceRelationships.tableName) { + columns(ActorInstanceRelationships.columns) + values(1, 1, true, true, true) + } + }.launch() + + val actorInstanceRelationship = ActorInstanceRelationship( + actorId = ActorId(1), instanceId = InstanceId(1), blocking = false, muting = false, doNotSendPrivate = false + ) + + change.setStartPointNow() + + repository.delete(actorInstanceRelationship) + + change.setEndPointNow() + + assertThat(change) + .changeOfDeletionOnTable(ActorInstanceRelationships.tableName) + .rowAtStartPoint() + .value(ActorInstanceRelationships.instanceId.name).isEqualTo(1) + .value(ActorInstanceRelationships.actorId.name).isEqualTo(1) + } + + @Test + fun findByActorIdAndInstanceId_指定したActorIdとInstanceIdで存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(ActorInstanceRelationships.tableName) { + columns(ActorInstanceRelationships.columns) + values(1, 1, true, true, true) + } + }.launch() + + val expected = ActorInstanceRelationship( + actorId = ActorId(1), instanceId = InstanceId(1), blocking = true, muting = true, doNotSendPrivate = true + ) + + val actual = repository.findByActorIdAndInstanceId(ActorId(1), InstanceId(1)) + + assertNotNull(actual) + assertEquals(expected, actual) + assertEquals(expected.actorId, actual.actorId) + assertEquals(expected.instanceId, actual.instanceId) + assertEquals(expected.blocking, actual.blocking) + assertEquals(expected.muting, actual.muting) + assertEquals(expected.doNotSendPrivate, actual.doNotSendPrivate) + } + + @Test + fun findByActorIdAndInstanceId_指定したActorIdとInstanceIdで存在しないとnull() = runTest { + assertNull(repository.findByActorIdAndInstanceId(ActorId(1), InstanceId(1))) + } + + @Test + fun save_ドメインイベントがパブリッシュされる() = runTest { + + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + }.launch() + + val actorInstanceRelationship = ActorInstanceRelationship( + actorId = ActorId(1), instanceId = InstanceId(1), blocking = false, muting = false, doNotSendPrivate = false + ) + actorInstanceRelationship.block() + repository.save(actorInstanceRelationship) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } + + @Test + fun delete_ドメインイベントがパブリッシュされる() = runTest { + + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(ActorInstanceRelationships.tableName) { + columns(ActorInstanceRelationships.columns) + values(1, 1, true, true, true) + } + }.launch() + + val actorInstanceRelationship = ActorInstanceRelationship( + actorId = ActorId(1), instanceId = InstanceId(1), blocking = false, muting = false, doNotSendPrivate = false + ) + actorInstanceRelationship.block() + repository.delete(actorInstanceRelationship) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepositoryTest.kt new file mode 100644 index 00000000..2cbe8f22 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedActorRepositoryTest.kt @@ -0,0 +1,558 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.* +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import dev.usbharu.hideout.core.infrastructure.exposed.ActorQueryMapper +import dev.usbharu.hideout.core.infrastructure.exposed.ActorResultRowMapper +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.db.api.Assertions.assertThat +import org.assertj.db.type.Changes +import org.jetbrains.exposed.sql.transactions.TransactionManager +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.any +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import utils.* +import java.net.URI +import java.sql.Timestamp +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@ExtendWith(MockitoExtension::class) +class ExposedActorRepositoryTest : AbstractRepositoryTest(Actors) { + + @InjectMocks + lateinit var repository: ExposedActorRepository + + @Mock + lateinit var domainEventPublisher: DomainEventPublisher + + @Spy + val actorQueryMapper = ActorQueryMapper(ActorResultRowMapper()) + + @Test + fun save_idが同じレコードがない場合はinsert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Instance.tableName) { + columns(Instance.columns) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val actor = TestActorFactory.create() + + repository.save(actor) + + assertThat(assertTable) + .row(1) + .isEqualTo(Actors.id, actor.id.id) + .isEqualTo(Actors.name, actor.name.name) + .isEqualTo(Actors.domain, actor.domain.domain) + .isEqualTo(Actors.screenName, actor.screenName.screenName) + .isEqualTo(Actors.description, actor.description.description) + .value(Actors.url).isEqualTo(actor.url.toString()) + .value(Actors.inbox).isEqualTo(actor.inbox.toString()) + .value(Actors.outbox).isEqualTo(actor.outbox.toString()) + .isEqualTo(Actors.publicKey, actor.publicKey.publicKey) + .isEqualTo(Actors.privateKey, actor.privateKey?.privateKey) + } + + @Test + fun save_idが同じレコードがある場合はupdate() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Instance.tableName) { + columns(Instance.columns) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(ActorsAlsoKnownAs.tableName) { + columns(ActorsAlsoKnownAs.columns) + values(1, 2) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val actor = TestActorFactory.create(1, alsoKnownAs = setOf(ActorId(1))) + + repository.save(actor) + + assertThat(assertTable) + .row(1) + .isEqualTo(Actors.id, actor.id.id) + .isEqualTo(Actors.name, actor.name.name) + .isEqualTo(Actors.domain, actor.domain.domain) + .isEqualTo(Actors.screenName, actor.screenName.screenName) + .isEqualTo(Actors.description, actor.description.description) + .value(Actors.url).isEqualTo(actor.url.toString()) + .value(Actors.inbox).isEqualTo(actor.inbox.toString()) + .value(Actors.outbox).isEqualTo(actor.outbox.toString()) + .isEqualTo(Actors.publicKey, actor.publicKey.publicKey) + .isEqualTo(Actors.privateKey, actor.privateKey?.privateKey) + + assertThat(getTable(ActorsAlsoKnownAs.tableName)) + .row(0) + .isEqualTo(ActorsAlsoKnownAs.actorId, 1) + .isEqualTo(ActorsAlsoKnownAs.alsoKnownAs, 1) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(ActorsAlsoKnownAs.tableName) { + columns(ActorsAlsoKnownAs.columns) + values(1, 1) + } + }.launch() + + val actor = TestActorFactory.create(1, alsoKnownAs = setOf(ActorId(1))) + + val changes = Changes(dataSource) + changes.withSuspend { + repository.delete(actor) + } + + assertThat(changes) + .changeOfDeletionOnTable(Actors.tableName) + .rowAtStartPoint() + .value(Actors.id.name).isEqualTo(actor.id.id) + .changeOfDeletionOnTable(ActorsAlsoKnownAs.tableName) + .rowAtStartPoint() + .value(ActorsAlsoKnownAs.alsoKnownAs.name) + .isEqualTo(actor.alsoKnownAs.first().id) + } + + @Test + fun findById_指定されたIdがあれば返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + }.launch() + + val expect = TestActorFactory.create( + id = 1, + actorName = "b", + domain = "test-hideout-dev.usbharu.dev", + actorScreenName = "b", + description = "", + inbox = URI.create("https://test-hideout-dev.usbharu.dev/users/b/inbox"), + outbox = URI.create("https://test-hideout-dev.usbharu.dev/users/b/outbox"), + uri = URI.create("https://test-hideout-dev.usbharu.dev/users/b"), + publicKey = ActorPublicKey("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----"), + privateKey = ActorPrivateKey( + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + ), + createdAt = Timestamp.valueOf("2024-09-09 17:12:03.941339").toInstant(), + keyId = "https://test-hideout-dev.usbharu.dev/users/b#main-key", + followingEndpoint = URI.create("https://test-hideout-dev.usbharu.dev/users/b/following"), + followersEndpoint = URI.create("https://test-hideout-dev.usbharu.dev/users/b/followers"), + instanceId = 1, + locked = false, + followersCount = 0, + followingCount = 0, + postCount = 0, + lastPostDate = null, + lastUpdateAt = Timestamp.valueOf("2024-09-09 17:12:03.941339").toInstant(), + suspend = false, + alsoKnownAs = emptySet(), + moveTo = null, + emojiIds = emptySet(), + deleted = false, + banner = null, + icon = null + ) + + val actual = repository.findById(ActorId(1)) + + assertEquals(actual, expect) + } + + private fun assertEquals( + actual: Actor?, + expect: Actor + ) { + assertNotNull(actual) + kotlin.test.assertEquals(expect, actual) + assertEquals(expect.id, actual.id) + assertEquals(expect.name, actual.name) + assertEquals(expect.domain, actual.domain) + assertEquals(expect.screenName, actual.screenName) + assertEquals(expect.description, actual.description) + assertEquals(expect.inbox, actual.inbox) + assertEquals(expect.outbox, actual.outbox) + assertEquals(expect.url, actual.url) + assertEquals(expect.publicKey, actual.publicKey) + assertEquals(expect.privateKey, actual.privateKey) + assertEquals(expect.createdAt, actual.createdAt) + assertEquals(expect.keyId, actual.keyId) + assertEquals(expect.followingEndpoint, actual.followingEndpoint) + assertEquals(expect.followersEndpoint, actual.followersEndpoint) + assertEquals(expect.postsCount, actual.postsCount) + assertEquals(expect.lastPostAt, actual.lastPostAt) + assertEquals(expect.lastUpdateAt, actual.lastUpdateAt) + assertEquals(expect.suspend, actual.suspend) + assertEquals(expect.moveTo, actual.moveTo) + assertEquals(expect.emojis, actual.emojis) + assertEquals(expect.deleted, actual.deleted) + assertEquals(expect.banner, actual.banner) + assertEquals(expect.icon, actual.icon) + assertEquals(expect.banner, actual.banner) + } + + @Test + fun findById_指定されたIdがなければnull() = runTest { + assertNull(repository.findById(ActorId(1))) + } + + @Test + fun findByNameAndDomain_指定されたNameとDomainがあれば返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + }.launch() + + val expect = TestActorFactory.create( + id = 1, + actorName = "b", + domain = "test-hideout-dev.usbharu.dev", + actorScreenName = "b", + description = "", + inbox = URI.create("https://test-hideout-dev.usbharu.dev/users/b/inbox"), + outbox = URI.create("https://test-hideout-dev.usbharu.dev/users/b/outbox"), + uri = URI.create("https://test-hideout-dev.usbharu.dev/users/b"), + publicKey = ActorPublicKey("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----"), + privateKey = ActorPrivateKey( + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + ), + createdAt = Timestamp.valueOf("2024-09-09 17:12:03.941339").toInstant(), + keyId = "https://test-hideout-dev.usbharu.dev/users/b#main-key", + followingEndpoint = URI.create("https://test-hideout-dev.usbharu.dev/users/b/following"), + followersEndpoint = URI.create("https://test-hideout-dev.usbharu.dev/users/b/followers"), + instanceId = 1, + locked = false, + followersCount = 0, + followingCount = 0, + postCount = 0, + lastPostDate = null, + lastUpdateAt = Timestamp.valueOf("2024-09-09 17:12:03.941339").toInstant(), + suspend = false, + alsoKnownAs = emptySet(), + moveTo = null, + emojiIds = emptySet(), + deleted = false, + banner = null, + icon = null + ) + + val actual = repository.findByNameAndDomain("b", "test-hideout-dev.usbharu.dev") + + assertEquals(actual, expect) + } + + @Test + fun findByNameAndDomain_指定されたNameとDomainがなければnull() = runTest { + assertNull(repository.findByNameAndDomain("a", "b")) + } + + @Test + fun findAllById_指定されたIdすべて返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + values( + 2, + "a", + "test-hideout-dev.usbharu.dev", + "a", + "", + "https://test-hideout-dev.usbharu.dev/users/a/inbox", + "https://test-hideout-dev.usbharu.dev/users/a/outbox", + "https://test-hideout-dev.usbharu.dev/users/a", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/a#main-key", + "https://test-hideout-dev.usbharu.dev/users/a/following", + "https://test-hideout-dev.usbharu.dev/users/a/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + }.launch() + + val findAllById = repository.findAllById(listOf(ActorId(1), ActorId(2))) + + assertThat(findAllById) + .hasSize(2) + } + + @Test + fun save_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + }.launch() + + val actor = TestActorFactory.create() + actor.checkUpdate() + repository.save(actor) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } + + @Test + fun delete_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(ActorsAlsoKnownAs.tableName) { + columns(ActorsAlsoKnownAs.columns) + values(1, 1) + } + }.launch() + + val actor = TestActorFactory.create(1, alsoKnownAs = setOf(ActorId(1))) + actor.delete() + + repository.delete(actor) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedApplicationRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedApplicationRepositoryTest.kt new file mode 100644 index 00000000..619db4fd --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedApplicationRepositoryTest.kt @@ -0,0 +1,70 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.application.Application +import dev.usbharu.hideout.core.domain.model.application.ApplicationId +import dev.usbharu.hideout.core.domain.model.application.ApplicationName +import kotlinx.coroutines.test.runTest +import org.assertj.db.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import utils.AbstractRepositoryTest +import utils.columns +import utils.isEqualTo +import utils.withSuspend + +class ExposedApplicationRepositoryTest : AbstractRepositoryTest(Applications) { + @Test + fun save_idが同じレコードが存在しないとinsert() = runTest { + val application = Application(ApplicationId(1), ApplicationName("test-application")) + + ExposedApplicationRepository().save(application) + + assertThat(assertTable) + .row(0) + .isEqualTo(Applications.id, application.applicationId.id) + .isEqualTo(Applications.name, application.name.name) + } + + @Test + fun save_idが同じレコードが存在したらupdate() = runTest { + dbSetup(to = dataSource) { + insertInto(Applications.tableName) { + columns(Applications.columns) + values(1, "application-test") + } + }.launch() + + val application = Application(ApplicationId(1), ApplicationName("test-application")) + + ExposedApplicationRepository().save(application) + + assertThat(assertTable) + .row(0) + .isEqualTo(Applications.id, application.applicationId.id) + .isEqualTo(Applications.name, application.name.name) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + insertInto(Applications.tableName) { + columns(Applications.columns) + values(1, "test-application") + } + }.launch() + + val application = Application(ApplicationId(1), ApplicationName("test-application")) + + change.withSuspend { + ExposedApplicationRepository().delete(application) + } + + assertThat(change) + .changeOfDeletionOnTable(Applications.tableName) + .rowAtStartPoint() + .value(Applications.id.name).isEqualTo(application.applicationId.id) + .value(Applications.name.name).isEqualTo(application.name.name) + + + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedCustomEmojiRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedCustomEmojiRepositoryTest.kt new file mode 100644 index 00000000..e32171d3 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedCustomEmojiRepositoryTest.kt @@ -0,0 +1,324 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmoji +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.model.support.domain.Domain +import kotlinx.coroutines.test.runTest +import org.assertj.db.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import utils.* +import java.net.URI +import java.sql.Timestamp +import java.time.Instant +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class ExposedCustomEmojiRepositoryTest : AbstractRepositoryTest(CustomEmojis) { + @Test + fun save_idが同じレコードが存在しないとinsert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Instance.tableName) { + columns(Instance.columns) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + val customEmoji = CustomEmoji( + CustomEmojiId(1), + "name", + Domain("example.com"), + InstanceId(1), + URI.create("https://example.com"), + null, + Instant.parse("2020-01-01T00:00:00Z") + ) + + ExposedCustomEmojiRepository().save(customEmoji) + + assertThat(assertTable).row(0).isEqualTo(CustomEmojis.id, customEmoji.id.emojiId) + .isEqualTo(CustomEmojis.name, customEmoji.name).isEqualTo(CustomEmojis.domain, customEmoji.domain.domain) + .isEqualTo(CustomEmojis.instanceId, customEmoji.instanceId.instanceId) + .isEqualTo(CustomEmojis.url, customEmoji.url.toString()) + .isEqualTo(CustomEmojis.category, customEmoji.category).value(CustomEmojis.createdAt.name) + .isEqualTo(Timestamp.from(customEmoji.createdAt)) + } + + @Test + fun save_idが同じレコードが存在したらupdate() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Instance.tableName) { + columns(Instance.columns) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + insertInto(CustomEmojis.tableName) { + columns(CustomEmojis.columns) + values( + 1, + "emoji", + "example.com", + 1, + "https://example.com", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + val customEmoji = CustomEmoji( + CustomEmojiId(1), + "name", + Domain("example.com"), + InstanceId(1), + URI.create("https://example.com"), + null, + Instant.parse("2020-01-01T00:00:00Z") + ) + + ExposedCustomEmojiRepository().save(customEmoji) + + assertThat(assertTable).row(0).isEqualTo(CustomEmojis.id, customEmoji.id.emojiId) + .isEqualTo(CustomEmojis.name, customEmoji.name).isEqualTo(CustomEmojis.domain, customEmoji.domain.domain) + .isEqualTo(CustomEmojis.instanceId, customEmoji.instanceId.instanceId) + .isEqualTo(CustomEmojis.url, customEmoji.url.toString()) + .isEqualTo(CustomEmojis.category, customEmoji.category).value(CustomEmojis.createdAt.name) + .isEqualTo(Timestamp.from(customEmoji.createdAt)) + } + + @Test + fun delete_削除される() = runTest { + + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(CustomEmojis.tableName) { + columns(CustomEmojis.columns) + values( + 1, + "emoji", + "example.com", + 1, + "https://example.com", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + } + }.launch() + + val customEmoji = CustomEmoji( + CustomEmojiId(1), + "name", + Domain("example.com"), + InstanceId(1), + URI.create("https://example.com"), + null, + Instant.parse("2020-01-01T00:00:00Z") + ) + + + change.withSuspend { + ExposedCustomEmojiRepository().delete(customEmoji) + } + + assertThat(change).changeOfDeletionOnTable(CustomEmojis.tableName).rowAtStartPoint().value(CustomEmojis.id.name) + .isEqualTo(customEmoji.id.emojiId) + } + + @Test + fun findById_指定したIdで存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(CustomEmojis.tableName) { + columns(CustomEmojis.columns) + values( + 1, + "emoji", + "example.com", + 1, + "https://example.com", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + } + }.launch() + + val customEmoji = CustomEmoji( + CustomEmojiId(1), + "emoji", + Domain("example.com"), + InstanceId(1), + URI.create("https://example.com"), + null, + Instant.parse("2020-01-01T00:00:00Z") + ) + + val actual = ExposedCustomEmojiRepository().findById(1) + + assertEquals(customEmoji, actual) + assertNotNull(actual) + assertEquals(customEmoji.id, actual.id) + assertEquals(customEmoji.createdAt, actual.createdAt) + assertEquals(customEmoji.url, actual.url) + assertEquals(customEmoji.category, actual.category) + assertEquals(customEmoji.instanceId, actual.instanceId) + assertEquals(customEmoji.domain, actual.domain) + assertEquals(customEmoji.name, actual.name) + } + + @Test + fun findById_指定したIdで存在しないとnull() = runTest { + assertNull(ExposedCustomEmojiRepository().findById(1)) + } + + @Test + fun findByNamesAndDomain_指定した条件全部返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(CustomEmojis.tableName) { + columns(CustomEmojis.columns) + values( + 1, + "emoji", + "example.com", + 1, + "https://example.com/1", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + values( + 2, + "emoji2", + "example.com", + 1, + "https://example.com/2", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + values( + 3, + "emoji3", + "example.com", + 1, + "https://example.com/3", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + } + }.launch() + + val expected = listOf( + CustomEmoji( + CustomEmojiId(1), + "emoji", + Domain("example.com"), + InstanceId(1), + URI.create("https://example.com/1"), + null, + Instant.parse("2020-01-01T00:00:00Z") + ), CustomEmoji( + CustomEmojiId(2), + "emoji2", + Domain("example.com"), + InstanceId(1), + URI.create("https://example.com/2"), + null, + Instant.parse("2020-01-01T00:00:00Z") + ) + ) + + val actual = ExposedCustomEmojiRepository().findByNamesAndDomain(listOf("emoji", "emoji2"), "example.com") + + assertContentEquals(expected, actual) + } + + @Test + fun findByIds_指定された条件全部返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(CustomEmojis.tableName) { + columns(CustomEmojis.columns) + values( + 1, + "emoji", + "example.com", + 1, + "https://example.com/1", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + values( + 2, + "emoji2", + "example.com", + 1, + "https://example.com/2", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + values( + 3, + "emoji3", + "example.com", + 1, + "https://example.com/3", + null, + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + } + }.launch() + + val expected = listOf( + CustomEmoji( + CustomEmojiId(1), + "emoji", + Domain("example.com"), + InstanceId(1), + URI.create("https://example.com/1"), + null, + Instant.parse("2020-01-01T00:00:00Z") + ), CustomEmoji( + CustomEmojiId(3), + "emoji3", + Domain("example.com"), + InstanceId(1), + URI.create("https://example.com/3"), + null, + Instant.parse("2020-01-01T00:00:00Z") + ) + ) + + val actual = ExposedCustomEmojiRepository().findByIds(listOf(1, 3)) + + assertContentEquals(expected, actual) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepositoryTest.kt new file mode 100644 index 00000000..952eb316 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedFilterRepositoryTest.kt @@ -0,0 +1,280 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import dev.usbharu.hideout.core.infrastructure.exposed.FilterQueryMapper +import dev.usbharu.hideout.core.infrastructure.exposed.FilterResultRowMapper +import kotlinx.coroutines.test.runTest +import org.assertj.db.api.Assertions.assertThat +import org.assertj.db.type.Changes +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.InjectMocks +import org.mockito.Spy +import org.mockito.junit.jupiter.MockitoExtension +import utils.* +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@ExtendWith(MockitoExtension::class) +class ExposedFilterRepositoryTest : AbstractRepositoryTest(Filters) { + + @InjectMocks + lateinit var repository: ExposedFilterRepository + + @Spy + val filterQueryMapper: FilterQueryMapper = FilterQueryMapper(FilterResultRowMapper()) + + @Test + fun save_idが同じレコードが存在しないとinsert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(UserDetails.tableName) { + columns(UserDetails.columns) + values(1, 2, "VeeeeeeeeeeeeeeeeryStrongPassword", false, null, null) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val filter = Filter( + FilterId(1), + UserDetailId(1), + FilterName("filter"), + setOf(), + FilterAction.HIDE, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("keyword"), FilterMode.NONE)) + ) + repository.save(filter) + + assertThat(assertTable).row(0).isEqualTo(Filters.id, filter.id.id) + .isEqualTo(Filters.userId, filter.userDetailId.id).isEqualTo(Filters.name, filter.name.name) + .isEqualTo(Filters.filterAction, filter.filterAction.name) + .isEqualTo(Filters.context, filter.filterContext.joinToString(",") { it.name }) + + assertThat(getTable(FilterKeywords.tableName)).row(0) + .isEqualTo(FilterKeywords.id, filter.filterKeywords.first().id.id) + .isEqualTo(FilterKeywords.filterId, filter.id.id) + .isEqualTo(FilterKeywords.keyword, filter.filterKeywords.first().keyword.keyword) + .isEqualTo(FilterKeywords.mode, filter.filterKeywords.first().mode.name) + } + + @Test + fun save_idが同じレコードが存在したらupdate() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(UserDetails.tableName) { + columns(UserDetails.columns) + values(1, 2, "VeeeeeeeeeeeeeeeeryStrongPassword", false, null, null) + } + insertInto(Filters.tableName) { + columns(Filters.columns) + values(1, 1, "name", "", "WARN") + } + insertInto(FilterKeywords.tableName) { + columns(FilterKeywords.columns) + values(1, 1, "aaaaaaaaaaaaaaaaa", "REGEX") + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val filter = Filter( + FilterId(1), + UserDetailId(1), + FilterName("filter"), + setOf(), + FilterAction.HIDE, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("keyword"), FilterMode.NONE)) + ) + repository.save(filter) + + assertThat(assertTable).row(0).isEqualTo(Filters.id, filter.id.id) + .isEqualTo(Filters.userId, filter.userDetailId.id).isEqualTo(Filters.name, filter.name.name) + .isEqualTo(Filters.filterAction, filter.filterAction.name) + .isEqualTo(Filters.context, filter.filterContext.joinToString(",") { it.name }) + + assertThat(getTable(FilterKeywords.tableName)).row(0) + .isEqualTo(FilterKeywords.id, filter.filterKeywords.first().id.id) + .isEqualTo(FilterKeywords.filterId, filter.id.id) + .isEqualTo(FilterKeywords.keyword, filter.filterKeywords.first().keyword.keyword) + .isEqualTo(FilterKeywords.mode, filter.filterKeywords.first().mode.name) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(UserDetails.tableName) { + columns(UserDetails.columns) + values(1, 2, "VeeeeeeeeeeeeeeeeryStrongPassword", false, null, null) + } + insertInto(Filters.tableName) { + columns(Filters.columns) + values(1, 1, "name", "", "WARN") + } + insertInto(FilterKeywords.tableName) { + columns(FilterKeywords.columns) + values(1, 1, "aaaaaaaaaaaaaaaaa", "REGEX") + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val filter = Filter( + FilterId(1), + UserDetailId(1), + FilterName("filter"), + setOf(), + FilterAction.HIDE, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("keyword"), FilterMode.NONE)) + ) + + val changes = Changes(dataSource) + changes.withSuspend { + repository.delete(filter) + } + + assertThat(changes).changeOfDeletionOnTable(Filters.tableName).rowAtStartPoint().value(Filters.id.name) + .isEqualTo(filter.id.id).changeOfDeletionOnTable(FilterKeywords.tableName).rowAtStartPoint() + .value(FilterKeywords.id.name).isEqualTo(filter.filterKeywords.first().id.id) + } + + @Test + fun findByFilterKeywordId_指定された条件で存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + + insertInto(Filters.tableName) { + columns(Filters.columns) + values(1, 1, "name", "PUBLIC", "WARN") + } + insertInto(FilterKeywords.tableName) { + columns(FilterKeywords.columns) + values(1, 1, "keyword", "REGEX") + } + + execute(enableReferenceIntegrityConstraints) + }.launch() + + val expected = Filter( + FilterId(1), + UserDetailId(1), + FilterName("name"), + setOf(FilterContext.PUBLIC), + FilterAction.WARN, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("keyword"), FilterMode.REGEX)) + ) + + val actual = repository.findByFilterKeywordId(FilterKeywordId(1)) + + assertEquals(expected, actual) + } + + private fun assertEquals( + expected: Filter, actual: Filter? + ) { + kotlin.test.assertEquals(expected, actual) + assertNotNull(actual) + assertEquals(expected.id, actual.id) + assertEquals(expected.name, actual.name) + assertContentEquals(expected.filterContext, actual.filterContext.asIterable()) + assertEquals(expected.userDetailId, actual.userDetailId) + assertEquals(expected.filterKeywords.size, actual.filterKeywords.size) + assertContentEquals(expected.filterKeywords, actual.filterKeywords.asIterable()) + } + + + @Test + fun findByFilterKeywordId_指定された条件で存在しないとnull() = runTest { + assertNull(repository.findByFilterKeywordId(FilterKeywordId(1))) + } + + @Test + fun findByFilterId_指定された条件で存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + + insertInto(Filters.tableName) { + columns(Filters.columns) + values(1, 1, "name", "PUBLIC", "WARN") + } + insertInto(FilterKeywords.tableName) { + columns(FilterKeywords.columns) + values(1, 1, "keyword", "REGEX") + } + + execute(enableReferenceIntegrityConstraints) + }.launch() + + val expected = Filter( + FilterId(1), + UserDetailId(1), + FilterName("name"), + setOf(FilterContext.PUBLIC), + FilterAction.WARN, + setOf(FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("keyword"), FilterMode.REGEX)) + ) + + val actual = repository.findByFilterId(FilterId(1)) + + assertEquals(expected, actual) + } + + @Test + fun findByFilterId_指定された条件で存在しないとnull() = runTest { + assertNull(repository.findByFilterId(FilterId(1))) + } + + @Test + fun findByUserDetailId_指定された条件全部返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + + insertInto(Filters.tableName) { + columns(Filters.columns) + values(1, 1, "name", "PUBLIC", "WARN") + values(2, 1, "name2", "PUBLIC", "WARN") + values(3, 1, "name3", "PUBLIC", "HIDE") + } + insertInto(FilterKeywords.tableName) { + columns(FilterKeywords.columns) + values(1, 1, "keyword", "REGEX") + values(2, 2, "keyword2", "REGEX") + values(3, 1, "keyword3", "REGEX") + } + + execute(enableReferenceIntegrityConstraints) + }.launch() + + val expected = listOf( + Filter( + FilterId(1), UserDetailId(1), FilterName("name"), setOf(FilterContext.PUBLIC), FilterAction.WARN, setOf( + FilterKeyword(FilterKeywordId(1), FilterKeywordKeyword("keyword"), FilterMode.REGEX), + FilterKeyword(FilterKeywordId(3), FilterKeywordKeyword("keyword3"), FilterMode.REGEX) + ) + ), Filter( + FilterId(2), + UserDetailId(1), + FilterName("name2"), + setOf(FilterContext.PUBLIC), + FilterAction.WARN, + setOf( + FilterKeyword(FilterKeywordId(2), FilterKeywordKeyword("keyword2"), FilterMode.REGEX) + ) + ), Filter( + FilterId(3), + UserDetailId(1), + FilterName("name3"), + setOf(FilterContext.PUBLIC), + FilterAction.HIDE, + setOf( + ) + ) + ) + + val actual = repository.findByUserDetailId(UserDetailId(1)) + + assertContentEquals(expected, actual) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedInstanceRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedInstanceRepositoryTest.kt new file mode 100644 index 00000000..1b6c47b5 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedInstanceRepositoryTest.kt @@ -0,0 +1,275 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.instance.* +import dev.usbharu.hideout.core.domain.model.instance.Instance +import kotlinx.coroutines.test.runTest +import org.assertj.db.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import utils.AbstractRepositoryTest +import utils.columns +import utils.isEqualTo +import utils.value +import java.net.URI +import java.sql.Timestamp +import java.time.Instant +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import dev.usbharu.hideout.core.infrastructure.exposedrepository.Instance as InstanceTable + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ExposedInstanceRepositoryTest : AbstractRepositoryTest(InstanceTable) { + + + @Test + fun save_idが同じレコードがない場合はinsertされる() = runTest { + ExposedInstanceRepository().save( + Instance( + id = InstanceId(1), + name = InstanceName("test"), + description = InstanceDescription("id"), + url = URI.create("https://www.example.com"), + iconUrl = URI.create("https://www.example.com"), + sharedInbox = null, + software = InstanceSoftware(""), + version = InstanceVersion(""), + isBlocked = false, + isMuted = false, + moderationNote = InstanceModerationNote(""), + createdAt = Instant.parse("2020-01-01T00:00:00Z"), + ) + ) + + val table = assertTable + assertThat(table).row(1).isEqualTo(InstanceTable.id, 1).isEqualTo(InstanceTable.name, "test") + .value(InstanceTable.url).isEqualTo("https://www.example.com") + .value(InstanceTable.iconUrl).isEqualTo("https://www.example.com") + .isEqualTo(InstanceTable.sharedInbox, null) + .isEqualTo(InstanceTable.software, "").isEqualTo(InstanceTable.version, "") + .isEqualTo(InstanceTable.isBlocked, false).isEqualTo(InstanceTable.isMuted, false) + .isEqualTo(InstanceTable.moderationNote, "").value(InstanceTable.createdAt) + .isEqualTo(Timestamp.from(Instant.parse("2020-01-01T00:00:00Z"))) + } + + @Test + fun save_idが同じレコードがある場合はupdateされる() = runTest { + dbSetup(to = dataSource) { + insertInto(InstanceTable.tableName) { + columns( + InstanceTable.columns + ) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + }.launch() + + + ExposedInstanceRepository().save( + Instance( + id = InstanceId(1), + name = InstanceName("test"), + description = InstanceDescription("id"), + url = URI.create("https://www.example.com"), + iconUrl = URI.create("https://www.example.com"), + sharedInbox = null, + software = InstanceSoftware(""), + version = InstanceVersion(""), + isBlocked = false, + isMuted = false, + moderationNote = InstanceModerationNote(""), + createdAt = Instant.parse("2020-01-01T00:00:00Z"), + ) + ) + + val table = assertTable + assertThat(table).row(1).isEqualTo(InstanceTable.id, 1).isEqualTo(InstanceTable.name, "test") + .value(InstanceTable.url).isEqualTo("https://www.example.com") + .value(InstanceTable.iconUrl).isEqualTo("https://www.example.com") + .isEqualTo(InstanceTable.sharedInbox, null) + .isEqualTo(InstanceTable.software, "").isEqualTo(InstanceTable.version, "") + .isEqualTo(InstanceTable.isBlocked, false).isEqualTo(InstanceTable.isMuted, false) + .isEqualTo(InstanceTable.moderationNote, "").value(InstanceTable.createdAt) + .isEqualTo(Timestamp.from(Instant.parse("2020-01-01T00:00:00Z"))) + } + + @Test + fun findById_指定したidで存在したら返す() = runTest { + dbSetup(to = dataSource) { + insertInto(InstanceTable.tableName) { + columns( + InstanceTable.columns + ) + values( + 1, + "test", + "description", + "https://www.example.com", + "https://www.example.com", + null, + "", + "", + false, + false, + "", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + } + }.launch() + + val actual = ExposedInstanceRepository().findById(InstanceId(1)) + val expected = Instance( + id = InstanceId(1), + name = InstanceName("test"), + description = InstanceDescription("description"), + url = URI.create("https://www.example.com"), + iconUrl = URI.create("https://www.example.com"), + sharedInbox = null, + software = InstanceSoftware(""), + version = InstanceVersion(""), + isBlocked = false, + isMuted = false, + moderationNote = InstanceModerationNote(""), + createdAt = Instant.parse("2020-01-01T00:00:00Z"), + ) + + assertEquals(expected, actual) + } + + @Test + fun findById_指定したIDで存在しないとnull() = runTest { + assertNull(ExposedInstanceRepository().findById(InstanceId(1))) + } + + @Test + fun findByUrl_指定したURLで存在したら返す() = runTest { + dbSetup(to = dataSource) { + insertInto(InstanceTable.tableName) { + columns( + InstanceTable.columns + ) + values( + 1, + "test", + "description", + "https://www.example.com", + "https://www.example.com", + null, + "", + "", + false, + false, + "", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + } + }.launch() + + val actual = ExposedInstanceRepository().findByUrl(URI.create("https://www.example.com")) + val expected = Instance( + id = InstanceId(1), + name = InstanceName("test"), + description = InstanceDescription("description"), + url = URI.create("https://www.example.com"), + iconUrl = URI.create("https://www.example.com"), + sharedInbox = null, + software = InstanceSoftware(""), + version = InstanceVersion(""), + isBlocked = false, + isMuted = false, + moderationNote = InstanceModerationNote(""), + createdAt = Instant.parse("2020-01-01T00:00:00Z"), + ) + + assertEquals(expected, actual) + } + + @Test + fun findByUrl_指定したURLで存在しないとnull() = runTest { + assertNull(ExposedInstanceRepository().findByUrl(URI.create("https://www.example.com"))) + } + + @Test + fun delete_削除() = runTest { + dbSetup(to = dataSource) { + insertInto(InstanceTable.tableName) { + columns( + InstanceTable.columns + ) + values( + 1, + "test", + "description", + "https://www.example.com", + "https://www.example.com", + null, + "", + "", + false, + false, + "", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")) + ) + } + }.launch() + + val instance = Instance( + id = InstanceId(1), + name = InstanceName("test"), + description = InstanceDescription("description"), + url = URI.create("https://www.example.com"), + iconUrl = URI.create("https://www.example.com"), + sharedInbox = null, + software = InstanceSoftware(""), + version = InstanceVersion(""), + isBlocked = false, + isMuted = false, + moderationNote = InstanceModerationNote(""), + createdAt = Instant.parse("2020-01-01T00:00:00Z"), + ) + + change.setStartPointNow() + + ExposedInstanceRepository().delete(instance) + + change.setEndPointNow() + + assertThat(change) + .hasNumberOfChanges(1) + .changeOfDeletionOnTable(InstanceTable.tableName) + .rowAtStartPoint() + .value(InstanceTable.id.name).isEqualTo(1) + + } + + companion object { + fun assertEquals(expected: Instance, actual: Instance?) { + assertNotNull(actual) + kotlin.test.assertEquals(expected, actual) + assertEquals(expected.name, actual.name) + assertEquals(expected.description, actual.description) + assertEquals(expected.url, actual.url) + assertEquals(expected.iconUrl, actual.iconUrl) + assertEquals(expected.sharedInbox, actual.sharedInbox) + assertEquals(expected.software, actual.software) + assertEquals(expected.version, actual.version) + assertEquals(expected.isBlocked, actual.isBlocked) + assertEquals(expected.moderationNote, actual.moderationNote) + assertEquals(expected.createdAt, actual.createdAt) + } + } +} + diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedMediaRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedMediaRepositoryTest.kt new file mode 100644 index 00000000..a1f7f425 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedMediaRepositoryTest.kt @@ -0,0 +1,363 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.media.* +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.db.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import utils.* +import java.net.URI +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import dev.usbharu.hideout.core.domain.model.media.Media as EntityMedia + + +class ExposedMediaRepositoryTest : AbstractRepositoryTest(Media) { + @Test + fun save_idが同じレコードが存在しないとinsert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + val media = EntityMedia( + id = MediaId(1), + name = MediaName("name"), + url = URI.create("https://www.example.com"), + remoteUrl = null, + thumbnailUrl = null, + type = FileType.Audio, + mimeType = MimeType("audio", "mp3", FileType.Audio), + blurHash = null, + description = null, + actorId = ActorId(1) + ) + ExposedMediaRepository().save(media) + + assertThat(assertTable) + .row(0) + .isEqualTo(Media.id, 1) + .isEqualTo(Media.name, "name") + .value(Media.url).isEqualTo("https://www.example.com") + .isEqualTo(Media.remoteUrl, null) + .isEqualTo(Media.thumbnailUrl, null) + .isEqualTo(Media.type, "Audio") + .isEqualTo(Media.mimeType, "audio/mp3") + .isEqualTo(Media.blurhash, null) + .isEqualTo(Media.description, null) + .isEqualTo(Media.actorId, 1) + } + + @Test + fun save_idが同じレコードが存在したらupdate() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto("public.media") { + columns( + "id", + "name", + "url", + "remote_url", + "thumbnail_url", + "type", + "blurhash", + "mime_type", + "description", + "actor_id" + ) + values( + 1, + "pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg", + "http://localhost:8081/files/1833054358862827520.jpeg", + null, + "http://localhost:8081/files/thumbnail-1833054358862827520.jpeg", + "Image", + "U\$JuAZWBxut7~qoLoft6j]t7Rjj[RjayWBay", + "image/jpeg", + null, + 1 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val media = EntityMedia( + id = MediaId(1), + name = MediaName("name"), + url = URI.create("https://www.example.com"), + remoteUrl = null, + thumbnailUrl = null, + type = FileType.Audio, + mimeType = MimeType("audio", "mp3", FileType.Audio), + blurHash = null, + description = null, + actorId = ActorId(1) + ) + ExposedMediaRepository().save(media) + + assertThat(assertTable) + .row(0) + .isEqualTo(Media.id, 1) + .isEqualTo(Media.name, "name") + .value(Media.url).isEqualTo("https://www.example.com") + .isEqualTo(Media.remoteUrl, null) + .isEqualTo(Media.thumbnailUrl, null) + .isEqualTo(Media.type, "Audio") + .isEqualTo(Media.mimeType, "audio/mp3") + .isEqualTo(Media.blurhash, null) + .isEqualTo(Media.description, null) + .isEqualTo(Media.actorId, 1) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.media") { + columns( + "id", + "name", + "url", + "remote_url", + "thumbnail_url", + "type", + "blurhash", + "mime_type", + "description", + "actor_id" + ) + values( + 1, + "pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg", + "http://localhost:8081/files/1833054358862827520.jpeg", + null, + "http://localhost:8081/files/thumbnail-1833054358862827520.jpeg", + "Image", + "U\$JuAZWBxut7~qoLoft6j]t7Rjj[RjayWBay", + "image/jpeg", + null, + 1 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + val media = EntityMedia( + id = MediaId(1), + name = MediaName("name"), + url = URI.create("https://www.example.com"), + remoteUrl = null, + thumbnailUrl = null, + type = FileType.Audio, + mimeType = MimeType("audio", "mp3", FileType.Audio), + blurHash = null, + description = null, + actorId = ActorId(1) + ) + + change.withSuspend { + ExposedMediaRepository().delete(media) + } + + assertThat(change) + .changeOfDeletionOnTable(Media.tableName) + .rowAtStartPoint() + .value(Media.id.name) + .isEqualTo(1) + } + + @Test + fun findById_指定されたIdで存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.media") { + columns( + "id", + "name", + "url", + "remote_url", + "thumbnail_url", + "type", + "blurhash", + "mime_type", + "description", + "actor_id" + ) + values( + 1, + "pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg", + "http://localhost:8081/files/1833054358862827520.jpeg", + "http://localhost:8081/files/183305453584862827520.jpeg", + "http://localhost:8081/files/thumbnail-1833054358862827520.jpeg", + "Image", + "U\$JuAZWBxut7~qoLoft6j]t7Rjj[RjayWBay", + "image/jpeg", + null, + 1 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val expect = EntityMedia( + id = MediaId(1), + name = MediaName("pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg"), + url = URI.create("http://localhost:8081/files/1833054358862827520.jpeg"), + remoteUrl = URI.create("http://localhost:8081/files/183305453584862827520.jpeg"), + thumbnailUrl = URI.create("http://localhost:8081/files/thumbnail-1833054358862827520.jpeg"), + type = FileType.Image, + mimeType = MimeType("image", "jpeg", FileType.Image), + blurHash = MediaBlurHash("U\$JuAZWBxut7~qoLoft6j]t7Rjj[RjayWBay"), + description = null, + actorId = ActorId(1) + ) + + val actual = ExposedMediaRepository().findById(MediaId(1)) + + assertNotNull(actual) + assertEquals(expect, actual) + assertEquals(expect.id, actual.id) + assertEquals(expect.name, actual.name) + assertEquals(expect.url, actual.url) + assertEquals(expect.remoteUrl, actual.remoteUrl) + assertEquals(expect.thumbnailUrl, actual.thumbnailUrl) + assertEquals(expect.type, actual.type) + assertEquals(expect.mimeType, actual.mimeType) + assertEquals(expect.blurHash, actual.blurHash) + assertEquals(expect.description, actual.description) + assertEquals(expect.actorId, actual.actorId) + } + + @Test + fun findById_指定されたIdで存在しないとnull() = runTest { + assertNull(ExposedMediaRepository().findById(MediaId(1))) + } + + @Test + fun findByIdIn_指定されたIdすべて返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.media") { + columns( + "id", + "name", + "url", + "remote_url", + "thumbnail_url", + "type", + "blurhash", + "mime_type", + "description", + "actor_id" + ) + values( + 1, + "pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg", + "http://localhost:8081/files/1833054358862827520.jpeg", + null, + null, + "Image", + "U\$JuAZWBxut7~qoLoft6j]t7Rjj[RjayWBay", + "image/jpeg", + "", + 1 + ) + values( + 3, + "pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg", + "http://localhost:8081/files/18330354358862827520.jpeg", + null, + "http://localhost:8081/files/thumbn3ail-1833054358862827520.jpeg", + "Image", + null, + "image/jpeg", + null, + 1 + ) + values( + 2, + "pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg", + "http://localhost:8081/files/18330545358862827520.jpeg", + "http://localhost:8081/files/183305453584862827520.jpeg", + null, + "Image", + "U\$JuAZWBxut7~qoLoft6j]t7Rjj[RjayWBay", + "image/jpeg", + null, + 1 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val actual = ExposedMediaRepository().findByIdIn(listOf(MediaId(1), MediaId(3))) + + assertThat(actual) + .hasSize(2) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepositoryTest.kt new file mode 100644 index 00000000..569432cd --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedPostRepositoryTest.kt @@ -0,0 +1,1200 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId +import dev.usbharu.hideout.core.domain.model.instance.InstanceId +import dev.usbharu.hideout.core.domain.model.media.MediaId +import dev.usbharu.hideout.core.domain.model.post.* +import dev.usbharu.hideout.core.domain.model.support.page.Page +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import dev.usbharu.hideout.core.infrastructure.exposed.PostQueryMapper +import dev.usbharu.hideout.core.infrastructure.exposed.PostResultRowMapper +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.db.api.Assertions.assertThat +import org.assertj.db.type.Changes +import org.jetbrains.exposed.sql.transactions.TransactionManager +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.any +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import utils.* +import java.net.URI +import java.sql.Timestamp +import java.time.Instant +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@ExtendWith(MockitoExtension::class) +class ExposedPostRepositoryTest : AbstractRepositoryTest(Posts) { + + @InjectMocks + lateinit var repository: ExposedPostRepository + + @Mock + lateinit var domainEventPublisher: DomainEventPublisher + + @Spy + val postQueryMapper = PostQueryMapper(PostResultRowMapper()) + + @Test + fun save_idが同じレコードがない場合はinsert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + values( + 2, + "a", + "test-hideout-dev.usbharu.dev", + "a", + "", + "https://test-hideout-dev.usbharu.dev/users/a/inbox", + "https://test-hideout-dev.usbharu.dev/users/a/outbox", + "https://test-hideout-dev.usbharu.dev/users/a", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/a#main-key", + "https://test-hideout-dev.usbharu.dev/users/a/following", + "https://test-hideout-dev.usbharu.dev/users/a/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(Instance.tableName) { + columns(Instance.columns) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + insertInto("public.media") { + columns(Media.columns) + values( + 3, + "pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg", + "http://localhost:8081/files/18330354358862827520.jpeg", + null, + "http://localhost:8081/files/thumbn3ail-1833054358862827520.jpeg", + "Image", + null, + "image/jpeg", + null, + 1 + ) + values( + 2, + "pnc__picked_media_256f8e6d-68cd-4a76-bb38-57e35f6ca8c6.jpg", + "http://localhost:8081/files/18330545358862827520.jpeg", + "http://localhost:8081/files/183305453584862827520.jpeg", + null, + "Image", + "U\$JuAZWBxut7~qoLoft6j]t7Rjj[RjayWBay", + "image/jpeg", + null, + 1 + ) + } + insertInto(CustomEmojis.tableName) { + columns(CustomEmojis.columns) + values( + 4, + "emoji", + "example.com", + 1, + "https://example.com", + null, + "2024-09-09 17:12:03.941339" + ) + values( + 5, + "emoji2", + "example.com", + 1, + "https://example.com/2", + "test", + "2024-09-09 17:12:03.941339" + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val post = TestPostFactory.create( + id = 1, + createdAt = Instant.parse("2021-01-01T00:00:00Z"), + mediaIds = listOf(2, 3), + emojiIds = listOf(4, 5), + visibility = Visibility.DIRECT, + visibleActors = listOf(1, 2) + ) + repository.save(post) + + assertThat(assertTable) + .row(0) + .isEqualTo(Posts.id, post.id.id) + .isEqualTo(Posts.actorId, post.actorId.id) + .isEqualTo(Posts.instanceId, post.instanceId.instanceId) + .isEqualTo(Posts.overview, post.overview?.overview) + .isEqualTo(Posts.content, post.content.content) + .isEqualTo(Posts.text, post.text) + .value(Posts.createdAt).isEqualTo(Timestamp.from(post.createdAt)) + .isEqualTo(Posts.visibility, post.visibility.name) + .value(Posts.url).isEqualTo(post.url.toString()) + .value(Posts.repostId).isEqualTo(post.repostId?.id) + .value(Posts.replyId).isEqualTo(post.replyId?.id) + .isEqualTo(Posts.sensitive, post.sensitive) + .value(Posts.apId).isEqualTo(post.apId.toString()) + .isEqualTo(Posts.deleted, post.deleted) + .isEqualTo(Posts.hide, post.hide) + .value(Posts.moveTo).isEqualTo(post.moveTo?.id) + + assertThat(getTable(PostsMedia.tableName)) + .row(0) + .value(PostsMedia.postId).isEqualTo(post.id.id) + .value(PostsMedia.mediaId).isEqualTo(2) + .row(1) + .value(PostsMedia.postId).isEqualTo(post.id.id) + .value(PostsMedia.mediaId).isEqualTo(3) + + assertThat(getTable(PostsEmojis.tableName)) + .row(0) + .value(PostsEmojis.postId).isEqualTo(post.id.id) + .value(PostsEmojis.emojiId).isEqualTo(4) + .row(1) + .value(PostsEmojis.postId).isEqualTo(post.id.id) + .value(PostsEmojis.emojiId).isEqualTo(5) + + assertThat(getTable(PostsVisibleActors.tableName)) + .row(0) + .value(PostsVisibleActors.postId).isEqualTo(post.id.id) + .value(PostsVisibleActors.actorId).isEqualTo(1) + .row(1) + .value(PostsVisibleActors.postId).isEqualTo(post.id.id) + .value(PostsVisibleActors.actorId).isEqualTo(2) + } + + @Test + fun save_idが同じレコードがある場合はupdate() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Instance.tableName) { + columns(Instance.columns) + values( + 1, + "system", + "", + "https://example.com", + "", + null, + "", + "", + false, + false, + "", + "2024-09-10 16:59:50.160202" + ) + } + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1832779978794602496, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + } + }.launch() + + val post = TestPostFactory.create( + id = 1, + instanceId = 1, + overview = "aaaaaaaa", + createdAt = Instant.parse("2021-01-01T00:00:00Z"), + replyId = 2, + repostId = 2, + moveTo = 2 + ) + + repository.save(post) + + assertThat(assertTable) + .row(0) + .isEqualTo(Posts.id, post.id.id) + .isEqualTo(Posts.actorId, post.actorId.id) + .isEqualTo(Posts.instanceId, post.instanceId.instanceId) + .isEqualTo(Posts.overview, post.overview?.overview) + .isEqualTo(Posts.content, post.content.content) + .isEqualTo(Posts.text, post.text) + .value(Posts.createdAt).isEqualTo(Timestamp.from(post.createdAt)) + .isEqualTo(Posts.visibility, post.visibility.name) + .value(Posts.url).isEqualTo(post.url.toString()) + .value(Posts.repostId).isEqualTo(post.repostId?.id) + .value(Posts.replyId).isEqualTo(post.replyId?.id) + .isEqualTo(Posts.sensitive, post.sensitive) + .value(Posts.apId).isEqualTo(post.apId.toString()) + .isEqualTo(Posts.deleted, post.deleted) + .isEqualTo(Posts.hide, post.hide) + .value(Posts.moveTo).isEqualTo(post.moveTo?.id) + } + + @Test + fun findById_指定したIdがある場合は返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1832779978794602496, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + } + insertInto(PostsMedia.tableName) { + columns(PostsMedia.columns) + values(1, 2) + } + insertInto(PostsEmojis.tableName) { + columns(PostsEmojis.columns) + values(1, 3) + } + insertInto(PostsVisibleActors.tableName) { + columns(PostsVisibleActors.columns) + values(1, 4) + } + }.launch() + + val actual = repository.findById(PostId(1)) + + val expect = Post( + id = PostId(1), + actorId = ActorId(1832779978794602496), + instanceId = InstanceId(1832779642545639424), + overview = PostOverview(""), + content = PostContent("test", "

test

", listOf(CustomEmojiId(3))), + createdAt = Instant.parse("2020-01-01T00:00:00Z"), + visibility = Visibility.PUBLIC, + url = URI.create("http://localhost:8081/users/a/posts/1832779994749734912"), + repostId = null, + replyId = null, + sensitive = false, + apId = URI.create("http://localhost:8081/users/a/posts/1832779994749734912"), + deleted = false, + mediaIds = listOf(MediaId(2)), + visibleActors = setOf(ActorId(4)), + hide = false, moveTo = null + ) + + assertNotNull(actual) + assertEquals(expect, actual) + assertEquals(expect.id, actual.id) + assertEquals(expect.actorId, actual.actorId) + assertEquals(expect.instanceId, actual.instanceId) + assertEquals(expect.overview, actual.overview) + assertEquals(expect.content, actual.content) + assertEquals(expect.createdAt, actual.createdAt) + assertEquals(expect.visibility, actual.visibility) + assertEquals(expect.url, actual.url) + assertEquals(expect.repostId, actual.repostId) + assertEquals(expect.replyId, actual.replyId) + assertEquals(expect.sensitive, actual.sensitive) + assertEquals(expect.apId, actual.apId) + assertEquals(expect.deleted, actual.deleted) + assertEquals(expect.mediaIds, actual.mediaIds) + assertEquals(expect.visibleActors, actual.visibleActors) + assertEquals(expect.hide, actual.hide) + + } + + @Test + fun findById_指定したIdで存在しないとnull() = runTest { + assertNull(repository.findById(PostId(1))) + } + + @Test + fun findAllById_指定されたIdすべて返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1832779978794602496, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 2, + 1832779978794602496, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/18327739994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/18327793994749734912", + false, + false, + null + ) + values( + 3, + 1832779978794602496, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + } + }.launch() + + val findAllById = repository.findAllById(listOf(PostId(1), PostId(3))) + + assertThat(findAllById) + .hasSize(2) + } + + @Test + fun findByActorId_指定されたActorIdすべて返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 2, + 2, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/18327739994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/18327793994749734912", + false, + false, + null + ) + values( + 3, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + } + }.launch() + + val findAllById = repository.findByActorId(ActorId(1)) + + assertThat(findAllById) + .hasSize(2) + } + + @Test + fun findByActorId_page_max() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 2, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-02T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/18327739994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/18327793994749734912", + false, + false, + null + ) + values( + 3, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-03T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + } + }.launch() + + val findAllById = repository.findByActorId(ActorId(1), Page.of(maxId = 3)) + + assertThat(findAllById) + .hasSize(2) + + assertEquals(2, findAllById[0].id.id) + assertEquals(1, findAllById[1].id.id) + } + + @Test + fun findByActorId_page_min() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 2, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-02T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/18327739994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/18327793994749734912", + false, + false, + null + ) + values( + 3, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-03T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + } + }.launch() + + val findAllById = repository.findByActorId(ActorId(1), Page.of(minId = 1)) + + assertThat(findAllById) + .hasSize(2) + + assertEquals(3, findAllById[0].id.id) + assertEquals(2, findAllById[1].id.id) + } + + @Test + fun findByActorId_page_since() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 2, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-02T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/18327739994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/18327793994749734912", + false, + false, + null + ) + values( + 3, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-03T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + } + }.launch() + + val findAllById = repository.findByActorId(ActorId(1), Page.of(sinceId = 1)) + + assertThat(findAllById) + .hasSize(2) + + assertEquals(3, findAllById[0].id.id) + assertEquals(2, findAllById[1].id.id) + } + + @Test + fun findByActorId_page_limit() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 2, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-02T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/18327739994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/18327793994749734912", + false, + false, + null + ) + values( + 3, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-03T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + } + }.launch() + + val findAllById = repository.findByActorId(ActorId(1), Page.of(limit = 1)) + + assertThat(findAllById) + .hasSize(1) + + assertEquals(3, findAllById[0].id.id) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1832779978794602496, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + } + insertInto(PostsMedia.tableName) { + columns(PostsMedia.columns) + values(1, 2) + } + insertInto(PostsEmojis.tableName) { + columns(PostsEmojis.columns) + values(1, 3) + } + insertInto(PostsVisibleActors.tableName) { + columns(PostsVisibleActors.columns) + values(1, 4) + } + }.launch() + + val post = TestPostFactory.create(1) + + val changes = Changes(dataSource) + changes.withSuspend { + repository.delete(post) + } + + assertThat(changes) + .changeOfDeletionOnTable(Posts.tableName) + .rowAtStartPoint() + .value(Posts.id.name).isEqualTo(1) + .changeOfDeletionOnTable(PostsMedia.tableName) + .rowAtStartPoint() + .value(PostsMedia.mediaId.name).isEqualTo(2) + .changeOfDeletionOnTable(PostsEmojis.tableName) + .rowAtStartPoint() + .value(PostsEmojis.emojiId.name).isEqualTo(3) + .changeOfDeletionOnTable(PostsVisibleActors.tableName) + .rowAtStartPoint() + .value(PostsVisibleActors.actorId.name).isEqualTo(4) + } + + @Test + fun findByActorIdAndVisiblilityInList_指定されたActorIdかつVisibilityのPostすべて返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 2, + 2, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-02T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/18327739994749734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/18327793994749734912", + false, + false, + null + ) + values( + 3, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-03T00:00:00Z")), + "UNLISTED", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + values( + 4, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-04T00:00:00Z")), + "FOLLOWERS", + "http://localhost:8081/users/a/posts/1832773999474947343912", + null, + null, + false, + "http://localhost:8081/users/a/posts/1832779399474937349312", + false, + false, + 2 + ) + } + }.launch() + + val findAllById = + repository.findByActorIdAndVisibilityInList(ActorId(1), listOf(Visibility.PUBLIC, Visibility.FOLLOWERS)) + + assertThat(findAllById) + .hasSize(2) + + assertEquals(4, findAllById[0].id.id) + assertEquals(1, findAllById[1].id.id) + } + + @Test + fun saveAll_idがある場合はinsertなければupdateされる() = runTest { + + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 3, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-03T00:00:00Z")), + "UNLISTED", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + } + insertInto(PostsMedia.tableName) { + columns(PostsMedia.columns) + values(1, 1) + } + insertInto(PostsEmojis.tableName) { + columns(PostsEmojis.columns) + values(1, 2) + } + insertInto(PostsVisibleActors.tableName) { + columns(PostsVisibleActors.columns) + values(1, 3) + } + }.launch() + + val pl = listOf( + TestPostFactory.create(1, createdAt = Instant.parse("2020-01-04T00:00:00Z"), mediaIds = listOf(5)), + TestPostFactory.create(2, createdAt = Instant.parse("2020-01-05T00:00:00Z"), emojiIds = listOf(6)), + TestPostFactory.create( + 3, + createdAt = Instant.parse("2020-01-06T00:00:00Z"), + visibleActors = listOf(7), + overview = "", + replyId = 1, + repostId = 1, + moveTo = 1 + ) + ) + + repository.saveAll(pl) + + assertThat(assertTable) + .row(0) + .isEqualTo(Posts.id, 1) + .value(Posts.createdAt).isEqualTo(Timestamp.from(pl[0].createdAt)) + .row(1) + .isEqualTo(Posts.id, 2) + .value(Posts.createdAt).isEqualTo(Timestamp.from(pl[1].createdAt)) + .row(2) + .isEqualTo(Posts.id, 3) + .value(Posts.createdAt).isEqualTo(Timestamp.from(pl[2].createdAt)) + + assertThat(getTable(PostsMedia.tableName)) + .row(0) + .isEqualTo(PostsMedia.mediaId, 5) + + assertThat(getTable(PostsEmojis.tableName)) + .row(0) + .isEqualTo(PostsEmojis.emojiId, 6) + + assertThat(getTable(PostsVisibleActors.tableName)) + .row(0) + .isEqualTo(PostsVisibleActors.actorId, 7) + } + + @Test + fun save_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + }.launch() + + val post = TestPostFactory.create() + post.checkUpdate() + repository.save(post) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } + + @Test + fun saveAll_ドメインイベントがすべてパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + values( + 3, + 1, + 1832779642545639424, + "", + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-03T00:00:00Z")), + "UNLISTED", + "http://localhost:8081/users/a/posts/183277399947494734912", + null, + null, + false, + "http://localhost:8081/users/a/posts/183277939947493734912", + false, + false, + 2 + ) + } + insertInto(PostsMedia.tableName) { + columns(PostsMedia.columns) + values(1, 1) + } + insertInto(PostsEmojis.tableName) { + columns(PostsEmojis.columns) + values(1, 2) + } + insertInto(PostsVisibleActors.tableName) { + columns(PostsVisibleActors.columns) + values(1, 3) + } + }.launch() + + val pl = listOf( + TestPostFactory.create(1, createdAt = Instant.parse("2020-01-04T00:00:00Z"), mediaIds = listOf(5)), + TestPostFactory.create(2, createdAt = Instant.parse("2020-01-05T00:00:00Z"), emojiIds = listOf(6)), + TestPostFactory.create( + 3, + createdAt = Instant.parse("2020-01-06T00:00:00Z"), + visibleActors = listOf(7), + overview = "", + replyId = 1, + repostId = 1, + moveTo = 1 + ) + ).map { it.checkUpdate();it } + + repository.saveAll(pl) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(3)).publishEvent(any()) + } + + @Test + fun delete_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.posts") { + columns(Posts.columns) + values( + 1, + 1, + 1832779642545639424, + null, + "

test

", + "test", + Timestamp.from(Instant.parse("2020-01-01T00:00:00Z")), + "PUBLIC", + "http://localhost:8081/users/a/posts/1832779994749734912", + 2, + 2, + false, + "http://localhost:8081/users/a/posts/1832779994749734912", + false, + false, + null + ) + + } + }.launch() + + val post = TestPostFactory.create(1) + post.checkUpdate() + repository.delete(post) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedReactionRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedReactionRepositoryTest.kt new file mode 100644 index 00000000..40dd62ed --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedReactionRepositoryTest.kt @@ -0,0 +1,440 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.emoji.CustomEmojiId +import dev.usbharu.hideout.core.domain.model.emoji.UnicodeEmoji +import dev.usbharu.hideout.core.domain.model.post.PostId +import dev.usbharu.hideout.core.domain.model.reaction.Reaction +import dev.usbharu.hideout.core.domain.model.reaction.ReactionId +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.db.api.Assertions.assertThat +import org.jetbrains.exposed.sql.transactions.TransactionManager +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.times +import org.mockito.kotlin.verify +import utils.* +import java.sql.Timestamp +import java.time.Instant +import kotlin.test.* + +@ExtendWith(MockitoExtension::class) +class ExposedReactionRepositoryTest : AbstractRepositoryTest(Reactions) { + + @InjectMocks + lateinit var repository: ExposedReactionRepository + + @Mock + lateinit var domainEventPublisher: DomainEventPublisher + + + @Test + fun save_idが同じレコードがなければinsert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Posts.tableName) { + columns(Posts.columns) + values( + 1, + 1, + 1, + null, + "test", + "test", + Timestamp.from(Instant.parse("2021-01-01T00:00:00Z")), + "PUBLIC", + "https://example.com", + null, + null, + false, + "https://example.com", + false, + false, + null + ) + } + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(CustomEmojis.tableName) { + columns(CustomEmojis.columns) + values(1, "emoji", "example.com", 1, "https://example.com", null, Timestamp.from(Instant.now())) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val create = Reaction.create( + id = ReactionId(1), + postId = PostId(1), + actorId = ActorId(1), + customEmojiId = CustomEmojiId(1), + unicodeEmoji = UnicodeEmoji("❤"), + createdAt = Instant.parse("2021-01-01T00:00:00Z") + ) + + repository.save(create) + + assertThat(assertTable) + .row(0) + .isEqualTo(Reactions.id, create.id.value) + .isEqualTo(Reactions.postId, create.postId.id) + .isEqualTo(Reactions.actorId, create.actorId.id) + .isEqualTo(Reactions.customEmojiId, create.customEmojiId?.emojiId) + .isEqualTo(Reactions.unicodeEmoji, create.unicodeEmoji.name) + .value(Reactions.createdAt).isEqualTo(Timestamp.from(create.createdAt)) + } + + @Test + fun save_idが同じレコードが存在したらupdate() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Posts.tableName) { + columns(Posts.columns) + values( + 1, + 1, + 1, + null, + "test", + "test", + Timestamp.from(Instant.parse("2021-01-01T00:00:00Z")), + "PUBLIC", + "https://example.com", + null, + null, + false, + "https://example.com", + false, + false, + null + ) + } + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(CustomEmojis.tableName) { + columns(CustomEmojis.columns) + values(1, "emoji", "example.com", 1, "https://example.com", null, Timestamp.from(Instant.now())) + } + insertInto(Reactions.tableName) { + columns(Reactions.columns) + values(1, 1, 1, 2, "☠️", Timestamp.from(Instant.now())) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val create = Reaction.create( + id = ReactionId(1), + postId = PostId(1), + actorId = ActorId(1), + customEmojiId = CustomEmojiId(1), + unicodeEmoji = UnicodeEmoji("❤"), + createdAt = Instant.parse("2021-01-01T00:00:00Z") + ) + + repository.save(create) + + assertThat(assertTable) + .row(0) + .isEqualTo(Reactions.id, create.id.value) + .isEqualTo(Reactions.postId, create.postId.id) + .isEqualTo(Reactions.actorId, create.actorId.id) + .isEqualTo(Reactions.customEmojiId, create.customEmojiId?.emojiId) + .isEqualTo(Reactions.unicodeEmoji, create.unicodeEmoji.name) + .value(Reactions.createdAt).isEqualTo(Timestamp.from(create.createdAt)) + } + + @Test + fun findById_指定されたIdが存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Reactions.tableName) { + columns(Reactions.columns) + values(1, 1, 1, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + } + }.launch() + + val expected = Reaction.create( + id = ReactionId(1), + postId = PostId(1), + actorId = ActorId(1), + customEmojiId = CustomEmojiId(1), + unicodeEmoji = UnicodeEmoji("❤"), + createdAt = Instant.parse("2021-01-01T00:00:00Z") + ) + + val actual = repository.findById(ReactionId(1)) + + assertEquals(expected, actual) + + } + + private fun assertEquals( + expected: Reaction, + actual: Reaction? + ) { + kotlin.test.assertEquals(expected, actual) + assertNotNull(actual) + assertEquals(expected.id, actual.id) + assertEquals(expected.postId, actual.postId) + assertEquals(expected.actorId, actual.actorId) + assertEquals(expected.customEmojiId, actual.customEmojiId) + assertEquals(expected.unicodeEmoji, actual.unicodeEmoji) + assertEquals(expected.createdAt, actual.createdAt) + } + + @Test + fun findById_指定されたIdがなければnull() = runTest { + assertNull(repository.findById(ReactionId(1))) + } + + @Test + fun findByPostId_指定されたId全部返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Reactions.tableName) { + columns(Reactions.columns) + values(1, 1, 1, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(2, 3, 2, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(3, 1, 3, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + } + }.launch() + + val actual = repository.findByPostId(PostId(1)) + + assertThat(actual) + .hasSize(2) + } + + @Test + fun existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji_指定された条件で存在したらtrue() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Reactions.tableName) { + columns(Reactions.columns) + values(1, 1, 1, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(2, 3, 2, null, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(3, 1, 3, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + } + }.launch() + + val actual1 = repository.existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji( + PostId(1), + ActorId(1), CustomEmojiId + (1), "❤" + ) + val actual2 = repository.existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji( + PostId(3), + ActorId(2), null, "❤" + ) + + assertTrue(actual1) + assertTrue(actual2) + } + + @Test + fun existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji_指定された条件で存在しないとfalse() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Reactions.tableName) { + columns(Reactions.columns) + values(1, 1, 1, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(2, 3, 2, null, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(3, 1, 3, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + } + }.launch() + + val actual1 = repository.existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji( + PostId(3), + ActorId(1), CustomEmojiId + (1), "❤" + ) + val actual2 = repository.existsByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji( + PostId(2), + ActorId(2), null, "❤" + ) + + assertFalse(actual1) + assertFalse(actual2) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Reactions.tableName) { + columns(Reactions.columns) + values(1, 1, 1, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(2, 3, 2, null, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(3, 1, 3, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + } + }.launch() + + val reaction = Reaction( + ReactionId(1), + PostId(1), + ActorId(1), + CustomEmojiId(1), + UnicodeEmoji("❤"), + Instant.parse("2021-01-01T00:00:00Z") + ) + + repository.delete(reaction) + } + + @Test + fun findByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji_指定された条件で存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Reactions.tableName) { + columns(Reactions.columns) + values(1, 1, 1, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(2, 3, 2, null, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(3, 1, 3, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + } + }.launch() + + val expected = Reaction( + ReactionId(1), + PostId(1), + ActorId(1), + CustomEmojiId(1), + UnicodeEmoji("❤"), + Instant.parse("2021-01-01T00:00:00Z") + ) + + val actual = + repository.findByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji( + PostId(1), + ActorId(1), CustomEmojiId + (1), "❤" + ) + + assertEquals(expected, actual) + } + + @Test + fun findByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji_指定された条件で存在しないとnull() = runTest { + assertNull( + repository.findByPostIdAndActorIdAndCustomEmojiIdOrUnicodeEmoji( + PostId(1), + ActorId(1), CustomEmojiId + (1), "❤" + ) + ) + } + + @Test + fun save_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + }.launch() + repository.save( + Reaction.create( + ReactionId(1), + PostId(1), ActorId + (1), CustomEmojiId + (1), UnicodeEmoji("❤"), Instant.now + () + ) + ) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } + + @Test + fun delete_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Reactions.tableName) { + columns(Reactions.columns) + values(1, 1, 1, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(2, 3, 2, null, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + values(3, 1, 3, 1, "❤", Timestamp.from(Instant.parse("2021-01-01T00:00:00Z"))) + } + }.launch() + + val reaction = Reaction( + ReactionId(1), + PostId(1), ActorId + (1), CustomEmojiId + (1), UnicodeEmoji("❤"), Instant.now + () + ) + reaction.delete() + repository.delete( + reaction + ) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepositoryTest.kt new file mode 100644 index 00000000..8220d6a2 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedRelationshipRepositoryTest.kt @@ -0,0 +1,503 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.relationship.Relationship +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import kotlinx.coroutines.test.runTest +import org.assertj.db.api.Assertions.assertThat +import org.jetbrains.exposed.sql.transactions.TransactionManager +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.times +import org.mockito.kotlin.verify +import utils.* +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@ExtendWith(MockitoExtension::class) +class ExposedRelationshipRepositoryTest : AbstractRepositoryTest(Relationships) { + + @InjectMocks + lateinit var repository: ExposedRelationshipRepository + + @Mock + lateinit var domainEventPublisher: DomainEventPublisher + + @Test + fun save_idが同じレコードがなければinsert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Actors.tableName) { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + values( + 2, + "a", + "test-hideout-dev.usbharu.dev", + "a", + "", + "https://test-hideout-dev.usbharu.dev/users/a/inbox", + "https://test-hideout-dev.usbharu.dev/users/a/outbox", + "https://test-hideout-dev.usbharu.dev/users/a", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/a#main-key", + "https://test-hideout-dev.usbharu.dev/users/a/following", + "https://test-hideout-dev.usbharu.dev/users/a/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + }.launch() + + val relationship = Relationship.default( + ActorId(1), ActorId(2) + ) + + repository.save(relationship) + + assertThat(assertTable) + .row(0) + .isEqualTo(Relationships.actorId, relationship.actorId.id) + .isEqualTo(Relationships.targetActorId, relationship.targetActorId.id) + .isEqualTo(Relationships.following, relationship.following) + .isEqualTo(Relationships.blocking, relationship.blocking) + .isEqualTo(Relationships.muting, relationship.muting) + .isEqualTo(Relationships.followRequesting, relationship.followRequesting) + .isEqualTo(Relationships.mutingFollowRequest, relationship.mutingFollowRequest) + } + + @Test + fun save_idが同じレコードがあればupdate() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Actors.tableName) { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + values( + 2, + "a", + "test-hideout-dev.usbharu.dev", + "a", + "", + "https://test-hideout-dev.usbharu.dev/users/a/inbox", + "https://test-hideout-dev.usbharu.dev/users/a/outbox", + "https://test-hideout-dev.usbharu.dev/users/a", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/a#main-key", + "https://test-hideout-dev.usbharu.dev/users/a/following", + "https://test-hideout-dev.usbharu.dev/users/a/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(Relationships.tableName) { + columns(Relationships.columns) + values(1, 2, true, false, false, false, false) + } + }.launch() + + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = false, + blocking = true, + muting = false, + followRequesting = false, + mutingFollowRequest = false, + ) + + repository.save(relationship) + + assertThat(assertTable) + .row(0) + .isEqualTo(Relationships.actorId, relationship.actorId.id) + .isEqualTo(Relationships.targetActorId, relationship.targetActorId.id) + .isEqualTo(Relationships.following, relationship.following) + .isEqualTo(Relationships.blocking, relationship.blocking) + .isEqualTo(Relationships.muting, relationship.muting) + .isEqualTo(Relationships.followRequesting, relationship.followRequesting) + .isEqualTo(Relationships.mutingFollowRequest, relationship.mutingFollowRequest) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Actors.tableName) { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + values( + 2, + "a", + "test-hideout-dev.usbharu.dev", + "a", + "", + "https://test-hideout-dev.usbharu.dev/users/a/inbox", + "https://test-hideout-dev.usbharu.dev/users/a/outbox", + "https://test-hideout-dev.usbharu.dev/users/a", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/a#main-key", + "https://test-hideout-dev.usbharu.dev/users/a/following", + "https://test-hideout-dev.usbharu.dev/users/a/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto(Relationships.tableName) { + columns(Relationships.columns) + values(1, 2, true, false, false, false, false) + } + }.launch() + + val relationship = Relationship.default( + ActorId(1), ActorId(2) + ) + + change.withSuspend { + repository.delete(relationship) + } + + assertThat(change) + .changeOfDeletionOnTable(Relationships.tableName) + .rowAtStartPoint() + .value(Relationships.actorId.name).isEqualTo(relationship.actorId.id) + .value(Relationships.targetActorId.name).isEqualTo(relationship.targetActorId.id) + + } + + @Test + fun findByActorIdAndTargetId_指定された条件で存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Relationships.tableName) { + columns(Relationships.columns) + values(1, 2, true, false, true, true, false) + values(2, 1, true, false, false, false, false) + values(4, 3, true, false, false, false, true) + values(3, 5, true, false, false, true, false) + } + }.launch() + + val expected = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = true, + blocking = false, + muting = true, + followRequesting = true, + mutingFollowRequest = false + ) + + val actual = repository.findByActorIdAndTargetId(ActorId(1), ActorId(2)) + + assertEquals(expected, actual) + assertNotNull(actual) + assertEquals(expected.actorId, actual.actorId) + assertEquals(expected.targetActorId, actual.targetActorId) + assertEquals(expected.following, actual.following) + assertEquals(expected.blocking, actual.blocking) + assertEquals(expected.muting, actual.muting) + assertEquals(expected.followRequesting, actual.followRequesting) + assertEquals(expected.mutingFollowRequest, actual.mutingFollowRequest) + } + + @Test + fun findByActorIdAndTargetId_指定された条件で存在しないとnull() = runTest { + assertNull(repository.findByActorIdAndTargetId(ActorId(1), ActorId(2))) + } + + @Test + fun findByActorIdsAndTargetIdAndBlocking_指定された条件全部返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Relationships.tableName) { + columns(Relationships.columns) + values(1, 2, true, false, true, true, false) + values(1, 3, false, true, true, true, false) + values(1, 4, true, false, true, true, false) + values(2, 1, true, false, false, false, false) + values(4, 3, true, false, false, false, true) + values(3, 5, true, false, false, true, false) + } + }.launch() + + val expected = listOf( + Relationship( + actorId = ActorId(1), + targetActorId = ActorId(3), + following = false, + blocking = true, + muting = true, + followRequesting = true, + mutingFollowRequest = false + ) + ) + + val actual = repository.findByActorIdsAndTargetIdAndBlocking( + listOf(ActorId(1), ActorId(4)), ActorId(3), blocking = true + ) + + assertContentEquals(expected, actual) + + val expected2 = listOf( + Relationship( + actorId = ActorId(4), + targetActorId = ActorId(3), + following = true, + blocking = false, + muting = false, + followRequesting = false, + mutingFollowRequest = true + ) + ) + + val actual2 = repository.findByActorIdsAndTargetIdAndBlocking( + listOf(ActorId(1), ActorId(4)), ActorId(3), blocking = false + ) + + assertContentEquals(expected2, actual2) + } + + @Test + fun findByActorIdAndTargetIdsAndFollowing_指定された条件全部返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Relationships.tableName) { + columns(Relationships.columns) + values(1, 2, true, false, true, true, false) + values(1, 3, false, false, true, true, false) + values(1, 4, true, false, true, true, false) + values(2, 1, true, false, false, false, false) + values(4, 3, true, false, false, false, true) + values(3, 5, true, false, false, true, false) + } + }.launch() + + val expected = listOf( + Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = false, + blocking = true, + muting = true, + followRequesting = true, + mutingFollowRequest = false + ), Relationship( + actorId = ActorId(1), + targetActorId = ActorId(4), + following = false, + blocking = true, + muting = true, + followRequesting = true, + mutingFollowRequest = false + ) + ) + + val actual = repository.findByActorIdAndTargetIdsAndFollowing( + ActorId(1), listOf(ActorId(2), ActorId(3), ActorId(4)), following = true + ) + + assertContentEquals(expected, actual) + + val expected2 = listOf( + Relationship( + actorId = ActorId(1), + targetActorId = ActorId(3), + following = true, + blocking = false, + muting = false, + followRequesting = false, + mutingFollowRequest = true + ) + ) + + val actual2 = repository.findByActorIdAndTargetIdsAndFollowing( + ActorId(1), listOf(ActorId(2), ActorId(3), ActorId(4)), following = false + ) + + assertContentEquals(expected2, actual2) + } + + @Test + fun save_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + }.launch() + + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = false, + blocking = true, + muting = false, + followRequesting = false, + mutingFollowRequest = false, + ) + + relationship.block() + + repository.save(relationship) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } + + @Test + fun delete_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Relationships.tableName) { + columns(Relationships.columns) + values(1, 2, true, false, false, false, false) + } + }.launch() + + val relationship = Relationship( + actorId = ActorId(1), + targetActorId = ActorId(2), + following = false, + blocking = true, + muting = false, + followRequesting = false, + mutingFollowRequest = false, + ) + relationship.block() + + repository.delete(relationship) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRelationshipRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRelationshipRepositoryTest.kt new file mode 100644 index 00000000..60e8ae36 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRelationshipRepositoryTest.kt @@ -0,0 +1,254 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.timeline.TimelineId +import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationship +import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipId +import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.db.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import utils.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class ExposedTimelineRelationshipRepositoryTest : AbstractRepositoryTest(TimelineRelationships) { + + @Test + fun save_idが同じレコードがなければinsert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Timelines.tableName) { + columns(Timelines.columns) + values(1, 1, "time-line", "PUBLIC", false) + } + insertInto("public.actors") { + columns( + Actors.columns + ) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val relationship = TimelineRelationship( + TimelineRelationshipId(1), TimelineId(1), ActorId(1), Visible.PUBLIC + ) + ExposedTimelineRelationshipRepository().save(relationship) + + assertThat(assertTable) + .row(0) + .isEqualTo(TimelineRelationships.id, relationship.id.value) + .isEqualTo(TimelineRelationships.timelineId, relationship.timelineId.value) + .isEqualTo(TimelineRelationships.actorId, relationship.actorId.id) + .isEqualTo(TimelineRelationships.visible, relationship.visible.name) + } + + @Test + fun save_idが同じレコードがあればupdate() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Timelines.tableName) { + columns(Timelines.columns) + values(1, 1, "time-line", "PUBLIC", false) + } + insertInto(TimelineRelationships.tableName) { + columns(TimelineRelationships.columns) + values(1, 2, 3, "PUBLIC") + } + insertInto("public.actors") { + columns( + Actors.columns + ) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val relationship = TimelineRelationship( + TimelineRelationshipId(1), TimelineId(1), ActorId(1), Visible.PUBLIC + ) + ExposedTimelineRelationshipRepository().save(relationship) + + assertThat(assertTable) + .row(0) + .isEqualTo(TimelineRelationships.id, relationship.id.value) + .isEqualTo(TimelineRelationships.timelineId, relationship.timelineId.value) + .isEqualTo(TimelineRelationships.actorId, relationship.actorId.id) + .isEqualTo(TimelineRelationships.visible, relationship.visible.name) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(TimelineRelationships.tableName) { + columns(TimelineRelationships.columns) + values(1, 2, 3, "PUBLIC") + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val timelineRelationship = TimelineRelationship( + TimelineRelationshipId(1), + TimelineId(1), ActorId + (3), Visible.PUBLIC + ) + + change.withSuspend { + ExposedTimelineRelationshipRepository().delete(timelineRelationship) + } + + assertThat(change) + .changeOfDeletionOnTable(TimelineRelationships.tableName) + .rowAtStartPoint() + .value(TimelineRelationships.id.name).isEqualTo(timelineRelationship.id.value) + } + + @Test + fun findByActorId_指定されたid全部返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(TimelineRelationships.tableName) { + columns(TimelineRelationships.columns) + values(1, 2, 3, "PUBLIC") + values(2, 3, 3, "PUBLIC") + values(3, 3, 4, "PUBLIC") + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + + val findByActorId = ExposedTimelineRelationshipRepository().findByActorId(actorId = ActorId(3)) + + assertThat(findByActorId) + .hasSize(2) + } + + @Test + fun findById_指定されたidで存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(TimelineRelationships.tableName) { + columns(TimelineRelationships.columns) + values(1, 2, 3, "PUBLIC") + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val expected = TimelineRelationship( + id = TimelineRelationshipId(1), + timelineId = TimelineId(2), + actorId = ActorId + (3), + visible = Visible.PUBLIC + ) + + val actual = ExposedTimelineRelationshipRepository().findById(TimelineRelationshipId(1)) + + assertEquals(expected, actual) + assertNotNull(actual) + assertEquals(expected.id, actual.id) + assertEquals(expected.actorId, actual.actorId) + assertEquals(expected.timelineId, actual.timelineId) + assertEquals(expected.visible, actual.visible) + } + + @Test + fun findById_指定されたIdで存在しないとnull() = runTest { + assertNull(ExposedTimelineRelationshipRepository().findById(TimelineRelationshipId(1))) + } + + @Test + fun findByTimelineIdAndActorId_指定された条件で存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(TimelineRelationships.tableName) { + columns(TimelineRelationships.columns) + values(1, 2, 3, "PUBLIC") + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val expected = TimelineRelationship( + id = TimelineRelationshipId(1), + timelineId = TimelineId(2), + actorId = ActorId + (3), + visible = Visible.PUBLIC + ) + + val actual = ExposedTimelineRelationshipRepository().findByTimelineIdAndActorId(TimelineId(2), ActorId(3)) + + assertEquals(expected, actual) + assertNotNull(actual) + assertEquals(expected.id, actual.id) + assertEquals(expected.actorId, actual.actorId) + assertEquals(expected.timelineId, actual.timelineId) + assertEquals(expected.visible, actual.visible) + } + + @Test + fun findByTimelineIdAndActorId_指定された条件で存在しないとnull() = runTest { + ExposedTimelineRelationshipRepository().findByTimelineIdAndActorId(TimelineId(1), ActorId(1)) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepositoryTest.kt new file mode 100644 index 00000000..82e7e5a7 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedTimelineRepositoryTest.kt @@ -0,0 +1,250 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.timeline.Timeline +import dev.usbharu.hideout.core.domain.model.timeline.TimelineId +import dev.usbharu.hideout.core.domain.model.timeline.TimelineName +import dev.usbharu.hideout.core.domain.model.timeline.TimelineVisibility +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.db.api.Assertions.assertThat +import org.jetbrains.exposed.sql.transactions.TransactionManager +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.times +import org.mockito.kotlin.verify +import utils.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@ExtendWith(MockitoExtension::class) +class ExposedTimelineRepositoryTest : AbstractRepositoryTest(Timelines) { + @InjectMocks + lateinit var repository: ExposedTimelineRepository + + @Mock + lateinit var domainEventPublisher: DomainEventPublisher + + @Test + fun save_idが同じレコードが存在しない場合insert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(UserDetails.tableName) { + columns(UserDetails.columns) + values(1, 1, "veeeeeeeeeeeeeeryStrongPassword", true, null, null) + } + }.launch() + val timeline = Timeline( + id = TimelineId(1), + userDetailId = UserDetailId(1), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PUBLIC, + isSystem = false + ) + + repository.save(timeline) + + assertThat(assertTable).row(0).isEqualTo(Timelines.id, timeline.id.value) + .isEqualTo(Timelines.userDetailId, timeline.userDetailId.id).isEqualTo(Timelines.name, timeline.name.value) + .isEqualTo(Timelines.visibility, timeline.visibility.name).isEqualTo(Timelines.isSystem, timeline.isSystem) + } + + @Test + fun save_idが同じレコードが存在する場合update() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(UserDetails.tableName) { + columns(UserDetails.columns) + values(1, 1, "veeeeeeeeeeeeeeryStrongPassword", true, null, null) + } + insertInto(Timelines.tableName) { + columns(Timelines.columns) + values(1, 1, "test-timeline", "PUBLIC", true) + } + + }.launch() + val timeline = Timeline( + id = TimelineId(1), + userDetailId = UserDetailId(1), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PRIVATE, + isSystem = false + ) + + repository.save(timeline) + + assertThat(assertTable).row(0).isEqualTo(Timelines.id, timeline.id.value) + .isEqualTo(Timelines.userDetailId, timeline.userDetailId.id).isEqualTo(Timelines.name, timeline.name.value) + .isEqualTo(Timelines.visibility, timeline.visibility.name).isEqualTo(Timelines.isSystem, timeline.isSystem) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(UserDetails.tableName) { + columns(UserDetails.columns) + values(1, 1, "veeeeeeeeeeeeeeryStrongPassword", true, null, null) + } + insertInto(Timelines.tableName) { + columns(Timelines.columns) + values(1, 1, "test-timeline", "PUBLIC", true) + } + + }.launch() + val timeline = Timeline( + id = TimelineId(1), + userDetailId = UserDetailId(1), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PRIVATE, + isSystem = false + ) + + change.withSuspend { + repository.delete(timeline) + } + + assertThat(change).changeOfDeletionOnTable(Timelines.tableName).rowAtStartPoint().value(Timelines.id.name) + .isEqualTo(timeline.id.value) + } + + @Test + fun findByIds_指定されたIdすべて返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Timelines.tableName) { + columns(Timelines.columns) + values(1, 1, "test-timeline", "PUBLIC", true) + values(2, 1, "test-timeline2", "PUBLIC", true) + values(3, 1, "test-timeline3", "PUBLIC", true) + } + }.launch() + + val findByIds = repository.findByIds(listOf(TimelineId(1), TimelineId(3))) + + assertThat(findByIds).hasSize(2) + } + + @Test + fun findById_指定されたIdが存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Timelines.tableName) { + columns(Timelines.columns) + values(1, 1, "test-timeline", "PUBLIC", true) + values(2, 1, "test-timeline2", "PUBLIC", true) + values(3, 1, "test-timeline3", "PUBLIC", true) + } + }.launch() + + val actual = repository.findById(TimelineId(1)) + + val expected = Timeline( + TimelineId(1), UserDetailId(1), TimelineName("test-timeline"), TimelineVisibility.PUBLIC, true + ) + + assertEquals(expected, actual) + assertNotNull(actual) + assertEquals(expected.id, actual.id) + assertEquals(expected.userDetailId, actual.userDetailId) + assertEquals(expected.name, actual.name) + assertEquals(expected.visibility, actual.visibility) + assertEquals(expected.isSystem, actual.isSystem) + } + + @Test + fun findById_指定されたIdがなければnull() = runTest { + assertNull(repository.findById(TimelineId(1))) + } + + @Test + fun findAllByUserDetailIdANdVisibilityIn_指定されたVisibilityで指定されたUserDetailId全部返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(Timelines.tableName) { + columns(Timelines.columns) + values(1, 1, "test-timeline", "PUBLIC", true) + values(2, 1, "test-timeline2", "PRIVATE", true) + values(3, 1, "test-timeline3", "PUBLIC", true) + } + }.launch() + + val timelines = + repository.findAllByUserDetailIdAndVisibilityIn(UserDetailId(1), listOf(TimelineVisibility.PUBLIC)) + + assertThat(timelines).hasSize(2) + } + + @Test + fun save_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + }.launch() + val timeline = Timeline( + id = TimelineId(1), + userDetailId = UserDetailId(1), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PRIVATE, + isSystem = false + ) + + timeline.setVisibility( + TimelineVisibility.PUBLIC, UserDetail.create( + UserDetailId(1), ActorId(1), + UserDetailHashedPassword("aaaaaa"), + ) + ) + + repository.save(timeline) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } + + @Test + fun delete_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto(UserDetails.tableName) { + columns(UserDetails.columns) + values(1, 1, "veeeeeeeeeeeeeeryStrongPassword", true, null, null) + } + insertInto(Timelines.tableName) { + columns(Timelines.columns) + values(1, 1, "test-timeline", "PUBLIC", true) + } + + }.launch() + val timeline = Timeline( + id = TimelineId(1), + userDetailId = UserDetailId(1), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PRIVATE, + isSystem = false + ) + + timeline.setVisibility( + TimelineVisibility.PUBLIC, UserDetail.create( + UserDetailId(1), ActorId(1), + UserDetailHashedPassword("aaaaaa"), + ) + ) + + repository.delete(timeline) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } +} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedUserDetailRepositoryTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedUserDetailRepositoryTest.kt new file mode 100644 index 00000000..532cbda6 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/exposedrepository/ExposedUserDetailRepositoryTest.kt @@ -0,0 +1,367 @@ +package dev.usbharu.hideout.core.infrastructure.exposedrepository + +import com.ninja_squad.dbsetup_kotlin.dbSetup +import dev.usbharu.hideout.core.domain.model.actor.ActorId +import dev.usbharu.hideout.core.domain.model.timeline.TimelineId +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailHashedPassword +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.assertj.db.api.Assertions.assertThat +import org.jetbrains.exposed.sql.transactions.TransactionManager +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.times +import org.mockito.kotlin.verify +import utils.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@ExtendWith(MockitoExtension::class) +class ExposedUserDetailRepositoryTest : AbstractRepositoryTest(UserDetails) { + + @InjectMocks + lateinit var userDetailRepository: ExposedUserDetailRepository + + @Mock + lateinit var domainEventPublisher: DomainEventPublisher + + @Test + fun save_idが同じレコードがない場合insert() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + userDetailRepository.save( + UserDetail.create( + UserDetailId(1), + ActorId(1), + UserDetailHashedPassword("VeeeeeeeeeeeeeryStrongPassword"), + false, + null, + null + ) + ) + + assertThat(assertTable).row(0).isEqualTo(UserDetails.id, 1).isEqualTo(UserDetails.actorId, 1) + .isEqualTo(UserDetails.password, "VeeeeeeeeeeeeeryStrongPassword") + .isEqualTo(UserDetails.lastMigration, null).isEqualTo(UserDetails.autoAcceptFolloweeFollowRequest, false) + .isEqualTo(UserDetails.homeTimelineId, null) + } + + @Test + fun save_idが同じレコードがある場合update() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.actors") { + columns(Actors.columns) + values( + 1, + "b", + "test-hideout-dev.usbharu.dev", + "b", + "", + "https://test-hideout-dev.usbharu.dev/users/b/inbox", + "https://test-hideout-dev.usbharu.dev/users/b/outbox", + "https://test-hideout-dev.usbharu.dev/users/b", + "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuMjzmQBsSxzK6NkOpZh\nWuohaUbzCY7AafXt+3+tiL6LulYNg/YRIqKc7Q/vTJE6CHrqo7RA/OqYrSMxF/LC\nf8aX5aHwJE1A2gSgCcs1IL5GJaYRlp4NcuazpBC9NO4xIrvH//jcVnZGXGWsCbls\nHXZGZdurWOF0Bl3mYN8CdupVumrGuOPs+wbI/Gh+OHw611TcXMyAwFwU2UjvPEgk\nEACW9OvJaq1K40jVCAa3b1nXt53vlXXZEUlL78L0C9xuFbJG0K/GKMBN44GyftJO\nhA95Rf1Nhd0vKDLBiRocGcARmBo9PaSCR5651gJEk5/wfLUnNAf0xj3R8LBoOhnT\nCQIDAQAB\n-----END PUBLIC KEY-----", + "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4yPOZAGxLHMr\no2Q6lmFa6iFpRvMJjsBp9e37f62Ivou6Vg2D9hEiopztD+9MkToIeuqjtED86pit\nIzEX8sJ/xpflofAkTUDaBKAJyzUgvkYlphGWng1y5rOkEL007jEiu8f/+NxWdkZc\nZawJuWwddkZl26tY4XQGXeZg3wJ26lW6asa44+z7Bsj8aH44fDrXVNxczIDAXBTZ\nSO88SCQQAJb068lqrUrjSNUIBrdvWde3ne+VddkRSUvvwvQL3G4VskbQr8YowE3j\ngbJ+0k6ED3lF/U2F3S8oMsGJGhwZwBGYGj09pIJHnrnWAkSTn/B8tSc0B/TGPdHw\nsGg6GdMJAgMBAAECggEAHkEhLEb70kdOGgJLUR9D/5zYBE0eXdz/MsMyd1AH+Shs\n9AmetKsYzWDmuhp9Cp5swyn328Hmn7B+DvInVn+5YvjNhY07SbaJcVls4g5UQFXk\nu6WC4ZfKap7IyAeaUg54858r8677xcWXuByN5dn+1iU2hJGYK3Cx7rx0PRrUURYG\n2BRaEEwkcPNm9u679OOTyvTmA3NhewUuDaTMkZnnAml87uYYnmFKjQcR+S2UqOm6\nvBZ/devG4TfPBeKEAya/ba8JJ8frGOtjmR9EIliTQoxI2izeAfoGs1OsCSpuPy6s\nV5f0X3HYM7CA+Fpkt2pnixuwg96LaVr4OpVxujhNlwKBgQD1827VuKFGrneNO+c+\n4EIvh+vLh462bJiaVsMHfRhNZF1/5i8gfNJ16ST60hJo11E4riHPzi3q6GWuxOYl\nCkVKvhJ2g3mgnhoehcgnT7UBkasaC7JYd+LsFDnWOTVSJOy2OqfLdLDGAuSTN3kO\nBF4p0ZqQ/AouFNin57WNRGVZ7wKBgQDTLUZtfTkOU3G1nIMTRKmZjqdER5glzHCm\n9o/1ZsQktL+nzSXqYeoWh9fr7fkmC0k/07+SHzzfWvOhWWWlRenUVL5mj7FRq+L9\n9kDjChLR3Jr4L6Sj1iaQ+0uqDSQNYSYO9ctMjAVjFiNhiAd+S6B451Q1VbDKTCHt\nkRW9omz6hwKBgBFTsgY6eJorJl77zmG+mMsSb0kqZqJxahrNa/X2GSUyoeelxsIq\nKQWHhERrUkKykJVGpzkllFSNRMSYOIJ5g8ItO82/m2z2Vm66DAzA78aJhZ1TH6Bd\n6c2p6x0tcJU15rs7zKBnuyBoCcRZTxzur9eQXaxDJVBzxYOmrkKig+VfAoGBAMCP\n2Fiehxh5HobsYNmBEuXjHsM0RZiyA0c8LakoPFL8PodUme5PupUw6cNJDJeUUwbQ\nny8vLOK+nMnUKsu6JK5pV/VNsfM3OZU6p5Bf7ylOcEE/sHF1JVWu0CAQO3+3xmx9\n1RPH2mGwHjMhRzPy4jFdP3wi10KgiY+HbLuvEJChAoGAYCsh3UhtTzGUOlPBkmLL\n17bD0wN4J/fOv8BoXPZ8H2CdqVgWy0s+s+QaPqRxNcA6YyGymBqrmQAn1Uii25r9\nKAwVAjg3S2KDEMSI2RbMMmQJSZ1u0GkxqOUC/MMeZqBYTYxVeqcQPoqJZ0Nk7IOA\nZPFif8bVfcZqeimxrFaV6YI=\n-----END PRIVATE KEY-----", + "2024-09-09 17:12:03.941339", + "https://test-hideout-dev.usbharu.dev/users/b#main-key", + "https://test-hideout-dev.usbharu.dev/users/b/following", + "https://test-hideout-dev.usbharu.dev/users/b/followers", + 1, + false, + 0, + 0, + 0, + null, + "2024-09-09 17:12:03.941339", + false, + null, + "", + false, + null, + null + ) + } + insertInto("public.user_details") { + columns(UserDetails.columns) + values( + 1, + 1, + "$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm", + false, + null, + 1832779979297918976 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + userDetailRepository.save( + UserDetail.create( + UserDetailId(1), + ActorId(1), + UserDetailHashedPassword("VeeeeeeeeeeeeeryStrongPassword"), + false, + null, + null + ) + ) + + assertThat(assertTable).row(0).isEqualTo(UserDetails.id, 1).isEqualTo(UserDetails.actorId, 1) + .isEqualTo(UserDetails.password, "VeeeeeeeeeeeeeryStrongPassword") + .isEqualTo(UserDetails.lastMigration, null).isEqualTo(UserDetails.autoAcceptFolloweeFollowRequest, false) + .isEqualTo(UserDetails.homeTimelineId, null) + } + + @Test + fun delete_削除される() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.user_details") { + columns(UserDetails.columns) + values( + 1, + 1, + "$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm", + false, + null, + 1832779979297918976 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val userDetail = UserDetail( + UserDetailId(1), ActorId(1), UserDetailHashedPassword("VeeeeeeeeeeeeeryStrongPassword"), false, null, null + ) + + change.withSuspend { + userDetailRepository.delete(userDetail) + } + + assertThat(change).changeOfDeletionOnTable(UserDetails.tableName).rowAtStartPoint().value(UserDetails.id.name) + .isEqualTo(1) + } + + @Test + fun findByActorId_指定したActorIdで存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.user_details") { + columns(UserDetails.columns) + values( + 1, + 1, + "$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm", + false, + null, + 1832779979297918976 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val expect = UserDetail( + id = UserDetailId(1), + actorId = ActorId(1), + password = UserDetailHashedPassword("$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm"), + autoAcceptFolloweeFollowRequest = false, + lastMigration = null, + homeTimelineId = TimelineId(1832779979297918976) + ) + + val actual = userDetailRepository.findByActorId(1) + + assertEquals(actual, expect) + } + + @Test + fun findByActorId_指定したActorIdで存在しないとnull() = runTest { + assertNull(userDetailRepository.findByActorId(1)) + } + + @Test + fun findById_指定したIdで存在したら返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.user_details") { + columns(UserDetails.columns) + values( + 1, + 1, + "$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm", + false, + null, + 1832779979297918976 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val expect = UserDetail( + id = UserDetailId(1), + actorId = ActorId(1), + password = UserDetailHashedPassword("$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm"), + autoAcceptFolloweeFollowRequest = false, + lastMigration = null, + homeTimelineId = TimelineId(1832779979297918976) + ) + + val actual = userDetailRepository.findById(UserDetailId(1)) + + assertEquals(actual, expect) + } + + @Test + fun findById_指定したIdで存在しないとnull() = runTest { + assertNull(userDetailRepository.findById(UserDetailId(1))) + } + + @Test + fun findAllById_指定されたidすべて返す() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.user_details") { + columns(UserDetails.columns) + values( + 1, 1, "$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm", false, null, null + ) + values( + 2, + 2, + "$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm", + false, + null, + 1832779979297918976 + ) + values( + 3, + 3, + "$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm", + false, + null, + 1832779979297918976 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + val userDetailList = userDetailRepository.findAllById(listOf(UserDetailId(1), UserDetailId(3))) + + assertThat(userDetailList).hasSize(2) + } + + @Test + fun save_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + }.launch() + + userDetailRepository.save( + UserDetail.create( + UserDetailId(1), + ActorId(1), + UserDetailHashedPassword("VeeeeeeeeeeeeeryStrongPassword"), + false, + null, + null + ) + ) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } + + @Test + fun delete_ドメインイベントがパブリッシュされる() = runTest { + dbSetup(to = dataSource) { + execute(disableReferenceIntegrityConstraints) + insertInto("public.user_details") { + columns( + UserDetails.columns + ) + values( + 1, + 1, + "$2a$10\$EBj3lstVOv0wz3CxLpzYJu8FFrUJ2MPJW9Vlklyg.bfGEOn5sqIwm", + false, + null, + 1832779979297918976 + ) + } + execute(enableReferenceIntegrityConstraints) + }.launch() + + userDetailRepository.delete( + UserDetail.create( + UserDetailId(1), + ActorId(1), + UserDetailHashedPassword("VeeeeeeeeeeeeeryStrongPassword"), + false, + null, + null + ) + ) + + TransactionManager.current().commit() + + verify(domainEventPublisher, times(1)).publishEvent(any()) + } + + private fun assertEquals( + actual: UserDetail?, expect: UserDetail + ) { + assertNotNull(actual) + kotlin.test.assertEquals(expect, actual) + assertEquals(expect.id, actual.id) + assertEquals(expect.actorId, actual.actorId) + assertEquals(expect.password, actual.password) + assertEquals(expect.autoAcceptFolloweeFollowRequest, actual.autoAcceptFolloweeFollowRequest) + assertEquals(expect.lastMigration, actual.lastMigration) + assertEquals(expect.homeTimelineId, actual.homeTimelineId) + } +} diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImplTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImplTest.kt index bb253ef6..cf24d329 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImplTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/oauth2/UserDetailsServiceImplTest.kt @@ -59,7 +59,7 @@ class UserDetailsServiceImplTest { fun userDetailが見つからない場合失敗() = runTest { whenever(actorRepository.findByNameAndDomain(eq("test"), eq("example.com"))).doReturn( TestActorFactory.create( - actorName = "test", id = 1 + id = 1, actorName = "test" ) ) assertThrows { diff --git a/hideout-core/src/test/kotlin/utils/AbstractRepositoryTest.kt b/hideout-core/src/test/kotlin/utils/AbstractRepositoryTest.kt new file mode 100644 index 00000000..4499081a --- /dev/null +++ b/hideout-core/src/test/kotlin/utils/AbstractRepositoryTest.kt @@ -0,0 +1,124 @@ +package utils + +import com.ninja_squad.dbsetup.Operations +import com.ninja_squad.dbsetup.operation.Insert +import com.ninja_squad.dbsetup_kotlin.dbSetup +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource +import org.assertj.db.api.TableRowAssert +import org.assertj.db.api.TableRowValueAssert +import org.assertj.db.type.Changes +import org.assertj.db.type.Table +import org.flywaydb.core.Flyway +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.DatabaseConfig +import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.transactions.TransactionManager +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import java.sql.Connection +import javax.sql.DataSource + +abstract class AbstractRepositoryTest(private val exposedTable: org.jetbrains.exposed.sql.Table) { + + protected val assertTable: Table + get() { + return Table(dataSource, exposedTable.tableName) + } + + protected fun getTable(name: String): Table { + return Table(dataSource, name) + } + + private lateinit var transaction: Transaction + + protected lateinit var change: Changes + + @BeforeEach + fun setUp() { + flyway.clean() + flyway.migrate() + transaction = TransactionManager.manager.newTransaction(Connection.TRANSACTION_READ_UNCOMMITTED) + change = Changes(assertTable) + } + + @AfterEach + fun tearDown() { + dbSetup(to = dataSource) { + execute(Operations.sql("SET REFERENTIAL_INTEGRITY TRUE")) + }.launch() + transaction.rollback() + transaction.close() + } + + companion object { + + lateinit var dataSource: DataSource + lateinit var flyway: Flyway + + @JvmStatic + @BeforeAll + fun setup() { + val hikariConfig = HikariConfig() + hikariConfig.jdbcUrl = + "jdbc:h2:mem:test;MODE=POSTGRESQL;DB_CLOSE_DELAY=-1;CASE_INSENSITIVE_IDENTIFIERS=true;TRACE_LEVEL_FILE=4;" + hikariConfig.driverClassName = "org.h2.Driver" + hikariConfig.transactionIsolation = "TRANSACTION_READ_UNCOMMITTED" + dataSource = HikariDataSource(hikariConfig) + + + flyway = Flyway.configure().cleanDisabled(false).dataSource(dataSource).load() + Database.connect(dataSource, databaseConfig = DatabaseConfig { + defaultMaxAttempts = 1 + + }) + flyway.clean() + flyway.migrate() + } + + @JvmStatic + @AfterAll + fun clean() { +// flyway.clean() + } + } +} + +fun TableRowAssert.value(column: Column): TableRowValueAssert = value(column.name) +fun TableRowValueAssert.value(column: Column): TableRowValueAssert = value(column.name) + +fun TableRowAssert.isEqualTo(column: Column, value: T): TableRowValueAssert { + return value(column).isEqualTo(value) +} + +fun TableRowValueAssert.isEqualTo(column: Column, value: T): TableRowValueAssert { + return value(column).isEqualTo(value) +} + +fun Insert.Builder.columns(columns: List>): Insert.Builder { + columns(*columns.map { it.name }.toTypedArray()) + return this +} + +fun Insert.Builder.columns(vararg columns: Column<*>): Insert.Builder { + columns(*columns.map { it.name }.toTypedArray()) + return this +} + +fun Changes.with(block: () -> Unit) { + setStartPointNow() + block() + setEndPointNow() +} + +suspend fun Changes.withSuspend(block: suspend () -> Unit) { + setStartPointNow() + block() + setEndPointNow() +} + +val disableReferenceIntegrityConstraints = Operations.sql("SET REFERENTIAL_INTEGRITY FALSE") +val enableReferenceIntegrityConstraints = Operations.sql("SET REFERENTIAL_INTEGRITY TRUE") \ No newline at end of file diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedAccountQueryServiceImpl.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedAccountQueryServiceImpl.kt index 4645c3af..51589189 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedAccountQueryServiceImpl.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedAccountQueryServiceImpl.kt @@ -50,7 +50,7 @@ class ExposedAccountQueryServiceImpl(private val applicationConfig: ApplicationC id = resultRow[Actors.id].toString(), username = resultRow[Actors.name], acct = "${resultRow[Actors.name]}@${resultRow[Actors.domain]}", - url = resultRow[Actors.url], + url = resultRow[Actors.url].toString(), displayName = resultRow[Actors.screenName], note = resultRow[Actors.description], avatar = "$userUrl/icon.jpg", diff --git a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedStatusQueryService.kt b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedStatusQueryService.kt index d80009f1..93101819 100644 --- a/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedStatusQueryService.kt +++ b/hideout-mastodon/src/main/kotlin/dev/usbharu/hideout/mastodon/infrastructure/exposedquery/ExposedStatusQueryService.kt @@ -225,13 +225,13 @@ private fun toStatus(it: ResultRow, queryAlias: QueryAlias, inReplyToAlias: Alia id = it[Actors.id].toString(), username = it[Actors.name], acct = "${it[Actors.name]}@${it[Actors.domain]}", - url = it[Actors.url], + url = it[Actors.url].toString(), displayName = it[Actors.screenName], note = it[Actors.description], - avatar = it[Actors.url] + "/icon.jpg", - avatarStatic = it[Actors.url] + "/icon.jpg", - header = it[Actors.url] + "/header.jpg", - headerStatic = it[Actors.url] + "/header.jpg", + avatar = "${it[Actors.url]}/icon.jpg", + avatarStatic = "${it[Actors.url]}/icon.jpg", + header = "${it[Actors.url]}/header.jpg", + headerStatic = "${it[Actors.url]}/header.jpg", locked = it[Actors.locked], fields = emptyList(), emojis = emptyList(), diff --git a/owl/gradle.properties b/owl/gradle.properties index 43cbcbb4..d345cf66 100644 --- a/owl/gradle.properties +++ b/owl/gradle.properties @@ -1,4 +1,5 @@ -kotlin.code.style=official -org.gradle.daemon=true org.gradle.parallel=true -org.gradle.configureondemand=true \ No newline at end of file +org.gradle.configureondemand=true +org.gradle.caching=true +org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC --add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED +kotlin.compiler.preciseCompilationResultsBackup=true \ No newline at end of file