Files
einundzwanzig-app/tests/Feature/Meetups/MeetupRestorePointTest.php
T
HolgerHatGarKeineNode f5cf85b438 Add restore_point functionality to Meetups
- 💾 Introduced `restore_point` JSON column in `meetups` table for saving and restoring master data.
- 🛠️ Added methods `captureRestorePoint` and `restoreFromRestorePoint` to `Meetup` model for managing restore points.
- 🔒 Implemented authorization for updating meetups via `updateViaPortal` policy to include pivot members.
- 🔗 Created Artisan commands `meetups:snapshot` and `meetups:restore` for managing restore points from CLI.
- 🚦 Added rate limiter to restrict excessive update attempts in Livewire meetup editing.
-  Developed exhaustive feature tests for snapshot and restore actions, portal editing rules, and rate limiting.
2026-06-10 10:56:38 +02:00

85 lines
2.8 KiB
PHP

<?php
use App\Models\City;
use App\Models\Country;
use App\Models\Meetup;
beforeEach(function () {
$country = Country::factory()->create(['code' => 'de']);
$this->city = City::factory()->create(['country_id' => $country->id]);
});
it('snapshots the current master data of all meetups', function () {
$meetup = Meetup::factory()->create([
'city_id' => $this->city->id,
'name' => 'Einundzwanzig Cham',
]);
$this->artisan('meetups:snapshot')
->expectsOutputToContain('Restore points saved for 1 meetups.')
->assertSuccessful();
$restorePoint = $meetup->refresh()->restore_point;
expect($restorePoint['attributes']['name'])->toBe('Einundzwanzig Cham')
->and($restorePoint['captured_at'])->not->toBeNull();
});
it('snapshots a single meetup by id', function () {
$meetup = Meetup::factory()->create(['city_id' => $this->city->id]);
$other = Meetup::factory()->create(['city_id' => $this->city->id]);
$this->artisan('meetups:snapshot', ['meetup' => $meetup->id])
->assertSuccessful();
expect($meetup->refresh()->restore_point)->not->toBeNull()
->and($other->refresh()->restore_point)->toBeNull();
});
it('restores master data from the restore point', function () {
$meetup = Meetup::factory()->create([
'city_id' => $this->city->id,
'name' => 'Einundzwanzig Cham',
'community' => 'einundzwanzig',
'telegram_link' => 'https://t.me/original',
]);
$this->artisan('meetups:snapshot', ['meetup' => $meetup->id])->assertSuccessful();
$meetup->update([
'name' => 'Vandalized Name',
'community' => 'other',
'telegram_link' => 'https://t.me/spam',
]);
$this->artisan('meetups:restore', ['meetup' => $meetup->id])
->expectsOutputToContain('restored from restore point')
->assertSuccessful();
$meetup->refresh();
expect($meetup->name)->toBe('Einundzwanzig Cham')
->and($meetup->community)->toBe('einundzwanzig')
->and($meetup->telegram_link)->toBe('https://t.me/original');
});
it('fails to restore when no restore point exists', function () {
$meetup = Meetup::factory()->create(['city_id' => $this->city->id]);
$this->artisan('meetups:restore', ['meetup' => $meetup->id])
->expectsOutputToContain('has no restore point')
->assertFailed();
});
it('fails to restore an unknown meetup', function () {
$this->artisan('meetups:restore', ['meetup' => 999999])
->expectsOutputToContain('not found')
->assertFailed();
});
it('does not allow mass-assigning the restore point via update', function () {
$meetup = Meetup::factory()->create(['city_id' => $this->city->id]);
$meetup->update(['restore_point' => ['attributes' => ['name' => 'Hacked']]]);
expect($meetup->refresh()->restore_point)->toBeNull();
});