Publication initiale de RelaisSMS : documentation et sources nettoyees
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Contracts\Sms\SmsProviderInterface;
|
||||
use App\Models\AppSetting;
|
||||
use App\Models\Contact;
|
||||
use App\Models\SmsCampaign;
|
||||
use App\Models\User;
|
||||
use App\Services\Sms\OvhSmsProvider;
|
||||
use App\Services\Sms\SmsCampaignSender;
|
||||
use App\Services\Sms\SmsSendResult;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SmsCampaignSendTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Carbon::setTestNow();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_other_provider_receives_links_unchanged(): void
|
||||
{
|
||||
config(['relais_sms.default_settings.active_sms_provider' => 'other']);
|
||||
$message = 'Reunion https://zoom.us/j/123';
|
||||
$provider = $this->mock(SmsProviderInterface::class);
|
||||
$provider->shouldReceive('send')->once()->withArgs(fn (SmsCampaign $campaign, array $recipients): bool => $campaign->message === $message)
|
||||
->andReturn(new SmsSendResult(serviceName: 'other', providerJobId: '1', requestPayload: ['message' => $message], responsePayload: []));
|
||||
$campaign = $this->app->make(SmsCampaignSender::class)->send(User::factory()->create(), $message, []);
|
||||
$this->assertSame($message, $campaign->message);
|
||||
}
|
||||
|
||||
public function test_history_keeps_the_message_actually_transmitted_by_ovh(): void
|
||||
{
|
||||
config([
|
||||
'relais_sms.ovh.sms_short_code' => true,
|
||||
'relais_sms.ovh.sms_sender' => '',
|
||||
'relais_sms.ovh.sms_service_name' => 'sms-test',
|
||||
'relais_sms.ovh.application_key' => 'test',
|
||||
'relais_sms.ovh.application_secret' => 'test',
|
||||
'relais_sms.ovh.consumer_key' => 'test',
|
||||
]);
|
||||
$client = new Client(['handler' => HandlerStack::create(new MockHandler([
|
||||
new Response(200, [], '123456'),
|
||||
new Response(200, [], '{"ids":[]}'),
|
||||
]))]);
|
||||
$this->app->instance(SmsProviderInterface::class, new OvhSmsProvider($client));
|
||||
$campaign = $this->app->make(SmsCampaignSender::class)->send(User::factory()->create(), 'Reunion https://zoom.us/j/123 demain', []);
|
||||
$this->assertSame('Reunion demain', $campaign->message);
|
||||
$this->assertSame('Reunion demain', $campaign->request_payload['message']);
|
||||
}
|
||||
|
||||
public function test_authenticated_user_can_send_sms_campaign_and_history_is_recorded(): void
|
||||
{
|
||||
$this->app->bind(SmsProviderInterface::class, fn (): SmsProviderInterface => new class implements SmsProviderInterface
|
||||
{
|
||||
public function send(SmsCampaign $campaign, array $recipients): SmsSendResult
|
||||
{
|
||||
return new SmsSendResult(
|
||||
serviceName: 'sms-test',
|
||||
providerJobId: 'job-123',
|
||||
requestPayload: ['receivers' => array_column($recipients, 'number')],
|
||||
responsePayload: ['ids' => ['message-123']],
|
||||
providerMessageIds: ['message-123'],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$user = User::factory()->create();
|
||||
$contact = $this->createSmsContact();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->post(route('sms-campaigns.store'), [
|
||||
'contact_ids' => [$contact->id],
|
||||
'message' => 'Message de test',
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
|
||||
$this->assertDatabaseHas('sms_campaigns', [
|
||||
'message' => 'Message de test',
|
||||
'status' => 'sent',
|
||||
'recipients_count' => 1,
|
||||
'sent_count' => 1,
|
||||
'provider_service_name' => 'sms-test',
|
||||
'provider_job_id' => 'job-123',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('sms_deliveries', [
|
||||
'contact_id' => $contact->id,
|
||||
'number' => '+33611111111',
|
||||
'status' => 'sent',
|
||||
'provider_message_id' => 'message-123',
|
||||
]);
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('sms-campaigns.index'))
|
||||
->assertOk()
|
||||
->assertSee('Message de test');
|
||||
}
|
||||
|
||||
public function test_sms_campaign_message_uses_configured_header_and_footer_when_selected(): void
|
||||
{
|
||||
AppSetting::putString('sms_message_header', 'AFPMA');
|
||||
AppSetting::putString('sms_message_footer', 'Envoyer STOP SMS pour ne plus etre contacte.');
|
||||
|
||||
$this->app->bind(SmsProviderInterface::class, fn (): SmsProviderInterface => new class implements SmsProviderInterface
|
||||
{
|
||||
public function send(SmsCampaign $campaign, array $recipients): SmsSendResult
|
||||
{
|
||||
return new SmsSendResult(
|
||||
serviceName: 'sms-test',
|
||||
providerJobId: 'job-headers',
|
||||
requestPayload: ['message' => $campaign->message],
|
||||
responsePayload: ['ids' => ['message-headers']],
|
||||
providerMessageIds: ['message-headers'],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$user = User::factory()->create();
|
||||
$contact = $this->createSmsContact();
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('sms-campaigns.store'), [
|
||||
'contact_ids' => [$contact->id],
|
||||
'message' => 'Message de test',
|
||||
'include_sms_header' => '1',
|
||||
'include_sms_footer' => '1',
|
||||
])
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertDatabaseHas('sms_campaigns', [
|
||||
'message' => "AFPMA\nMessage de test\nEnvoyer STOP SMS pour ne plus etre contacte.",
|
||||
'provider_job_id' => 'job-headers',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_sms_campaign_message_can_remove_configured_footer_in_composition(): void
|
||||
{
|
||||
AppSetting::putString('sms_message_header', 'AFPMA');
|
||||
AppSetting::putString('sms_message_footer', 'Envoyer STOP SMS pour ne plus etre contacte.');
|
||||
|
||||
$this->app->bind(SmsProviderInterface::class, fn (): SmsProviderInterface => new class implements SmsProviderInterface
|
||||
{
|
||||
public function send(SmsCampaign $campaign, array $recipients): SmsSendResult
|
||||
{
|
||||
return new SmsSendResult(
|
||||
serviceName: 'sms-test',
|
||||
providerJobId: 'job-no-footer',
|
||||
requestPayload: ['message' => $campaign->message],
|
||||
responsePayload: ['ids' => ['message-no-footer']],
|
||||
providerMessageIds: ['message-no-footer'],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$user = User::factory()->create();
|
||||
$contact = $this->createSmsContact();
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('sms-campaigns.store'), [
|
||||
'contact_ids' => [$contact->id],
|
||||
'message' => 'Message de test',
|
||||
'include_sms_header' => '1',
|
||||
'include_sms_footer' => '0',
|
||||
])
|
||||
->assertRedirect();
|
||||
|
||||
$this->assertDatabaseHas('sms_campaigns', [
|
||||
'message' => "AFPMA\nMessage de test",
|
||||
'provider_job_id' => 'job-no-footer',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_authenticated_user_can_schedule_sms_campaign_without_sending_immediately(): void
|
||||
{
|
||||
Carbon::setTestNow(Carbon::parse('2026-06-12 10:00:00', 'UTC'));
|
||||
|
||||
$provider = new class implements SmsProviderInterface
|
||||
{
|
||||
public int $calls = 0;
|
||||
|
||||
public function send(SmsCampaign $campaign, array $recipients): SmsSendResult
|
||||
{
|
||||
$this->calls++;
|
||||
|
||||
return new SmsSendResult(
|
||||
serviceName: 'sms-test',
|
||||
providerJobId: 'job-123',
|
||||
requestPayload: ['receivers' => array_column($recipients, 'number')],
|
||||
responsePayload: ['ids' => ['message-123']],
|
||||
providerMessageIds: ['message-123'],
|
||||
);
|
||||
}
|
||||
};
|
||||
$this->app->instance(SmsProviderInterface::class, $provider);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$contact = $this->createSmsContact();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->post(route('sms-campaigns.store'), [
|
||||
'contact_ids' => [$contact->id],
|
||||
'message' => 'Message programme',
|
||||
'send_mode' => 'scheduled',
|
||||
'scheduled_at' => '2026-06-12T12:05',
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
|
||||
$this->assertSame(0, $provider->calls);
|
||||
$this->assertDatabaseHas('sms_campaigns', [
|
||||
'message' => 'Message programme',
|
||||
'status' => 'scheduled',
|
||||
'recipients_count' => 1,
|
||||
'sent_count' => 0,
|
||||
]);
|
||||
$this->assertDatabaseHas('sms_deliveries', [
|
||||
'contact_id' => $contact->id,
|
||||
'number' => '+33611111111',
|
||||
'status' => 'queued',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_scheduled_campaign_can_be_canceled_before_processing(): void
|
||||
{
|
||||
Carbon::setTestNow(Carbon::parse('2026-06-12 10:00:00', 'UTC'));
|
||||
|
||||
$provider = new class implements SmsProviderInterface
|
||||
{
|
||||
public int $calls = 0;
|
||||
|
||||
public function send(SmsCampaign $campaign, array $recipients): SmsSendResult
|
||||
{
|
||||
$this->calls++;
|
||||
|
||||
return new SmsSendResult('sms-test', 'job-123', [], []);
|
||||
}
|
||||
};
|
||||
$this->app->instance(SmsProviderInterface::class, $provider);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$contact = $this->createSmsContact();
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('sms-campaigns.store'), [
|
||||
'contact_ids' => [$contact->id],
|
||||
'message' => 'Message annulable',
|
||||
'send_mode' => 'scheduled',
|
||||
'scheduled_at' => '2026-06-12T12:05',
|
||||
]);
|
||||
|
||||
$campaign = SmsCampaign::query()->firstOrFail();
|
||||
|
||||
$this->actingAs($user)
|
||||
->patch(route('sms-campaigns.cancel', $campaign))
|
||||
->assertRedirect(route('sms-campaigns.show', $campaign));
|
||||
|
||||
$this->assertSame(0, $provider->calls);
|
||||
$this->assertDatabaseHas('sms_campaigns', [
|
||||
'id' => $campaign->id,
|
||||
'status' => 'canceled',
|
||||
]);
|
||||
$this->assertDatabaseHas('sms_deliveries', [
|
||||
'sms_campaign_id' => $campaign->id,
|
||||
'status' => 'canceled',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_due_scheduled_campaign_is_sent_by_scheduler_command(): void
|
||||
{
|
||||
Carbon::setTestNow(Carbon::parse('2026-06-12 10:00:00', 'UTC'));
|
||||
Log::spy();
|
||||
|
||||
$provider = new class implements SmsProviderInterface
|
||||
{
|
||||
public int $calls = 0;
|
||||
|
||||
public function send(SmsCampaign $campaign, array $recipients): SmsSendResult
|
||||
{
|
||||
$this->calls++;
|
||||
|
||||
return new SmsSendResult(
|
||||
serviceName: 'sms-test',
|
||||
providerJobId: 'job-456',
|
||||
requestPayload: ['receivers' => array_column($recipients, 'number')],
|
||||
responsePayload: ['ids' => ['message-456']],
|
||||
providerMessageIds: ['message-456'],
|
||||
);
|
||||
}
|
||||
};
|
||||
$this->app->instance(SmsProviderInterface::class, $provider);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$contact = $this->createSmsContact();
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('sms-campaigns.store'), [
|
||||
'contact_ids' => [$contact->id],
|
||||
'message' => 'Message automatique',
|
||||
'send_mode' => 'scheduled',
|
||||
'scheduled_at' => '2026-06-12T12:05',
|
||||
]);
|
||||
|
||||
Carbon::setTestNow(Carbon::parse('2026-06-12 10:05:00', 'UTC'));
|
||||
|
||||
$this->artisan('sms-campaigns:send-due')
|
||||
->expectsOutput('1 campagne(s) SMS programmee(s) prise(s) en charge.')
|
||||
->assertExitCode(0);
|
||||
|
||||
$this->assertSame(1, $provider->calls);
|
||||
$this->assertDatabaseHas('sms_campaigns', [
|
||||
'message' => 'Message automatique',
|
||||
'status' => 'sent',
|
||||
'sent_count' => 1,
|
||||
'provider_job_id' => 'job-456',
|
||||
]);
|
||||
$this->assertDatabaseHas('sms_deliveries', [
|
||||
'number' => '+33611111111',
|
||||
'status' => 'sent',
|
||||
'provider_message_id' => 'message-456',
|
||||
]);
|
||||
Log::shouldHaveReceived('info')
|
||||
->once()
|
||||
->with('SMS campaign status changed from scheduled to sending.', \Mockery::type('array'));
|
||||
}
|
||||
|
||||
private function createSmsContact(): Contact
|
||||
{
|
||||
$contact = Contact::query()->create([
|
||||
'source' => 'local',
|
||||
'first_name' => 'Marie',
|
||||
'last_name' => 'Durand',
|
||||
]);
|
||||
$contact->phoneNumbers()->create([
|
||||
'raw_number' => '06 11 11 11 11',
|
||||
'normalized_number' => '+33611111111',
|
||||
'country' => 'FR',
|
||||
'is_valid' => true,
|
||||
'is_mobile' => true,
|
||||
'is_primary' => true,
|
||||
]);
|
||||
|
||||
return $contact;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user