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 $filenames */ $filenames = []; /** @var array $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 $body */ $body = $r->data(); /** @var array $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([]); });