feat: add persistent mobile notifications
CI / 🧪 Tests Laravel (push) Successful in 2m7s
CI / 🐳 Build & Push Image (push) Successful in 42s

This commit is contained in:
2026-08-11 16:46:05 +02:00
parent bd1b4bba20
commit df4f523089
24 changed files with 822 additions and 75 deletions
@@ -0,0 +1,95 @@
<?php
use App\Actions\ReviewIdentityVerification;
use App\Enums\IdentityVerificationStatus;
use App\Enums\UserRole;
use App\Listeners\HandleStripeWebhook;
use App\Models\IdentityVerificationRequest;
use App\Models\User;
use App\Notifications\CertificationReviewedNotification;
use App\Notifications\EngagementReminderNotification;
use App\Notifications\NewFollowerNotification;
use App\Notifications\PaymentFailedNotification;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Storage;
use Laravel\Sanctum\Sanctum;
uses(RefreshDatabase::class);
it('respects social and engagement push preferences', function () {
$follower = User::factory()->create();
$user = User::factory()->create([
'social_notifications_enabled' => false,
'engagement_reminders_enabled' => false,
]);
expect((new NewFollowerNotification($follower))->via($user))
->toBe(['database'])
->and((new EngagementReminderNotification('publish_meal'))->via($user))
->toBe([])
->and((new PaymentFailedNotification)->via($user))
->toBe(['database', 'expo']);
$user->notifyNow(new NewFollowerNotification($follower));
$this->assertDatabaseHas('notifications', [
'type' => 'new_follower',
'notifiable_id' => $user->getKey(),
]);
});
it('notifies a user only once when a new follower is attached', function () {
Notification::fake();
$follower = User::factory()->create();
$followed = User::factory()->create();
Sanctum::actingAs($follower);
$this->postJson("/api/users/{$followed->id}/follow")->assertOk();
$this->postJson("/api/users/{$followed->id}/follow")->assertOk();
Notification::assertSentToTimes($followed, NewFollowerNotification::class, 1);
});
it('notifies the user when certification is reviewed', function () {
Notification::fake();
Storage::fake('identity_documents');
$user = User::factory()->create(['certification_purchased_at' => now()]);
$admin = User::factory()->create(['role' => UserRole::ADMIN]);
$verificationRequest = IdentityVerificationRequest::factory()->for($user)->create();
Storage::disk('identity_documents')->put($verificationRequest->document_path, 'identity');
app(ReviewIdentityVerification::class)->execute(
$verificationRequest,
IdentityVerificationStatus::APPROVED,
$admin,
);
Notification::assertSentTo($user, CertificationReviewedNotification::class);
});
it('notifies a user only once when a failed invoice webhook is replayed', function () {
Notification::fake();
$user = User::factory()->create(['stripe_id' => 'cus_failed_payment']);
$event = (object) [
'payload' => [
'id' => 'evt_failed_payment',
'type' => 'invoice.payment_failed',
'data' => [
'object' => [
'id' => 'in_failed_payment',
'customer' => 'cus_failed_payment',
'amount_due' => 999,
'currency' => 'eur',
'lines' => ['data' => []],
],
],
],
];
$listener = app(HandleStripeWebhook::class);
$listener->handle($event);
$listener->handle($event);
Notification::assertSentToTimes($user, PaymentFailedNotification::class, 1);
});