Skip to content

Commit df7fed0

Browse files
authored
Merge pull request #7 from bucketeer-io/add/e2e
ci: add e2e test
2 parents b2eb993 + 1b8d63b commit df7fed0

File tree

8 files changed

+651
-9
lines changed

8 files changed

+651
-9
lines changed

.github/workflows/e2e.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: e2e
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
branches:
7+
- main
8+
push:
9+
branches:
10+
- main
11+
paths-ignore:
12+
- "**/**.md"
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
e2e:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
23+
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
24+
with:
25+
go-version-file: 'go.mod'
26+
cache: true
27+
- name: E2E Test
28+
run: API_KEY=${{ secrets.API_KEY_FOR_E2E }} API_KEY_SERVER=${{ secrets.API_KEY_SERVER_FOR_E2E }} API_ENDPOINT=${{ secrets.API_ENDPOINT_FOR_E2E }} SCHEME=https make e2e

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
*.out
1616

1717
# Dependency directories (remove the comment below to include it)
18-
# vendor/
18+
vendor/
1919

2020
# Go workspace file
2121
go.work
2222
go.work.sum
2323

2424
# env file
25-
.env
25+
.env

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ build:
3434
test:
3535
go test -v -race ./pkg/...
3636

37+
.PHONY: e2e
38+
e2e:
39+
go test -v -race ./test/e2e/... \
40+
-args -api-key=${API_KEY} -api-key-server=${API_KEY_SERVER} -api-endpoint=${API_ENDPOINT} -scheme=${SCHEME}

pkg/provider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func toBucketeerUser(evalCtx openfeature.FlattenedContext) (user.User, *openfeat
299299
if !ok {
300300
return user.User{},
301301
ToPtr(openfeature.NewTargetingKeyMissingResolutionError(
302-
fmt.Sprintf("key %q, value %q can not be converted to string", key, val),
302+
fmt.Sprintf("key %q, value %v can not be converted to string", key, val),
303303
),
304304
)
305305
}

pkg/provider_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func TestBooleanEvaluation(t *testing.T) {
143143
defaultValue: false,
144144
expectedValue: false,
145145
expectedReason: openfeature.ErrorReason,
146-
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value '{' can not be converted to string`),
146+
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value 123 can not be converted to string`),
147147
failToBucketeerUser: false,
148148
boolEvaluation: model.BKTEvaluationDetails[bool]{
149149
FeatureID: "bool-flag",
@@ -326,7 +326,7 @@ func TestStringEvaluation(t *testing.T) {
326326
defaultValue: "default-value",
327327
expectedValue: "default-value",
328328
expectedReason: openfeature.ErrorReason,
329-
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value '{' can not be converted to string`),
329+
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value 123 can not be converted to string`),
330330
failToBucketeerUser: false,
331331
stringEvaluation: model.BKTEvaluationDetails[string]{
332332
FeatureID: "string-flag",
@@ -434,7 +434,7 @@ func TestIntEvaluation(t *testing.T) {
434434
defaultValue: 0,
435435
expectedValue: 0,
436436
expectedReason: openfeature.ErrorReason,
437-
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value '{' can not be converted to string`),
437+
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value 123 can not be converted to string`),
438438
failToBucketeerUser: false,
439439
int64Evaluation: model.BKTEvaluationDetails[int64]{
440440
FeatureID: "int-flag",
@@ -520,7 +520,7 @@ func TestFloatEvaluation(t *testing.T) {
520520
defaultValue: 0.0,
521521
expectedValue: 0.0,
522522
expectedReason: openfeature.ErrorReason,
523-
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value '{' can not be converted to string`),
523+
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value 123 can not be converted to string`),
524524
failToBucketeerUser: false,
525525
float64Evaluation: model.BKTEvaluationDetails[float64]{
526526
FeatureID: "float-flag",
@@ -614,7 +614,7 @@ func TestObjectEvaluation(t *testing.T) {
614614
defaultValue: map[string]interface{}{"default": true},
615615
expectedValue: map[string]interface{}{"default": true},
616616
expectedReason: openfeature.ErrorReason,
617-
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value '{' can not be converted to string`),
617+
expectedResolutionError: openfeature.NewTargetingKeyMissingResolutionError(`key "targetingKey", value 123 can not be converted to string`),
618618
failToBucketeerUser: false,
619619
objectEvaluation: model.BKTEvaluationDetails[interface{}]{
620620
FeatureID: "object-flag",
@@ -696,7 +696,7 @@ func TestToBucketeerUser(t *testing.T) {
696696
evalCtx: openfeature.FlattenedContext{
697697
openfeature.TargetingKey: 123,
698698
},
699-
expectedErr: errors.New(`TARGETING_KEY_MISSING: key "targetingKey", value '{' can not be converted to string`),
699+
expectedErr: errors.New(`TARGETING_KEY_MISSING: key "targetingKey", value 123 can not be converted to string`),
700700
},
701701
{
702702
desc: "empty context",

test/e2e/provider_local_test.go

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/bucketeer-io/go-server-sdk/pkg/bucketeer"
9+
provider "github.com/bucketeer-io/openfeature-go-server-sdk/pkg"
10+
"github.com/open-feature/go-sdk/openfeature"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func setupProviderForLocal(t *testing.T, ctx context.Context) *provider.Provider {
15+
t.Helper()
16+
options := []bucketeer.Option{
17+
bucketeer.WithCachePollingInterval(5 * time.Second),
18+
bucketeer.WithEnableLocalEvaluation(true),
19+
bucketeer.WithTag(tag),
20+
bucketeer.WithAPIKey(*apiKeyServer),
21+
bucketeer.WithAPIEndpoint(*apiEndpoint),
22+
bucketeer.WithScheme(*scheme),
23+
bucketeer.WithEventQueueCapacity(100),
24+
bucketeer.WithNumEventFlushWorkers(3),
25+
bucketeer.WithEventFlushSize(1),
26+
bucketeer.WithWrapperSDKVersion(sdkVersion),
27+
bucketeer.WithWrapperSourceID(sourceID),
28+
}
29+
30+
p, err := provider.NewProviderWithContext(ctx, options)
31+
assert.NoError(t, err)
32+
return p
33+
}
34+
35+
func TestLocalStringEvaluation(t *testing.T) {
36+
t.Parallel()
37+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
38+
defer cancel()
39+
p := setupProviderForLocal(t, ctx)
40+
tests := []struct {
41+
desc string
42+
userID string
43+
flagID string
44+
defaultValue string
45+
expectedValue string
46+
expectedReason openfeature.Reason
47+
}{
48+
{
49+
desc: "Evaluation by default user",
50+
userID: "user-1",
51+
flagID: featureIDString,
52+
defaultValue: "default",
53+
expectedValue: featureIDStringVariation1,
54+
expectedReason: openfeature.DefaultReason,
55+
},
56+
{
57+
desc: "Evaluation by target user",
58+
userID: targetUserID,
59+
flagID: featureIDString,
60+
defaultValue: "default",
61+
expectedValue: featureIDStringTargetVariation,
62+
expectedReason: openfeature.TargetingMatchReason,
63+
},
64+
{
65+
desc: "Evaluation by Segment user",
66+
userID: targetSegmentUserID,
67+
flagID: featureIDString,
68+
defaultValue: "default",
69+
expectedValue: featureIDStringVariation3,
70+
expectedReason: openfeature.TargetingMatchReason,
71+
},
72+
}
73+
74+
time.Sleep(10 * time.Second) // Wait for the cache updates
75+
76+
for _, tt := range tests {
77+
t.Run(tt.desc, func(t *testing.T) {
78+
t.Parallel()
79+
evalCtx := createEvalContext(tt.userID)
80+
result := p.StringEvaluation(ctx, tt.flagID, tt.defaultValue, evalCtx)
81+
82+
assert.NotNil(t, result)
83+
assert.Equal(t, tt.expectedValue, result.Value, "userID: %s, flagID: %s", tt.userID, tt.flagID)
84+
assert.Equal(t, tt.expectedReason, result.Reason, "userID: %s, flagID: %s", tt.userID, tt.flagID)
85+
})
86+
}
87+
}
88+
89+
func TestLocalBoolEvaluation(t *testing.T) {
90+
t.Parallel()
91+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
92+
defer cancel()
93+
p := setupProviderForLocal(t, ctx)
94+
tests := []struct {
95+
desc string
96+
userID string
97+
flagID string
98+
defaultValue bool
99+
expectedValue bool
100+
expectedReason openfeature.Reason
101+
}{
102+
{
103+
desc: "Evaluation by default user",
104+
userID: "user-1",
105+
flagID: featureIDBoolean,
106+
defaultValue: false,
107+
expectedValue: true,
108+
expectedReason: openfeature.DefaultReason,
109+
},
110+
{
111+
desc: "Evaluation by target user",
112+
userID: targetUserID,
113+
flagID: featureIDBoolean,
114+
defaultValue: false,
115+
expectedValue: featureIDBooleanTargetVariation,
116+
expectedReason: openfeature.TargetingMatchReason,
117+
},
118+
}
119+
120+
time.Sleep(10 * time.Second) // Wait for the cache updates
121+
122+
for _, tt := range tests {
123+
t.Run(tt.desc, func(t *testing.T) {
124+
t.Parallel()
125+
evalCtx := createEvalContext(tt.userID)
126+
result := p.BooleanEvaluation(ctx, tt.flagID, tt.defaultValue, evalCtx)
127+
128+
assert.NotNil(t, result)
129+
assert.Equal(t, tt.expectedValue, result.Value, "userID: %s, flagID: %s", tt.userID, tt.flagID)
130+
assert.Equal(t, tt.expectedReason, result.Reason, "userID: %s, flagID: %s", tt.userID, tt.flagID)
131+
})
132+
}
133+
}
134+
135+
func TestLocalIntEvaluation(t *testing.T) {
136+
t.Parallel()
137+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
138+
defer cancel()
139+
p := setupProviderForLocal(t, ctx)
140+
141+
tests := []struct {
142+
desc string
143+
userID string
144+
flagID string
145+
defaultValue int64
146+
expectedValue int64
147+
expectedReason openfeature.Reason
148+
}{
149+
{
150+
desc: "Evaluation by default user",
151+
userID: "user-1",
152+
flagID: featureIDInt64,
153+
defaultValue: -1000000000,
154+
expectedValue: featureIDInt64Variation1,
155+
expectedReason: openfeature.DefaultReason,
156+
},
157+
{
158+
desc: "Evaluation by target user",
159+
userID: targetUserID,
160+
flagID: featureIDInt64,
161+
defaultValue: -1000000000,
162+
expectedValue: featureIDInt64TargetVariation,
163+
expectedReason: openfeature.TargetingMatchReason,
164+
},
165+
}
166+
167+
time.Sleep(10 * time.Second) // Wait for the cache updates
168+
169+
for _, tt := range tests {
170+
t.Run(tt.desc, func(t *testing.T) {
171+
t.Parallel()
172+
evalCtx := createEvalContext(tt.userID)
173+
result := p.IntEvaluation(ctx, tt.flagID, tt.defaultValue, evalCtx)
174+
175+
assert.NotNil(t, result)
176+
assert.Equal(t, tt.expectedValue, result.Value, "userID: %s, flagID: %s", tt.userID, tt.flagID)
177+
assert.Equal(t, tt.expectedReason, result.Reason, "userID: %s, flagID: %s", tt.userID, tt.flagID)
178+
})
179+
}
180+
}
181+
182+
func TestLocalFloatEvaluation(t *testing.T) {
183+
t.Parallel()
184+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
185+
defer cancel()
186+
p := setupProviderForLocal(t, ctx)
187+
188+
tests := []struct {
189+
desc string
190+
userID string
191+
flagID string
192+
defaultValue float64
193+
expectedValue float64
194+
expectedReason openfeature.Reason
195+
}{
196+
{
197+
desc: "Evaluation by default user",
198+
userID: "user-1",
199+
flagID: featureIDFloat,
200+
defaultValue: -1.1,
201+
expectedValue: featureIDFloatVariation1,
202+
expectedReason: openfeature.DefaultReason,
203+
},
204+
{
205+
desc: "Evaluation by target user",
206+
userID: targetUserID,
207+
flagID: featureIDFloat,
208+
defaultValue: -1.1,
209+
expectedValue: featureIDFloatTargetVariation,
210+
expectedReason: openfeature.TargetingMatchReason,
211+
},
212+
}
213+
214+
time.Sleep(10 * time.Second) // Wait for the cache updates
215+
216+
for _, tt := range tests {
217+
t.Run(tt.desc, func(t *testing.T) {
218+
evalCtx := createEvalContext(tt.userID)
219+
result := p.FloatEvaluation(ctx, tt.flagID, tt.defaultValue, evalCtx)
220+
221+
assert.NotNil(t, result)
222+
assert.Equal(t, tt.expectedValue, result.Value, "userID: %s, flagID: %s", tt.userID, tt.flagID)
223+
assert.Equal(t, tt.expectedReason, result.Reason, "userID: %s, flagID: %s", tt.userID, tt.flagID)
224+
})
225+
}
226+
}
227+
228+
func TestLocalObjectEvaluation(t *testing.T) {
229+
t.Parallel()
230+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
231+
defer cancel()
232+
p := setupProviderForLocal(t, ctx)
233+
tests := []struct {
234+
desc string
235+
userID string
236+
flagID string
237+
defaultValue interface{}
238+
expectedValue interface{}
239+
expectedReason openfeature.Reason
240+
}{
241+
{
242+
desc: "Evaluation by default user",
243+
userID: "user-1",
244+
flagID: featureIDJson,
245+
defaultValue: map[string]interface{}{
246+
"name": "default-object",
247+
"value": 0,
248+
},
249+
expectedValue: map[string]interface{}{"str": "str1", "int": "int1"},
250+
expectedReason: openfeature.DefaultReason,
251+
},
252+
{
253+
desc: "Evaluation by target user",
254+
userID: targetUserID,
255+
flagID: featureIDJson,
256+
defaultValue: map[string]interface{}{
257+
"name": "default-object",
258+
"value": 0,
259+
},
260+
expectedValue: map[string]interface{}{"str": "str2", "int": "int2"},
261+
expectedReason: openfeature.TargetingMatchReason,
262+
},
263+
}
264+
time.Sleep(10 * time.Second) // Wait for the cache updates
265+
266+
for _, tt := range tests {
267+
t.Run(tt.desc, func(t *testing.T) {
268+
t.Parallel()
269+
evalCtx := createEvalContext(tt.userID)
270+
result := p.ObjectEvaluation(ctx, tt.flagID, tt.defaultValue, evalCtx)
271+
272+
assert.NotNil(t, result)
273+
assert.Equal(t, tt.expectedValue, result.Value, "userID: %s, flagID: %s", tt.userID, tt.flagID)
274+
assert.Equal(t, tt.expectedReason, result.Reason, "userID: %s, flagID: %s", tt.userID, tt.flagID)
275+
})
276+
}
277+
}

0 commit comments

Comments
 (0)