search-console: vorübergehende Google-Fehler einmal wiederholt und nur als Warnung protokolliert, Portal-Abbruch nach fünf Fehlern in Folge, Zeitbudget 40 Minuten (GlitchTip server-pressekonto 11–14)
Am 06.10.2026 hing die URL Inspection API zeitweise: 28 Zeitüberschreitungen und 4 × 500, jede einzeln als Fehler gemeldet, der Lauf zog sich über 1¾ Stunden. Neu: SearchConsoleUnavailable für Timeout/5xx/429 (Warnung, Meldung bleibt fällig), echte Fehler (401/403/400) weiter über report(); FAILURE — und damit ein Betriebs-Alert — nur bei Abbruch oder ≥ 50 % Fehlern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f02acceaf
commit
a90f24a43f
5 changed files with 228 additions and 19 deletions
|
|
@ -13,7 +13,9 @@ use App\Services\SearchConsole\IndexStatusChecker;
|
|||
use App\Services\SearchConsole\IndexStatusReport;
|
||||
use App\Services\SearchConsole\SearchConsoleClient;
|
||||
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||
use Illuminate\Support\Facades\Exceptions;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
|
|
@ -151,6 +153,124 @@ test('the command does nothing without configuration', function () {
|
|||
expect(PressReleaseIndexCheck::query()->count())->toBe(0);
|
||||
});
|
||||
|
||||
/**
|
||||
* Robustheit des Laufs (06.10.2026: 28 Zeitüberschreitungen und 4 × 500 von
|
||||
* Google, jede einzeln als Fehler gemeldet, Lauf über 1¾ Stunden).
|
||||
*/
|
||||
function inspectionOk(): array
|
||||
{
|
||||
return ['inspectionResult' => ['indexStatusResult' => [
|
||||
'verdict' => 'PASS',
|
||||
'coverageState' => 'Submitted and indexed',
|
||||
'indexingState' => 'INDEXING_ALLOWED',
|
||||
]]];
|
||||
}
|
||||
|
||||
function robustIndexRun(): void
|
||||
{
|
||||
fakeServiceAccount();
|
||||
config()->set('search_console.properties.presseecho', 'sc-domain:presseecho.test');
|
||||
config()->set('search_console.properties.businessportal24', null);
|
||||
config()->set('search_console.retry_sleep_ms', 0);
|
||||
}
|
||||
|
||||
test('a google timeout is retried once and then counts as a normal check', function () {
|
||||
/** @var TestCase $this */
|
||||
robustIndexRun();
|
||||
publishedOn(Portal::Presseecho, 2);
|
||||
|
||||
Http::fake([
|
||||
'https://oauth2.googleapis.com/token' => Http::response(['access_token' => 'token-123']),
|
||||
SearchConsoleClient::INSPECT_URL => Http::sequence()
|
||||
->pushFailedConnection('cURL error 28: Operation timed out after 20002 milliseconds')
|
||||
->push(inspectionOk()),
|
||||
]);
|
||||
|
||||
$this->artisan(CheckIndexStatus::class, ['--budget' => 5])
|
||||
->expectsOutputToContain('1 Prüfungen gespeichert, 0 fehlgeschlagen.')
|
||||
->assertSuccessful();
|
||||
|
||||
Http::assertSentCount(3);
|
||||
expect(PressReleaseIndexCheck::query()->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('temporary google failures are logged as warnings, not reported, and stay due', function () {
|
||||
/** @var TestCase $this */
|
||||
robustIndexRun();
|
||||
Exceptions::fake();
|
||||
Log::spy();
|
||||
publishedOn(Portal::Presseecho, 2);
|
||||
publishedOn(Portal::Presseecho, 3);
|
||||
|
||||
Http::fake([
|
||||
'https://oauth2.googleapis.com/token' => Http::response(['access_token' => 'token-123']),
|
||||
SearchConsoleClient::INSPECT_URL => Http::response(['error' => ['message' => 'Internal error encountered.']], 500),
|
||||
]);
|
||||
|
||||
$this->artisan(CheckIndexStatus::class, ['--budget' => 5])
|
||||
->expectsOutputToContain('0 Prüfungen gespeichert, 2 fehlgeschlagen.')
|
||||
->assertSuccessful();
|
||||
|
||||
Exceptions::assertNothingReported();
|
||||
Log::shouldHaveReceived('warning')->with('Search Console vorübergehend nicht verfügbar.', Mockery::any())->twice();
|
||||
expect(PressReleaseIndexCheck::query()->count())->toBe(0)
|
||||
->and(app(IndexStatusChecker::class)->candidates(Portal::Presseecho, 5))->toHaveCount(2);
|
||||
});
|
||||
|
||||
test('a portal is aborted after five failures in a row and the run fails once', function () {
|
||||
/** @var TestCase $this */
|
||||
robustIndexRun();
|
||||
Exceptions::fake();
|
||||
foreach (range(1, 8) as $day) {
|
||||
publishedOn(Portal::Presseecho, $day);
|
||||
}
|
||||
|
||||
Http::fake([
|
||||
'https://oauth2.googleapis.com/token' => Http::response(['access_token' => 'token-123']),
|
||||
SearchConsoleClient::INSPECT_URL => Http::failedConnection(),
|
||||
]);
|
||||
|
||||
$this->artisan(CheckIndexStatus::class, ['--budget' => 10])
|
||||
->expectsOutputToContain('Abbruch nach 5 Fehlern in Folge')
|
||||
->assertFailed();
|
||||
|
||||
// Fünf Meldungen mit je einer Wiederholung plus Token — nicht alle acht.
|
||||
Http::assertSentCount(1 + 5 * 2);
|
||||
Exceptions::assertNothingReported();
|
||||
});
|
||||
|
||||
test('a configuration error is reported and not retried', function () {
|
||||
/** @var TestCase $this */
|
||||
robustIndexRun();
|
||||
Exceptions::fake();
|
||||
publishedOn(Portal::Presseecho, 2);
|
||||
|
||||
Http::fake([
|
||||
'https://oauth2.googleapis.com/token' => Http::response(['access_token' => 'token-123']),
|
||||
SearchConsoleClient::INSPECT_URL => Http::response(['error' => ['message' => 'User does not have sufficient permission for site.']], 403),
|
||||
]);
|
||||
|
||||
$this->artisan(CheckIndexStatus::class, ['--budget' => 5])->assertSuccessful();
|
||||
|
||||
Http::assertSentCount(2);
|
||||
Exceptions::assertReported(fn (RuntimeException $e) => str_contains($e->getMessage(), 'antwortet mit 403'));
|
||||
});
|
||||
|
||||
test('the run stops at its time budget and leaves the rest for tomorrow', function () {
|
||||
/** @var TestCase $this */
|
||||
robustIndexRun();
|
||||
config()->set('search_console.max_runtime_minutes', 0);
|
||||
publishedOn(Portal::Presseecho, 2);
|
||||
|
||||
Http::fake();
|
||||
|
||||
$this->artisan(CheckIndexStatus::class, ['--budget' => 5])
|
||||
->expectsOutputToContain('Zeitbudget erreicht')
|
||||
->assertSuccessful();
|
||||
|
||||
Http::assertNothingSent();
|
||||
});
|
||||
|
||||
test('the admin report splits the distribution by origin and lists the latest checks', function () {
|
||||
/** @var TestCase $this */
|
||||
test()->seed(RolesAndPermissionsSeeder::class);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue