--- /dev/null
+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)
+ }
+
+}
--- /dev/null
+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*$"), "")
+ }
+
+}
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
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
}
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.
class WetterComHamburgWatcher : DefaultWatcher(
HttpQuery("http://www.wetter.com/wetter_aktuell/wettervorhersage/heute/deutschland/hamburg/DE0004130.html"),
listOf(HtmlFilter(), WetterComFilter()),
- WetterComTrigger()
+ WeatherTrigger()
)
+++ /dev/null
-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)
- }
-
-}
+++ /dev/null
-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()
- }
-
-}
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
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
}
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.
class WetterDeHamburgWatcher : DefaultWatcher(
HttpQuery("http://www.wetter.de/deutschland/wetter-hamburg-18219464/wetterbericht-aktuell.html"),
listOf(HtmlFilter(), WetterDeFilter()),
- WetterDeTrigger()
+ WeatherTrigger()
)
+++ /dev/null
-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)
- }
-
-}
+++ /dev/null
-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*$"), "")
- }
-
-}
--- /dev/null
+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))
+ }
+
+}
--- /dev/null
+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"))
+ }
+
+}
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`
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"),
+++ /dev/null
-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))
- }
-
-}
+++ /dev/null
-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"))
- }
-
-}
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`
@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"),
+++ /dev/null
-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))
- }
-
-}
+++ /dev/null
-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"))
- }
-
-}