122 lines
4.8 KiB
PHP
122 lines
4.8 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Admin\Incidents as IncidentConsole;
|
|
use App\Models\Incident;
|
|
use App\Support\LocalTime;
|
|
use Livewire\Livewire;
|
|
|
|
/**
|
|
* Writing the record.
|
|
*
|
|
* The public page can only be as honest as what is typed here, so these tests
|
|
* are about the two ways the console could make it dishonest: letting somebody
|
|
* revise what was already said, and letting the incident and its timeline drift
|
|
* apart.
|
|
*/
|
|
it('publishes an incident together with its first update, or not at all', function () {
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(IncidentConsole::class)
|
|
->set('title', 'Instanzen nicht erreichbar')
|
|
->set('components', ['instances'])
|
|
->set('impact', 'down')
|
|
->set('startedAt', LocalTime::toField(now()->subMinutes(20)))
|
|
->set('firstUpdate', 'Mehrere Instanzen antworten nicht.')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
$incident = Incident::query()->firstOrFail();
|
|
|
|
// A headline with nothing under it is what a reader would see if the
|
|
// second insert were allowed to fail on its own.
|
|
expect($incident->updates)->toHaveCount(1)
|
|
->and($incident->updates->first()->status)->toBe('investigating')
|
|
->and($incident->isPublished())->toBeTrue()
|
|
->and($incident->components)->toBe(['instances']);
|
|
});
|
|
|
|
it('refuses an incident that names no service', function () {
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(IncidentConsole::class)
|
|
->set('title', 'Irgendwas')
|
|
->set('components', [])
|
|
->set('firstUpdate', 'Text')
|
|
->call('create')
|
|
->assertHasErrors('components');
|
|
|
|
expect(Incident::query()->count())->toBe(0);
|
|
});
|
|
|
|
it('closes the incident with the same action that says it is resolved', function () {
|
|
// Two separate actions is how a status page ends up with a "behoben" note
|
|
// under an incident that still reports as ongoing.
|
|
$incident = Incident::query()->create([
|
|
'title' => 'x', 'impact' => 'down', 'components' => ['portal'],
|
|
'started_at' => now()->subHour(), 'published_at' => now()->subHour(),
|
|
]);
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(IncidentConsole::class)
|
|
->call('startUpdate', $incident->uuid)
|
|
->set('updateStage', 'resolved')
|
|
->set('updateBody', 'Seit 20:51 stabil.')
|
|
->call('postUpdate')
|
|
->assertHasNoErrors();
|
|
|
|
expect($incident->fresh()->isResolved())->toBeTrue()
|
|
->and($incident->fresh()->stage())->toBe('resolved');
|
|
});
|
|
|
|
it('takes a correction as a further entry, never as an edit', function () {
|
|
// An update is a statement made at a time. There is deliberately no way
|
|
// here to change one after the fact.
|
|
$incident = Incident::query()->create([
|
|
'title' => 'x', 'impact' => 'degraded', 'components' => ['backups'],
|
|
'started_at' => now()->subHour(), 'published_at' => now()->subHour(),
|
|
]);
|
|
$incident->updates()->create(['status' => 'investigating', 'body' => 'Erste Einschätzung.']);
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(IncidentConsole::class)
|
|
->call('startUpdate', $incident->uuid)
|
|
->set('updateStage', 'identified')
|
|
->set('updateBody', 'Korrektur: es betrifft nur einen Host.')
|
|
->call('postUpdate');
|
|
|
|
$bodies = $incident->fresh()->updates->pluck('body');
|
|
|
|
expect($bodies)->toHaveCount(2)
|
|
->and($bodies)->toContain('Erste Einschätzung.');
|
|
|
|
expect(class_exists(App\Livewire\Admin\Incidents::class))->toBeTrue();
|
|
expect(method_exists(App\Livewire\Admin\Incidents::class, 'editUpdate'))->toBeFalse();
|
|
});
|
|
|
|
it('needs the capability, not merely an operator account', function () {
|
|
// Asserted on the PAGE rather than on the action: render() authorizes too,
|
|
// so an operator without the capability never gets far enough to press a
|
|
// button — and a Livewire::test() against a component that refuses to
|
|
// render fails with a snapshot error that says nothing about permissions.
|
|
$this->actingAs(operator('Support'), 'operator')
|
|
->get(route('admin.incidents'))
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('records the operator who wrote each line', function () {
|
|
// "Nachvollziehbar" includes who said it. Not shown publicly — this is for
|
|
// the operator looking back at their own record months later.
|
|
$owner = operator('Owner');
|
|
|
|
Livewire::actingAs($owner, 'operator')
|
|
->test(IncidentConsole::class)
|
|
->set('title', 'x')
|
|
->set('components', ['portal'])
|
|
->set('startedAt', LocalTime::toField(now()))
|
|
->set('firstUpdate', 'Text')
|
|
->call('create');
|
|
|
|
$incident = Incident::query()->firstOrFail();
|
|
|
|
expect($incident->created_by)->toBe($owner->email)
|
|
->and($incident->updates->first()->created_by)->toBe($owner->email);
|
|
});
|