diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timelineobject/TimelineObjectWarnFilter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timelineobject/TimelineObjectWarnFilter.kt index 6950d4e9..bdd0764d 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timelineobject/TimelineObjectWarnFilter.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/timelineobject/TimelineObjectWarnFilter.kt @@ -2,4 +2,4 @@ package dev.usbharu.hideout.core.domain.model.timelineobject import dev.usbharu.hideout.core.domain.model.filter.FilterId -class TimelineObjectWarnFilter(val filterId: FilterId, val matchedKeyword: String) +data class TimelineObjectWarnFilter(val filterId: FilterId, val matchedKeyword: String) diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/DefaultPostContentFormatter.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/DefaultPostContentFormatter.kt index c6c02dc5..0998a495 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/DefaultPostContentFormatter.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/other/DefaultPostContentFormatter.kt @@ -43,8 +43,6 @@ class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : Po // 文字だけのHTMLなどはここでpタグで囲む val flattenHtml = unsafeElement.childNodes().mapNotNull { - println(it.toString()) - println(it.javaClass) if (it is Element) { it } else if (it is TextNode) { @@ -59,8 +57,6 @@ class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : Po val safeHtml = policyFactory.sanitize(unsafeHtml) - println(safeHtml) - val safeDocument = Jsoup.parseBodyFragment(safeHtml).getElementsByTag("body").first() ?: return FormattedPostContent("", "") @@ -97,6 +93,9 @@ class DefaultPostContentFormatter(private val policyFactory: PolicyFactory) : Po } val elements = Elements(formattedHtml) + val document1 = Document("") + document1.outputSettings().syntax(Document.OutputSettings.Syntax.xml) + document1.insertChildren(0, elements) return FormattedPostContent(elements.outerHtml().replace("\n", ""), printHtml(elements)) } diff --git a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt index 38560c81..564f188a 100644 --- a/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt +++ b/hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/AbstractTimelineStore.kt @@ -263,7 +263,7 @@ abstract class AbstractTimelineStore(private val idGenerateService: IdGenerateSe ) } - abstract suspend fun getActors(actorIds: List): Map + protected abstract suspend fun getActors(actorIds: List): Map - abstract suspend fun getUserDetails(userDetailIdList: List): Map + protected abstract suspend fun getUserDetails(userDetailIdList: List): Map } diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/other/DefaultPostContentFormatterTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/other/DefaultPostContentFormatterTest.kt index 05ee9f62..91c6413f 100644 --- a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/other/DefaultPostContentFormatterTest.kt +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/other/DefaultPostContentFormatterTest.kt @@ -1,11 +1,13 @@ package dev.usbharu.hideout.core.infrastructure.other import dev.usbharu.hideout.core.config.HtmlSanitizeConfig +import dev.usbharu.hideout.core.domain.service.post.FormattedPostContent 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 kotlin.test.assertEquals @ExtendWith(MockitoExtension::class) class DefaultPostContentFormatterTest { @@ -17,40 +19,57 @@ class DefaultPostContentFormatterTest { @Test fun 文字だけのHTMLをPで囲む() { - formatter.format("a") + val actual = formatter.format("a") + + assertEquals(FormattedPostContent("

a

", "a"), actual) } @Test fun エレメントはそのまま() { - formatter.format("

a

") + val actual = formatter.format("

a

") + + assertEquals(FormattedPostContent("

a

", "a"), actual) } @Test fun コメントは無視() { - formatter.format("") + val actual = formatter.format("") + + assertEquals(FormattedPostContent("", ""), actual) } @Test fun brタグを改行に() { - formatter.format("

a

") + val actual = formatter.format("

a

") + + assertEquals(FormattedPostContent("

a

", "a\n"), actual) } @Test fun brタグ2連続を段落に() { val format = formatter.format("

a

a

") - println(format) + assertEquals(FormattedPostContent("

a

a

", "a\n\na"), format) + } + + @Test + fun brタグ3連続以上を段落にして改行2つに変換() { + val format = formatter.format("

a


a

") + + assertEquals(FormattedPostContent("

a

a

", "a\n\na"), format) } @Test fun aタグは許可される() { val format = formatter.format("p") - println(format) + assertEquals(FormattedPostContent("p", "p"), format) } @Test fun pの中のaタグも許可される() { - formatter.format("

a

") + val actual = formatter.format("

a

") + + assertEquals(FormattedPostContent("

a

", "a"), actual) } } \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/other/TwitterSnowflakeIdGenerateServiceTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/other/TwitterSnowflakeIdGenerateServiceTest.kt new file mode 100644 index 00000000..98855ca8 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/other/TwitterSnowflakeIdGenerateServiceTest.kt @@ -0,0 +1,30 @@ +package dev.usbharu.hideout.core.infrastructure.other + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class TwitterSnowflakeIdGenerateServiceTest { + @Test + fun noDuplicateTest() = runBlocking { + val mutex = Mutex() + val mutableListOf = mutableListOf() + coroutineScope { + repeat(500000) { + launch(Dispatchers.IO) { + val id = TwitterSnowflakeIdGenerateService.generateId() + mutex.withLock { + mutableListOf.add(id) + } + } + } + } + + assertEquals(0, mutableListOf.size - mutableListOf.toSet().size) + } +} \ No newline at end of file diff --git a/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStoreTest.kt b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStoreTest.kt new file mode 100644 index 00000000..66eeb1a0 --- /dev/null +++ b/hideout-core/src/test/kotlin/dev/usbharu/hideout/core/infrastructure/timeline/DefaultTimelineStoreTest.kt @@ -0,0 +1,348 @@ +package dev.usbharu.hideout.core.infrastructure.timeline + +import dev.usbharu.hideout.core.config.DefaultTimelineStoreConfig +import dev.usbharu.hideout.core.domain.model.actor.ActorRepository +import dev.usbharu.hideout.core.domain.model.filter.* +import dev.usbharu.hideout.core.domain.model.post.PostRepository +import dev.usbharu.hideout.core.domain.model.post.TestPostFactory +import dev.usbharu.hideout.core.domain.model.post.Visibility +import dev.usbharu.hideout.core.domain.model.timeline.* +import dev.usbharu.hideout.core.domain.model.timelineobject.TimelineObject +import dev.usbharu.hideout.core.domain.model.timelineobject.TimelineObjectWarnFilter +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.TimelineRelationshipRepository +import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId +import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository +import dev.usbharu.hideout.core.domain.service.filter.FilterDomainService +import dev.usbharu.hideout.core.infrastructure.other.TwitterSnowflakeIdGenerateService +import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.InjectMocks +import org.mockito.Mock +import org.mockito.Spy +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.* + +@ExtendWith(MockitoExtension::class) +class DefaultTimelineStoreTest { + @InjectMocks + lateinit var timelineStore: DefaultTimelineStore + + @Mock + lateinit var timelineRepository: TimelineRepository + + @Mock + lateinit var timelineRelationshipRepository: TimelineRelationshipRepository + + @Mock + lateinit var filterRepository: FilterRepository + + @Mock + lateinit var postRepository: PostRepository + + @Mock + lateinit var filterDomainService: FilterDomainService + + @Mock + lateinit var internalTimelineObjectRepository: InternalTimelineObjectRepository + + @Mock + lateinit var userDetailRepository: UserDetailRepository + + @Mock + lateinit var actorRepository: ActorRepository + + @Spy + val defaultTimelineStoreConfig = DefaultTimelineStoreConfig(500) + + @Spy + val idGenerateService = TwitterSnowflakeIdGenerateService + + @Test + fun addPost() = runTest { + val post = TestPostFactory.create() + whenever(timelineRelationshipRepository.findByActorId(post.actorId)).doReturn( + listOf( + TimelineRelationship( + TimelineRelationshipId(1), + TimelineId(12), + post.actorId, + Visible.PUBLIC + ) + ) + ) + + whenever(timelineRepository.findByIds(listOf(TimelineId(12)))).doReturn( + listOf( + Timeline( + id = TimelineId(12), + userDetailId = UserDetailId(post.actorId.id), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PUBLIC, + isSystem = false + ) + ) + ) + + val filters = listOf( + Filter( + id = FilterId(13), + userDetailId = UserDetailId(post.actorId.id), + name = FilterName("filter"), + filterContext = setOf(FilterContext.HOME), + filterAction = FilterAction.HIDE, + filterKeywords = setOf( + FilterKeyword(FilterKeywordId(14), FilterKeywordKeyword("aa"), FilterMode.NONE) + ) + ) + ) + + whenever(filterRepository.findByUserDetailId(UserDetailId(post.actorId.id))).doReturn(filters) + + whenever(filterDomainService.apply(post, FilterContext.HOME, filters)).doReturn( + FilteredPost( + post, listOf( + FilterResult(filters.first(), "aaa") + ) + ) + ) + + timelineStore.addPost(post) + + argumentCaptor> { + verify(internalTimelineObjectRepository, times(1)).saveAll(capture()) + val timelineObjectList = allValues.first() + + assertThat(timelineObjectList).allSatisfy { + assertThat(it.postId).isEqualTo(post.id) + assertThat(it.postActorId).isEqualTo(post.actorId) + assertThat(it.replyId).isNull() + assertThat(it.replyActorId).isNull() + assertThat(it.repostId).isNull() + assertThat(it.repostActorId).isNull() + + assertThat(it.userDetailId).isEqualTo(UserDetailId(post.actorId.id)) + assertThat(it.timelineId).isEqualTo(TimelineId(12)) + assertThat(it.warnFilters).contains(TimelineObjectWarnFilter(FilterId(13), "aaa")) + } + } + } + + @Test + fun `addPost direct投稿は追加されない`() = runTest { + val post = TestPostFactory.create(visibility = Visibility.DIRECT) + whenever(timelineRelationshipRepository.findByActorId(post.actorId)).doReturn( + listOf( + TimelineRelationship( + TimelineRelationshipId(1), + TimelineId(12), + post.actorId, + Visible.PUBLIC + ) + ) + ) + + whenever(timelineRepository.findByIds(listOf(TimelineId(12)))).doReturn( + listOf( + Timeline( + id = TimelineId(12), + userDetailId = UserDetailId(post.actorId.id), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PUBLIC, + isSystem = false + ) + ) + ) + + timelineStore.addPost(post) + + argumentCaptor> { + verify(internalTimelineObjectRepository, times(1)).saveAll(capture()) + val timelineObjectList = allValues.first() + + assertThat(timelineObjectList).isEmpty() + } + } + + @Test + fun timelineがpublicでpostがUNLISTEDの時追加されない() = runTest { + val post = TestPostFactory.create(visibility = Visibility.UNLISTED) + whenever(timelineRelationshipRepository.findByActorId(post.actorId)).doReturn( + listOf( + TimelineRelationship( + TimelineRelationshipId(1), + TimelineId(12), + post.actorId, + Visible.PUBLIC + ) + ) + ) + + whenever(timelineRepository.findByIds(listOf(TimelineId(12)))).doReturn( + listOf( + Timeline( + id = TimelineId(12), + userDetailId = UserDetailId(post.actorId.id), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PUBLIC, + isSystem = false + ) + ) + ) + + timelineStore.addPost(post) + + argumentCaptor> { + verify(internalTimelineObjectRepository, times(1)).saveAll(capture()) + val timelineObjectList = allValues.first() + + assertThat(timelineObjectList).isEmpty() + } + } + + @Test + fun timelineがpublicでpostがFOLLOWERSの時追加されない() = runTest { + val post = TestPostFactory.create(visibility = Visibility.FOLLOWERS) + whenever(timelineRelationshipRepository.findByActorId(post.actorId)).doReturn( + listOf( + TimelineRelationship( + TimelineRelationshipId(1), + TimelineId(12), + post.actorId, + Visible.PUBLIC + ) + ) + ) + + whenever(timelineRepository.findByIds(listOf(TimelineId(12)))).doReturn( + listOf( + Timeline( + id = TimelineId(12), + userDetailId = UserDetailId(post.actorId.id), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PUBLIC, + isSystem = false + ) + ) + ) + + timelineStore.addPost(post) + + argumentCaptor> { + verify(internalTimelineObjectRepository, times(1)).saveAll(capture()) + val timelineObjectList = allValues.first() + + assertThat(timelineObjectList).isEmpty() + } + } + + @Test + fun timelineがUNLISTEDでpostがFOLLOWERSの時追加されない() = runTest { + val post = TestPostFactory.create(visibility = Visibility.FOLLOWERS) + whenever(timelineRelationshipRepository.findByActorId(post.actorId)).doReturn( + listOf( + TimelineRelationship( + TimelineRelationshipId(1), + TimelineId(12), + post.actorId, + Visible.PUBLIC + ) + ) + ) + + whenever(timelineRepository.findByIds(listOf(TimelineId(12)))).doReturn( + listOf( + Timeline( + id = TimelineId(12), + userDetailId = UserDetailId(post.actorId.id), + name = TimelineName("timeline"), + visibility = TimelineVisibility.UNLISTED, + isSystem = false + ) + ) + ) + + timelineStore.addPost(post) + + argumentCaptor> { + verify(internalTimelineObjectRepository, times(1)).saveAll(capture()) + val timelineObjectList = allValues.first() + + assertThat(timelineObjectList).isEmpty() + } + } + + @Test + fun timelineがPRIVATEでpostがFOLLOWERSの時追加される() = runTest { + val post = TestPostFactory.create(visibility = Visibility.FOLLOWERS) + whenever(timelineRelationshipRepository.findByActorId(post.actorId)).doReturn( + listOf( + TimelineRelationship( + TimelineRelationshipId(1), + TimelineId(12), + post.actorId, + Visible.PUBLIC + ) + ) + ) + + whenever(timelineRepository.findByIds(listOf(TimelineId(12)))).doReturn( + listOf( + Timeline( + id = TimelineId(12), + userDetailId = UserDetailId(post.actorId.id), + name = TimelineName("timeline"), + visibility = TimelineVisibility.PRIVATE, + isSystem = false + ) + ) + ) + + val filters = listOf( + Filter( + id = FilterId(13), + userDetailId = UserDetailId(post.actorId.id), + name = FilterName("filter"), + filterContext = setOf(FilterContext.HOME), + filterAction = FilterAction.HIDE, + filterKeywords = setOf( + FilterKeyword(FilterKeywordId(14), FilterKeywordKeyword("aa"), FilterMode.NONE) + ) + ) + ) + + whenever(filterRepository.findByUserDetailId(UserDetailId(post.actorId.id))).doReturn(filters) + + whenever(filterDomainService.apply(post, FilterContext.HOME, filters)).doReturn( + FilteredPost( + post, listOf( + FilterResult(filters.first(), "aaa") + ) + ) + ) + + timelineStore.addPost(post) + + argumentCaptor> { + verify(internalTimelineObjectRepository, times(1)).saveAll(capture()) + val timelineObjectList = allValues.first() + + assertThat(timelineObjectList).allSatisfy { + assertThat(it.postId).isEqualTo(post.id) + assertThat(it.postActorId).isEqualTo(post.actorId) + assertThat(it.replyId).isNull() + assertThat(it.replyActorId).isNull() + assertThat(it.repostId).isNull() + assertThat(it.repostActorId).isNull() + + assertThat(it.userDetailId).isEqualTo(UserDetailId(post.actorId.id)) + assertThat(it.timelineId).isEqualTo(TimelineId(12)) + assertThat(it.warnFilters).contains(TimelineObjectWarnFilter(FilterId(13), "aaa")) + } + } + } +} \ No newline at end of file