Add tests for town sync job and search endpoint
All checks were successful
linter / quality (pull_request) Successful in 1m37s
security / Dependency Audit (pull_request) Successful in 1m36s
security / Static Analysis (pull_request) Successful in 1m25s
tests / ci (8.4) (pull_request) Successful in 1m57s
tests / ci (8.5) (pull_request) Successful in 1m31s

- SyncTownsTest: covers API syncing, pagination, upsert, retired towns, error logging, and success logging
- TownSearchTest: covers auth requirement, min query length, prefix matching, state filtering, retired town exclusion, result limit, response shape, and ordering
- Update TownFactory with retired() and inState() states
This commit is contained in:
2026-03-06 04:22:49 +00:00
parent a37ada96e2
commit 72fb80b673
3 changed files with 289 additions and 1 deletions

View File

@@ -0,0 +1,150 @@
<?php
namespace Tests\Feature\Jobs;
use App\Jobs\SyncTowns;
use App\Models\Town;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Tests\TestCase;
class SyncTownsTest extends TestCase
{
use RefreshDatabase;
private const API_URL = 'https://services-ap1.arcgis.com/ypkPEy1AmwPKGNNv/arcgis/rest/services/Town_Point/FeatureServer/0/query*';
private function makeTownFeature(array $overrides = []): array
{
return [
'attributes' => array_merge([
'town_pid' => 'TWN0001',
'town_name' => 'Perth',
'state' => 5,
'population' => 2000000,
'town_class' => 1,
'date_retired' => null,
], $overrides),
];
}
public function test_syncs_towns_from_api(): void
{
Http::fake([
self::API_URL => Http::sequence()
->push(['features' => [
$this->makeTownFeature(['town_pid' => 'TWN0001', 'town_name' => 'Perth', 'state' => 5]),
$this->makeTownFeature(['town_pid' => 'TWN0002', 'town_name' => 'Fremantle', 'state' => 5]),
]])
->push(['features' => []]),
]);
(new SyncTowns)->handle();
$this->assertDatabaseHas('towns', ['town_pid' => 'TWN0001', 'town_name' => 'Perth']);
$this->assertDatabaseHas('towns', ['town_pid' => 'TWN0002', 'town_name' => 'Fremantle']);
$this->assertDatabaseCount('towns', 2);
}
public function test_paginates_through_all_pages(): void
{
$pageOne = [];
for ($i = 1; $i <= 1000; $i++) {
$pageOne[] = $this->makeTownFeature(['town_pid' => 'TWN'.str_pad($i, 4, '0', STR_PAD_LEFT)]);
}
Http::fake([
self::API_URL => Http::sequence()
->push(['features' => $pageOne])
->push(['features' => [
$this->makeTownFeature(['town_pid' => 'TWN1001', 'town_name' => 'Albany']),
]])
->push(['features' => []]),
]);
(new SyncTowns)->handle();
$this->assertDatabaseCount('towns', 1001);
$this->assertDatabaseHas('towns', ['town_pid' => 'TWN1001', 'town_name' => 'Albany']);
}
public function test_upserts_existing_towns_with_updated_data(): void
{
Town::factory()->create([
'town_pid' => 'TWN0001',
'town_name' => 'Old Name',
'state' => 5,
]);
Http::fake([
self::API_URL => Http::sequence()
->push(['features' => [
$this->makeTownFeature(['town_pid' => 'TWN0001', 'town_name' => 'Perth', 'population' => 2100000]),
]])
->push(['features' => []]),
]);
(new SyncTowns)->handle();
$this->assertDatabaseCount('towns', 1);
$this->assertDatabaseHas('towns', ['town_pid' => 'TWN0001', 'town_name' => 'Perth', 'population' => 2100000]);
}
public function test_marks_retired_towns_with_date_retired(): void
{
$retiredAt = Carbon::parse('2024-01-15')->startOfDay();
Http::fake([
self::API_URL => Http::sequence()
->push(['features' => [
$this->makeTownFeature([
'town_pid' => 'TWN0001',
'town_name' => 'OldTown',
'date_retired' => $retiredAt->getTimestampMs(),
]),
]])
->push(['features' => []]),
]);
(new SyncTowns)->handle();
$town = Town::where('town_pid', 'TWN0001')->first();
$this->assertNotNull($town->date_retired);
}
public function test_logs_error_and_stops_when_api_fails(): void
{
Log::spy();
Http::fake([
self::API_URL => Http::response(null, 500),
]);
(new SyncTowns)->handle();
$this->assertDatabaseCount('towns', 0);
Log::shouldHaveReceived('error')
->once()
->with('SyncTowns: API request failed', \Mockery::any());
}
public function test_logs_synced_count_on_success(): void
{
Log::spy();
Http::fake([
self::API_URL => Http::sequence()
->push(['features' => [
$this->makeTownFeature(['town_pid' => 'TWN0001']),
$this->makeTownFeature(['town_pid' => 'TWN0002']),
]])
->push(['features' => []]),
]);
(new SyncTowns)->handle();
Log::shouldHaveReceived('info')->once()->with('SyncTowns: synced 2 towns.');
}
}