f9dea24ac4
Создание сегмента — два шага, а не один: upload_csv_file даёт статус uploaded
(черновик, в списке Аудиторий его нет и Директу он не виден), и только
segment/{id}/confirm сохраняет сегмент. Второй шаг отсутствовал — 19.07 заливка
на бою отчиталась успехом, id записался, а в Аудиториях было пусто.
content_type для телефонов строго 'crm'. Регресс-тест закрывает.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
135 lines
5.5 KiB
PHP
135 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Services\Sales\YandexAudienceClient;
|
|
use Illuminate\Http\Client\Request;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
// tests/Unit (кроме Unit/Autopodbor) не привязан к TestCase в Pest.php —
|
|
// фасад Http без контейнера Laravel не поднимется.
|
|
uses(TestCase::class);
|
|
|
|
it('создаёт сегмент из CSV с заголовком phone', function () {
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['segment' => ['id' => 58029600, 'status' => 'uploaded']], 200),
|
|
]);
|
|
|
|
$id = (new YandexAudienceClient('test-token'))->createSegment('Кандидаты', ['79990000001', '79990000002']);
|
|
|
|
expect($id)->toBe(58029600);
|
|
|
|
Http::assertSent(function (Request $r) {
|
|
if (! str_contains($r->url(), 'upload_csv_file')) {
|
|
return false;
|
|
}
|
|
|
|
expect($r->hasHeader('Authorization', 'OAuth test-token'))->toBeTrue();
|
|
expect($r->isMultipart())->toBeTrue();
|
|
|
|
// Тело multipart: имя файла и сам CSV c заголовком phone.
|
|
// Раскладываем в плоские массивы явно — иначе статанализ видит здесь mixed.
|
|
/** @var array<string, string> $filenames */
|
|
$filenames = [];
|
|
/** @var array<string, string> $contents */
|
|
$contents = [];
|
|
|
|
foreach ($r->data() as $part) {
|
|
if (! is_array($part) || ! isset($part['name']) || ! is_string($part['name'])) {
|
|
continue;
|
|
}
|
|
$filenames[$part['name']] = is_string($part['filename'] ?? null) ? $part['filename'] : '';
|
|
$contents[$part['name']] = is_string($part['contents'] ?? null) ? $part['contents'] : '';
|
|
}
|
|
|
|
expect($filenames)->toHaveKey('file');
|
|
expect($filenames['file'])->toBe('phones.csv');
|
|
expect($contents['file'])->toBe("phone\n79990000001\n79990000002\n");
|
|
|
|
return true;
|
|
});
|
|
});
|
|
|
|
it('после загрузки файла ОБЯЗАТЕЛЬНО подтверждает сегмент', function () {
|
|
// 🔴 Регресс на боевую поломку 19.07: без второго вызова confirm сегмент
|
|
// остаётся черновиком (status uploaded), в списке Аудиторий его нет,
|
|
// Директу он не виден — а база при этом пишет «успешно создан».
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['segment' => ['id' => 58029600, 'status' => 'uploaded']], 200),
|
|
]);
|
|
|
|
(new YandexAudienceClient('test-token'))->createSegment('Кандидаты', ['79990000001', '79990000002']);
|
|
|
|
Http::assertSent(function (Request $r) {
|
|
if (! str_contains($r->url(), '/segment/58029600/confirm')) {
|
|
return false;
|
|
}
|
|
|
|
expect($r->hasHeader('Authorization', 'OAuth test-token'))->toBeTrue();
|
|
|
|
/** @var array<string, mixed> $body */
|
|
$body = $r->data();
|
|
/** @var array<string, mixed> $segment */
|
|
$segment = is_array($body['segment'] ?? null) ? $body['segment'] : [];
|
|
|
|
expect($segment['id'] ?? null)->toBe(58029600);
|
|
expect($segment['name'] ?? null)->toBe('Кандидаты');
|
|
expect($segment['hashed'] ?? null)->toBeFalse();
|
|
// Единственное верное значение для телефонов — 'crm'.
|
|
expect($segment['content_type'] ?? null)->toBe('crm');
|
|
|
|
return true;
|
|
});
|
|
});
|
|
|
|
it('не считает повторную заливку того же файла аварией', function () {
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['errors' => [['error_type' => 'no_changes']]], 400),
|
|
]);
|
|
|
|
$result = (new YandexAudienceClient('test-token'))->modifyData(58029600, ['79990000001'], 'addition');
|
|
|
|
expect($result)->toBe('no_changes');
|
|
|
|
Http::assertSent(function (Request $r) {
|
|
expect($r->url())->toContain('/v1/management/segment/58029600/modify_data');
|
|
expect($r->url())->toContain('modification_type=addition');
|
|
|
|
return true;
|
|
});
|
|
});
|
|
|
|
it('бросает исключение на настоящей ошибке заливки', function () {
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['errors' => [['error_type' => 'invalid_file']]], 400),
|
|
]);
|
|
|
|
(new YandexAudienceClient('test-token'))->modifyData(58029600, ['79990000001'], 'addition');
|
|
})->throws(RuntimeException::class);
|
|
|
|
it('читает охват и статус сегмента по id', function () {
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['segments' => [
|
|
['id' => 111, 'name' => 'Чужой', 'status' => 'processed'],
|
|
['id' => 58029600, 'name' => 'Кандидаты', 'status' => 'processed', 'matched_quantity' => 4200],
|
|
]], 200),
|
|
]);
|
|
|
|
$segment = (new YandexAudienceClient('test-token'))->segment(58029600);
|
|
|
|
expect($segment['status'])->toBe('processed');
|
|
expect($segment['matched_quantity'])->toBe(4200);
|
|
|
|
Http::assertSent(fn (Request $r) => str_contains($r->url(), '/v1/management/segments')
|
|
&& $r->hasHeader('Authorization', 'OAuth test-token'));
|
|
});
|
|
|
|
it('отдаёт пустой массив, если сегмента нет в списке', function () {
|
|
Http::fake([
|
|
'api-audience.yandex.ru/*' => Http::response(['segments' => []], 200),
|
|
]);
|
|
|
|
expect((new YandexAudienceClient('test-token'))->segment(58029600))->toBe([]);
|
|
});
|