Skip to content

Commit f0617b1

Browse files
authored
fix: ensure compatibility with SDKs with default environment (#4857)
Signed-off-by: Roman Dmytrenko <[email protected]>
1 parent 00d34a1 commit f0617b1

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed

internal/server/environments/storage.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ func (e *EnvironmentStore) GetFromContext(ctx context.Context) (Environment, err
232232
if ok {
233233
ee, err := e.Get(ctx, env)
234234
if err != nil {
235+
// If the requested environment is "default" and it doesn't exist,
236+
// fall back to the default environment instead of returning an error.
237+
// This ensures compatibility with SDKs that explicitly set environment="default"
238+
// when the actual default environment has a different key.
239+
if env == flipt.DefaultEnvironment {
240+
return e.defaultEnv, nil
241+
}
235242
return nil, fmt.Errorf("failed to get environment %q from context: %w", env, err)
236243
}
237244
return ee, nil
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package environments
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"go.flipt.io/flipt/internal/common"
10+
"go.flipt.io/flipt/rpc/flipt"
11+
"go.uber.org/zap/zaptest"
12+
)
13+
14+
func TestEnvironmentStore_GetFromContext_DefaultFallback(t *testing.T) {
15+
logger := zaptest.NewLogger(t)
16+
17+
// Create a mock environment with default: true but key != "default"
18+
mockEnv := NewMockEnvironment(t)
19+
mockEnv.On("Key").Return("production")
20+
mockEnv.On("Default").Return(true)
21+
22+
store, err := NewEnvironmentStore(logger, mockEnv)
23+
require.NoError(t, err)
24+
require.NotNil(t, store)
25+
26+
tests := []struct {
27+
name string
28+
setupContext func() context.Context
29+
expectedKey string
30+
expectError bool
31+
errorContains string
32+
}{
33+
{
34+
name: "no environment in context returns default environment",
35+
setupContext: t.Context,
36+
expectedKey: "production",
37+
expectError: false,
38+
},
39+
{
40+
name: "explicit 'default' in context falls back to default environment when no 'default' key exists",
41+
setupContext: func() context.Context {
42+
return common.WithFliptEnvironment(t.Context(), flipt.DefaultEnvironment)
43+
},
44+
expectedKey: "production",
45+
expectError: false,
46+
},
47+
{
48+
name: "explicit 'production' in context returns production environment",
49+
setupContext: func() context.Context {
50+
return common.WithFliptEnvironment(t.Context(), "production")
51+
},
52+
expectedKey: "production",
53+
expectError: false,
54+
},
55+
{
56+
name: "non-existent environment in context returns error",
57+
setupContext: func() context.Context {
58+
return common.WithFliptEnvironment(t.Context(), "non-existent")
59+
},
60+
expectError: true,
61+
errorContains: "non-existent",
62+
},
63+
}
64+
65+
for _, tt := range tests {
66+
t.Run(tt.name, func(t *testing.T) {
67+
ctx := tt.setupContext()
68+
env, err := store.GetFromContext(ctx)
69+
70+
if tt.expectError {
71+
require.Error(t, err)
72+
assert.Contains(t, err.Error(), tt.errorContains)
73+
} else {
74+
require.NoError(t, err)
75+
require.NotNil(t, env)
76+
assert.Equal(t, tt.expectedKey, env.Key())
77+
}
78+
})
79+
}
80+
}
81+
82+
func TestEnvironmentStore_GetFromContext_WithActualDefaultEnvironment(t *testing.T) {
83+
logger := zaptest.NewLogger(t)
84+
85+
// Create an environment with key "default"
86+
mockDefaultEnv := NewMockEnvironment(t)
87+
mockDefaultEnv.On("Key").Return("default")
88+
mockDefaultEnv.On("Default").Return(false)
89+
90+
// Create another environment with default: true
91+
mockProdEnv := NewMockEnvironment(t)
92+
mockProdEnv.On("Key").Return("production")
93+
mockProdEnv.On("Default").Return(true)
94+
95+
store, err := NewEnvironmentStore(logger, mockDefaultEnv, mockProdEnv)
96+
require.NoError(t, err)
97+
require.NotNil(t, store)
98+
99+
tests := []struct {
100+
name string
101+
contextEnv string
102+
expectedKey string
103+
}{
104+
{
105+
name: "explicit 'default' returns 'default' environment",
106+
contextEnv: "default",
107+
expectedKey: "default",
108+
},
109+
{
110+
name: "explicit 'production' returns 'production' environment",
111+
contextEnv: "production",
112+
expectedKey: "production",
113+
},
114+
{
115+
name: "no context environment returns default (production)",
116+
contextEnv: "",
117+
expectedKey: "production",
118+
},
119+
}
120+
121+
for _, tt := range tests {
122+
t.Run(tt.name, func(t *testing.T) {
123+
ctx := t.Context()
124+
if tt.contextEnv != "" {
125+
ctx = common.WithFliptEnvironment(ctx, tt.contextEnv)
126+
}
127+
128+
env, err := store.GetFromContext(ctx)
129+
require.NoError(t, err)
130+
require.NotNil(t, env)
131+
assert.Equal(t, tt.expectedKey, env.Key())
132+
})
133+
}
134+
}
135+
136+
func TestEnvironmentStore_NewEnvironmentStore_DefaultSelection(t *testing.T) {
137+
logger := zaptest.NewLogger(t)
138+
139+
tests := []struct {
140+
name string
141+
environments []Environment
142+
expectedDefault string
143+
expectError bool
144+
}{
145+
{
146+
name: "environment with default: true is selected",
147+
environments: func() []Environment {
148+
mockEnv1 := NewMockEnvironment(t)
149+
mockEnv1.On("Key").Return("staging")
150+
mockEnv1.On("Default").Return(false)
151+
152+
mockEnv2 := NewMockEnvironment(t)
153+
mockEnv2.On("Key").Return("production")
154+
mockEnv2.On("Default").Return(true)
155+
156+
return []Environment{mockEnv1, mockEnv2}
157+
}(),
158+
expectedDefault: "production",
159+
},
160+
{
161+
name: "environment named 'default' is used when no default: true",
162+
environments: func() []Environment {
163+
mockEnv := NewMockEnvironment(t)
164+
mockEnv.On("Key").Return("default")
165+
mockEnv.On("Default").Return(false)
166+
167+
return []Environment{mockEnv}
168+
}(),
169+
expectedDefault: "default",
170+
},
171+
{
172+
name: "single environment is used as default",
173+
environments: func() []Environment {
174+
mockEnv := NewMockEnvironment(t)
175+
mockEnv.On("Key").Return("production")
176+
mockEnv.On("Default").Return(false)
177+
178+
return []Environment{mockEnv}
179+
}(),
180+
expectedDefault: "production",
181+
},
182+
{
183+
name: "error when multiple environments and no default",
184+
environments: func() []Environment {
185+
mockEnv1 := NewMockEnvironment(t)
186+
mockEnv1.On("Key").Return("staging")
187+
mockEnv1.On("Default").Return(false)
188+
189+
mockEnv2 := NewMockEnvironment(t)
190+
mockEnv2.On("Key").Return("production")
191+
mockEnv2.On("Default").Return(false)
192+
193+
return []Environment{mockEnv1, mockEnv2}
194+
}(),
195+
expectError: true,
196+
},
197+
}
198+
199+
for _, tt := range tests {
200+
t.Run(tt.name, func(t *testing.T) {
201+
store, err := NewEnvironmentStore(logger, tt.environments...)
202+
203+
if tt.expectError {
204+
require.Error(t, err)
205+
} else {
206+
require.NoError(t, err)
207+
require.NotNil(t, store)
208+
assert.Equal(t, tt.expectedDefault, store.defaultEnv.Key())
209+
}
210+
})
211+
}
212+
}

0 commit comments

Comments
 (0)