feature flags

This commit is contained in:
2026-01-03 12:07:20 +01:00
parent 9a7efa496f
commit 213580511c
5 changed files with 289 additions and 26 deletions

View File

@@ -0,0 +1,144 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { FeatureFlagService, MockFeatureFlagService, mockFeatureFlags } from './FeatureFlagService';
describe('FeatureFlagService', () => {
describe('fromEnv() with alpha mode integration', () => {
let originalMode: string | undefined;
let originalFlags: string | undefined;
beforeEach(() => {
originalMode = process.env.NEXT_PUBLIC_GRIDPILOT_MODE;
originalFlags = process.env.FEATURE_FLAGS;
});
afterEach(() => {
if (originalMode !== undefined) {
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = originalMode;
} else {
delete process.env.NEXT_PUBLIC_GRIDPILOT_MODE;
}
if (originalFlags !== undefined) {
process.env.FEATURE_FLAGS = originalFlags;
} else {
delete process.env.FEATURE_FLAGS;
}
});
it('should enable all features when NEXT_PUBLIC_GRIDPILOT_MODE is alpha', () => {
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'alpha';
const service = FeatureFlagService.fromEnv();
expect(service.isEnabled('driver_profiles')).toBe(true);
expect(service.isEnabled('team_profiles')).toBe(true);
expect(service.isEnabled('wallets')).toBe(true);
expect(service.isEnabled('sponsors')).toBe(true);
expect(service.isEnabled('team_feature')).toBe(true);
expect(service.isEnabled('alpha_features')).toBe(true);
});
it('should enable no features when NEXT_PUBLIC_GRIDPILOT_MODE is pre-launch', () => {
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'pre-launch';
const service = FeatureFlagService.fromEnv();
expect(service.isEnabled('driver_profiles')).toBe(false);
expect(service.isEnabled('team_profiles')).toBe(false);
expect(service.isEnabled('wallets')).toBe(false);
expect(service.isEnabled('sponsors')).toBe(false);
expect(service.isEnabled('team_feature')).toBe(false);
expect(service.isEnabled('alpha_features')).toBe(false);
});
it('should enable no features when NEXT_PUBLIC_GRIDPILOT_MODE is not set', () => {
delete process.env.NEXT_PUBLIC_GRIDPILOT_MODE;
const service = FeatureFlagService.fromEnv();
expect(service.isEnabled('driver_profiles')).toBe(false);
expect(service.isEnabled('team_profiles')).toBe(false);
expect(service.isEnabled('wallets')).toBe(false);
expect(service.isEnabled('sponsors')).toBe(false);
expect(service.isEnabled('team_feature')).toBe(false);
expect(service.isEnabled('alpha_features')).toBe(false);
});
it('should allow FEATURE_FLAGS to override alpha mode', () => {
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'alpha';
process.env.FEATURE_FLAGS = 'driver_profiles,wallets';
const service = FeatureFlagService.fromEnv();
expect(service.isEnabled('driver_profiles')).toBe(true);
expect(service.isEnabled('wallets')).toBe(true);
expect(service.isEnabled('team_profiles')).toBe(false);
expect(service.isEnabled('sponsors')).toBe(false);
expect(service.isEnabled('team_feature')).toBe(false);
});
it('should return correct list of enabled flags in alpha mode', () => {
process.env.NEXT_PUBLIC_GRIDPILOT_MODE = 'alpha';
const service = FeatureFlagService.fromEnv();
const enabledFlags = service.getEnabledFlags();
expect(enabledFlags).toContain('driver_profiles');
expect(enabledFlags).toContain('team_profiles');
expect(enabledFlags).toContain('wallets');
expect(enabledFlags).toContain('sponsors');
expect(enabledFlags).toContain('team_feature');
expect(enabledFlags).toContain('alpha_features');
expect(enabledFlags.length).toBe(6);
});
});
describe('Constructor behavior', () => {
it('should use provided flags array', () => {
const service = new FeatureFlagService(['test_flag']);
expect(service.isEnabled('test_flag')).toBe(true);
expect(service.isEnabled('other_flag')).toBe(false);
});
it('should parse FEATURE_FLAGS environment variable', () => {
process.env.FEATURE_FLAGS = 'flag1, flag2, flag3';
const service = new FeatureFlagService();
expect(service.isEnabled('flag1')).toBe(true);
expect(service.isEnabled('flag2')).toBe(true);
expect(service.isEnabled('flag3')).toBe(true);
expect(service.isEnabled('flag4')).toBe(false);
delete process.env.FEATURE_FLAGS;
});
it('should handle empty FEATURE_FLAGS', () => {
process.env.FEATURE_FLAGS = '';
const service = new FeatureFlagService();
expect(service.isEnabled('any_flag')).toBe(false);
expect(service.getEnabledFlags()).toEqual([]);
delete process.env.FEATURE_FLAGS;
});
});
});
describe('MockFeatureFlagService', () => {
it('should work with provided flags', () => {
const service = new MockFeatureFlagService(['test_flag']);
expect(service.isEnabled('test_flag')).toBe(true);
expect(service.isEnabled('other_flag')).toBe(false);
});
it('should return empty array when no flags provided', () => {
const service = new MockFeatureFlagService();
expect(service.getEnabledFlags()).toEqual([]);
});
});
describe('mockFeatureFlags default instance', () => {
it('should have alpha_features enabled by default', () => {
expect(mockFeatureFlags.isEnabled('alpha_features')).toBe(true);
});
});

View File

@@ -1,7 +1,12 @@
/**
* FeatureFlagService - Manages feature flags for both server and client
*
*
* Automatic Alpha Mode Integration:
* When NEXT_PUBLIC_GRIDPILOT_MODE=alpha, all features are automatically enabled.
* This eliminates the need to manually set FEATURE_FLAGS for alpha deployments.
*
* Server: Reads from process.env.FEATURE_FLAGS (comma-separated)
* OR auto-enables all features if in alpha mode
* Client: Reads from session context or provides mock implementation
*/
@@ -15,7 +20,7 @@ export class FeatureFlagService {
} else {
// Parse from environment variable
const flagsEnv = process.env.FEATURE_FLAGS;
this.flags = flagsEnv
this.flags = flagsEnv
? new Set(flagsEnv.split(',').map(f => f.trim()))
: new Set();
}
@@ -37,8 +42,31 @@ export class FeatureFlagService {
/**
* Factory method to create service with environment flags
* Automatically enables all features if in alpha mode
* FEATURE_FLAGS can override alpha mode defaults
*/
static fromEnv(): FeatureFlagService {
const mode = process.env.NEXT_PUBLIC_GRIDPILOT_MODE;
const flagsEnv = process.env.FEATURE_FLAGS;
// If FEATURE_FLAGS is explicitly set, use it (overrides alpha mode)
if (flagsEnv) {
return new FeatureFlagService();
}
// If in alpha mode, automatically enable all features
if (mode === 'alpha') {
return new FeatureFlagService([
'driver_profiles',
'team_profiles',
'wallets',
'sponsors',
'team_feature',
'alpha_features'
]);
}
// Otherwise, use FEATURE_FLAGS environment variable (empty if not set)
return new FeatureFlagService();
}
}
@@ -67,4 +95,12 @@ export class MockFeatureFlagService implements FeatureFlagContextType {
}
// Default mock instance for client-side usage
export const mockFeatureFlags = new MockFeatureFlagService(['alpha_features']);
// Enables all features for development/demo mode
export const mockFeatureFlags = new MockFeatureFlagService([
'driver_profiles',
'team_profiles',
'wallets',
'sponsors',
'team_feature',
'alpha_features'
]);