environment('local', 'testing')) { $this->error('e2e:banking-fixture is only available in local/testing environments.'); return self::FAILURE; } return match ($this->argument('action')) { 'seed' => $this->seed(), 'inspect' => $this->inspect(), 'expire' => $this->expire(), default => $this->invalidAction(), }; } private function seed(): int { $onboarding = $this->resetUser(self::ONBOARDING_EMAIL, onboarded: false, pro: false); $settings = $this->resetUser(self::SETTINGS_EMAIL, onboarded: true, pro: true); $this->line((string) json_encode([ 'onboarding' => ['email' => $onboarding->email, 'password' => self::PASSWORD], 'settings' => ['email' => $settings->email, 'password' => self::PASSWORD], ], JSON_PRETTY_PRINT)); return self::SUCCESS; } private function inspect(): int { $connection = $this->latestConnection(); if (! $connection) { $this->line((string) json_encode(['connection' => null])); return self::SUCCESS; } $accountIds = $connection->accounts()->pluck('id'); $this->line((string) json_encode([ 'connection' => [ 'id' => $connection->id, 'status' => $connection->status->value, 'aspsp_name' => $connection->aspsp_name, 'session_id' => $connection->session_id !== null, 'valid_until' => $connection->valid_until?->toIso8601String(), 'accounts_count' => $accountIds->count(), 'accounts_without_external_id' => $connection->accounts()->whereNull('external_account_id')->count(), 'transactions_count' => Transaction::whereIn('account_id', $accountIds)->count(), ], ], JSON_PRETTY_PRINT)); return self::SUCCESS; } private function expire(): int { $connection = $this->latestConnection(); if (! $connection) { $this->error('No connection found for '.$this->argument('email')); return self::FAILURE; } $connection->update([ 'status' => BankingConnectionStatus::Expired, 'valid_until' => now()->subDay(), ]); $this->line((string) json_encode(['expired' => $connection->id])); return self::SUCCESS; } private function resetUser(string $email, bool $onboarded, bool $pro): User { // Reuse the user across runs (the model soft-deletes, so recreating the same // email would collide). Clear everything the flows produce so each run starts // from a clean slate. $user = User::withTrashed()->firstWhere('email', $email) ?? User::factory()->create(['email' => $email]); $user->accounts()->forceDelete(); $user->bankingConnections()->forceDelete(); $user->subscriptions()->delete(); $user->restore(); $user->forceFill([ 'email_verified_at' => now(), 'password' => Hash::make(self::PASSWORD), 'onboarded_at' => $onboarded ? now() : null, 'two_factor_secret' => null, 'two_factor_recovery_codes' => null, 'two_factor_confirmed_at' => null, ])->save(); if ($pro) { $user->subscriptions()->create([ 'type' => 'default', 'stripe_id' => 'sub_e2e_'.$user->id, 'stripe_status' => 'active', 'stripe_price' => 'price_e2e', 'quantity' => 1, ]); } return $user; } private function latestConnection(): ?BankingConnection { $email = $this->argument('email'); $user = User::where('email', $email)->first(); if (! $user) { return null; } return $user->bankingConnections()->latest()->first(); } private function invalidAction(): int { $this->error('Unknown action. Use one of: seed, inspect, expire.'); return self::FAILURE; } }