49ee084b7e
Номера, заведённые до v8.77, остались без firm_id. Ночной пересчёт обходит фирмы и такой номер не видит, а заливка видела — он крутился бы вечно. Нашёл rls-reviewer при сквозной проверке. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
4.3 KiB
PHP
98 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Jobs\SyncAdAudienceJob;
|
|
use App\Models\SalesAdAudienceFirm;
|
|
use App\Models\SalesAdAudiencePhone;
|
|
use App\Models\SalesAdAudienceState;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Http\Client\Request;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\Concerns\SharesSupplierPdo;
|
|
|
|
/**
|
|
* Ночная заливка состава аудитории в Яндекс (Task 6).
|
|
*
|
|
* SyncAdAudienceJob использует уже готовый YandexAudienceClient (тесты — в
|
|
* tests/Unit/YandexAudienceClientTest.php, здесь его не трогаем) и режим
|
|
* modify_data(..., 'replace'): заливает весь активный список целиком.
|
|
*
|
|
* Ключевое правило владельца: пока рубильник `enabled` выключен — в Яндекс
|
|
* не уходит НИЧЕГО (защита от случайной траты денег).
|
|
*/
|
|
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
|
|
|
|
beforeEach(function () {
|
|
config(['services.yandex_audience.token' => 'test-token']);
|
|
SalesAdAudienceState::current()->update(['enabled' => true, 'yandex_segment_id' => 58029600]);
|
|
});
|
|
|
|
it('номер без хозяина в рекламу не попадает', function () {
|
|
// Сирота из схемы до v8.77: фирмы у него нет, значит ночной пересчёт его
|
|
// никогда не тронет. Попади он в сегмент — крутился бы вечно.
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['segment' => ['id' => 58029600]], 200),
|
|
]);
|
|
|
|
$firm = SalesAdAudienceFirm::create(['firm_name' => 'Тестовая стоматология']);
|
|
|
|
SalesAdAudiencePhone::create(['phone' => '79990000001', 'firm_id' => $firm->id, 'expires_at' => now()->addDays(3), 'state' => 'active']);
|
|
SalesAdAudiencePhone::create(['phone' => '79990000009', 'firm_id' => null, 'expires_at' => now()->addDays(3), 'state' => 'active']);
|
|
|
|
SyncAdAudienceJob::dispatchSync();
|
|
|
|
Http::assertSent(function (Request $request) {
|
|
$body = (string) $request->body();
|
|
|
|
return str_contains($body, '79990000001') && ! str_contains($body, '79990000009');
|
|
});
|
|
});
|
|
|
|
it('пока рубильник выключен, в Яндекс ничего не уходит', function () {
|
|
Http::fake();
|
|
SalesAdAudienceState::current()->update(['enabled' => false]);
|
|
|
|
SyncAdAudienceJob::dispatchSync();
|
|
|
|
Http::assertNothingSent();
|
|
});
|
|
|
|
it('заливает только активные номера', function () {
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['segment' => ['id' => 58029600]], 200),
|
|
]);
|
|
|
|
$firm = SalesAdAudienceFirm::create(['firm_name' => 'Тестовая стоматология']);
|
|
|
|
SalesAdAudiencePhone::create(['phone' => '79990000001', 'firm_id' => $firm->id, 'expires_at' => now()->addDays(3), 'state' => 'active']);
|
|
SalesAdAudiencePhone::create(['phone' => '79990000002', 'firm_id' => $firm->id, 'expires_at' => now()->addDays(3), 'state' => 'paused']);
|
|
SalesAdAudiencePhone::create(['phone' => '79990000003', 'firm_id' => $firm->id, 'expires_at' => now()->addDays(3), 'state' => 'stopped']);
|
|
|
|
SyncAdAudienceJob::dispatchSync();
|
|
|
|
Http::assertSent(function (Request $request) {
|
|
$body = (string) $request->body();
|
|
|
|
return str_contains($request->url(), 'modify_data')
|
|
&& str_contains($body, '79990000001')
|
|
&& ! str_contains($body, '79990000002')
|
|
&& ! str_contains($body, '79990000003');
|
|
});
|
|
|
|
expect(SalesAdAudienceState::current()->last_synced_at)->not->toBeNull();
|
|
});
|
|
|
|
it('ошибку Яндекса показывает начальнику, а не прячет', function () {
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['error' => 'quota exceeded'], 429),
|
|
]);
|
|
|
|
$firm = SalesAdAudienceFirm::create(['firm_name' => 'Тестовая стоматология']);
|
|
SalesAdAudiencePhone::create(['phone' => '79990000001', 'firm_id' => $firm->id, 'expires_at' => now()->addDays(3), 'state' => 'active']);
|
|
|
|
SyncAdAudienceJob::dispatchSync();
|
|
|
|
expect(SalesAdAudienceState::current()->last_error)->toContain('quota');
|
|
});
|