Files
portal/app/tests/Feature/Sales/AdAudiencePlatformApiTest.php
T

170 lines
7.4 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\SalesAdAudienceFirm;
use App\Models\SalesAdAudiencePlatform;
use App\Models\SalesAdAudienceState;
use App\Models\SalesUser;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\Concerns\SharesSupplierPdo;
/**
* API «Реклама на кандидатов» по площадкам (Task 4+5 плана трёх площадок).
*
* Одна общая ручка /api/sales/ad-audience разъезжается на три:
* /api/sales/warming/{yandex|vk|mts}. Настройки и статус — построчно
* в sales_ad_audience_platforms; days/сроки воронки пока общие
* (sales_ad_audience_state). Членство фирмы в площадке — булевы
* ch_yandex/ch_vk/ch_mts на sales_ad_audience_firms.
*
* Аутентификация — как в AdAudienceScreenTest: SalesUser::create() (у SalesUser
* нет HasFactory/factory()) + $this->actingAs(..., 'sales'). Паттерн $this->
* (не test()->) — чтобы larastan понимал тип TestCase.
*/
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
function warmingHead(): SalesUser
{
return SalesUser::create([
'name' => 'Начальник '.uniqid(),
'email' => 'warminghead'.uniqid().'@sales.local',
'password' => bcrypt('secret'),
'role' => 'head',
'is_active' => true,
]);
}
function warmingManager(): SalesUser
{
return SalesUser::create([
'name' => 'Менеджер '.uniqid(),
'email' => 'warmingmgr'.uniqid().'@sales.local',
'password' => bcrypt('secret'),
'role' => 'manager',
'is_active' => true,
]);
}
it('отдаёт настройки именно запрошенной площадки', function () {
SalesAdAudiencePlatform::forCode('vk')->update(['status' => 'waiting_volume']);
$this->actingAs(warmingHead(), 'sales')
->getJson('/api/sales/warming/vk')
->assertOk()
->assertJsonPath('data.platform', 'vk')
->assertJsonPath('data.min_phones', 2000)
->assertJsonPath('data.status', 'waiting_volume');
});
it('неизвестную площадку отвергает', function () {
$this->actingAs(warmingHead(), 'sales')
->getJson('/api/sales/warming/facebook')
->assertNotFound();
});
it('рубильник пишется в строку площадки', function () {
$this->actingAs(warmingHead(), 'sales')
->patchJson('/api/sales/warming/vk', ['enabled' => true])
->assertOk();
expect(SalesAdAudiencePlatform::forCode('vk')->enabled)->toBeTrue();
});
it('менеджеру доступ закрыт', function () {
$this->actingAs(warmingManager(), 'sales')
->getJson('/api/sales/warming/yandex')
->assertForbidden();
});
it('на вкладке площадки видны только фирмы со строкой этого канала', function () {
// Task C1: firms(vk) отдаёт только фирмы со строкой firm_channels(vk),
// а не весь синхронизированный состав. Фирма без vk-строки на вкладку не попадает.
$vkFirm = SalesAdAudienceFirm::create(['firm_name' => 'Только ВК', 'ch_vk' => true]);
$vkFirm->firmChannels()->create([
'channel' => 'vk', 'status' => 'loaded', 'mode' => 'funnel',
'warming_started_at' => now(), 'warmed_times' => 0,
]);
$yandexFirm = SalesAdAudienceFirm::create(['firm_name' => 'Только Яндекс', 'ch_yandex' => true]);
$yandexFirm->firmChannels()->create([
'channel' => 'yandex', 'status' => 'loaded', 'mode' => 'funnel',
'warming_started_at' => now(), 'warmed_times' => 0,
]);
$resp = $this->actingAs(warmingHead(), 'sales')
->getJson('/api/sales/warming/vk/firms')
->assertOk()
->assertJsonCount(1, 'firms');
expect($resp->json('firms.0.firm_name'))->toBe('Только ВК')
->and($resp->json('firms.0.ch_vk'))->toBeTrue();
});
it('переключатель добавляет и убирает фирму с площадки', function () {
// Task 8: членство площадки теперь — строка firm_channels(vk), а не флаг ch_vk.
$firm = SalesAdAudienceFirm::create(['firm_name' => 'Плавающая', 'warmup_started_at' => now()->subDay()]);
$head = warmingHead();
$this->actingAs($head, 'sales')
->postJson("/api/sales/warming/vk/firms/{$firm->id}/toggle", ['on' => true])
->assertOk();
expect($firm->firmChannels()->where('channel', 'vk')->where('status', 'warming')->count())->toBe(1);
$this->actingAs($head, 'sales')
->postJson("/api/sales/warming/vk/firms/{$firm->id}/toggle", ['on' => false])
->assertOk();
expect($firm->firmChannels()->where('channel', 'vk')->count())->toBe(0);
});
it('отдаёт ВСЕ фирмы прогрева, без фильтра площадки (для модуля СМС)', function () {
SalesAdAudienceFirm::create(['firm_name' => 'Только Яндекс', 'ch_yandex' => true]);
SalesAdAudienceFirm::create(['firm_name' => 'Только ВК', 'ch_vk' => true]);
SalesAdAudienceFirm::create(['firm_name' => 'Нигде', 'ch_yandex' => false, 'ch_vk' => false, 'ch_mts' => false]);
$this->actingAs(warmingHead(), 'sales')
->getJson('/api/sales/ad-audience/firms')
->assertOk()
->assertJsonCount(3, 'firms');
});
it('список всех фирм закрыт для менеджера', function () {
$this->actingAs(warmingManager(), 'sales')
->getJson('/api/sales/ad-audience/firms')
->assertForbidden();
});
it('PATCH сроков пишет в строку площадки, не в state и не в другие площадки', function () {
$head = warmingHead();
$this->actingAs($head, 'sales')
->patchJson('/api/sales/warming/yandex', ['in_work_days' => 20])
->assertOk()
->assertJsonPath('data.durations.in_work_days', 20);
expect(SalesAdAudiencePlatform::forCode('yandex')->in_work_days)->toBe(20)
->and(SalesAdAudiencePlatform::forCode('vk')->in_work_days)->toBe(14) // default, не тронут
->and(SalesAdAudiencePlatform::forCode('mts')->in_work_days)->toBe(14)
->and(SalesAdAudienceState::current()->in_work_days)->toBe(14); // state не тронут
});
it('сроки площадок независимы: yandex=20, vk=5', function () {
$head = warmingHead();
$this->actingAs($head, 'sales')->patchJson('/api/sales/warming/yandex', ['in_work_days' => 20])->assertOk();
$this->actingAs($head, 'sales')->patchJson('/api/sales/warming/vk', ['in_work_days' => 5])->assertOk();
$this->actingAs($head, 'sales')->getJson('/api/sales/warming/yandex')->assertJsonPath('data.durations.in_work_days', 20);
$this->actingAs($head, 'sales')->getJson('/api/sales/warming/vk')->assertJsonPath('data.durations.in_work_days', 5);
expect(SalesAdAudiencePlatform::forCode('yandex')->in_work_days)->toBe(20);
});
it('PATCH days пишется в строку площадки', function () {
$head = warmingHead();
$this->actingAs($head, 'sales')
->patchJson('/api/sales/warming/mts', ['days' => 45])
->assertOk()
->assertJsonPath('data.days', 45);
expect(SalesAdAudiencePlatform::forCode('mts')->days)->toBe(45);
});