feat: get weather from weatherapi (#5668)

This commit is contained in:
Alexis Saettler
2021-10-30 23:00:14 +02:00
committed by GitHub
parent 223eb85ed1
commit d19b6adc37
20 changed files with 481 additions and 235 deletions
+2 -3
View File
@@ -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
@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use RuntimeException;
class NoCoordinatesException extends RuntimeException
{
}
+21 -16
View File
@@ -2,9 +2,11 @@
namespace App\Helpers;
use App\Jobs\GetGPSCoordinate;
use App\Models\Account\Weather;
use App\Models\Contact\Address;
use App\Services\Instance\Weather\GetWeatherInformation;
use App\Jobs\GetWeatherInformation;
use Illuminate\Support\Facades\Bus;
class WeatherHelper
{
@@ -20,16 +22,14 @@ class WeatherHelper
return null;
}
$weather = $address->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 @@ class WeatherHelper
* 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();
}
}
+6 -1
View File
@@ -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 @@ use App\Services\Instance\Geolocalization\GetGPSCoordinate as GetGPSCoordinateSe
class GetGPSCoordinate implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var Place
@@ -64,6 +65,10 @@ class GetGPSCoordinate implements ShouldQueue
*/
public function handle()
{
if (($batch = $this->batch()) !== null && $batch->cancelled()) {
return;
}
try {
app(GetGPSCoordinateService::class)->execute([
'account_id' => $this->place->account_id,
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace App\Jobs;
use App\Models\Account\Place;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use App\Exceptions\NoCoordinatesException;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Services\Instance\Weather\GetWeatherInformation as GetWeatherInformationService;
class GetWeatherInformation implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var Place
*/
protected $place;
/**
* The number of times the job may be attempted.
*
* @var int
*/
public $tries = 10;
/**
* The maximum number of unhandled exceptions to allow before failing.
*
* @var int
*/
public $maxExceptions = 1;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Place $place)
{
$this->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,
]);
}
}
}
+86 -23
View File
@@ -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',
];
@@ -55,50 +59,90 @@ class Weather extends Model
return $this->belongsTo(Place::class);
}
/**
* Get the weather code.
*
* @return string
*/
public function getSummaryCodeAttribute()
{
$json = $this->weather_json;
// 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.
*
* @return string
*/
public function getSummaryAttribute($value)
public function getSummaryAttribute()
{
$json = $this->weather_json;
return $json['currently']['summary'];
return trans('app.weather_'.$this->summary_code);
}
/**
* Get the weather summary icon.
* Get the weather icon.
*
* @return string
*/
public function getSummaryIconAttribute($value)
{
$json = $this->weather_json;
return $json['currently']['icon'];
}
/**
* Get the emoji representing the weather.
*
* @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 @@ class Weather extends Model
$string = '💨';
break;
case 'fog':
case 'mist':
case 'blizzard':
case 'freezing-fog':
$string = '🌫️';
break;
case 'overcast':
case 'cloudy':
$string = '☁️';
break;
@@ -117,6 +165,21 @@ class Weather extends Model
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 @@ class Weather extends Model
{
$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);
+2 -2
View File
@@ -1490,9 +1490,9 @@ class Contact extends Model
* 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());
}
@@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Log;
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 @@ class GetGPSCoordinate extends BaseService
*/
public function execute(array $data)
{
$this->validateWeatherEnvVariables();
$this->validate($data);
$place = Place::where('account_id', $data['account_id'])
@@ -42,18 +45,26 @@ class GetGPSCoordinate extends BaseService
return $this->query($place);
}
/**
* Make sure that geolocation env variables are set.
*
* @return void
*/
private function validateWeatherEnvVariables()
{
if (! config('monica.enable_geolocation') || is_null(config('monica.location_iq_api_key'))) {
throw new MissingEnvVariableException();
}
}
/**
* Build the query to send with the API call.
*
* @param Place $place
* @return string|null
* @return string
*/
private function buildQuery(Place $place): ?string
private function buildQuery(Place $place): string
{
if (! config('monica.enable_geolocation') || is_null(config('monica.location_iq_api_key'))) {
return null;
}
$query = http_build_query([
'format' => 'json',
'key' => config('monica.location_iq_api_key'),
@@ -73,10 +84,6 @@ class GetGPSCoordinate extends BaseService
{
$query = $this->buildQuery($place);
if (is_null($query)) {
return null;
}
try {
$response = Http::get($query);
$response->throw();
@@ -5,10 +5,10 @@ namespace App\Services\Instance\Weather;
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 @@ class GetWeatherInformation extends BaseService
$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 @@ class GetWeatherInformation extends BaseService
*/
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 @@ class GetWeatherInformation extends BaseService
*
* @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 @@ class GetWeatherInformation extends BaseService
* @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;
$query = [
'key' => config('monica.weatherapi_key'),
'q' => $coords,
'lang' => $lang ?? 'en',
];
if ($lang !== null && $lang !== 'en') {
$query['lang'] = $lang;
}
/**
* 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;
}
return null;
return Str::of(config('location.weatherapi_url'))->rtrim('/').'?'.http_build_query($query);
}
}
+3 -3
View File
@@ -94,12 +94,12 @@ return [
/*
|--------------------------------------------------------------------------
| 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'),
];
+3 -4
View File
@@ -249,11 +249,10 @@ return [
| 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),
/*
|--------------------------------------------------------------------------
+40 -26
View File
@@ -110,33 +110,47 @@ $factory->define(App\Models\Account\Weather::class, function (Faker\Generator $f
])->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
{
"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": -5
}'),
"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(),
];
});
+49 -3
View File
@@ -441,16 +441,62 @@ return [
'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',
+1 -1
View File
@@ -51,7 +51,7 @@
</div>
<p class="mb0">
{{ $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)]) }}
</p>
</div>
@endif
@@ -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
}
}
+28
View File
@@ -4,7 +4,12 @@ namespace Tests\Unit\Helpers;
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 @@ class WeatherHelperTest extends FeatureTestCase
$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;
});
}
}
@@ -0,0 +1,57 @@
<?php
namespace Tests\Unit\Jobs;
use Tests\TestCase;
use Mockery\MockInterface;
use App\Models\Account\Place;
use Illuminate\Bus\PendingBatch;
use App\Jobs\GetWeatherInformation;
use Illuminate\Support\Facades\Bus;
use Illuminate\Bus\DatabaseBatchRepository;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Services\Instance\Weather\GetWeatherInformation as GetWeatherInformationService;
class GetWeatherInformationTest extends TestCase
{
use DatabaseTransactions;
/** @test */
public function it_run_job_weather_information()
{
$fake = Bus::fake();
$place = factory(Place::class)->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();
}
}
+7 -7
View File
@@ -34,7 +34,7 @@ class WeatherTest extends TestCase
$weather = factory(Weather::class)->create();
$this->assertEquals(
7.6,
13,
$weather->temperature()
);
}
@@ -45,7 +45,7 @@ class WeatherTest extends TestCase
$weather = factory(Weather::class)->create();
$this->assertEquals(
7.6,
13,
$weather->temperature('celsius')
);
}
@@ -56,7 +56,7 @@ class WeatherTest extends TestCase
$weather = factory(Weather::class)->create();
$this->assertEquals(
45.6,
55.4,
$weather->temperature('fahrenheit')
);
}
@@ -67,19 +67,19 @@ class WeatherTest extends TestCase
$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 @@ class WeatherTest extends TestCase
$this->assertEquals(
'🎑',
$weather->getEmoji()
$weather->emoji
);
}
}
@@ -7,6 +7,7 @@ use App\Models\Account\Place;
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 @@ class GetGPSCoordinateTest extends TestCase
'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 @@ class GetGPSCoordinateTest extends TestCase
/** @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,
];
@@ -4,9 +4,9 @@ namespace Tests\Unit\Services\Instance\Weather;
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,14 +25,15 @@ class GetWeatherInformationTest extends TestCase
]);
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 = [
'account_id' => $place->account_id,
'place_id' => $place->id,
];
@@ -45,57 +46,7 @@ class GetWeatherInformationTest extends TestCase
]);
$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,
'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 @@ class GetWeatherInformationTest extends TestCase
}
/** @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 @@ class GetWeatherInformationTest extends TestCase
]);
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 @@ class GetWeatherInformationTest extends TestCase
$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 = [];