From a6a3b1d26b438d0f2db7c8d416112205d36cb4ed Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:06:34 +0200 Subject: [PATCH] revert: undo the app changes from the test clean-up Revert the app code changes of d666a0a95, 69eeb2f62, 1e66c9f9b and a32082b03, so login, registration, OAuth, team danger zone, service templates, and the other touched files are back to their earlier state. Keep the removal of unused components and markup-only tests (d666a0a95 also contained those deletions by mistake). StartupExecutionCleanupTest now fakes the template CDN with the current file content, so app:init in the test no longer empties templates/service-templates-latest.json. Co-Authored-By: Claude Opus 5.5 --- app/Actions/Fortify/CreateNewUser.php | 2 +- .../Controllers/Api/ServersController.php | 2 +- app/Http/Controllers/Webhook/Gitlab.php | 7 +-- app/Http/Middleware/TrustHosts.php | 52 ++++++++----------- app/Jobs/PullTemplatesFromCDN.php | 6 +-- app/Livewire/Profile/Index.php | 4 -- .../Shared/EnvironmentVariable/Show.php | 4 +- app/Livewire/Security/ApiTokens.php | 10 +--- app/Livewire/Server/New/ByDigitalOcean.php | 4 +- app/Livewire/Server/New/ByVultr.php | 4 +- app/Livewire/SettingsEmail.php | 2 - app/Livewire/Team/DangerZone.php | 1 - app/Providers/FortifyServiceProvider.php | 8 +-- app/Services/Auth/OauthLoginService.php | 2 +- .../ConfigurationDiffer.php | 4 +- app/Traits/HasSecretManager.php | 5 +- bootstrap/helpers/shared.php | 10 +--- .../components/team/settings-layout.blade.php | 4 +- .../views/livewire/server/navbar.blade.php | 4 +- .../partials/localhost-general.blade.php | 7 --- .../views/livewire/server/show.blade.php | 18 ------- .../livewire/source/github/change.blade.php | 9 +--- .../Authorization/TeamAuthorizationTest.php | 25 ++------- .../Jobs/StartupExecutionCleanupTest.php | 7 ++- .../PullServiceTemplatesFromCdnTest.php | 27 ---------- 25 files changed, 58 insertions(+), 170 deletions(-) diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index f24b9699f2..c69863c572 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -81,7 +81,7 @@ class CreateNewUser implements CreatesNewUsers } } // Set session variable - session(['currentTeam' => $team]); + session(['currentTeam' => $user->currentTeam = $team]); return $user; } diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index f886bef8c8..76e36fe873 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -305,7 +305,7 @@ class ServersController extends Controller if (is_null($teamId)) { return invalidTokenResponse(); } - $server = ModelsServer::whereTeamId($teamId)->whereUuid($request->route('uuid'))->first(); + $server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first(); if (is_null($server)) { return response()->json(['message' => 'Server not found.'], 404); } diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index 62199ba6ec..27138135ef 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -17,6 +17,7 @@ use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; +use Visus\Cuid2\Cuid2; class Gitlab extends Controller { @@ -184,7 +185,7 @@ class Gitlab extends Controller continue; } - $deployment_uuid = new_public_id(); + $deployment_uuid = new Cuid2; $result = queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, @@ -202,7 +203,7 @@ class Gitlab extends Controller 'mode' => 'app', 'application_uuid' => $application->uuid, 'application_name' => $application->name, - 'deployment_uuid' => $deployment_uuid, + 'deployment_uuid' => $deployment_uuid->toString(), 'commit' => $commit, ]); @@ -274,7 +275,7 @@ class Gitlab extends Controller continue; } - $deployment_uuid = new_public_id(); + $deployment_uuid = new Cuid2; $found = ApplicationPreview::where('application_id', $application->id) ->where('pull_request_id', $pull_request_id) ->first(); diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php index a12feccd34..350d5359c1 100644 --- a/app/Http/Middleware/TrustHosts.php +++ b/app/Http/Middleware/TrustHosts.php @@ -28,7 +28,8 @@ class TrustHosts extends Middleware } // Skip host validation if no FQDN is configured (initial setup) - if ($this->configuredFqdnHost() === null) { + $fqdnHost = Cache::get('instance_settings_fqdn_host'); + if ($fqdnHost === '' || $fqdnHost === null) { return $next($request); } @@ -45,7 +46,27 @@ class TrustHosts extends Middleware { $trustedHosts = []; - $fqdnHost = $this->configuredFqdnHost(); + // Trust the configured FQDN from InstanceSettings (cached to avoid DB query on every request) + // Use empty string as sentinel value instead of null so negative results are cached + $fqdnHost = Cache::remember('instance_settings_fqdn_host', 300, function () { + try { + $settings = InstanceSettings::get(); + if ($settings && $settings->fqdn) { + $url = Url::fromString($settings->fqdn); + $host = $url->getHost(); + + return $host ?: ''; + } + } catch (\Exception $e) { + // If instance settings table doesn't exist yet (during installation), + // return empty string (sentinel) so this result is cached + } + + return ''; + }); + + // Convert sentinel value back to null for consumption + $fqdnHost = $fqdnHost !== '' ? $fqdnHost : null; if ($fqdnHost) { $trustedHosts[] = $fqdnHost; @@ -76,31 +97,4 @@ class TrustHosts extends Middleware return array_filter($trustedHosts); } - - /** - * Resolve the configured instance FQDN host, cached to avoid a DB query on every request. - * - * An empty string is cached as the "no FQDN" sentinel so negative results are cached too. - */ - private function configuredFqdnHost(): ?string - { - $fqdnHost = Cache::remember('instance_settings_fqdn_host', 300, function () { - try { - $settings = InstanceSettings::get(); - if ($settings && $settings->fqdn) { - $url = Url::fromString($settings->fqdn); - $host = $url->getHost(); - - return $host ?: ''; - } - } catch (\Exception $e) { - // If instance settings table doesn't exist yet (during installation), - // return empty string (sentinel) so this result is cached - } - - return ''; - }); - - return $fqdnHost !== '' ? $fqdnHost : null; - } } diff --git a/app/Jobs/PullTemplatesFromCDN.php b/app/Jobs/PullTemplatesFromCDN.php index ec09285e1e..e8c655449b 100644 --- a/app/Jobs/PullTemplatesFromCDN.php +++ b/app/Jobs/PullTemplatesFromCDN.php @@ -34,11 +34,7 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue ->get(config('constants.services.official')); if ($response->successful()) { // Shared cache so Cloud HTTP nodes see the same bundle Horizon pulled. - if (! store_service_templates_bundle($response->body())) { - Log::error('PullTemplatesFromCDN received an invalid bundle', [ - 'body' => str($response->body())->limit(500)->toString(), - ]); - } + store_service_templates_bundle($response->body()); } else { Log::error('PullTemplatesFromCDN failed', [ 'status' => $response->status(), diff --git a/app/Livewire/Profile/Index.php b/app/Livewire/Profile/Index.php index a69424cffd..9b1ef42ce2 100644 --- a/app/Livewire/Profile/Index.php +++ b/app/Livewire/Profile/Index.php @@ -2,19 +2,15 @@ namespace App\Livewire\Profile; -use App\Services\AvatarStorageService; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Validation\Rules\Password; use Livewire\Attributes\Validate; use Livewire\Component; -use Livewire\WithFileUploads; class Index extends Component { - use WithFileUploads; - public int $userId; public string $email; diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 694f18c9c9..e47d3818fe 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -17,7 +17,6 @@ use App\Traits\HasSecretManagerAutocomplete; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; -use Illuminate\Validation\ValidationException; use Livewire\Attributes\Computed; use Livewire\Component; @@ -189,6 +188,7 @@ class Show extends Component } private function syncData(bool $toModel = false): void + { if ($toModel) { $this->key = ValidationPatterns::normalizeEnvironmentVariableKey($this->key); @@ -330,8 +330,6 @@ class Show extends Component if ($this->is_required && $this->resource instanceof Service) { event(new ApplicationConfigurationChanged($this->resource->team()->id)); } - } catch (ValidationException $e) { - throw $e; } catch (\Exception $e) { return handleError($e); } diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php index ca98698450..7a530fe297 100644 --- a/app/Livewire/Security/ApiTokens.php +++ b/app/Livewire/Security/ApiTokens.php @@ -15,13 +15,7 @@ class ApiTokens extends Component public ?string $description = null; - /** - * Untyped on purpose: Livewire skips hydrating null into typed properties, which - * would silently revert a "Never" (null) choice back to the default on the next request. - * - * @var int|string|null - */ - public $expiresInDays = 30; + public ?int $expiresInDays = 30; public $tokens = []; @@ -149,7 +143,7 @@ class ApiTokens extends Component throw new \Exception('You do not have permission to create tokens with read:sensitive permissions.'); } - $expiresAt = filled($validated['expiresInDays'] ?? null) ? now()->addDays((int) $validated['expiresInDays']) : null; + $expiresAt = $this->expiresInDays ? now()->addDays($this->expiresInDays) : null; $token = auth()->user()->createToken($this->description, $permissions, $expiresAt); auditLog('ui.api_token.created', [ 'team_id' => currentTeam()->id, diff --git a/app/Livewire/Server/New/ByDigitalOcean.php b/app/Livewire/Server/New/ByDigitalOcean.php index 1cfcc56634..d05532776e 100644 --- a/app/Livewire/Server/New/ByDigitalOcean.php +++ b/app/Livewire/Server/New/ByDigitalOcean.php @@ -296,9 +296,7 @@ class ByDigitalOcean extends Component $details = data_get($e->response->json(), $jsonMessageKey) ?: $e->response->body() ?: $details; } - $prefix = "{$providerName} API error: "; - - return str_starts_with($details, $prefix) ? $details : $prefix.$details; + return "{$providerName} API error: {$details}"; } public function getAvailableSizesProperty(): array diff --git a/app/Livewire/Server/New/ByVultr.php b/app/Livewire/Server/New/ByVultr.php index 321d8f6588..3a6ce532df 100644 --- a/app/Livewire/Server/New/ByVultr.php +++ b/app/Livewire/Server/New/ByVultr.php @@ -375,9 +375,7 @@ class ByVultr extends Component $details = data_get($e->response->json(), $jsonMessageKey) ?: $e->response->body() ?: $details; } - $prefix = "{$providerName} API error: "; - - return str_starts_with($details, $prefix) ? $details : $prefix.$details; + return "{$providerName} API error: {$details}"; } private function createVultrServer(VultrService $vultrService): array diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index 320285970d..975ce9a241 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -163,7 +163,6 @@ class SettingsEmail extends Component public function toggleSmtp() { try { - $this->authorize('update', $this->settings); $this->resetErrorBag(); if ($this->smtpEnabled) { @@ -186,7 +185,6 @@ class SettingsEmail extends Component public function toggleResend() { try { - $this->authorize('update', $this->settings); $this->resetErrorBag(); if ($this->resendEnabled) { diff --git a/app/Livewire/Team/DangerZone.php b/app/Livewire/Team/DangerZone.php index 643477e24d..74cf3d7ef8 100644 --- a/app/Livewire/Team/DangerZone.php +++ b/app/Livewire/Team/DangerZone.php @@ -16,7 +16,6 @@ class DangerZone extends Component public function mount(): void { $this->team = currentTeam(); - $this->authorize('delete', $this->team); } public function delete(): mixed diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 50df845f7b..6426860187 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -27,7 +27,7 @@ class FortifyServiceProvider extends ServiceProvider public function toResponse($request) { // First user (root) will be redirected to /settings instead of / on registration. - if ($request->user()->currentTeam()?->id === 0) { + if ($request->user()->currentTeam->id === 0) { return redirect()->route('settings.index'); } @@ -88,9 +88,9 @@ class FortifyServiceProvider extends ServiceProvider if (! $user->teams()->where('team_id', $invitation->team->id)->exists()) { $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); } - $team = $invitation->team; + $user->currentTeam = $invitation->team; $invitation->delete(); - session(['currentTeam' => $team]); + session(['currentTeam' => $user->currentTeam]); } else { // Restore the last active team; only fall back when unambiguous. $team = $user->resolveStoredTeam(); @@ -98,7 +98,7 @@ class FortifyServiceProvider extends ServiceProvider $team = $user->recreate_personal_team(); } if ($team) { - session(['currentTeam' => $team]); + session(['currentTeam' => $user->currentTeam = $team]); } // Otherwise (multiple teams, no stored choice) leave the session // team unset so the user is sent to the team-selection screen. diff --git a/app/Services/Auth/OauthLoginService.php b/app/Services/Auth/OauthLoginService.php index 5915b0cd8a..761b6a8ccb 100644 --- a/app/Services/Auth/OauthLoginService.php +++ b/app/Services/Auth/OauthLoginService.php @@ -29,7 +29,7 @@ class OauthLoginService : $this->resolveOauthUser($oauthUser, $oauthSetting, $email); $team = $user->currentTeam() ?? $user->teams()->first() ?? $user->recreate_personal_team(); - session(['currentTeam' => $team]); + session(['currentTeam' => $user->currentTeam = $team]); if ($this->requiresTwoFactorChallenge($user)) { Auth::logout(); diff --git a/app/Services/DeploymentConfiguration/ConfigurationDiffer.php b/app/Services/DeploymentConfiguration/ConfigurationDiffer.php index 688db3f0a4..ace4888338 100644 --- a/app/Services/DeploymentConfiguration/ConfigurationDiffer.php +++ b/app/Services/DeploymentConfiguration/ConfigurationDiffer.php @@ -39,9 +39,7 @@ class ConfigurationDiffer $previous = $previousItems[$key] ?? null; $current = $currentItems[$key] ?? null; - // Static settings missing from an older snapshot were newly tracked, not changed. - // Without any previous snapshot (legacy fallback), every setting is reported. - if ($previous === null && $previousItems !== [] && ! in_array(data_get($current, 'section'), self::DYNAMIC_SECTIONS, true)) { + if ($previous === null && ! in_array(data_get($current, 'section'), self::DYNAMIC_SECTIONS, true)) { continue; } diff --git a/app/Traits/HasSecretManager.php b/app/Traits/HasSecretManager.php index a788ae823e..8b3e50b7bd 100644 --- a/app/Traits/HasSecretManager.php +++ b/app/Traits/HasSecretManager.php @@ -15,10 +15,7 @@ trait HasSecretManager public static function bootHasSecretManager(): void { - // Return nothing: a non-null result halts the "deleting" event and skips later listeners. - static::deleting(function ($resource): void { - $resource->secretManagerLink()->delete(); - }); + static::deleting(fn ($resource) => $resource->secretManagerLink()->delete()); } public function secretManagerLink(): MorphOne diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 7b094e268d..b12a1e9a38 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1484,12 +1484,6 @@ function service_templates_path(): string */ function store_service_templates_bundle(string $json, ?string $fetchedAt = null): bool { - // A 200 response can still be an empty body or an error page; keep the current templates then. - $templates = json_decode($json, true); - if (! is_array($templates) || $templates === [] || array_is_list($templates)) { - return false; - } - $fetchedAt ??= now()->toIso8601String(); $path = service_templates_path(); @@ -1536,9 +1530,7 @@ function get_service_templates(bool $force = false): Collection if ($response->failed()) { return collect([]); } - if (! store_service_templates_bundle($response->body())) { - return get_service_templates(); - } + store_service_templates_bundle($response->body()); return collect(json_decode($response->body()))->sortKeys(); } catch (Throwable) { diff --git a/resources/views/components/team/settings-layout.blade.php b/resources/views/components/team/settings-layout.blade.php index be3ea573e1..3b6db03c3a 100644 --- a/resources/views/components/team/settings-layout.blade.php +++ b/resources/views/components/team/settings-layout.blade.php @@ -24,13 +24,13 @@ 'active' => request()->routeIs('team.admin-view'), 'icon' => 'admin', ] : null, - auth()->user()->can('delete', currentTeam()) ? [ + [ 'label' => 'Danger Zone', 'route' => 'team.danger-zone', 'active' => request()->routeIs('team.danger-zone'), 'icon' => 'shield-alert', 'sectionStart' => true, - ] : null, + ], ])->filter(); @endphp diff --git a/resources/views/livewire/server/navbar.blade.php b/resources/views/livewire/server/navbar.blade.php index 01cc64891c..41aedc45ae 100644 --- a/resources/views/livewire/server/navbar.blade.php +++ b/resources/views/livewire/server/navbar.blade.php @@ -85,9 +85,7 @@ $activeServerMenuItem['route'], $serverRouteParameters, ); - $showSentinelStatus = $server->isFunctional() - && $server->isSentinelEnabled() - && auth()->user()?->can('viewSentinel', $server); + $showSentinelStatus = $server->isFunctional() && $server->isSentinelEnabled(); $proxyCanBeStopped = in_array($proxyStatus, ['running', 'starting', 'restarting'], true); @endphp diff --git a/resources/views/livewire/server/partials/localhost-general.blade.php b/resources/views/livewire/server/partials/localhost-general.blade.php index 3876b4edbe..fef1609998 100644 --- a/resources/views/livewire/server/partials/localhost-general.blade.php +++ b/resources/views/livewire/server/partials/localhost-general.blade.php @@ -13,13 +13,6 @@ :type="$server->isFunctional() ? 'success' : 'warning'" /> - @if ($this->limaStartCommand) - - {{ $this->limaStartCommand }} - - @endif -
diff --git a/resources/views/livewire/server/show.blade.php b/resources/views/livewire/server/show.blade.php index 6ae3c02aa8..4edbf479a3 100644 --- a/resources/views/livewire/server/show.blade.php +++ b/resources/views/livewire/server/show.blade.php @@ -88,24 +88,6 @@ @endif - @if ($server->cloudProviderToken) - @if ($provider === 'Hetzner' && !$server->isFunctional() && $hetznerServerStatus === 'off') - - Power On - - @elseif ($provider === 'DigitalOcean' && $digitalOceanDropletStatus === 'off') - - Power On - - @elseif ($provider === 'Vultr' && $vultrInstanceStatus === 'stopped') - - Power On - - @endif - @endif @endif @if ($server->server_metadata) - @if ($isConnected) - - Test connection - - @endif - + Sync name @@ -196,7 +191,7 @@ shortConfirmationLabel="GitHub App Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" /> @else - + Delete @endcan diff --git a/tests/Feature/Authorization/TeamAuthorizationTest.php b/tests/Feature/Authorization/TeamAuthorizationTest.php index 7d437c823f..0637a48db5 100644 --- a/tests/Feature/Authorization/TeamAuthorizationTest.php +++ b/tests/Feature/Authorization/TeamAuthorizationTest.php @@ -171,34 +171,17 @@ test('team index mounts when is_mcp_server_enabled is null on the session team', // --- Team Danger Zone Livewire: delete --- -test('member cannot open the team danger zone', function () { +test('member cannot delete team via danger zone', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); - Livewire::test(DangerZone::class)->assertForbidden(); + Livewire::test(DangerZone::class) + ->call('delete') + ->assertDispatched('error'); expect(Team::find($this->team->id))->not->toBeNull(); }); -test('owner can open the team danger zone', function () { - $this->actingAs($this->owner); - session(['currentTeam' => $this->team]); - - Livewire::test(DangerZone::class)->assertSuccessful(); -}); - -test('team settings show the danger zone link only to the team owner', function () { - session(['currentTeam' => $this->team]); - - $this->actingAs($this->owner); - Livewire::test(TeamIndex::class)->assertSee('Danger Zone'); - - foreach ([$this->admin, $this->member] as $user) { - $this->actingAs($user); - Livewire::test(TeamIndex::class)->assertDontSee('Danger Zone'); - } -}); - test('admin cannot delete team via policy', function () { $this->actingAs($this->admin); session(['currentTeam' => $this->team]); diff --git a/tests/Feature/Jobs/StartupExecutionCleanupTest.php b/tests/Feature/Jobs/StartupExecutionCleanupTest.php index 9abbde990a..0b5cc4af2b 100644 --- a/tests/Feature/Jobs/StartupExecutionCleanupTest.php +++ b/tests/Feature/Jobs/StartupExecutionCleanupTest.php @@ -30,7 +30,12 @@ beforeEach(function () { // Fake notifications to ensure none are sent Notification::fake(); - Http::fake(); + // app:init pulls the service templates and writes the real repository file, + // so answer with its current content instead of an empty body. + Http::fake([ + config('constants.services.official') => Http::response(file_get_contents(service_templates_path())), + '*' => Http::response(), + ]); Process::fake(); Queue::fake(); diff --git a/tests/Feature/PullServiceTemplatesFromCdnTest.php b/tests/Feature/PullServiceTemplatesFromCdnTest.php index d70d787a76..848ad44b69 100644 --- a/tests/Feature/PullServiceTemplatesFromCdnTest.php +++ b/tests/Feature/PullServiceTemplatesFromCdnTest.php @@ -119,33 +119,6 @@ it('skips pulling templates in local development', function () { expect(Cache::get(service_templates_cache_key()))->toBeNull(); }); -it('keeps the local templates when the CDN returns an unusable bundle', function (string $body) { - Http::fake([ - config('constants.services.official') => Http::response($body, 200), - ]); - - $path = service_templates_path(); - $original = File::get($path); - - try { - config(['app.env' => 'production']); - - (new PullTemplatesFromCDN)->handle(); - - expect(md5(File::get($path)))->toBe(md5($original)) - ->and(Cache::get(service_templates_cache_key()))->toBeNull() - ->and(get_service_templates())->not->toBeEmpty(); - } finally { - File::put($path, $original); - Cache::forget(service_templates_cache_key()); - } -})->with([ - 'empty body' => '', - 'html error page' => 'Bad gateway', - 'empty object' => '{}', - 'json list' => '[1, 2]', -]); - it('logs when the CDN responds with a non-success status', function () { Http::fake([ 'cdn.coollabs.io/*' => Http::response('nope', 503),