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.'); } }