mirror of https://github.com/pixelfed/pixelfed
Invalidate notification cache when deleting a status
parent
e0550c441c
commit
f1e4536d19
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
|
use App\Models\Notification;
|
||||||
|
use App\Models\Status;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
uses(LazilyRefreshDatabase::class);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| StatusDelete invalidates cached status notifications
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Notifications cache a fully-transformed snapshot (with an embedded status
|
||||||
|
| payload) under ITEM_KEY for 24h, and the web feed serves that verbatim. When
|
||||||
|
| a status is deleted, its status-typed notifications must be removed per-row so
|
||||||
|
| NotificationObserver::forceDeleted -> NotificationService::del invalidates the
|
||||||
|
| cached snapshot. A bulk forceDelete() fires no model events and would leave
|
||||||
|
| the web feed serving the deleted status as a ghost for up to 24h.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist a status-typed like notification and warm its ITEM_KEY snapshot, the
|
||||||
|
* way createNotification/setNotification does on the live path.
|
||||||
|
*/
|
||||||
|
function warmStatusNotification(int $profileId, int $actorId, int $statusId, string $itemType): Notification
|
||||||
|
{
|
||||||
|
$n = new Notification;
|
||||||
|
$n->profile_id = $profileId;
|
||||||
|
$n->actor_id = $actorId;
|
||||||
|
$n->action = 'like';
|
||||||
|
$n->item_id = $statusId;
|
||||||
|
$n->item_type = $itemType;
|
||||||
|
$n->save();
|
||||||
|
|
||||||
|
NotificationService::setNotification($n); // writes ITEM_KEY
|
||||||
|
NotificationService::set($n->profile_id, $n->id);
|
||||||
|
|
||||||
|
return $n;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('invalidates the cached notification snapshot when its status is deleted', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->refresh();
|
||||||
|
$actor = User::factory()->create();
|
||||||
|
$actor->refresh();
|
||||||
|
|
||||||
|
$status = Status::factory()->create(['profile_id' => $user->profile_id, 'scope' => 'public']);
|
||||||
|
$n = warmStatusNotification($user->profile_id, $actor->profile_id, $status->id, Status::class);
|
||||||
|
|
||||||
|
// Snapshot is warm before deletion.
|
||||||
|
expect(Cache::get(NotificationService::ITEM_KEY.$n->id))->not->toBeNull();
|
||||||
|
|
||||||
|
(new StatusDelete($status))->unlinkRemoveMedia($status);
|
||||||
|
|
||||||
|
// Row is force-deleted and the cached snapshot was invalidated by the
|
||||||
|
// observer (no manual NotificationService::del in the test).
|
||||||
|
expect(Notification::withTrashed()->whereId($n->id)->exists())->toBeFalse();
|
||||||
|
expect(Cache::get(NotificationService::ITEM_KEY.$n->id))->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalidates and removes legacy App\\Status notifications too', function () {
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$user->refresh();
|
||||||
|
$actor = User::factory()->create();
|
||||||
|
$actor->refresh();
|
||||||
|
|
||||||
|
$status = Status::factory()->create(['profile_id' => $user->profile_id, 'scope' => 'public']);
|
||||||
|
// Legacy morph alias predating the App\ -> App\Models\ migration.
|
||||||
|
$n = warmStatusNotification($user->profile_id, $actor->profile_id, $status->id, 'App\Status');
|
||||||
|
|
||||||
|
expect(Cache::get(NotificationService::ITEM_KEY.$n->id))->not->toBeNull();
|
||||||
|
|
||||||
|
(new StatusDelete($status))->unlinkRemoveMedia($status);
|
||||||
|
|
||||||
|
// The alias-aware cleanup matches legacy rows: no DB orphan, cache cleared.
|
||||||
|
expect(Notification::withTrashed()->whereId($n->id)->exists())->toBeFalse();
|
||||||
|
expect(Cache::get(NotificationService::ITEM_KEY.$n->id))->toBeNull();
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue