feature flags
This commit is contained in:
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user