package net.pterodactylus.sone.core
import com.google.common.eventbus.EventBus
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KMutableProperty0
+import kotlin.reflect.KProperty
import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent
import net.pterodactylus.sone.core.event.StrictFilteringActivatedEvent
import net.pterodactylus.sone.core.event.StrictFilteringDeactivatedEvent
}
private val unsupported: Nothing get() = throw UnsupportedOperationException()
+
+fun Preferences.withDefaults() = object : Preferences by this {
+ override var insertionDelay: Int? by default(60, this@withDefaults::insertionDelay)
+ override var postsPerPage: Int? by default(10, this@withDefaults::postsPerPage)
+ override var imagesPerPage: Int? by default(9, this@withDefaults::imagesPerPage)
+ override var charactersPerPost: Int? by default(400, this@withDefaults::charactersPerPost)
+ override var postCutOffLength: Int? by default(200, this@withDefaults::postCutOffLength)
+ override var requireFullAccess: Boolean? by default(false, this@withDefaults::requireFullAccess)
+ override var fcpInterfaceActive: Boolean? by default(false, this@withDefaults::fcpInterfaceActive)
+ override var fcpFullAccessRequired: FullAccessRequired? by default(ALWAYS, this@withDefaults::fcpFullAccessRequired)
+ override var strictFiltering: Boolean? by default(false, this@withDefaults::strictFiltering)
+}
+
+fun Preferences.validated() = object : Preferences by this {
+ override var insertionDelay: Int? by validated(this@validated::insertionDelay) { v -> (v == null) || (v >= 0) }
+ override var postsPerPage: Int? by validated(this@validated::postsPerPage) { v -> (v == null) || (v >= 1) }
+ override var imagesPerPage: Int? by validated(this@validated::imagesPerPage) { v -> (v == null) || (v >= 1) }
+ override var charactersPerPost: Int? by validated(this@validated::charactersPerPost) { v -> (v == null) || (v == -1) || (v >= 50) }
+ override var postCutOffLength: Int? by validated(this@validated::postCutOffLength) { v -> (v == null) || (v >= 50) }
+}
+
+fun Preferences.withEvents(eventBus: EventBus) = object : Preferences by this {
+ override var insertionDelay: Int? by sendEvent(this@withEvents::insertionDelay) { eventBus.post(InsertionDelayChangedEvent(it!!)) }
+ override var fcpInterfaceActive: Boolean? by sendEvent(this@withEvents::fcpInterfaceActive) { eventBus.post(if (it!!) FcpInterfaceActivatedEvent() else FcpInterfaceDeactivatedEvent()) }
+ override var fcpFullAccessRequired: FullAccessRequired? by sendEvent(this@withEvents::fcpFullAccessRequired) { eventBus.post(FullAccessRequiredChanged(it!!)) }
+ override var strictFiltering: Boolean? by sendEvent(this@withEvents::strictFiltering) { eventBus.post(if (it!!) StrictFilteringActivatedEvent() else StrictFilteringDeactivatedEvent()) }
+}
+
+private fun <V> default(defaultValue: V, parent: KMutableProperty0<V?>) = object : ReadWriteProperty<Preferences, V?> {
+ override operator fun getValue(thisRef: Preferences, property: KProperty<*>) = parent.get() ?: defaultValue
+ override operator fun setValue(thisRef: Preferences, property: KProperty<*>, value: V?) {
+ parent.set(value)
+ }
+}
+
+private fun <V> validated(parent: KMutableProperty0<V?>, validator: (V?) -> Boolean) = object : ReadWriteProperty<Preferences, V?> {
+ override fun getValue(thisRef: Preferences, property: KProperty<*>) = parent.get()
+ override operator fun setValue(thisRef: Preferences, property: KProperty<*>, value: V?) {
+ if (!validator(value)) {
+ throw IllegalArgumentException("$value is not valid for ${property.name}")
+ }
+ parent.set(value)
+ }
+}
+
+private fun <V> sendEvent(parent: KMutableProperty0<V?>, valueConsumer: (V?) -> Unit) = object : ReadWriteProperty<Preferences, V?> {
+ override operator fun getValue(thisRef: Preferences, property: KProperty<*>) = parent.get()
+ override operator fun setValue(thisRef: Preferences, property: KProperty<*>, value: V?) {
+ parent.set(value)
+ valueConsumer(parent.get())
+ }
+}
import com.google.common.eventbus.EventBus
import com.google.common.eventbus.Subscribe
+import java.util.concurrent.atomic.AtomicBoolean
import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent
import net.pterodactylus.sone.core.event.StrictFilteringActivatedEvent
import net.pterodactylus.sone.core.event.StrictFilteringDeactivatedEvent
import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent
import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent
import net.pterodactylus.sone.fcp.event.FullAccessRequiredChanged
+import net.pterodactylus.sone.test.allNullPreferences
+import net.pterodactylus.sone.test.assertThrows
import net.pterodactylus.util.config.Configuration
import net.pterodactylus.util.config.MapConfigurationBackend
import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.contains
import org.hamcrest.Matchers.emptyIterable
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.hasItem
}
}
+
+class PreferencesTest {
+
+ @Test
+ fun `preferences with defaults return 60s post insertion delay if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().insertionDelay, equalTo(60))
+ }
+
+ @Test
+ fun `preferences with defaults return 10 posts per page if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().postsPerPage, equalTo(10))
+ }
+
+ @Test
+ fun `preferences with defaults return 9 images per page if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().imagesPerPage, equalTo(9))
+ }
+
+ @Test
+ fun `preferences with defaults return 400 characters per post if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().charactersPerPost, equalTo(400))
+ }
+
+ @Test
+ fun `preferences with defaults return a post cut off length of 200 if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().postCutOffLength, equalTo(200))
+ }
+
+ @Test
+ fun `preferences with defaults return false for require full access if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().requireFullAccess, equalTo(false))
+ }
+
+ @Test
+ fun `preferences with defaults return false for fcp interface active if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().fcpInterfaceActive, equalTo(false))
+ }
+
+ @Test
+ fun `preferences with defaults return always for fcp full access required if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().fcpFullAccessRequired, equalTo(ALWAYS))
+ }
+
+ @Test
+ fun `preferences with defaults return false for strict filtering if underlying preferences return null`() {
+ assertThat(testPreferences.withDefaults().strictFiltering, equalTo(false))
+ }
+
+ @Test
+ fun `preferences with validation refuse negative insertion delay`() {
+ assertThrows<IllegalArgumentException> { testPreferences.validated().insertionDelay = -1 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().insertionDelay = -17 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().insertionDelay = -38 }
+ }
+
+ @Test
+ fun `preferences with validation accept positive insertion delay`() {
+ testPreferences.validated().insertionDelay = 0
+ testPreferences.validated().insertionDelay = 1
+ testPreferences.validated().insertionDelay = 300
+ }
+
+ @Test
+ fun `preferences with validation accept null as insertion delay`() {
+ testPreferences.validated().insertionDelay = null
+ }
+
+ @Test
+ fun `preferences with validation refuse less than one post per page`() {
+ assertThrows<IllegalArgumentException> { testPreferences.validated().postsPerPage = 0 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().postsPerPage = -1 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().postsPerPage = -23 }
+ }
+
+ @Test
+ fun `preferences with validation accept one or more posts per page`() {
+ testPreferences.validated().postsPerPage = 1
+ testPreferences.validated().postsPerPage = 2
+ testPreferences.validated().postsPerPage = 432
+ }
+
+ @Test
+ fun `preferences with validation accept null as posts per page`() {
+ testPreferences.validated().postsPerPage = null
+ }
+
+ @Test
+ fun `preferences with validation refuse less than one image per page`() {
+ assertThrows<IllegalArgumentException> { testPreferences.validated().imagesPerPage = 0 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().imagesPerPage = -1 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().imagesPerPage = -71 }
+ }
+
+ @Test
+ fun `preferences with validation accept one or more images per page`() {
+ testPreferences.validated().imagesPerPage = 1
+ testPreferences.validated().imagesPerPage = 2
+ testPreferences.validated().imagesPerPage = 123
+ }
+
+ @Test
+ fun `preferences with validation accept null as images per page`() {
+ testPreferences.validated().imagesPerPage = null
+ }
+
+ @Test
+ fun `preferences with validation refuse less than 50 characters per post other than -1`() {
+ assertThrows<IllegalArgumentException> { testPreferences.validated().charactersPerPost = 49 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().charactersPerPost = 0 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().charactersPerPost = -2 }
+ }
+
+ @Test
+ fun `preferences with validation accept -1 or greater than or equal to 50 characters per post`() {
+ testPreferences.validated().charactersPerPost = -1
+ testPreferences.validated().charactersPerPost = 50
+ testPreferences.validated().charactersPerPost = 234
+ }
+
+ @Test
+ fun `preferences with validation accept null as characters per post`() {
+ testPreferences.validated().charactersPerPost = null
+ }
+
+ @Test
+ fun `preferences with validation refuse less than 50 characters post cut off length`() {
+ assertThrows<IllegalArgumentException> { testPreferences.validated().postCutOffLength = 49 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().postCutOffLength = 0 }
+ assertThrows<IllegalArgumentException> { testPreferences.validated().postCutOffLength = -1 }
+ }
+
+ @Test
+ fun `preferences with validation accept greater than or equal to 50 characters post cut off length`() {
+ testPreferences.validated().postCutOffLength = 50
+ testPreferences.validated().postCutOffLength = 135
+ testPreferences.validated().postCutOffLength = 234
+ }
+
+ @Test
+ fun `preferences with validation accept null as characters post cut off length`() {
+ testPreferences.validated().postCutOffLength = null
+ }
+
+ @Test
+ fun `preferences with validation allows all booleans for require full access`() {
+ testPreferences.validated().requireFullAccess = false
+ testPreferences.validated().requireFullAccess = true
+ }
+
+ @Test
+ fun `preferences with validation accept null as require full access`() {
+ testPreferences.validated().requireFullAccess = null
+ }
+
+ @Test
+ fun `preferences with validation allows all booleans for fcp interface active`() {
+ testPreferences.validated().fcpInterfaceActive = false
+ testPreferences.validated().fcpInterfaceActive = true
+ }
+
+ @Test
+ fun `preferences with validation accept null as fcp interface active`() {
+ testPreferences.validated().fcpInterfaceActive = null
+ }
+
+ @Test
+ fun `preferences with validation allows all enum values for fcp full access required`() {
+ testPreferences.validated().fcpFullAccessRequired = NO
+ testPreferences.validated().fcpFullAccessRequired = WRITING
+ testPreferences.validated().fcpFullAccessRequired = ALWAYS
+ }
+
+ @Test
+ fun `preferences with validation accept null as fcp full access required`() {
+ testPreferences.validated().fcpFullAccessRequired = null
+ }
+
+ @Test
+ fun `preferences with validation allows all booleans for strict filtering`() {
+ testPreferences.validated().strictFiltering = false
+ testPreferences.validated().strictFiltering = true
+ }
+
+ @Test
+ fun `preferences with validation accept null as strict filtering`() {
+ testPreferences.validated().strictFiltering = null
+ }
+
+ @Test
+ fun `preferences with events send insertion delay changed event when insertion delay is changed`() {
+ val newInsertionDelay = mutableListOf<Int?>()
+ eventBus.register(object {
+ @Subscribe
+ fun onInsertionDelayChanged(event: InsertionDelayChangedEvent) {
+ newInsertionDelay += event.insertionDelay
+ }
+ })
+ testPreferences.withEvents(eventBus).insertionDelay = 123
+ assertThat(newInsertionDelay, contains(123))
+ }
+
+ @Test
+ fun `preferences with defaults and events send default insertion delay changed event when insertion delay is changed to null`() {
+ val newInsertionDelay = mutableListOf<Int?>()
+ eventBus.register(object {
+ @Subscribe
+ fun onInsertionDelayChanged(event: InsertionDelayChangedEvent) {
+ newInsertionDelay += event.insertionDelay
+ }
+ })
+ testPreferences.withDefaults().withEvents(eventBus).insertionDelay = null
+ assertThat(newInsertionDelay, contains(60))
+ }
+
+ @Test
+ fun `preferences with events send fcp interface activated event when fcp interface active is set to true`() {
+ val called = AtomicBoolean(false)
+ eventBus.register(object {
+ @Subscribe
+ fun onFcpInterfaceActivated(event: FcpInterfaceActivatedEvent) = called.set(true)
+ })
+ testPreferences.withEvents(eventBus).fcpInterfaceActive = true
+ assertThat(called.get(), equalTo(true))
+ }
+
+ @Test
+ fun `preferences with events send fcp interface deactivated event when fcp interface active is set to false`() {
+ val called = AtomicBoolean(false)
+ eventBus.register(object {
+ @Subscribe
+ fun onFcpInterfaceDeactivated(event: FcpInterfaceDeactivatedEvent) = called.set(true)
+ })
+ testPreferences.withEvents(eventBus).fcpInterfaceActive = false
+ assertThat(called.get(), equalTo(true))
+ }
+
+ @Test
+ fun `preferences with events send fcp full access required changed event when fcp full access required is changed`() {
+ val fcpFullAccessRequiredValues = mutableListOf<FullAccessRequired>()
+ eventBus.register(object {
+ @Subscribe
+ fun onFcpFullAccessRequiredChanged(event: FullAccessRequiredChanged) {
+ fcpFullAccessRequiredValues += event.fullAccessRequired
+ }
+ })
+ testPreferences.withEvents(eventBus).apply {
+ fcpFullAccessRequired = WRITING
+ fcpFullAccessRequired = ALWAYS
+ fcpFullAccessRequired = NO
+ }
+ assertThat(fcpFullAccessRequiredValues, contains(WRITING, ALWAYS, NO))
+ }
+
+ @Test
+ fun `preferences with events send strict filtering activated event when strict filtering is set to true`() {
+ val called = AtomicBoolean(false)
+ eventBus.register(object {
+ @Subscribe
+ fun onStrictFilteringActivated(event: StrictFilteringActivatedEvent) = called.set(true)
+ })
+ testPreferences.withEvents(eventBus).strictFiltering = true
+ assertThat(called.get(), equalTo(true))
+ }
+
+ @Test
+ fun `preferences with events send strict filtering deactivated event when strict filtering is set to false`() {
+ val called = AtomicBoolean(false)
+ eventBus.register(object {
+ @Subscribe
+ fun onStrictFilteringDeactivated(event: StrictFilteringDeactivatedEvent) = called.set(true)
+ })
+ testPreferences.withEvents(eventBus).strictFiltering = false
+ assertThat(called.get(), equalTo(true))
+ }
+
+ private val eventBus = EventBus()
+ private val testPreferences = allNullPreferences()
+
+}