feat: FuelPriceService with OAuth token caching

Also extend Pest TestCase to Unit tests and guard MySQL-only migration
DDL (composite PK + PARTITION BY) behind a driver check so in-memory
SQLite tests can run migrations cleanly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ovidiu U
2026-04-03 18:52:40 +01:00
parent 80a8a9f93b
commit a83d06d76a
4 changed files with 107 additions and 33 deletions

View File

@@ -12,7 +12,9 @@ return new class extends Migration
*/
public function up(): void
{
Schema::create('station_prices', function (Blueprint $table): void {
$isMysql = DB::getDriverName() === 'mysql';
Schema::create('station_prices', function (Blueprint $table) use ($isMysql): void {
$table->bigIncrements('id');
$table->string('station_id', 64);
$table->string('fuel_type', 20);
@@ -21,41 +23,45 @@ return new class extends Migration
$table->dateTime('price_reported_at');
$table->dateTime('recorded_at');
// Composite PK required for MySQL range partitioning
$table->primary(['id', 'price_effective_at']);
// Composite PK required for MySQL range partitioning (not supported by SQLite)
if ($isMysql) {
$table->primary(['id', 'price_effective_at']);
}
$table->index(['station_id', 'fuel_type', 'price_effective_at']);
$table->index('price_effective_at');
});
// Monthly partitions 20262027 + MAXVALUE catch-all
DB::statement("ALTER TABLE station_prices
PARTITION BY RANGE (YEAR(price_effective_at) * 100 + MONTH(price_effective_at)) (
PARTITION p202601 VALUES LESS THAN (202602),
PARTITION p202602 VALUES LESS THAN (202603),
PARTITION p202603 VALUES LESS THAN (202604),
PARTITION p202604 VALUES LESS THAN (202605),
PARTITION p202605 VALUES LESS THAN (202606),
PARTITION p202606 VALUES LESS THAN (202607),
PARTITION p202607 VALUES LESS THAN (202608),
PARTITION p202608 VALUES LESS THAN (202609),
PARTITION p202609 VALUES LESS THAN (202610),
PARTITION p202610 VALUES LESS THAN (202611),
PARTITION p202611 VALUES LESS THAN (202612),
PARTITION p202612 VALUES LESS THAN (202701),
PARTITION p202701 VALUES LESS THAN (202702),
PARTITION p202702 VALUES LESS THAN (202703),
PARTITION p202703 VALUES LESS THAN (202704),
PARTITION p202704 VALUES LESS THAN (202705),
PARTITION p202705 VALUES LESS THAN (202706),
PARTITION p202706 VALUES LESS THAN (202707),
PARTITION p202707 VALUES LESS THAN (202708),
PARTITION p202708 VALUES LESS THAN (202709),
PARTITION p202709 VALUES LESS THAN (202710),
PARTITION p202710 VALUES LESS THAN (202711),
PARTITION p202711 VALUES LESS THAN (202712),
PARTITION p202712 VALUES LESS THAN (202801),
PARTITION pFuture VALUES LESS THAN MAXVALUE
)");
// Monthly partitions 20262027 + MAXVALUE catch-all (MySQL only)
if ($isMysql) {
DB::statement("ALTER TABLE station_prices
PARTITION BY RANGE (YEAR(price_effective_at) * 100 + MONTH(price_effective_at)) (
PARTITION p202601 VALUES LESS THAN (202602),
PARTITION p202602 VALUES LESS THAN (202603),
PARTITION p202603 VALUES LESS THAN (202604),
PARTITION p202604 VALUES LESS THAN (202605),
PARTITION p202605 VALUES LESS THAN (202606),
PARTITION p202606 VALUES LESS THAN (202607),
PARTITION p202607 VALUES LESS THAN (202608),
PARTITION p202608 VALUES LESS THAN (202609),
PARTITION p202609 VALUES LESS THAN (202610),
PARTITION p202610 VALUES LESS THAN (202611),
PARTITION p202611 VALUES LESS THAN (202612),
PARTITION p202612 VALUES LESS THAN (202701),
PARTITION p202701 VALUES LESS THAN (202702),
PARTITION p202702 VALUES LESS THAN (202703),
PARTITION p202703 VALUES LESS THAN (202704),
PARTITION p202704 VALUES LESS THAN (202705),
PARTITION p202705 VALUES LESS THAN (202706),
PARTITION p202706 VALUES LESS THAN (202707),
PARTITION p202707 VALUES LESS THAN (202708),
PARTITION p202708 VALUES LESS THAN (202709),
PARTITION p202709 VALUES LESS THAN (202710),
PARTITION p202710 VALUES LESS THAN (202711),
PARTITION p202711 VALUES LESS THAN (202712),
PARTITION p202712 VALUES LESS THAN (202801),
PARTITION pFuture VALUES LESS THAN MAXVALUE
)");
}
}
/**