api/tests/Feature/ExpoPushChannelTest.php

158 lines
4.8 KiB
PHP

<?php
use App\Broadcasting\ExpoPushChannel;
use App\Models\DeviceToken;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\Request;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Http;
uses(RefreshDatabase::class);
it('sends expo push notifications to every registered device token', function () {
Http::fake([
'https://exp.host/*' => Http::response([
'data' => [
['status' => 'ok', 'id' => 'ticket-one'],
['status' => 'ok', 'id' => 'ticket-two'],
],
]),
]);
$user = User::factory()->create();
$user->deviceTokens()->create([
'expo_push_token' => 'ExponentPushToken[first]',
'platform' => 'ios',
]);
$user->deviceTokens()->create([
'expo_push_token' => 'ExponentPushToken[second]',
'platform' => 'android',
]);
(new ExpoPushChannel)->send($user, expoPushTestNotification());
Http::assertSent(fn (Request $request) => $request->url() === 'https://exp.host/--/api/v2/push/send'
&& $request->data() === [
[
'to' => 'ExponentPushToken[first]',
'title' => 'Nouveau repas',
'body' => 'Un ami vient de publier son repas.',
'data' => ['meal_post_id' => 123],
],
[
'to' => 'ExponentPushToken[second]',
'title' => 'Nouveau repas',
'body' => 'Un ami vient de publier son repas.',
'data' => ['meal_post_id' => 123],
],
]);
});
it('can be used from a notification via the expo channel alias', function () {
Http::fake([
'https://exp.host/*' => Http::response([
'data' => [
['status' => 'ok', 'id' => 'ticket-one'],
],
]),
]);
$user = User::factory()->create();
$user->deviceTokens()->create([
'expo_push_token' => 'ExponentPushToken[first]',
'platform' => 'ios',
]);
$user->notify(new class extends Notification
{
public function via(object $notifiable): array
{
return ['expo'];
}
public function toExpoPush(object $notifiable): array
{
return [
'title' => 'Nouveau repas',
'body' => 'Un ami vient de publier son repas.',
'data' => ['meal_post_id' => 123],
];
}
});
Http::assertSent(fn (Request $request) => $request->data()[0]['to'] === 'ExponentPushToken[first]');
});
it('removes device tokens rejected as not registered by expo', function () {
Http::fake([
'https://exp.host/*' => Http::response([
'data' => [
[
'status' => 'error',
'message' => '"ExponentPushToken[invalid]" is not a registered push notification recipient',
'details' => ['error' => 'DeviceNotRegistered'],
],
['status' => 'ok', 'id' => 'ticket-two'],
],
]),
]);
$user = User::factory()->create();
$invalidToken = $user->deviceTokens()->create([
'expo_push_token' => 'ExponentPushToken[invalid]',
'platform' => 'ios',
]);
$validToken = $user->deviceTokens()->create([
'expo_push_token' => 'ExponentPushToken[valid]',
'platform' => 'android',
]);
(new ExpoPushChannel)->send($user, expoPushTestNotification());
expect(DeviceToken::query()->whereKey($invalidToken->getKey())->exists())->toBeFalse()
->and(DeviceToken::query()->whereKey($validToken->getKey())->exists())->toBeTrue();
});
it('chunks expo push notifications into requests of one hundred messages', function () {
Http::fake([
'https://exp.host/*' => Http::response(['data' => []]),
]);
$user = User::factory()->create();
foreach (range(1, 101) as $number) {
$user->deviceTokens()->create([
'expo_push_token' => sprintf('ExponentPushToken[%03d]', $number),
'platform' => 'ios',
]);
}
(new ExpoPushChannel)->send($user, expoPushTestNotification());
Http::assertSentCount(2);
$requests = Http::recorded()
->map(fn (array $pair) => $pair[0]->data())
->values();
expect($requests[0])->toHaveCount(100)
->and($requests[1])->toHaveCount(1)
->and($requests[1][0]['to'])->toBe('ExponentPushToken[101]');
});
function expoPushTestNotification(): Notification
{
return new class extends Notification
{
public function toExpoPush(object $notifiable): array
{
return [
'title' => 'Nouveau repas',
'body' => 'Un ami vient de publier son repas.',
'data' => ['meal_post_id' => 123],
];
}
};
}