diff --git a/.env.example b/.env.example index 83047018b13..35da0470cd0 100644 --- a/.env.example +++ b/.env.example @@ -166,10 +166,9 @@ LOCATION_IQ_API_KEY= ENABLE_WEATHER=false # Access to weather data from darksky api -# https://darksky.net/dev/register -# Darksky provides an api with 1000 free API calls per day +# https://www.weatherapi.com/signup.aspx # You need to enable the weather above if you provide an API key here. -DARKSKY_API_KEY= +WEATHERAPI_KEY= # Configure rate limits for RouteService per minute RATE_LIMIT_PER_MINUTE_API=60 diff --git a/app/Exceptions/NoCoordinatesException.php b/app/Exceptions/NoCoordinatesException.php new file mode 100644 index 00000000000..3f8cc91a4bb --- /dev/null +++ b/app/Exceptions/NoCoordinatesException.php @@ -0,0 +1,9 @@ +place->weathers()->orderBy('created_at', 'desc')->first(); + $weather = $address->place->weathers() + ->orderBy('created_at', 'desc') + ->first(); // only get weather data if weather is either not existant or if is // more than 6h old - if (is_null($weather)) { - $weather = self::callWeatherAPI($address); - } else { - if (! $weather->created_at->between(now()->subHours(6), now())) { - $weather = self::callWeatherAPI($address); - } + if (is_null($weather) || ! $weather->created_at->between(now()->subHours(6), now())) { + self::callWeatherAPI($address); } return $weather; @@ -39,16 +39,21 @@ public static function getWeatherForAddress($address): ?Weather * Make the call to the weather service. * * @param Address $address - * @return Weather|null */ - private static function callWeatherAPI(Address $address): ?Weather + private static function callWeatherAPI(Address $address): void { - try { - return app(GetWeatherInformation::class)->execute([ - 'place_id' => $address->place->id, - ]); - } catch (\Exception $e) { - return null; + $jobs = []; + + if (is_null($address->place->latitude) + && config('monica.enable_geolocation') && ! is_null(config('monica.location_iq_api_key'))) { + $jobs[] = new GetGPSCoordinate($address->place); } + + if (config('monica.enable_weather') && ! is_null(config('monica.weatherapi_key'))) { + $jobs[] = new GetWeatherInformation($address->place); + } + + Bus::batch($jobs) + ->dispatch(); } } diff --git a/app/Jobs/GetGPSCoordinate.php b/app/Jobs/GetGPSCoordinate.php index 9bcd7a7a4b6..abfda6c2ab4 100644 --- a/app/Jobs/GetGPSCoordinate.php +++ b/app/Jobs/GetGPSCoordinate.php @@ -3,6 +3,7 @@ namespace App\Jobs; use App\Models\Account\Place; +use Illuminate\Bus\Batchable; use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; @@ -14,7 +15,7 @@ class GetGPSCoordinate implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * @var Place @@ -64,6 +65,10 @@ public function middleware() */ public function handle() { + if (($batch = $this->batch()) !== null && $batch->cancelled()) { + return; + } + try { app(GetGPSCoordinateService::class)->execute([ 'account_id' => $this->place->account_id, diff --git a/app/Jobs/GetWeatherInformation.php b/app/Jobs/GetWeatherInformation.php new file mode 100644 index 00000000000..bd69b709602 --- /dev/null +++ b/app/Jobs/GetWeatherInformation.php @@ -0,0 +1,67 @@ +place = $place->withoutRelations(); + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + if (! $this->batching()) { + return; + } + + if (is_null($this->place->latitude)) { + $this->fail(new NoCoordinatesException()); + } else { + app(GetWeatherInformationService::class)->execute([ + 'account_id' => $this->place->account_id, + 'place_id' => $this->place->id, + ]); + } + } +} diff --git a/app/Models/Account/Weather.php b/app/Models/Account/Weather.php index 315a1eb6ecb..4d82fc99148 100644 --- a/app/Models/Account/Weather.php +++ b/app/Models/Account/Weather.php @@ -2,6 +2,8 @@ namespace App\Models\Account; +use Illuminate\Support\Arr; +use Illuminate\Support\Str; use Illuminate\Support\Facades\App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -16,6 +18,8 @@ class Weather extends Model * @var array */ protected $fillable = [ + 'account_id', + 'place_id', 'weather_json', ]; @@ -56,49 +60,89 @@ public function place() } /** - * Get the weather summary. + * Get the weather code. * * @return string */ - public function getSummaryAttribute($value) + public function getSummaryCodeAttribute() { $json = $this->weather_json; - return $json['currently']['summary']; + // currently.icon: Darksky version + if (! ($icon = Arr::get($json, 'currently.icon'))) { + if (($text = Arr::get($json, 'current.condition.text')) === 'Partly cloudy') { + $icon = ((bool) Arr::get($json, 'current.is_day')) ? 'partly-cloudy-day' : 'partly-cloudy-night'; + } else { + $icon = Str::of($text)->lower()->replace(' ', '-'); + } + } + + return $icon; } /** - * Get the weather summary icon. + * Get the weather summary. * * @return string */ - public function getSummaryIconAttribute($value) + public function getSummaryAttribute() { - $json = $this->weather_json; - - return $json['currently']['icon']; + return trans('app.weather_'.$this->summary_code); } /** - * Get the emoji representing the weather. + * Get the weather icon. * * @return string + * + * @codeCoverageIgnore */ - public function getEmoji() + public function getEmojiAttribute() { - switch ($this->summary_icon) { + switch ($this->summary_code) { + case 'sunny': case 'clear-day': - $string = '☀️'; + $string = '🌞'; break; + case 'clear': case 'clear-night': - $string = '🌌'; + $string = '🌃'; break; + case 'light-drizzle': + case 'patchy-light-drizzle': + case 'patchy-light-rain': + case 'light-rain': + case 'moderate-rain-at-times': + case 'moderate-rain': + case 'patchy-rain-possible': + case 'heavy-rain-at-times': + case 'heavy-rain': + case 'light-freezing-rain': + case 'moderate-or-heavy-freezing-rain': + case 'light-sleet': + case 'moderate-or-heavy-rain-shower': + case 'light-rain-shower': + case 'torrential-rain-shower': case 'rain': $string = '🌧️'; break; case 'snow': + case 'blowing-snow': + case 'patchy-light-snow': + case 'light-snow': + case 'patchy-moderate-snow': + case 'moderate-snow': + case 'patchy-heavy-snow': + case 'heavy-snow': + case 'light-snow-showers': + case 'moderate-or-heavy-snow-showers': $string = '❄️'; break; + case 'patchy-snow-possible': + case 'patchy-sleet-possible': + case 'moderate-or-heavy-sleet': + case 'light-sleet-showers': + case 'moderate-or-heavy-sleet-showers': case 'sleet': $string = '🌨️'; break; @@ -106,8 +150,12 @@ public function getEmoji() $string = '💨'; break; case 'fog': + case 'mist': + case 'blizzard': + case 'freezing-fog': $string = '🌫️'; break; + case 'overcast': case 'cloudy': $string = '☁️'; break; @@ -117,6 +165,21 @@ public function getEmoji() case 'partly-cloudy-night': $string = '🎑'; break; + case 'freezing-drizzle': + case 'heavy-freezing-drizzle': + case 'patchy-freezing-drizzle-possible': + case 'ice-pellets': + case 'light-showers-of-ice-pellets': + case 'moderate-or-heavy-showers-of-ice-pellets': + $string = '🧊'; + break; + case 'thundery-outbreaks-possible': + case 'patchy-light-rain-with-thunder': + case 'moderate-or-heavy-rain-with-thunder': + case 'patchy-light-snow-with-thunder': + case 'moderate-or-heavy-snow-with-thunder': + $string = '⛈️'; + break; default: $string = '🌈'; break; @@ -137,10 +200,10 @@ public function temperature($scale = 'celsius') { $json = $this->weather_json; - $temperature = $json['currently']['temperature']; + $temperature = Arr::get($json, 'currently.temperature') ?? Arr::get($json, 'current.temp_c'); - if ($scale == 'fahrenheit') { - $temperature = 9 / 5 * $temperature + 32; + if ($scale === 'fahrenheit') { + $temperature = Arr::get($json, 'current.temp_f', 9 / 5 * $temperature + 32); } $temperature = round($temperature, 1); diff --git a/app/Models/Contact/Contact.php b/app/Models/Contact/Contact.php index 0ff757ac7fb..429b53f69e6 100644 --- a/app/Models/Contact/Contact.php +++ b/app/Models/Contact/Contact.php @@ -1490,9 +1490,9 @@ public function setStayInTouchTriggerDate($frequency, $triggerDate = null) * Get the weather information for this contact, based on the first address * on the profile. * - * @return Weather + * @return Weather|null */ - public function getWeather() + public function getWeather(): ?Weather { return WeatherHelper::getWeatherForAddress($this->addresses()->first()); } diff --git a/app/Services/Instance/Geolocalization/GetGPSCoordinate.php b/app/Services/Instance/Geolocalization/GetGPSCoordinate.php index 7845838ffb8..390a14de064 100644 --- a/app/Services/Instance/Geolocalization/GetGPSCoordinate.php +++ b/app/Services/Instance/Geolocalization/GetGPSCoordinate.php @@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Http; use Illuminate\Http\Client\RequestException; use App\Exceptions\RateLimitedSecondException; +use App\Exceptions\MissingEnvVariableException; class GetGPSCoordinate extends BaseService { @@ -34,6 +35,8 @@ public function rules() */ public function execute(array $data) { + $this->validateWeatherEnvVariables(); + $this->validate($data); $place = Place::where('account_id', $data['account_id']) @@ -43,17 +46,25 @@ public function execute(array $data) } /** - * Build the query to send with the API call. + * Make sure that geolocation env variables are set. * - * @param Place $place - * @return string|null + * @return void */ - private function buildQuery(Place $place): ?string + private function validateWeatherEnvVariables() { if (! config('monica.enable_geolocation') || is_null(config('monica.location_iq_api_key'))) { - return null; + throw new MissingEnvVariableException(); } + } + /** + * Build the query to send with the API call. + * + * @param Place $place + * @return string + */ + private function buildQuery(Place $place): string + { $query = http_build_query([ 'format' => 'json', 'key' => config('monica.location_iq_api_key'), @@ -73,10 +84,6 @@ private function query(Place $place): ?Place { $query = $this->buildQuery($place); - if (is_null($query)) { - return null; - } - try { $response = Http::get($query); $response->throw(); diff --git a/app/Services/Instance/Weather/GetWeatherInformation.php b/app/Services/Instance/Weather/GetWeatherInformation.php index 7006315a213..d88374afa76 100644 --- a/app/Services/Instance/Weather/GetWeatherInformation.php +++ b/app/Services/Instance/Weather/GetWeatherInformation.php @@ -5,10 +5,10 @@ use Illuminate\Support\Str; use App\Models\Account\Place; use App\Services\BaseService; -use App\Jobs\GetGPSCoordinate; use App\Models\Account\Weather; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Http; +use App\Exceptions\NoCoordinatesException; use App\Exceptions\MissingEnvVariableException; use Illuminate\Http\Client\HttpClientException; @@ -22,6 +22,7 @@ class GetWeatherInformation extends BaseService public function rules() { return [ + 'account_id' => 'required|integer|exists:accounts,id', 'place_id' => 'required|integer|exists:places,id', ]; } @@ -42,17 +43,14 @@ public function execute(array $data): ?Weather $this->validate($data); - $place = Place::findOrFail($data['place_id']); + $place = Place::where('account_id', $data['account_id']) + ->findOrFail($data['place_id']); if (is_null($place->latitude)) { - $place = $this->fetchGPS($place); - - if (is_null($place)) { - return null; - } + throw new NoCoordinatesException(); } - return $this->query($place); + return $this->query($place, 'en'); } /** @@ -62,11 +60,7 @@ public function execute(array $data): ?Weather */ private function validateWeatherEnvVariables() { - if (! config('monica.enable_weather')) { - throw new MissingEnvVariableException(); - } - - if (is_null(config('monica.darksky_api_key'))) { + if (! config('monica.enable_weather') || is_null(config('monica.weatherapi_key'))) { throw new MissingEnvVariableException(); } } @@ -79,24 +73,22 @@ private function validateWeatherEnvVariables() * * @throws \Exception */ - private function query(Place $place): ?Weather + private function query(Place $place, ?string $lang = null): ?Weather { - $query = $this->buildQuery($place); + $query = $this->buildQuery($place, $lang); try { $response = Http::get($query); $response->throw(); - $weather = new Weather(); - $weather->weather_json = $response->object(); - $weather->account_id = $place->account_id; - $weather->place_id = $place->id; - $weather->save(); - - return $weather; + return Weather::create([ + 'account_id' => $place->account_id, + 'place_id' => $place->id, + 'weather_json' => $response->object(), + ]); } catch (HttpClientException $e) { Log::error(__CLASS__.' '.__FUNCTION__.': Error making the call: '.$e->getMessage(), [ - 'query' => Str::of($query)->replace(config('monica.darksky_api_key'), '******'), + 'query' => Str::of($query)->replace(config('monica.weatherapi_key'), '******'), $e, ]); } @@ -110,35 +102,19 @@ private function query(Place $place): ?Weather * @param Place $place * @return string */ - private function buildQuery(Place $place) + private function buildQuery(Place $place, ?string $lang = null) { - $url = Str::finish(config('location.darksky_url'), '/'); - $key = config('monica.darksky_api_key'); $coords = $place->latitude.','.$place->longitude; - $query = http_build_query([ - 'exclude' => 'alerts,minutely,hourly,daily,flags', - 'units' => 'si', - ]); - - return $url.$key.'/'.$coords.'?'.$query; - } - - /** - * Fetch missing longitude/latitude. - * - * @param Place $place - * @return Place|null - */ - private function fetchGPS(Place $place): ?Place - { - if (config('monica.enable_geolocation') && ! is_null(config('monica.location_iq_api_key'))) { - GetGPSCoordinate::dispatchSync($place); - $place->refresh(); - - return $place; + $query = [ + 'key' => config('monica.weatherapi_key'), + 'q' => $coords, + 'lang' => $lang ?? 'en', + ]; + if ($lang !== null && $lang !== 'en') { + $query['lang'] = $lang; } - return null; + return Str::of(config('location.weatherapi_url'))->rtrim('/').'?'.http_build_query($query); } } diff --git a/config/location.php b/config/location.php index 5de4dc9d47b..de28e457f26 100644 --- a/config/location.php +++ b/config/location.php @@ -94,12 +94,12 @@ /* |-------------------------------------------------------------------------- - | Darksky API Url + | Weatherapi Url |-------------------------------------------------------------------------- | - | Url to call Darksy api. See https://darksky.net/dev/docs + | Url to call Weatherapi. | */ - 'darksky_url' => env('DARKSKY_URL', 'https://api.darksky.net/forecast/'), + 'weatherapi_url' => env('WEATHERAPI_URL', 'https://api.weatherapi.com/v1/current.json'), ]; diff --git a/config/monica.php b/config/monica.php index 4104741886a..ad62c3515dc 100644 --- a/config/monica.php +++ b/config/monica.php @@ -249,11 +249,10 @@ | API key for weather data. |-------------------------------------------------------------------------- | - | To provide weather information, we use Darksky. - | Darksky provides an api with 1000 free API calls per day. - | https://darksky.net/dev/register + | To provide weather information, we use WeatherAPI. + | See https://www.weatherapi.com/ */ - 'darksky_api_key' => env('DARKSKY_API_KEY', null), + 'weatherapi_key' => env('WEATHERAPI_KEY', null), /* |-------------------------------------------------------------------------- diff --git a/database/factories/AccountFactory.php b/database/factories/AccountFactory.php index 9636499139a..197a010f103 100644 --- a/database/factories/AccountFactory.php +++ b/database/factories/AccountFactory.php @@ -110,33 +110,47 @@ ])->id; }, 'weather_json' => json_decode(' -{ - "latitude": 45.487685, - "longitude": -73.590259, - "timezone": "America\/Toronto", - "currently": { - "time": 1541637005, - "summary": "Mostly Cloudy", - "icon": "partly-cloudy-night", - "nearestStormDistance": 39, - "nearestStormBearing": 307, - "precipIntensity": 0, - "precipProbability": 0, - "temperature": 7.57, - "apparentTemperature": 3.82, - "dewPoint": 1.24, - "humidity": 0.64, - "pressure": 1009.91, - "windSpeed": 6.98, - "windGust": 12.99, - "windBearing": 249, - "cloudCover": 0.73, - "uvIndex": 0, - "visibility": 16.09, - "ozone": 304.17 - }, - "offset": -5 -}'), + { + "location": { + "name": "Le Pre-Saint-Gervais", + "region": "Ile-de-France", + "country": "France", + "lat": 48.89, + "lon": 2.39, + "tz_id": "Europe\/Paris", + "localtime_epoch": 1635605324, + "localtime": "2021-10-30 16:48" + }, + "current": { + "last_updated_epoch": 1635605100, + "last_updated": "2021-10-30 16:45", + "temp_c": 13, + "temp_f": 55.4, + "is_day": 0, + "condition": { + "text": "Partly cloudy", + "icon": "\/\/cdn.weatherapi.com\/weather\/64x64\/night\/116.png", + "code": 1003 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 210, + "wind_dir": "SSW", + "pressure_mb": 1001, + "pressure_in": 29.56, + "precip_mm": 0, + "precip_in": 0, + "humidity": 94, + "cloud": 75, + "feelslike_c": 11.2, + "feelslike_f": 52.1, + "vis_km": 10, + "vis_miles": 6, + "uv": 4, + "gust_mph": 17.9, + "gust_kph": 28.8 + } + }'), 'created_at' => now(), ]; }); diff --git a/resources/lang/en/app.php b/resources/lang/en/app.php index cade7d64e43..a7ad760b78a 100644 --- a/resources/lang/en/app.php +++ b/resources/lang/en/app.php @@ -441,16 +441,62 @@ 'emotion_dread' => 'Dread', // weather - 'weather_clear-day' => 'Clear day', + 'weather_sunny' => 'Sunny', + 'weather_clear' => 'Clear', + 'weather_clear-day' => 'Clear', 'weather_clear-night' => 'Clear night', + 'weather_light-drizzle' => 'Light drizzle', + 'weather_patchy-light-drizzle' => 'Patchy light drizzle', + 'weather_patchy-light-rain' => 'Patchy light rain', + 'weather_light-rain' => 'Light rain', + 'weather_moderate-rain-at-times' => 'Moderate rain at times', + 'weather_moderate-rain' => 'Moderate rain', + 'weather_patchy-rain-possible' => 'Patchy rain possible', + 'weather_heavy-rain-at-times' => 'Heavy rain at times', + 'weather_heavy-rain' => 'Heavy rain', + 'weather_light-freezing-rain' => 'Light freezing rain', + 'weather_moderate-or-heavy-freezing-rain' => 'Moderate or heavy freezing rain', + 'weather_light-sleet' => 'Light sleet', + 'weather_moderate-or-heavy-rain-shower' => 'Moderate or heavy rain shower', + 'weather_light-rain-shower' => 'Light rain shower', + 'weather_torrential-rain-shower' => 'Torrential rain shower', 'weather_rain' => 'Rain', 'weather_snow' => 'Snow', + 'weather_blowing-snow' => 'Blowing snow', + 'weather_patchy-light-snow' => 'Patchy light snow', + 'weather_light-snow' => 'Light snow', + 'weather_patchy-moderate-snow' => 'Patchy moderate snow', + 'weather_moderate-snow' => 'Moderate snow', + 'weather_patchy-heavy-snow' => 'Patchy heavy snow', + 'weather_heavy-snow' => 'Heavy snow', + 'weather_light-snow-showers' => 'Light snow showers', + 'weather_moderate-or-heavy-snow-showers' => 'Moderate or heavy snow showers', + 'weather_patchy-snow-possible' => 'Patchy snow possible', + 'weather_patchy-sleet-possible' => 'Patchy sleet possible', + 'weather_moderate-or-heavy-sleet' => 'Moderate or heavy sleet', + 'weather_light-sleet-showers' => 'Light sleet showers', + 'weather_moderate-or-heavy-sleet-showers' => 'Moderate or heavy sleet showers', 'weather_sleet' => 'Sleet', 'weather_wind' => 'Wind', 'weather_fog' => 'Fog', + 'weather_freezing-fog' => 'Freezing fog', + 'weather_mist' => 'Mist', + 'weather_blizzard' => 'Blizzard', + 'weather_overcast' => 'Overcast', 'weather_cloudy' => 'Cloudy', - 'weather_partly-cloudy-day' => 'Partly cloudy day', - 'weather_partly-cloudy-night' => 'Partly cloudy night', + 'weather_partly-cloudy-day' => 'Partly cloudy', + 'weather_partly-cloudy-night' => 'Partly cloudy', + 'weather_freezing-drizzle' => 'Freezing drizzle', + 'weather_heavy-freezing-drizzle' => 'Heavy freezing drizzle', + 'weather_patchy-freezing-drizzle-possible' => 'Patchy freezing drizzle possible', + 'weather_ice-pellets' => 'Ice pellets', + 'weather_light-showers-of-ice-pellets' => 'Light showers of ice pellets', + 'weather_moderate-or-heavy-showers-of-ice-pellets' => 'Moderate or heavy showers of ice pellets', + 'weather_thundery-outbreaks-possible' => 'Thundery outbreaks possible', + 'weather_patchy-light-rain-with-thunder' => 'Patchy light rain with thunder', + 'weather_moderate-or-heavy-rain-with-thunder' => 'Moderate or heavy rain with thunder', + 'weather_patchy-light-snow-with-thunder' => 'Patchy light snow with thunder', + 'weather_moderate-or-heavy-snow-with-thunder' => 'Moderate or heavy snow with thunder', 'weather_current_temperature_celsius' => ':temperature °C', 'weather_current_temperature_fahrenheit' => ':temperature °F', 'weather_current_title' => 'Current weather', diff --git a/resources/views/people/profile.blade.php b/resources/views/people/profile.blade.php index 5e260d7bb7e..445e70664a6 100644 --- a/resources/views/people/profile.blade.php +++ b/resources/views/people/profile.blade.php @@ -51,7 +51,7 @@

- {{ $weather->getEmoji() }} {{ trans('app.weather_'.$weather->summary_icon) }} / {{ trans('app.weather_current_temperature_'.auth()->user()->temperature_scale, ['temperature' => $weather->temperature(auth()->user()->temperature_scale)]) }} + {{ $weather->emoji }} {{ $weather->summary }} / {{ trans('app.weather_current_temperature_'.auth()->user()->temperature_scale, ['temperature' => $weather->temperature(auth()->user()->temperature_scale)]) }}

@endif diff --git a/tests/Fixtures/Services/Instance/Weather/GetWeatherInformationSampleResponse.json b/tests/Fixtures/Services/Instance/Weather/GetWeatherInformationSampleResponse.json index fcb9493e87d..fb16fe80ec4 100644 --- a/tests/Fixtures/Services/Instance/Weather/GetWeatherInformationSampleResponse.json +++ b/tests/Fixtures/Services/Instance/Weather/GetWeatherInformationSampleResponse.json @@ -1,27 +1,41 @@ { - "latitude":34.112456, - "longitude":-118.4270732, - "timezone":"America/Los_Angeles", - "currently":{ - "time":1545426311, - "summary":"Partly Cloudy", - "icon":"partly-cloudy-day", - "nearestStormDistance":10, - "nearestStormBearing":296, - "precipIntensity":0, - "precipProbability":0, - "temperature":67.94, - "apparentTemperature":67.94, - "dewPoint":41.87, - "humidity":0.39, - "pressure":1014.57, - "windSpeed":3.35, - "windGust":6.12, - "windBearing":232, - "cloudCover":0.28, - "uvIndex":2, - "visibility":10, - "ozone":277.93 + "location": { + "name": "Le Pre-Saint-Gervais", + "region": "Ile-de-France", + "country": "France", + "lat": 48.89, + "lon": 2.39, + "tz_id": "Europe\/Paris", + "localtime_epoch": 1635605324, + "localtime": "2021-10-30 16:48" }, - "offset":-8 + "current": { + "last_updated_epoch": 1635605100, + "last_updated": "2021-10-30 16:45", + "temp_c": 13, + "temp_f": 55.4, + "is_day": 0, + "condition": { + "text": "Partly cloudy", + "icon": "\/\/cdn.weatherapi.com\/weather\/64x64\/night\/116.png", + "code": 1003 + }, + "wind_mph": 5.6, + "wind_kph": 9, + "wind_degree": 210, + "wind_dir": "SSW", + "pressure_mb": 1001, + "pressure_in": 29.56, + "precip_mm": 0, + "precip_in": 0, + "humidity": 94, + "cloud": 75, + "feelslike_c": 11.2, + "feelslike_f": 52.1, + "vis_km": 10, + "vis_miles": 6, + "uv": 4, + "gust_mph": 17.9, + "gust_kph": 28.8 + } } diff --git a/tests/Unit/Helpers/WeatherHelperTest.php b/tests/Unit/Helpers/WeatherHelperTest.php index ee1c159af36..d3af88dd3eb 100644 --- a/tests/Unit/Helpers/WeatherHelperTest.php +++ b/tests/Unit/Helpers/WeatherHelperTest.php @@ -4,7 +4,12 @@ use Tests\FeatureTestCase; use App\Helpers\WeatherHelper; +use App\Jobs\GetGPSCoordinate; +use App\Models\Contact\Address; use App\Models\Contact\Contact; +use Illuminate\Bus\PendingBatch; +use App\Jobs\GetWeatherInformation; +use Illuminate\Support\Facades\Bus; use Illuminate\Foundation\Testing\DatabaseTransactions; class WeatherHelperTest extends FeatureTestCase @@ -17,4 +22,27 @@ public function it_returns_null_if_address_is_not_set() $contact = factory(Contact::class)->create([]); $this->assertNull(WeatherHelper::getWeatherForAddress($contact->addresses()->first())); } + + /** @test */ + public function it_dispatch_batch_with_get_coordinates() + { + config(['monica.enable_geolocation' => true]); + config(['monica.location_iq_api_key' => 'test']); + config(['monica.enable_weather' => true]); + config(['monica.weatherapi_key' => 'test']); + + $fake = Bus::fake(); + + $address = factory(Address::class)->create(); + + WeatherHelper::getWeatherForAddress($address); + + $fake->assertBatched(function (PendingBatch $pendingBatch) { + $this->assertCount(2, $pendingBatch->jobs); + $this->assertInstanceOf(GetGPSCoordinate::class, $pendingBatch->jobs[0]); + $this->assertInstanceOf(GetWeatherInformation::class, $pendingBatch->jobs[1]); + + return true; + }); + } } diff --git a/tests/Unit/Jobs/GetWeatherInformationTest.php b/tests/Unit/Jobs/GetWeatherInformationTest.php new file mode 100644 index 00000000000..1476bed3967 --- /dev/null +++ b/tests/Unit/Jobs/GetWeatherInformationTest.php @@ -0,0 +1,57 @@ +create([ + 'latitude' => '34.112456', + 'longitude' => '-118.4270732', + ]); + + $this->mock(GetWeatherInformationService::class, function (MockInterface $mock) use ($place) { + $mock->shouldReceive('execute') + ->once() + ->withArgs(function ($data) use ($place) { + $this->assertEquals([ + 'account_id' => $place->account_id, + 'place_id' => $place->id, + ], $data); + + return true; + }); + }); + + $pendingBatch = $fake->batch([ + $job = new GetWeatherInformation($place), + ]); + $batch = $pendingBatch->dispatch(); + + $fake->assertBatched(function (PendingBatch $pendingBatch) { + $this->assertCount(1, $pendingBatch->jobs); + $this->assertInstanceOf(GetWeatherInformation::class, $pendingBatch->jobs->first()); + + return true; + }); + + $batch = app(DatabaseBatchRepository::class)->store($pendingBatch); + $job->withBatchId($batch->id)->handle(); + } +} diff --git a/tests/Unit/Models/WeatherTest.php b/tests/Unit/Models/WeatherTest.php index a891958e543..b35ddcca62d 100644 --- a/tests/Unit/Models/WeatherTest.php +++ b/tests/Unit/Models/WeatherTest.php @@ -34,7 +34,7 @@ public function it_gets_current_temperature() $weather = factory(Weather::class)->create(); $this->assertEquals( - 7.6, + 13, $weather->temperature() ); } @@ -45,7 +45,7 @@ public function it_gets_current_temperature_in_celsius() $weather = factory(Weather::class)->create(); $this->assertEquals( - 7.6, + 13, $weather->temperature('celsius') ); } @@ -56,7 +56,7 @@ public function it_gets_current_temperature_in_fahrenheit() $weather = factory(Weather::class)->create(); $this->assertEquals( - 45.6, + 55.4, $weather->temperature('fahrenheit') ); } @@ -67,19 +67,19 @@ public function it_gets_current_summary() $weather = factory(Weather::class)->create(); $this->assertEquals( - 'Mostly Cloudy', + 'Partly cloudy', $weather->summary ); } /** @test */ - public function it_gets_current_icon() + public function it_gets_current_code() { $weather = factory(Weather::class)->create(); $this->assertEquals( 'partly-cloudy-night', - $weather->summaryIcon + $weather->summary_code ); } @@ -90,7 +90,7 @@ public function it_gets_weather_emoji() $this->assertEquals( '🎑', - $weather->getEmoji() + $weather->emoji ); } } diff --git a/tests/Unit/Services/Instance/Geolocalization/GetGPSCoordinateTest.php b/tests/Unit/Services/Instance/Geolocalization/GetGPSCoordinateTest.php index a6b4f9204cd..3d08930e0de 100644 --- a/tests/Unit/Services/Instance/Geolocalization/GetGPSCoordinateTest.php +++ b/tests/Unit/Services/Instance/Geolocalization/GetGPSCoordinateTest.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Http; use App\Exceptions\RateLimitedSecondException; use Illuminate\Validation\ValidationException; +use App\Exceptions\MissingEnvVariableException; use Illuminate\Foundation\Testing\DatabaseTransactions; use App\Services\Instance\Geolocalization\GetGPSCoordinate; @@ -26,9 +27,8 @@ public function it_returns_null_if_geolocation_is_disabled() 'place_id' => $place->id, ]; - $place = app(GetGPSCoordinate::class)->execute($request); - - $this->assertNull($place); + $this->expectException(MissingEnvVariableException::class); + app(GetGPSCoordinate::class)->execute($request); } /** @test */ @@ -92,6 +92,9 @@ public function it_returns_null_if_address_is_garbage() /** @test */ public function it_fails_if_wrong_parameters_are_given() { + config(['monica.enable_geolocation' => true]); + config(['monica.location_iq_api_key' => 'test']); + $request = [ 'account_id' => 111, ]; diff --git a/tests/Unit/Services/Instance/Weather/GetWeatherInformationTest.php b/tests/Unit/Services/Instance/Weather/GetWeatherInformationTest.php index b71f82c0582..52f386de94d 100644 --- a/tests/Unit/Services/Instance/Weather/GetWeatherInformationTest.php +++ b/tests/Unit/Services/Instance/Weather/GetWeatherInformationTest.php @@ -4,9 +4,9 @@ use Tests\TestCase; use App\Models\Account\Place; -use App\Models\Account\Account; use App\Models\Account\Weather; use Illuminate\Support\Facades\Http; +use App\Exceptions\NoCoordinatesException; use Illuminate\Validation\ValidationException; use App\Exceptions\MissingEnvVariableException; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -17,7 +17,7 @@ class GetWeatherInformationTest extends TestCase use DatabaseTransactions; /** @test */ - public function it_gets_weather_information() + public function it_gets_weather_information_normal() { $place = factory(Place::class)->create([ 'latitude' => '34.112456', @@ -25,77 +25,28 @@ public function it_gets_weather_information() ]); config(['monica.enable_weather' => true]); - config(['monica.darksky_api_key' => 'test']); + config(['monica.weatherapi_key' => 'test']); $body = file_get_contents(base_path('tests/Fixtures/Services/Instance/Weather/GetWeatherInformationSampleResponse.json')); Http::fake([ - 'api.darksky.net/forecast/*' => Http::response($body, 200), + 'api.weatherapi.com/v1/*' => Http::response($body, 200), ]); $request = [ - 'place_id' => $place->id, - ]; - - $weather = app(GetWeatherInformation::class)->execute($request); - - $this->assertDatabaseHas('weather', [ - 'id' => $weather->id, 'account_id' => $place->account_id, 'place_id' => $place->id, - ]); - - $this->assertEquals( - 'Partly Cloudy', - $weather->summary - ); - - $this->assertInstanceOf( - Weather::class, - $weather - ); - } - - /** @test */ - public function it_gets_weather_information_for_new_place() - { - $account = factory(Account::class)->create(); - $place = factory(Place::class)->create([ - 'account_id' => $account->id, - ]); - - config(['monica.enable_weather' => true]); - config(['monica.darksky_api_key' => 'test']); - config(['monica.enable_geolocation' => true]); - config(['monica.location_iq_api_key' => 'test']); - - $body = file_get_contents(base_path('tests/Fixtures/Services/Instance/Weather/GetWeatherInformationSampleResponse.json')); - $placeBody = file_get_contents(base_path('tests/Fixtures/Services/Account/Place/CreatePlaceSampleResponse.json')); - Http::fake([ - 'us1.locationiq.com/v1/*' => Http::response($placeBody, 200), - 'api.darksky.net/forecast/*' => Http::response($body, 200), - ]); - - $request = [ - 'place_id' => $place->id, ]; $weather = app(GetWeatherInformation::class)->execute($request); $this->assertDatabaseHas('weather', [ 'id' => $weather->id, - 'account_id' => $account->id, + 'account_id' => $place->account_id, 'place_id' => $place->id, ]); - $this->assertDatabaseHas('places', [ - 'id' => $place->id, - 'account_id' => $account->id, - 'street' => '12', - 'latitude' => 34.0736204, - 'longitude' => -118.4003563, - ]); $this->assertEquals( - 'Partly Cloudy', + 'Partly cloudy', $weather->summary ); @@ -124,7 +75,7 @@ public function it_cant_get_weather_info_if_weather_not_enabled() } /** @test */ - public function it_cant_get_weather_info_if_darksky_api_key_not_provided() + public function it_cant_get_weather_info_if_weatherapi_key_not_provided() { $place = factory(Place::class)->create([ 'latitude' => '34.112456', @@ -132,9 +83,10 @@ public function it_cant_get_weather_info_if_darksky_api_key_not_provided() ]); config(['monica.enable_weather' => true]); - config(['monica.darksky_api_key' => null]); + config(['monica.weatherapi_key' => null]); $request = [ + 'account_id' => $place->account_id, 'place_id' => $place->id, ]; @@ -148,21 +100,23 @@ public function it_cant_get_weather_info_if_latitude_longitude_are_null() $place = factory(Place::class)->create([]); config(['monica.enable_weather' => true]); - config(['monica.darksky_api_key' => 'test']); + config(['monica.weatherapi_key' => 'test']); config(['monica.enable_geolocation' => false]); $request = [ + 'account_id' => $place->account_id, 'place_id' => $place->id, ]; - $this->assertNull(app(GetWeatherInformation::class)->execute($request)); + $this->expectException(NoCoordinatesException::class); + app(GetWeatherInformation::class)->execute($request); } /** @test */ public function it_fails_if_wrong_parameters_are_given() { config(['monica.enable_weather' => true]); - config(['monica.darksky_api_key' => 'test']); + config(['monica.weatherapi_key' => 'test']); $request = [];