c6137b910d
Хвосты от pre-commit хука: импорт Builder и сокращение полных имён классов в PHPDoc. Поведение не меняется, тесты те же. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
135 lines
5.9 KiB
PHP
135 lines
5.9 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');
|
|
});
|
|
|
|
it('фирму, помеченную только для ВК, в Яндекс не льёт', function () {
|
|
// 🔴 Смысл фичи: начальник увёл фирму в ВК — из яндексового сегмента она
|
|
// должна уйти сама при ближайшей заливке, без ручной чистки.
|
|
config(['services.yandex_audience.token' => 'test-token']);
|
|
SalesAdAudienceState::current()->update(['enabled' => true, 'yandex_segment_id' => 58029600]);
|
|
|
|
$yandexFirm = SalesAdAudienceFirm::create([
|
|
'firm_name' => 'Греем в Яндексе', 'warmup_started_at' => now(), 'channels' => 'yandex',
|
|
]);
|
|
SalesAdAudiencePhone::create([
|
|
'phone' => '79990000001', 'firm_id' => $yandexFirm->id,
|
|
'expires_at' => now()->addDays(30), 'state' => 'active',
|
|
]);
|
|
|
|
$vkFirm = SalesAdAudienceFirm::create([
|
|
'firm_name' => 'Греем в ВК', 'warmup_started_at' => now(), 'channels' => 'vk',
|
|
]);
|
|
SalesAdAudiencePhone::create([
|
|
'phone' => '79990000002', 'firm_id' => $vkFirm->id,
|
|
'expires_at' => now()->addDays(30), 'state' => 'active',
|
|
]);
|
|
|
|
Http::fake(['api-audience.yandex.ru/*' => Http::response(['result' => true], 200)]);
|
|
|
|
SyncAdAudienceJob::dispatchSync();
|
|
|
|
Http::assertSent(function (Request $r) {
|
|
$body = collect($r->data())->firstWhere('name', 'file');
|
|
$csv = is_array($body) && is_string($body['contents'] ?? null) ? $body['contents'] : '';
|
|
|
|
expect($csv)->toContain('79990000001')
|
|
->and($csv)->not->toContain('79990000002');
|
|
|
|
return true;
|
|
});
|
|
});
|