From 56c53ab9c5b938b5916b332fc67df64386269084 Mon Sep 17 00:00:00 2001 From: Josh Salway Date: Wed, 22 Apr 2026 20:15:54 +1000 Subject: [PATCH] Prove failed() log shape with behavior test Tests that UpdateApps::failed() and ProcessApps::failed(): - call Log::error with the 'permanently failed' message - include exception_class, exception_message, and file context keys - for UpdateApps, still call Cache::lock('updateApps')->forceRelease() Uses Log::spy() and Mockery to capture facade calls. 2 tests, 4 assertions, green on PHP 8.4.20. See follow-up commit for why this lands in history but not in the shipped suite. --- tests/Feature/QueueFailedHandlerTest.php | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/Feature/QueueFailedHandlerTest.php diff --git a/tests/Feature/QueueFailedHandlerTest.php b/tests/Feature/QueueFailedHandlerTest.php new file mode 100644 index 000000000..2e6b3a1d2 --- /dev/null +++ b/tests/Feature/QueueFailedHandlerTest.php @@ -0,0 +1,63 @@ +failed($exception); + + Log::shouldHaveReceived('error') + ->once() + ->with( + ProcessApps::class . ' permanently failed', + Mockery::on(function ($context) { + return $context['exception_class'] === RuntimeException::class + && $context['exception_message'] === 'GitHub API rate limit exceeded' + && str_contains($context['file'], 'QueueFailedHandlerTest.php'); + }) + ); + } + + /** @test */ + public function updateApps_failed_logs_structured_context_and_releases_cache_lock(): void + { + Log::spy(); + + $lockMock = Mockery::mock(); + $lockMock->shouldReceive('forceRelease')->once(); + Cache::shouldReceive('lock')->with('updateApps')->once()->andReturn($lockMock); + + $job = new UpdateApps(); + $exception = new RuntimeException('Something broke'); + + $job->failed($exception); + + Log::shouldHaveReceived('error') + ->once() + ->with( + UpdateApps::class . ' permanently failed', + Mockery::on(function ($context) { + return $context['exception_class'] === RuntimeException::class + && $context['exception_message'] === 'Something broke' + && isset($context['file']) + && str_contains($context['file'], ':'); + }) + ); + } +}