2026-07-19 08:16:14 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
use App\Jobs\SyncAdAudienceJob;
|
2026-07-19 10:35:41 +03:00
|
|
|
|
use App\Models\SalesAdAudienceFirm;
|
2026-07-19 08:16:14 +03:00
|
|
|
|
use App\Models\SalesAdAudiencePhone;
|
2026-07-22 16:39:45 +03:00
|
|
|
|
use App\Models\SalesAdAudiencePlatform;
|
2026-07-19 08:16:14 +03:00
|
|
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
2026-07-22 16:39:45 +03:00
|
|
|
|
use Illuminate\Http\Client\Request;
|
|
|
|
|
|
use Illuminate\Support\Facades\Http;
|
2026-07-19 08:16:14 +03:00
|
|
|
|
use Tests\Concerns\SharesSupplierPdo;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-19 10:35:41 +03:00
|
|
|
|
* Приём фирм в прогрев рекламой сервис-каналом «Поиск → Портал» (контракт v2).
|
|
|
|
|
|
*
|
2026-07-22 16:39:45 +03:00
|
|
|
|
* Фаза 2 этап B: заезд БОЛЬШЕ НЕ запускает прогрев. Он только ГРУЗИТ фирму во
|
|
|
|
|
|
* все 4 канала (yandex/vk/mts/sms) в состоянии «загружена, выключена»
|
|
|
|
|
|
* (firm_channels.status='loaded'). Сам прогрев запускается отдельно кнопкой
|
|
|
|
|
|
* «Греть» на портале (этап C). Поэтому:
|
|
|
|
|
|
* • заезд новой фирмы создаёт 4 строки firm_channels со status='loaded'
|
|
|
|
|
|
* (ни одной warming — фирма в заливки/сегменты пока не идёт);
|
|
|
|
|
|
* • повторный заезд НЕ рестартует прогрев (не сбрасывает warmup_started_at/
|
|
|
|
|
|
* ready_at/stopped_at/stop_reason) и НЕ понижает уже греющийся канал в loaded;
|
|
|
|
|
|
* • поля channels/ch_* в payload на членство больше не влияют — источник
|
|
|
|
|
|
* членства это строки firm_channels, а не булевы ch_* на фирме.
|
2026-07-19 08:16:14 +03:00
|
|
|
|
*
|
|
|
|
|
|
* Токен канала — config('sales.integration_token') (middleware SalesIntegrationToken).
|
|
|
|
|
|
* В тестовом окружении env пуст, поэтому задаём его здесь, как в SalesIntegrationApiTest.
|
|
|
|
|
|
*
|
2026-07-22 16:39:45 +03:00
|
|
|
|
* SharesSupplierPdo обязателен: модели прибиты к соединению pgsql_supplier
|
2026-07-19 08:16:14 +03:00
|
|
|
|
* (отдельный PDO), и без шаринга DatabaseTransactions откатывал бы только
|
|
|
|
|
|
* соединение по умолчанию — записи текли бы из теста в тест.
|
|
|
|
|
|
*/
|
|
|
|
|
|
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
|
|
|
|
|
|
|
|
|
|
|
|
beforeEach(function () {
|
2026-07-19 10:35:41 +03:00
|
|
|
|
config(['sales.integration_token' => 'test-token']);
|
2026-07-19 08:16:14 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
function warmupPayload(array $override = []): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return ['firms' => [array_merge([
|
|
|
|
|
|
'firm_name' => 'Тестовая стоматология',
|
|
|
|
|
|
'firm_inn' => '7700000001',
|
|
|
|
|
|
'city' => 'Москва',
|
|
|
|
|
|
'rubric' => 'Стоматологии',
|
|
|
|
|
|
'phones' => ['79990000001', '79990000002'],
|
|
|
|
|
|
'payload' => ['site' => 'example.test'],
|
|
|
|
|
|
'contacts' => [['name' => 'Директор', 'phones' => ['79990000001']]],
|
|
|
|
|
|
], $override)]];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
it('без токена канал закрыт', function () {
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload())
|
|
|
|
|
|
->assertStatus(401);
|
2026-07-19 08:16:14 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
it('заводит фирму на прогрев вместе с её номерами', function () {
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), ['X-Sales-Token' => 'test-token'])
|
|
|
|
|
|
->assertOk()
|
|
|
|
|
|
->assertJson(['added' => 1]);
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
$firm = SalesAdAudienceFirm::where('firm_inn', '7700000001')->firstOrFail();
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
expect($firm->phones()->count())->toBe(2)
|
|
|
|
|
|
->and($firm->prospect_id)->toBeNull()
|
|
|
|
|
|
->and($firm->warmup_started_at)->not->toBeNull();
|
2026-07-19 08:16:14 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
it('заезд грузит фирму во все 4 канала как loaded, прогрев не стартует', function () {
|
|
|
|
|
|
// Смысл этапа B: заезд = загрузка, не старт. Четыре строки firm_channels
|
|
|
|
|
|
// (yandex/vk/mts/sms) заводятся сразу, но ВСЕ в loaded — ни одной warming.
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), ['X-Sales-Token' => 'test-token'])
|
|
|
|
|
|
->assertOk()
|
|
|
|
|
|
->assertJson(['added' => 1]);
|
|
|
|
|
|
|
|
|
|
|
|
$firm = SalesAdAudienceFirm::where('firm_inn', '7700000001')->firstOrFail();
|
|
|
|
|
|
|
|
|
|
|
|
expect($firm->firmChannels()->count())->toBe(4)
|
|
|
|
|
|
->and($firm->firmChannels()->orderBy('channel')->pluck('channel')->all())
|
|
|
|
|
|
->toBe(['mts', 'sms', 'vk', 'yandex'])
|
|
|
|
|
|
->and($firm->firmChannels()->where('status', 'loaded')->count())->toBe(4)
|
|
|
|
|
|
->and($firm->firmChannels()->where('status', 'warming')->count())->toBe(0);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('загруженная (не греется) фирма не попадает в состав Яндекс-заливки', function () {
|
|
|
|
|
|
// Фирма заехала (loaded), но прогрев не запускали — значит в ночной сегмент
|
|
|
|
|
|
// Яндекса она входить НЕ должна: греется только warming.
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), ['X-Sales-Token' => 'test-token'])->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
SalesAdAudiencePlatform::forCode('yandex')->update(['enabled' => true, 'external_id' => 777]);
|
|
|
|
|
|
config(['services.yandex_audience.token' => 't']);
|
|
|
|
|
|
Http::fake(['api-audience.yandex.ru/*' => Http::response(['result' => true], 200)]);
|
|
|
|
|
|
|
2026-07-23 19:20:15 +03:00
|
|
|
|
// Живая (warming) фирма рядом — иначе активных номеров ноль и джоб тихо
|
|
|
|
|
|
// выйдет (пустую заливку не шлём), а исключение loaded проверялось бы на
|
|
|
|
|
|
// тишине. Здесь оно проверяется на настоящем составе заливки.
|
|
|
|
|
|
$warm = SalesAdAudienceFirm::create(['firm_name' => 'Греется', 'ch_yandex' => true]);
|
|
|
|
|
|
$warm->firmChannels()->create([
|
|
|
|
|
|
'channel' => 'yandex', 'status' => 'warming', 'mode' => 'funnel',
|
|
|
|
|
|
'warming_started_at' => now(), 'warmed_times' => 1,
|
|
|
|
|
|
]);
|
|
|
|
|
|
SalesAdAudiencePhone::create([
|
|
|
|
|
|
'phone' => '79990000009', 'firm_id' => $warm->id,
|
|
|
|
|
|
'expires_at' => now()->addDays(30), 'state' => 'active',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
SyncAdAudienceJob::dispatchSync();
|
|
|
|
|
|
|
|
|
|
|
|
Http::assertSent(function (Request $r) {
|
|
|
|
|
|
$file = collect($r->data())->firstWhere('name', 'file');
|
|
|
|
|
|
$csv = is_array($file) && is_string($file['contents'] ?? null) ? $file['contents'] : (string) $r->body();
|
|
|
|
|
|
|
2026-07-23 19:20:15 +03:00
|
|
|
|
return str_contains($csv, '79990000009') && ! str_contains($csv, '79990000001');
|
2026-07-22 16:39:45 +03:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('повторный заезд не понижает уже греющийся канал и не сбивает его срок', function () {
|
|
|
|
|
|
// Начальник нажал «Греть» по Яндексу (loaded → warming). Повторный заезд той
|
|
|
|
|
|
// же фирмы из поиска НЕ должен погасить прогрев обратно в loaded или сбить срок.
|
|
|
|
|
|
$headers = ['X-Sales-Token' => 'test-token'];
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), $headers)->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
$firm = SalesAdAudienceFirm::where('firm_inn', '7700000001')->firstOrFail();
|
|
|
|
|
|
$startedAt = now()->subDays(4);
|
|
|
|
|
|
$firm->firmChannels()->where('channel', 'yandex')->update([
|
|
|
|
|
|
'status' => 'warming', 'warming_started_at' => $startedAt,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), $headers)->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
$row = $firm->firmChannels()->where('channel', 'yandex')->firstOrFail();
|
|
|
|
|
|
|
|
|
|
|
|
expect($row->status)->toBe('warming')
|
|
|
|
|
|
->and($row->warming_started_at->timestamp)->toBe($startedAt->timestamp);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('повторный заезд не сбрасывает warmup_started_at фирмы на сегодня', function () {
|
|
|
|
|
|
// Заезд ≠ рестарт прогрева: срок фирмы, выставленный ранее, остаётся как был.
|
|
|
|
|
|
$headers = ['X-Sales-Token' => 'test-token'];
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), $headers)->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
SalesAdAudienceFirm::where('firm_inn', '7700000001')
|
|
|
|
|
|
->update(['warmup_started_at' => now()->subDays(3)]);
|
|
|
|
|
|
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), $headers)->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
$firm = SalesAdAudienceFirm::where('firm_inn', '7700000001')->firstOrFail();
|
|
|
|
|
|
|
|
|
|
|
|
expect($firm->warmup_started_at->isToday())->toBeFalse()
|
|
|
|
|
|
->and($firm->warmup_started_at->lessThan(now()->subDays(2)))->toBeTrue();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('повторная отправка не плодит дубль и не рестартует прогрев', function () {
|
|
|
|
|
|
// Фаза 2 этап B: повторный заезд НЕ трогает warmup_started_at/stopped_at —
|
|
|
|
|
|
// заезд грузит, а не перезапускает прогрев (раньше сбрасывал, теперь нет).
|
2026-07-19 10:35:41 +03:00
|
|
|
|
$headers = ['X-Sales-Token' => 'test-token'];
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), $headers)->assertOk();
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
SalesAdAudienceFirm::where('firm_inn', '7700000001')
|
|
|
|
|
|
->update(['warmup_started_at' => now()->subDays(10), 'stopped_at' => now()]);
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), $headers)
|
|
|
|
|
|
->assertOk()
|
|
|
|
|
|
->assertJson(['added' => 0, 'renewed' => 1]);
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
$firm = SalesAdAudienceFirm::where('firm_inn', '7700000001')->firstOrFail();
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
expect(SalesAdAudienceFirm::count())->toBe(1)
|
2026-07-22 16:39:45 +03:00
|
|
|
|
->and($firm->stopped_at)->not->toBeNull()
|
|
|
|
|
|
->and($firm->warmup_started_at->isToday())->toBeFalse();
|
2026-07-19 08:16:14 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
it('добавляет новый номер к уже заведённой фирме', function () {
|
|
|
|
|
|
$headers = ['X-Sales-Token' => 'test-token'];
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', warmupPayload(), $headers)->assertOk();
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => ['79990000001', '79990000003']]),
|
|
|
|
|
|
$headers
|
|
|
|
|
|
)->assertOk();
|
2026-07-19 08:16:14 +03:00
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
expect(SalesAdAudiencePhone::count())->toBe(3);
|
2026-07-19 08:16:14 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
it('отвергает номер не в формате Яндекса', function () {
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => ['+7 (999) 000-00-01']]),
|
|
|
|
|
|
['X-Sales-Token' => 'test-token']
|
|
|
|
|
|
)->assertStatus(422);
|
2026-07-19 08:16:14 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-19 10:35:41 +03:00
|
|
|
|
it('фирму без единого номера в прогрев не берём', function () {
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => []]),
|
|
|
|
|
|
['X-Sales-Token' => 'test-token']
|
|
|
|
|
|
)->assertStatus(422);
|
2026-07-19 08:16:14 +03:00
|
|
|
|
});
|
2026-07-19 19:33:58 +03:00
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
it('старый формат channels=both больше не задаёт членство — грузятся все 4 канала loaded', function () {
|
|
|
|
|
|
// Фаза 2: channels/ch_* в payload на членство не влияют. Заезд грузит ВСЕ 4
|
|
|
|
|
|
// канала в loaded вне зависимости от подсказки channels.
|
2026-07-19 19:33:58 +03:00
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['channels' => 'both']),
|
|
|
|
|
|
['X-Sales-Token' => 'test-token']
|
|
|
|
|
|
)->assertOk();
|
|
|
|
|
|
|
2026-07-20 08:31:50 +03:00
|
|
|
|
$firm = SalesAdAudienceFirm::where('firm_inn', '7700000001')->firstOrFail();
|
|
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
expect($firm->firmChannels()->where('status', 'loaded')->count())->toBe(4);
|
2026-07-20 08:31:50 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
it('без указания площадки заезд всё равно грузит все 4 канала loaded', function () {
|
2026-07-20 08:31:50 +03:00
|
|
|
|
$payload = warmupPayload();
|
|
|
|
|
|
unset($payload['firms'][0]['channels']);
|
|
|
|
|
|
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', $payload, ['X-Sales-Token' => 'test-token'])
|
|
|
|
|
|
->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
$firm = SalesAdAudienceFirm::where('firm_inn', '7700000001')->firstOrFail();
|
|
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
expect($firm->firmChannels()->where('status', 'loaded')->count())->toBe(4);
|
2026-07-19 19:33:58 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
it('ch_yandex/ch_vk/ch_mts в payload больше не влияют на членство — все 4 канала loaded', function () {
|
2026-07-19 19:33:58 +03:00
|
|
|
|
$payload = warmupPayload();
|
|
|
|
|
|
unset($payload['firms'][0]['channels']);
|
2026-07-20 08:31:50 +03:00
|
|
|
|
$payload['firms'][0]['ch_yandex'] = false;
|
|
|
|
|
|
$payload['firms'][0]['ch_vk'] = true;
|
|
|
|
|
|
$payload['firms'][0]['ch_mts'] = true;
|
2026-07-19 19:33:58 +03:00
|
|
|
|
|
|
|
|
|
|
$this->postJson('/api/sales/integration/ad-audience', $payload, ['X-Sales-Token' => 'test-token'])
|
|
|
|
|
|
->assertOk();
|
|
|
|
|
|
|
2026-07-20 08:31:50 +03:00
|
|
|
|
$firm = SalesAdAudienceFirm::where('firm_inn', '7700000001')->firstOrFail();
|
|
|
|
|
|
|
2026-07-22 16:39:45 +03:00
|
|
|
|
expect($firm->firmChannels()->where('status', 'loaded')->count())->toBe(4);
|
2026-07-19 19:33:58 +03:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('отвергает выдуманную площадку', function () {
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['channels' => 'telegram']),
|
|
|
|
|
|
['X-Sales-Token' => 'test-token']
|
|
|
|
|
|
)->assertStatus(422);
|
|
|
|
|
|
});
|
2026-07-21 06:35:43 +03:00
|
|
|
|
|
|
|
|
|
|
// ── Task 11: оператор номера доезжает из «Поиска клиентов» ───────────────────
|
|
|
|
|
|
//
|
|
|
|
|
|
// 🔴 Эти тесты идут ЧЕРЕЗ HTTP намеренно. Проверка формата номера живёт в
|
|
|
|
|
|
// SalesIntegrationController, а не в AdAudienceIntake, поэтому тест, дёргающий
|
|
|
|
|
|
// сервис напрямую, дыру в валидации НЕ УВИДИТ: он будет зелёным, пока живой путь
|
|
|
|
|
|
// отдаёт 422 и не пишет ни строки. Ровно так эта дыра и пряталась.
|
|
|
|
|
|
|
|
|
|
|
|
it('принимает номера со словарём через HTTP и сохраняет оператора', function () {
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => [
|
|
|
|
|
|
['phone' => '79990000021', 'operator' => 'ВымпелКом', 'phone_type' => 'mobile'],
|
|
|
|
|
|
]]),
|
|
|
|
|
|
['X-Sales-Token' => 'test-token']
|
|
|
|
|
|
)->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
$row = SalesAdAudiencePhone::where('phone', '79990000021')->first();
|
|
|
|
|
|
|
|
|
|
|
|
expect($row)->not->toBeNull()
|
|
|
|
|
|
->and($row->operator)->toBe('ВымпелКом')
|
|
|
|
|
|
->and($row->phone_type)->toBe('mobile');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('по-прежнему принимает старый формат — голые строки через HTTP', function () {
|
|
|
|
|
|
// Python-служба переезжает не мгновенно: пока часть посылок идёт старым
|
|
|
|
|
|
// форматом, приём обязан их брать, а не отбивать 422.
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => ['79990000022']]),
|
|
|
|
|
|
['X-Sales-Token' => 'test-token']
|
|
|
|
|
|
)->assertOk();
|
|
|
|
|
|
|
|
|
|
|
|
$row = SalesAdAudiencePhone::where('phone', '79990000022')->first();
|
|
|
|
|
|
|
|
|
|
|
|
expect($row)->not->toBeNull()
|
|
|
|
|
|
->and($row->operator)->toBeNull();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('отвергает мусор вместо номера в обоих форматах', function () {
|
|
|
|
|
|
$headers = ['X-Sales-Token' => 'test-token'];
|
|
|
|
|
|
|
|
|
|
|
|
// старый формат — голая строка не по формату Яндекса
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => ['не-телефон']]),
|
|
|
|
|
|
$headers
|
|
|
|
|
|
)->assertStatus(422);
|
|
|
|
|
|
|
|
|
|
|
|
// новый формат — строгость к САМОМУ номеру теряться не должна оттого,
|
|
|
|
|
|
// что он приехал завёрнутым в словарь
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => [['phone' => 'не-телефон', 'operator' => 'ВымпелКом']]]),
|
|
|
|
|
|
$headers
|
|
|
|
|
|
)->assertStatus(422);
|
|
|
|
|
|
|
|
|
|
|
|
// словарь вообще без номера
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => [['operator' => 'ВымпелКом']]]),
|
|
|
|
|
|
$headers
|
|
|
|
|
|
)->assertStatus(422);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('отвергает оператора длиннее колонки в базе', function () {
|
|
|
|
|
|
// operator VARCHAR(30) / phone_type VARCHAR(12): без ограничения в проверке
|
|
|
|
|
|
// длинное значение упало бы уже на записи в БД, а не на входе.
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => [
|
|
|
|
|
|
['phone' => '79990000023', 'operator' => str_repeat('я', 31)],
|
|
|
|
|
|
]]),
|
|
|
|
|
|
['X-Sales-Token' => 'test-token']
|
|
|
|
|
|
)->assertStatus(422);
|
|
|
|
|
|
|
|
|
|
|
|
$this->postJson(
|
|
|
|
|
|
'/api/sales/integration/ad-audience',
|
|
|
|
|
|
warmupPayload(['phones' => [
|
|
|
|
|
|
['phone' => '79990000023', 'phone_type' => str_repeat('m', 13)],
|
|
|
|
|
|
]]),
|
|
|
|
|
|
['X-Sales-Token' => 'test-token']
|
|
|
|
|
|
)->assertStatus(422);
|
|
|
|
|
|
});
|