/* * Copyright 2023 Tusky Contributors * * This file is a part of Tusky. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ package com.keylesspalace.tusky import androidx.test.ext.junit.runners.AndroidJUnit4 import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.PollOption import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.view.getSecondsForDurationIndex import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.robolectric.annotation.Config import java.time.Instant import java.util.Date @Config(sdk = [28]) @RunWith(AndroidJUnit4::class) class FilterTest { private lateinit var filterModel: FilterModel @Before fun setup() { filterModel = FilterModel() val filters = listOf( Filter( id = "123", phrase = "badWord", context = listOf(Filter.HOME), expiresAt = null, irreversible = false, wholeWord = false ), Filter( id = "123", phrase = "badWholeWord", context = listOf(Filter.HOME, Filter.PUBLIC), expiresAt = null, irreversible = false, wholeWord = true ), Filter( id = "123", phrase = "@twitter.com", context = listOf(Filter.HOME), expiresAt = null, irreversible = false, wholeWord = true ), Filter( id = "123", phrase = "#hashtag", context = listOf(Filter.HOME), expiresAt = null, irreversible = false, wholeWord = true ), Filter( id = "123", phrase = "expired", context = listOf(Filter.HOME), expiresAt = Date.from(Instant.now().minusSeconds(10)), irreversible = false, wholeWord = true ), Filter( id = "123", phrase = "unexpired", context = listOf(Filter.HOME), expiresAt = Date.from(Instant.now().plusSeconds(3600)), irreversible = false, wholeWord = true ), Filter( id = "123", phrase = "href", context = listOf(Filter.HOME), expiresAt = null, irreversible = false, wholeWord = false ), ) filterModel.initWithFilters(filters) } @Test fun shouldNotFilter() { assertFalse( filterModel.shouldFilterStatus( mockStatus(content = "should not be filtered") ) ) } @Test fun shouldFilter_whenContentMatchesBadWord() { assertTrue( filterModel.shouldFilterStatus( mockStatus(content = "one two badWord three") ) ) } @Test fun shouldFilter_whenContentMatchesBadWordPart() { assertTrue( filterModel.shouldFilterStatus( mockStatus(content = "one two badWordPart three") ) ) } @Test fun shouldFilter_whenContentMatchesBadWholeWord() { assertTrue( filterModel.shouldFilterStatus( mockStatus(content = "one two badWholeWord three") ) ) } @Test fun shouldNotFilter_whenContentDoesNotMatchWholeWord() { assertFalse( filterModel.shouldFilterStatus( mockStatus(content = "one two badWholeWordTest three") ) ) } @Test fun shouldFilter_whenSpoilerTextDoesMatch() { assertTrue( filterModel.shouldFilterStatus( mockStatus( content = "should not be filtered", spoilerText = "badWord should be filtered" ) ) ) } @Test fun shouldFilter_whenPollTextDoesMatch() { assertTrue( filterModel.shouldFilterStatus( mockStatus( content = "should not be filtered", spoilerText = "should not be filtered", pollOptions = listOf("should not be filtered", "badWord") ) ) ) } @Test fun shouldFilter_whenMediaDescriptionDoesMatch() { assertTrue( filterModel.shouldFilterStatus( mockStatus( content = "should not be filtered", spoilerText = "should not be filtered", attachmentsDescriptions = listOf("should not be filtered", "badWord"), ) ) ) } @Test fun shouldFilterPartialWord_whenWholeWordFilterContainsNonAlphanumericCharacters() { assertTrue( filterModel.shouldFilterStatus( mockStatus(content = "one two someone@twitter.com three") ) ) } @Test fun shouldFilterHashtags() { assertTrue( filterModel.shouldFilterStatus( mockStatus(content = "#hashtag one two three") ) ) } @Test fun shouldFilterHashtags_whenContentIsMarkedUp() { assertTrue( filterModel.shouldFilterStatus( mockStatus(content = "

#hashtagone two three

") ) ) } @Test fun shouldNotFilterHtmlAttributes() { assertFalse( filterModel.shouldFilterStatus( mockStatus(content = "

https://foo.bar/ one two three

") ) ) } @Test fun shouldNotFilter_whenFilterIsExpired() { assertFalse( filterModel.shouldFilterStatus( mockStatus(content = "content matching expired filter should not be filtered") ) ) } @Test fun shouldFilter_whenFilterIsUnexpired() { assertTrue( filterModel.shouldFilterStatus( mockStatus(content = "content matching unexpired filter should be filtered") ) ) } @Test fun unchangedExpiration_shouldBeNegative_whenFilterIsExpired() { val expiredBySeconds = 3600 val expiredDate = Date.from(Instant.now().minusSeconds(expiredBySeconds.toLong())) val updatedDuration = getSecondsForDurationIndex(-1, null, expiredDate) assert(updatedDuration != null && updatedDuration <= -expiredBySeconds) } @Test fun unchangedExpiration_shouldBePositive_whenFilterIsUnexpired() { val expiresInSeconds = 3600 val expiredDate = Date.from(Instant.now().plusSeconds(expiresInSeconds.toLong())) val updatedDuration = getSecondsForDurationIndex(-1, null, expiredDate) assert(updatedDuration != null && updatedDuration > (expiresInSeconds - 60)) } companion object { fun mockStatus( content: String = "", spoilerText: String = "", pollOptions: List? = null, attachmentsDescriptions: List? = null ): Status { return Status( id = "123", url = "https://mastodon.social/@Tusky/100571663297225812", account = mock(), inReplyToId = null, inReplyToAccountId = null, reblog = null, content = content, createdAt = Date(), editedAt = null, emojis = emptyList(), reblogsCount = 0, favouritesCount = 0, repliesCount = 0, reblogged = false, favourited = false, bookmarked = false, sensitive = false, spoilerText = spoilerText, visibility = Status.Visibility.PUBLIC, attachments = if (attachmentsDescriptions != null) { ArrayList( attachmentsDescriptions.map { Attachment( id = "1234", url = "", previewUrl = null, meta = null, type = Attachment.Type.IMAGE, description = it, blurhash = null ) } ) } else arrayListOf(), mentions = listOf(), tags = listOf(), application = null, pinned = false, muted = false, poll = if (pollOptions != null) { Poll( id = "1234", expiresAt = null, expired = false, multiple = false, votesCount = 0, votersCount = 0, options = pollOptions.map { PollOption(it, 0) }, voted = false, ownVotes = null ) } else null, card = null, language = null, ) } } }