CluPilotCloud/tests/Feature/Cancellation/DashboardCountdownTest.php

159 lines
6.2 KiB
PHP

<?php // tests/Feature/Cancellation/DashboardCountdownTest.php
use App\Livewire\Dashboard;
use App\Models\Customer;
use App\Models\Instance;
use App\Models\Order;
use App\Models\User;
use Livewire\Livewire;
/**
* Ein Kunde mit einer gekündigten Instanz — nach dem Muster von
* kuendbareInstanz() (tests/Feature/Cancellation/CancelAsksAboutExportTest.php):
* Nutzer, Kunde, Bestellung, Instanz. Anders als dort schon im Zustand NACH
* der Kündigung, so wie ihn ConfirmCancelPackage::cancelPackage() hinterlässt
* — status und service_ends_at sind bereits gesetzt.
*
* @return array{0: User, 1: Instance}
*/
function gekuendigteInstanz(\Illuminate\Support\Carbon $endetIn, string $status = 'cancellation_scheduled'): array
{
$user = User::factory()->create(['email' => 'gekuendigt@example.test', 'email_verified_at' => now()]);
$customer = Customer::factory()->create([
'email' => 'gekuendigt@example.test',
'user_id' => $user->id,
'status' => 'active',
]);
$order = Order::factory()->create(['customer_id' => $customer->id, 'plan' => 'team']);
$instance = Instance::factory()->create([
'customer_id' => $customer->id,
'order_id' => $order->id,
'plan' => 'team',
'status' => $status,
'subdomain' => 'gekuendigt',
'cancel_requested_at' => now()->subDays(2),
'service_ends_at' => $endetIn,
]);
return [$user, $instance->refresh()];
}
/**
* Ein Kunde mit einer laufenden Instanz, die niemand gekündigt hat —
* dieselbe Form wie gekuendigteInstanz(), nur ohne Kündigung.
*
* @return array{0: User, 1: Instance}
*/
function laufendeInstanz(): array
{
$user = User::factory()->create(['email' => 'laeuft@example.test', 'email_verified_at' => now()]);
$customer = Customer::factory()->create([
'email' => 'laeuft@example.test',
'user_id' => $user->id,
'status' => 'active',
]);
$order = Order::factory()->create(['customer_id' => $customer->id, 'plan' => 'team']);
$instance = Instance::factory()->create([
'customer_id' => $customer->id,
'order_id' => $order->id,
'plan' => 'team',
'status' => 'active',
'subdomain' => 'laeuft',
]);
return [$user, $instance->refresh()];
}
it('zeigt nach einer Kuendigung, wie lange der Zugang noch steht', function () {
[$user, $instance] = gekuendigteInstanz(endetIn: now()->addDays(12));
Livewire::actingAs($user)->test(Dashboard::class)
->assertSee(__('dashboard.ending.title'))
->assertSee($instance->service_ends_at->local()->isoFormat('LL'));
});
it('zeigt den Streifen nicht, solange nicht gekuendigt wurde', function () {
[$user] = laufendeInstanz();
Livewire::actingAs($user)->test(Dashboard::class)
->assertDontSee(__('dashboard.ending.title'));
});
it('zeigt ihn auch nach dem Laufzeitende nicht mehr', function () {
// Danach ist der Zugang zu, und ein Streifen, der eine Restzeit nennt,
// wäre schlicht falsch. Was dann gilt, sagt die Seite an anderer Stelle.
[$user] = gekuendigteInstanz(endetIn: now()->subDay(), status: 'ended');
Livewire::actingAs($user)->test(Dashboard::class)
->assertDontSee(__('dashboard.ending.title'));
});
it('nennt keine Restzeit, die schon abgelaufen ist', function () {
// Der Fall zwischen den Stühlen: das Laufzeitende ist vorbei, aber der
// stündliche Lauf hat die Instanz noch nicht auf `ended` gestellt. Eine
// Zeile "noch -3 Stunden" ist die Sorte Fehler, die niemand meldet und
// jeder sieht.
//
// Korrektur gegenüber dem Zettel: `not->toContain('-')` über das ganze
// Dokument trägt nicht — ein Datum und eine CSS-Klasse enthalten ebenso
// Bindestriche, und die Prüfung wäre aus dem falschen Grund rot oder
// grün. Sie zielt stattdessen auf die Restzeit selbst: der Streifen darf
// gar nicht erst erscheinen, wenn das Laufzeitende vorbei ist — unabhängig
// davon, ob der Status das schon nachvollzogen hat. Genau das ist die
// Prüfung, die eine Implementierung ertappt, die sich allein auf den
// Status verlässt und das Datum ignoriert.
[$user] = gekuendigteInstanz(endetIn: now()->subHours(3));
Livewire::actingAs($user)->test(Dashboard::class)
->assertDontSee(__('dashboard.ending.title'));
});
it('nennt die Restzeit in ganzen Tagen, solange mehr als ein Tag bleibt', function () {
// Nicht im Zettel geprüft, aber ausdrücklich gefordert: "noch 12 Tage"
// liest sich, "noch 11 Tage 7 Stunden" nicht — abgerundet auf ganze Tage.
[$user] = gekuendigteInstanz(endetIn: now()->addDays(3)->addHours(5));
Livewire::actingAs($user)->test(Dashboard::class)
->assertSee(trans_choice('dashboard.ending.remaining_days', 3));
});
it('zaehlt am letzten Tag in Stunden, nicht mehr in Tagen', function () {
[$user] = gekuendigteInstanz(endetIn: now()->addHours(5));
Livewire::actingAs($user)->test(Dashboard::class)
->assertSee(trans_choice('dashboard.ending.remaining_hours', 5));
});
it('traegt die Antwort zum Export und laesst sie aendern', function () {
[$user, $instance] = gekuendigteInstanz(endetIn: now()->addDays(12));
$instance->update(['export_wish' => false]);
Livewire::actingAs($user)->test(Dashboard::class)
->assertSee(__('dashboard.ending.export_no'))
->call('setExportWish', true);
expect($instance->fresh()->export_wish)->toBeTrue();
});
it('laesst die Antwort nach dem Laufzeitende nicht mehr aendern', function () {
// Aufgabe 4: danach ist die Instanz `ended`, die Adresse eingezogen, und
// ob ein Export vorbereitet wurde, ist entschieden. Ein Aufruf, der dort
// noch etwas verspricht, wäre die nächste Attrappe — geprüft direkt gegen
// die Methode, nicht nur gegen den (dann ohnehin nicht mehr sichtbaren)
// Schalter im Streifen: setExportWish() ist über /livewire/update auch am
// Formular vorbei erreichbar.
[$user, $instance] = gekuendigteInstanz(endetIn: now()->subDay(), status: 'ended');
$instance->update(['export_wish' => false]);
Livewire::actingAs($user)->test(Dashboard::class)
->call('setExportWish', true);
expect($instance->fresh()->export_wish)->toBeFalse();
});