Track when expiration warnings are sent on personal access tokens so repeated job runs or cache flushes do not send duplicate notifications.
114 lines
3.9 KiB
PHP
114 lines
3.9 KiB
PHP
<?php
|
|
|
|
use App\Jobs\ApiTokenExpirationWarningJob;
|
|
use App\Models\PersonalAccessToken;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use App\Notifications\ApiTokenExpiringNotification;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Notification;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
$this->team = Team::factory()->create();
|
|
$this->user = User::factory()->create();
|
|
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
|
|
$this->team->emailNotificationSettings()->update(['use_instance_email_settings' => true]);
|
|
$this->team->discordNotificationSettings()->update([
|
|
'discord_enabled' => true,
|
|
'discord_webhook_url' => 'https://discord.com/api/webhooks/fake/fake',
|
|
]);
|
|
|
|
session(['currentTeam' => $this->team]);
|
|
$this->actingAs($this->user);
|
|
|
|
Cache::flush();
|
|
Notification::fake();
|
|
});
|
|
|
|
function createTokenExpiring(User $user, Team $team, ?Carbon $expiresAt, ?Carbon $warningSentAt = null): PersonalAccessToken
|
|
{
|
|
$plain = $user->createToken('t-'.uniqid(), ['read'], $expiresAt);
|
|
$token = $plain->accessToken;
|
|
$token->team_id = $team->id;
|
|
$token->api_token_expiration_warning_sent_at = $warningSentAt;
|
|
$token->save();
|
|
|
|
return $token->fresh();
|
|
}
|
|
|
|
describe('ApiTokenExpirationWarningJob', function () {
|
|
test('notifies team when token expires within 24h', function () {
|
|
$token = createTokenExpiring($this->user, $this->team, now()->addHours(23));
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Notification::assertSentTo($this->team, ApiTokenExpiringNotification::class);
|
|
expect($token->fresh()->api_token_expiration_warning_sent_at)->not->toBeNull();
|
|
});
|
|
|
|
test('database marker prevents duplicate warnings on repeat runs', function () {
|
|
createTokenExpiring($this->user, $this->team, now()->addHours(12));
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Notification::assertSentToTimes($this->team, ApiTokenExpiringNotification::class, 1);
|
|
});
|
|
|
|
test('database marker prevents duplicate warnings after cache is flushed', function () {
|
|
createTokenExpiring($this->user, $this->team, now()->addHours(12));
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Cache::flush();
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Notification::assertSentToTimes($this->team, ApiTokenExpiringNotification::class, 1);
|
|
});
|
|
|
|
test('skips tokens that already have an expiration warning marker', function () {
|
|
createTokenExpiring($this->user, $this->team, now()->addHours(12), now()->subHour());
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Notification::assertNothingSent();
|
|
});
|
|
|
|
test('notifies once for each unmarked expiring token', function () {
|
|
createTokenExpiring($this->user, $this->team, now()->addHours(12));
|
|
createTokenExpiring($this->user, $this->team, now()->addHours(23));
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Notification::assertSentToTimes($this->team, ApiTokenExpiringNotification::class, 2);
|
|
});
|
|
|
|
test('skips tokens expiring more than 24h out', function () {
|
|
createTokenExpiring($this->user, $this->team, now()->addDays(3));
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Notification::assertNothingSent();
|
|
});
|
|
|
|
test('skips already-expired tokens', function () {
|
|
createTokenExpiring($this->user, $this->team, now()->subHour());
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Notification::assertNothingSent();
|
|
});
|
|
|
|
test('skips tokens with null expires_at', function () {
|
|
createTokenExpiring($this->user, $this->team, null);
|
|
|
|
(new ApiTokenExpirationWarningJob)->handle();
|
|
|
|
Notification::assertNothingSent();
|
|
});
|
|
});
|