2026-07-13 17:08:21 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
2026-07-13 19:48:39 +03:00
|
|
|
|
use App\Models\AutopodborCompetitor;
|
|
|
|
|
|
use App\Models\AutopodborSource;
|
2026-07-13 17:08:21 +03:00
|
|
|
|
use App\Models\Deal;
|
|
|
|
|
|
use App\Models\Project;
|
|
|
|
|
|
use App\Models\Tenant;
|
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
|
|
|
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
|
|
|
|
|
|
|
|
it('заводит демо-клиента с красивыми данными и выдуманными телефонами', function () {
|
|
|
|
|
|
$this->artisan('demo:showcase')->assertExitCode(0);
|
|
|
|
|
|
|
|
|
|
|
|
$tenant = Tenant::query()->where('organization_name', 'Стоматология Улыбка')->first();
|
|
|
|
|
|
expect($tenant)->not->toBeNull();
|
|
|
|
|
|
|
|
|
|
|
|
expect(User::query()->where('tenant_id', $tenant->id)->where('email', 'demo@liderra.ru')->exists())->toBeTrue();
|
|
|
|
|
|
expect(Project::query()->where('tenant_id', $tenant->id)->count())->toBe(3);
|
|
|
|
|
|
expect(Deal::query()->where('tenant_id', $tenant->id)->count())->toBe(25);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (Deal::query()->where('tenant_id', $tenant->id)->pluck('phone') as $phone) {
|
|
|
|
|
|
expect($phone)->toStartWith('7999000'); // 🔴 ПДн: несуществующий диапазон
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-13 19:48:39 +03:00
|
|
|
|
it('заводит конкурентное поле стоматологии — конкуренты, источники, дубль и три пометки номеров', function () {
|
|
|
|
|
|
$this->artisan('demo:showcase')->assertExitCode(0);
|
|
|
|
|
|
|
|
|
|
|
|
$tenant = Tenant::query()->where('organization_name', 'Стоматология Улыбка')->first();
|
|
|
|
|
|
expect($tenant)->not->toBeNull();
|
|
|
|
|
|
|
|
|
|
|
|
$competitors = AutopodborCompetitor::query()->where('tenant_id', $tenant->id)->get();
|
|
|
|
|
|
expect($competitors->count())->toBe(8); // 6 в поле (в т.ч. дубль) + 2 в предложениях
|
|
|
|
|
|
|
|
|
|
|
|
$field = $competitors->where('box', 'field');
|
|
|
|
|
|
$proposals = $competitors->where('box', 'proposal');
|
|
|
|
|
|
expect($field->count())->toBe(6)
|
|
|
|
|
|
->and($proposals->count())->toBe(2);
|
|
|
|
|
|
|
|
|
|
|
|
// Все имена — выдуманные стоматологии, ни одна не про займы/авто (старый посторонний демо-тенант).
|
|
|
|
|
|
foreach ($competitors as $c) {
|
|
|
|
|
|
expect($c->name)->not->toContain('Ломбард')
|
|
|
|
|
|
->and($c->name)->not->toContain('Авто')
|
|
|
|
|
|
->and($c->site_url)->not->toBeNull()
|
|
|
|
|
|
->and($c->site_url)->toEndWith('-demo.ru');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Настоящий дубль: два конкурента с одинаковым site_url («Белый Зуб» / «Белый Зуб (доп. филиал)»).
|
|
|
|
|
|
$sameSite = $field->where('site_url', 'belyi-zub-demo.ru');
|
|
|
|
|
|
expect($sameSite->count())->toBe(2);
|
|
|
|
|
|
|
|
|
|
|
|
// Три штатные пометки у найденных номеров: настоящий / подменный / требует проверки.
|
|
|
|
|
|
$sources = AutopodborSource::query()
|
|
|
|
|
|
->whereIn('competitor_id', $field->pluck('id'))
|
|
|
|
|
|
->where('signal_type', 'call')
|
|
|
|
|
|
->get();
|
|
|
|
|
|
expect($sources->pluck('phone_kind')->unique()->sort()->values()->all())
|
|
|
|
|
|
->toBe(['real', 'substitute', 'uncertain']);
|
|
|
|
|
|
|
|
|
|
|
|
// Телефоны источников — только выдуманные (+7 999 000-XX-XX), ПДн исключены.
|
|
|
|
|
|
foreach ($sources as $s) {
|
|
|
|
|
|
expect($s->identifier)->toStartWith('7999000');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-14 05:01:04 +03:00
|
|
|
|
it('главный проект выглядит живым: sync_status агрегируется в ok через демо-привязку к поставщику', function () {
|
|
|
|
|
|
$this->artisan('demo:showcase')->assertExitCode(0);
|
|
|
|
|
|
|
|
|
|
|
|
$tenant = Tenant::query()->where('organization_name', 'Стоматология Улыбка')->first();
|
|
|
|
|
|
|
|
|
|
|
|
$working = Project::query()
|
|
|
|
|
|
->where('tenant_id', $tenant->id)
|
|
|
|
|
|
->where('name', 'Стоматология — Москва')
|
|
|
|
|
|
->with(['supplierB1', 'supplierB2', 'supplierB3'])
|
|
|
|
|
|
->firstOrFail();
|
|
|
|
|
|
|
|
|
|
|
|
// Поле, которое реально управляет плашкой на карточке (ProjectCard.vue syncStatusLabel):
|
|
|
|
|
|
// aggregateSyncStatus() читает sync_status у связанных supplier_projects (B1/B2/B3).
|
|
|
|
|
|
expect($working->supplier_b1_project_id)->not->toBeNull()
|
|
|
|
|
|
->and($working->aggregateSyncStatus())->toBe('ok')
|
|
|
|
|
|
->and($working->supplierB1->sync_status)->toBe('ok');
|
|
|
|
|
|
|
|
|
|
|
|
// Данные поставщика в демо — явно выдуманные, не настоящий домен/id.
|
|
|
|
|
|
expect($working->supplierB1->unique_key)->toContain('demo')
|
|
|
|
|
|
->and($working->supplierB1->supplier_external_id)->toContain('DEMO');
|
|
|
|
|
|
|
|
|
|
|
|
// Второй и третий проекты остаются честными (не «ok») — пауза и блок по балансу.
|
|
|
|
|
|
$paused = Project::query()->where('tenant_id', $tenant->id)->where('name', 'Импланты — Санкт-Петербург')->firstOrFail();
|
|
|
|
|
|
expect($paused->is_active)->toBeFalse()
|
|
|
|
|
|
->and($paused->aggregateSyncStatus())->not->toBe('ok');
|
|
|
|
|
|
|
|
|
|
|
|
$blocked = Project::query()->where('tenant_id', $tenant->id)->where('name', 'Отбеливание — Казань')->firstOrFail();
|
|
|
|
|
|
expect($blocked->preflight_blocked_at)->not->toBeNull()
|
|
|
|
|
|
->and($blocked->aggregateSyncStatus())->not->toBe('ok');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-13 17:08:21 +03:00
|
|
|
|
it('не запускается на боевом окружении', function () {
|
|
|
|
|
|
config(['app.env' => 'production']);
|
|
|
|
|
|
|
|
|
|
|
|
$this->artisan('demo:showcase')->assertExitCode(1);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('запускается дважды без ошибок (данные не дублируются)', function () {
|
|
|
|
|
|
$this->artisan('demo:showcase')->assertExitCode(0);
|
|
|
|
|
|
$this->artisan('demo:showcase')->assertExitCode(0);
|
|
|
|
|
|
|
|
|
|
|
|
$tenant = Tenant::query()->where('organization_name', 'Стоматология Улыбка')->first();
|
|
|
|
|
|
expect(Project::query()->where('tenant_id', $tenant->id)->count())->toBe(3)
|
2026-07-13 19:48:39 +03:00
|
|
|
|
->and(Deal::query()->where('tenant_id', $tenant->id)->count())->toBe(25)
|
|
|
|
|
|
->and(AutopodborCompetitor::query()->where('tenant_id', $tenant->id)->count())->toBe(8);
|
2026-07-13 17:08:21 +03:00
|
|
|
|
});
|