environment(['local', 'testing'])) { $this->error('This fixture command is limited to local and testing environments.'); return self::FAILURE; } $fixture = config('tenantpilot.backup_health.browser_smoke_fixture'); if (! is_array($fixture)) { $this->error('The backup-health browser smoke fixture is not configured.'); return self::FAILURE; } $workspaceConfig = is_array($fixture['workspace'] ?? null) ? $fixture['workspace'] : []; $userConfig = is_array($fixture['user'] ?? null) ? $fixture['user'] : []; $scenarioConfig = is_array($fixture['blocked_drillthrough'] ?? null) ? $fixture['blocked_drillthrough'] : []; $tenantRouteKey = (string) ($scenarioConfig['tenant_id'] ?? $scenarioConfig['tenant_external_id'] ?? '18000000-0000-4000-8000-000000000180'); $workspace = Workspace::query()->updateOrCreate( ['slug' => (string) ($workspaceConfig['slug'] ?? 'spec-180-backup-health-smoke')], ['name' => (string) ($workspaceConfig['name'] ?? 'Spec 180 Backup Health Smoke')], ); $password = (string) ($userConfig['password'] ?? 'password'); $user = User::query()->updateOrCreate( ['email' => (string) ($userConfig['email'] ?? 'smoke-requester+180@tenantpilot.local')], [ 'name' => (string) ($userConfig['name'] ?? 'Spec 180 Requester'), 'password' => Hash::make($password), 'email_verified_at' => now(), ], ); $tenant = Tenant::query()->updateOrCreate( ['external_id' => $tenantRouteKey], [ 'workspace_id' => (int) $workspace->getKey(), 'name' => (string) ($scenarioConfig['tenant_name'] ?? 'Spec 180 Blocked Backup Tenant'), 'tenant_id' => $tenantRouteKey, 'app_client_id' => (string) ($scenarioConfig['app_client_id'] ?? '18000000-0000-4000-8000-000000000182'), 'app_client_secret' => null, 'app_certificate_thumbprint' => null, 'app_status' => 'ok', 'app_notes' => null, 'status' => Tenant::STATUS_ACTIVE, 'environment' => 'dev', 'is_current' => false, 'metadata' => ['fixture' => 'spec-180-browser-smoke'], 'rbac_status' => 'ok', 'rbac_last_checked_at' => now(), ], ); WorkspaceMembership::query()->updateOrCreate( ['workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey()], ['role' => 'owner'], ); TenantMembership::query()->updateOrCreate( ['tenant_id' => (int) $tenant->getKey(), 'user_id' => (int) $user->getKey()], ['role' => 'owner', 'source' => 'manual', 'source_ref' => 'spec-180-browser-smoke'], ); if (Schema::hasColumn('users', 'last_workspace_id')) { $user->forceFill(['last_workspace_id' => (int) $workspace->getKey()])->save(); } if (Schema::hasTable('user_tenant_preferences')) { UserTenantPreference::query()->updateOrCreate( ['user_id' => (int) $user->getKey(), 'tenant_id' => (int) $tenant->getKey()], ['last_used_at' => now()], ); } $policy = Policy::query()->updateOrCreate( [ 'tenant_id' => (int) $tenant->getKey(), 'external_id' => (string) ($scenarioConfig['policy_external_id'] ?? 'spec-180-rbac-stale-policy'), 'policy_type' => (string) ($scenarioConfig['policy_type'] ?? 'settingsCatalogPolicy'), ], [ 'display_name' => (string) ($scenarioConfig['policy_name'] ?? 'Spec 180 RBAC Smoke Policy'), 'platform' => 'windows', 'last_synced_at' => now(), 'metadata' => ['fixture' => 'spec-180-browser-smoke'], ], ); $backupSet = BackupSet::withTrashed()->firstOrNew([ 'tenant_id' => (int) $tenant->getKey(), 'name' => (string) ($scenarioConfig['backup_set_name'] ?? 'Spec 180 Blocked Stale Backup'), ]); $backupSet->forceFill([ 'created_by' => (string) $user->email, 'status' => 'completed', 'item_count' => 1, 'completed_at' => now()->subHours(max(25, (int) ($scenarioConfig['stale_age_hours'] ?? 48))), 'metadata' => ['fixture' => 'spec-180-browser-smoke'], 'deleted_at' => null, ])->save(); if (method_exists($backupSet, 'trashed') && $backupSet->trashed()) { $backupSet->restore(); } $backupItem = BackupItem::withTrashed()->firstOrNew([ 'backup_set_id' => (int) $backupSet->getKey(), 'policy_identifier' => (string) ($scenarioConfig['policy_external_id'] ?? 'spec-180-rbac-stale-policy'), 'policy_type' => (string) ($scenarioConfig['policy_type'] ?? 'settingsCatalogPolicy'), ]); $backupItem->forceFill([ 'tenant_id' => (int) $tenant->getKey(), 'policy_id' => (int) $policy->getKey(), 'platform' => 'windows', 'captured_at' => $backupSet->completed_at, 'payload' => [ 'id' => (string) ($scenarioConfig['policy_external_id'] ?? 'spec-180-rbac-stale-policy'), 'name' => (string) ($scenarioConfig['policy_name'] ?? 'Spec 180 RBAC Smoke Policy'), ], 'metadata' => [ 'policy_name' => (string) ($scenarioConfig['policy_name'] ?? 'Spec 180 RBAC Smoke Policy'), 'fixture' => 'spec-180-browser-smoke', ], 'assignments' => [], 'deleted_at' => null, ])->save(); if (method_exists($backupItem, 'trashed') && $backupItem->trashed()) { $backupItem->restore(); } if ((bool) $this->option('force-refresh')) { $backupSet->forceFill([ 'completed_at' => now()->subHours(max(25, (int) ($scenarioConfig['stale_age_hours'] ?? 48))), ])->save(); $backupItem->forceFill([ 'captured_at' => $backupSet->completed_at, ])->save(); } $this->table( ['Fixture', 'Value'], [ ['Workspace', (string) $workspace->name], ['User email', (string) $user->email], ['User password', $password], ['Tenant', (string) $tenant->name], ['Tenant external id', (string) $tenant->external_id], ['Dashboard URL', "/admin/t/{$tenant->external_id}"], ['Fixture login URL', route('admin.local.backup-health-browser-fixture-login', absolute: false)], ['Blocked route', "/admin/t/{$tenant->external_id}/backup-sets"], ['Locally denied capability', 'tenant.view'], ], ); $this->info('The dashboard remains visible for this fixture user, while backup drill-through routes stay forbidden via a local/testing-only capability deny seam.'); return self::SUCCESS; } }