Skip to content

Commit

Permalink
add third test
Browse files Browse the repository at this point in the history
  • Loading branch information
cwaldren-ld committed Oct 16, 2024
1 parent 4b3c33c commit 3668f83
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 36 deletions.
77 changes: 45 additions & 32 deletions integrationtests/flag_builder_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build integrationtests

package integrationtests

import (
Expand All @@ -17,28 +19,13 @@ type flagBuilder struct {
on bool
variations []ldapi.Variation
prerequisites []ldapi.Prerequisite
clientSide ldapi.ClientSideAvailabilityPost
helper *apiHelper
}

func (f *flagBuilder) logAPIError(desc string, err error) error {
var apiError ldapi.GenericOpenAPIError
if errors.As(err, &apiError) {
body := string(apiError.Body())
f.helper.loggers.Errorf("%s: %s (response body: %s)", f.scopedOp(desc), err, body)
} else {
f.helper.loggers.Errorf("%s: %s", f.scopedOp(desc), err)
}
return err
}

func (f *flagBuilder) scopedOp(desc string) string {
return fmt.Sprintf("%s in %s/%s", desc, f.projectKey, f.envKey)
}

func (f *flagBuilder) logAPISuccess(desc string) {
f.helper.loggers.Infof(f.scopedOp(desc))
}

// newFlagBuilder creates a builder for a flag which will be created in the specified project and environment.
// By default, the flag has two variations: off = false, and on = true. The flag is on by default.
// Additionally, the flag is available to both mobile and client SDKs.
func newFlagBuilder(helper *apiHelper, flagKey string, projectKey string, envKey string) *flagBuilder {
builder := &flagBuilder{
key: flagKey,
Expand All @@ -48,10 +35,15 @@ func newFlagBuilder(helper *apiHelper, flagKey string, projectKey string, envKey
offVariation: 0,
fallthroughVariation: 1,
helper: helper,
clientSide: ldapi.ClientSideAvailabilityPost{
UsingMobileKey: true,
UsingEnvironmentId: true,
},
}
return builder.Variations(ldvalue.Bool(false), ldvalue.Bool(true))
}

// Variations overwrites the flag's variations. A valid flag has two or more variations.
func (f *flagBuilder) Variations(variation1 ldvalue.Value, variations ...ldvalue.Value) *flagBuilder {
f.variations = nil
for _, value := range append([]ldvalue.Value{variation1}, variations...) {
Expand All @@ -61,45 +53,47 @@ func (f *flagBuilder) Variations(variation1 ldvalue.Value, variations ...ldvalue
return f
}

// ClientSideUsingEnvironmentID enables the flag for clients that use environment ID for auth.
func (f *flagBuilder) ClientSideUsingEnvironmentID(usingEnvID bool) *flagBuilder {
f.clientSide.UsingEnvironmentId = usingEnvID
return f
}

// Prerequisites overwrites the flag's prerequisites.
func (f *flagBuilder) Prerequisites(prerequisites []ldapi.Prerequisite) *flagBuilder {
f.prerequisites = prerequisites
return f
}

// Prerequisite is a helper that calls Prerequisites with a single value.
func (f *flagBuilder) Prerequisite(prerequisiteKey string, variation int32) *flagBuilder {
return f.Prerequisites([]ldapi.Prerequisite{{Key: prerequisiteKey, Variation: variation}})
}

// OffVariation sets the flag's off variation.
func (f *flagBuilder) OffVariation(v int) *flagBuilder {
f.offVariation = v
return f
}

// FallthroughVariation sets the flag's fallthrough variation.
func (f *flagBuilder) FallthroughVariation(v int) *flagBuilder {
f.fallthroughVariation = v
return f
}

// On enables or disables flag targeting.
func (f *flagBuilder) On(on bool) *flagBuilder {
f.on = on
return f
}

// Create creates the flag using the LD REST API.
func (f *flagBuilder) Create() error {

if len(f.variations) < 2 {
return errors.New("must have >= 2 variations")
}
if f.offVariation < 0 || f.offVariation >= len(f.variations) {
return errors.New("offVariation out of range")
}
if f.fallthroughVariation < 0 || f.fallthroughVariation >= len(f.variations) {
return errors.New("fallthroughVariation out of range")
}

flagPost := ldapi.FeatureFlagBody{
Name: f.key,
Key: f.key,
Name: f.key,
Key: f.key,
ClientSideAvailability: &f.clientSide,
}

_, _, err := f.helper.apiClient.FeatureFlagsApi.
Expand Down Expand Up @@ -136,3 +130,22 @@ func (f *flagBuilder) Create() error {

return nil
}

func (f *flagBuilder) logAPIError(desc string, err error) error {
var apiError ldapi.GenericOpenAPIError
if errors.As(err, &apiError) {
body := string(apiError.Body())
f.helper.loggers.Errorf("%s: %s (response body: %s)", f.scopedOp(desc), err, body)
} else {
f.helper.loggers.Errorf("%s: %s", f.scopedOp(desc), err)
}
return err
}

func (f *flagBuilder) scopedOp(desc string) string {
return fmt.Sprintf("%s %s in %s/%s", desc, f.key, f.projectKey, f.envKey)
}

func (f *flagBuilder) logAPISuccess(desc string) {
f.helper.loggers.Infof(f.scopedOp(desc))
}
52 changes: 52 additions & 0 deletions integrationtests/standard_mode_prerequisite_flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,40 @@ func makeFailedPrerequisites(api *scopedApiHelper) (map[string][]string, error)
}, nil
}

func makeIgnoreClientSideOnlyPrereqs(api *scopedApiHelper) (map[string][]string, error) {

// flag -> prereq1, prereq2

if err := api.newFlag("prereq1").
On(true).Variations(ldvalue.Bool(false), ldvalue.Bool(true)).
ClientSideUsingEnvironmentID(true).
Create(); err != nil {
return nil, err
}

if err := api.newFlag("prereq2").
On(true).Variations(ldvalue.Bool(false), ldvalue.Bool(true)).
ClientSideUsingEnvironmentID(false).
Create(); err != nil {
return nil, err
}
if err := api.newFlag("flag").
On(true).Variations(ldvalue.Bool(false), ldvalue.Bool(true)).
ClientSideUsingEnvironmentID(true).
Prerequisites([]ldapi.Prerequisite{
{Key: "prereq1", Variation: 1},
{Key: "prereq2", Variation: 1},
}).Create(); err != nil {
return nil, err
}

return map[string][]string{
"flag": {"prereq1", "prereq2"},
"prereq1": {},
}, nil

}

func testStandardModeWithPrerequisites(t *testing.T, manager *integrationTestManager) {
t.Run("includes top-level prerequisites", func(t *testing.T) {
api, err := newScopedApiHelper(manager.apiHelper)
Expand Down Expand Up @@ -199,4 +233,22 @@ func testStandardModeWithPrerequisites(t *testing.T, manager *integrationTestMan
})
manager.verifyFlagPrerequisites(t, api.projAndEnvs(), prerequisites)
})

t.Run("ignores client-side-only for prereq keys", func(t *testing.T) {
api, err := newScopedApiHelper(manager.apiHelper)
require.NoError(t, err)
defer api.cleanup()

prerequisites, err := makeIgnoreClientSideOnlyPrereqs(api)
require.NoError(t, err)

manager.startRelay(t, api.envVariables())
defer manager.stopRelay(t)

manager.awaitEnvironments(t, api.projAndEnvs(), nil, func(proj projectInfo, env environmentInfo) string {
return env.name
})

manager.verifyFlagPrerequisites(t, api.projAndEnvs(), prerequisites)
})
}
24 changes: 20 additions & 4 deletions integrationtests/test_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"golang.org/x/exp/maps"
"io"
"log"
"net/http"
"net/url"
"os"
"slices"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -368,7 +370,19 @@ func (m *integrationTestManager) verifyFlagPrerequisites(t *testing.T, projsAndE
userJSON := `{"key":"any-user-key"}`

projsAndEnvs.enumerateEnvs(func(proj projectInfo, env environmentInfo) {
prereqMap := m.getFlagPrerequisites(t, proj, env, userJSON)
prereqMap := m.getFlagPrerequisites(t, env, userJSON)

expectedKeys := maps.Keys(prereqs)
slices.Sort(expectedKeys)

gotKeys := prereqMap.Keys(nil)
slices.Sort(gotKeys)

if !slices.Equal(expectedKeys, gotKeys) {
m.loggers.Errorf("Expected %v flag keys with prerequisites, but got %v", expectedKeys, gotKeys)
t.Fail()
}

for flagKey, prereqKeys := range prereqs {
prereqArray := prereqMap.GetByKey(flagKey).AsValueArray()

Expand Down Expand Up @@ -482,10 +496,12 @@ func (m *integrationTestManager) getFlagValues(t *testing.T, proj projectInfo, e
return ldvalue.Null()
}

func (m *integrationTestManager) getFlagPrerequisites(t *testing.T, proj projectInfo, env environmentInfo, userJSON string) ldvalue.Value {
// Note: unlike getFlagValues, this helper makes a request to a client-side endpoint, rather than the server-side
// evaluation endpoint *that returns a client side payload.*
func (m *integrationTestManager) getFlagPrerequisites(t *testing.T, env environmentInfo, userJSON string) ldvalue.Value {
userBase64 := base64.URLEncoding.EncodeToString([]byte(userJSON))

u, err := url.Parse(m.relayBaseURL + "/sdk/evalx/users/" + userBase64)
u, err := url.Parse(m.relayBaseURL + "/sdk/evalx/" + string(env.id) + "/users/" + userBase64)
if err != nil {
t.Fatalf("couldn't parse flag evaluation URL: %v", err)
}
Expand All @@ -498,7 +514,7 @@ func (m *integrationTestManager) getFlagPrerequisites(t *testing.T, proj project

req, err := http.NewRequest("GET", u.String(), nil)
require.NoError(t, err)
req.Header.Add("Authorization", string(env.sdkKey))
req.Header.Add("Authorization", string(env.id))
resp, err := m.makeHTTPRequestToRelay(req)
require.NoError(t, err)
if assert.Equal(t, 200, resp.StatusCode, "requested flags for environment "+env.key) {
Expand Down

0 comments on commit 3668f83

Please sign in to comment.