122 lines
4.7 KiB
PHP
122 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Mailing;
|
|
use App\Models\MailingBounce;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* Also: blocks against our sending server (Microsoft's S3150 block list,
|
|
* "550 5.7.1") suppressed hotmail addresses on 2026-10-10, and a backlog of
|
|
* August bounces was recorded as if it bounced that day.
|
|
*
|
|
* A second bounce report for the same address and mailing (unique index)
|
|
* updates the first. Inserting it threw, and from 2026-09-27 at the latest
|
|
* that stopped every hourly bounce run at the same message.
|
|
*/
|
|
class MailingBounceRecordTest extends TestCase
|
|
{
|
|
use DatabaseTransactions;
|
|
|
|
private function mailing(): Mailing
|
|
{
|
|
return Mailing::create([
|
|
'title' => 'Bounce test',
|
|
'type' => 'general_newsletter',
|
|
'subject' => ['en' => 'Bounce test'],
|
|
'status' => 'sent',
|
|
]);
|
|
}
|
|
|
|
public function test_a_second_report_for_the_same_mailing_updates_the_first(): void
|
|
{
|
|
$mailing = $this->mailing();
|
|
|
|
MailingBounce::recordBounce('Bounce.Test@Example.test', 'soft', 'mailbox full', $mailing->id);
|
|
$bounce = MailingBounce::recordBounce('bounce.test@example.test ', 'hard', 'user unknown', $mailing->id);
|
|
|
|
$rows = MailingBounce::where('email', 'bounce.test@example.test')->get();
|
|
|
|
$this->assertCount(1, $rows);
|
|
$this->assertSame($bounce->id, $rows->first()->id);
|
|
$this->assertSame('hard', $rows->first()->bounce_type);
|
|
$this->assertSame('user unknown', $rows->first()->bounce_reason);
|
|
}
|
|
|
|
public function test_bounces_without_a_mailing_still_add_up(): void
|
|
{
|
|
MailingBounce::recordBounce('bounce.test@example.test', 'hard', 'user unknown');
|
|
MailingBounce::recordBounce('bounce.test@example.test', 'hard', 'user unknown');
|
|
|
|
$this->assertSame(2, MailingBounce::where('email', 'bounce.test@example.test')->count());
|
|
}
|
|
|
|
public function test_other_mailings_get_their_own_row(): void
|
|
{
|
|
$first = $this->mailing();
|
|
$second = $this->mailing();
|
|
|
|
MailingBounce::recordBounce('bounce.test@example.test', 'hard', 'user unknown', $first->id);
|
|
MailingBounce::recordBounce('bounce.test@example.test', 'hard', 'user unknown', $second->id);
|
|
|
|
$this->assertSame(2, MailingBounce::where('email', 'bounce.test@example.test')->count());
|
|
}
|
|
|
|
private function thresholds(int $windowDays = 30): void
|
|
{
|
|
$key = config('app.timebank_config') . '.mailing.bounce_thresholds';
|
|
config([
|
|
"{$key}.suppression_threshold" => 1,
|
|
"{$key}.verification_reset_threshold" => 1,
|
|
"{$key}.counting_window_days" => $windowDays,
|
|
]);
|
|
}
|
|
|
|
public function test_a_block_against_our_server_does_not_suppress_the_address(): void
|
|
{
|
|
$this->thresholds();
|
|
$user = User::factory()->create(['email' => 'bounce.test@example.test', 'email_verified_at' => now()]);
|
|
|
|
MailingBounce::recordBounce('bounce.test@example.test', 'hard',
|
|
"smtp; 550 5.7.1 Unfortunately, messages from [195.190.28.89] weren't sent. Please contact your Internet service provider since part of their network is on our block list (S3150).");
|
|
|
|
$this->assertFalse(MailingBounce::isSuppressed('bounce.test@example.test'));
|
|
$this->assertNotNull($user->fresh()->email_verified_at);
|
|
}
|
|
|
|
public function test_an_unknown_user_still_suppresses_the_address(): void
|
|
{
|
|
$this->thresholds();
|
|
$user = User::factory()->create(['email' => 'bounce.test@example.test', 'email_verified_at' => now()]);
|
|
|
|
MailingBounce::recordBounce('bounce.test@example.test', 'hard', 'smtp; 550 5.1.1 user unknown');
|
|
|
|
$this->assertTrue(MailingBounce::isSuppressed('bounce.test@example.test'));
|
|
$this->assertNull($user->fresh()->email_verified_at);
|
|
}
|
|
|
|
public function test_a_missing_address_counts_even_with_a_5_7_code(): void
|
|
{
|
|
$this->thresholds();
|
|
|
|
MailingBounce::recordBounce('bounce.test@example.test', 'hard',
|
|
'smtp; 550 5.7.1 <bounce.test@example.test>: Recipient address rejected: Recipient not found');
|
|
|
|
$this->assertTrue(MailingBounce::isSuppressed('bounce.test@example.test'));
|
|
}
|
|
|
|
public function test_a_bounce_is_dated_when_it_happened_and_old_ones_do_not_count(): void
|
|
{
|
|
$this->thresholds(30);
|
|
$when = now()->subDays(70)->startOfSecond();
|
|
|
|
$bounce = MailingBounce::recordBounce('bounce.test@example.test', 'hard', 'smtp; 550 5.1.1 user unknown', null, $when);
|
|
|
|
$this->assertTrue($bounce->fresh()->bounced_at->equalTo($when));
|
|
$this->assertFalse(MailingBounce::isSuppressed('bounce.test@example.test'));
|
|
}
|
|
}
|