feature flags

This commit is contained in:
2026-01-07 22:05:53 +01:00
parent 1b63fa646c
commit 606b64cec7
530 changed files with 2092 additions and 2943 deletions

View File

@@ -27,8 +27,20 @@ describe('Feature Flag Configuration', () => {
expect(result.loadedFrom).toBe('config-file');
expect(result.features).toBeDefined();
// Core platform features (alpha mode - all enabled)
expect(result.features['platform.dashboard']).toBe('enabled');
expect(result.features['platform.leagues']).toBe('enabled');
// Sponsor features
expect(result.features['sponsors.portal']).toBe('enabled');
expect(result.features['sponsors.management']).toBe('enabled');
// Admin features
expect(result.features['admin.dashboard']).toBe('enabled');
// Beta features (development has them enabled for testing)
expect(result.features['beta.newUI']).toBe('enabled');
});
it('should load test config when NODE_ENV=test', async () => {
@@ -36,8 +48,18 @@ describe('Feature Flag Configuration', () => {
const result = await loadFeatureConfig();
expect(result.loadedFrom).toBe('config-file');
// Core platform features
expect(result.features['platform.dashboard']).toBe('enabled');
// Sponsor features
expect(result.features['sponsors.portal']).toBe('enabled');
// Admin features
expect(result.features['admin.dashboard']).toBe('enabled');
// Beta features (disabled in test)
expect(result.features['beta.newUI']).toBe('disabled');
});
it('should load production config when NODE_ENV=production', async () => {
@@ -45,11 +67,22 @@ describe('Feature Flag Configuration', () => {
const result = await loadFeatureConfig();
expect(result.loadedFrom).toBe('config-file');
// Core platform features (stable)
expect(result.features['platform.dashboard']).toBe('enabled');
// Sponsor features (gradual rollout - management disabled)
expect(result.features['sponsors.portal']).toBe('enabled');
expect(result.features['sponsors.management']).toBe('disabled');
// Admin features (analytics disabled)
expect(result.features['admin.dashboard']).toBe('enabled');
expect(result.features['admin.analytics']).toBe('disabled');
// Beta features (disabled in production)
expect(result.features['beta.newUI']).toBe('disabled');
});
it('should handle invalid environment', async () => {
process.env.NODE_ENV = 'invalid-env';
@@ -107,4 +140,24 @@ describe('Feature Flag Configuration', () => {
expect(result.features['admin.dashboard']).toBeDefined();
});
});
describe('Feature state differences', () => {
it('should correctly handle all feature states', async () => {
process.env.NODE_ENV = 'staging';
const result = await loadFeatureConfig();
// 'enabled' - fully available
expect(result.features['platform.dashboard']).toBe('enabled');
// 'disabled' - turned off completely
expect(result.features['beta.experimental']).toBe('hidden');
// 'coming_soon' - visible but not available
expect(result.features['sponsors.management']).toBe('coming_soon');
expect(result.features['beta.newUI']).toBe('coming_soon');
// 'hidden' - completely invisible
expect(result.features['beta.experimental']).toBe('hidden');
});
});
});

View File

@@ -2,6 +2,12 @@
* Feature Flag Configuration Types
*
* Provides type-safe configuration for feature flags across different environments
*
* FEATURE STATES:
* - 'enabled': Feature is fully available to users
* - 'disabled': Feature is turned off (not visible/functional)
* - 'coming_soon': Feature is visible but not yet available (shows "coming soon")
* - 'hidden': Feature is completely invisible (internal/experimental only)
*/
export type FeatureState = 'enabled' | 'disabled' | 'coming_soon' | 'hidden';

View File

@@ -2,33 +2,121 @@ import { FeatureFlagConfig } from './feature-types';
/**
* Feature flag configuration for all environments
* This provides type safety, IntelliSense, and environment-specific settings
*
* ARCHITECTURE: API-Driven Features
* - All feature control comes from the API /features endpoint
* - FeatureFlagService fetches and caches features
* - Components use FeatureFlagService or ModeGuard for conditional rendering
*
* FEATURE STATES - DETAILED EXPLANATION:
*
* 'enabled' = Feature is fully available to users
* - Visible in UI
* - Fully functional
* - No restrictions
* - Example: "Users can create leagues"
*
* 'disabled' = Feature is turned off for everyone
* - Not visible in UI (or shown as unavailable)
* - Non-functional
* - Users cannot access it
* - Use when: Feature is broken, not ready, or intentionally removed
* - Example: "Sponsor management disabled due to bug"
*
* 'coming_soon' = Feature is visible but not yet available
* - Visible in UI with "Coming Soon" badge
* - Shows users what's coming
* - Builds anticipation
* - Use when: Feature is in development, users should know about it
* - Example: "New UI coming soon" - users see it but can't use it
*
* 'hidden' = Feature is completely invisible
* - Not shown in UI at all
* - No user knows it exists
* - Use when: Feature is experimental, internal-only, or not ready for ANY visibility
* - Example: "Experimental AI feature" - only devs know it exists
*
* DECISION TREE:
* - Should users see this feature exists?
* - NO → 'hidden' or 'disabled'
* - YES → Should they be able to use it?
* - NO → 'coming_soon'
* - YES → 'enabled'
*
* REAL-WORLD EXAMPLES:
* - 'enabled': Dashboard, leagues, teams (core features working)
* - 'disabled': Sponsor management (broken, don't show anything)
* - 'coming_soon': New UI redesign (users see "coming soon" banner)
* - 'hidden': Experimental chat feature (internal testing only)
*/
export const featureConfig: FeatureFlagConfig = {
// Development environment - features for local development
// Development environment - all features enabled for testing
development: {
// Core platform features
platform: {
dashboard: 'enabled',
leagues: 'enabled',
teams: 'enabled',
drivers: 'enabled',
races: 'enabled',
leaderboards: 'enabled',
},
// Authentication & onboarding
auth: {
signup: 'enabled',
login: 'enabled',
forgotPassword: 'enabled',
resetPassword: 'enabled',
},
onboarding: {
wizard: 'enabled',
},
// Sponsor features
sponsors: {
portal: 'enabled',
dashboard: 'enabled',
management: 'enabled',
campaigns: 'enabled',
billing: 'enabled',
},
// Admin features
admin: {
dashboard: 'enabled',
userManagement: 'enabled',
analytics: 'enabled',
},
// Beta features for testing
beta: {
newUI: 'enabled', // Enable new UI for testing
newUI: 'enabled',
experimental: 'coming_soon',
},
},
// Test environment - features for automated tests
// Test environment - all features enabled for automated tests
test: {
platform: {
dashboard: 'enabled',
leagues: 'enabled',
teams: 'enabled',
drivers: 'enabled',
races: 'enabled',
leaderboards: 'enabled',
},
auth: {
signup: 'enabled',
login: 'enabled',
forgotPassword: 'enabled',
resetPassword: 'enabled',
},
onboarding: {
wizard: 'enabled',
},
sponsors: {
portal: 'enabled',
dashboard: 'enabled',
management: 'enabled',
campaigns: 'enabled',
billing: 'enabled',
},
admin: {
dashboard: 'enabled',
@@ -41,18 +129,42 @@ export const featureConfig: FeatureFlagConfig = {
},
},
// Staging environment - features for pre-production testing
// Staging environment - controlled feature rollout
staging: {
// Core platform features
platform: {
dashboard: 'enabled',
leagues: 'enabled',
teams: 'enabled',
drivers: 'enabled',
races: 'enabled',
leaderboards: 'enabled',
},
// Authentication & onboarding
auth: {
signup: 'enabled',
login: 'enabled',
forgotPassword: 'enabled',
resetPassword: 'enabled',
},
onboarding: {
wizard: 'enabled',
},
// Sponsor features (gradual rollout)
sponsors: {
portal: 'enabled',
dashboard: 'enabled',
management: 'enabled',
management: 'coming_soon', // Ready for testing but not fully rolled out
campaigns: 'enabled',
billing: 'enabled',
},
// Admin features
admin: {
dashboard: 'enabled',
userManagement: 'enabled',
analytics: 'enabled',
},
// Beta features (controlled rollout)
beta: {
newUI: 'coming_soon', // Ready for testing but not fully rolled out
experimental: 'hidden',
@@ -61,18 +173,42 @@ export const featureConfig: FeatureFlagConfig = {
// Production environment - stable features only
production: {
// Core platform features (stable)
platform: {
dashboard: 'enabled',
leagues: 'enabled',
teams: 'enabled',
drivers: 'enabled',
races: 'enabled',
leaderboards: 'enabled',
},
// Authentication & onboarding (stable)
auth: {
signup: 'enabled',
login: 'enabled',
forgotPassword: 'enabled',
resetPassword: 'enabled',
},
onboarding: {
wizard: 'enabled',
},
// Sponsor features (gradual rollout)
sponsors: {
portal: 'enabled',
dashboard: 'enabled',
management: 'disabled', // Feature not ready yet
campaigns: 'enabled',
billing: 'enabled',
},
// Admin features (stable)
admin: {
dashboard: 'enabled',
userManagement: 'enabled',
analytics: 'disabled', // Feature not ready yet
},
// Beta features (controlled rollout)
beta: {
newUI: 'disabled',
newUI: 'disabled', // Not ready for production
experimental: 'hidden',
},
},

View File

@@ -16,28 +16,87 @@ describe('Feature Flag Integration Test', () => {
expect(result.loadedFrom).toBe('config-file');
expect(result.configPath).toBe('apps/api/src/config/features.config.ts');
// Verify specific features from our config
// Verify core platform features (new structure)
expect(result.features['platform.dashboard']).toBe('enabled');
expect(result.features['platform.leagues']).toBe('enabled');
expect(result.features['platform.teams']).toBe('enabled');
// Verify auth features
expect(result.features['auth.signup']).toBe('enabled');
expect(result.features['auth.login']).toBe('enabled');
// Verify sponsor features (expanded)
expect(result.features['sponsors.portal']).toBe('enabled');
expect(result.features['sponsors.dashboard']).toBe('enabled');
expect(result.features['sponsors.campaigns']).toBe('enabled');
// Verify admin features
expect(result.features['admin.dashboard']).toBe('enabled');
expect(result.features['admin.userManagement']).toBe('enabled');
// Verify beta features (controlled in test)
expect(result.features['beta.newUI']).toBe('disabled');
expect(result.features['beta.experimental']).toBe('disabled');
// Verify utility functions work
expect(isFeatureEnabled(result.features, 'sponsors.portal')).toBe(true);
expect(isFeatureEnabled(result.features, 'platform.dashboard')).toBe(true);
expect(isFeatureEnabled(result.features, 'beta.newUI')).toBe(false);
expect(getFeatureState(result.features, 'sponsors.portal')).toBe('enabled');
expect(getFeatureState(result.features, 'platform.dashboard')).toBe('enabled');
expect(getFeatureState(result.features, 'nonexistent')).toBe('disabled');
});
it('should work with different environments', async () => {
// Test development environment
// Test development environment - alpha mode (all enabled)
process.env.NODE_ENV = 'development';
const devResult = await loadFeatureConfig();
expect(devResult.features['beta.newUI']).toBe('enabled'); // dev has beta enabled
expect(devResult.features['beta.newUI']).toBe('enabled');
expect(devResult.features['platform.dashboard']).toBe('enabled');
expect(devResult.features['sponsors.management']).toBe('enabled');
// Test production environment
// Test production environment - beta mode (controlled rollout)
process.env.NODE_ENV = 'production';
const prodResult = await loadFeatureConfig();
expect(prodResult.features['beta.newUI']).toBe('disabled'); // prod has beta disabled
expect(prodResult.features['beta.newUI']).toBe('disabled');
expect(prodResult.features['platform.dashboard']).toBe('enabled');
expect(prodResult.features['sponsors.management']).toBe('disabled'); // Not ready yet
expect(prodResult.features['admin.analytics']).toBe('disabled'); // Not ready yet
});
it('should handle the new two-tier architecture correctly', async () => {
// Development should have all platform features enabled (alpha mode)
process.env.NODE_ENV = 'development';
const devResult = await loadFeatureConfig();
// All core platform features should be enabled
expect(devResult.features['platform.dashboard']).toBe('enabled');
expect(devResult.features['platform.leagues']).toBe('enabled');
expect(devResult.features['platform.teams']).toBe('enabled');
expect(devResult.features['platform.drivers']).toBe('enabled');
expect(devResult.features['platform.races']).toBe('enabled');
expect(devResult.features['platform.leaderboards']).toBe('enabled');
// All auth features should be enabled
expect(devResult.features['auth.signup']).toBe('enabled');
expect(devResult.features['auth.login']).toBe('enabled');
expect(devResult.features['auth.forgotPassword']).toBe('enabled');
expect(devResult.features['auth.resetPassword']).toBe('enabled');
// Onboarding should be enabled
expect(devResult.features['onboarding.wizard']).toBe('enabled');
});
it('should return flattened features with expected structure', async () => {
process.env.NODE_ENV = 'test';
const result = await loadFeatureConfig();
// Verify the result has the expected shape for API response
expect(result).toHaveProperty('features');
expect(result).toHaveProperty('loadedFrom');
expect(result).toHaveProperty('configPath');
// Verify features is a flat object with dot-notation keys
expect(typeof result.features).toBe('object');
expect(result.features['platform.dashboard']).toBeDefined();
expect(result.features['sponsors.portal']).toBeDefined();
});
});