Skip to content

Commit

Permalink
FFM-4043 - Add checks for nested pre req flags (#95)
Browse files Browse the repository at this point in the history
* Change 'number' to 'int' input on number variations

* Check prereq flags for nested prereqs, update example app

* simple refactor
  • Loading branch information
stephenmcconkey committed Aug 8, 2022
1 parent ad5712c commit f81fb5e
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 5 deletions.
33 changes: 33 additions & 0 deletions evaluation/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,13 @@ func prereqsSatisfied(fc FeatureConfig, target *Target, flags map[string]Feature
continue
}

if prereqFlag.Prerequisites != nil {
res := checkPreReqsForPreReqs(prereqFlag.Prerequisites, flags, target)
if !res {
return false
}
}

variationToMatch := prereqFlag.Variations.FindByIdentifier(prereqFlag.GetVariationName(target))

if pre.Feature == prereqFlag.Feature {
Expand Down Expand Up @@ -540,3 +547,29 @@ type WeightedVariation struct {
Variation string
Weight int
}

func checkPreReqsForPreReqs(preReqFlagPreReqs []Prerequisite, flags map[string]FeatureConfig, target *Target) bool {
for _, preReq := range preReqFlagPreReqs {
nestedPreReq, ok := flags[preReq.Feature]
if !ok {
continue
}

if len(nestedPreReq.Prerequisites) > 0 {
nested := checkPreReqsForPreReqs(nestedPreReq.Prerequisites, flags, target)
if !nested {
return false
}
}

preReqVariationToMatch := nestedPreReq.Variations.FindByIdentifier(nestedPreReq.GetVariationName(target))
if preReq.Feature == nestedPreReq.Feature {
for _, variation := range preReq.Variations {
if variation != preReqVariationToMatch.Value {
return false
}
}
}
}
return true
}
200 changes: 200 additions & 0 deletions evaluation/feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1546,3 +1546,203 @@ func TestFeatureConfig_prereqsSatisfied(t *testing.T) {
})
}
}

func TestCheckPreReqsForPreReqs(t *testing.T) {
trueVariation := Variation{
Name: stringPtr("True"),
Value: "true",
Identifier: "true",
}

falseVariation := Variation{
Name: stringPtr("False"),
Value: "false",
Identifier: "false",
}

blueVariation := Variation{
Name: stringPtr("blue"),
Value: "blue",
Identifier: "blue",
}

redVariation := Variation{
Name: stringPtr("red"),
Value: "red",
Identifier: "red",
}

flags := map[string]FeatureConfig{
"flag1": {
DefaultServe: Serve{
Variation: stringPtr("true"),
},
Environment: "dev",
Feature: "flag1",
Kind: "boolean",
OffVariation: "false",
Prerequisites: nil,
Project: "default",
Rules: nil,
State: "on",
VariationToTargetMap: nil,
Variations: Variations{
trueVariation, falseVariation,
},
Segments: nil,
},
"flag2": {
DefaultServe: Serve{
Variation: stringPtr("true"),
},
Environment: "dev",
Feature: "flag2",
Kind: "boolean",
OffVariation: "false",
Prerequisites: []Prerequisite{
{Feature: "flag1",
Variations: []string{"true"},
},
},
Project: "default",
Rules: nil,
State: "on",
VariationToTargetMap: nil,
Variations: Variations{
trueVariation, falseVariation,
},
Segments: nil,
},
"flag3": {
DefaultServe: Serve{
Variation: stringPtr("true"),
},
Environment: "dev",
Feature: "flag3",
Kind: "boolean",
OffVariation: "false",
Prerequisites: []Prerequisite{
{Feature: "flag2",
Variations: []string{"true"},
},
},
Project: "default",
Rules: nil,
State: "on",
VariationToTargetMap: nil,
Variations: Variations{
trueVariation, falseVariation,
},
Segments: nil,
},
"mv1": {
DefaultServe: Serve{
Variation: stringPtr("red"),
},
Environment: "dev",
Feature: "mv1",
Kind: "string",
OffVariation: "blue",
Prerequisites: []Prerequisite{
{Feature: "flag1",
Variations: []string{"true"},
},
},
Project: "default",
Rules: nil,
State: "on",
VariationToTargetMap: nil,
Variations: Variations{
redVariation, blueVariation,
},
Segments: nil,
},
}
tests := []struct {
name string
expected bool
preReqFlagPreReqs []Prerequisite
flags map[string]FeatureConfig
target *Target
}{
{
name: "Given I have no preReqFlagPreReqs",
expected: true,
flags: nil,
preReqFlagPreReqs: nil,
target: nil,
},
{
name: "Given I have a preReqFlagPreReqs that has no nested preregs that will match",
expected: true,
flags: flags,
preReqFlagPreReqs: []Prerequisite{
{Feature: "flag1",
Variations: []string{"true"},
},
},
target: nil,
},
{
name: "Given I have a preReqFlagPreReqs that has no nested prereqs that will not match",
expected: false,
flags: flags,
preReqFlagPreReqs: []Prerequisite{
{Feature: "flag1",
Variations: []string{"false"},
},
},
target: nil,
},
{
name: "Given I have a preReqFlagPreReqs that has nested prereqs that will match i.e. flag 3 depends on flag 2, which depends on flag 1",
expected: true,
flags: flags,
preReqFlagPreReqs: []Prerequisite{
{Feature: "flag3",
Variations: []string{"true"},
},
},
target: nil,
},
{
name: "Given I have a preReqFlagPreReqs that has nested prereqs that will not match",
expected: false,
flags: flags,
preReqFlagPreReqs: []Prerequisite{
{Feature: "flag3",
Variations: []string{"false"},
},
},
target: nil,
},
{
name: "Given I have a preReqFlagPreReqs of a mv flag that has nested prereqs that will not match",
expected: false,
flags: flags,
preReqFlagPreReqs: []Prerequisite{
{Feature: "mv1",
Variations: []string{"blue"},
},
},
target: nil,
},
{
name: "Given I have a preReqFlagPreReqs of a mv flag that has nested prereqs that will match",
expected: true,
flags: flags,
preReqFlagPreReqs: []Prerequisite{
{Feature: "mv1",
Variations: []string{"red"},
},
},
target: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, checkPreReqsForPreReqs(tt.preReqFlagPreReqs, tt.flags, tt.target))
})
}
}
10 changes: 5 additions & 5 deletions examples/getting_started.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,24 @@ import (

var (
flagName string = getEnvOrDefault("FF_FLAG_NAME", "harnessappdemodarkmode")
apiKey string = getEnvOrDefault("FF_API_KEY", "changeme")
sdkKey string = getEnvOrDefault("FF_API_KEY", "changeme")
)

func main() {
log.Println("Harness SDK Getting Started")

// Create a feature flag client
client, err := harness.NewCfClient(apiKey)
client, err := harness.NewCfClient(sdkKey)
if err != nil {
log.Fatalf("could not connect to CF servers %s\n", err)
}
defer func() { client.Close() }()

// Create a target (different targets can get different results based on rules)
target := evaluation.Target{
Identifier: "golangsdk",
Name: "GolangSDK",
Attributes: &map[string]interface{}{"location": "emea"},
Identifier: "HT_1",
Name: "Harness_Target_1",
Attributes: &map[string]interface{}{"email": "demo@harness.io"},
}

// Loop forever reporting the state of the flag
Expand Down

0 comments on commit f81fb5e

Please sign in to comment.