Skip to content

Commit ed7f8a8

Browse files
committed
tests, improvements and documentation
Signed-off-by: Kavindu Dodanduwa <[email protected]>
1 parent aa449cf commit ed7f8a8

File tree

8 files changed

+131
-71
lines changed

8 files changed

+131
-71
lines changed

providers/ofrep/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
This is the Go implementation of the OFREP provider.
44
The provider works by evaluating flags against OFREP single flag evaluation endpoint.
5-
And there is no caching layer in this version.
65

76
## Installation
87

providers/ofrep/internal/evaluate/flags.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,18 @@ import (
88
of "github.com/open-feature/go-sdk/openfeature"
99
)
1010

11-
type resolver interface {
12-
resolveSingle(ctx context.Context, key string, evalCtx map[string]interface{}) (*successDto, *of.ResolutionError)
13-
}
14-
11+
// Flags is the flag evaluator implementation. It contains domain logic of the OpenFeature flag evaluation.
1512
type Flags struct {
1613
resolver resolver
1714
}
1815

19-
func NewFlagsEvaluator(cfg outbound.Configuration) *Flags {
20-
client := outbound.NewHttp(cfg)
16+
type resolver interface {
17+
resolveSingle(ctx context.Context, key string, evalCtx map[string]interface{}) (*successDto, *of.ResolutionError)
18+
}
2119

20+
func NewFlagsEvaluator(cfg outbound.Configuration) *Flags {
2221
return &Flags{
23-
resolver: NewOutboundResolver(client),
22+
resolver: NewOutboundResolver(cfg),
2423
}
2524
}
2625

providers/ofrep/internal/evaluate/flags_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package evaluate
22

33
import (
44
"context"
5-
"fmt"
65
"reflect"
76
"testing"
87

@@ -302,19 +301,19 @@ func genericValidator[T knownTypes](test testDefinition[T], resolvedValue T, rea
302301
}
303302

304303
if !reflect.DeepEqual(test.defaultValue, resolvedValue) {
305-
t.Error(fmt.Sprintf("expected deafault value %v, but got %v", test.defaultValue, resolvedValue))
304+
t.Errorf("expected deafault value %v, but got %v", test.defaultValue, resolvedValue)
306305
}
307306

308307
if reason != of.ErrorReason {
309-
t.Error(fmt.Sprintf("expected reason %v, but got %v", of.ErrorReason, reason))
308+
t.Errorf("expected reason %v, but got %v", of.ErrorReason, reason)
310309
}
311310
} else {
312311
if err != nil {
313-
t.Fatal(fmt.Sprintf("expected no error, but got none nil error: %v", err))
312+
t.Fatalf("expected no error, but got none nil error: %v", err)
314313
}
315314

316315
if !reflect.DeepEqual(test.expect, resolvedValue) {
317-
t.Error(fmt.Sprintf("expected value %v, but got %v", test.expect, resolvedValue))
316+
t.Errorf("expected value %v, but got %v", test.expect, resolvedValue)
318317
}
319318
}
320319
}

providers/ofrep/internal/evaluate/resolver.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,22 @@ import (
99
"strconv"
1010
"time"
1111

12+
"github.com/open-feature/go-sdk-contrib/providers/ofrep/internal/outbound"
1213
of "github.com/open-feature/go-sdk/openfeature"
1314
)
1415

15-
type Outbound interface {
16-
PostSingle(ctx context.Context, key string, payload []byte) (*http.Response, error)
17-
}
18-
16+
// OutboundResolver is responsible for resolving flags with outbound communications.
17+
// It contains domain logic of the OFREP specification.
1918
type OutboundResolver struct {
2019
client Outbound
2120
}
2221

23-
func NewOutboundResolver(client Outbound) *OutboundResolver {
24-
return &OutboundResolver{client}
22+
type Outbound interface {
23+
PostSingle(ctx context.Context, key string, payload []byte) (*http.Response, error)
24+
}
25+
26+
func NewOutboundResolver(cfg outbound.Configuration) *OutboundResolver {
27+
return &OutboundResolver{client: outbound.NewHttp(cfg)}
2528
}
2629

2730
func (g *OutboundResolver) resolveSingle(ctx context.Context, key string, evalCtx map[string]interface{}) (
@@ -116,7 +119,7 @@ func parse429(rsp *http.Response) time.Duration {
116119
return 0
117120
}
118121

119-
return parsed.Sub(time.Now())
122+
return time.Until(parsed)
120123
}
121124

122125
func parseError500(body io.ReadCloser) *of.ResolutionError {

providers/ofrep/internal/evaluate/resolver_test.go

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"context"
66
"encoding/json"
77
"errors"
8-
"fmt"
98
"io"
109
"net/http"
1110
"strings"
@@ -36,45 +35,49 @@ func TestSuccess200(t *testing.T) {
3635
t.Fatal(err)
3736
}
3837

39-
client := mockOutbound{
38+
resolver := OutboundResolver{client: mockOutbound{
4039
rsp: http.Response{
4140
StatusCode: http.StatusOK,
4241
Body: io.NopCloser(bytes.NewReader(successBytes)),
4342
},
44-
}
43+
}}
4544

46-
successDto, resolutionError := NewOutboundResolver(client).resolveSingle(context.Background(), "", make(map[string]interface{}))
45+
successDto, resolutionError := resolver.resolveSingle(context.Background(), "", make(map[string]interface{}))
4746

4847
if resolutionError != nil {
49-
t.Error(fmt.Sprintf("expected no errors, but got error: %v", err))
48+
t.Errorf("expected no errors, but got error: %v", err)
5049
}
5150

5251
if successDto == nil {
5352
t.Fatal("expected non empty success response")
5453
}
5554

5655
if successDto.Value != success.Value {
57-
t.Errorf(fmt.Sprintf("expected value %v, but got %v", success.Value, successDto.Value))
56+
t.Errorf("expected value %v, but got %v", success.Value, successDto.Value)
5857
}
5958

6059
if successDto.Variant != success.Variant {
61-
t.Errorf(fmt.Sprintf("expected variant %v, but got %v", success.Variant, successDto.Variant))
60+
t.Errorf("expected variant %v, but got %v", success.Variant, successDto.Variant)
6261
}
6362

6463
if successDto.Reason != success.Reason {
65-
t.Errorf(fmt.Sprintf("expected reason %s, but got %s", success.Reason, successDto.Reason))
64+
t.Errorf("expected reason %s, but got %s", success.Reason, successDto.Reason)
65+
}
66+
67+
if successDto.Metadata["key"] != "value" {
68+
t.Errorf("expected key to contain value %s, but got %s", "value", successDto.Metadata["key"])
6669
}
6770
})
6871

6972
t.Run("invalid payload type results in general error", func(t *testing.T) {
70-
client := mockOutbound{
73+
resolver := OutboundResolver{client: mockOutbound{
7174
rsp: http.Response{
7275
StatusCode: http.StatusOK,
7376
Body: io.NopCloser(bytes.NewReader([]byte("some payload"))),
7477
},
75-
}
78+
}}
79+
success, resolutionError := resolver.resolveSingle(context.Background(), "", make(map[string]interface{}))
7680

77-
success, resolutionError := NewOutboundResolver(client).resolveSingle(context.Background(), "", make(map[string]interface{}))
7881
validateErrorCode(success, resolutionError, of.GeneralCode, t)
7982
})
8083
}
@@ -116,8 +119,9 @@ func TestResolveGeneralErrors(t *testing.T) {
116119

117120
for _, test := range tests {
118121
t.Run(test.name, func(t *testing.T) {
119-
// when
120-
success, resolutionError := NewOutboundResolver(test.client).resolveSingle(context.Background(), "key", map[string]interface{}{})
122+
resolver := OutboundResolver{client: test.client}
123+
success, resolutionError := resolver.resolveSingle(context.Background(), "key", map[string]interface{}{})
124+
121125
validateErrorCode(success, resolutionError, of.GeneralCode, t)
122126
})
123127
}
@@ -166,31 +170,27 @@ func TestEvaluationError4xx(t *testing.T) {
166170
t.Fatal(err)
167171
}
168172

169-
client := mockOutbound{
173+
resolver := OutboundResolver{client: mockOutbound{
170174
rsp: http.Response{
171175
StatusCode: http.StatusBadRequest,
172176
Body: io.NopCloser(bytes.NewReader(errBytes)),
173177
},
174-
}
175-
// when
176-
success, resolutionError := NewOutboundResolver(client).resolveSingle(context.Background(), "", make(map[string]interface{}))
178+
}}
179+
success, resolutionError := resolver.resolveSingle(context.Background(), "", make(map[string]interface{}))
177180

178-
// then
179181
validateErrorCode(success, resolutionError, test.expectCode, t)
180182
})
181183
}
182184
}
183185

184186
func TestFlagNotFound404(t *testing.T) {
185-
client := mockOutbound{
187+
resolver := OutboundResolver{client: mockOutbound{
186188
rsp: http.Response{
187189
StatusCode: http.StatusNotFound,
188190
},
189-
}
190-
// when
191-
success, resolutionError := NewOutboundResolver(client).resolveSingle(context.Background(), "", make(map[string]interface{}))
191+
}}
192+
success, resolutionError := resolver.resolveSingle(context.Background(), "", make(map[string]interface{}))
192193

193-
// then
194194
validateErrorCode(success, resolutionError, of.FlagNotFoundCode, t)
195195
}
196196

@@ -225,29 +225,24 @@ func Test429(t *testing.T) {
225225
}
226226
}
227227

228-
client := mockOutbound{
229-
rsp: response,
230-
}
228+
resolver := OutboundResolver{client: mockOutbound{rsp: response}}
229+
success, resolutionError := resolver.resolveSingle(context.Background(), "", make(map[string]interface{}))
231230

232-
// when
233-
success, resolutionError := NewOutboundResolver(client).resolveSingle(context.Background(), "", make(map[string]interface{}))
234231
validateErrorCode(success, resolutionError, of.GeneralCode, t)
235232
})
236233
}
237234
}
238235

239236
func TestEvaluationError5xx(t *testing.T) {
240237
t.Run("without body", func(t *testing.T) {
241-
client := mockOutbound{
238+
resolver := OutboundResolver{client: mockOutbound{
242239
rsp: http.Response{
243240
StatusCode: http.StatusInternalServerError,
244241
Body: io.NopCloser(bytes.NewReader([]byte{})),
245242
},
246-
}
247-
// when
248-
success, resolutionError := NewOutboundResolver(client).resolveSingle(context.Background(), "", make(map[string]interface{}))
243+
}}
244+
success, resolutionError := resolver.resolveSingle(context.Background(), "", make(map[string]interface{}))
249245

250-
// then
251246
validateErrorCode(success, resolutionError, of.GeneralCode, t)
252247
})
253248

@@ -257,30 +252,26 @@ func TestEvaluationError5xx(t *testing.T) {
257252
t.Fatal(err)
258253
}
259254

260-
client := mockOutbound{
255+
resolver := OutboundResolver{client: mockOutbound{
261256
rsp: http.Response{
262257
StatusCode: http.StatusInternalServerError,
263258
Body: io.NopCloser(bytes.NewReader(errorBytes)),
264259
},
265-
}
266-
// when
267-
success, resolutionError := NewOutboundResolver(client).resolveSingle(context.Background(), "", make(map[string]interface{}))
260+
}}
261+
success, resolutionError := resolver.resolveSingle(context.Background(), "", make(map[string]interface{}))
268262

269-
// then
270263
validateErrorCode(success, resolutionError, of.GeneralCode, t)
271264
})
272265

273266
t.Run("with invalid body", func(t *testing.T) {
274-
client := mockOutbound{
267+
resolver := OutboundResolver{client: mockOutbound{
275268
rsp: http.Response{
276269
StatusCode: http.StatusInternalServerError,
277270
Body: io.NopCloser(bytes.NewReader([]byte("some error"))),
278271
},
279-
}
280-
// when
281-
success, resolutionError := NewOutboundResolver(client).resolveSingle(context.Background(), "", make(map[string]interface{}))
272+
}}
273+
success, resolutionError := resolver.resolveSingle(context.Background(), "", make(map[string]interface{}))
282274

283-
// then
284275
validateErrorCode(success, resolutionError, of.GeneralCode, t)
285276
})
286277
}

providers/ofrep/internal/outbound/http_test.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,23 @@ import (
99

1010
func TestHttpOutbound(t *testing.T) {
1111
// given
12+
host := "localhost:18181"
1213
key := "flag"
14+
1315
server := http.Server{
14-
Addr: "localhost:18181",
16+
Addr: host,
1517
Handler: mockHandler{
1618
t: t,
1719
key: key,
1820
},
1921
}
2022

2123
go func() {
22-
server.ListenAndServe()
24+
err := server.ListenAndServe()
25+
if err != nil {
26+
t.Logf("error starting mock server: %v", err)
27+
return
28+
}
2329
}()
2430

2531
outbound := NewHttp(Configuration{
@@ -28,7 +34,7 @@ func TestHttpOutbound(t *testing.T) {
2834
return "Authorization", "Token"
2935
},
3036
},
31-
BaseURI: "http://localhost:18181",
37+
BaseURI: fmt.Sprintf("http://%s", host),
3238
})
3339

3440
// when
@@ -37,14 +43,16 @@ func TestHttpOutbound(t *testing.T) {
3743
return
3844
}
3945

40-
// then - expect a ok response
46+
// then - expect an ok response
4147
if response.StatusCode != http.StatusOK {
4248
t.Errorf("expected 200, but got %d", response.StatusCode)
4349
}
4450

4551
// cleanup
46-
server.Close()
47-
server.Shutdown(context.Background())
52+
err = server.Shutdown(context.Background())
53+
if err != nil {
54+
t.Errorf("error shuttting down mock server: %v", err)
55+
}
4856
}
4957

5058
type mockHandler struct {
@@ -73,5 +81,4 @@ func (r mockHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
7381
}
7482

7583
resp.WriteHeader(http.StatusOK)
76-
return
7784
}

providers/ofrep/provider.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ type Provider struct {
1616

1717
type option func(*outbound.Configuration)
1818

19-
// NewProvider returns a provider configured with provided configuration
19+
// NewProvider returns an OFREP provider configured with provided configuration.
20+
// The only mandatory configuration is the baseUri, which is the base path of the OFREP service implementation.
2021
func NewProvider(baseUri string, options ...option) *Provider {
2122
cfg := outbound.Configuration{
2223
BaseURI: baseUri,

0 commit comments

Comments
 (0)