f78ab4af3d
Создавать проекты можно всегда; баланс проверяется в момент ЗАПУСКА (создать-и-запустить / запустить / возобновить одиночно и пачкой / автоподбор) под замком на клиента (без гонок). Не хватает — проект остаётся на паузе с меткой preflight_blocked_at, клиенту сообщение в рублях (сколько пополнить). Групповой запуск «сколько влезло». Нет активного тарифа на дату → запуск запрещён (fail-closed). Гейт реквизитов первого проекта добавлен и в автоподбор. - LaunchBalanceGate — единый гейт вместо 3 копий preflight (ProjectController store/update, AutopodborController), под DB::transaction + lockForUpdate(Tenant). - ProjectService::create($launch) + новый setActive(); bulk resume «сколько влезло». - AutopodborProjectCreator: пачка в транзакции через общий ProjectService::create. - Идемпотентность box/phone_type миграций автоподбора (Schema::hasColumn guard). - Тест-инфра: afterRefreshingDatabase восстанавливает месячные партиции. Тесты фичи 40/40 зелёные. Спека и план — docs/superpowers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
4.2 KiB
PHP
70 lines
4.2 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Models\AutopodborCompetitor;
|
||
use App\Models\AutopodborRun;
|
||
use App\Models\AutopodborSource;
|
||
use App\Models\Project;
|
||
use App\Models\SystemSetting;
|
||
use App\Models\Tenant;
|
||
use App\Models\User;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Queue;
|
||
use Tests\Concerns\SharesSupplierPdo;
|
||
|
||
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
|
||
beforeEach(fn () => Queue::fake());
|
||
|
||
it('GET /api/autopodbor/state — доступность, прогоны, цены', function () {
|
||
$tenant = Tenant::factory()->create();
|
||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||
SystemSetting::updateOrCreate(['key' => 'autopodbor_enabled'], ['value' => '1', 'type' => 'bool']);
|
||
SystemSetting::updateOrCreate(['key' => 'autopodbor_price_search_rub'], ['value' => '500', 'type' => 'decimal']);
|
||
SystemSetting::updateOrCreate(['key' => 'autopodbor_price_study_rub'], ['value' => '300', 'type' => 'decimal']);
|
||
|
||
$this->actingAs($user)->getJson('/api/autopodbor/state')
|
||
->assertOk()
|
||
->assertJsonStructure(['enabled', 'runs', 'prices' => ['search', 'study']]);
|
||
});
|
||
|
||
it('POST /api/autopodbor/search — стартует прогон (201)', function () {
|
||
$tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);
|
||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||
SystemSetting::updateOrCreate(['key' => 'autopodbor_price_search_rub'], ['value' => '1', 'type' => 'decimal']);
|
||
|
||
$this->actingAs($user)->postJson('/api/autopodbor/search', [
|
||
'region_code' => 16, 'examples' => ['okna.ru'], 'about_self' => [], 'include_federal' => true,
|
||
])->assertCreated()->assertJsonPath('data.kind', 'search');
|
||
});
|
||
|
||
it('GET /api/autopodbor/competitors/{id} — источники с existing_project_id', function () {
|
||
$tenant = Tenant::factory()->create();
|
||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||
DB::statement('SET app.current_tenant_id = '.$tenant->id);
|
||
$run = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'search', 'status' => 'done', 'region_code' => 16, 'params' => []]);
|
||
$comp = AutopodborCompetitor::create(['tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'name' => 'Окна Комфорт', 'dedup_key' => 'okna']);
|
||
AutopodborSource::create(['tenant_id' => $tenant->id, 'competitor_id' => $comp->id, 'study_run_id' => $run->id, 'signal_type' => 'site', 'identifier' => 'okna-komfort.ru', 'dedup_key' => 'site:okna-komfort.ru']);
|
||
|
||
$this->actingAs($user)->getJson("/api/autopodbor/competitors/{$comp->id}")
|
||
->assertOk()
|
||
->assertJsonStructure(['data' => ['id', 'name'], 'sources' => [['id', 'signal_type', 'identifier', 'existing_project_id']]]);
|
||
});
|
||
|
||
it('POST /api/autopodbor/projects — создаёт проекты из источников (201)', function () {
|
||
// withRequisites: гейт реквизитов первого проекта (паритет с ProjectController@store,
|
||
// добавлен для автоподбора в launch-gate-balance Task 11) иначе вернёт 422.
|
||
$tenant = Tenant::factory()->withRequisites()->create(['balance_rub' => '500000.00']);
|
||
$user = User::factory()->create(['tenant_id' => $tenant->id]);
|
||
DB::statement('SET app.current_tenant_id = '.$tenant->id);
|
||
$run = AutopodborRun::create(['tenant_id' => $tenant->id, 'kind' => 'study', 'status' => 'done', 'region_code' => 16, 'params' => []]);
|
||
$comp = AutopodborCompetitor::create(['tenant_id' => $tenant->id, 'search_run_id' => $run->id, 'name' => 'Окна Комфорт', 'dedup_key' => 'okna']);
|
||
$s1 = AutopodborSource::create(['tenant_id' => $tenant->id, 'competitor_id' => $comp->id, 'study_run_id' => $run->id, 'signal_type' => 'site', 'identifier' => 'okna-komfort.ru', 'dedup_key' => 'site:okna-komfort.ru']);
|
||
|
||
$this->actingAs($user)->postJson('/api/autopodbor/projects', [
|
||
'source_ids' => [$s1->id], 'regions' => [16], 'daily_limit_target' => 20, 'delivery_days_mask' => 127, 'launch' => false,
|
||
])->assertCreated();
|
||
expect(Project::where('tenant_id', $tenant->id)->where('signal_identifier', 'okna-komfort.ru')->exists())->toBeTrue();
|
||
});
|