88ace4e3d9
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Снижение остатка 19 to 5. Всё тест-сторона: - PdErasureServiceTest + AdminPdSubjectRequestsControllerTest: SharesSupplierPdo — перестали коммитить pd_processing_log через pgsql_supplier, что ломало глобальный audit:verify-chains (6 падений) и амплифицировало PhoneRegionSmoke. - ReportFileDeletePdLogTest: SharesSupplierPdo — cron reports:cleanup-expired теперь видит незакоммиченные job'ы теста. - AdminSuppliersControllerTest: устойчивый ассерт (с фазы 3 в suppliers есть direct). - AuthLogCoverageTest/AuthFlowIntegrationTest: новый флоу самозаписи G1/SP1 — register_success пишется после confirm-email; добавлен шаг подтверждения. - ImpersonationTest end: verify (G7-B) ставит маркер impersonation → admin-зона закрыта by design; помечаем токен used напрямую вместо session-takeover. - CleanupInactiveSupplierProjectsJobTest: phase A читает pivot project_supplier_links — добавлена привязка linkProjectToSupplier (раньше был только legacy FK). - Pint-нормализация uses() FQN to import в ранее тронутых файлах. Остаток 5 (НЕ слепой патч): webhook B-префикс ×2 (решение владельца), advisory-lock audit-цепочки (возможный дрейф схемы, флажок), SupplierConnection WARN#2 (cap-3, поведенческое), SupplierPortalClientTest (пре-существующий, не от этих правок). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
143 lines
5.5 KiB
PHP
143 lines
5.5 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Jobs\RouteSupplierLeadJob;
|
||
use App\Models\Project;
|
||
use App\Models\SupplierLead;
|
||
use App\Models\SupplierProject;
|
||
use App\Models\Tenant;
|
||
use App\Services\Billing\LedgerService;
|
||
use App\Services\LeadDistributor;
|
||
use App\Services\LeadRouter;
|
||
use App\Services\MonthlyPartitionManager;
|
||
use App\Services\NotificationService;
|
||
use App\Services\RegionTagResolver;
|
||
use App\Services\SupplierProjects\SupplierProjectResolver;
|
||
use Carbon\Carbon;
|
||
use Database\Seeders\PricingTierSeeder;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Tests\Concerns\SharesSupplierPdo;
|
||
|
||
uses(DatabaseTransactions::class);
|
||
uses(SharesSupplierPdo::class);
|
||
|
||
beforeEach(function (): void {
|
||
$this->seed(PricingTierSeeder::class);
|
||
DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");
|
||
// Тесты фиксируют Carbon на 2026-05-28; доставка пишет в несколько
|
||
// партиционированных таблиц (deals/balance_transactions/activity_log/
|
||
// pd_processing_log/tenant_operations_log) → гарантируем партиции мая для всех.
|
||
$mpm = app(MonthlyPartitionManager::class);
|
||
foreach (array_keys(MonthlyPartitionManager::PARTITIONED_TABLES) as $table) {
|
||
$mpm->ensureRange($table, Carbon::parse('2026-05-01'), Carbon::parse('2026-05-31'));
|
||
}
|
||
});
|
||
|
||
function runSnapshotRouteJob(int $supplierLeadId): void
|
||
{
|
||
(new RouteSupplierLeadJob($supplierLeadId))->handle(
|
||
app(LeadRouter::class),
|
||
app(SupplierProjectResolver::class),
|
||
app(NotificationService::class),
|
||
app(LedgerService::class),
|
||
app(LeadDistributor::class),
|
||
app(RegionTagResolver::class),
|
||
);
|
||
}
|
||
|
||
it('uses snapshot daily_limit, not live daily_limit_target (R-04/R-06)', function (): void {
|
||
Carbon::setTestNow('2026-05-28 12:00:00', 'Europe/Moscow');
|
||
|
||
$tenant = Tenant::factory()->create(['balance_rub' => '500.00', 'frozen_by_balance_at' => null]);
|
||
$project = Project::factory()->for($tenant)->create([
|
||
'is_active' => true,
|
||
'delivery_days_mask' => 127,
|
||
'daily_limit_target' => 100, // live limit big
|
||
'delivered_today' => 4,
|
||
'delivered_in_month' => 0,
|
||
]);
|
||
$sp = SupplierProject::factory()->create(['signal_type' => 'site']);
|
||
linkProjectToSupplier($project, $sp);
|
||
// Snapshot имеет МАЛЕНЬКИЙ daily_limit=5 — после доставки 1 deal'a должно стать 5.
|
||
createRoutingSnapshotFromProject(
|
||
$project,
|
||
date: '2026-05-28',
|
||
signalType: 'site',
|
||
signalIdentifier: $sp->unique_key,
|
||
dailyLimit: 5,
|
||
);
|
||
|
||
$lead = SupplierLead::factory()->create([
|
||
'supplier_project_id' => $sp->id,
|
||
'raw_payload' => ['project' => $sp->platform.'_'.$sp->unique_key, 'phones' => ['79161234567']],
|
||
'phone' => '79161234567',
|
||
'vid' => 1001,
|
||
]);
|
||
|
||
runSnapshotRouteJob($lead->id);
|
||
|
||
expect(DB::table('deals')->where('tenant_id', $tenant->id)->count())->toBe(1);
|
||
|
||
// delivered_today инкрементнут на live, delivered_count на snapshot.
|
||
expect((int) DB::table('projects')->where('id', $project->id)->value('delivered_today'))->toBe(5);
|
||
|
||
$snap = DB::table('project_routing_snapshots')
|
||
->where('snapshot_date', '2026-05-28')
|
||
->where('project_id', $project->id)
|
||
->first();
|
||
expect((int) $snap->delivered_count)->toBe(1);
|
||
|
||
Carbon::setTestNow();
|
||
});
|
||
|
||
it('rejects lead when is_active becomes false under lock (R-09)', function (): void {
|
||
Carbon::setTestNow('2026-05-28 12:00:00', 'Europe/Moscow');
|
||
|
||
$tenant = Tenant::factory()->create(['balance_rub' => '500.00', 'frozen_by_balance_at' => null]);
|
||
// Live state: is_active=true для snapshot:backfill, но потом клиент нажмёт «пауза»
|
||
// (между matchEligibleProjects и handle). Имитируем это inline UPDATE'ом.
|
||
$project = Project::factory()->for($tenant)->create([
|
||
'is_active' => true,
|
||
'delivery_days_mask' => 127,
|
||
'daily_limit_target' => 10,
|
||
'delivered_today' => 0,
|
||
'delivered_in_month' => 0,
|
||
]);
|
||
$sp = SupplierProject::factory()->create(['signal_type' => 'site']);
|
||
linkProjectToSupplier($project, $sp);
|
||
// Snapshot НЕ paused (fixed до момента pause'а).
|
||
createRoutingSnapshotFromProject(
|
||
$project,
|
||
date: '2026-05-28',
|
||
signalType: 'site',
|
||
signalIdentifier: $sp->unique_key,
|
||
dailyLimit: 10,
|
||
);
|
||
|
||
// ↓ Имитация: клиент paused проект в окне между matchEligible и handle.
|
||
DB::table('projects')->where('id', $project->id)->update(['is_active' => false]);
|
||
|
||
$lead = SupplierLead::factory()->create([
|
||
'supplier_project_id' => $sp->id,
|
||
'raw_payload' => ['project' => $sp->platform.'_'.$sp->unique_key, 'phones' => ['79161234567']],
|
||
'phone' => '79161234567',
|
||
'vid' => 1002,
|
||
]);
|
||
|
||
runSnapshotRouteJob($lead->id);
|
||
|
||
// Snapshot говорит «доставлять», но live is_active=false под lock'ом — НЕ доставляем.
|
||
expect(DB::table('deals')->where('tenant_id', $tenant->id)->count())->toBe(0);
|
||
expect((int) DB::table('projects')->where('id', $project->id)->value('delivered_today'))->toBe(0);
|
||
|
||
$snap = DB::table('project_routing_snapshots')
|
||
->where('snapshot_date', '2026-05-28')
|
||
->where('project_id', $project->id)
|
||
->first();
|
||
expect((int) $snap->delivered_count)->toBe(0);
|
||
|
||
Carbon::setTestNow();
|
||
});
|