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

115 lines
4.4 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Jobs\Supplier\SyncSupplierProjectsJob;
use App\Models\Project;
use App\Models\Tenant;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class);
uses(SharesSupplierPdo::class);
/**
* Task 2.6 — офлайн-батч обязан заказывать у поставщика по источнику ИЗ СЛЕПКА,
* а не по live projects.* . Закрывает рассинхрон окна 18:02 (snapshot) → 18:05 (sync):
* клиент мог сменить источник между двумя cron'ами, но завтрашний заказ уже зафиксирован
* слепком (slepok-инвариант) — иначе поставщик заберёт лиды по НОВОМУ источнику,
* а раздача доводит хвост по СТАРОМУ → рассинхрон денег.
*/
function insertTomorrowSmsSnapshot(
Project $project,
array $smsSenders,
?string $smsKeyword = null,
): void {
$tomorrow = Carbon::tomorrow('Europe/Moscow')->toDateString();
DB::table('project_routing_snapshots')->insert([
'snapshot_date' => $tomorrow,
'project_id' => $project->id,
'tenant_id' => $project->tenant_id,
'daily_limit' => 10,
'delivery_days_mask' => 127,
'regions' => '{}',
'signal_type' => 'sms',
'signal_identifier' => null,
'sms_senders' => json_encode($smsSenders),
'sms_keyword' => $smsKeyword,
'expected_volume' => 10,
'delivered_count' => 0,
'created_at' => now(),
]);
}
it('батч заказывает по источнику из слепка, даже если live-источник сменили после слепка', function (): void {
Carbon::setTestNow(Carbon::parse('2026-05-27 18:04:00', 'Europe/Moscow'));
$tenant = Tenant::factory()->create(['frozen_by_balance_at' => null]);
$project = Project::factory()->for($tenant)->create([
'is_active' => true,
'signal_type' => 'sms',
'signal_identifier' => null,
'sms_senders' => ['Caranga'],
'sms_keyword' => null,
]);
// Снимок снят с источника Caranga.
insertTomorrowSmsSnapshot($project, smsSenders: ['Caranga']);
// ↓ Клиент сменил live-источник в окне 18:02→18:05 (после слепка).
DB::table('projects')->where('id', $project->id)->update([
'sms_senders' => json_encode(['NewSender']),
]);
$projects = (new SyncSupplierProjectsJob)->collectEligibleProjects();
$ours = $projects->where('id', $project->id);
expect($ours)->toHaveCount(1);
// Источник ИЗ СЛЕПКА (Caranga), не live (NewSender).
expect((array) $ours->first()->sms_senders)->toBe(['Caranga']);
Carbon::setTestNow();
});
it('батч переопределяет signal_identifier (site) значением слепка', function (): void {
Carbon::setTestNow(Carbon::parse('2026-05-27 18:04:00', 'Europe/Moscow'));
$tenant = Tenant::factory()->create(['frozen_by_balance_at' => null]);
$project = Project::factory()->for($tenant)->create([
'is_active' => true,
'signal_type' => 'site',
'signal_identifier' => 'old-site.ru',
'sms_senders' => null,
'sms_keyword' => null,
]);
$tomorrow = Carbon::tomorrow('Europe/Moscow')->toDateString();
DB::table('project_routing_snapshots')->insert([
'snapshot_date' => $tomorrow,
'project_id' => $project->id,
'tenant_id' => $project->tenant_id,
'daily_limit' => 10,
'delivery_days_mask' => 127,
'regions' => '{}',
'signal_type' => 'site',
'signal_identifier' => 'old-site.ru',
'sms_senders' => null,
'sms_keyword' => null,
'expected_volume' => 10,
'delivered_count' => 0,
'created_at' => now(),
]);
DB::table('projects')->where('id', $project->id)->update([
'signal_identifier' => 'new-site.ru',
]);
$projects = (new SyncSupplierProjectsJob)->collectEligibleProjects();
$ours = $projects->where('id', $project->id);
expect($ours)->toHaveCount(1);
expect($ours->first()->signal_identifier)->toBe('old-site.ru');
Carbon::setTestNow();
});