Files
kandev/apps/backend/internal/runtimeflags/registry_test.go
T
2026-08-02 15:36:21 +01:00

403 lines
13 KiB
Go

package runtimeflags
import (
"reflect"
"strings"
"testing"
"github.com/kandev/kandev/internal/common/config"
"github.com/kandev/kandev/internal/profiles"
)
type featureFieldBinding struct {
fieldIndex int
fieldName string
key string
profileKey string
envVar string
}
func TestDefinitionsIncludeOfficeExperimentalMetadata(t *testing.T) {
def, ok := DefinitionByKey("features.office")
if !ok {
t.Fatal("features.office definition missing")
}
if def.EnvVar != "KANDEV_FEATURES_OFFICE" {
t.Fatalf("EnvVar = %q, want KANDEV_FEATURES_OFFICE", def.EnvVar)
}
if def.Stability != StabilityExperimental {
t.Fatalf("Stability = %q, want %q", def.Stability, StabilityExperimental)
}
if def.RiskDescription == "" {
t.Fatal("RiskDescription empty")
}
if !def.RestartRequired {
t.Fatal("RestartRequired = false, want true")
}
}
// TestDefinitionsExcludePlugins pins the graduation of the plugin system out
// of the feature-flag tier: plugins ship in the base product, so no toggle may
// reappear in Settings > System > Feature Toggles.
func TestDefinitionsExcludePlugins(t *testing.T) {
if _, ok := DefinitionByKey("features.plugins"); ok {
t.Fatal("features.plugins definition present; plugins are a base feature and must not be toggleable")
}
for _, def := range Definitions() {
if def.EnvVar == "KANDEV_FEATURES_PLUGINS" {
t.Fatalf("definition %q still binds KANDEV_FEATURES_PLUGINS", def.Key)
}
}
}
func TestRetiredRuntimeFlagIdentitiesIncludePlugins(t *testing.T) {
for _, identity := range retiredRuntimeFlagIdentities {
if identity.key == "features.plugins" && identity.envVar == "KANDEV_FEATURES_PLUGINS" {
return
}
}
t.Fatal("graduated plugins flag identity is missing from the retired identity set")
}
func TestDefinitionsIncludeAppStatusBarMetadata(t *testing.T) {
def, ok := DefinitionByKey("features.appStatusBar")
if !ok {
t.Fatal("features.appStatusBar definition missing")
}
if def.EnvVar != "KANDEV_FEATURES_APP_STATUS_BAR" {
t.Fatalf("EnvVar = %q, want KANDEV_FEATURES_APP_STATUS_BAR", def.EnvVar)
}
if !def.RestartRequired {
t.Fatal("RestartRequired = false, want true")
}
}
func TestDefinitionsIncludeClaudeBackgroundPromptHandoffMetadata(t *testing.T) {
def, ok := DefinitionByKey("features.claudeBackgroundPromptHandoff")
if !ok {
t.Fatal("features.claudeBackgroundPromptHandoff definition missing")
}
if def.EnvVar != "KANDEV_FEATURES_CLAUDE_BACKGROUND_PROMPT_HANDOFF" {
t.Fatalf(
"EnvVar = %q, want KANDEV_FEATURES_CLAUDE_BACKGROUND_PROMPT_HANDOFF",
def.EnvVar,
)
}
if def.Stability != StabilityExperimental {
t.Fatalf("Stability = %q, want %q", def.Stability, StabilityExperimental)
}
if def.RiskLevel != RiskHigh {
t.Fatalf("RiskLevel = %q, want %q", def.RiskLevel, RiskHigh)
}
if def.RiskDescription == "" {
t.Fatal("RiskDescription empty")
}
if !def.RestartRequired {
t.Fatal("RestartRequired = false, want true")
}
if !def.Mutable {
t.Fatal("Mutable = false, want true")
}
}
func TestDefinitionsExposeSingleUserFacingDebugToggle(t *testing.T) {
def, ok := DefinitionByKey("debug.devMode")
if !ok {
t.Fatal("debug.devMode definition missing")
}
if def.EnvVar != "KANDEV_DEBUG_DEV_MODE" {
t.Fatalf("EnvVar = %q, want KANDEV_DEBUG_DEV_MODE", def.EnvVar)
}
if len(def.ImpliedEnvVars) == 0 {
t.Fatal("Debug mode should imply subordinate debug env vars")
}
if _, ok := DefinitionByKey("debug.agentMessages"); ok {
t.Fatal("debug.agentMessages must not be a top-level user-facing toggle")
}
}
func TestRuntimeFlagRegistrationsHaveCompleteMetadata(t *testing.T) {
retiredKeys, retiredEnvVars := retiredIdentitySets(t)
activeKeys := make(map[string]struct{}, len(registrations))
activeEnvVars := make(map[string]string, len(registrations))
for _, registration := range registrations {
definition := registration.definition
validateRuntimeFlagDefinition(t, definition)
if registration.read == nil || registration.apply == nil {
t.Fatalf("runtime flag registration %q is missing a config binding", definition.Key)
}
if _, exists := activeKeys[definition.Key]; exists {
t.Fatalf("duplicate runtime flag registration for %q", definition.Key)
}
activeKeys[definition.Key] = struct{}{}
if _, retired := retiredKeys[definition.Key]; retired {
t.Fatalf("active runtime flag %q reuses a retired key", definition.Key)
}
registerActiveEnvVar(t, activeEnvVars, retiredEnvVars, definition.Key, definition.EnvVar)
for _, envVar := range definition.ImpliedEnvVars {
registerActiveEnvVar(t, activeEnvVars, retiredEnvVars, definition.Key, envVar)
}
}
}
func TestRuntimeFlagConfigBindingsAreIsolated(t *testing.T) {
// Keep debug-mode's intentional environment side effects inert while this
// test exercises only the typed config bindings.
t.Setenv(envDebugAgentMessages, "explicit")
t.Setenv(envDebugPprofEnabled, "explicit")
for _, target := range registrations {
cfg := &config.Config{}
target.apply(cfg, true)
for _, registration := range registrations {
want := registration.definition.Key == target.definition.Key
if got := registration.read(cfg); got != want {
t.Fatalf(
"applying %q changed read value for %q to %t; want %t",
target.definition.Key,
registration.definition.Key,
got,
want,
)
}
}
target.apply(cfg, false)
for _, registration := range registrations {
if registration.read(cfg) {
t.Fatalf("disabling %q left %q enabled", target.definition.Key, registration.definition.Key)
}
}
}
}
// TestFeatureBindingsExactlyMatchConfigAndProfiles keeps the typed config,
// profile defaults, and feature registry in lockstep in both directions.
func TestFeatureBindingsExactlyMatchConfigAndProfiles(t *testing.T) {
defaults, err := profiles.FeatureFlagDefaults()
if err != nil {
t.Fatalf("FeatureFlagDefaults: %v", err)
}
fields := featureFieldBindings(t)
registered := registeredFeatureBindings(t)
for key, field := range fields {
registration, ok := registered[key]
if !ok {
t.Fatalf("FeaturesConfig.%s has no runtime flag registration for %q", field.fieldName, key)
}
if registration.definition.EnvVar != field.envVar {
t.Fatalf("FeaturesConfig.%s EnvVar = %q, want %q", field.fieldName, registration.definition.EnvVar, field.envVar)
}
if _, ok := defaults[field.profileKey]; !ok {
t.Fatalf("FeaturesConfig.%s has no profile default for %q", field.fieldName, field.profileKey)
}
}
for key := range registered {
if _, ok := fields[key]; !ok {
t.Fatalf("feature registration %q has no FeaturesConfig field", key)
}
}
for profileKey := range defaults {
if !hasProfileKey(fields, profileKey) {
t.Fatalf("profile feature %q has no FeaturesConfig field", profileKey)
}
}
if len(fields) != len(registered) || len(fields) != len(defaults) {
t.Fatalf(
"feature contract size mismatch: config=%d registry=%d profiles=%d",
len(fields),
len(registered),
len(defaults),
)
}
assertFeatureConfigRoundTrips(t, fields)
}
func retiredIdentitySets(t *testing.T) (map[string]struct{}, map[string]struct{}) {
t.Helper()
keys := make(map[string]struct{}, len(retiredRuntimeFlagIdentities))
envVars := make(map[string]struct{}, len(retiredRuntimeFlagIdentities))
for _, identity := range retiredRuntimeFlagIdentities {
if strings.TrimSpace(identity.key) == "" || strings.TrimSpace(identity.envVar) == "" {
t.Fatal("retired runtime flag identities must include both key and environment variable")
}
if _, exists := keys[identity.key]; exists {
t.Fatalf("duplicate retired runtime flag key %q", identity.key)
}
keys[identity.key] = struct{}{}
if _, exists := envVars[identity.envVar]; exists {
t.Fatalf("duplicate retired runtime flag environment variable %q", identity.envVar)
}
envVars[identity.envVar] = struct{}{}
}
return keys, envVars
}
func registerActiveEnvVar(
t *testing.T,
active map[string]string,
retired map[string]struct{},
key string,
envVar string,
) {
t.Helper()
if strings.TrimSpace(envVar) == "" {
t.Fatalf("runtime flag %q has an empty environment variable", key)
}
if owner, exists := active[envVar]; exists {
t.Fatalf("runtime flags %q and %q both use environment variable %q", owner, key, envVar)
}
if _, exists := retired[envVar]; exists {
t.Fatalf("active runtime flag %q reuses retired environment variable %q", key, envVar)
}
active[envVar] = key
}
func validateRuntimeFlagDefinition(t *testing.T, definition RuntimeFlagDefinition) {
t.Helper()
wantKeyPrefix, wantEnvPrefix := definitionPrefixes(t, definition.Kind)
if !strings.HasPrefix(definition.Key, wantKeyPrefix) || len(definition.Key) == len(wantKeyPrefix) {
t.Fatalf("runtime flag key %q must start with %q and include a name", definition.Key, wantKeyPrefix)
}
if !strings.HasPrefix(definition.EnvVar, wantEnvPrefix) || len(definition.EnvVar) == len(wantEnvPrefix) {
t.Fatalf("runtime flag %q environment variable %q must start with %q", definition.Key, definition.EnvVar, wantEnvPrefix)
}
for name, value := range map[string]string{
"label": definition.Label, "description": definition.Description, "risk description": definition.RiskDescription,
} {
if strings.TrimSpace(value) == "" {
t.Fatalf("runtime flag %q has an empty %s", definition.Key, name)
}
}
if !validStability(definition.Stability) {
t.Fatalf("runtime flag %q has invalid stability %q", definition.Key, definition.Stability)
}
if !validRiskLevel(definition.RiskLevel) {
t.Fatalf("runtime flag %q has invalid risk level %q", definition.Key, definition.RiskLevel)
}
if !definition.RestartRequired {
t.Fatalf("runtime flag %q must declare restart_required", definition.Key)
}
if !definition.Mutable {
t.Fatalf("runtime flag %q must declare mutable", definition.Key)
}
}
func definitionPrefixes(t *testing.T, kind RuntimeFlagKind) (string, string) {
t.Helper()
switch kind {
case KindFeature:
return "features.", "KANDEV_FEATURES_"
case KindDebug:
return "debug.", "KANDEV_DEBUG_"
default:
t.Fatalf("invalid runtime flag kind %q", kind)
return "", ""
}
}
func validStability(stability RuntimeFlagStability) bool {
switch stability {
case StabilityStable, StabilityBeta, StabilityExperimental:
return true
default:
return false
}
}
func validRiskLevel(risk RuntimeFlagRiskLevel) bool {
switch risk {
case RiskLow, RiskMedium, RiskHigh:
return true
default:
return false
}
}
func featureFieldBindings(t *testing.T) map[string]featureFieldBinding {
t.Helper()
typeOfFeatures := reflect.TypeOf(config.FeaturesConfig{})
fields := make(map[string]featureFieldBinding, typeOfFeatures.NumField())
for i := 0; i < typeOfFeatures.NumField(); i++ {
field := typeOfFeatures.Field(i)
if field.Type.Kind() != reflect.Bool {
t.Fatalf("FeaturesConfig.%s has type %s; feature flags must be bool", field.Name, field.Type)
}
jsonName := requiredTagName(t, field, "json")
profileKey := requiredTagName(t, field, "mapstructure")
key := "features." + jsonName
if _, exists := fields[key]; exists {
t.Fatalf("duplicate FeaturesConfig JSON key %q", jsonName)
}
fields[key] = featureFieldBinding{
fieldIndex: i,
fieldName: field.Name,
key: key,
profileKey: profileKey,
envVar: "KANDEV_FEATURES_" + strings.ToUpper(profileKey),
}
}
return fields
}
func requiredTagName(t *testing.T, field reflect.StructField, tag string) string {
t.Helper()
name := strings.Split(field.Tag.Get(tag), ",")[0]
if name == "" || name == "-" {
t.Fatalf("FeaturesConfig.%s is missing a %s name", field.Name, tag)
}
return name
}
func registeredFeatureBindings(t *testing.T) map[string]runtimeFlagRegistration {
t.Helper()
registered := make(map[string]runtimeFlagRegistration)
for _, registration := range registrations {
definition := registration.definition
if definition.Kind != KindFeature && !strings.HasPrefix(definition.Key, "features.") {
continue
}
if _, exists := registered[definition.Key]; exists {
t.Fatalf("duplicate feature registration %q", definition.Key)
}
registered[definition.Key] = registration
}
return registered
}
func hasProfileKey(fields map[string]featureFieldBinding, profileKey string) bool {
for _, field := range fields {
if field.profileKey == profileKey {
return true
}
}
return false
}
func assertFeatureConfigRoundTrips(t *testing.T, fields map[string]featureFieldBinding) {
t.Helper()
typeOfFeatures := reflect.TypeOf(config.FeaturesConfig{})
for key, field := range fields {
cfg := &config.Config{}
features := reflect.ValueOf(&cfg.Features).Elem()
ApplyStatesToConfig(cfg, []RuntimeFlagState{{Key: key, EffectiveValue: true}})
if !features.Field(field.fieldIndex).Bool() {
t.Fatalf("ApplyStatesToConfig(%q) did not enable target field %s", key, field.fieldName)
}
for i := 0; i < features.NumField(); i++ {
if i != field.fieldIndex && features.Field(i).Bool() {
t.Fatalf("ApplyStatesToConfig(%q) changed unrelated field %s", key, typeOfFeatures.Field(i).Name)
}
}
if got := ValuesFromConfig(cfg)[key]; !got {
t.Fatalf("ValuesFromConfig(%q) = false after enabling %s", key, field.fieldName)
}
ApplyStatesToConfig(cfg, []RuntimeFlagState{{Key: key, EffectiveValue: false}})
if features.Field(field.fieldIndex).Bool() || ValuesFromConfig(cfg)[key] {
t.Fatalf("ApplyStatesToConfig(%q) did not disable target field %s", key, field.fieldName)
}
}
}