feat: add PostcodeService and price validation with DB constraints
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

- Add PostcodeService to resolve UK postcodes, outcodes, and place names to coordinates via postcodes.io API with 30-day caching
- Add LocationResult value object for resolved location data
- Add per-fuel-type price validation (80p-1050p range) to FuelPriceService with warning logs for out-of-range prices
- Change price_pence column from unsignedSmallInteger to unsignedMediumInteger in station_prices tables
- Add CHECK constraints (5000-50000 range) on price_pence columns as database-level guard
- Improve error handling in PollFuelPrices command with file/line/trace output
- Add tests for PostcodeService covering full postcodes, outcodes, place names, caching, and error handling
- Add test for price validation range checks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ovidiu U
2026-04-04 12:40:43 +01:00
parent 097f1b0529
commit e532cc1208
8 changed files with 530 additions and 1 deletions

View File

@@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('station_prices', function (Blueprint $table): void {
$table->unsignedMediumInteger('price_pence')
->comment('Price in pence × 100, e.g. 15990 = 159.90p')
->change();
});
Schema::table('station_prices_current', function (Blueprint $table): void {
$table->unsignedMediumInteger('price_pence')
->comment('Price in pence × 100, e.g. 15990 = 159.90p')
->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('station_prices', function (Blueprint $table): void {
$table->unsignedSmallInteger('price_pence')
->comment('Price in pence × 100')
->change();
});
Schema::table('station_prices_current', function (Blueprint $table): void {
$table->unsignedSmallInteger('price_pence')
->comment('Price in pence × 100, e.g. 15990 = 159.90p')
->change();
});
}
};

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
/**
* Add database-level CHECK constraint on price_pence (MySQL only).
*
* Range: 5 00050 000 = 50p500p per litre.
* This is a broad last-resort guard; the application layer enforces
* tighter per-fuel-type limits before any insert reaches the DB.
*/
return new class extends Migration
{
public function up(): void
{
if (DB::getDriverName() !== 'mysql') {
return;
}
DB::statement('ALTER TABLE station_prices ADD CONSTRAINT chk_price_pence_range CHECK (price_pence BETWEEN 5000 AND 50000)');
DB::statement('ALTER TABLE station_prices_current ADD CONSTRAINT chk_current_price_pence_range CHECK (price_pence BETWEEN 5000 AND 50000)');
}
public function down(): void
{
if (DB::getDriverName() !== 'mysql') {
return;
}
DB::statement('ALTER TABLE station_prices DROP CHECK chk_price_pence_range');
DB::statement('ALTER TABLE station_prices_current DROP CHECK chk_current_price_pence_range');
}
};