3917fb1344
Задача 8e. SaaS-оператор видит кампании в статусе queued по всем тенантам и вписывает номер оформленного в конструкторе Яндекса адаптивного креатива yandex_creative_id, после чего кампанию можно запускать. Два эндпоинта AdminAdvertisingController campaignsAwaiting и setCampaignCreative под saas-admin+admin-db, карточка в AdminAdvertisingView, api-функции и типы. 🔴 Запись строго через сырой DB::table update только колонки yandex_creative_id Eloquent-билдер добавил бы updated_at, а колоночный GRANT у crm_admin_user разрешает писать лишь эту колонку тесты идут под суперюзером и это не ловят. RLS не трогаем ad_campaigns уже покрыт srv_bypass и грантами. Бэкенд AdminAd 19/19, реклама-модуль 195/195, фронт админки 11/11. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
119 lines
4.6 KiB
PHP
119 lines
4.6 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Models\AdCampaign;
|
||
use App\Models\Tenant;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
|
||
uses(DatabaseTransactions::class);
|
||
|
||
/**
|
||
* 8e: SaaS-admin вписывает номер оформленного в Яндекс.Директе адаптивного креатива
|
||
* кампаниям в статусе queued, после чего кампанию можно запускать (CampaignLauncher).
|
||
*/
|
||
test('GET /api/admin/advertising/campaigns возвращает только queued-кампании', function () {
|
||
$tenant = Tenant::factory()->create(['organization_name' => 'Окна Москва']);
|
||
|
||
$queued = AdCampaign::create([
|
||
'tenant_id' => $tenant->id,
|
||
'name' => 'Кампания в очереди',
|
||
'status' => AdCampaign::STATUS_QUEUED,
|
||
'audience_days' => 10,
|
||
'budget_rub' => '500.00',
|
||
'landing_url' => 'https://example.ru/landing',
|
||
'estimated_impressions' => 12000,
|
||
]);
|
||
|
||
AdCampaign::create([
|
||
'tenant_id' => $tenant->id,
|
||
'name' => 'Черновик',
|
||
'status' => AdCampaign::STATUS_DRAFT,
|
||
'audience_days' => 10,
|
||
'budget_rub' => '500.00',
|
||
]);
|
||
|
||
$r = $this->getJson('/api/admin/advertising/campaigns');
|
||
$r->assertStatus(200);
|
||
|
||
$data = $r->json('data');
|
||
expect($data)->toHaveCount(1);
|
||
expect($data[0]['id'])->toBe($queued->id);
|
||
expect($data[0]['tenant_id'])->toBe($tenant->id);
|
||
expect($data[0]['tenant_name'])->toBe('Окна Москва');
|
||
expect($data[0]['name'])->toBe('Кампания в очереди');
|
||
expect($data[0]['status'])->toBe(AdCampaign::STATUS_QUEUED);
|
||
expect($data[0]['yandex_creative_id'])->toBeNull();
|
||
expect($data[0]['landing_url'])->toBe('https://example.ru/landing');
|
||
expect($data[0]['estimated_impressions'])->toBe(12000);
|
||
});
|
||
|
||
test('GET /api/admin/advertising/campaigns сортирует по id desc', function () {
|
||
$tenant = Tenant::factory()->create();
|
||
|
||
$first = AdCampaign::create([
|
||
'tenant_id' => $tenant->id, 'name' => 'A', 'status' => AdCampaign::STATUS_QUEUED,
|
||
'audience_days' => 10, 'budget_rub' => '500.00',
|
||
]);
|
||
$second = AdCampaign::create([
|
||
'tenant_id' => $tenant->id, 'name' => 'B', 'status' => AdCampaign::STATUS_QUEUED,
|
||
'audience_days' => 10, 'budget_rub' => '500.00',
|
||
]);
|
||
|
||
$r = $this->getJson('/api/admin/advertising/campaigns');
|
||
|
||
expect($r->json('data.0.id'))->toBe($second->id);
|
||
expect($r->json('data.1.id'))->toBe($first->id);
|
||
});
|
||
|
||
test('PATCH .../creative с валидным номером сохраняет yandex_creative_id', function () {
|
||
$tenant = Tenant::factory()->create();
|
||
$campaign = AdCampaign::create([
|
||
'tenant_id' => $tenant->id, 'name' => 'X', 'status' => AdCampaign::STATUS_QUEUED,
|
||
'audience_days' => 10, 'budget_rub' => '500.00',
|
||
]);
|
||
|
||
$r = $this->patchJson("/api/admin/advertising/campaigns/{$campaign->id}/creative", [
|
||
'yandex_creative_id' => 987654321,
|
||
]);
|
||
|
||
$r->assertStatus(200);
|
||
expect($r->json('id'))->toBe($campaign->id);
|
||
expect($r->json('yandex_creative_id'))->toBe(987654321);
|
||
|
||
expect(AdCampaign::find($campaign->id)->yandex_creative_id)->toBe(987654321);
|
||
});
|
||
|
||
test('PATCH .../creative с невалидным номером (0/строка/пусто) → 422', function () {
|
||
$tenant = Tenant::factory()->create();
|
||
$campaign = AdCampaign::create([
|
||
'tenant_id' => $tenant->id, 'name' => 'X', 'status' => AdCampaign::STATUS_QUEUED,
|
||
'audience_days' => 10, 'budget_rub' => '500.00',
|
||
]);
|
||
|
||
$this->patchJson("/api/admin/advertising/campaigns/{$campaign->id}/creative", ['yandex_creative_id' => 0])
|
||
->assertStatus(422);
|
||
|
||
$this->patchJson("/api/admin/advertising/campaigns/{$campaign->id}/creative", ['yandex_creative_id' => 'abc'])
|
||
->assertStatus(422);
|
||
|
||
$this->patchJson("/api/admin/advertising/campaigns/{$campaign->id}/creative", [])
|
||
->assertStatus(422);
|
||
});
|
||
|
||
test('PATCH .../creative на несуществующий id → 404', function () {
|
||
$r = $this->patchJson('/api/admin/advertising/campaigns/999999999/creative', [
|
||
'yandex_creative_id' => 123,
|
||
]);
|
||
|
||
$r->assertStatus(404);
|
||
expect($r->json('message'))->toBe('Кампания не найдена.');
|
||
});
|
||
|
||
test('GET /api/admin/advertising/campaigns — гейт: включён + нет REMOTE_USER → 403', function () {
|
||
config(['admin.basic_auth_gate' => true]);
|
||
config(['admin.basic_auth_allowlist' => ['admin']]);
|
||
|
||
$this->getJson('/api/admin/advertising/campaigns')->assertStatus(403);
|
||
});
|