diff --git a/app/Console/Commands/FetchOilPrices.php b/app/Console/Commands/FetchOilPrices.php index 7cbe695..be11547 100644 --- a/app/Console/Commands/FetchOilPrices.php +++ b/app/Console/Commands/FetchOilPrices.php @@ -7,6 +7,7 @@ use App\Services\BrentPriceSources\BrentPriceFetchException; use Illuminate\Console\Attributes\Description; use Illuminate\Console\Attributes\Signature; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Log; #[Signature('oil:fetch')] #[Description('Fetch latest Brent crude prices (EIA primary, FRED fallback)')] @@ -20,6 +21,7 @@ class FetchOilPrices extends Command return self::SUCCESS; } catch (BrentPriceFetchException $e) { + Log::warning('FetchOilPrices: EIA fetch failed, falling back to FRED', ['error' => $e->getMessage()]); $this->warn('EIA fetch failed: '.$e->getMessage().'. Trying FRED...'); } @@ -29,6 +31,7 @@ class FetchOilPrices extends Command return self::SUCCESS; } catch (BrentPriceFetchException $e) { + Log::error('FetchOilPrices: both EIA and FRED failed', ['error' => $e->getMessage()]); $this->error('Both EIA and FRED failed: '.$e->getMessage()); return self::FAILURE; diff --git a/app/Filament/Resources/UserResource.php b/app/Filament/Resources/UserResource.php index 1ec4ce3..ef3ffeb 100644 --- a/app/Filament/Resources/UserResource.php +++ b/app/Filament/Resources/UserResource.php @@ -9,6 +9,7 @@ use App\Filament\Resources\UserResource\Pages\EditUser; use App\Filament\Resources\UserResource\Pages\ListUsers; use App\Models\Plan; use App\Models\User; +use App\Services\PlanFeatures; use Filament\Actions\DeleteAction; use Filament\Actions\EditAction; use Filament\Forms\Components\DateTimePicker; @@ -75,7 +76,7 @@ class UserResource extends Resource ->live() ->dehydrated(false) ->afterStateHydrated(fn (Select $component, ?User $record) => $component - ->state($record ? Plan::resolveForUser($record)->name : PlanTier::Free->value)), + ->state($record ? PlanFeatures::for($record)->tier() : PlanTier::Free->value)), Select::make('cadence') ->label('Billing Cadence') ->options([ @@ -131,7 +132,7 @@ class UserResource extends Resource TextColumn::make('postcode')->placeholder('—'), TextColumn::make('tier') ->label('Tier') - ->state(fn (User $record): string => Plan::resolveForUser($record)->name) + ->state(fn (User $record): string => PlanFeatures::for($record)->tier()) ->badge() ->colors([ 'gray' => 'free', diff --git a/app/Http/Controllers/Api/AuthController.php b/app/Http/Controllers/Api/AuthController.php index 9740606..5a1827e 100644 --- a/app/Http/Controllers/Api/AuthController.php +++ b/app/Http/Controllers/Api/AuthController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\Plan; use App\Models\User; +use App\Services\PlanFeatures; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -70,7 +71,7 @@ class AuthController extends Controller return response()->json(array_merge( $user->toArray(), [ - 'tier' => Plan::resolveForUser($user)->name, + 'tier' => PlanFeatures::for($user)->tier(), 'subscription_cancelled' => $subscription?->canceled() ?? false, 'subscription_cadence' => Plan::resolveCadenceForUser($user), 'subscribed_at' => $subscription?->created_at?->toIso8601String(), diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f8e6951..4279bbf 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -59,13 +59,6 @@ class AppServiceProvider extends ServiceProvider app()->isProduction(), ); - // SQLite lacks GREATEST/LEAST scalar functions — register them for tests. - if (DB::connection()->getDriverName() === 'sqlite') { - $pdo = DB::connection()->getPdo(); - $pdo->sqliteCreateFunction('GREATEST', fn (...$args) => max($args), -1); - $pdo->sqliteCreateFunction('LEAST', fn (...$args) => min($args), -1); - } - Password::defaults(fn (): ?Password => app()->isProduction() ? Password::min(12) ->mixedCase() diff --git a/app/Services/FuelPriceService.php b/app/Services/FuelPriceService.php index ad66f47..c7ab1e3 100644 --- a/app/Services/FuelPriceService.php +++ b/app/Services/FuelPriceService.php @@ -209,9 +209,9 @@ class FuelPriceService 'postcode' => $data['location']['postcode'], 'lat' => $data['location']['latitude'], 'lng' => $data['location']['longitude'], - 'amenities' => self::flattenEnabledFlags($data['amenities'] ?? []), + 'amenities' => $this->flattenEnabledFlags($data['amenities'] ?? []), 'opening_times' => $data['opening_times'] ?? null, - 'fuel_types' => self::flattenEnabledFlags($data['fuel_types'] ?? []), + 'fuel_types' => $this->flattenEnabledFlags($data['fuel_types'] ?? []), 'last_seen_at' => $now, ]); @@ -242,7 +242,7 @@ class FuelPriceService * @param array|array $flags * @return array */ - private static function flattenEnabledFlags(array $flags): array + private function flattenEnabledFlags(array $flags): array { if ($flags === []) { return []; diff --git a/tests/TestCase.php b/tests/TestCase.php index a574f37..529477b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,10 +3,26 @@ namespace Tests; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; +use Illuminate\Support\Facades\DB; use Laravel\Fortify\Features; abstract class TestCase extends BaseTestCase { + protected function setUp(): void + { + parent::setUp(); + + // SQLite lacks GREATEST/LEAST scalar functions — register PHP-backed + // shims so the haversine and other math expressions used in + // production-style queries run identically in :memory: tests. + // Idempotent: registering twice on the same PDO is harmless. + if (DB::connection()->getDriverName() === 'sqlite') { + $pdo = DB::connection()->getPdo(); + $pdo->sqliteCreateFunction('GREATEST', fn (...$args) => max($args), -1); + $pdo->sqliteCreateFunction('LEAST', fn (...$args) => min($args), -1); + } + } + protected function skipUnlessFortifyHas(string $feature, ?string $message = null): void { if (! Features::enabled($feature)) {