Files
portal/app/tests/Feature/Supplier/DeleteSupplierProjectTailGuardTest.php
T

103 lines
4.5 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Jobs\Supplier\DeleteSupplierProjectJob;
use App\Models\Project;
use App\Models\SupplierProject;
use App\Models\Tenant;
use App\Services\Supplier\SupplierPortalClient;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
/**
* Task 2.4 — отложенное удаление донора, пока летит хвост слепка.
*
* Если у supplier_project нет других потребителей, но за активную/завтрашнюю дату
* есть снимок маршрутизации с его источником — хвостовой лид ещё может прийти, и
* LeadRouter ищет snapshot по источнику донора. Удалив донора, мы оборвём матч.
* Поэтому удаление откладывается до следующего CleanupInactiveSupplierProjectsJob.
*/
function insertTailSnapshot(
Tenant $tenant,
string $signalType,
?string $signalIdentifier,
?array $smsSenders = null,
?string $smsKeyword = null,
?string $date = null,
): void {
DB::table('project_routing_snapshots')->insert([
'snapshot_date' => $date ?? Carbon::tomorrow('Europe/Moscow')->toDateString(),
'project_id' => random_int(900000, 999999),
'tenant_id' => $tenant->id,
'daily_limit' => 10,
'delivery_days_mask' => 127,
'regions' => '{}',
'signal_type' => $signalType,
'signal_identifier' => $signalIdentifier,
'sms_senders' => $smsSenders === null ? null : json_encode($smsSenders),
'sms_keyword' => $smsKeyword,
'expected_volume' => 10,
'delivered_count' => 0,
'created_at' => now(),
]);
}
it('не удаляет supplier_project, пока есть снимок с его источником за активную дату', function (): void {
$tenant = Tenant::factory()->create();
$sp = SupplierProject::query()->create([
'platform' => 'B1', 'signal_type' => 'call', 'unique_key' => '79991110000',
'supplier_external_id' => '555', 'current_limit' => 1,
]);
// Снимок-хвост по тому же источнику (call/79991110000), потребителей pivot — нет.
insertTailSnapshot($tenant, 'call', '79991110000');
$mock = Mockery::mock(SupplierPortalClient::class);
$mock->shouldNotReceive('deleteProject');
app()->instance(SupplierPortalClient::class, $mock);
(new DeleteSupplierProjectJob([$sp->id]))->handle(app(SupplierPortalClient::class));
// Донор НЕ удалён — удаление отложено, пока летит хвост.
expect(SupplierProject::find($sp->id))->not->toBeNull();
});
it('откладывает удаление sms-донора, пока снимок несёт его sender в хвосте', function (): void {
$tenant = Tenant::factory()->create();
$sp = SupplierProject::query()->create([
'platform' => 'B3', 'signal_type' => 'sms', 'unique_key' => 'Caranga',
'supplier_external_id' => '777', 'current_limit' => 1,
]);
insertTailSnapshot($tenant, 'sms', null, smsSenders: ['Caranga']);
$mock = Mockery::mock(SupplierPortalClient::class);
$mock->shouldNotReceive('deleteProject');
app()->instance(SupplierPortalClient::class, $mock);
(new DeleteSupplierProjectJob([$sp->id]))->handle(app(SupplierPortalClient::class));
expect(SupplierProject::find($sp->id))->not->toBeNull();
});
it('удаляет донора, когда хвоста слепка нет (регрессия базового удаления)', function (): void {
$tenant = Tenant::factory()->create();
$sp = SupplierProject::query()->create([
'platform' => 'B1', 'signal_type' => 'call', 'unique_key' => '79992220000',
'supplier_external_id' => '600', 'current_limit' => 1,
]);
// Снимок есть, но по ДРУГОМУ источнику — хвоста для этого донора нет.
insertTailSnapshot($tenant, 'call', '70000000000');
$mock = Mockery::mock(SupplierPortalClient::class);
$mock->shouldReceive('deleteProject')->once()->with(600);
app()->instance(SupplierPortalClient::class, $mock);
(new DeleteSupplierProjectJob([$sp->id]))->handle(app(SupplierPortalClient::class));
expect(SupplierProject::find($sp->id))->toBeNull();
});