create(['frozen_by_balance_at' => null]); $project = Project::factory()->for($tenant)->create([ 'is_active' => true, 'signal_type' => 'call', 'signal_identifier' => $identifier, 'daily_limit_target' => $limit, 'delivery_days_mask' => 127, 'regions' => [], ]); insertSnapshotForTomorrow($project, dailyLimit: $limit, deliveryDaysMask: 127); return $project; } /** * Наша supplier_project, которую мы уже выключили (inactive_since IS NOT NULL) — * SupplierOrderVerifier::diff должен поймать kind=should_be_off, если поставщик * до сих пор показывает её включённой (status=1). supplier_external_id — признак * «наша строка» в normalizeLive() (баг 1: tag НЕ годится — робот шлёт регион/«РФ»). */ function seedPausedSupplierProject(string $identifier, string $platform, string $signalType, string $externalId): SupplierProject { return SupplierProject::factory()->create([ 'platform' => $platform, 'signal_type' => $signalType, 'unique_key' => $identifier, 'supplier_external_id' => $externalId, 'inactive_since' => now(), ]); } it('нет расхождений → письма нет, статус ok', function (): void { Mail::fake(); seedActiveCallProject('79135397707', 3); SupplierProject::factory()->create([ 'platform' => 'B1', 'signal_type' => 'call', 'unique_key' => '79135397707', 'supplier_external_id' => '1001', 'current_limit' => 1, 'inactive_since' => null, ]); SupplierProject::factory()->create([ 'platform' => 'B2', 'signal_type' => 'call', 'unique_key' => '79135397707', 'supplier_external_id' => '1002', 'current_limit' => 1, 'inactive_since' => null, ]); SupplierProject::factory()->create([ 'platform' => 'B3', 'signal_type' => 'call', 'unique_key' => '79135397707', 'supplier_external_id' => '1003', 'current_limit' => 1, 'inactive_since' => null, ]); $this->mock(SupplierPortalClient::class, function ($m): void { $m->shouldReceive('listProjects')->andReturn([ ['id' => '1001', 'name' => 'B1_79135397707', 'content' => '79135397707', 'type' => 'calls', 'lim' => 1, 'status' => '1', 'tag' => 'РФ'], ['id' => '1002', 'name' => 'B2_79135397707', 'content' => '79135397707', 'type' => 'calls', 'lim' => 1, 'status' => '1', 'tag' => 'РФ'], ['id' => '1003', 'name' => 'B3_79135397707', 'content' => '79135397707', 'type' => 'calls', 'lim' => 1, 'status' => '1', 'tag' => 'РФ'], ]); }); (new VerifySupplierOrderJob(attempt: 2))->handle(); Mail::assertNothingQueued(); $row = DB::connection('pgsql_supplier')->table('supplier_order_checks')->latest('id')->first(); expect($row)->not->toBeNull(); expect($row->status)->toBe('ok')->and($row->mismatch_count)->toBe(0); }); it('пауза не дошла (should_be_off) → письмо + статус mismatch на попытке 2', function (): void { Mail::fake(); seedPausedSupplierProject('automoney.ru', 'B3', 'site', '2001'); $this->mock(SupplierPortalClient::class, function ($m): void { $m->shouldReceive('listProjects')->andReturn([ ['id' => '2001', 'name' => 'B3_automoney.ru', 'content' => 'automoney.ru', 'type' => 'hosts', 'lim' => 1, 'status' => '1', 'tag' => 'РФ'], ]); }); (new VerifySupplierOrderJob(attempt: 2))->handle(); Mail::assertQueued(SupplierOrderMismatchMail::class); $row = DB::connection('pgsql_supplier')->table('supplier_order_checks')->latest('id')->first(); expect($row)->not->toBeNull(); expect($row->status)->toBe('mismatch')->and($row->mismatch_count)->toBe(1); }); it('ключ активен по формуле, но inactive_since завис → НЕ ложная тревога (баг 2)', function (): void { Mail::fake(); seedActiveCallProject('79999999999', 3); // B1 «завис» с inactive_since, хотя группа сейчас активна по формуле — // должен быть исключён из shouldBeOff, а не дать ложный should_be_off. SupplierProject::factory()->create([ 'platform' => 'B1', 'signal_type' => 'call', 'unique_key' => '79999999999', 'supplier_external_id' => '3001', 'current_limit' => 1, 'inactive_since' => now(), ]); SupplierProject::factory()->create([ 'platform' => 'B2', 'signal_type' => 'call', 'unique_key' => '79999999999', 'supplier_external_id' => '3002', 'current_limit' => 1, 'inactive_since' => null, ]); SupplierProject::factory()->create([ 'platform' => 'B3', 'signal_type' => 'call', 'unique_key' => '79999999999', 'supplier_external_id' => '3003', 'current_limit' => 1, 'inactive_since' => null, ]); $this->mock(SupplierPortalClient::class, function ($m): void { $m->shouldReceive('listProjects')->andReturn([ ['id' => '3001', 'name' => 'B1_79999999999', 'content' => '79999999999', 'type' => 'calls', 'lim' => 1, 'status' => '1', 'tag' => 'РФ'], ['id' => '3002', 'name' => 'B2_79999999999', 'content' => '79999999999', 'type' => 'calls', 'lim' => 1, 'status' => '1', 'tag' => 'РФ'], ['id' => '3003', 'name' => 'B3_79999999999', 'content' => '79999999999', 'type' => 'calls', 'lim' => 1, 'status' => '1', 'tag' => 'РФ'], ]); }); (new VerifySupplierOrderJob(attempt: 2))->handle(); Mail::assertNothingQueued(); $row = DB::connection('pgsql_supplier')->table('supplier_order_checks')->latest('id')->first(); expect($row)->not->toBeNull(); expect($row->status)->toBe('ok')->and($row->mismatch_count)->toBe(0); });