From 4a9996615e5aabaa978c4469a801cdef1aa3090f Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Thu, 21 Mar 2024 09:07:30 +0100 Subject: [PATCH 1/9] data model --- app/src/main/kotlin/data/DailyAverageData.kt | 5 ++++ app/src/main/kotlin/data/DailyTempData.kt | 6 +++++ app/src/main/kotlin/data/HourlyData.kt | 12 +++++++++ app/src/main/kotlin/data/HourlyUnits.kt | 11 +++++++++ app/src/main/kotlin/data/WeatherResponse.kt | 26 ++++++++++++++++++++ app/src/main/kotlin/data/WeeklyTempData.kt | 5 ++++ 6 files changed, 65 insertions(+) create mode 100644 app/src/main/kotlin/data/DailyAverageData.kt create mode 100644 app/src/main/kotlin/data/DailyTempData.kt create mode 100644 app/src/main/kotlin/data/HourlyData.kt create mode 100644 app/src/main/kotlin/data/HourlyUnits.kt create mode 100644 app/src/main/kotlin/data/WeatherResponse.kt create mode 100644 app/src/main/kotlin/data/WeeklyTempData.kt diff --git a/app/src/main/kotlin/data/DailyAverageData.kt b/app/src/main/kotlin/data/DailyAverageData.kt new file mode 100644 index 0000000..f7604cc --- /dev/null +++ b/app/src/main/kotlin/data/DailyAverageData.kt @@ -0,0 +1,5 @@ +package hu.vanio.kotlin.feladat.ms.data + +import java.time.LocalDate + +data class DailyAverageData(var date: LocalDate, var average_temp: Double) \ No newline at end of file diff --git a/app/src/main/kotlin/data/DailyTempData.kt b/app/src/main/kotlin/data/DailyTempData.kt new file mode 100644 index 0000000..f3de742 --- /dev/null +++ b/app/src/main/kotlin/data/DailyTempData.kt @@ -0,0 +1,6 @@ +package hu.vanio.kotlin.feladat.ms.data + +import java.time.LocalDate +import java.time.LocalTime + +data class DailyTempData(var date: LocalDate, val hourlyTempData: Map) diff --git a/app/src/main/kotlin/data/HourlyData.kt b/app/src/main/kotlin/data/HourlyData.kt new file mode 100644 index 0000000..1e53aef --- /dev/null +++ b/app/src/main/kotlin/data/HourlyData.kt @@ -0,0 +1,12 @@ +package hu.vanio.kotlin.feladat.ms.data + +import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.Serializable + +@Serializable +data class HourlyData( + @JsonProperty("time") + val time: List?, + @JsonProperty("temperature_2m") + val temperature_2m: List? +) diff --git a/app/src/main/kotlin/data/HourlyUnits.kt b/app/src/main/kotlin/data/HourlyUnits.kt new file mode 100644 index 0000000..e3e6944 --- /dev/null +++ b/app/src/main/kotlin/data/HourlyUnits.kt @@ -0,0 +1,11 @@ +package hu.vanio.kotlin.feladat.ms.data + +import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.Serializable + +@Serializable +data class HourlyUnits( + @JsonProperty("time") val time: String, + @JsonProperty("temperature_2m") val temperature_2m: String +) { +} \ No newline at end of file diff --git a/app/src/main/kotlin/data/WeatherResponse.kt b/app/src/main/kotlin/data/WeatherResponse.kt new file mode 100644 index 0000000..79fea57 --- /dev/null +++ b/app/src/main/kotlin/data/WeatherResponse.kt @@ -0,0 +1,26 @@ +package hu.vanio.kotlin.feladat.ms.data + +import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.Serializable + +@Serializable +data class WeatherResponse( + @JsonProperty("latitude") + val latitude: Double, + @JsonProperty("longitude") + val longitude: Double, + @JsonProperty("generationtime_ms") + val generationtime_ms: Double, + @JsonProperty("utc_offset_seconds") + val utc_offset_seconds: Int, + @JsonProperty("timezone") + val timezone: String, + @JsonProperty("timezone_abbreviation") + val timezone_abbreviation: String, + @JsonProperty("elevation") + val elevation: Double, + @JsonProperty("hourly_units") + val hourly_units: HourlyUnits, + @JsonProperty("hourly") + val hourly: HourlyData +) diff --git a/app/src/main/kotlin/data/WeeklyTempData.kt b/app/src/main/kotlin/data/WeeklyTempData.kt new file mode 100644 index 0000000..c78fb98 --- /dev/null +++ b/app/src/main/kotlin/data/WeeklyTempData.kt @@ -0,0 +1,5 @@ +package hu.vanio.kotlin.feladat.ms.data + +import java.time.LocalDate + +data class WeeklyTempData(var from: LocalDate?, var to: LocalDate?, val dailyTempData: List) \ No newline at end of file From adf2d41707d7f4c58da749118eb923e94683abac Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Thu, 21 Mar 2024 09:08:53 +0100 Subject: [PATCH 2/9] weather feign client --- app/build.gradle.kts | 15 +- .../main/kotlin/configuration/FeignConfig.kt | 24 ++ .../kotlin/httpclient/WeatherFeignClient.kt | 11 + app/src/main/resources/application.properties | 6 + app/src/test/kotlin/WeatherAppTest.kt | 11 - .../test/kotlin/it/WeatherFeignClientIT.kt | 54 +++ app/src/test/kotlin/mock/WeatherAPIMock.kt | 32 ++ .../resources/application-test.properties | 1 + .../payload/weather-forecast-response.json | 355 ++++++++++++++++++ 9 files changed, 497 insertions(+), 12 deletions(-) create mode 100644 app/src/main/kotlin/configuration/FeignConfig.kt create mode 100644 app/src/main/kotlin/httpclient/WeatherFeignClient.kt create mode 100644 app/src/main/resources/application.properties delete mode 100644 app/src/test/kotlin/WeatherAppTest.kt create mode 100644 app/src/test/kotlin/it/WeatherFeignClientIT.kt create mode 100644 app/src/test/kotlin/mock/WeatherAPIMock.kt create mode 100644 app/src/test/resources/application-test.properties create mode 100644 app/src/test/resources/payload/weather-forecast-response.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1182c84..e7e913f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,6 +3,7 @@ plugins { kotlin("jvm") version kotlinVersion kotlin("plugin.spring") version kotlinVersion id("org.springframework.boot") version "3.2.3" + kotlin("plugin.serialization") version "1.5.31" } group = "hu.kotlin.feladat.ms" @@ -15,8 +16,20 @@ dependencies { implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("io.ktor:ktor-client-okhttp:1.6.6") + implementation("io.ktor:ktor-client-core:1.6.6") + implementation("io.ktor:ktor-client-json:1.6.6") + implementation("io.ktor:ktor-client-serialization:1.6.6") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0") + implementation("org.springframework.cloud:spring-cloud-starter-openfeign:3.1.3") // Spring Cloud OpenFeign integration + implementation("io.github.openfeign:feign-okhttp:11.2") + implementation("io.github.openfeign:feign-jackson:11.2") + implementation("org.reactivestreams:reactive-streams:1.0.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.6.0") + testImplementation("org.springframework.boot:spring-boot-starter-test:3.2.3") testImplementation(kotlin("test")) testImplementation("io.mockk:mockk:1.4.1") + testImplementation("com.github.tomakehurst:wiremock-standalone:3.0.1") } tasks.test { @@ -25,4 +38,4 @@ tasks.test { kotlin { jvmToolchain(17) -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/configuration/FeignConfig.kt b/app/src/main/kotlin/configuration/FeignConfig.kt new file mode 100644 index 0000000..b3693a0 --- /dev/null +++ b/app/src/main/kotlin/configuration/FeignConfig.kt @@ -0,0 +1,24 @@ +package hu.vanio.kotlin.feladat.ms.configuration + +import feign.Feign +import feign.jackson.JacksonDecoder +import feign.jackson.JacksonEncoder +import feign.okhttp.OkHttpClient +import hu.vanio.kotlin.feladat.ms.httpclient.WeatherFeignClient +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class FeignConfig { + @Value("\${base.url.weather.api}") + private lateinit var weatherApiBaseUrl: String + @Bean + fun weatherFeignClient(): WeatherFeignClient { + return Feign.builder() + .client(OkHttpClient()) + .encoder(JacksonEncoder()) + .decoder(JacksonDecoder()) + .target(WeatherFeignClient::class.java, weatherApiBaseUrl) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/httpclient/WeatherFeignClient.kt b/app/src/main/kotlin/httpclient/WeatherFeignClient.kt new file mode 100644 index 0000000..a3f0a5d --- /dev/null +++ b/app/src/main/kotlin/httpclient/WeatherFeignClient.kt @@ -0,0 +1,11 @@ +package hu.vanio.kotlin.feladat.ms.httpclient + +import feign.RequestLine +import hu.vanio.kotlin.feladat.ms.data.WeatherResponse +import org.springframework.cloud.openfeign.FeignClient + +@FeignClient(name = "weatherFeignClient") +interface WeatherFeignClient { + @RequestLine("GET /forecast?latitude=47.4984&longitude=19.0404&hourly=temperature_2m&timezone=auto") + fun getWeather(): WeatherResponse +} \ No newline at end of file diff --git a/app/src/main/resources/application.properties b/app/src/main/resources/application.properties new file mode 100644 index 0000000..69659a6 --- /dev/null +++ b/app/src/main/resources/application.properties @@ -0,0 +1,6 @@ +logging.level.org.springframework.context.annotation = DEBUG + +server.tomcat.accesslog.enabled=true +server.tomcat.accesslog.pattern='%h %l %u %t "%r" %s %b' + +base.url.weather.api=https://api.open-meteo.com/v1 \ No newline at end of file diff --git a/app/src/test/kotlin/WeatherAppTest.kt b/app/src/test/kotlin/WeatherAppTest.kt deleted file mode 100644 index a81a55a..0000000 --- a/app/src/test/kotlin/WeatherAppTest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hu.vanio.kotlin.feladat.ms - -import kotlin.test.Test - -class WeatherAppTest { - - @Test fun `sikeres lekerdezes`() { - TODO() - } - -} \ No newline at end of file diff --git a/app/src/test/kotlin/it/WeatherFeignClientIT.kt b/app/src/test/kotlin/it/WeatherFeignClientIT.kt new file mode 100644 index 0000000..d275c0b --- /dev/null +++ b/app/src/test/kotlin/it/WeatherFeignClientIT.kt @@ -0,0 +1,54 @@ +package hu.vanio.kotlin.feladat.ms.it + +import com.github.tomakehurst.wiremock.WireMockServer +import hu.vanio.kotlin.feladat.ms.httpclient.WeatherFeignClient +import hu.vanio.kotlin.feladat.ms.mock.WeatherAPIMock.Companion.setupMockWeatherAPIResponse +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.cloud.openfeign.EnableFeignClients +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension +import kotlin.test.assertEquals + +@SpringBootTest +@ActiveProfiles("test") +@EnableFeignClients +@EnableConfigurationProperties +@ExtendWith(SpringExtension::class) +class WeatherFeignClientIT { + companion object { + private lateinit var wireMockServer: WireMockServer + + @BeforeAll + @JvmStatic + fun setUp() { + wireMockServer = WireMockServer(1040) + wireMockServer.start() + setupMockWeatherAPIResponse(wireMockServer) + } + + @AfterAll + @JvmStatic + fun tearDown() { + wireMockServer.stop() + } + } + + @Autowired + private lateinit var weatherFeignClient: WeatherFeignClient + + @Test + fun `test weather feign client get weather`() { + val weatherResponse = weatherFeignClient.getWeather() + + assertEquals(47.5, weatherResponse.latitude) + assertEquals(19.0625, weatherResponse.longitude) + assertEquals(168, weatherResponse.hourly.time?.size) + assertEquals(168, weatherResponse.hourly.temperature_2m?.size) + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/mock/WeatherAPIMock.kt b/app/src/test/kotlin/mock/WeatherAPIMock.kt new file mode 100644 index 0000000..7f148b1 --- /dev/null +++ b/app/src/test/kotlin/mock/WeatherAPIMock.kt @@ -0,0 +1,32 @@ +package hu.vanio.kotlin.feladat.ms.mock + +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.util.StreamUtils.copyToString +import java.io.IOException +import java.nio.charset.Charset + +class WeatherAPIMock { + companion object { + @JvmStatic + @Throws(IOException::class) + fun setupMockWeatherAPIResponse(mockServer: WireMockServer) { + mockServer.stubFor( + WireMock.get(WireMock.urlPathMatching("/v1/forecast.*")) + .willReturn( + WireMock.aResponse() + .withStatus(HttpStatus.OK.value()) + .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .withBody( + copyToString( + WeatherAPIMock::class.java.classLoader.getResourceAsStream("payload/weather-forecast-response.json"), + Charset.defaultCharset() + ) + ) + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/test/resources/application-test.properties b/app/src/test/resources/application-test.properties new file mode 100644 index 0000000..c71351f --- /dev/null +++ b/app/src/test/resources/application-test.properties @@ -0,0 +1 @@ +base.url.weather.api=http://localhost:1040/v1 \ No newline at end of file diff --git a/app/src/test/resources/payload/weather-forecast-response.json b/app/src/test/resources/payload/weather-forecast-response.json new file mode 100644 index 0000000..2934ec6 --- /dev/null +++ b/app/src/test/resources/payload/weather-forecast-response.json @@ -0,0 +1,355 @@ +{ + "latitude": 47.5, + "longitude": 19.0625, + "generationtime_ms": 0.02300739288330078, + "utc_offset_seconds": 3600, + "timezone": "Europe/Budapest", + "timezone_abbreviation": "CET", + "elevation": 124.0, + "hourly_units": { + "time": "iso8601", + "temperature_2m": "°C" + }, + "hourly": { + "time": [ + "2024-03-20T00:00", + "2024-03-20T01:00", + "2024-03-20T02:00", + "2024-03-20T03:00", + "2024-03-20T04:00", + "2024-03-20T05:00", + "2024-03-20T06:00", + "2024-03-20T07:00", + "2024-03-20T08:00", + "2024-03-20T09:00", + "2024-03-20T10:00", + "2024-03-20T11:00", + "2024-03-20T12:00", + "2024-03-20T13:00", + "2024-03-20T14:00", + "2024-03-20T15:00", + "2024-03-20T16:00", + "2024-03-20T17:00", + "2024-03-20T18:00", + "2024-03-20T19:00", + "2024-03-20T20:00", + "2024-03-20T21:00", + "2024-03-20T22:00", + "2024-03-20T23:00", + "2024-03-21T00:00", + "2024-03-21T01:00", + "2024-03-21T02:00", + "2024-03-21T03:00", + "2024-03-21T04:00", + "2024-03-21T05:00", + "2024-03-21T06:00", + "2024-03-21T07:00", + "2024-03-21T08:00", + "2024-03-21T09:00", + "2024-03-21T10:00", + "2024-03-21T11:00", + "2024-03-21T12:00", + "2024-03-21T13:00", + "2024-03-21T14:00", + "2024-03-21T15:00", + "2024-03-21T16:00", + "2024-03-21T17:00", + "2024-03-21T18:00", + "2024-03-21T19:00", + "2024-03-21T20:00", + "2024-03-21T21:00", + "2024-03-21T22:00", + "2024-03-21T23:00", + "2024-03-22T00:00", + "2024-03-22T01:00", + "2024-03-22T02:00", + "2024-03-22T03:00", + "2024-03-22T04:00", + "2024-03-22T05:00", + "2024-03-22T06:00", + "2024-03-22T07:00", + "2024-03-22T08:00", + "2024-03-22T09:00", + "2024-03-22T10:00", + "2024-03-22T11:00", + "2024-03-22T12:00", + "2024-03-22T13:00", + "2024-03-22T14:00", + "2024-03-22T15:00", + "2024-03-22T16:00", + "2024-03-22T17:00", + "2024-03-22T18:00", + "2024-03-22T19:00", + "2024-03-22T20:00", + "2024-03-22T21:00", + "2024-03-22T22:00", + "2024-03-22T23:00", + "2024-03-23T00:00", + "2024-03-23T01:00", + "2024-03-23T02:00", + "2024-03-23T03:00", + "2024-03-23T04:00", + "2024-03-23T05:00", + "2024-03-23T06:00", + "2024-03-23T07:00", + "2024-03-23T08:00", + "2024-03-23T09:00", + "2024-03-23T10:00", + "2024-03-23T11:00", + "2024-03-23T12:00", + "2024-03-23T13:00", + "2024-03-23T14:00", + "2024-03-23T15:00", + "2024-03-23T16:00", + "2024-03-23T17:00", + "2024-03-23T18:00", + "2024-03-23T19:00", + "2024-03-23T20:00", + "2024-03-23T21:00", + "2024-03-23T22:00", + "2024-03-23T23:00", + "2024-03-24T00:00", + "2024-03-24T01:00", + "2024-03-24T02:00", + "2024-03-24T03:00", + "2024-03-24T04:00", + "2024-03-24T05:00", + "2024-03-24T06:00", + "2024-03-24T07:00", + "2024-03-24T08:00", + "2024-03-24T09:00", + "2024-03-24T10:00", + "2024-03-24T11:00", + "2024-03-24T12:00", + "2024-03-24T13:00", + "2024-03-24T14:00", + "2024-03-24T15:00", + "2024-03-24T16:00", + "2024-03-24T17:00", + "2024-03-24T18:00", + "2024-03-24T19:00", + "2024-03-24T20:00", + "2024-03-24T21:00", + "2024-03-24T22:00", + "2024-03-24T23:00", + "2024-03-25T00:00", + "2024-03-25T01:00", + "2024-03-25T02:00", + "2024-03-25T03:00", + "2024-03-25T04:00", + "2024-03-25T05:00", + "2024-03-25T06:00", + "2024-03-25T07:00", + "2024-03-25T08:00", + "2024-03-25T09:00", + "2024-03-25T10:00", + "2024-03-25T11:00", + "2024-03-25T12:00", + "2024-03-25T13:00", + "2024-03-25T14:00", + "2024-03-25T15:00", + "2024-03-25T16:00", + "2024-03-25T17:00", + "2024-03-25T18:00", + "2024-03-25T19:00", + "2024-03-25T20:00", + "2024-03-25T21:00", + "2024-03-25T22:00", + "2024-03-25T23:00", + "2024-03-26T00:00", + "2024-03-26T01:00", + "2024-03-26T02:00", + "2024-03-26T03:00", + "2024-03-26T04:00", + "2024-03-26T05:00", + "2024-03-26T06:00", + "2024-03-26T07:00", + "2024-03-26T08:00", + "2024-03-26T09:00", + "2024-03-26T10:00", + "2024-03-26T11:00", + "2024-03-26T12:00", + "2024-03-26T13:00", + "2024-03-26T14:00", + "2024-03-26T15:00", + "2024-03-26T16:00", + "2024-03-26T17:00", + "2024-03-26T18:00", + "2024-03-26T19:00", + "2024-03-26T20:00", + "2024-03-26T21:00", + "2024-03-26T22:00", + "2024-03-26T23:00" + ], + "temperature_2m": [ + 3.4, + 3.0, + 2.6, + 2.2, + 1.6, + 1.4, + 1.2, + 1.7, + 3.4, + 5.6, + 9.1, + 10.9, + 12.0, + 12.5, + 12.9, + 13.1, + 13.6, + 12.9, + 11.6, + 10.0, + 8.3, + 7.0, + 6.1, + 5.2, + 4.9, + 4.9, + 4.8, + 4.6, + 4.1, + 3.9, + 3.8, + 4.5, + 6.5, + 9.4, + 11.7, + 13.3, + 14.4, + 15.3, + 15.8, + 16.1, + 16.0, + 15.4, + 13.8, + 12.2, + 11.1, + 10.2, + 9.7, + 9.9, + 9.8, + 9.6, + 9.3, + 8.8, + 9.1, + 9.3, + 9.1, + 9.5, + 11.0, + 12.7, + 14.1, + 15.2, + 16.0, + 16.2, + 16.4, + 16.4, + 16.0, + 15.3, + 14.0, + 12.6, + 11.6, + 10.7, + 9.8, + 9.4, + 9.0, + 8.2, + 8.0, + 7.7, + 7.3, + 7.2, + 7.4, + 8.1, + 9.4, + 11.0, + 13.1, + 14.6, + 15.8, + 17.0, + 17.4, + 17.6, + 17.5, + 17.2, + 16.4, + 12.4, + 11.0, + 9.7, + 8.6, + 8.0, + 7.7, + 7.4, + 7.0, + 6.7, + 6.5, + 6.2, + 6.0, + 6.2, + 7.1, + 8.5, + 9.6, + 10.3, + 10.9, + 11.2, + 11.4, + 11.3, + 11.0, + 10.4, + 9.5, + 8.7, + 7.8, + 6.9, + 6.2, + 5.8, + 5.5, + 5.3, + 4.9, + 4.5, + 4.3, + 4.1, + 4.2, + 4.8, + 6.5, + 8.8, + 10.7, + 11.9, + 12.8, + 13.4, + 13.6, + 13.8, + 13.5, + 12.5, + 11.1, + 9.9, + 9.1, + 8.3, + 7.8, + 7.5, + 7.3, + 7.0, + 6.5, + 5.8, + 5.4, + 5.4, + 5.6, + 6.1, + 7.0, + 8.1, + 9.1, + 9.9, + 10.7, + 11.4, + 12.3, + 13.0, + 13.4, + 13.2, + 12.6, + 11.8, + 11.0, + 10.0, + 9.2, + 8.6 + ] + } +} \ No newline at end of file From d158cf1f4720646535a59989e9ae2e5722c59553 Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Thu, 21 Mar 2024 09:09:53 +0100 Subject: [PATCH 3/9] weather api forecast response parser --- .../kotlin/parser/WeatherResponseParser.kt | 64 +++++++++++ .../parser/WeatherResponseParserTest.kt | 105 ++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 app/src/main/kotlin/parser/WeatherResponseParser.kt create mode 100644 app/src/test/kotlin/parser/WeatherResponseParserTest.kt diff --git a/app/src/main/kotlin/parser/WeatherResponseParser.kt b/app/src/main/kotlin/parser/WeatherResponseParser.kt new file mode 100644 index 0000000..6c1d5df --- /dev/null +++ b/app/src/main/kotlin/parser/WeatherResponseParser.kt @@ -0,0 +1,64 @@ +package hu.vanio.kotlin.feladat.ms.parser + +import hu.vanio.kotlin.feladat.ms.data.DailyTempData +import hu.vanio.kotlin.feladat.ms.data.HourlyData +import hu.vanio.kotlin.feladat.ms.data.WeeklyTempData +import org.springframework.stereotype.Service +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.format.DateTimeFormatter + +@Service +class WeatherResponseParser { + + @Throws(IllegalArgumentException::class) + fun groupToWeekData(hourlyData: HourlyData): WeeklyTempData { + val dailyTempDataList = mutableListOf() + val (time, temp) = validateData(hourlyData) + + val days = time.map { LocalDate.parse(it.substring(0, 10)) }.distinct() + for (day in days) { + val hourlyTempsForDay = time.zip(temp) + .filter { it.first.toDate() == day } + .map { it.first.toTime() to it.second } + .toMap() + + dailyTempDataList.add(DailyTempData(day, hourlyTempsForDay)) + } + + return weeklyTempData(dailyTempDataList) + } + + private fun validateData(hourlyData: HourlyData): Pair, List> { + val time = hourlyData.time + val temp = hourlyData.temperature_2m + + if (time.isNullOrEmpty()) { + throw IllegalArgumentException("No time data available") + } + + if (temp.isNullOrEmpty()) { + throw IllegalArgumentException("No temp data available") + } + + return time to temp + } + + private fun weeklyTempData(dailyTempDataList: MutableList): WeeklyTempData { + val from = dailyTempDataList.first().date + val to = dailyTempDataList.last().date + return WeeklyTempData(from, to, dailyTempDataList) + } + + private fun String.toDate(): LocalDate { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm") + return LocalDate.parse(this, formatter) + } + + private fun String.toTime(): LocalTime { + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm") + val dateTime = LocalDateTime.parse(this, formatter) + return dateTime.toLocalTime() + } +} diff --git a/app/src/test/kotlin/parser/WeatherResponseParserTest.kt b/app/src/test/kotlin/parser/WeatherResponseParserTest.kt new file mode 100644 index 0000000..06dff43 --- /dev/null +++ b/app/src/test/kotlin/parser/WeatherResponseParserTest.kt @@ -0,0 +1,105 @@ +package parser + +import hu.vanio.kotlin.feladat.ms.data.DailyTempData +import hu.vanio.kotlin.feladat.ms.data.HourlyData +import hu.vanio.kotlin.feladat.ms.data.WeeklyTempData +import hu.vanio.kotlin.feladat.ms.parser.WeatherResponseParser +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.time.LocalDate +import java.time.LocalTime + +class WeatherResponseParserTest { + private val parser = WeatherResponseParser() + + @Test + fun `test groupToWeekData with null hourly time`() { + val hourlyData = HourlyData(null, temp()) + + val exception = assertThrows(IllegalArgumentException::class.java) { + parser.groupToWeekData(hourlyData) + } + + assertEquals("No time data available", exception.message) + } + + @Test + fun `test groupToWeekData with null hourly temp`() { + val hourlyData = HourlyData(time(), null) + + val exception = assertThrows(IllegalArgumentException::class.java) { + parser.groupToWeekData(hourlyData) + } + + assertEquals("No temp data available", exception.message) + } + + @Test + fun `test groupToWeekData when time data is empty`() { + val hourlyData = HourlyData(emptyList(), temp()) + + val exception = assertThrows(IllegalArgumentException::class.java) { + parser.groupToWeekData(hourlyData) + } + + assertEquals("No time data available", exception.message) + } + + @Test + fun `test groupToWeekData when temp data is empty`() { + val hourlyData = HourlyData(time(), emptyList()) + + val exception = assertThrows(IllegalArgumentException::class.java) { + parser.groupToWeekData(hourlyData) + } + + assertEquals("No temp data available", exception.message) + } + + @Test + fun `test groupToWeekData with valid data`() { + val hourlyData = HourlyData(time(), temp()) + + val result = parser.groupToWeekData(hourlyData) + + assertEquals(WeeklyTempData::class.java, result.javaClass) + assertEquals(LocalDate.parse("2024-03-19"), result.from) + assertEquals(LocalDate.parse("2024-03-20"), result.to) + assertEquals(2, result.dailyTempData.size) + + assertEquals(expectedFirstDayData().hourlyTempData, result.dailyTempData[0].hourlyTempData) + assertEquals(expectedSecondDayData().hourlyTempData, result.dailyTempData[1].hourlyTempData) + } + + private fun time(): List { + val time = mutableListOf() + time.add("2024-03-19T00:00") + time.add("2024-03-19T01:00") + time.add("2024-03-19T02:00") + time.add("2024-03-20T10:00") + time.add("2024-03-20T11:00") + + return time + } + + private fun expectedFirstDayData(): DailyTempData { + val expectedHourlyTempData = mutableMapOf() + expectedHourlyTempData[LocalTime.of(0,0)] = 0.0 + expectedHourlyTempData[LocalTime.of(1,0)] = 1.0 + expectedHourlyTempData[LocalTime.of(2,0)] = 2.0 + + return DailyTempData(LocalDate.of(2024, 3, 19), expectedHourlyTempData); + } + + private fun expectedSecondDayData(): DailyTempData { + val expectedHourlyTempData = mutableMapOf() + expectedHourlyTempData[LocalTime.of(10,0)] = 3.0 + expectedHourlyTempData[LocalTime.of(11,0)] = 4.0 + + return DailyTempData(LocalDate.of(2024, 3, 20), expectedHourlyTempData); + } + + private fun temp(): List { + return List(5) { it.toDouble() } + } +} \ No newline at end of file From 1dbc4bf03a8571658d711724e0a2a76c91175132 Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Thu, 21 Mar 2024 09:10:17 +0100 Subject: [PATCH 4/9] average daily temp calculator --- .../service/AverageDailyTempCalculator.kt | 22 +++++++++ .../service/AverageDailyTempCalculatorTest.kt | 46 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 app/src/main/kotlin/service/AverageDailyTempCalculator.kt create mode 100644 app/src/test/kotlin/service/AverageDailyTempCalculatorTest.kt diff --git a/app/src/main/kotlin/service/AverageDailyTempCalculator.kt b/app/src/main/kotlin/service/AverageDailyTempCalculator.kt new file mode 100644 index 0000000..ce2d257 --- /dev/null +++ b/app/src/main/kotlin/service/AverageDailyTempCalculator.kt @@ -0,0 +1,22 @@ +package hu.vanio.kotlin.feladat.ms.service + +import hu.vanio.kotlin.feladat.ms.data.DailyTempData +import org.springframework.stereotype.Service + +@Service +class AverageDailyTempCalculator { + fun getAverageTemp(dailyTempData: DailyTempData): Double { + if (dailyTempData.hourlyTempData.isEmpty()){ + throw IllegalArgumentException("Hourly temperature data for ${dailyTempData.date} is empty.") + } + + return getAverage(dailyTempData) + } + + private fun getAverage(dailyTempData: DailyTempData): Double { + val sum = dailyTempData.hourlyTempData.values.sum() + val size = dailyTempData.hourlyTempData.size + return sum / size + } +} + diff --git a/app/src/test/kotlin/service/AverageDailyTempCalculatorTest.kt b/app/src/test/kotlin/service/AverageDailyTempCalculatorTest.kt new file mode 100644 index 0000000..c5119c3 --- /dev/null +++ b/app/src/test/kotlin/service/AverageDailyTempCalculatorTest.kt @@ -0,0 +1,46 @@ +package service + +import hu.vanio.kotlin.feladat.ms.data.DailyTempData +import hu.vanio.kotlin.feladat.ms.service.AverageDailyTempCalculator +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.time.LocalDate +import java.time.LocalTime + +class AverageDailyTempCalculatorTest { + private val calculator = AverageDailyTempCalculator() + + @Test + fun `test getAverage`() { + val dailyTempData = dailyTempData() + + val expectedAverage = (10.0 + 15.0 + 20.0) / 3 + + val result = calculator.getAverageTemp(dailyTempData) + + assertEquals(expectedAverage, result) + } + + @Test + fun `test getAverage with empty data`() { + val dailyTempData = DailyTempData( + LocalDate.of(2024, 3, 19), + hourlyTempData = emptyMap() + ) + + val exception = assertThrows(IllegalArgumentException::class.java) { + calculator.getAverageTemp(dailyTempData) + } + + assertEquals("Hourly temperature data for 2024-03-19 is empty.", exception.message) + } + + private fun dailyTempData(): DailyTempData { + val expectedHourlyTempData = mutableMapOf() + expectedHourlyTempData[LocalTime.of(0,0)] = 10.0 + expectedHourlyTempData[LocalTime.of(1,0)] = 15.0 + expectedHourlyTempData[LocalTime.of(2,0)] = 20.0 + + return DailyTempData(LocalDate.of(2024, 3, 19), expectedHourlyTempData); + } +} \ No newline at end of file From 92e9825101c6a19dd19d452df6b556f5b21d29a9 Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Thu, 21 Mar 2024 09:10:40 +0100 Subject: [PATCH 5/9] weather service --- app/src/main/kotlin/service/WeatherService.kt | 35 ++++++++ .../test/kotlin/service/WeatherServiceIT.kt | 89 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 app/src/main/kotlin/service/WeatherService.kt create mode 100644 app/src/test/kotlin/service/WeatherServiceIT.kt diff --git a/app/src/main/kotlin/service/WeatherService.kt b/app/src/main/kotlin/service/WeatherService.kt new file mode 100644 index 0000000..57bc5ed --- /dev/null +++ b/app/src/main/kotlin/service/WeatherService.kt @@ -0,0 +1,35 @@ +package hu.vanio.kotlin.feladat.ms.service + +import hu.vanio.kotlin.feladat.ms.data.DailyAverageData +import hu.vanio.kotlin.feladat.ms.data.WeeklyTempData +import hu.vanio.kotlin.feladat.ms.httpclient.WeatherFeignClient +import hu.vanio.kotlin.feladat.ms.parser.WeatherResponseParser +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.springframework.stereotype.Service + +@Service +class WeatherService( + private val weatherFeignClient: WeatherFeignClient, + private val weatherResponseParser: WeatherResponseParser, + private val averageDailyTempCalculator: AverageDailyTempCalculator +) { + @Throws(IllegalArgumentException::class) + suspend fun getWeeklyTempData(): WeeklyTempData { + val response = withContext(Dispatchers.IO) { + weatherFeignClient.getWeather() + } + return weatherResponseParser.groupToWeekData(response.hourly) + } + + @Throws(IllegalStateException::class) + suspend fun getDailyAverageTempForOneWeek(): List { + val dailyAverageTempList = mutableListOf() + val weeklyTempData = getWeeklyTempData() + for (dailyTempData in weeklyTempData.dailyTempData) { + val average = averageDailyTempCalculator.getAverageTemp(dailyTempData); + dailyAverageTempList.add(DailyAverageData(dailyTempData.date, average)) + } + return dailyAverageTempList + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/service/WeatherServiceIT.kt b/app/src/test/kotlin/service/WeatherServiceIT.kt new file mode 100644 index 0000000..8d02527 --- /dev/null +++ b/app/src/test/kotlin/service/WeatherServiceIT.kt @@ -0,0 +1,89 @@ +package hu.vanio.kotlin.feladat.ms.service + +import com.github.tomakehurst.wiremock.WireMockServer +import hu.vanio.kotlin.feladat.ms.mock.WeatherAPIMock.Companion.setupMockWeatherAPIResponse +import hu.vanio.kotlin.feladat.ms.parser.WeatherResponseParser +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.cloud.openfeign.EnableFeignClients +import org.springframework.context.annotation.Import +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.junit.jupiter.SpringExtension +import java.time.LocalDate +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +@SpringBootTest +@ActiveProfiles("test") +@EnableFeignClients +@EnableConfigurationProperties +@ExtendWith(SpringExtension::class) +@Import( + WeatherService::class, + WeatherResponseParser::class, + AverageDailyTempCalculator::class +) +class WeatherServiceIT { + companion object { + private lateinit var wireMockServer: WireMockServer + + @BeforeAll + @JvmStatic + fun setUp() { + wireMockServer = WireMockServer(1040) + wireMockServer.start() + setupMockWeatherAPIResponse(wireMockServer) + } + + @AfterAll + @JvmStatic + fun tearDown() { + wireMockServer.stop() + } + } + + @Autowired + private lateinit var weatherService: WeatherService + + @Test + fun `test get weekly temp data`() { + runBlocking { + val weeklyTempData = weatherService.getWeeklyTempData() + assertNotNull(weeklyTempData) + } + } + + @Test + fun `test get daily average temp for one week`() { + runBlocking { + val dailyAverageTempForOneWeek = weatherService.getDailyAverageTempForOneWeek() + assertNotNull(dailyAverageTempForOneWeek) + assertEquals(LocalDate.parse("2024-03-20"), dailyAverageTempForOneWeek[0].date) + assertEquals(7.137499999999999, dailyAverageTempForOneWeek[0].average_temp) + + assertEquals(LocalDate.parse("2024-03-21"), dailyAverageTempForOneWeek[1].date) + assertEquals(9.845833333333333, dailyAverageTempForOneWeek[1].average_temp) + + assertEquals(LocalDate.parse("2024-03-22"), dailyAverageTempForOneWeek[2].date) + assertEquals(12.1625, dailyAverageTempForOneWeek[2].average_temp) + + assertEquals(LocalDate.parse("2024-03-23"), dailyAverageTempForOneWeek[3].date) + assertEquals(11.65, dailyAverageTempForOneWeek[3].average_temp) + + assertEquals(LocalDate.parse("2024-03-24"), dailyAverageTempForOneWeek[4].date) + assertEquals(8.345833333333335, dailyAverageTempForOneWeek[4].average_temp) + + assertEquals(LocalDate.parse("2024-03-25"), dailyAverageTempForOneWeek[5].date) + assertEquals(8.700000000000001, dailyAverageTempForOneWeek[5].average_temp) + + assertEquals(LocalDate.parse("2024-03-26"), dailyAverageTempForOneWeek[6].date) + assertEquals(9.183333333333334, dailyAverageTempForOneWeek[6].average_temp) + } + } +} \ No newline at end of file From c49f8913dc7f858c942d7d3916502e43be7a916f Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Thu, 21 Mar 2024 09:10:48 +0100 Subject: [PATCH 6/9] weather controller --- .../kotlin/controller/WeatherController.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/src/main/kotlin/controller/WeatherController.kt diff --git a/app/src/main/kotlin/controller/WeatherController.kt b/app/src/main/kotlin/controller/WeatherController.kt new file mode 100644 index 0000000..909019d --- /dev/null +++ b/app/src/main/kotlin/controller/WeatherController.kt @@ -0,0 +1,33 @@ +package hu.vanio.kotlin.feladat.ms.controller + +import hu.vanio.kotlin.feladat.ms.data.DailyAverageData +import hu.vanio.kotlin.feladat.ms.data.WeeklyTempData +import hu.vanio.kotlin.feladat.ms.service.WeatherService +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +class WeatherController(private val weatherService: WeatherService) { + @GetMapping("/weather") + suspend fun getWeeklyTempData(): ResponseEntity { + return ResponseEntity.ok(weatherService.getWeeklyTempData()) + } + + @GetMapping("/average") + suspend fun getAverageTemps(): ResponseEntity> { + return ResponseEntity.ok(weatherService.getDailyAverageTempForOneWeek()) + } + + @ExceptionHandler(IllegalArgumentException::class) + fun handleIllegalArgumentException(e: IllegalArgumentException): ResponseEntity<*> { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid request: ${e.message}") + } + + @ExceptionHandler(Exception::class) + fun handleException(e: Exception): ResponseEntity<*> { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred: ${e.message}") + } +} \ No newline at end of file From 5ea7c0617ceae80ef1a5926cdf06e3e44fbf9135 Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Thu, 21 Mar 2024 20:42:47 +0100 Subject: [PATCH 7/9] clean up weather response parser --- app/src/main/kotlin/parser/WeatherResponseParser.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/parser/WeatherResponseParser.kt b/app/src/main/kotlin/parser/WeatherResponseParser.kt index 6c1d5df..c63904a 100644 --- a/app/src/main/kotlin/parser/WeatherResponseParser.kt +++ b/app/src/main/kotlin/parser/WeatherResponseParser.kt @@ -14,9 +14,14 @@ class WeatherResponseParser { @Throws(IllegalArgumentException::class) fun groupToWeekData(hourlyData: HourlyData): WeeklyTempData { - val dailyTempDataList = mutableListOf() val (time, temp) = validateData(hourlyData) + val dailyTempData = groupByDays(time, temp) + + return weeklyTempData(dailyTempData) + } + private fun groupByDays(time: List, temp: List): List { + val dailyTempDataList = mutableListOf() val days = time.map { LocalDate.parse(it.substring(0, 10)) }.distinct() for (day in days) { val hourlyTempsForDay = time.zip(temp) @@ -26,8 +31,7 @@ class WeatherResponseParser { dailyTempDataList.add(DailyTempData(day, hourlyTempsForDay)) } - - return weeklyTempData(dailyTempDataList) + return dailyTempDataList } private fun validateData(hourlyData: HourlyData): Pair, List> { @@ -45,7 +49,7 @@ class WeatherResponseParser { return time to temp } - private fun weeklyTempData(dailyTempDataList: MutableList): WeeklyTempData { + private fun weeklyTempData(dailyTempDataList: List): WeeklyTempData { val from = dailyTempDataList.first().date val to = dailyTempDataList.last().date return WeeklyTempData(from, to, dailyTempDataList) From b7ea12eb1e4cf3e4e7efa7aa60d257cb11a7326e Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Fri, 22 Mar 2024 12:56:16 +0100 Subject: [PATCH 8/9] AverageDailyTempCalculator rename to TempCalculator --- .../service/AverageDailyTempCalculator.kt | 22 ------------------- app/src/main/kotlin/service/TempCalculator.kt | 18 +++++++++++++++ app/src/main/kotlin/service/WeatherService.kt | 4 ++-- ...alculatorTest.kt => TempCalculatorTest.kt} | 10 ++++----- .../test/kotlin/service/WeatherServiceIT.kt | 2 +- 5 files changed, 26 insertions(+), 30 deletions(-) delete mode 100644 app/src/main/kotlin/service/AverageDailyTempCalculator.kt create mode 100644 app/src/main/kotlin/service/TempCalculator.kt rename app/src/test/kotlin/service/{AverageDailyTempCalculatorTest.kt => TempCalculatorTest.kt} (80%) diff --git a/app/src/main/kotlin/service/AverageDailyTempCalculator.kt b/app/src/main/kotlin/service/AverageDailyTempCalculator.kt deleted file mode 100644 index ce2d257..0000000 --- a/app/src/main/kotlin/service/AverageDailyTempCalculator.kt +++ /dev/null @@ -1,22 +0,0 @@ -package hu.vanio.kotlin.feladat.ms.service - -import hu.vanio.kotlin.feladat.ms.data.DailyTempData -import org.springframework.stereotype.Service - -@Service -class AverageDailyTempCalculator { - fun getAverageTemp(dailyTempData: DailyTempData): Double { - if (dailyTempData.hourlyTempData.isEmpty()){ - throw IllegalArgumentException("Hourly temperature data for ${dailyTempData.date} is empty.") - } - - return getAverage(dailyTempData) - } - - private fun getAverage(dailyTempData: DailyTempData): Double { - val sum = dailyTempData.hourlyTempData.values.sum() - val size = dailyTempData.hourlyTempData.size - return sum / size - } -} - diff --git a/app/src/main/kotlin/service/TempCalculator.kt b/app/src/main/kotlin/service/TempCalculator.kt new file mode 100644 index 0000000..3339b8c --- /dev/null +++ b/app/src/main/kotlin/service/TempCalculator.kt @@ -0,0 +1,18 @@ +package hu.vanio.kotlin.feladat.ms.service + +import hu.vanio.kotlin.feladat.ms.data.DailyTempData +import org.springframework.stereotype.Service + +@Service +class TempCalculator { + fun getAverageDailyTemp(dailyTempData: DailyTempData): Double { + if (dailyTempData.hourlyTempData.isEmpty()){ + throw IllegalArgumentException("Hourly temperature data for ${dailyTempData.date} is empty.") + } + + return dailyTempData.hourlyTempData.values.average() + } + + +} + diff --git a/app/src/main/kotlin/service/WeatherService.kt b/app/src/main/kotlin/service/WeatherService.kt index 57bc5ed..5819ab7 100644 --- a/app/src/main/kotlin/service/WeatherService.kt +++ b/app/src/main/kotlin/service/WeatherService.kt @@ -12,7 +12,7 @@ import org.springframework.stereotype.Service class WeatherService( private val weatherFeignClient: WeatherFeignClient, private val weatherResponseParser: WeatherResponseParser, - private val averageDailyTempCalculator: AverageDailyTempCalculator + private val tempCalculator: TempCalculator ) { @Throws(IllegalArgumentException::class) suspend fun getWeeklyTempData(): WeeklyTempData { @@ -27,7 +27,7 @@ class WeatherService( val dailyAverageTempList = mutableListOf() val weeklyTempData = getWeeklyTempData() for (dailyTempData in weeklyTempData.dailyTempData) { - val average = averageDailyTempCalculator.getAverageTemp(dailyTempData); + val average = tempCalculator.getAverageDailyTemp(dailyTempData); dailyAverageTempList.add(DailyAverageData(dailyTempData.date, average)) } return dailyAverageTempList diff --git a/app/src/test/kotlin/service/AverageDailyTempCalculatorTest.kt b/app/src/test/kotlin/service/TempCalculatorTest.kt similarity index 80% rename from app/src/test/kotlin/service/AverageDailyTempCalculatorTest.kt rename to app/src/test/kotlin/service/TempCalculatorTest.kt index c5119c3..0dd6c6e 100644 --- a/app/src/test/kotlin/service/AverageDailyTempCalculatorTest.kt +++ b/app/src/test/kotlin/service/TempCalculatorTest.kt @@ -1,14 +1,14 @@ package service import hu.vanio.kotlin.feladat.ms.data.DailyTempData -import hu.vanio.kotlin.feladat.ms.service.AverageDailyTempCalculator +import hu.vanio.kotlin.feladat.ms.service.TempCalculator import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import java.time.LocalDate import java.time.LocalTime -class AverageDailyTempCalculatorTest { - private val calculator = AverageDailyTempCalculator() +class TempCalculatorTest { + private val calculator = TempCalculator() @Test fun `test getAverage`() { @@ -16,7 +16,7 @@ class AverageDailyTempCalculatorTest { val expectedAverage = (10.0 + 15.0 + 20.0) / 3 - val result = calculator.getAverageTemp(dailyTempData) + val result = calculator.getAverageDailyTemp(dailyTempData) assertEquals(expectedAverage, result) } @@ -29,7 +29,7 @@ class AverageDailyTempCalculatorTest { ) val exception = assertThrows(IllegalArgumentException::class.java) { - calculator.getAverageTemp(dailyTempData) + calculator.getAverageDailyTemp(dailyTempData) } assertEquals("Hourly temperature data for 2024-03-19 is empty.", exception.message) diff --git a/app/src/test/kotlin/service/WeatherServiceIT.kt b/app/src/test/kotlin/service/WeatherServiceIT.kt index 8d02527..e2d068f 100644 --- a/app/src/test/kotlin/service/WeatherServiceIT.kt +++ b/app/src/test/kotlin/service/WeatherServiceIT.kt @@ -27,7 +27,7 @@ import kotlin.test.assertNotNull @Import( WeatherService::class, WeatherResponseParser::class, - AverageDailyTempCalculator::class + TempCalculator::class ) class WeatherServiceIT { companion object { From 5c5faf66ac68d9b61549b0e5b7a09806cb10f866 Mon Sep 17 00:00:00 2001 From: "mikusik.gabor" Date: Fri, 22 Mar 2024 13:11:10 +0100 Subject: [PATCH 9/9] refactor: improving flexibility by making api parser more general --- .../kotlin/controller/WeatherController.kt | 8 +++---- .../kotlin/data/DailyTempDataContainer.kt | 5 ++++ app/src/main/kotlin/data/WeeklyTempData.kt | 5 ---- .../kotlin/parser/WeatherResponseParser.kt | 10 ++++---- app/src/main/kotlin/service/WeatherService.kt | 10 ++++---- .../parser/WeatherResponseParserTest.kt | 24 +++++++++---------- .../test/kotlin/service/WeatherServiceIT.kt | 4 ++-- 7 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 app/src/main/kotlin/data/DailyTempDataContainer.kt delete mode 100644 app/src/main/kotlin/data/WeeklyTempData.kt diff --git a/app/src/main/kotlin/controller/WeatherController.kt b/app/src/main/kotlin/controller/WeatherController.kt index 909019d..6ec6aae 100644 --- a/app/src/main/kotlin/controller/WeatherController.kt +++ b/app/src/main/kotlin/controller/WeatherController.kt @@ -1,7 +1,7 @@ package hu.vanio.kotlin.feladat.ms.controller import hu.vanio.kotlin.feladat.ms.data.DailyAverageData -import hu.vanio.kotlin.feladat.ms.data.WeeklyTempData +import hu.vanio.kotlin.feladat.ms.data.DailyTempDataContainer import hu.vanio.kotlin.feladat.ms.service.WeatherService import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -12,13 +12,13 @@ import org.springframework.web.bind.annotation.RestController @RestController class WeatherController(private val weatherService: WeatherService) { @GetMapping("/weather") - suspend fun getWeeklyTempData(): ResponseEntity { - return ResponseEntity.ok(weatherService.getWeeklyTempData()) + suspend fun getWeeklyTempData(): ResponseEntity { + return ResponseEntity.ok(weatherService.getDailyTempData()) } @GetMapping("/average") suspend fun getAverageTemps(): ResponseEntity> { - return ResponseEntity.ok(weatherService.getDailyAverageTempForOneWeek()) + return ResponseEntity.ok(weatherService.getDailyAverageTemp()) } @ExceptionHandler(IllegalArgumentException::class) diff --git a/app/src/main/kotlin/data/DailyTempDataContainer.kt b/app/src/main/kotlin/data/DailyTempDataContainer.kt new file mode 100644 index 0000000..e84bd7a --- /dev/null +++ b/app/src/main/kotlin/data/DailyTempDataContainer.kt @@ -0,0 +1,5 @@ +package hu.vanio.kotlin.feladat.ms.data + +import java.time.LocalDate + +data class DailyTempDataContainer(var from: LocalDate?, var to: LocalDate?, val dailyTempData: List) \ No newline at end of file diff --git a/app/src/main/kotlin/data/WeeklyTempData.kt b/app/src/main/kotlin/data/WeeklyTempData.kt deleted file mode 100644 index c78fb98..0000000 --- a/app/src/main/kotlin/data/WeeklyTempData.kt +++ /dev/null @@ -1,5 +0,0 @@ -package hu.vanio.kotlin.feladat.ms.data - -import java.time.LocalDate - -data class WeeklyTempData(var from: LocalDate?, var to: LocalDate?, val dailyTempData: List) \ No newline at end of file diff --git a/app/src/main/kotlin/parser/WeatherResponseParser.kt b/app/src/main/kotlin/parser/WeatherResponseParser.kt index c63904a..2274ab4 100644 --- a/app/src/main/kotlin/parser/WeatherResponseParser.kt +++ b/app/src/main/kotlin/parser/WeatherResponseParser.kt @@ -2,7 +2,7 @@ package hu.vanio.kotlin.feladat.ms.parser import hu.vanio.kotlin.feladat.ms.data.DailyTempData import hu.vanio.kotlin.feladat.ms.data.HourlyData -import hu.vanio.kotlin.feladat.ms.data.WeeklyTempData +import hu.vanio.kotlin.feladat.ms.data.DailyTempDataContainer import org.springframework.stereotype.Service import java.time.LocalDate import java.time.LocalDateTime @@ -13,11 +13,11 @@ import java.time.format.DateTimeFormatter class WeatherResponseParser { @Throws(IllegalArgumentException::class) - fun groupToWeekData(hourlyData: HourlyData): WeeklyTempData { + fun groupToDailyData(hourlyData: HourlyData): DailyTempDataContainer { val (time, temp) = validateData(hourlyData) val dailyTempData = groupByDays(time, temp) - return weeklyTempData(dailyTempData) + return dailyTempDataContainer(dailyTempData) } private fun groupByDays(time: List, temp: List): List { @@ -49,10 +49,10 @@ class WeatherResponseParser { return time to temp } - private fun weeklyTempData(dailyTempDataList: List): WeeklyTempData { + private fun dailyTempDataContainer(dailyTempDataList: List): DailyTempDataContainer { val from = dailyTempDataList.first().date val to = dailyTempDataList.last().date - return WeeklyTempData(from, to, dailyTempDataList) + return DailyTempDataContainer(from, to, dailyTempDataList) } private fun String.toDate(): LocalDate { diff --git a/app/src/main/kotlin/service/WeatherService.kt b/app/src/main/kotlin/service/WeatherService.kt index 5819ab7..ebe4159 100644 --- a/app/src/main/kotlin/service/WeatherService.kt +++ b/app/src/main/kotlin/service/WeatherService.kt @@ -1,7 +1,7 @@ package hu.vanio.kotlin.feladat.ms.service import hu.vanio.kotlin.feladat.ms.data.DailyAverageData -import hu.vanio.kotlin.feladat.ms.data.WeeklyTempData +import hu.vanio.kotlin.feladat.ms.data.DailyTempDataContainer import hu.vanio.kotlin.feladat.ms.httpclient.WeatherFeignClient import hu.vanio.kotlin.feladat.ms.parser.WeatherResponseParser import kotlinx.coroutines.Dispatchers @@ -15,17 +15,17 @@ class WeatherService( private val tempCalculator: TempCalculator ) { @Throws(IllegalArgumentException::class) - suspend fun getWeeklyTempData(): WeeklyTempData { + suspend fun getDailyTempData(): DailyTempDataContainer { val response = withContext(Dispatchers.IO) { weatherFeignClient.getWeather() } - return weatherResponseParser.groupToWeekData(response.hourly) + return weatherResponseParser.groupToDailyData(response.hourly) } @Throws(IllegalStateException::class) - suspend fun getDailyAverageTempForOneWeek(): List { + suspend fun getDailyAverageTemp(): List { val dailyAverageTempList = mutableListOf() - val weeklyTempData = getWeeklyTempData() + val weeklyTempData = getDailyTempData() for (dailyTempData in weeklyTempData.dailyTempData) { val average = tempCalculator.getAverageDailyTemp(dailyTempData); dailyAverageTempList.add(DailyAverageData(dailyTempData.date, average)) diff --git a/app/src/test/kotlin/parser/WeatherResponseParserTest.kt b/app/src/test/kotlin/parser/WeatherResponseParserTest.kt index 06dff43..214e83b 100644 --- a/app/src/test/kotlin/parser/WeatherResponseParserTest.kt +++ b/app/src/test/kotlin/parser/WeatherResponseParserTest.kt @@ -2,7 +2,7 @@ package parser import hu.vanio.kotlin.feladat.ms.data.DailyTempData import hu.vanio.kotlin.feladat.ms.data.HourlyData -import hu.vanio.kotlin.feladat.ms.data.WeeklyTempData +import hu.vanio.kotlin.feladat.ms.data.DailyTempDataContainer import hu.vanio.kotlin.feladat.ms.parser.WeatherResponseParser import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test @@ -13,56 +13,56 @@ class WeatherResponseParserTest { private val parser = WeatherResponseParser() @Test - fun `test groupToWeekData with null hourly time`() { + fun `test groupToDailyData with null hourly time`() { val hourlyData = HourlyData(null, temp()) val exception = assertThrows(IllegalArgumentException::class.java) { - parser.groupToWeekData(hourlyData) + parser.groupToDailyData(hourlyData) } assertEquals("No time data available", exception.message) } @Test - fun `test groupToWeekData with null hourly temp`() { + fun `test groupToDailyData with null hourly temp`() { val hourlyData = HourlyData(time(), null) val exception = assertThrows(IllegalArgumentException::class.java) { - parser.groupToWeekData(hourlyData) + parser.groupToDailyData(hourlyData) } assertEquals("No temp data available", exception.message) } @Test - fun `test groupToWeekData when time data is empty`() { + fun `test groupToDailyData when time data is empty`() { val hourlyData = HourlyData(emptyList(), temp()) val exception = assertThrows(IllegalArgumentException::class.java) { - parser.groupToWeekData(hourlyData) + parser.groupToDailyData(hourlyData) } assertEquals("No time data available", exception.message) } @Test - fun `test groupToWeekData when temp data is empty`() { + fun `test groupToDailyData when temp data is empty`() { val hourlyData = HourlyData(time(), emptyList()) val exception = assertThrows(IllegalArgumentException::class.java) { - parser.groupToWeekData(hourlyData) + parser.groupToDailyData(hourlyData) } assertEquals("No temp data available", exception.message) } @Test - fun `test groupToWeekData with valid data`() { + fun `test groupToDailyData with valid data`() { val hourlyData = HourlyData(time(), temp()) - val result = parser.groupToWeekData(hourlyData) + val result = parser.groupToDailyData(hourlyData) - assertEquals(WeeklyTempData::class.java, result.javaClass) + assertEquals(DailyTempDataContainer::class.java, result.javaClass) assertEquals(LocalDate.parse("2024-03-19"), result.from) assertEquals(LocalDate.parse("2024-03-20"), result.to) assertEquals(2, result.dailyTempData.size) diff --git a/app/src/test/kotlin/service/WeatherServiceIT.kt b/app/src/test/kotlin/service/WeatherServiceIT.kt index e2d068f..2c1f879 100644 --- a/app/src/test/kotlin/service/WeatherServiceIT.kt +++ b/app/src/test/kotlin/service/WeatherServiceIT.kt @@ -54,7 +54,7 @@ class WeatherServiceIT { @Test fun `test get weekly temp data`() { runBlocking { - val weeklyTempData = weatherService.getWeeklyTempData() + val weeklyTempData = weatherService.getDailyTempData() assertNotNull(weeklyTempData) } } @@ -62,7 +62,7 @@ class WeatherServiceIT { @Test fun `test get daily average temp for one week`() { runBlocking { - val dailyAverageTempForOneWeek = weatherService.getDailyAverageTempForOneWeek() + val dailyAverageTempForOneWeek = weatherService.getDailyAverageTemp() assertNotNull(dailyAverageTempForOneWeek) assertEquals(LocalDate.parse("2024-03-20"), dailyAverageTempForOneWeek[0].date) assertEquals(7.137499999999999, dailyAverageTempForOneWeek[0].average_temp)