- Star (is_starred) and colored tags for services, customers, orders, invoices:
Markable trait, tags/taggables tables, PATCH administrator/marks/{type}/{key},
tag CRUD, list filters starred / tag_ids[]
- GET administrator/services accepts product_type (falls back to product type
for legacy rows without services.type)
- Mail campaigns: bulk cancel / retry failed / delete; cancelled campaigns are
skipped by SendMailCampaignRecipient
- POST administrator/settings/test-smtp sends a test email through the saved
mail transport
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
3.2 KiB
PHP
89 lines
3.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Api\Admin;
|
|
|
|
use App\Jobs\Mail\SendMailCampaignRecipient;
|
|
use App\Models\Mail;
|
|
use App\Models\MailCampaign;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Mail as MailFacade;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Spatie\Permission\Models\Role;
|
|
use Tests\TestCase;
|
|
|
|
class MailCampaignBulkActionTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $admin;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Role::firstOrCreate(['name' => 'Full Administrator', 'guard_name' => 'admin']);
|
|
$this->admin = User::factory()->create();
|
|
$this->admin->assignRole('Full Administrator');
|
|
}
|
|
|
|
private function campaign(string $status, int $sent = 0, int $failed = 0): MailCampaign
|
|
{
|
|
return MailCampaign::create([
|
|
'name' => 'C', 'subject' => 'S', 'message' => '<p>x</p>', 'recipient_type' => 'selected',
|
|
'total_recipients' => 2, 'sent_count' => $sent, 'failed_count' => $failed, 'status' => $status,
|
|
]);
|
|
}
|
|
|
|
public function test_cancel_stops_pending_jobs_and_keeps_finished_campaigns(): void
|
|
{
|
|
MailFacade::fake();
|
|
$sending = $this->campaign('sending');
|
|
$done = $this->campaign('completed', 2);
|
|
|
|
$this->actingAs($this->admin, 'admin')
|
|
->postJson('/api/v1/administrator/mail-campaigns/bulk-action', ['action' => 'cancel', 'uuids' => [$sending->uuid, $done->uuid]])
|
|
->assertOk();
|
|
|
|
$this->assertSame('cancelled', $sending->fresh()->status);
|
|
$this->assertSame('completed', $done->fresh()->status);
|
|
|
|
(new SendMailCampaignRecipient($sending->id, null, 'a@example.com'))->handle(app(\App\Services\Email\MailCampaignRenderer::class));
|
|
MailFacade::assertNothingSent();
|
|
$this->assertSame(0, $sending->fresh()->sent_count);
|
|
}
|
|
|
|
public function test_retry_requeues_only_failed_recipients(): void
|
|
{
|
|
Queue::fake();
|
|
$campaign = $this->campaign('completed', 1, 1);
|
|
foreach (['ok@example.com' => 'sent', 'bad@example.com' => 'failed'] as $to => $status) {
|
|
Mail::create(['campaign_id' => $campaign->id, 'template_id' => 0, 'subject' => 'S', 'to' => $to, 'message' => 'x', 'status' => $status]);
|
|
}
|
|
|
|
$this->actingAs($this->admin, 'admin')
|
|
->postJson('/api/v1/administrator/mail-campaigns/bulk-action', ['action' => 'retry-failed', 'uuids' => [$campaign->uuid]])
|
|
->assertOk();
|
|
|
|
$campaign->refresh();
|
|
$this->assertSame('sending', $campaign->status);
|
|
$this->assertSame(0, $campaign->failed_count);
|
|
$this->assertDatabaseMissing('mail', ['to' => 'bad@example.com']);
|
|
Queue::assertPushed(SendMailCampaignRecipient::class, 1);
|
|
}
|
|
|
|
public function test_delete_skips_campaigns_still_sending(): void
|
|
{
|
|
$sending = $this->campaign('queued');
|
|
$done = $this->campaign('failed', 0, 2);
|
|
|
|
$this->actingAs($this->admin, 'admin')
|
|
->postJson('/api/v1/administrator/mail-campaigns/bulk-delete', ['uuids' => [$sending->uuid, $done->uuid]])
|
|
->assertOk();
|
|
|
|
$this->assertNotNull($sending->fresh());
|
|
$this->assertNull($done->fresh());
|
|
}
|
|
}
|