- minor code clean up
 - Remove old api and response
 - Tests passed
 - Weather API successfully replaces
This commit is contained in:
2024-10-02 00:19:31 +01:00
committed by GitHub
parent 0aff414b1c
commit 54a7d523c0
65 changed files with 54166 additions and 1870 deletions

View File

@@ -18,8 +18,8 @@ android {
applicationId "com.appttude.h_mal.atlas_weather" applicationId "com.appttude.h_mal.atlas_weather"
minSdkVersion MIN_SDK_VERSION minSdkVersion MIN_SDK_VERSION
targetSdkVersion TARGET_SDK_VERSION targetSdkVersion TARGET_SDK_VERSION
versionCode 5 versionCode 6
versionName "3.0" versionName "3.1"
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner" testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -32,6 +32,10 @@ android {
buildConfigField "String", "ParamOne", System.getenv('WEATHER_API') buildConfigField "String", "ParamOne", System.getenv('WEATHER_API')
buildConfigField "String", "ParamTwo", System.getenv('SEARCH_API') buildConfigField "String", "ParamTwo", System.getenv('SEARCH_API')
} }
packagingOptions {
resources.excludes.add("META-INF/*")
}
} }
android { android {
sourceSets { sourceSets {

View File

@@ -1,4 +0,0 @@
{
"cod": 401,
"message": "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info."
}

View File

@@ -0,0 +1 @@
No account found with API key 'wrong api key'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -80,8 +80,8 @@ open class BaseTest<A : Activity>(
afterLaunch() afterLaunch()
} }
fun stubEndpoint(url: String, stub: Stubs, code: Int = 200) { fun stubEndpoint(url: String, stub: Stubs, code: Int = 200, extension: String = ".json") {
testApp.stubUrl(url, stub.id, code) testApp.stubUrl(url, stub.id, code, extension)
} }
fun unstubEndpoint(url: String) { fun unstubEndpoint(url: String) {
@@ -125,7 +125,6 @@ open class BaseTest<A : Activity>(
}) })
} }
@Suppress("DEPRECATION")
fun checkToastMessage(message: String) { fun checkToastMessage(message: String) {
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView))) Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
.check(ViewAssertions.matches(ViewMatchers.isDisplayed())) .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))

View File

@@ -17,7 +17,12 @@ import androidx.test.espresso.action.ViewActions.swipeDown
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.PickerActions import androidx.test.espresso.contrib.PickerActions
import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
import com.appttude.h_mal.atlas_weather.helpers.checkErrorMessage import com.appttude.h_mal.atlas_weather.helpers.checkErrorMessage
import com.appttude.h_mal.atlas_weather.helpers.checkImage import com.appttude.h_mal.atlas_weather.helpers.checkImage
@@ -60,7 +65,7 @@ open class BaseTestRobot {
.atPosition(position).perform(click()) .atPosition(position).perform(click())
} }
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? { fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction {
return matchView(recyclerId) return matchView(recyclerId)
.perform( .perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.
@@ -73,7 +78,7 @@ open class BaseTestRobot {
fun <VH : ViewHolder> scrollToRecyclerItem( fun <VH : ViewHolder> scrollToRecyclerItem(
recyclerId: Int, recyclerId: Int,
resIdForString: Int resIdForString: Int
): ViewInteraction? { ): ViewInteraction {
return matchView(recyclerId) return matchView(recyclerId)
.perform( .perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.
@@ -86,7 +91,7 @@ open class BaseTestRobot {
fun <VH : ViewHolder> scrollToRecyclerItemByPosition( fun <VH : ViewHolder> scrollToRecyclerItemByPosition(
recyclerId: Int, recyclerId: Int,
position: Int position: Int
): ViewInteraction? { ): ViewInteraction {
return matchView(recyclerId) return matchView(recyclerId)
.perform( .perform(
// scrollTo will fail the test if no item matches. // scrollTo will fail the test if no item matches.

View File

@@ -16,9 +16,9 @@ class MockingNetworkInterceptor(
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
idlingResource.increment() idlingResource.increment()
val original = chain.request() val original = chain.request()
val originalHttpUrl = original.url.toString().split("?")[0] val originalHttpUrl = original.url.toString()
feedMap[originalHttpUrl]?.let { responsePair -> feedMap[feedMap.keys.first { originalHttpUrl.contains(it) }]?.let { responsePair ->
val code = responsePair.second val code = responsePair.second
val jsonBody = responsePair.first val jsonBody = responsePair.first

View File

@@ -1,5 +1,6 @@
package com.appttude.h_mal.atlas_weather.utils package com.appttude.h_mal.atlas_weather.utils
const val baseUrl = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
enum class Stubs( enum class Stubs(
val id: String val id: String
) { ) {
@@ -7,5 +8,5 @@ enum class Stubs(
Imperial("valid_response_imperial"), Imperial("valid_response_imperial"),
WrongLocation("wrong_location_response"), WrongLocation("wrong_location_response"),
InvalidKey("invalid_api_key_response"), InvalidKey("invalid_api_key_response"),
Sydney("valid_response_metric_sydney") Sydney("valid_response_metric_sydney"),
} }

View File

@@ -49,9 +49,9 @@ class TestAppClass : AppClass() {
return database return database
} }
fun stubUrl(url: String, rawPath: String, code: Int = 200) { fun stubUrl(url: String, rawPath: String, code: Int = 200, extension: String = ".json") {
val iStream = val iStream =
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json") InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath$extension")
val data = iStream.bufferedReader().use(BufferedReader::readText) val data = iStream.bufferedReader().use(BufferedReader::readText)
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code) mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
} }

View File

@@ -13,6 +13,7 @@ import com.appttude.h_mal.atlas_weather.BaseTestRobot
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
import com.appttude.h_mal.atlas_weather.model.types.UnitType import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.model.types.UnitType.Companion.getLabel
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() } fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
@@ -24,10 +25,7 @@ class SettingsScreen : BaseTestRobot() {
RecyclerViewActions.actionOnItem<ViewHolder>( RecyclerViewActions.actionOnItem<ViewHolder>(
ViewMatchers.hasDescendant(withText(R.string.weather_units)), ViewMatchers.hasDescendant(withText(R.string.weather_units)),
click())) click()))
val label = when (unitType) { val label = unitType.getLabel()
UnitType.METRIC -> "Metric"
UnitType.IMPERIAL -> "Imperial"
}
onView(withText(label)) onView(withText(label))
.inRoot(isDialog()) .inRoot(isDialog())

View File

@@ -9,6 +9,7 @@ import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.robot.furtherInfoScreen import com.appttude.h_mal.atlas_weather.robot.furtherInfoScreen
import com.appttude.h_mal.atlas_weather.robot.settingsScreen import com.appttude.h_mal.atlas_weather.robot.settingsScreen
import com.appttude.h_mal.atlas_weather.robot.weatherScreen import com.appttude.h_mal.atlas_weather.robot.weatherScreen
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import org.junit.Test import org.junit.Test
import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.Screengrab
@@ -17,7 +18,7 @@ import tools.fastlane.screengrab.Screengrab
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) { class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
stubLocation("London", 51.51, -0.13) stubLocation("London", 51.51, -0.13)
clearPrefs() clearPrefs()
} }
@@ -50,7 +51,7 @@ class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
openMenuItem() openMenuItem()
} }
settingsScreen { settingsScreen {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial) stubEndpoint(baseUrl, Stubs.Imperial)
Screengrab.screenshot("SettingsScreen") Screengrab.screenshot("SettingsScreen")
} }
} }

View File

@@ -1,36 +1,27 @@
package com.appttude.h_mal.atlas_weather.tests package com.appttude.h_mal.atlas_weather.tests
import com.appttude.h_mal.atlas_weather.BaseTest import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.robot.homeScreen
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.robot.homeScreen import com.appttude.h_mal.atlas_weather.utils.baseUrl
import org.junit.Test import org.junit.Test
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) { class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.InvalidKey, 400) stubEndpoint(baseUrl, Stubs.InvalidKey, 400, ".txt")
}
@Test
fun loadApp_invalidKeyWeatherResponse_returnsEmptyViewPage() {
homeScreen {
waitFor(2000)
// verify empty
verifyUnableToRetrieve()
}
} }
@Test @Test
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() { fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
homeScreen { homeScreen {
waitFor(2000)
// verify empty // verify empty
verifyUnableToRetrieve() verifyUnableToRetrieve()
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
refresh() refresh()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }
} }

View File

@@ -5,19 +5,20 @@ import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.robot.homeScreen import com.appttude.h_mal.atlas_weather.robot.homeScreen
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import org.junit.Test import org.junit.Test
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) { class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
} }
@Test @Test
fun loadApp_validWeatherResponse_returnsValidPage() { fun loadApp_validWeatherResponse_returnsValidPage() {
homeScreen { homeScreen {
isDisplayed() isDisplayed()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }
} }

View File

@@ -18,7 +18,6 @@ import org.kodein.di.LazyKodein
import java.io.BufferedReader import java.io.BufferedReader
class TestAppClass : AppClass() { class TestAppClass : AppClass() {
override val kodein: LazyKodein = super.kodein override val kodein: LazyKodein = super.kodein
private val idlingResources = CountingIdlingResource("Data_loader") private val idlingResources = CountingIdlingResource("Data_loader")
@@ -53,9 +52,9 @@ class TestAppClass : AppClass() {
return database return database
} }
fun stubUrl(url: String, rawPath: String, code: Int = 200) { fun stubUrl(url: String, rawPath: String, code: Int = 200, extension: String = ".json") {
val iStream = val iStream =
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json") InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath$extension")
val data = iStream.bufferedReader().use(BufferedReader::readText) val data = iStream.bufferedReader().use(BufferedReader::readText)
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code) mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
} }

View File

@@ -6,10 +6,10 @@ import androidx.test.filters.SmallTest
import com.appttude.h_mal.atlas_weather.BaseTest import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen
import com.appttude.h_mal.monoWeather.robot.settingsScreen import com.appttude.h_mal.monoWeather.robot.settingsScreen
import com.appttude.h_mal.monoWeather.robot.weatherScreen import com.appttude.h_mal.monoWeather.robot.weatherScreen
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.Screengrab
@@ -18,8 +18,8 @@ import tools.fastlane.screengrab.Screengrab
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) { class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
stubLocation("London", 51.51, -0.13) stubLocation("London", 51.5064, -0.12721)
clearPrefs() clearPrefs()
} }
@@ -52,7 +52,7 @@ class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
openMenuItem() openMenuItem()
} }
settingsScreen { settingsScreen {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial) stubEndpoint(baseUrl, Stubs.Imperial)
Screengrab.screenshot("SettingsScreen") Screengrab.screenshot("SettingsScreen")
} }
} }

View File

@@ -13,6 +13,7 @@ import com.appttude.h_mal.atlas_weather.BaseTestRobot
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
import com.appttude.h_mal.atlas_weather.model.types.UnitType import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.model.types.UnitType.Companion.getLabel
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() } fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
@@ -24,10 +25,7 @@ class SettingsScreen : BaseTestRobot() {
RecyclerViewActions.actionOnItem<ViewHolder>( RecyclerViewActions.actionOnItem<ViewHolder>(
ViewMatchers.hasDescendant(withText(R.string.weather_units)), ViewMatchers.hasDescendant(withText(R.string.weather_units)),
click())) click()))
val label = when (unitType) { val label = unitType.getLabel()
UnitType.METRIC -> "Metric"
UnitType.IMPERIAL -> "Imperial"
}
onView(withText(label)) onView(withText(label))
.inRoot(isDialog()) .inRoot(isDialog())

View File

@@ -3,7 +3,6 @@ package com.appttude.h_mal.monoWeather.robot
import com.appttude.h_mal.atlas_weather.BaseTestRobot import com.appttude.h_mal.atlas_weather.BaseTestRobot
import com.appttude.h_mal.atlas_weather.R import com.appttude.h_mal.atlas_weather.R
import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
import com.appttude.h_mal.monoWeather.ui.home.adapter.further.ViewHolderFurtherDetails
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() } fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
class WeatherScreen : BaseTestRobot() { class WeatherScreen : BaseTestRobot() {

View File

@@ -4,35 +4,30 @@ package com.appttude.h_mal.monoWeather.tests
import com.appttude.h_mal.atlas_weather.BaseTest import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.weatherScreen import com.appttude.h_mal.monoWeather.robot.weatherScreen
import org.junit.Ignore import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runners.MethodSorters
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) { class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.InvalidKey, 400) stubEndpoint(baseUrl, Stubs.InvalidKey, 400, ".txt")
} }
@Test
fun loadApp_invalidKeyWeatherResponse_returnsEmptyViewPage() {
weatherScreen {
// verify empty
verifyUnableToRetrieve()
}
}
@Ignore("Test is flakey - must investigate")
@Test @Test
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() { fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
weatherScreen { weatherScreen {
// verify empty // verify empty
verifyUnableToRetrieve() verifyUnableToRetrieve()
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
refresh() refresh()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }
} }
} }

View File

@@ -2,20 +2,17 @@ package com.appttude.h_mal.monoWeather.tests
import com.appttude.h_mal.atlas_weather.BaseTest import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen
import com.appttude.h_mal.monoWeather.robot.settingsScreen
import com.appttude.h_mal.monoWeather.robot.weatherScreen import com.appttude.h_mal.monoWeather.robot.weatherScreen
import okio.IOException
import org.junit.Test import org.junit.Test
import tools.fastlane.screengrab.Screengrab
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) { class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
clearPrefs() clearPrefs()
} }
@@ -23,7 +20,7 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
fun loadApp_validWeatherResponse_returnsValidPage() { fun loadApp_validWeatherResponse_returnsValidPage() {
weatherScreen { weatherScreen {
isDisplayed() isDisplayed()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
} }
} }
@@ -32,35 +29,15 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
fun loadApp_validWeatherResponse_viewFurtherDetailsPage() { fun loadApp_validWeatherResponse_viewFurtherDetailsPage() {
weatherScreen { weatherScreen {
isDisplayed() isDisplayed()
verifyCurrentTemperature(2) verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location") verifyCurrentLocation("Mock Location")
tapDayInformationByPosition(4) tapDayInformationByPosition(4)
} }
furtherInfoScreen { furtherInfoScreen {
isDisplayed() isDisplayed()
verifyMaxTemperature(12) verifyMaxTemperature(15)
verifyAverageTemperature(9) verifyAverageTemperature(11)
} }
} }
@Test
fun loadApp_changeToImperial_returnsValidPage() {
weatherScreen {
isDisplayed()
verifyCurrentTemperature(2)
verifyCurrentLocation("Mock Location")
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial)
openMenuItem()
}
settingsScreen {
selectWeatherUnits(UnitType.IMPERIAL)
goBack()
}
weatherScreen {
isDisplayed()
refresh()
verifyCurrentTemperature(58)
verifyCurrentLocation("Mock Location")
}
}
} }

View File

@@ -0,0 +1,40 @@
package com.appttude.h_mal.monoWeather.tests
import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.settingsScreen
import com.appttude.h_mal.monoWeather.robot.weatherScreen
import org.junit.Test
class SettingsPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() {
stubEndpoint(baseUrl, Stubs.Metric)
clearPrefs()
}
@Test
fun loadApp_changeToImperial_returnsValidPage() {
weatherScreen {
isDisplayed()
verifyCurrentTemperature(13)
verifyCurrentLocation("Mock Location")
stubEndpoint(baseUrl, Stubs.Imperial)
openMenuItem()
}
settingsScreen {
selectWeatherUnits(UnitType.IMPERIAL)
goBack()
}
weatherScreen {
isDisplayed()
refresh()
verifyCurrentTemperature(56)
verifyCurrentLocation("Mock Location")
}
}
}

View File

@@ -4,6 +4,7 @@ package com.appttude.h_mal.monoWeather.tests
import com.appttude.h_mal.atlas_weather.BaseTest import com.appttude.h_mal.atlas_weather.BaseTest
import com.appttude.h_mal.atlas_weather.ui.MainActivity import com.appttude.h_mal.atlas_weather.ui.MainActivity
import com.appttude.h_mal.atlas_weather.utils.Stubs import com.appttude.h_mal.atlas_weather.utils.Stubs
import com.appttude.h_mal.atlas_weather.utils.baseUrl
import com.appttude.h_mal.monoWeather.robot.ContainerRobot.Tab.WORLD import com.appttude.h_mal.monoWeather.robot.ContainerRobot.Tab.WORLD
import com.appttude.h_mal.monoWeather.robot.addLocation import com.appttude.h_mal.monoWeather.robot.addLocation
import com.appttude.h_mal.monoWeather.robot.container import com.appttude.h_mal.monoWeather.robot.container
@@ -14,7 +15,8 @@ import org.junit.Test
class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) { class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
override fun beforeLaunch() { override fun beforeLaunch() {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric) stubEndpoint(baseUrl, Stubs.Metric)
stubLocation("London", 51.5064,-0.12721)
} }
@Test @Test
@@ -26,8 +28,8 @@ class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
clickFab() clickFab()
} }
addLocation { addLocation {
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Sydney) stubEndpoint(baseUrl, Stubs.Sydney)
stubLocation("Sydney", -33.89, -151.12) stubLocation("Sydney",-33.8696,151.207)
setLocation("Sydney") setLocation("Sydney")
submit() submit()
} }
@@ -36,7 +38,7 @@ class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
} }
weatherScreen { weatherScreen {
isDisplayed() isDisplayed()
verifyCurrentTemperature(12) verifyCurrentTemperature(16)
verifyCurrentLocation("Sydney") verifyCurrentLocation("Sydney")
} }
} }

View File

@@ -3,6 +3,7 @@ package com.appttude.h_mal.atlas_weather.application
import android.app.Application import android.app.Application
import com.appttude.h_mal.atlas_weather.data.WeatherSource import com.appttude.h_mal.atlas_weather.data.WeatherSource
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
import com.appttude.h_mal.atlas_weather.data.network.Api
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
@@ -12,7 +13,6 @@ import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
import com.google.gson.Gson import com.google.gson.Gson
import org.kodein.di.Kodein import org.kodein.di.Kodein
import org.kodein.di.KodeinAware import org.kodein.di.KodeinAware
import org.kodein.di.KodeinContainer
import org.kodein.di.android.x.androidXModule import org.kodein.di.android.x.androidXModule
import org.kodein.di.generic.bind import org.kodein.di.generic.bind
import org.kodein.di.generic.instance import org.kodein.di.generic.instance
@@ -29,7 +29,7 @@ abstract class BaseAppClass : Application(), KodeinAware {
val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) { val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) {
import(androidXModule(this@BaseAppClass)) import(androidXModule(this@BaseAppClass))
bind() from singleton { createNetworkModule() } bind() from singleton { createNetworkModule() as WeatherApi }
bind() from singleton { createLocationModule() } bind() from singleton { createLocationModule() }
bind() from singleton { Gson() } bind() from singleton { Gson() }
@@ -41,7 +41,7 @@ abstract class BaseAppClass : Application(), KodeinAware {
bind() from singleton { WeatherSource(instance(), instance()) } bind() from singleton { WeatherSource(instance(), instance()) }
} }
abstract fun createNetworkModule(): WeatherApi abstract fun createNetworkModule(): Api
abstract fun createLocationModule(): LocationProvider abstract fun createLocationModule(): LocationProvider
abstract fun createRoomDatabase(): AppDatabase abstract fun createRoomDatabase(): AppDatabase

View File

@@ -5,8 +5,8 @@ import com.appttude.h_mal.atlas_weather.data.repository.Repository
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
import com.appttude.h_mal.atlas_weather.model.types.LocationType import com.appttude.h_mal.atlas_weather.model.types.LocationType
import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
import com.appttude.h_mal.atlas_weather.utils.getSymbol
import java.io.IOException import java.io.IOException
class WeatherSource( class WeatherSource(
@@ -33,12 +33,14 @@ class WeatherSource(
} }
@Throws(IOException::class) @Throws(IOException::class)
suspend fun forceFetchWeather(latLon: Pair<Double, Double>, suspend fun forceFetchWeather(
locationType: LocationType = LocationType.Town): FullWeather { latLon: Pair<Double, Double>,
locationType: LocationType = LocationType.Town
): FullWeather {
// get data from database // get data from database
val weatherEntity = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION) val weatherEntity = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
// check unit type - if same do nothing // check unit type - if same do nothing
val units = if (repository.getUnitType() == UnitType.METRIC) "°C" else "°F" val units = repository.getUnitType().getSymbol()
if (weatherEntity.weather.temperatureUnit == units) return weatherEntity.weather if (weatherEntity.weather.temperatureUnit == units) return weatherEntity.weather
// load data for forced // load data for forced
return fetchWeather( return fetchWeather(
@@ -55,11 +57,13 @@ class WeatherSource(
// Get weather from api // Get weather from api
val weather = repository val weather = repository
.getWeatherFromApi(latLon.first.toString(), latLon.second.toString()) .getWeatherFromApi(latLon.first.toString(), latLon.second.toString())
val lat = weather.latitude ?: latLon.first
val long = weather.longitude ?: latLon.second
val currentLocation = val currentLocation =
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon, locationType) locationProvider.getLocationNameFromLatLong(lat, long, locationType)
val unit = repository.getUnitType() val unit = repository.getUnitType().getSymbol()
val fullWeather = FullWeather(weather).apply { val fullWeather = weather.mapData().apply {
temperatureUnit = if (unit == UnitType.METRIC) "°C" else "°F" temperatureUnit = unit
locationString = currentLocation locationString = currentLocation
} }
val entityItem = EntityItem(locationName, fullWeather) val entityItem = EntityItem(locationName, fullWeather)

View File

@@ -1,6 +1,6 @@
package com.appttude.h_mal.atlas_weather.data.network package com.appttude.h_mal.atlas_weather.data.network
class NetworkModule : BaseNetworkModule() { class NetworkModule : BaseNetworkModule() {
override fun baseUrl(): String = "https://api.openweathermap.org/data/2.5/" override fun baseUrl(): String = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
} }

View File

@@ -1,9 +1,7 @@
package com.appttude.h_mal.atlas_weather.data.network package com.appttude.h_mal.atlas_weather.data.network
import org.json.JSONException import retrofit2.HttpException
import org.json.JSONObject
import retrofit2.Response import retrofit2.Response
import java.io.IOException
abstract class ResponseUnwrap { abstract class ResponseUnwrap {
@@ -15,18 +13,7 @@ abstract class ResponseUnwrap {
if (response.isSuccessful) { if (response.isSuccessful) {
return response.body()!! return response.body()!!
} else { } else {
val error = response.errorBody()?.string() throw HttpException(response)
val errorMessage = error?.let {
try {
JSONObject(it).getString("message")
} catch (e: JSONException) {
e.printStackTrace()
null
}
} ?: "Error Code: ${response.code()}"
throw IOException(errorMessage)
} }
} }
} }

View File

@@ -1,20 +1,20 @@
package com.appttude.h_mal.atlas_weather.data.network package com.appttude.h_mal.atlas_weather.data.network
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import retrofit2.Response import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
interface WeatherApi : Api { interface WeatherApi : Api {
@GET("onecall?") @GET("{location}")
suspend fun getFromApi( suspend fun getFromApi(
@Query("lat") query: String, @Path("location") location: String,
@Query("lon") lon: String, @Query("contentType") exclude: String = "json",
@Query("exclude") exclude: String = "minutely", @Query("unitGroup") units: String = "metric"
@Query("units") units: String = "metric" ): Response<WeatherApiResponse>
): Response<WeatherResponse>
} }

View File

@@ -16,7 +16,7 @@ class QueryParamsInterceptor : Interceptor {
val original = chain.request() val original = chain.request()
val url = original.url.newBuilder() val url = original.url.newBuilder()
.addQueryParameter("appid", id) .addQueryParameter("key", id)
.build() .build()
// Request customization: add request headers // Request customization: add request headers

View File

@@ -1,11 +1,13 @@
package com.appttude.h_mal.atlas_weather.data.network.networkUtils package com.appttude.h_mal.atlas_weather.data.network.networkUtils
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor
import com.google.gson.GsonBuilder
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.lang.reflect.Modifier
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
val loggingInterceptor = HttpLoggingInterceptor().apply { val loggingInterceptor = HttpLoggingInterceptor().apply {
@@ -34,6 +36,13 @@ fun buildOkHttpClient(
return builder.build() return builder.build()
} }
fun createGsonConverterFactory(): GsonConverterFactory {
val gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.create()
return GsonConverterFactory.create(gson)
}
fun <T> createRetrofit( fun <T> createRetrofit(
baseUrl: String, baseUrl: String,
okHttpClient: OkHttpClient, okHttpClient: OkHttpClient,
@@ -42,7 +51,7 @@ fun <T> createRetrofit(
return Retrofit.Builder() return Retrofit.Builder()
.client(okHttpClient) .client(okHttpClient)
.baseUrl(baseUrl) .baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(createGsonConverterFactory())
.build() .build()
.create(service) .create(service)
} }

View File

@@ -1,48 +0,0 @@
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
import com.google.gson.annotations.SerializedName
data class Current(
@field:SerializedName("sunrise")
val sunrise: Int? = null,
@field:SerializedName("temp")
val temp: Double? = null,
@field:SerializedName("visibility")
val visibility: Int? = null,
@field:SerializedName("uvi")
val uvi: Double? = null,
@field:SerializedName("pressure")
val pressure: Int? = null,
@field:SerializedName("clouds")
val clouds: Int? = null,
@field:SerializedName("feels_like")
val feelsLike: Double? = null,
@field:SerializedName("dt")
val dt: Int? = null,
@field:SerializedName("wind_deg")
val windDeg: Int? = null,
@field:SerializedName("dew_point")
val dewPoint: Double? = null,
@field:SerializedName("sunset")
val sunset: Int? = null,
@field:SerializedName("weather")
val weather: List<WeatherItem?>? = null,
@field:SerializedName("humidity")
val humidity: Int? = null,
@field:SerializedName("wind_speed")
val windSpeed: Double? = null
)

View File

@@ -1,51 +0,0 @@
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
import com.google.gson.annotations.SerializedName
data class DailyItem(
@field:SerializedName("sunrise")
val sunrise: Int? = null,
@field:SerializedName("temp")
val temp: Temp? = null,
@field:SerializedName("uvi")
val uvi: Double? = null,
@field:SerializedName("pressure")
val pressure: Int? = null,
@field:SerializedName("clouds")
val clouds: Int? = null,
@field:SerializedName("feels_like")
val feelsLike: FeelsLike? = null,
@field:SerializedName("dt")
val dt: Int? = null,
@field:SerializedName("pop")
val pop: Double? = null,
@field:SerializedName("wind_deg")
val windDeg: Int? = null,
@field:SerializedName("dew_point")
val dewPoint: Double? = null,
@field:SerializedName("sunset")
val sunset: Int? = null,
@field:SerializedName("weather")
val weather: List<WeatherItem?>? = null,
@field:SerializedName("humidity")
val humidity: Int? = null,
@field:SerializedName("wind_speed")
val windSpeed: Double? = null,
@field:SerializedName("rain")
val rain: Double? = null
)

View File

@@ -1,18 +0,0 @@
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
import com.google.gson.annotations.SerializedName
data class FeelsLike(
@field:SerializedName("eve")
val eve: Double? = null,
@field:SerializedName("night")
val night: Double? = null,
@field:SerializedName("day")
val day: Double? = null,
@field:SerializedName("morn")
val morn: Double? = null
)

View File

@@ -1,48 +0,0 @@
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
import com.google.gson.annotations.SerializedName
data class Hour(
@field:SerializedName("sunrise")
val sunrise: Int? = null,
@field:SerializedName("temp")
val temp: Double? = null,
@field:SerializedName("visibility")
val visibility: Int? = null,
@field:SerializedName("uvi")
val uvi: Double? = null,
@field:SerializedName("pressure")
val pressure: Int? = null,
@field:SerializedName("clouds")
val clouds: Int? = null,
@field:SerializedName("feels_like")
val feelsLike: Double? = null,
@field:SerializedName("dt")
val dt: Int? = null,
@field:SerializedName("wind_deg")
val windDeg: Int? = null,
@field:SerializedName("dew_point")
val dewPoint: Double? = null,
@field:SerializedName("sunset")
val sunset: Int? = null,
@field:SerializedName("weather")
val weather: List<WeatherItem?>? = null,
@field:SerializedName("humidity")
val humidity: Int? = null,
@field:SerializedName("wind_speed")
val windSpeed: Double? = null
)

View File

@@ -1,24 +0,0 @@
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
import com.google.gson.annotations.SerializedName
data class Response(
@field:SerializedName("current")
val current: Current? = null,
@field:SerializedName("timezone")
val timezone: String? = null,
@field:SerializedName("timezone_offset")
val timezoneOffset: Int? = null,
@field:SerializedName("daily")
val daily: List<DailyItem?>? = null,
@field:SerializedName("lon")
val lon: Double? = null,
@field:SerializedName("lat")
val lat: Double? = null
)

View File

@@ -1,24 +0,0 @@
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
import com.google.gson.annotations.SerializedName
data class Temp(
@field:SerializedName("min")
val min: Double? = null,
@field:SerializedName("max")
val max: Double? = null,
@field:SerializedName("eve")
val eve: Double? = null,
@field:SerializedName("night")
val night: Double? = null,
@field:SerializedName("day")
val day: Double? = null,
@field:SerializedName("morn")
val morn: Double? = null
)

View File

@@ -1,18 +0,0 @@
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
import com.google.gson.annotations.SerializedName
data class WeatherItem(
@field:SerializedName("icon")
val icon: String? = null,
@field:SerializedName("description")
val description: String? = null,
@field:SerializedName("main")
val main: String? = null,
@field:SerializedName("id")
val id: Int? = null
)

View File

@@ -1,28 +0,0 @@
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
import com.google.gson.annotations.SerializedName
data class WeatherResponse(
@field:SerializedName("current")
val current: Current? = null,
@field:SerializedName("timezone")
val timezone: String? = null,
@field:SerializedName("timezone_offset")
val timezoneOffset: Int? = null,
@field:SerializedName("hourly")
val hourly: List<Hour>? = null,
@field:SerializedName("daily")
val daily: List<DailyItem>? = null,
@field:SerializedName("lon")
val lon: Double = 0.00,
@field:SerializedName("lat")
val lat: Double = 0.00
)

View File

@@ -0,0 +1,16 @@
package com.appttude.h_mal.atlas_weather.data.network.response.weather
import com.google.gson.annotations.SerializedName
data class Alerts(
@SerializedName("event") var event: String? = null,
@SerializedName("headline") var headline: String? = null,
@SerializedName("ends") var ends: String? = null,
@SerializedName("endsEpoch") var endsEpoch: Int? = null,
@SerializedName("onset") var onset: String? = null,
@SerializedName("onsetEpoch") var onsetEpoch: Int? = null,
@SerializedName("id") var id: String? = null,
@SerializedName("language") var language: String? = null,
@SerializedName("link") var link: String? = null,
@SerializedName("description") var description: String? = null,
)

View File

@@ -0,0 +1,36 @@
package com.appttude.h_mal.atlas_weather.data.network.response.weather
import com.google.gson.annotations.SerializedName
data class CurrentConditions(
@SerializedName("datetime") var datetime: String? = null,
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
@SerializedName("temp") var temp: Double? = null,
@SerializedName("feelslike") var feelslike: Double? = null,
@SerializedName("humidity") var humidity: Double? = null,
@SerializedName("dew") var dew: Double? = null,
@SerializedName("precip") var precip: Double? = null,
@SerializedName("precipprob") var precipprob: Double? = null,
@SerializedName("snow") var snow: Int? = null,
@SerializedName("snowdepth") var snowdepth: Int? = null,
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
@SerializedName("windgust") var windgust: Double? = null,
@SerializedName("windspeed") var windspeed: Double? = null,
@SerializedName("winddir") var winddir: Double? = null,
@SerializedName("pressure") var pressure: Double? = null,
@SerializedName("visibility") var visibility: Double? = null,
@SerializedName("cloudcover") var cloudcover: Double? = null,
@SerializedName("solarradiation") var solarradiation: Double? = null,
@SerializedName("solarenergy") var solarenergy: Double? = null,
@SerializedName("uvindex") var uvindex: Int? = null,
@SerializedName("conditions") var conditions: String? = null,
@SerializedName("icon") var icon: String? = null,
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
@SerializedName("source") var source: String? = null,
@SerializedName("sunrise") var sunrise: String? = null,
@SerializedName("sunriseEpoch") var sunriseEpoch: Int? = null,
@SerializedName("sunset") var sunset: String? = null,
@SerializedName("sunsetEpoch") var sunsetEpoch: Int? = null,
@SerializedName("moonphase") var moonphase: Double? = null
)

View File

@@ -0,0 +1,45 @@
package com.appttude.h_mal.atlas_weather.data.network.response.weather
import com.google.gson.annotations.SerializedName
data class Days(
@SerializedName("datetime") var datetime: String? = null,
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
@SerializedName("tempmax") var tempmax: Double? = null,
@SerializedName("tempmin") var tempmin: Double? = null,
@SerializedName("temp") var temp: Double? = null,
@SerializedName("feelslikemax") var feelslikemax: Double? = null,
@SerializedName("feelslikemin") var feelslikemin: Double? = null,
@SerializedName("feelslike") var feelslike: Double? = null,
@SerializedName("dew") var dew: Double? = null,
@SerializedName("humidity") var humidity: Double? = null,
@SerializedName("precip") var precip: Number? = null,
@SerializedName("precipprob") var precipprob: Double? = null,
@SerializedName("precipcover") var precipcover: Double? = null,
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
@SerializedName("snow") var snow: Int? = null,
@SerializedName("snowdepth") var snowdepth: Int? = null,
@SerializedName("windgust") var windgust: Double? = null,
@SerializedName("windspeed") var windspeed: Double? = null,
@SerializedName("winddir") var winddir: Double? = null,
@SerializedName("pressure") var pressure: Double? = null,
@SerializedName("cloudcover") var cloudcover: Double? = null,
@SerializedName("visibility") var visibility: Double? = null,
@SerializedName("solarradiation") var solarradiation: Double? = null,
@SerializedName("solarenergy") var solarenergy: Double? = null,
@SerializedName("uvindex") var uvindex: Int? = null,
@SerializedName("severerisk") var severerisk: Int? = null,
@SerializedName("sunrise") var sunrise: String? = null,
@SerializedName("sunriseEpoch") var sunriseEpoch: Int? = null,
@SerializedName("sunset") var sunset: String? = null,
@SerializedName("sunsetEpoch") var sunsetEpoch: Int? = null,
@SerializedName("moonphase") var moonphase: Double? = null,
@SerializedName("conditions") var conditions: String? = null,
@SerializedName("description") var description: String? = null,
@SerializedName("icon") var icon: String? = null,
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
@SerializedName("source") var source: String? = null,
@SerializedName("hours") var hours: ArrayList<Hours> = arrayListOf()
)

View File

@@ -0,0 +1,32 @@
package com.appttude.h_mal.atlas_weather.data.network.response.weather
import com.google.gson.annotations.SerializedName
data class Hours(
@SerializedName("datetime") var datetime: String? = null,
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
@SerializedName("temp") var temp: Double? = null,
@SerializedName("feelslike") var feelslike: Double? = null,
@SerializedName("humidity") var humidity: Double? = null,
@SerializedName("dew") var dew: Double? = null,
@SerializedName("precip") var precip: Number? = null,
@SerializedName("precipprob") var precipprob: Double? = null,
@SerializedName("snow") var snow: Int? = null,
@SerializedName("snowdepth") var snowdepth: Int? = null,
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
@SerializedName("windgust") var windgust: Double? = null,
@SerializedName("windspeed") var windspeed: Double? = null,
@SerializedName("winddir") var winddir: Double? = null,
@SerializedName("pressure") var pressure: Double? = null,
@SerializedName("visibility") var visibility: Double? = null,
@SerializedName("cloudcover") var cloudcover: Double? = null,
@SerializedName("solarradiation") var solarradiation: Double? = null,
@SerializedName("solarenergy") var solarenergy: Double? = null,
@SerializedName("uvindex") var uvindex: Int? = null,
@SerializedName("severerisk") var severerisk: Int? = null,
@SerializedName("conditions") var conditions: String? = null,
@SerializedName("icon") var icon: String? = null,
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
@SerializedName("source") var source: String? = null
)

View File

@@ -0,0 +1,40 @@
package com.appttude.h_mal.atlas_weather.data.network.response.weather
import com.appttude.h_mal.atlas_weather.model.DataMapper
import com.appttude.h_mal.atlas_weather.model.weather.Current
import com.appttude.h_mal.atlas_weather.model.weather.DailyWeather
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
import com.google.gson.annotations.SerializedName
import com.appttude.h_mal.atlas_weather.model.weather.Hour as FullWeatherHour
data class WeatherApiResponse(
@SerializedName("queryCost") var queryCost: Int? = null,
@SerializedName("latitude") var latitude: Double? = null,
@SerializedName("longitude") var longitude: Double? = null,
@SerializedName("resolvedAddress") var resolvedAddress: String? = null,
@SerializedName("address") var address: String? = null,
@SerializedName("timezone") var timezone: String? = null,
@SerializedName("tzoffset") var tzoffset: Int? = null,
@SerializedName("description") var description: String? = null,
@SerializedName("days") var days: ArrayList<Days> = arrayListOf(),
@SerializedName("alerts") var alerts: ArrayList<Alerts> = arrayListOf(),
@SerializedName("currentConditions") var currentConditions: CurrentConditions? = CurrentConditions()
): DataMapper<FullWeather> {
override fun mapData(): FullWeather {
val hours = mutableListOf(days[0].hours).apply { add(days[1].hours) }.flatten().subList(0,23).map { FullWeatherHour(it) }.toList()
val collectedDays = mutableListOf(days.subList(0,7)).flatten().map { DailyWeather(it) }.toList()
return FullWeather(
current = Current(currentConditions),
timezone = timezone,
timezoneOffset = tzoffset,
hourly = hours,
daily = collectedDays,
lat = latitude,
lon = longitude,
locationString = address,
temperatureUnit = null
)
}
}

View File

@@ -1,13 +1,13 @@
package com.appttude.h_mal.atlas_weather.data.repository package com.appttude.h_mal.atlas_weather.data.repository
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
import com.appttude.h_mal.atlas_weather.model.types.UnitType import com.appttude.h_mal.atlas_weather.model.types.UnitType
interface Repository { interface Repository {
suspend fun getWeatherFromApi(lat: String, long: String): WeatherResponse suspend fun getWeatherFromApi(lat: String, long: String): WeatherApiResponse
suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem)
suspend fun saveWeatherListToRoom(list: List<EntityItem>) suspend fun saveWeatherListToRoom(list: List<EntityItem>)
fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>> fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>>

View File

@@ -2,7 +2,7 @@ package com.appttude.h_mal.atlas_weather.data.repository
import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
@@ -20,8 +20,9 @@ class RepositoryImpl(
override suspend fun getWeatherFromApi( override suspend fun getWeatherFromApi(
lat: String, lat: String,
long: String long: String
): WeatherResponse { ): WeatherApiResponse {
return responseUnwrap { api.getFromApi(lat, long, units = prefs.getUnitsType().name.lowercase()) } val unit = if (prefs.getUnitsType() == UnitType.METRIC) "metric" else "us"
return responseUnwrap { api.getFromApi(location = "$lat,$long", units = unit) }
} }
override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) { override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) {

View File

@@ -11,14 +11,13 @@ import com.appttude.h_mal.atlas_weather.data.repository.Repository
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
import com.appttude.h_mal.atlas_weather.model.types.UnitType
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetCellData import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetCellData
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetData import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetData
import com.appttude.h_mal.atlas_weather.model.widget.WidgetData import com.appttude.h_mal.atlas_weather.model.widget.WidgetData
import com.appttude.h_mal.atlas_weather.model.widget.WidgetError import com.appttude.h_mal.atlas_weather.model.widget.WidgetError
import com.appttude.h_mal.atlas_weather.model.widget.WidgetState import com.appttude.h_mal.atlas_weather.model.widget.WidgetState
import com.appttude.h_mal.atlas_weather.model.widget.WidgetWeatherCollection import com.appttude.h_mal.atlas_weather.model.widget.WidgetWeatherCollection
import com.appttude.h_mal.atlas_weather.utils.getSymbol
import com.appttude.h_mal.atlas_weather.utils.toSmallDayName import com.appttude.h_mal.atlas_weather.utils.toSmallDayName
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import com.squareup.picasso.Target import com.squareup.picasso.Target
@@ -45,9 +44,11 @@ class ServicesHelper(
// Get weather from api // Get weather from api
val weather = repository val weather = repository
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString()) .getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
val lat = weather.latitude ?: latLong.first
val long = weather.longitude ?: latLong.second
val currentLocation = val currentLocation =
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon) locationProvider.getLocationNameFromLatLong(lat, long)
val fullWeather = FullWeather(weather).apply { val fullWeather = weather.mapData().apply {
temperatureUnit = "°C" temperatureUnit = "°C"
locationString = currentLocation locationString = currentLocation
} }
@@ -105,8 +106,11 @@ class ServicesHelper(
return WidgetState.HasError(error) return WidgetState.HasError(error)
} }
val lat = weather.latitude ?: latLong.first
val long = weather.longitude ?: latLong.second
val currentLocation = try { val currentLocation = try {
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon) locationProvider.getLocationNameFromLatLong(lat, long)
} catch (e: IOException) { } catch (e: IOException) {
val data = getWidgetWeatherCollection() val data = getWidgetWeatherCollection()
data?.let { data?.let {
@@ -120,8 +124,8 @@ class ServicesHelper(
return WidgetState.HasError(error) return WidgetState.HasError(error)
} }
val fullWeather = FullWeather(weather).apply { val fullWeather = weather.mapData().apply {
temperatureUnit = if (repository.getUnitType() == UnitType.METRIC) "°C" else "°F" temperatureUnit = repository.getUnitType().getSymbol()
locationString = currentLocation locationString = currentLocation
} }
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather) val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
@@ -140,7 +144,7 @@ class ServicesHelper(
result.weather.let { result.weather.let {
val bitmap = it.current?.icon val bitmap = it.current?.icon
val location = locationProvider.getLocationNameFromLatLong(it.lat, it.lon) val location = locationProvider.getLocationNameFromLatLong(it.lat!!, it.lon!!)
val temp = it.current?.temp?.toInt().toString() val temp = it.current?.temp?.toInt().toString()
WidgetData(location, bitmap, temp, epoc) WidgetData(location, bitmap, temp, epoc)
@@ -177,7 +181,7 @@ class ServicesHelper(
val widgetData = result.weather.let { val widgetData = result.weather.let {
val bitmap = it.current?.icon val bitmap = it.current?.icon
val location = locationProvider.getLocationNameFromLatLong(it.lat, it.lon) val location = locationProvider.getLocationNameFromLatLong(it.lat!!, it.lon!!)
val temp = it.current?.temp?.toInt().toString() val temp = it.current?.temp?.toInt().toString()
val epoc = System.currentTimeMillis() val epoc = System.currentTimeMillis()
@@ -186,7 +190,7 @@ class ServicesHelper(
val list = mutableListOf<InnerWidgetCellData>() val list = mutableListOf<InnerWidgetCellData>()
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather -> result.weather.daily?.drop(1)?.dropLast(1)?.forEach { dailyWeather ->
val day = dailyWeather.dt?.toSmallDayName() val day = dailyWeather.dt?.toSmallDayName()
val icon = dailyWeather.icon val icon = dailyWeather.icon
val temp = dailyWeather.max?.toInt().toString() val temp = dailyWeather.max?.toInt().toString()
@@ -216,7 +220,7 @@ class ServicesHelper(
val list = mutableListOf<InnerWidgetCellData>() val list = mutableListOf<InnerWidgetCellData>()
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather -> result.weather.daily?.drop(1)?.dropLast(1)?.forEach { dailyWeather ->
val day = dailyWeather.dt?.toSmallDayName() val day = dailyWeather.dt?.toSmallDayName()
val icon = dailyWeather.icon val icon = dailyWeather.icon
val temp = dailyWeather.max?.toInt().toString() val temp = dailyWeather.max?.toInt().toString()

View File

@@ -0,0 +1,5 @@
package com.appttude.h_mal.atlas_weather.model
interface DataMapper <T: Any> {
fun mapData(): T
}

View File

@@ -0,0 +1,28 @@
package com.appttude.h_mal.atlas_weather.model
enum class IconMapper(val label: String) {
snow("13d"),
snow_showers_day("13d"),
snow_showers_night("13n"),
thunder_rain("11d"),
thunder_showers_day("11d"),
thunder_showers_night("11n"),
rain("10d"),
showers_day("10d"),
showers_night("10n"),
fog("50d"),
wind("50d"),
cloudy("04d"),
partly_cloudy_day("03d"),
partly_cloudy_night("03n"),
clear_day("01d"),
clear_night("01n");
companion object{
fun findIconCode(iconId: String?): String? {
val label = iconId?.replace("-", "_")
val enumName = IconMapper.entries.find { it.name == label }
return enumName?.label
}
}
}

View File

@@ -40,8 +40,7 @@ data class Forecast(
parcel.readString(), parcel.readString(),
parcel.readString(), parcel.readString(),
parcel.readString() parcel.readString()
) { )
}
constructor(dailyWeather: DailyWeather) : this( constructor(dailyWeather: DailyWeather) : this(
dailyWeather.dt?.toDayString(), dailyWeather.dt?.toDayString(),

View File

@@ -10,7 +10,7 @@ data class WeatherDisplay(
val averageTemp: Double?, val averageTemp: Double?,
var unit: String?, var unit: String?,
var location: String?, var location: String?,
val iconURL: String?, var iconURL: String?,
val description: String?, val description: String?,
val hourly: List<Hour>?, val hourly: List<Hour>?,
val forecast: List<Forecast>?, val forecast: List<Forecast>?,
@@ -40,8 +40,7 @@ data class WeatherDisplay(
parcel.readDouble(), parcel.readDouble(),
parcel.readDouble(), parcel.readDouble(),
parcel.readString() parcel.readString()
) { )
}
constructor(entity: EntityItem) : this( constructor(entity: EntityItem) : this(
entity.weather.current?.temp, entity.weather.current?.temp,
@@ -56,8 +55,8 @@ data class WeatherDisplay(
entity.weather.daily?.get(0)?.pop?.times(100)?.toInt()?.toString(), entity.weather.daily?.get(0)?.pop?.times(100)?.toInt()?.toString(),
entity.weather.current?.humidity?.toString(), entity.weather.current?.humidity?.toString(),
entity.weather.current?.clouds?.toString(), entity.weather.current?.clouds?.toString(),
entity.weather.lat, entity.weather.lat!!,
entity.weather.lon, entity.weather.lon!!,
entity.weather.locationString entity.weather.locationString
) )

View File

@@ -8,11 +8,15 @@ enum class UnitType {
companion object { companion object {
fun getByName(name: String?): UnitType? { fun getByName(name: String?): UnitType? {
return values().firstOrNull { return entries.firstOrNull {
it.name.lowercase(Locale.ROOT) == name?.lowercase( it.name.lowercase(Locale.ROOT) == name?.lowercase(
Locale.ROOT Locale.ROOT
) )
} }
} }
fun UnitType.getLabel() = name.lowercase().replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}
} }
} }

View File

@@ -1,6 +1,7 @@
package com.appttude.h_mal.atlas_weather.model.weather package com.appttude.h_mal.atlas_weather.model.weather
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.Current import com.appttude.h_mal.atlas_weather.data.network.response.weather.CurrentConditions
import com.appttude.h_mal.atlas_weather.model.IconMapper
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
data class Current( data class Current(
@@ -23,23 +24,23 @@ data class Current(
val windSpeed: Double? = null val windSpeed: Double? = null
) { ) {
constructor(dailyItem: Current) : this( constructor(currentConditions: CurrentConditions?) : this(
dailyItem.dt, dt = currentConditions?.datetimeEpoch,
dailyItem.sunrise, sunrise = currentConditions?.sunriseEpoch,
dailyItem.sunset, sunset = currentConditions?.sunsetEpoch,
dailyItem.temp, temp = currentConditions?.temp,
dailyItem.visibility, visibility = currentConditions?.visibility?.toInt(),
dailyItem.uvi, uvi = currentConditions?.uvindex?.toDouble(),
dailyItem.pressure, pressure = currentConditions?.pressure?.toInt(),
dailyItem.clouds, clouds = currentConditions?.cloudcover?.toInt(),
dailyItem.feelsLike, feelsLike = currentConditions?.feelslike,
dailyItem.windDeg, windDeg = currentConditions?.winddir?.toInt(),
dailyItem.dewPoint, dewPoint = currentConditions?.dew,
generateIconUrlString(dailyItem.weather?.getOrNull(0)?.icon), icon = generateIconUrlString(IconMapper.findIconCode(currentConditions?.icon)),
dailyItem.weather?.get(0)?.description, description = currentConditions?.conditions,
dailyItem.weather?.get(0)?.main, main = currentConditions?.conditions,
dailyItem.weather?.get(0)?.id, id = currentConditions?.datetimeEpoch,
dailyItem.humidity, humidity = currentConditions?.humidity?.toInt(),
dailyItem.windSpeed windSpeed = currentConditions?.windspeed
) )
} }

View File

@@ -1,6 +1,7 @@
package com.appttude.h_mal.atlas_weather.model.weather package com.appttude.h_mal.atlas_weather.model.weather
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.DailyItem import com.appttude.h_mal.atlas_weather.data.network.response.weather.Days
import com.appttude.h_mal.atlas_weather.model.IconMapper
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
@@ -27,28 +28,29 @@ data class DailyWeather(
val rain: Double? val rain: Double?
) { ) {
constructor(dailyItem: DailyItem) : this( constructor(days: Days) : this(
dailyItem.dt, days.datetimeEpoch,
dailyItem.sunrise, days.sunriseEpoch,
dailyItem.sunset, days.sunsetEpoch,
dailyItem.temp?.min, days.tempmin,
dailyItem.temp?.max, days.tempmax,
dailyItem.temp?.day, days.temp,
dailyItem.feelsLike?.day, days.feelslike,
dailyItem.pressure, days.pressure?.toInt(),
dailyItem.humidity, days.humidity?.toInt(),
dailyItem.dewPoint, days.dew,
dailyItem.windSpeed, days.windspeed,
dailyItem.windDeg, days.winddir?.toInt(),
generateIconUrlString(dailyItem.weather?.getOrNull(0)?.icon), generateIconUrlString(
dailyItem.weather?.get(0)?.description, IconMapper.findIconCode(days.icon)
dailyItem.weather?.get(0)?.main, ),
dailyItem.weather?.get(0)?.id, days.description,
dailyItem.clouds, days.conditions,
dailyItem.pop, days.datetimeEpoch,
dailyItem.uvi, days.cloudcover?.toInt(),
dailyItem.rain days.precipprob,
days.uvindex?.toDouble(),
days.precip?.toDouble()
) )
} }

View File

@@ -1,29 +1,15 @@
package com.appttude.h_mal.atlas_weather.model.weather package com.appttude.h_mal.atlas_weather.model.weather
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
data class FullWeather( data class FullWeather(
val current: Current? = null, val current: Current? = null,
val timezone: String? = null, val timezone: String? = null,
val timezoneOffset: Int? = null, val timezoneOffset: Int? = null,
val hourly: List<Hour>? = null, val hourly: List<Hour>? = null,
val daily: List<DailyWeather>? = null, val daily: List<DailyWeather>? = null,
val lon: Double = 0.00, val lon: Double? = null,
val lat: Double = 0.00, val lat: Double? = null,
var locationString: String? = null, var locationString: String? = null,
var temperatureUnit: String? = null var temperatureUnit: String? = null
) { )
constructor(weatherResponse: WeatherResponse) : this(
weatherResponse.current?.let { Current(it) },
weatherResponse.timezone,
weatherResponse.timezoneOffset,
weatherResponse.hourly?.subList(0, 23)?.map { Hour(it) },
weatherResponse.daily?.map { DailyWeather(it) },
weatherResponse.lon,
weatherResponse.lat
)
}

View File

@@ -2,8 +2,9 @@ package com.appttude.h_mal.atlas_weather.model.weather
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.appttude.h_mal.atlas_weather.model.IconMapper
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.Hour as ForecastHour import com.appttude.h_mal.atlas_weather.data.network.response.weather.Hours as WeatherHour
data class Hour( data class Hour(
@@ -16,13 +17,13 @@ data class Hour(
parcel.readValue(Int::class.java.classLoader) as? Int, parcel.readValue(Int::class.java.classLoader) as? Int,
parcel.readValue(Double::class.java.classLoader) as? Double, parcel.readValue(Double::class.java.classLoader) as? Double,
parcel.readString() parcel.readString()
) { )
}
constructor(hour: ForecastHour) : this(
hour.dt, constructor(weatherHour: WeatherHour) : this(
hour.temp, weatherHour.datetimeEpoch,
generateIconUrlString(hour.weather?.getOrNull(0)?.icon) weatherHour.temp,
generateIconUrlString(IconMapper.findIconCode(weatherHour.icon))
) )
override fun writeToParcel(parcel: Parcel, flags: Int) { override fun writeToParcel(parcel: Parcel, flags: Int) {

View File

@@ -1,5 +1,7 @@
package com.appttude.h_mal.atlas_weather.utils package com.appttude.h_mal.atlas_weather.utils
import com.appttude.h_mal.atlas_weather.model.types.UnitType
fun generateIconUrlString(icon: String?): String? { fun generateIconUrlString(icon: String?): String? {
return icon?.let { return icon?.let {
@@ -9,4 +11,6 @@ fun generateIconUrlString(icon: String?): String? {
.append("@2x.png") .append("@2x.png")
.toString() .toString()
} }
} }
fun UnitType.getSymbol(): String = if (this == UnitType.METRIC) "°C" else "°F"

View File

@@ -1,7 +1,7 @@
package com.appttude.h_mal.atlas_weather.data package com.appttude.h_mal.atlas_weather.data
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
@@ -18,6 +18,7 @@ import kotlinx.coroutines.runBlocking
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.IOException import java.io.IOException
import kotlin.properties.Delegates
import kotlin.test.assertEquals import kotlin.test.assertEquals
class WeatherSourceTest : BaseTest() { class WeatherSourceTest : BaseTest() {
@@ -31,41 +32,43 @@ class WeatherSourceTest : BaseTest() {
@MockK @MockK
lateinit var locationProvider: LocationProviderImpl lateinit var locationProvider: LocationProviderImpl
private lateinit var weatherResponse: WeatherResponse private lateinit var weatherResponse: WeatherApiResponse
private var lat by Delegates.notNull<Double>()
private var long by Delegates.notNull<Double>()
private lateinit var latlon: Pair<Double, Double>
private lateinit var fullWeather: FullWeather
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
weatherResponse = getTestData("weather_sample.json", WeatherResponse::class.java) weatherResponse = getTestData("new_response.json", WeatherApiResponse::class.java)
lat = weatherResponse.latitude!!
long = weatherResponse.longitude!!
latlon = Pair(lat, long)
fullWeather = weatherResponse.mapData().apply {
temperatureUnit = "°C"
locationString = CURRENT_LOCATION
}
} }
@Test @Test
fun fetchDataForSingleLocation_validLocation_validReturn() { fun fetchDataForSingleLocation_validLocation_validReturn() {
// Arrange // Arrange
val latlon = Pair(
weatherResponse.lat,
weatherResponse.lon
)
val fullWeather = FullWeather(weatherResponse).apply {
temperatureUnit = "°C"
locationString = CURRENT_LOCATION
}
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather) val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
// Act // Act
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true) every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
coEvery { coEvery {
repository.getWeatherFromApi( repository.getWeatherFromApi(
weatherResponse.lat.toString(), lat.toString(),
weatherResponse.lon.toString() long.toString()
) )
}.returns(weatherResponse) }.returns(weatherResponse)
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat,
weatherResponse.lon, long,
LocationType.City LocationType.City
) )
}.returns(CURRENT_LOCATION) }.returns(CURRENT_LOCATION)
@@ -82,17 +85,13 @@ class WeatherSourceTest : BaseTest() {
@Test(expected = IOException::class) @Test(expected = IOException::class)
fun fetchDataForSingleLocation_failedWeatherApi_invalidReturn() { fun fetchDataForSingleLocation_failedWeatherApi_invalidReturn() {
// Arrange // Arrange
val latlon = Pair(
weatherResponse.lat,
weatherResponse.lon
)
// Act // Act
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true) every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
coEvery { coEvery {
repository.getWeatherFromApi( repository.getWeatherFromApi(
weatherResponse.lat.toString(), lat.toString(),
weatherResponse.lon.toString() long.toString()
) )
} throws IOException("Unable fetch data") } throws IOException("Unable fetch data")
@@ -103,23 +102,19 @@ class WeatherSourceTest : BaseTest() {
@Test(expected = IOException::class) @Test(expected = IOException::class)
fun fetchDataForSingleLocation_failedLocation_invalidReturn() { fun fetchDataForSingleLocation_failedLocation_invalidReturn() {
// Arrange // Arrange
val latlon = Pair(
weatherResponse.lat,
weatherResponse.lon
)
// Act // Act
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true) every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
coEvery { coEvery {
repository.getWeatherFromApi( repository.getWeatherFromApi(
weatherResponse.lat.toString(), lat.toString(),
weatherResponse.lon.toString() long.toString()
) )
} returns weatherResponse } returns weatherResponse
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat,
weatherResponse.lon long
) )
}.throws(IOException()) }.throws(IOException())
@@ -130,14 +125,6 @@ class WeatherSourceTest : BaseTest() {
@Test @Test
fun searchAboveFallbackTime_validLocation_validReturn() { fun searchAboveFallbackTime_validLocation_validReturn() {
// Arrange // Arrange
val latlon = Pair(
weatherResponse.lat,
weatherResponse.lon
)
val fullWeather = FullWeather(weatherResponse).apply {
temperatureUnit = "°C"
locationString = CURRENT_LOCATION
}
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather) val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
// Act // Act
@@ -153,14 +140,6 @@ class WeatherSourceTest : BaseTest() {
@Test @Test
fun forceFetchDataForSingleLocation_validLocation_validReturn() { fun forceFetchDataForSingleLocation_validLocation_validReturn() {
// Arrange // Arrange
val latlon = Pair(
weatherResponse.lat,
weatherResponse.lon
)
val fullWeather = FullWeather(weatherResponse).apply {
temperatureUnit = "°C"
locationString = CURRENT_LOCATION
}
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather) val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
// Act // Act
@@ -169,14 +148,14 @@ class WeatherSourceTest : BaseTest() {
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
coEvery { coEvery {
repository.getWeatherFromApi( repository.getWeatherFromApi(
weatherResponse.lat.toString(), weatherResponse.latitude.toString(),
weatherResponse.lon.toString() weatherResponse.longitude.toString()
) )
}.returns(weatherResponse) }.returns(weatherResponse)
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat,
weatherResponse.lon, long,
LocationType.City LocationType.City
) )
}.returns(CURRENT_LOCATION) }.returns(CURRENT_LOCATION)

View File

@@ -1,7 +1,7 @@
package com.appttude.h_mal.atlas_weather.data.repository package com.appttude.h_mal.atlas_weather.data.repository
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
@@ -17,7 +17,9 @@ import io.mockk.mockk
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.IOException import org.mockito.ArgumentMatchers.anyDouble
import org.mockito.ArgumentMatchers.anyString
import retrofit2.HttpException
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertIs import kotlin.test.assertIs
@@ -29,8 +31,7 @@ class RepositoryImplTest : BaseTest() {
lateinit var repository: RepositoryImpl lateinit var repository: RepositoryImpl
@MockK @MockK lateinit var api: WeatherApi
lateinit var api: WeatherApi
@MockK @MockK
lateinit var db: AppDatabase lateinit var db: AppDatabase
@@ -86,37 +87,44 @@ class RepositoryImplTest : BaseTest() {
@Test @Test
fun getWeatherFromApi_validLatLong_validSearch() { fun getWeatherFromApi_validLatLong_validSearch() {
//Arrange //Arrange
val mockResponse = createSuccessfulRetrofitMock<WeatherResponse>() val lat = anyDouble().toString()
val long = anyDouble().toString()
val mockResponse = createSuccessfulRetrofitMock<WeatherApiResponse>()
//Act //Act
//create a successful retrofit response //create a successful retrofit response
every { prefs.getUnitsType() } returns (UnitType.METRIC) every { prefs.getUnitsType() } returns (UnitType.METRIC)
coEvery { api.getFromApi("", "") }.returns(mockResponse) coEvery { api.getFromApi(location = "$lat,$long") }.returns(mockResponse)
// Assert // Assert
runBlocking { runBlocking {
val result = repository.getWeatherFromApi("", "") val result = repository.getWeatherFromApi(lat, long)
assertIs<WeatherResponse>(result) assertIs<WeatherApiResponse>(result)
} }
} }
@Test @Test
fun getWeatherFromApi_validLatLong_invalidResponse() { fun getWeatherFromApi_validLatLong_invalidResponse() {
//Arrange //Arrange
val mockResponse = createErrorRetrofitMock<WeatherResponse>() val errorMessage = "Why dont you have a valid api key?"
val mockResponse = createErrorRetrofitMock<WeatherApiResponse>(errorMessage)
val lat = anyString()
val long = anyString()
//Act //Act
//create a successful retrofit response //create a successful retrofit response
every { prefs.getUnitsType() } returns (UnitType.METRIC) every { prefs.getUnitsType() } returns (UnitType.METRIC)
coEvery { api.getFromApi(any(), any()) } returns (mockResponse) coEvery { api.getFromApi(location = "$lat,$long") } returns (mockResponse)
// Assert // Assert
val ioExceptionReturned = assertFailsWith<IOException> { val ioExceptionReturned = assertFailsWith<HttpException> {
runBlocking { runBlocking {
repository.getWeatherFromApi("", "") repository.getWeatherFromApi(lat, long)
} }
} }
assertEquals(ioExceptionReturned.message, "Error Code: 400")
assertEquals(ioExceptionReturned.code(), 400)
assertEquals(ioExceptionReturned.message(), "Why dont you have a valid api key?")
} }
@Test @Test

View File

@@ -1,7 +1,7 @@
package com.appttude.h_mal.atlas_weather.helper package com.appttude.h_mal.atlas_weather.helper
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import com.appttude.h_mal.atlas_weather.data.repository.Repository import com.appttude.h_mal.atlas_weather.data.repository.Repository
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
@@ -17,6 +17,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.io.IOException import java.io.IOException
import kotlin.properties.Delegates
class ServicesHelperTest : BaseTest() { class ServicesHelperTest : BaseTest() {
@@ -31,40 +32,45 @@ class ServicesHelperTest : BaseTest() {
@MockK @MockK
lateinit var locationProvider: LocationProviderImpl lateinit var locationProvider: LocationProviderImpl
lateinit var weatherResponse: WeatherResponse lateinit var weatherResponse: WeatherApiResponse
private var lat by Delegates.notNull<Double>()
private var long by Delegates.notNull<Double>()
private lateinit var latlon: Pair<Double, Double>
private lateinit var fullWeather: FullWeather
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
helper = ServicesHelper(repository, settingsRepository, locationProvider) helper = ServicesHelper(repository, settingsRepository, locationProvider)
weatherResponse = getTestData("weather_sample.json", WeatherResponse::class.java) weatherResponse = getTestData("new_response.json", WeatherApiResponse::class.java)
lat = weatherResponse.latitude!!
long = weatherResponse.longitude!!
latlon = Pair(lat, long)
fullWeather = weatherResponse.mapData().apply {
temperatureUnit = "°C"
locationString = CURRENT_LOCATION
}
} }
@Test @Test
fun testWidgetDataAsync_successfulResponse() = runBlocking { fun testWidgetDataAsync_successfulResponse() = runBlocking {
// Arrange // Arrange
val entityItem = EntityItem(CURRENT_LOCATION, FullWeather(weatherResponse).apply { val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
temperatureUnit = "°C"
locationString = CURRENT_LOCATION
})
// Act // Act
coEvery { locationProvider.getCurrentLatLong() } returns Pair( coEvery { locationProvider.getCurrentLatLong() } returns Pair(lat, long)
weatherResponse.lat,
weatherResponse.lon
)
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true) every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
coEvery { coEvery {
repository.getWeatherFromApi( repository.getWeatherFromApi(
weatherResponse.lat.toString(), lat.toString(),
weatherResponse.lon.toString() long.toString()
) )
}.returns(weatherResponse) }.returns(weatherResponse)
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat,
weatherResponse.lon long
) )
}.returns(CURRENT_LOCATION) }.returns(CURRENT_LOCATION)
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit

View File

@@ -4,7 +4,9 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody
import retrofit2.Response import retrofit2.Response
@@ -29,9 +31,17 @@ open class BaseTest {
fun <T: Any> createErrorRetrofitMock(code: Int = 400): Response<T> { fun <T: Any> createErrorRetrofitMock(code: Int = 400): Response<T> {
val responseBody = mockk<ResponseBody>(relaxed = true) val responseBody = mockk<ResponseBody>(relaxed = true)
val rawResponse = mockk<okhttp3.Response>().also {
every { it.code } returns code
}
return Response.error<T>(code, responseBody) return Response.error<T>(code, responseBody)
} }
fun <T: Any> createErrorRetrofitMock(errorMessage: String, code: Int = 400): Response<T> {
val responseBody = errorMessage.toResponseBody("application/json".toMediaType())
val rawResponse = mockk<okhttp3.Response>(relaxed = true).also {
every { it.code } returns code
every { it.isSuccessful } returns false
every { it.body } returns responseBody
every { it.message } returns errorMessage
}
return Response.error<T>(responseBody, rawResponse)
}
} }

View File

@@ -3,7 +3,7 @@ package com.appttude.h_mal.atlas_weather.viewmodel
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.appttude.h_mal.atlas_weather.data.WeatherSource import com.appttude.h_mal.atlas_weather.data.WeatherSource
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
import com.appttude.h_mal.atlas_weather.model.ViewState import com.appttude.h_mal.atlas_weather.model.ViewState
import com.appttude.h_mal.atlas_weather.model.types.LocationType import com.appttude.h_mal.atlas_weather.model.types.LocationType
@@ -21,10 +21,10 @@ import io.mockk.impl.annotations.RelaxedMockK
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyList import org.mockito.ArgumentMatchers.anyList
import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.anyString
import java.io.IOException import java.io.IOException
import kotlin.properties.Delegates
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertIs import kotlin.test.assertIs
@@ -43,12 +43,23 @@ class WorldViewModelTest : BaseTest() {
@MockK @MockK
lateinit var locationProvider: LocationProviderImpl lateinit var locationProvider: LocationProviderImpl
private lateinit var weatherResponse: WeatherResponse private lateinit var weatherResponse: WeatherApiResponse
private var lat by Delegates.notNull<Double>()
private var long by Delegates.notNull<Double>()
private lateinit var latlon: Pair<Double, Double>
private lateinit var fullWeather: FullWeather
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
weatherResponse = getTestData("weather_sample.json", WeatherResponse::class.java) weatherResponse = getTestData("new_response.json", WeatherApiResponse::class.java)
lat = weatherResponse.latitude!!
long = weatherResponse.longitude!!
latlon = Pair(lat, long)
fullWeather = weatherResponse.mapData().apply {
temperatureUnit = "°C"
locationString = CURRENT_LOCATION
}
} }
@Test @Test
@@ -58,13 +69,13 @@ class WorldViewModelTest : BaseTest() {
// Act // Act
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair( coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
weatherResponse.lat, lat,
weatherResponse.lon long
) )
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat,
weatherResponse.lon, long,
LocationType.City LocationType.City
) )
}.returns(CURRENT_LOCATION) }.returns(CURRENT_LOCATION)
@@ -74,7 +85,7 @@ class WorldViewModelTest : BaseTest() {
CURRENT_LOCATION, CURRENT_LOCATION,
locationType = LocationType.City locationType = LocationType.City
) )
} returns FullWeather(weatherResponse) } returns fullWeather
// Assert // Assert
viewModel.fetchDataForSingleLocation(CURRENT_LOCATION) viewModel.fetchDataForSingleLocation(CURRENT_LOCATION)
@@ -86,17 +97,17 @@ class WorldViewModelTest : BaseTest() {
@Test @Test
fun fetchDataForSingleLocation_failedLocation_validReturn() { fun fetchDataForSingleLocation_failedLocation_validReturn() {
// Arrange // Arrange
val errorMessage = ArgumentMatchers.anyString() val errorMessage = anyString()
// Act // Act
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair( coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
weatherResponse.lat, lat,
weatherResponse.lon long
) )
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat,
weatherResponse.lon, long,
LocationType.City LocationType.City
) )
} throws IOException(errorMessage) } throws IOException(errorMessage)
@@ -112,18 +123,17 @@ class WorldViewModelTest : BaseTest() {
@Test @Test
fun fetchDataForSingleLocation_failedApi_validReturn() { fun fetchDataForSingleLocation_failedApi_validReturn() {
// Arrange // Arrange
val latlon = Pair(weatherResponse.lat, weatherResponse.lon) val errorMessage = anyString()
val errorMessage = ArgumentMatchers.anyString()
// Act // Act
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair( coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
weatherResponse.lat, lat,
weatherResponse.lon long
) )
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat,
weatherResponse.lon, long,
LocationType.City LocationType.City
) )
}.returns(CURRENT_LOCATION) }.returns(CURRENT_LOCATION)
@@ -146,15 +156,14 @@ class WorldViewModelTest : BaseTest() {
@Test @Test
fun fetchDataForSingleLocationSearch_validLocation_validReturn() { fun fetchDataForSingleLocationSearch_validLocation_validReturn() {
// Arrange // Arrange
val latlon = Pair(weatherResponse.lat, weatherResponse.lon)
// Act // Act
every { weatherSource.repository.getSavedLocations() } returns anyList() every { weatherSource.repository.getSavedLocations() } returns anyList()
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat,
weatherResponse.lon, long,
LocationType.City LocationType.City
) )
}.returns(CURRENT_LOCATION) }.returns(CURRENT_LOCATION)
@@ -164,7 +173,7 @@ class WorldViewModelTest : BaseTest() {
CURRENT_LOCATION, CURRENT_LOCATION,
locationType = LocationType.City locationType = LocationType.City
) )
} returns FullWeather(weatherResponse).apply { locationString = CURRENT_LOCATION } } returns fullWeather.apply { locationString = CURRENT_LOCATION }
// Assert // Assert
viewModel.fetchDataForSingleLocationSearch(CURRENT_LOCATION) viewModel.fetchDataForSingleLocationSearch(CURRENT_LOCATION)
@@ -192,7 +201,6 @@ class WorldViewModelTest : BaseTest() {
@Test @Test
fun fetchDataForSingleLocationSearch_retrievedLocationExists_validError() { fun fetchDataForSingleLocationSearch_retrievedLocationExists_validError() {
// Arrange // Arrange
val latlon = Pair(weatherResponse.lat, weatherResponse.lon)
val retrievedLocation = anyString() val retrievedLocation = anyString()
// Act // Act
@@ -200,8 +208,7 @@ class WorldViewModelTest : BaseTest() {
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
coEvery { coEvery {
locationProvider.getLocationNameFromLatLong( locationProvider.getLocationNameFromLatLong(
weatherResponse.lat, lat, long,
weatherResponse.lon,
LocationType.City LocationType.City
) )
}.returns(CURRENT_LOCATION) }.returns(CURRENT_LOCATION)
@@ -211,7 +218,7 @@ class WorldViewModelTest : BaseTest() {
CURRENT_LOCATION, CURRENT_LOCATION,
locationType = LocationType.City locationType = LocationType.City
) )
} returns FullWeather(weatherResponse).apply { locationString = retrievedLocation } } returns fullWeather.apply { locationString = retrievedLocation }
// Assert // Assert
viewModel.fetchDataForSingleLocationSearch(CURRENT_LOCATION) viewModel.fetchDataForSingleLocationSearch(CURRENT_LOCATION)

File diff suppressed because it is too large Load Diff

View File

@@ -1,317 +0,0 @@
{
"lat": 51.5,
"lon": -0.12,
"timezone": "Europe/London",
"timezone_offset": 0,
"current": {
"dt": 1608394319,
"sunrise": 1608364972,
"sunset": 1608393158,
"temp": 9.42,
"feels_like": 4.9,
"pressure": 1007,
"humidity": 91,
"dew_point": 8.03,
"uvi": 0,
"clouds": 33,
"visibility": 10000,
"wind_speed": 5.8,
"wind_deg": 215,
"weather": [
{
"id": 802,
"main": "Clouds",
"description": "scattered clouds",
"icon": "03n"
}
]
},
"daily": [
{
"dt": 1608375600,
"sunrise": 1608364972,
"sunset": 1608393158,
"temp": {
"day": 11.78,
"min": 9.1,
"max": 12.31,
"night": 9.1,
"eve": 9.8,
"morn": 10.8
},
"feels_like": {
"day": 6.46,
"night": 5.08,
"eve": 5.47,
"morn": 4.63
},
"pressure": 1005,
"humidity": 69,
"dew_point": 6.42,
"wind_speed": 6.37,
"wind_deg": 217,
"weather": [
{
"id": 500,
"main": "Rain",
"description": "light rain",
"icon": "10d"
}
],
"clouds": 97,
"pop": 0.98,
"rain": 1.55,
"uvi": 0.53
},
{
"dt": 1608462000,
"sunrise": 1608451406,
"sunset": 1608479581,
"temp": {
"day": 9.9,
"min": 7.41,
"max": 10.52,
"night": 7.41,
"eve": 8.83,
"morn": 8.59
},
"feels_like": {
"day": 4.19,
"night": 3.99,
"eve": 4.55,
"morn": 4.79
},
"pressure": 1013,
"humidity": 64,
"dew_point": 3.48,
"wind_speed": 6.12,
"wind_deg": 226,
"weather": [
{
"id": 500,
"main": "Rain",
"description": "light rain",
"icon": "10d"
}
],
"clouds": 8,
"pop": 0.58,
"rain": 0.65,
"uvi": 0.45
},
{
"dt": 1608548400,
"sunrise": 1608537838,
"sunset": 1608566008,
"temp": {
"day": 11.06,
"min": 7.01,
"max": 13.57,
"night": 13.2,
"eve": 12.68,
"morn": 8.59
},
"feels_like": {
"day": 6.32,
"night": 9.99,
"eve": 9.75,
"morn": 4.64
},
"pressure": 1005,
"humidity": 91,
"dew_point": 9.69,
"wind_speed": 6.7,
"wind_deg": 185,
"weather": [
{
"id": 501,
"main": "Rain",
"description": "moderate rain",
"icon": "10d"
}
],
"clouds": 100,
"pop": 1,
"rain": 7.85,
"uvi": 0.21
},
{
"dt": 1608634800,
"sunrise": 1608624266,
"sunset": 1608652438,
"temp": {
"day": 12.97,
"min": 11.7,
"max": 13.21,
"night": 11.7,
"eve": 12.37,
"morn": 12.93
},
"feels_like": {
"day": 11.39,
"night": 10.49,
"eve": 10.96,
"morn": 9.65
},
"pressure": 1012,
"humidity": 83,
"dew_point": 10.31,
"wind_speed": 2.38,
"wind_deg": 214,
"weather": [
{
"id": 500,
"main": "Rain",
"description": "light rain",
"icon": "10d"
}
],
"clouds": 100,
"pop": 1,
"rain": 3.25,
"uvi": 0.34
},
{
"dt": 1608721200,
"sunrise": 1608710690,
"sunset": 1608738871,
"temp": {
"day": 12.28,
"min": 10.12,
"max": 12.62,
"night": 10.12,
"eve": 10.12,
"morn": 11.73
},
"feels_like": {
"day": 7.48,
"night": 6.73,
"eve": 6.6,
"morn": 8.15
},
"pressure": 1006,
"humidity": 64,
"dew_point": 5.76,
"wind_speed": 5.45,
"wind_deg": 224,
"weather": [
{
"id": 500,
"main": "Rain",
"description": "light rain",
"icon": "10d"
}
],
"clouds": 97,
"pop": 0.94,
"rain": 2.52,
"uvi": 0.52
},
{
"dt": 1608811200,
"sunrise": 1608797112,
"sunset": 1608825307,
"temp": {
"day": 7.3,
"min": 4.66,
"max": 8.32,
"night": 4.66,
"eve": 5.76,
"morn": 5.85
},
"feels_like": {
"day": 0.36,
"night": -1.25,
"eve": -0.31,
"morn": -2.46
},
"pressure": 1020,
"humidity": 60,
"dew_point": 0.15,
"wind_speed": 7.09,
"wind_deg": 5,
"weather": [
{
"id": 500,
"main": "Rain",
"description": "light rain",
"icon": "10d"
}
],
"clouds": 85,
"pop": 0.52,
"rain": 0.6,
"uvi": 1
},
{
"dt": 1608897600,
"sunrise": 1608883530,
"sunset": 1608911747,
"temp": {
"day": 4.12,
"min": 2.2,
"max": 4.63,
"night": 2.97,
"eve": 3.44,
"morn": 2.28
},
"feels_like": {
"day": -0.33,
"night": -0.43,
"eve": -0.1,
"morn": -2.82
},
"pressure": 1033,
"humidity": 70,
"dew_point": -3.09,
"wind_speed": 3.35,
"wind_deg": 334,
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
],
"clouds": 0,
"pop": 0,
"uvi": 1
},
{
"dt": 1608984000,
"sunrise": 1608969944,
"sunset": 1608998190,
"temp": {
"day": 6.03,
"min": 2.76,
"max": 6.92,
"night": 6.92,
"eve": 6.45,
"morn": 3.59
},
"feels_like": {
"day": 0.49,
"night": -0.96,
"eve": -0.28,
"morn": -0.44
},
"pressure": 1024,
"humidity": 69,
"dew_point": 0.84,
"wind_speed": 5.25,
"wind_deg": 251,
"weather": [
{
"id": 804,
"main": "Clouds",
"description": "overcast clouds",
"icon": "04d"
}
],
"clouds": 100,
"pop": 0,
"uvi": 1
}
]
}

View File

@@ -40,7 +40,7 @@ RETROFIT_VERSION = 2.9.0
OKHTTP_VERSION = 4.9.0 OKHTTP_VERSION = 4.9.0
MOKITO_INLINE_VERSION = 2.13.0 MOKITO_INLINE_VERSION = 2.13.0
CORE_TEST_VERSION = 2.2.0 CORE_TEST_VERSION = 2.2.0
MOCKK_VERSION = 1.10.5 MOCKK_VERSION = 1.13.12
TEST_JUNIT_VERSION = 1.2.0 TEST_JUNIT_VERSION = 1.2.0
TEST_RUNNER_VERSION = 1.5.2 TEST_RUNNER_VERSION = 1.5.2
ESPRESSO_VERSION = 3.6.0 ESPRESSO_VERSION = 3.6.0