Use a single state and trigger for weather services
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 29 May 2016 18:00:09 +0000 (20:00 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 29 May 2016 18:00:09 +0000 (20:00 +0200)
18 files changed:
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherState.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherTrigger.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComFilter.kt
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComHamburgWatcher.kt
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComState.kt [deleted file]
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComTrigger.kt [deleted file]
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeFilter.kt
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeHamburgWatcher.kt
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeState.kt [deleted file]
src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTrigger.kt [deleted file]
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherStateTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherTriggerTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComFilterTest.kt
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComStateTest.kt [deleted file]
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComTriggerTest.kt [deleted file]
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeFilterTest.kt
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeStateTest.kt [deleted file]
src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTriggerTest.kt [deleted file]

diff --git a/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherState.kt b/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherState.kt
new file mode 100644 (file)
index 0000000..dff71d7
--- /dev/null
@@ -0,0 +1,40 @@
+package net.pterodactylus.rhynodge.webpages.weather
+
+import com.fasterxml.jackson.annotation.JsonGetter
+import com.fasterxml.jackson.annotation.JsonProperty
+import net.pterodactylus.rhynodge.states.AbstractState
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+
+/**
+ * Contains a weather state.
+ *
+ * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
+ */
+class WeatherState(val service: String, val dateTime: ZonedDateTime) : AbstractState(true), Iterable<HourState> {
+
+    constructor(@JsonProperty("service") service: String, @JsonProperty("dateTime") time: Long) :
+    this(service, Instant.ofEpochMilli(time).atZone(ZoneId.of("Europe/Berlin")))
+
+    @JsonProperty("hours")
+    val hours: List<HourState> = mutableListOf()
+
+    val timeMillis: Long
+        @JsonGetter("dateTime")
+        get() = dateTime.toInstant().toEpochMilli()
+
+    operator fun plusAssign(hourState: HourState) {
+        (hours as MutableList<HourState>).add(hourState)
+    }
+
+    override fun iterator(): Iterator<HourState> {
+        return hours.iterator()
+    }
+
+    override fun equals(other: Any?): Boolean {
+        other as? WeatherState ?: return false
+        return (dateTime == other.dateTime) and (hours == other.hours)
+    }
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherTrigger.kt b/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherTrigger.kt
new file mode 100644 (file)
index 0000000..4bc46d4
--- /dev/null
@@ -0,0 +1,90 @@
+package net.pterodactylus.rhynodge.webpages.weather
+
+import kotlinx.html.body
+import kotlinx.html.div
+import kotlinx.html.h1
+import kotlinx.html.head
+import kotlinx.html.html
+import kotlinx.html.img
+import kotlinx.html.stream.createHTML
+import kotlinx.html.style
+import kotlinx.html.unsafe
+import net.pterodactylus.rhynodge.Reaction
+import net.pterodactylus.rhynodge.State
+import net.pterodactylus.rhynodge.Trigger
+import net.pterodactylus.rhynodge.output.DefaultOutput
+import net.pterodactylus.rhynodge.output.Output
+import java.text.DateFormat
+import java.time.temporal.ChronoUnit
+import java.util.Locale
+
+/**
+ * Detects changes in the weather and creates email texts.
+ *
+ * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
+ */
+class WeatherTrigger : Trigger {
+
+    private val dateFormatter = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH)
+    private lateinit var state: WeatherState
+    private var changed = false
+
+    override fun mergeStates(previousState: State, currentState: State): State {
+        changed = previousState != currentState
+        state = currentState as WeatherState
+        return currentState
+    }
+
+    override fun triggers(): Boolean {
+        return changed
+    }
+
+    override fun output(reaction: Reaction): Output {
+        val output = DefaultOutput("The Weather (according to ${state.service}) on ${dateFormatter.format(state.dateTime.toInstant().toEpochMilli())}")
+        output.addText("text/html", generateHtmlOutput())
+        return output
+    }
+
+    private fun generateHtmlOutput(): String {
+        return createHTML().html {
+            head {
+                style("text/css") {
+                    unsafe {
+                        +".hour-state { display: table-row; } "
+                        +".hour-state > div { display: table-cell; padding-right: 1em; } "
+                    }
+                }
+            }
+            body {
+                val startTime = state.dateTime.toInstant()
+                h1 { +"The Weather (according to wetter.de) on %s".format(dateFormatter.format(startTime.toEpochMilli())) }
+                state.forEach {
+                    div("hour-state") {
+                        div("time") { +"%tH:%<tM".format(startTime.plus(it.hourIndex.toLong(), ChronoUnit.HOURS).toEpochMilli()) }
+                        div("temperature") { +"%d °C".format(it.temperature) }
+                        it.feltTemperature?.let {
+                            div("felt-temperature") { +"(%d °C)".format(it) }
+                        }
+                        div("rain-probability") { +"%d%%".format((it.rainProbability * 100).toInt()) }
+                        div("rain-amount") { +"%s l/m²".format(it.rainAmount.minDigits()) }
+                        div("wind-direction") { +it.windDirection.arrow }
+                        div("wind-speed") { +"%d km/h".format(it.windSpeed) }
+                        it.gustSpeed?.let {
+                            div("gust-speed") { +"(up to %d km/h)".format(it) }
+                        }
+                        it.humidity?.let {
+                            div("humidity") { +"%d%%".format((it * 100).toInt()) }
+                        }
+                        div("description") { +it.description }
+                        div("image") { img(src = it.image) }
+                    }
+                }
+            }
+        }.toString()
+    }
+
+    private fun Double.minDigits(): String {
+        return this.toString().replace(Regex("\\.0*$"), "")
+    }
+
+}
index d6e1a08..9f25334 100644 (file)
@@ -5,6 +5,7 @@ import net.pterodactylus.rhynodge.State
 import net.pterodactylus.rhynodge.states.FailedState
 import net.pterodactylus.rhynodge.states.HtmlState
 import net.pterodactylus.rhynodge.webpages.weather.HourState
+import net.pterodactylus.rhynodge.webpages.weather.WeatherState
 import net.pterodactylus.rhynodge.webpages.weather.WindDirection
 import net.pterodactylus.rhynodge.webpages.weather.toWindDirection
 import org.jsoup.nodes.Document
@@ -36,7 +37,7 @@ class WetterComFilter : Filter {
 
     private fun parseWetterComState(state: HtmlState): State {
         val dateTime = parseDateTime(state.document()) ?: return FailedState(IllegalArgumentException("no date present"))
-        val wetterComState = WetterComState(dateTime)
+        val wetterComState = WeatherState("wetter.com", dateTime)
         parseHourStates(state.document()).forEach { wetterComState += it }
         return wetterComState
     }
index 5b73d01..78da5b0 100644 (file)
@@ -3,6 +3,7 @@ package net.pterodactylus.rhynodge.webpages.weather.wettercom
 import net.pterodactylus.rhynodge.filters.HtmlFilter
 import net.pterodactylus.rhynodge.queries.HttpQuery
 import net.pterodactylus.rhynodge.watchers.DefaultWatcher
+import net.pterodactylus.rhynodge.webpages.weather.WeatherTrigger
 
 /**
  * [DefaultWatcher] implementation that gets the weather for Hamburg.
@@ -12,5 +13,5 @@ import net.pterodactylus.rhynodge.watchers.DefaultWatcher
 class WetterComHamburgWatcher : DefaultWatcher(
         HttpQuery("http://www.wetter.com/wetter_aktuell/wettervorhersage/heute/deutschland/hamburg/DE0004130.html"),
         listOf(HtmlFilter(), WetterComFilter()),
-        WetterComTrigger()
+        WeatherTrigger()
 )
diff --git a/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComState.kt b/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComState.kt
deleted file mode 100644 (file)
index e6c2b5e..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package net.pterodactylus.rhynodge.webpages.weather.wettercom
-
-import com.fasterxml.jackson.annotation.JsonGetter
-import com.fasterxml.jackson.annotation.JsonProperty
-import net.pterodactylus.rhynodge.states.AbstractState
-import net.pterodactylus.rhynodge.webpages.weather.HourState
-import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
-
-/**
- * Contains the state parsed from [wetter.com](https://www.wetter.com/).
- *
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
- */
-class WetterComState(val dateTime: ZonedDateTime) : AbstractState(true), Iterable<HourState> {
-
-    constructor(@JsonProperty("dateTime") time: Long) :
-    this(Instant.ofEpochMilli(time).atZone(ZoneId.of("Europe/Berlin")))
-
-    @JsonProperty("hours")
-    val hours: List<HourState> = mutableListOf()
-
-    val timeMillis: Long
-        @JsonGetter("dateTime")
-        get() = dateTime.toInstant().toEpochMilli()
-
-    operator fun plusAssign(hourState: HourState) {
-        (hours as MutableList<HourState>).add(hourState)
-    }
-
-    override fun iterator(): Iterator<HourState> {
-        return hours.iterator()
-    }
-
-    override fun equals(other: Any?): Boolean {
-        other as? WetterComState ?: return false
-        return (dateTime == other.dateTime) and (hours == other.hours)
-    }
-
-}
diff --git a/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComTrigger.kt b/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComTrigger.kt
deleted file mode 100644 (file)
index 7a9e7f0..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package net.pterodactylus.rhynodge.webpages.weather.wettercom
-
-import kotlinx.html.body
-import kotlinx.html.div
-import kotlinx.html.h1
-import kotlinx.html.head
-import kotlinx.html.html
-import kotlinx.html.img
-import kotlinx.html.stream.createHTML
-import kotlinx.html.style
-import kotlinx.html.unsafe
-import net.pterodactylus.rhynodge.Reaction
-import net.pterodactylus.rhynodge.State
-import net.pterodactylus.rhynodge.Trigger
-import net.pterodactylus.rhynodge.output.DefaultOutput
-import net.pterodactylus.rhynodge.output.Output
-import java.text.DateFormat
-import java.time.temporal.ChronoUnit.HOURS
-import java.util.Locale
-
-/**
- * TODO
- *
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
- */
-class WetterComTrigger : Trigger {
-
-    private val dateFormatter = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH)
-    private var newState = false
-    private lateinit var currentState: WetterComState
-
-    override fun mergeStates(previousState: State, currentState: State): State? {
-        newState = previousState != currentState
-        this.currentState = currentState as WetterComState
-        return currentState
-    }
-
-    override fun triggers(): Boolean {
-        return newState
-    }
-
-    override fun output(reaction: Reaction): Output {
-        val output = DefaultOutput("The weather (according to wetter.com) on %s".format(dateFormatter.format(currentState.dateTime.toInstant().toEpochMilli())))
-        output.addText("text/html", htmlOutput())
-        return output
-    }
-
-    private fun htmlOutput(): String {
-        return createHTML().html {
-            head {
-                style("text/css") {
-                    unsafe {
-                        +".hour-state { display: table-row; } "
-                        +".hour-state > div { display: table-cell; padding-right: 1em; } "
-                    }
-                }
-            }
-            body {
-                val startTime = currentState.dateTime.toInstant()
-                h1 { +"The Weather (according to wetter.com) on %s".format(dateFormatter.format(startTime.toEpochMilli())) }
-                currentState.forEach {
-                    div("hour-state") {
-                        div("time") { +"%tH:%<tM".format(startTime.plus(it.hourIndex.toLong(), HOURS).toEpochMilli()) }
-                        div("temperature") { +"%d °C".format(it.temperature.toInt()) }
-                        div("rain-probability") { +"%d%%".format((it.rainProbability * 100).toInt()) }
-                        div("rain-amount") { +"%d l/m²".format(it.rainAmount.toInt()) }
-                        div("wind-direction") { +it.windDirection.arrow }
-                        div("wind-speed") { +"%d km/h".format(it.windSpeed.toInt()) }
-                        div("description") { +it.description }
-                        div("image") { img(src = it.image) }
-                    }
-                }
-            }
-        }.toString()
-    }
-
-}
index 245a8a4..fc3f500 100644 (file)
@@ -5,6 +5,7 @@ import net.pterodactylus.rhynodge.State
 import net.pterodactylus.rhynodge.states.FailedState
 import net.pterodactylus.rhynodge.states.HtmlState
 import net.pterodactylus.rhynodge.webpages.weather.HourState
+import net.pterodactylus.rhynodge.webpages.weather.WeatherState
 import net.pterodactylus.rhynodge.webpages.weather.WindDirection
 import net.pterodactylus.rhynodge.webpages.weather.toWindDirection
 import org.jsoup.nodes.Document
@@ -37,9 +38,9 @@ class WetterDeFilter : Filter {
         return parseWetterDeState(state)
     }
 
-    private fun parseWetterDeState(htmlState: HtmlState): WetterDeState {
+    private fun parseWetterDeState(htmlState: HtmlState): WeatherState {
         val dateTime = parseDate(htmlState.document()) ?: throw IllegalArgumentException("date can not be parsed")
-        val wetterDeState = WetterDeState(dateTime)
+        val wetterDeState = WeatherState("wetter.de", dateTime)
         parseHourStates(htmlState.document()).forEach { wetterDeState += it }
         return wetterDeState
     }
index 503d7e9..33df56c 100644 (file)
@@ -3,6 +3,7 @@ package net.pterodactylus.rhynodge.webpages.weather.wetterde
 import net.pterodactylus.rhynodge.filters.HtmlFilter
 import net.pterodactylus.rhynodge.queries.HttpQuery
 import net.pterodactylus.rhynodge.watchers.DefaultWatcher
+import net.pterodactylus.rhynodge.webpages.weather.WeatherTrigger
 
 /**
  * [DefaultWatcher] implementation that gets the weather for Hamburg.
@@ -12,5 +13,5 @@ import net.pterodactylus.rhynodge.watchers.DefaultWatcher
 class WetterDeHamburgWatcher : DefaultWatcher(
         HttpQuery("http://www.wetter.de/deutschland/wetter-hamburg-18219464/wetterbericht-aktuell.html"),
         listOf(HtmlFilter(), WetterDeFilter()),
-        WetterDeTrigger()
+        WeatherTrigger()
 )
diff --git a/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeState.kt b/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeState.kt
deleted file mode 100644 (file)
index 74b7ef0..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package net.pterodactylus.rhynodge.webpages.weather.wetterde
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import net.pterodactylus.rhynodge.states.AbstractState
-import net.pterodactylus.rhynodge.webpages.weather.HourState
-import java.time.Instant
-import java.time.ZoneId.of
-import java.time.ZonedDateTime
-
-/**
- * Contains the state parsed from [wetter.de](https://www.wetter.de/).
- *
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
- */
-class WetterDeState(val dateTime: ZonedDateTime) : AbstractState(true), Iterable<HourState> {
-
-    constructor(@JsonProperty("dateTime") timeMillis: Long) : this(Instant.ofEpochMilli(timeMillis).atZone(of("Europe/Berlin")))
-
-    val hours: List<HourState> = mutableListOf()
-
-    val timeMillis: Long
-        @JsonProperty("dateTime") get() {
-            return dateTime.toInstant().toEpochMilli()
-        }
-
-    override fun iterator(): Iterator<HourState> = hours.iterator()
-
-    operator fun plusAssign(hourState: HourState) {
-        (hours as MutableList).add(hourState)
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (other !is WetterDeState) {
-            return false
-        }
-        return (dateTime == other.dateTime) and (hours == other.hours)
-    }
-
-}
diff --git a/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTrigger.kt b/src/main/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTrigger.kt
deleted file mode 100644 (file)
index 178fce4..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-package net.pterodactylus.rhynodge.webpages.weather.wetterde
-
-import kotlinx.html.body
-import kotlinx.html.div
-import kotlinx.html.h1
-import kotlinx.html.head
-import kotlinx.html.html
-import kotlinx.html.img
-import kotlinx.html.stream.createHTML
-import kotlinx.html.style
-import kotlinx.html.unsafe
-import net.pterodactylus.rhynodge.Reaction
-import net.pterodactylus.rhynodge.State
-import net.pterodactylus.rhynodge.Trigger
-import net.pterodactylus.rhynodge.output.DefaultOutput
-import net.pterodactylus.rhynodge.output.Output
-import java.text.DateFormat
-import java.time.temporal.ChronoUnit
-import java.util.Locale
-
-/**
- * TODO
- *
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
- */
-class WetterDeTrigger : Trigger {
-
-    private val dateFormatter = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH)
-    private lateinit var state: WetterDeState
-    private var changed = false
-
-    override fun mergeStates(previousState: State, currentState: State): State {
-        changed = previousState != currentState
-        state = currentState as WetterDeState
-        return currentState
-    }
-
-    override fun triggers(): Boolean {
-        return changed
-    }
-
-    override fun output(reaction: Reaction): Output {
-        val output = DefaultOutput("The Weather (according to wetter.de) on %s".format(dateFormatter.format(state.dateTime.toInstant().toEpochMilli())))
-        output.addText("text/html", generateHtmlOutput())
-        return output
-    }
-
-    private fun generateHtmlOutput(): String {
-        return createHTML().html {
-            head {
-                style("text/css") {
-                    unsafe {
-                        +".hour-state { display: table-row; } "
-                        +".hour-state > div { display: table-cell; padding-right: 1em; } "
-                    }
-                }
-            }
-            body {
-                val startTime = state.dateTime.toInstant()
-                h1 { +"The Weather (according to wetter.de) on %s".format(dateFormatter.format(startTime.toEpochMilli())) }
-                state.forEach {
-                    div("hour-state") {
-                        div("time") { +"%tH:%<tM".format(startTime.plus(it.hourIndex.toLong(), ChronoUnit.HOURS).toEpochMilli()) }
-                        div("temperature") { +"%d °C".format(it.temperature) }
-                        it.feltTemperature?.let {
-                            div("felt-temperature") { +"(%d °C)".format(it) }
-                        }
-                        div("rain-probability") { +"%d%%".format((it.rainProbability * 100).toInt()) }
-                        div("rain-amount") { +"%s l/m²".format(it.rainAmount.minDigits()) }
-                        div("wind-direction") { +it.windDirection.arrow }
-                        div("wind-speed") { +"%d km/h".format(it.windSpeed) }
-                        it.gustSpeed?.let {
-                            div("gust-speed") { +"(up to %d km/h)".format(it) }
-                        }
-                        it.humidity?.let {
-                            div("humidity") { +"%d%%".format((it * 100).toInt()) }
-                        }
-                        div("description") { +it.description }
-                        div("image") { img(src = it.image) }
-                    }
-                }
-            }
-        }.toString()
-    }
-
-    private fun Double.minDigits(): String {
-        return this.toString().replace(Regex("\\.0*$"), "")
-    }
-
-}
diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherStateTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherStateTest.kt
new file mode 100644 (file)
index 0000000..75db972
--- /dev/null
@@ -0,0 +1,70 @@
+package net.pterodactylus.rhynodge.webpages.weather
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.`is`
+import org.junit.Test
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+
+/**
+ * Unit test for [WeatherState].
+
+ * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
+ */
+const val SERVICE_NAME = "Weather Service"
+
+class WeatherStateTest {
+
+    private val now = Instant.now().atZone(ZoneId.of("Europe/Berlin"))
+
+    @Test
+    fun stateRetainsServiceName() {
+        val state = WeatherState(SERVICE_NAME, now)
+        assertThat(state.service, `is`(SERVICE_NAME))
+    }
+
+    @Test
+    fun statesWithoutHoursEqualOneAnother() {
+        val now = now
+        val firstState = WeatherState(SERVICE_NAME, ZonedDateTime.from(now))
+        val secondState = WeatherState(SERVICE_NAME, ZonedDateTime.from(now))
+        assertThat(firstState, `is`(secondState))
+    }
+
+    @Test
+    fun statesWithTheSameHoursAreEqual() {
+        val now = now
+        val firstState = WeatherState(SERVICE_NAME, ZonedDateTime.from(now))
+        firstState += HourState(0, 10, null, 0.05, 0.0, WindDirection.NORTH, 5, null, null, "Fine", "http://1")
+        firstState += HourState(1, 12, null, 0.1, 2.0, WindDirection.WEST, 8, null, null, "Superb", "http://2")
+        val secondState = WeatherState(SERVICE_NAME, ZonedDateTime.from(now))
+        secondState += HourState(0, 10, null, 0.05, 0.0, WindDirection.NORTH, 5, null, null, "Fine", "http://1")
+        secondState += HourState(1, 12, null, 0.1, 2.0, WindDirection.WEST, 8, null, null, "Superb", "http://2")
+        assertThat(firstState, `is`(secondState))
+    }
+
+    @Test
+    fun iteratingDeliversHourStates() {
+        val now = now
+        val firstState = WeatherState(SERVICE_NAME, ZonedDateTime.from(now))
+        firstState += HourState(0, 10, null, 0.05, 0.0, WindDirection.NORTH, 5, null, null, "Fine", "http://1")
+        firstState += HourState(1, 12, null, 0.1, 2.0, WindDirection.WEST, 8, null, null, "Superb", "http://2")
+        assertThat(firstState.iterator().asSequence().toList(), `is`(firstState.hours as Iterable<HourState>))
+    }
+
+    @Test
+    fun stateIsSerializableAsJson() {
+        val objectMapper = ObjectMapper()
+        val now = now
+        val originalState = WeatherState(SERVICE_NAME, ZonedDateTime.from(now))
+        originalState += HourState(0, 10, null, 0.05, 0.0, WindDirection.NORTH, 5, null, null, "Fine", "http://1")
+        originalState += HourState(1, 12, null, 0.1, 2.0, WindDirection.WEST, 8, null, null, "Superb", "http://2")
+        val json = objectMapper.writeValueAsString(originalState)
+        println(json)
+        val parsedState = objectMapper.readValue(json, WeatherState::class.java)
+        assertThat(parsedState, `is`(originalState))
+    }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherTriggerTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/WeatherTriggerTest.kt
new file mode 100644 (file)
index 0000000..48dfe89
--- /dev/null
@@ -0,0 +1,88 @@
+package net.pterodactylus.rhynodge.webpages.weather
+
+import net.pterodactylus.rhynodge.Reaction
+import net.pterodactylus.rhynodge.State
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.`is`
+import org.hamcrest.Matchers.containsString
+import org.junit.Test
+import org.mockito.Mockito.mock
+import java.io.File
+import java.time.ZoneId.of
+import java.time.ZonedDateTime
+
+/**
+ * Unit test for [WeatherTrigger].
+
+ * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
+ */
+class WeatherTriggerTest {
+
+    private val now = ZonedDateTime.now(of("Europe/Berlin"))
+    private val previousState = WeatherState("Weather", now.minusDays(1))
+    private val trigger = WeatherTrigger()
+
+    @Test
+    fun currentStateIsAlwaysReturned() {
+        val currentState = WeatherState("Weather", now)
+        assertThat(trigger.mergeStates(previousState, currentState), `is`(currentState as State))
+    }
+
+    @Test
+    fun triggerDoesNotTriggerIfStateHasNotChanged() {
+        val currentState = WeatherState("Weather", now.minusDays(1))
+        trigger.mergeStates(previousState, currentState)
+        assertThat(trigger.triggers(), `is`(false))
+    }
+
+    @Test
+    fun triggerDoesTriggerIfStateHasChanged() {
+        val currentState = WeatherState("Weather", now)
+        trigger.mergeStates(previousState, currentState)
+        assertThat(trigger.triggers(), `is`(true))
+    }
+
+    @Test
+    fun outputContainsCorrectSummary() {
+        val currentState = WeatherState("Weather", ZonedDateTime.of(2016, 5, 28, 0, 0, 0, 0, of("Europe/Berlin")))
+        trigger.mergeStates(previousState, currentState)
+        val reaction = mock(Reaction::class.java)
+        val output = trigger.output(reaction)
+        assertThat(output.summary(), `is`("The Weather (according to Weather) on May 28, 2016"))
+    }
+
+    @Test
+    fun outputContainsCorrectHourData() {
+        val currentState = WeatherState("Weather", ZonedDateTime.of(2016, 5, 28, 0, 0, 0, 0, of("Europe/Berlin")))
+        currentState += HourState(0, 10, 11, 0.12, 13.0, WindDirection.SOUTHSOUTHEAST, 14, 15, 0.16, "17", "http://18")
+        currentState += HourState(1, 20, 21, 0.22, 23.0, WindDirection.NORTHNORTHWEST, 24, 25, 0.26, "27", "http://28")
+        trigger.mergeStates(previousState, currentState)
+        val reaction = mock(Reaction::class.java)
+        val output = trigger.output(reaction)
+        val htmlText = output.text("text/html", -1)
+        File("/tmp/wetter.html").writer().use { it.write(htmlText) }
+        assertThat(htmlText, containsString("00:00"))
+        assertThat(htmlText, containsString("10 °C"))
+        assertThat(htmlText, containsString("(11 °C)"))
+        assertThat(htmlText, containsString("12%"))
+        assertThat(htmlText, containsString("13 l/m²"))
+        assertThat(htmlText, containsString("↖↑"))
+        assertThat(htmlText, containsString("14 km/h"))
+        assertThat(htmlText, containsString("15 km/h"))
+        assertThat(htmlText, containsString("16%"))
+        assertThat(htmlText, containsString("17"))
+        assertThat(htmlText, containsString("http://18"))
+        assertThat(htmlText, containsString("01:00"))
+        assertThat(htmlText, containsString("20 °C"))
+        assertThat(htmlText, containsString("(21 °C)"))
+        assertThat(htmlText, containsString("22%"))
+        assertThat(htmlText, containsString("23 l/m²"))
+        assertThat(htmlText, containsString("↘↓"))
+        assertThat(htmlText, containsString("24 km/h"))
+        assertThat(htmlText, containsString("25 km/h"))
+        assertThat(htmlText, containsString("26%"))
+        assertThat(htmlText, containsString("27"))
+        assertThat(htmlText, containsString("http://28"))
+    }
+
+}
index 8f28c0b..c58656f 100644 (file)
@@ -5,6 +5,7 @@ import net.pterodactylus.rhynodge.filters.ResourceLoader.loadDocument
 import net.pterodactylus.rhynodge.states.FailedState
 import net.pterodactylus.rhynodge.states.HtmlState
 import net.pterodactylus.rhynodge.webpages.weather.HourState
+import net.pterodactylus.rhynodge.webpages.weather.WeatherState
 import net.pterodactylus.rhynodge.webpages.weather.WindDirection
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.`is`
@@ -51,7 +52,7 @@ class WetterComFilterTest {
     fun filterParsesHtmlCorrectly() {
         val document = loadDocument(javaClass, "wetter.com.html", url)
         val htmlState = HtmlState(url, document)
-        val newState = filter.filter(htmlState) as WetterComState
+        val newState = filter.filter(htmlState) as WeatherState
         assertThat(newState.dateTime, `is`(LocalDateTime.of(2016, Month.MAY, 23, 5, 0).atZone(ZoneId.of("Europe/Berlin"))))
         assertThat(newState.hours, contains(
                 HourState(0, 15, null, 0.65, 0.8, WindDirection.NORTH, 5, null, null, "leichter Regen-schauer", "http://ls1.wettercomassets.com/wcomv5/images/icons/small/d_80_S.png?201605201518"),
diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComStateTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComStateTest.kt
deleted file mode 100644 (file)
index d32e563..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package net.pterodactylus.rhynodge.webpages.weather.wettercom
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import net.pterodactylus.rhynodge.webpages.weather.HourState
-import net.pterodactylus.rhynodge.webpages.weather.WindDirection
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
-import org.junit.Test
-import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
-
-/**
- * Unit test for [WetterComState].
-
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
- */
-class WetterComStateTest {
-
-    @Test
-    fun statesWithoutHoursEqualOneAnother() {
-        val now = Instant.now().atZone(ZoneId.of("Europe/Berlin"))
-        val firstState = WetterComState(ZonedDateTime.from(now))
-        val secondState = WetterComState(ZonedDateTime.from(now))
-        assertThat(firstState, `is`(secondState))
-    }
-
-    @Test
-    fun statesWithTheSameHoursAreEqual() {
-        val now = Instant.now().atZone(ZoneId.of("Europe/Berlin"))
-        val firstState = WetterComState(ZonedDateTime.from(now))
-        firstState += HourState(0, 10, null, 0.05, 0.0, WindDirection.NORTH, 5, null, null, "Fine", "http://1")
-        firstState += HourState(1, 12, null, 0.1, 2.0, WindDirection.WEST, 8, null, null, "Superb", "http://2")
-        val secondState = WetterComState(ZonedDateTime.from(now))
-        secondState += HourState(0, 10, null, 0.05, 0.0, WindDirection.NORTH, 5, null, null, "Fine", "http://1")
-        secondState += HourState(1, 12, null, 0.1, 2.0, WindDirection.WEST, 8, null, null, "Superb", "http://2")
-        assertThat(firstState, `is`(secondState))
-    }
-
-    @Test
-    fun iteratingDeliversHourStates() {
-        val now = Instant.now().atZone(ZoneId.of("Europe/Berlin"))
-        val firstState = WetterComState(ZonedDateTime.from(now))
-        firstState += HourState(0, 10, null, 0.05, 0.0, WindDirection.NORTH, 5, null, null, "Fine", "http://1")
-        firstState += HourState(1, 12, null, 0.1, 2.0, WindDirection.WEST, 8, null, null, "Superb", "http://2")
-        assertThat(firstState.iterator().asSequence().toList(), `is`(firstState.hours as Iterable<HourState>))
-    }
-
-    @Test
-    fun stateIsSerializableAsJson() {
-        val objectMapper = ObjectMapper()
-        val now = Instant.now().atZone(ZoneId.of("Europe/Berlin"))
-        val originalState = WetterComState(ZonedDateTime.from(now))
-        originalState += HourState(0, 10, null, 0.05, 0.0, WindDirection.NORTH, 5, null, null, "Fine", "http://1")
-        originalState += HourState(1, 12, null, 0.1, 2.0, WindDirection.WEST, 8, null, null, "Superb", "http://2")
-        val json = objectMapper.writeValueAsString(originalState)
-        println(json)
-        val parsedState = objectMapper.readValue(json, WetterComState::class.java)
-        assertThat(parsedState, `is`(originalState))
-    }
-
-}
diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComTriggerTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wettercom/WetterComTriggerTest.kt
deleted file mode 100644 (file)
index aa69a07..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-package net.pterodactylus.rhynodge.webpages.weather.wettercom
-
-import net.pterodactylus.rhynodge.Reaction
-import net.pterodactylus.rhynodge.webpages.weather.HourState
-import net.pterodactylus.rhynodge.webpages.weather.WindDirection
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
-import org.hamcrest.Matchers.containsString
-import org.junit.Test
-import org.mockito.Mockito
-import java.time.ZoneId
-import java.time.ZonedDateTime
-
-/**
- * Unit test for [WetterComTrigger].
-
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
- */
-class WetterComTriggerTest {
-
-    private val trigger = WetterComTrigger()
-    private val now = ZonedDateTime.now()
-    private val previousState = WetterComState(now)
-
-    @Test
-    fun equalStatesAreNotMerged() {
-        val currentState = WetterComState(now)
-        val newState = trigger.mergeStates(previousState, currentState) as WetterComState
-        assertThat(newState, `is`(previousState))
-        assertThat(newState, `is`(currentState))
-    }
-
-    @Test
-    fun currentStateIsReturnedIfDifferentFromPreviousState() {
-        val currentState = WetterComState(now.minusDays(1))
-        val newState = trigger.mergeStates(previousState, currentState) as WetterComState
-        assertThat(newState, `is`(currentState))
-    }
-
-    @Test
-    fun mergingEqualStatesDoesNotTrigger() {
-        val currentState = WetterComState(now)
-        trigger.mergeStates(previousState, currentState) as WetterComState
-        assertThat(trigger.triggers(), `is`(false))
-    }
-
-    @Test
-    fun mergingDifferentStatesDoesTrigger() {
-        val currentState = WetterComState(now.minusDays(1))
-        trigger.mergeStates(previousState, currentState) as WetterComState
-        assertThat(trigger.triggers(), `is`(true))
-    }
-
-    @Test
-    fun outputSummaryContainsCorrectDate() {
-        val currentState = WetterComState(ZonedDateTime.of(2000, 1, 1, 5, 0, 0, 0, ZoneId.of("Europe/Berlin")))
-        trigger.mergeStates(previousState, currentState) as WetterComState
-        val reaction = Mockito.mock(Reaction::class.java)
-        val output = trigger.output(reaction)
-        assertThat(output.summary(), `is`("The weather (according to wetter.com) on January 1, 2000"))
-    }
-
-    @Test
-    fun outputHtmlContainsHourStates() {
-        val currentState = WetterComState(now.minusDays(1))
-        currentState += HourState(0, 10, null, 0.11, 12.0, WindDirection.NORTH, 13, null, null, "Rain", "http://1")
-        currentState += HourState(1, 14, null, 0.15, 16.0, WindDirection.SOUTH, 17, null, null, "Sun", "http://2")
-        trigger.mergeStates(previousState, currentState) as WetterComState
-        val reaction = Mockito.mock(Reaction::class.java)
-        val output = trigger.output(reaction)
-        val htmlText = output.text("text/html", -1)
-        assertThat(htmlText, containsString("10 °C"))
-        assertThat(htmlText, containsString("11%"))
-        assertThat(htmlText, containsString("12 l/m"))
-        assertThat(htmlText, containsString("13 km/h"))
-        assertThat(htmlText, containsString("Rain"))
-        assertThat(htmlText, containsString("http://1"))
-        assertThat(htmlText, containsString("14 °C"))
-        assertThat(htmlText, containsString("15%"))
-        assertThat(htmlText, containsString("16 l/m"))
-        assertThat(htmlText, containsString("17 km/h"))
-        assertThat(htmlText, containsString("Sun"))
-        assertThat(htmlText, containsString("http://2"))
-    }
-
-}
index fc435c2..fa79869 100644 (file)
@@ -5,6 +5,7 @@ import net.pterodactylus.rhynodge.states.AbstractState
 import net.pterodactylus.rhynodge.states.FailedState
 import net.pterodactylus.rhynodge.states.HtmlState
 import net.pterodactylus.rhynodge.webpages.weather.HourState
+import net.pterodactylus.rhynodge.webpages.weather.WeatherState
 import net.pterodactylus.rhynodge.webpages.weather.WindDirection
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.`is`
@@ -43,14 +44,14 @@ class WetterDeFilterTest {
     @Test
     fun filterCanParseDateCorrectly() {
         val htmlState = HtmlState(url, ResourceLoader.loadDocument(javaClass, "wetter.de.html", url))
-        val wetterDeState = filter.filter(htmlState) as WetterDeState
+        val wetterDeState = filter.filter(htmlState) as WeatherState
         assertThat(wetterDeState.dateTime, `is`(ZonedDateTime.of(2016, 5, 30, 0, 0, 0, 0, ZoneId.of("Europe/Berlin"))))
     }
 
     @Test
     fun filterParsesHoursCorrectly() {
         val htmlState = HtmlState(url, ResourceLoader.loadDocument(javaClass, "wetter.de.html", url))
-        val wetterDeState = filter.filter(htmlState) as WetterDeState
+        val wetterDeState = filter.filter(htmlState) as WeatherState
         assertThat(wetterDeState, contains(
                 HourState(0, 18, 18, 0.47, 0.4, WindDirection.NORTHEAST, 19, 41, 0.91, "gewittrig", "http://cdn.static-fra.de/wetterv3/css/images/icons/weather/m/017_M.png?o44shg"),
                 HourState(1, 18, 18, 0.60, 0.6, WindDirection.NORTHEAST, 20, 39, 0.91, "gewittrig", "http://cdn.static-fra.de/wetterv3/css/images/icons/weather/m/017_M.png?o44shg"),
diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeStateTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeStateTest.kt
deleted file mode 100644 (file)
index aa21c0c..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package net.pterodactylus.rhynodge.webpages.weather.wetterde
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import net.pterodactylus.rhynodge.webpages.weather.HourState
-import net.pterodactylus.rhynodge.webpages.weather.WindDirection
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
-import org.junit.Test
-import java.time.ZoneId.of
-import java.time.ZonedDateTime
-
-/**
- * Unit test for [WetterDeState].
- *
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
- */
-class WetterDeStateTest {
-
-    private val now = ZonedDateTime.now(of("Europe/Berlin"))
-
-    @Test
-    fun statesWithTheSameDateAndHoursAreEqual() {
-        val firstState = WetterDeState(now)
-        firstState += createHourState(0)
-        firstState += createHourState(1)
-        val secondState = WetterDeState(now)
-        secondState += createHourState(0)
-        secondState += createHourState(1)
-        assertThat(firstState, `is`(secondState))
-    }
-
-    private fun createHourState(hourIndex: Int): HourState {
-        return HourState(
-                hourIndex,
-                10 + hourIndex,
-                12 + hourIndex,
-                (50 + hourIndex) / 100.0,
-                hourIndex.toDouble(),
-                WindDirection.values().get(hourIndex % WindDirection.values().size),
-                20 + hourIndex,
-                30 + hourIndex,
-                (40 + hourIndex) / 100.0,
-                "foo: " + hourIndex,
-                "//" + hourIndex
-        )
-    }
-
-    @Test
-    fun stateIsSerializableAsJson() {
-        val originalState = WetterDeState(now)
-        originalState += createHourState(0)
-        originalState += createHourState(1)
-        val json = ObjectMapper().writeValueAsString(originalState)
-        val parsedState = ObjectMapper().readValue(json, WetterDeState::class.java)
-        assertThat(parsedState, `is`(originalState))
-    }
-
-}
diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTriggerTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/webpages/weather/wetterde/WetterDeTriggerTest.kt
deleted file mode 100644 (file)
index 1dac1ab..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-package net.pterodactylus.rhynodge.webpages.weather.wetterde
-
-import net.pterodactylus.rhynodge.Reaction
-import net.pterodactylus.rhynodge.State
-import net.pterodactylus.rhynodge.webpages.weather.HourState
-import net.pterodactylus.rhynodge.webpages.weather.WindDirection
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.`is`
-import org.hamcrest.Matchers.containsString
-import org.junit.Test
-import org.mockito.Mockito.mock
-import java.time.ZoneId.of
-import java.time.ZonedDateTime
-
-/**
- * Unit test for [WetterDeTriggerTest].
-
- * @author [David ‘Bombe’ Roden](mailto:bombe@pterodactylus.net)
- */
-class WetterDeTriggerTest {
-
-    private val now = ZonedDateTime.now(of("Europe/Berlin"))
-    private val previousState = WetterDeState(now.minusDays(1))
-    private val trigger = WetterDeTrigger()
-
-    @Test
-    fun currentStateIsAlwaysReturned() {
-        val currentState = WetterDeState(now)
-        assertThat(trigger.mergeStates(previousState, currentState), `is`(currentState as State))
-    }
-
-    @Test
-    fun triggerDoesNotTriggerIfStateHasNotChanged() {
-        val currentState = WetterDeState(now.minusDays(1))
-        trigger.mergeStates(previousState, currentState)
-        assertThat(trigger.triggers(), `is`(false))
-    }
-
-    @Test
-    fun triggerDoesTriggerIfStateHasChanged() {
-        val currentState = WetterDeState(now)
-        trigger.mergeStates(previousState, currentState)
-        assertThat(trigger.triggers(), `is`(true))
-    }
-
-    @Test
-    fun outputContainsCorrectSummary() {
-        val currentState = WetterDeState(ZonedDateTime.of(2016, 5, 28, 0, 0, 0, 0, of("Europe/Berlin")))
-        trigger.mergeStates(previousState, currentState)
-        val reaction = mock(Reaction::class.java)
-        val output = trigger.output(reaction)
-        assertThat(output.summary(), `is`("The Weather (according to wetter.de) on May 28, 2016"))
-    }
-
-    @Test
-    fun outputContainsCorrectHourData() {
-        val currentState = WetterDeState(ZonedDateTime.of(2016, 5, 28, 0, 0, 0, 0, of("Europe/Berlin")))
-        currentState += HourState(0, 10, 11, 0.12, 13.0, WindDirection.SOUTHSOUTHEAST, 14, 15, 0.16, "17", "http://18")
-        currentState += HourState(1, 20, 21, 0.22, 23.0, WindDirection.NORTHNORTHWEST, 24, 25, 0.26, "27", "http://28")
-        trigger.mergeStates(previousState, currentState)
-        val reaction = mock(Reaction::class.java)
-        val output = trigger.output(reaction)
-        val htmlText = output.text("text/html", -1)
-        assertThat(htmlText, containsString("00:00"))
-        assertThat(htmlText, containsString("10 °C"))
-        assertThat(htmlText, containsString("(11 °C)"))
-        assertThat(htmlText, containsString("12%"))
-        assertThat(htmlText, containsString("13 l/m²"))
-        assertThat(htmlText, containsString("↖↑"))
-        assertThat(htmlText, containsString("14 km/h"))
-        assertThat(htmlText, containsString("15 km/h"))
-        assertThat(htmlText, containsString("16%"))
-        assertThat(htmlText, containsString("17"))
-        assertThat(htmlText, containsString("http://18"))
-        assertThat(htmlText, containsString("01:00"))
-        assertThat(htmlText, containsString("20 °C"))
-        assertThat(htmlText, containsString("(21 °C)"))
-        assertThat(htmlText, containsString("22%"))
-        assertThat(htmlText, containsString("23 l/m²"))
-        assertThat(htmlText, containsString("↘↓"))
-        assertThat(htmlText, containsString("24 km/h"))
-        assertThat(htmlText, containsString("25 km/h"))
-        assertThat(htmlText, containsString("26%"))
-        assertThat(htmlText, containsString("27"))
-        assertThat(htmlText, containsString("http://28"))
-    }
-
-}