From d6c6f0af1c00cd1a7d2736bdd0e4b2b286a22547 Mon Sep 17 00:00:00 2001 From: E98Developer Date: Sat, 8 Aug 2026 16:09:54 +0200 Subject: [PATCH] =?UTF-8?q?EV3-431=20Profitcenter=20-=20bet=C5=B1k=C3=B3d?= =?UTF-8?q?=20phase1=20addd=20feature=20flag=20with=20laravel=20pennant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/launch.json | 11 ++ .../FeatureFlags/FeatureFlagResource.php | 51 ++++++++ .../FeatureFlags/Pages/CreateFeatureFlag.php | 11 ++ .../FeatureFlags/Pages/EditFeatureFlag.php | 19 +++ .../FeatureFlags/Pages/ListFeatureFlags.php | 19 +++ .../FeatureFlags/Schemas/FeatureFlagForm.php | 55 ++++++++ .../FeatureFlags/Tables/FeatureFlagsTable.php | 93 +++++++++++++ app/Models/FeatureFlag.php | 12 ++ app/Models/FeatureFlagOverride.php | 22 ++++ app/Observers/FeatureFlagObserver.php | 23 ++++ app/Providers/AppServiceProvider.php | 16 +++ app/Services/FeatureFlagRegistrar.php | 53 ++++++++ composer.json | 1 + composer.lock | 79 ++++++++++- config/pennant.php | 44 +++++++ ...026_08_08_152549_create_features_table.php | 32 +++++ ...8_08_153000_create_feature_flags_table.php | 38 ++++++ ...00_create_feature_flag_overrides_table.php | 37 ++++++ .../components/speed-button-nav-bar.blade.php | 1 + tests/Feature/FeatureFlagAdminUiTest.php | 30 +++++ tests/Feature/FeatureFlagTest.php | 123 ++++++++++++++++++ 21 files changed, 769 insertions(+), 1 deletion(-) create mode 100644 .claude/launch.json create mode 100644 app/Filament/Resources/FeatureFlags/FeatureFlagResource.php create mode 100644 app/Filament/Resources/FeatureFlags/Pages/CreateFeatureFlag.php create mode 100644 app/Filament/Resources/FeatureFlags/Pages/EditFeatureFlag.php create mode 100644 app/Filament/Resources/FeatureFlags/Pages/ListFeatureFlags.php create mode 100644 app/Filament/Resources/FeatureFlags/Schemas/FeatureFlagForm.php create mode 100644 app/Filament/Resources/FeatureFlags/Tables/FeatureFlagsTable.php create mode 100644 app/Models/FeatureFlag.php create mode 100644 app/Models/FeatureFlagOverride.php create mode 100644 app/Observers/FeatureFlagObserver.php create mode 100644 app/Services/FeatureFlagRegistrar.php create mode 100644 config/pennant.php create mode 100644 database/migrations/2026_08_08_152549_create_features_table.php create mode 100644 database/migrations/2026_08_08_153000_create_feature_flags_table.php create mode 100644 database/migrations/2026_08_08_154500_create_feature_flag_overrides_table.php create mode 100644 tests/Feature/FeatureFlagAdminUiTest.php create mode 100644 tests/Feature/FeatureFlagTest.php diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..7265e92 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "t2t-dev", + "runtimeExecutable": "php", + "runtimeArgs": ["artisan", "serve", "--port=8123"], + "port": 8123 + } + ] +} diff --git a/app/Filament/Resources/FeatureFlags/FeatureFlagResource.php b/app/Filament/Resources/FeatureFlags/FeatureFlagResource.php new file mode 100644 index 0000000..4acbdda --- /dev/null +++ b/app/Filament/Resources/FeatureFlags/FeatureFlagResource.php @@ -0,0 +1,51 @@ + ListFeatureFlags::route('/'), + 'create' => CreateFeatureFlag::route('/create'), + 'edit' => EditFeatureFlag::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/FeatureFlags/Pages/CreateFeatureFlag.php b/app/Filament/Resources/FeatureFlags/Pages/CreateFeatureFlag.php new file mode 100644 index 0000000..6f8b944 --- /dev/null +++ b/app/Filament/Resources/FeatureFlags/Pages/CreateFeatureFlag.php @@ -0,0 +1,11 @@ +components([ + Section::make('Alapadatok') + ->columnSpanFull() + ->schema([ + TextInput::make('name') + ->label('Kulcs') + ->helperText('Ezt a kulcsot kell használni a kódban: Feature::active(\'kulcs\')') + ->required() + ->unique(ignoreRecord: true) + ->maxLength(255), + TextInput::make('label') + ->label('Megnevezés') + ->required() + ->maxLength(255), + Textarea::make('description') + ->label('Leírás') + ->columnSpanFull(), + Toggle::make('enabled') + ->label('Bekapcsolva (rendszer szintű kapcsoló)') + ->default(true), + ]), + Section::make('Célzás') + ->columnSpanFull() + ->schema([ + TagsInput::make('stages') + ->label('Stage-ek') + ->helperText('Üresen hagyva minden környezeten aktív. Ismert értékek: DEV, TEST, PROD, d2dtst, e2etst.') + ->placeholder('pl. TEST'), + Select::make('roles') + ->label('Szerepkörök') + ->helperText('Üresen hagyva minden szerepkörnek aktív.') + ->multiple() + ->options(fn () => Role::pluck('display_name', 'name')->filter()->all()), + ]), + ]); + } +} diff --git a/app/Filament/Resources/FeatureFlags/Tables/FeatureFlagsTable.php b/app/Filament/Resources/FeatureFlags/Tables/FeatureFlagsTable.php new file mode 100644 index 0000000..0eb0349 --- /dev/null +++ b/app/Filament/Resources/FeatureFlags/Tables/FeatureFlagsTable.php @@ -0,0 +1,93 @@ +columns([ + TextColumn::make('name') + ->label('Kulcs') + ->searchable() + ->sortable(), + TextColumn::make('label') + ->label('Megnevezés') + ->searchable(), + ToggleColumn::make('enabled') + ->label('Bekapcsolva'), + TextColumn::make('stages') + ->label('Stage-ek') + ->badge() + ->placeholder('mind'), + TextColumn::make('roles') + ->label('Szerepkörök') + ->badge() + ->placeholder('mind'), + TextColumn::make('updated_at') + ->label('Módosítva') + ->dateTime() + ->sortable(), + ]) + ->recordActions([ + EditAction::make(), + Action::make('userOverride') + ->label('Egyéni felülbírálás') + ->icon('heroicon-o-user') + ->form([ + Select::make('user_id') + ->label('Felhasználó') + ->searchable() + ->options(fn () => User::pluck('name', 'id')) + ->required(), + Radio::make('override') + ->label('Állapot') + ->options([ + 'inherit' => 'Örökölt (alapértelmezett szabály érvényes)', + 'on' => 'Bekapcsolva', + 'off' => 'Kikapcsolva', + ]) + ->default('inherit') + ->required(), + ]) + ->action(function (array $data, FeatureFlag $record) { + $user = User::findOrFail($data['user_id']); + + if ($data['override'] === 'inherit') { + FeatureFlagOverride::where('feature_flag_id', $record->id) + ->where('user_id', $user->id) + ->delete(); + } else { + FeatureFlagOverride::updateOrCreate( + ['feature_flag_id' => $record->id, 'user_id' => $user->id], + ['active' => $data['override'] === 'on'], + ); + } + + // csak ennek az egy usernek a gyorsítótárazott Pennant-feloldását + // érvénytelenítjük, a többi felhasználót nem érinti + Feature::for($user)->forget($record->name); + }), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ]), + ]); + } +} diff --git a/app/Models/FeatureFlag.php b/app/Models/FeatureFlag.php new file mode 100644 index 0000000..7261fee --- /dev/null +++ b/app/Models/FeatureFlag.php @@ -0,0 +1,12 @@ + 'boolean', + 'stages' => 'array', + 'roles' => 'array', + ]; +} diff --git a/app/Models/FeatureFlagOverride.php b/app/Models/FeatureFlagOverride.php new file mode 100644 index 0000000..84b6185 --- /dev/null +++ b/app/Models/FeatureFlagOverride.php @@ -0,0 +1,22 @@ + 'boolean', + ]; + + public function featureFlag(): BelongsTo + { + return $this->belongsTo(FeatureFlag::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Observers/FeatureFlagObserver.php b/app/Observers/FeatureFlagObserver.php new file mode 100644 index 0000000..75c15be --- /dev/null +++ b/app/Observers/FeatureFlagObserver.php @@ -0,0 +1,23 @@ +name); + } + + public function deleted(FeatureFlag $flag): void + { + Feature::purge($flag->name); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8c0eee5..93b779d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,8 @@ namespace App\Providers; +use App\Models\FeatureFlag; +use App\Observers\FeatureFlagObserver; use App\Repositories\AddressRepository; use App\Repositories\AddressRepositoryInterface; use App\Repositories\ContactRepository; @@ -20,6 +22,8 @@ use App\Repositories\ProfitCenterRepositoryInterface; use App\Repositories\SupplierRepository; use App\Repositories\SupplierRepositoryInterface; +use App\Services\FeatureFlagRegistrar; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; @@ -53,6 +57,14 @@ public function boot(): void { URL::forceScheme('https'); + FeatureFlag::observe(FeatureFlagObserver::class); + + // Schema::hasTable védi a friss telepítést / migrate-et megelőző state-et, + // amikor a feature_flags tábla még nem létezik. + if (Schema::hasTable('feature_flags')) { + app(FeatureFlagRegistrar::class)->registerAll(); + } + \Illuminate\Database\Schema\Blueprint::macro('userIdFields', function () { $this->unsignedInteger('created_by')->nullable(); $this->unsignedInteger('updated_by')->nullable(); @@ -88,6 +100,10 @@ public function boot(): void \Livewire\Livewire::component('app.filament.resources.profit-center-supplier-schedules.pages.create-profit-center-supplier-schedule', \App\Filament\Resources\ProfitCenterSupplierSchedules\Pages\CreateProfitCenterSupplierSchedule::class); \Livewire\Livewire::component('app.filament.resources.profit-center-supplier-schedules.pages.edit-profit-center-supplier-schedule', \App\Filament\Resources\ProfitCenterSupplierSchedules\Pages\EditProfitCenterSupplierSchedule::class); + \Livewire\Livewire::component('app.filament.resources.feature-flags.pages.list-feature-flags', \App\Filament\Resources\FeatureFlags\Pages\ListFeatureFlags::class); + \Livewire\Livewire::component('app.filament.resources.feature-flags.pages.create-feature-flag', \App\Filament\Resources\FeatureFlags\Pages\CreateFeatureFlag::class); + \Livewire\Livewire::component('app.filament.resources.feature-flags.pages.edit-feature-flag', \App\Filament\Resources\FeatureFlags\Pages\EditFeatureFlag::class); + \Livewire\Livewire::component('filament.livewire.notifications', \Filament\Livewire\Notifications::class); \Livewire\Livewire::component('filament.livewire.database-notifications', \Filament\Livewire\DatabaseNotifications::class); \Livewire\Livewire::component('filament.livewire.sidebar', \Filament\Livewire\Sidebar::class); diff --git a/app/Services/FeatureFlagRegistrar.php b/app/Services/FeatureFlagRegistrar.php new file mode 100644 index 0000000..a564af3 --- /dev/null +++ b/app/Services/FeatureFlagRegistrar.php @@ -0,0 +1,53 @@ +activate()) keresztül kezeljük, mert a + * Pennant purge() minden scope-ra töröl egy adott flag névre - egy admin + * mentés (pl. enabled kikapcsolása) így törölné az explicit felülbírálásokat + * is. Ezért az override-okat saját táblában tároljuk, a resolver pedig ezt + * nézi meg elsőként; a Pennant tárolója így csak a szabály-alapú + * alapértelmezett feloldást cache-eli, amit bármikor biztonságos purge-elni. + */ + public function registerAll(): void + { + FeatureFlag::all()->each(function (FeatureFlag $flag) { + Feature::define($flag->name, function (?User $user) use ($flag) { + if ($user) { + $override = FeatureFlagOverride::where('feature_flag_id', $flag->id) + ->where('user_id', $user->id) + ->first(); + + if ($override) { + return $override->active; + } + } + + if (! $flag->enabled) { + return false; + } + + if ($flag->stages && ! in_array(config('app.stage'), $flag->stages, true)) { + return false; + } + + if ($flag->roles && (! $user || ! $user->hasRole($flag->roles))) { + return false; + } + + return true; + }); + }); + } +} diff --git a/composer.json b/composer.json index a2bbddc..4f3ded8 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "laravel-json-api/laravel": "^5.1", "laravel-lang/lang": "^15.0", "laravel/framework": "^12.48", + "laravel/pennant": "^1.24", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.10.1", "league/flysystem-aws-s3-v3": "^3.0", diff --git a/composer.lock b/composer.lock index 751d610..adbbaf2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "05f09a61a6722503ee896c57c530bf6b", + "content-hash": "e6b0d54f37a81c8c859c977ad5d3527c", "packages": [ { "name": "ahmedhakeem/extra", @@ -5084,6 +5084,83 @@ }, "time": "2026-03-26T14:51:54+00:00" }, + { + "name": "laravel/pennant", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/pennant.git", + "reference": "b99fbc8038eb2c0a2642e9e13077a75e892b61d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pennant/zipball/b99fbc8038eb2c0a2642e9e13077a75e892b61d9", + "reference": "b99fbc8038eb2c0a2642e9e13077a75e892b61d9", + "shasum": "" + }, + "require": { + "illuminate/console": "^10.0|^11.0|^12.0|^13.0", + "illuminate/container": "^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "illuminate/database": "^10.0|^11.0|^12.0|^13.0", + "illuminate/queue": "^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1", + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/finder": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "laravel/octane": "^1.4|^2.0", + "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0", + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Feature": "Laravel\\Pennant\\Feature" + }, + "providers": [ + "Laravel\\Pennant\\PennantServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php", + "src/functions.php" + ], + "psr-4": { + "Laravel\\Pennant\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "A simple, lightweight library for managing feature flags.", + "homepage": "https://github.com/laravel/pennant", + "keywords": [ + "feature", + "flags", + "laravel", + "pennant" + ], + "support": { + "issues": "https://github.com/laravel/pennant/issues", + "source": "https://github.com/laravel/pennant" + }, + "time": "2026-06-28T17:14:13+00:00" + }, { "name": "laravel/prompts", "version": "v0.3.16", diff --git a/config/pennant.php b/config/pennant.php new file mode 100644 index 0000000..d03ebfb --- /dev/null +++ b/config/pennant.php @@ -0,0 +1,44 @@ + env('PENNANT_STORE', 'database'), + + /* + |-------------------------------------------------------------------------- + | Pennant Stores + |-------------------------------------------------------------------------- + | + | Here you may configure each of the stores that should be available to + | Pennant. These stores shall be used to store resolved feature flag + | values - you may configure as many as your application requires. + | + */ + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => null, + 'table' => 'features', + ], + + ], +]; diff --git a/database/migrations/2026_08_08_152549_create_features_table.php b/database/migrations/2026_08_08_152549_create_features_table.php new file mode 100644 index 0000000..a64eea2 --- /dev/null +++ b/database/migrations/2026_08_08_152549_create_features_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name'); + $table->string('scope'); + $table->text('value'); + $table->timestamps(); + + $table->unique(['name', 'scope']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('features'); + } +}; diff --git a/database/migrations/2026_08_08_153000_create_feature_flags_table.php b/database/migrations/2026_08_08_153000_create_feature_flags_table.php new file mode 100644 index 0000000..9238afa --- /dev/null +++ b/database/migrations/2026_08_08_153000_create_feature_flags_table.php @@ -0,0 +1,38 @@ +id(); + $table->string('name')->unique(); + $table->string('label'); + $table->text('description')->nullable(); + $table->boolean('enabled')->default(true); + $table->json('stages')->nullable(); + $table->json('roles')->nullable(); + + // Audit mezők a BaseAuditable-hoz + $table->unsignedBigInteger('created_by')->nullable(); + $table->unsignedBigInteger('updated_by')->nullable(); + $table->unsignedBigInteger('deleted_by')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('feature_flags'); + } +}; diff --git a/database/migrations/2026_08_08_154500_create_feature_flag_overrides_table.php b/database/migrations/2026_08_08_154500_create_feature_flag_overrides_table.php new file mode 100644 index 0000000..a3c55e6 --- /dev/null +++ b/database/migrations/2026_08_08_154500_create_feature_flag_overrides_table.php @@ -0,0 +1,37 @@ +id(); + $table->foreignId('feature_flag_id')->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->boolean('active'); + + // Audit mezők a BaseAuditable-hoz + $table->unsignedBigInteger('created_by')->nullable(); + $table->unsignedBigInteger('updated_by')->nullable(); + $table->unsignedBigInteger('deleted_by')->nullable(); + $table->timestamps(); + + $table->unique(['feature_flag_id', 'user_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('feature_flag_overrides'); + } +}; diff --git a/resources/views/filament/components/speed-button-nav-bar.blade.php b/resources/views/filament/components/speed-button-nav-bar.blade.php index d1492d7..9ed7a9e 100644 --- a/resources/views/filament/components/speed-button-nav-bar.blade.php +++ b/resources/views/filament/components/speed-button-nav-bar.blade.php @@ -20,6 +20,7 @@ ['displayName' => 'Munkanaptárak', 'link' => \App\Filament\Resources\WorkCalendars\WorkCalendarResource::getUrl()], ]], ['name' => 'calendarTest', 'displayName' => 'Naptár Teszt', 'icon' => 'idopontok', 'link' => \App\Filament\Pages\CalendarTest::getUrl(), 'roles' => ['root','developer','admin']], + ['name' => 'featureFlags', 'displayName' => 'Feature flagek', 'icon' => 'setup', 'link' => \App\Filament\Resources\FeatureFlags\FeatureFlagResource::getUrl(), 'roles' => ['root','developer']], ]; @endphp diff --git a/tests/Feature/FeatureFlagAdminUiTest.php b/tests/Feature/FeatureFlagAdminUiTest.php new file mode 100644 index 0000000..286c14f --- /dev/null +++ b/tests/Feature/FeatureFlagAdminUiTest.php @@ -0,0 +1,30 @@ + 'admin', 'display_name' => 'Admin']); + $admin = User::factory()->create(); + $admin->addRole($adminRole); + + FeatureFlag::create([ + 'name' => 'ui-test-flag', + 'label' => 'UI teszt flag', + 'enabled' => true, + 'stages' => ['TEST'], + 'roles' => ['admin'], + ]); + + $this->actingAs($admin); + + Livewire::test(ListFeatureFlags::class) + ->assertSuccessful(); +}); diff --git a/tests/Feature/FeatureFlagTest.php b/tests/Feature/FeatureFlagTest.php new file mode 100644 index 0000000..13dc8ad --- /dev/null +++ b/tests/Feature/FeatureFlagTest.php @@ -0,0 +1,123 @@ + 'test-flag', + 'label' => 'Test flag', + 'enabled' => true, + 'stages' => null, + 'roles' => null, + ], $attributes)); + + app(FeatureFlagRegistrar::class)->registerAll(); + + return $flag; +} + +test('a kikapcsolt flag mindenkinek inaktív', function () { + defineFlag(['enabled' => false]); + + $user = User::factory()->create(); + + expect(Feature::for($user)->active('test-flag'))->toBeFalse(); +}); + +test('a stage-mismatch inaktívvá teszi a flaget', function () { + Config::set('app.stage', 'PROD'); + + defineFlag(['stages' => ['TEST']]); + + $user = User::factory()->create(); + + expect(Feature::for($user)->active('test-flag'))->toBeFalse(); +}); + +test('a stage-match aktívvá teszi a flaget', function () { + Config::set('app.stage', 'TEST'); + + defineFlag(['stages' => ['TEST']]); + + $user = User::factory()->create(); + + expect(Feature::for($user)->active('test-flag'))->toBeTrue(); +}); + +test('a role-mismatch inaktívvá teszi a flaget', function () { + Role::create(['name' => 'admin', 'display_name' => 'Admin']); + + defineFlag(['roles' => ['admin']]); + + $user = User::factory()->create(); + + expect(Feature::for($user)->active('test-flag'))->toBeFalse(); +}); + +test('a role-match aktívvá teszi a flaget', function () { + $role = Role::create(['name' => 'admin', 'display_name' => 'Admin']); + + defineFlag(['roles' => ['admin']]); + + $user = User::factory()->create(); + $user->addRole($role); + + expect(Feature::for($user)->active('test-flag'))->toBeTrue(); +}); + +test('a user-szintű explicit felülbírálás felülír mindent', function () { + $flag = defineFlag(['enabled' => false]); + + $user = User::factory()->create(); + + FeatureFlagOverride::create([ + 'feature_flag_id' => $flag->id, + 'user_id' => $user->id, + 'active' => true, + ]); + Feature::for($user)->forget('test-flag'); + + expect(Feature::for($user)->active('test-flag'))->toBeTrue(); +}); + +test('a flag admin-mentése nem törli az egyéni felülbírálásokat', function () { + $flag = defineFlag(['enabled' => true]); + + $user = User::factory()->create(); + + FeatureFlagOverride::create([ + 'feature_flag_id' => $flag->id, + 'user_id' => $user->id, + 'active' => false, + ]); + Feature::for($user)->forget('test-flag'); + + expect(Feature::for($user)->active('test-flag'))->toBeFalse(); + + // Admin módosítja a flaget (pl. leírás) - ez kiváltja a FeatureFlagObserver purge-öt + $flag->update(['description' => 'módosítva']); + app(FeatureFlagRegistrar::class)->registerAll(); + + expect(FeatureFlagOverride::where('feature_flag_id', $flag->id)->where('user_id', $user->id)->exists()) + ->toBeTrue(); + expect(Feature::for($user)->active('test-flag'))->toBeFalse(); +}); + +test('üres stages/roles esetén mindenkinek aktív', function () { + defineFlag([]); + + $user = User::factory()->create(); + + expect(Feature::for($user)->active('test-flag'))->toBeTrue(); +});