Skip to content

Commit 1bae773

Browse files
chore: Coverage increase (thomaspoignant#2478)
1 parent afb2bef commit 1bae773

File tree

12 files changed

+249
-92
lines changed

12 files changed

+249
-92
lines changed

cmd/relayproxy/api/server_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package api_test
22

33
import (
44
"net/http"
5+
"strings"
56
"testing"
67
"time"
78

@@ -133,3 +134,86 @@ func Test_Starting_RelayProxy_with_monitoring_on_different_port(t *testing.T) {
133134
assert.NoError(t, err)
134135
assert.Equal(t, http.StatusOK, responseI1.StatusCode)
135136
}
137+
138+
func Test_CheckOFREPAPIExists(t *testing.T) {
139+
proxyConf := &config.Config{
140+
Retriever: &config.RetrieverConf{
141+
Kind: "file",
142+
Path: "../../../testdata/flag-config.yaml",
143+
},
144+
ListenPort: 11024,
145+
AuthorizedKeys: config.APIKeys{
146+
Admin: nil,
147+
Evaluation: []string{"test"},
148+
},
149+
}
150+
log := log.InitLogger()
151+
defer func() { _ = log.ZapLogger.Sync() }()
152+
153+
metricsV2, err := metric.NewMetrics()
154+
if err != nil {
155+
log.ZapLogger.Error("impossible to initialize prometheus metrics", zap.Error(err))
156+
}
157+
wsService := service.NewWebsocketService()
158+
defer wsService.Close() // close all the open connections
159+
prometheusNotifier := metric.NewPrometheusNotifier(metricsV2)
160+
proxyNotifier := service.NewNotifierWebsocket(wsService)
161+
goff, err := service.NewGoFeatureFlagClient(proxyConf, log.ZapLogger, []notifier.Notifier{
162+
prometheusNotifier,
163+
proxyNotifier,
164+
})
165+
if err != nil {
166+
panic(err)
167+
}
168+
169+
services := service.Services{
170+
MonitoringService: service.NewMonitoring(goff),
171+
WebsocketService: wsService,
172+
GOFeatureFlagService: goff,
173+
Metrics: metricsV2,
174+
}
175+
176+
s := api.New(proxyConf, services, log.ZapLogger)
177+
go func() { s.Start() }()
178+
defer s.Stop()
179+
180+
time.Sleep(10 * time.Millisecond)
181+
182+
req, err := http.NewRequest("GET",
183+
"http://localhost:11024/ofrep/v1/configuration", nil)
184+
assert.NoError(t, err)
185+
req.Header.Add("Authorization", "Bearer test")
186+
response, err := http.DefaultClient.Do(req)
187+
assert.NoError(t, err)
188+
assert.Equal(t, http.StatusOK, response.StatusCode)
189+
190+
req, err = http.NewRequest("POST",
191+
"http://localhost:11024/ofrep/v1/evaluate/flags",
192+
strings.NewReader(`{ "context":{"targetingKey":"some-key"}}`))
193+
assert.NoError(t, err)
194+
req.Header.Add("Authorization", "Bearer test")
195+
req.Header.Add("Content-Type", "application/json")
196+
response, err = http.DefaultClient.Do(req)
197+
assert.NoError(t, err)
198+
assert.Equal(t, http.StatusOK, response.StatusCode)
199+
200+
req, err = http.NewRequest("POST",
201+
"http://localhost:11024/ofrep/v1/evaluate/flags/some-key",
202+
strings.NewReader(`{ "context":{"targetingKey":"some-key"}}`))
203+
assert.NoError(t, err)
204+
req.Header.Add("Authorization", "Bearer test")
205+
req.Header.Add("Content-Type", "application/json")
206+
response, err = http.DefaultClient.Do(req)
207+
assert.NoError(t, err)
208+
assert.Equal(t, http.StatusNotFound, response.StatusCode)
209+
210+
req, err = http.NewRequest("POST",
211+
"http://localhost:11024/ofrep/v1/evaluate/flags/test-flag",
212+
strings.NewReader(`{ "context":{"targetingKey":"some-key"}}`))
213+
assert.NoError(t, err)
214+
req.Header.Add("Authorization", "Bearer test")
215+
req.Header.Add("Content-Type", "application/json")
216+
response, err = http.DefaultClient.Do(req)
217+
assert.NoError(t, err)
218+
assert.Equal(t, http.StatusOK, response.StatusCode)
219+
}

codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ ignore:
1212
- "cmd/relayproxy/docs/"
1313
- "cmd/jsonschema-generator/"
1414
- "cmd/relayproxy/modeldocs/"
15+
- "model/dto/converter_v0.go"

config_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,12 @@ func TestConfig_GetRetrievers(t *testing.T) {
113113
})
114114
}
115115
}
116+
117+
func TestOfflineConfig(t *testing.T) {
118+
c := ffClient.Config{
119+
Offline: true,
120+
}
121+
assert.True(t, c.IsOffline())
122+
c.SetOffline(false)
123+
assert.False(t, c.IsOffline())
124+
}

exporter/gcstorageexporter/exporter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func (f *Exporter) Export(ctx context.Context, logger *fflog.FFLogger, featureEv
9595
for _, file := range files {
9696
of, err := os.Open(outputDir + "/" + file.Name())
9797
if err != nil {
98-
logger.Error("[GCP DeprecatedExporter] impossible to open the file",
98+
logger.Error("[GCP Exporter] impossible to open the file",
9999
slog.String("path", outputDir+"/"+file.Name()))
100100
continue
101101
}
@@ -111,7 +111,7 @@ func (f *Exporter) Export(ctx context.Context, logger *fflog.FFLogger, featureEv
111111
_, err = io.Copy(wc, of)
112112
_ = wc.Close()
113113
if err != nil {
114-
return fmt.Errorf("error: [DeprecatedExporter] impossible to copy the file from %s to bucket %s: %v",
114+
return fmt.Errorf("error: [GCP Exporter] impossible to copy the file from %s to bucket %s: %v",
115115
source, f.Bucket, err)
116116
}
117117
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ require (
4444
github.com/swaggo/echo-swagger v1.4.1
4545
github.com/swaggo/swag v1.16.3
4646
github.com/testcontainers/testcontainers-go v0.33.0
47+
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
4748
github.com/testcontainers/testcontainers-go/modules/redis v0.33.0
4849
github.com/thejerf/slogassert v0.3.4
4950
github.com/xitongsys/parquet-go v1.6.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,8 @@ github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
862862
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
863863
github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw=
864864
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
865+
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0 h1:iXVA84s5hKMS5gn01GWOYHE3ymy/2b+0YkpFeTxB2XY=
866+
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0/go.mod h1:R6tMjTojRiaoo89fh/hf7tOmfzohdqSU17R9DwSVSog=
865867
github.com/testcontainers/testcontainers-go/modules/redis v0.33.0 h1:S/QvMOwpr00MM2aWH+krzP73Erlp/Ug0dr2rkgZYI5s=
866868
github.com/testcontainers/testcontainers-go/modules/redis v0.33.0/go.mod h1:gudb3+6uZ9SsAysOVoLs7nazbjGlkHegBW8nqPXvDMI=
867869
github.com/thejerf/slogassert v0.3.4 h1:VoTsXixRbXMrRSSxDjYTiEDCM4VWbsYPW5rB/hX24kM=

internal/flagstate/all_flags_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package flagstate_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/thomaspoignant/go-feature-flag/internal/flag"
9+
"github.com/thomaspoignant/go-feature-flag/internal/flagstate"
10+
)
11+
12+
func TestAllFlags(t *testing.T) {
13+
afs := flagstate.NewAllFlags()
14+
assert.NotNil(t, afs.GetFlags())
15+
assert.Equal(t, 0, len(afs.GetFlags()))
16+
assert.True(t, afs.IsValid())
17+
18+
fs := flagstate.FlagState{
19+
Value: 20,
20+
Timestamp: time.Date(2022, 8, 1, 0, 0, 10, 0, time.UTC).Unix(),
21+
VariationType: "var_a",
22+
TrackEvents: false,
23+
Failed: false,
24+
Reason: flag.ReasonStatic,
25+
}
26+
afs.AddFlag("my-key", fs)
27+
assert.Equal(t, 1, len(afs.GetFlags()))
28+
assert.True(t, afs.IsValid())
29+
fs2 := flagstate.FlagState{
30+
Value: 20,
31+
Timestamp: time.Date(2022, 8, 1, 0, 0, 10, 0, time.UTC).Unix(),
32+
VariationType: "var_a",
33+
TrackEvents: false,
34+
Failed: true,
35+
ErrorCode: flag.ErrorCodeTargetingKeyMissing,
36+
ErrorDetails: "The targeting key is missing",
37+
Reason: flag.ReasonError,
38+
}
39+
afs.AddFlag("my-key-2", fs2)
40+
assert.Equal(t, 2, len(afs.GetFlags()))
41+
assert.False(t, afs.IsValid())
42+
43+
want := "{\"flags\":{\"my-key\":{\"value\":20,\"timestamp\":1659312010,\"variationType\":\"var_a\",\"trackEvents\":false,\"errorCode\":\"\",\"reason\":\"STATIC\"},\"my-key-2\":{\"value\":20,\"timestamp\":1659312010,\"variationType\":\"var_a\",\"trackEvents\":false,\"errorCode\":\"TARGETING_KEY_MISSING\",\"errorDetails\":\"The targeting key is missing\",\"reason\":\"ERROR\"}},\"valid\":false}\n"
44+
got, err := afs.MarshalJSON()
45+
assert.NoError(t, err)
46+
assert.JSONEq(t, want, string(got))
47+
}

model/dto/converter_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func TestConvertV1DtoToInternalFlag(t *testing.T) {
137137
}
138138
for _, tt := range tests {
139139
t.Run(tt.name, func(t *testing.T) {
140-
result := dto.ConvertV1DtoToInternalFlag(tt.input)
140+
result := tt.input.Convert(nil, "random-flag-name")
141141
assert.Equal(t, tt.expected, result)
142142
})
143143
}

retriever/mongodbretriever/retriever.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ func (r *Retriever) Init(ctx context.Context, logger *fflog.FFLogger) error {
4444

4545
// Status returns the current status of the retriever
4646
func (r *Retriever) Status() retriever.Status {
47+
if r == nil || r.status == "" {
48+
return retriever.RetrieverNotReady
49+
}
4750
return r.status
4851
}
4952

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1-
package mongodbretriever
1+
//go:build docker
2+
// +build docker
3+
4+
package mongodbretriever_test
25

36
import (
47
"context"
58
"encoding/json"
9+
"github.com/stretchr/testify/require"
10+
"github.com/thomaspoignant/go-feature-flag/retriever"
11+
"github.com/thomaspoignant/go-feature-flag/retriever/mongodbretriever"
12+
"go.mongodb.org/mongo-driver/bson"
13+
"go.mongodb.org/mongo-driver/mongo"
14+
"go.mongodb.org/mongo-driver/mongo/options"
615
"testing"
716

817
"github.com/stretchr/testify/assert"
18+
"github.com/testcontainers/testcontainers-go/modules/mongodb"
919
"github.com/thomaspoignant/go-feature-flag/testutils"
1020
"github.com/thomaspoignant/go-feature-flag/utils/fflog"
1121
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
@@ -17,64 +27,120 @@ func Test_MongoDBRetriever_Retrieve(t *testing.T) {
1727
tests := []struct {
1828
name string
1929
want []byte
20-
mocker *func(t *mtest.T)
30+
data string
2131
wantErr bool
2232
}{
2333
{
2434
name: "Returns well formed flag definition document",
25-
mocker: &testutils.MockSuccessFind,
35+
data: testutils.MongoFindResultString,
2636
want: []byte(testutils.QueryResult),
2737
wantErr: false,
2838
},
2939
{
3040
name: "One of the Flag definition document does not have 'flag' key/value (ignore this document)",
31-
mocker: &testutils.MockNoFlagKeyResult,
41+
data: testutils.MongoMissingFlagKey,
3242
want: []byte(testutils.MissingFlagKeyResult),
3343
wantErr: false,
3444
},
3545
{
3646
name: "Flag definition document 'flag' key does not have 'string' value (ignore this document)",
37-
mocker: &testutils.MockFlagNotStrResult,
47+
data: testutils.MongoFindResultFlagNoStr,
3848
want: []byte(testutils.FlagKeyNotStringResult),
3949
wantErr: false,
4050
},
4151
{
4252
name: "No flags found on DB",
43-
mocker: &testutils.MockNoFlags,
44-
want: nil,
53+
want: []byte("{}"),
4554
wantErr: true,
4655
},
4756
}
4857
for _, tt := range tests {
4958
mtDB.Run(tt.name, func(t *mtest.T) {
50-
mdb := Retriever{
51-
URI: "mongouri",
52-
Collection: "collection",
53-
Database: "database",
54-
dbConnection: t.DB,
55-
}
59+
mongodbContainer, err := mongodb.Run(context.TODO(), "mongo:6")
60+
require.NoError(t, err)
61+
defer func() {
62+
err := mongodbContainer.Terminate(context.TODO())
63+
require.NoError(t, err)
64+
}()
5665

57-
if tt.mocker != nil {
58-
(*tt.mocker)(t)
59-
}
66+
uri, err := mongodbContainer.ConnectionString(context.Background())
6067

61-
_ = mdb.Init(context.TODO(), &fflog.FFLogger{})
62-
got, err := mdb.Retrieve(context.Background())
68+
if tt.data != "" {
69+
// insert data
70+
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
71+
coll := client.Database("database").Collection("collection")
72+
var documents []bson.M
73+
err = json.Unmarshal([]byte(tt.data), &documents)
74+
require.NoError(t, err)
6375

64-
if tt.wantErr {
65-
assert.Error(t, err, "Retrieve() error = %v, wantErr %v", err, tt.wantErr)
66-
return
76+
for _, doc := range documents {
77+
_, err := coll.InsertOne(context.TODO(), doc)
78+
require.NoError(t, err)
79+
}
6780
}
6881

69-
var gotUnm, wantUn interface{}
70-
if err := json.Unmarshal(tt.want, &wantUn); err != nil {
71-
assert.Fail(t, "could not json unmarshall wanted flag structure")
72-
}
73-
if err := json.Unmarshal(got, &gotUnm); err != nil {
74-
assert.Fail(t, "could not json unmarshall got flag structure")
82+
// retriever
83+
mdb := mongodbretriever.Retriever{
84+
URI: uri,
85+
Collection: "collection",
86+
Database: "database",
7587
}
88+
assert.Equal(t, retriever.RetrieverNotReady, mdb.Status())
89+
err = mdb.Init(context.TODO(), &fflog.FFLogger{})
90+
assert.NoError(t, err)
91+
defer func() { _ = mdb.Shutdown(context.TODO()) }()
92+
assert.Equal(t, retriever.RetrieverReady, mdb.Status())
7693

77-
assert.Equal(t, wantUn, gotUnm)
94+
got, err := mdb.Retrieve(context.Background())
95+
if tt.want == nil {
96+
assert.Nil(t, got)
97+
} else {
98+
modifiedGot, err := removeIDFromJSON(string(got))
99+
require.NoError(t, err)
100+
assert.JSONEq(t, string(tt.want), modifiedGot)
101+
}
78102
})
79103
}
80104
}
105+
106+
func Test_MongoDBRetriever_InvalidURI(t *testing.T) {
107+
mdb := mongodbretriever.Retriever{
108+
URI: "invalidURI",
109+
Collection: "collection",
110+
Database: "database",
111+
}
112+
assert.Equal(t, retriever.RetrieverNotReady, mdb.Status())
113+
err := mdb.Init(context.TODO(), &fflog.FFLogger{})
114+
assert.Error(t, err)
115+
assert.Equal(t, retriever.RetrieverError, mdb.Status())
116+
}
117+
118+
func removeIDFromJSON(jsonStr string) (string, error) {
119+
var data interface{}
120+
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
121+
return "", err
122+
}
123+
124+
removeIDFields(data)
125+
126+
modifiedJSON, err := json.Marshal(data)
127+
if err != nil {
128+
return "", err
129+
}
130+
131+
return string(modifiedJSON), nil
132+
}
133+
134+
func removeIDFields(data interface{}) {
135+
switch v := data.(type) {
136+
case map[string]interface{}:
137+
delete(v, "_id")
138+
for _, value := range v {
139+
removeIDFields(value)
140+
}
141+
case []interface{}:
142+
for _, item := range v {
143+
removeIDFields(item)
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)