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'); // 🔴 ПДн: несуществующий диапазон } }); 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'); } }); 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'); }); 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) ->and(Deal::query()->where('tenant_id', $tenant->id)->count())->toBe(25) ->and(AutopodborCompetitor::query()->where('tenant_id', $tenant->id)->count())->toBe(8); });