From 76a3861fb6560e41715fc2d8c8f9856ef6aa8a32 Mon Sep 17 00:00:00 2001 From: Marcin Olko Date: Fri, 6 Mar 2026 12:02:15 +0000 Subject: [PATCH 1/3] Added null defaultVariant handling Signed-off-by: Marcin Olko --- providers/flagd/src/evaluator.cpp | 9 ++-- providers/flagd/src/sync.h | 2 +- providers/flagd/tests/evaluator_test.cpp | 57 ++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/providers/flagd/src/evaluator.cpp b/providers/flagd/src/evaluator.cpp index 8dfa91b..518b0fc 100644 --- a/providers/flagd/src/evaluator.cpp +++ b/providers/flagd/src/evaluator.cpp @@ -150,12 +150,11 @@ JsonLogicEvaluator::ResolveAny(std::string_view flag_key, T default_value, variant_name = std::move(*variant); reason = openfeature::Reason::kTargetingMatch; } else { - if (!flag_config.contains("defaultVariant")) { + if (!flag_config.contains("defaultVariant") || + flag_config["defaultVariant"].is_null()) { return std::make_unique>( - std::move(default_value), openfeature::Reason::kError, "", - openfeature::FlagMetadata(), openfeature::ErrorCode::kFlagNotFound, - absl::StrCat("flag: ", flag_key, - " doesn't have defaultVariant defined.")); + std::move(default_value), openfeature::Reason::kDefault, "", + openfeature::FlagMetadata(), std::nullopt, std::nullopt); } variant_name = flag_config["defaultVariant"]; diff --git a/providers/flagd/src/sync.h b/providers/flagd/src/sync.h index 17d96f8..53c35be 100644 --- a/providers/flagd/src/sync.h +++ b/providers/flagd/src/sync.h @@ -54,7 +54,7 @@ class GrpcSync final : public FlagSync { kUninitialized, kInitializing, // Thread started, waiting for first connection kReady, // First sync complete, running normally - kShuttingDown // Shutdown requested, cleaning up + kShuttingDown, // Shutdown requested, cleaning up }; std::thread background_thread_; diff --git a/providers/flagd/tests/evaluator_test.cpp b/providers/flagd/tests/evaluator_test.cpp index ea41804..956b592 100644 --- a/providers/flagd/tests/evaluator_test.cpp +++ b/providers/flagd/tests/evaluator_test.cpp @@ -254,3 +254,60 @@ TEST_F(EvaluatorTest, ResolveDouble_TypeMismatch) { EXPECT_EQ(result->GetReason(), openfeature::Reason::kError); EXPECT_EQ(result->GetErrorCode(), openfeature::ErrorCode::kTypeMismatch); } + +TEST_F(EvaluatorTest, ResolveBoolean_MissingDefaultVariant) { + nlohmann::json flags = {{"flags", + {{"my-bool-flag", + {{"state", "ENABLED"}, + {"variants", {{"on", true}, {"off", false}}}}}}}}; + + sync_->TriggerUpdate(flags); + + openfeature::EvaluationContext ctx = + openfeature::EvaluationContext::Builder().build(); + std::unique_ptr result = + evaluator_->ResolveBoolean("my-bool-flag", false, ctx); + + EXPECT_EQ(result->GetValue(), false); + EXPECT_EQ(result->GetReason(), openfeature::Reason::kDefault); + EXPECT_FALSE(result->GetErrorCode().has_value()); +} + +TEST_F(EvaluatorTest, ResolveBoolean_NullDefaultVariant) { + nlohmann::json flags = {{"flags", + {{"my-bool-flag", + {{"state", "ENABLED"}, + {"variants", {{"on", true}, {"off", false}}}, + {"defaultVariant", nullptr}}}}}}; + + sync_->TriggerUpdate(flags); + + openfeature::EvaluationContext ctx = + openfeature::EvaluationContext::Builder().build(); + std::unique_ptr result = + evaluator_->ResolveBoolean("my-bool-flag", false, ctx); + + EXPECT_EQ(result->GetValue(), false); + EXPECT_EQ(result->GetReason(), openfeature::Reason::kDefault); + EXPECT_FALSE(result->GetErrorCode().has_value()); +} + +TEST_F(EvaluatorTest, ResolveBoolean_TargetingNull_MissingDefaultVariant) { + nlohmann::json flags = { + {"flags", + {{"my-bool-flag", + {{"state", "ENABLED"}, + {"variants", {{"on", true}, {"off", false}}}, + {"targeting", {{"if", {{false}, "on", nullptr}}}}}}}}}; + + sync_->TriggerUpdate(flags); + + openfeature::EvaluationContext ctx = + openfeature::EvaluationContext::Builder().build(); + std::unique_ptr result = + evaluator_->ResolveBoolean("my-bool-flag", false, ctx); + + EXPECT_EQ(result->GetValue(), false); + EXPECT_EQ(result->GetReason(), openfeature::Reason::kDefault); + EXPECT_FALSE(result->GetErrorCode().has_value()); +} From aa7a30aa0ce744cdf2dc9896d1328d0522a5f10a Mon Sep 17 00:00:00 2001 From: Marcin Olko Date: Fri, 6 Mar 2026 13:41:51 +0000 Subject: [PATCH 2/3] Fixed bug and linter errors Signed-off-by: Marcin Olko --- providers/flagd/src/evaluator.cpp | 3 +-- providers/flagd/tests/evaluator_test.cpp | 33 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/providers/flagd/src/evaluator.cpp b/providers/flagd/src/evaluator.cpp index 518b0fc..bda61a6 100644 --- a/providers/flagd/src/evaluator.cpp +++ b/providers/flagd/src/evaluator.cpp @@ -150,8 +150,7 @@ JsonLogicEvaluator::ResolveAny(std::string_view flag_key, T default_value, variant_name = std::move(*variant); reason = openfeature::Reason::kTargetingMatch; } else { - if (!flag_config.contains("defaultVariant") || - flag_config["defaultVariant"].is_null()) { + if (flag_config.value("defaultVariant", nlohmann::json()).is_null()) { return std::make_unique>( std::move(default_value), openfeature::Reason::kDefault, "", openfeature::FlagMetadata(), std::nullopt, std::nullopt); diff --git a/providers/flagd/tests/evaluator_test.cpp b/providers/flagd/tests/evaluator_test.cpp index 956b592..d2ffa84 100644 --- a/providers/flagd/tests/evaluator_test.cpp +++ b/providers/flagd/tests/evaluator_test.cpp @@ -32,7 +32,7 @@ class EvaluatorTest : public ::testing::Test { std::unique_ptr evaluator_; }; -TEST_F(EvaluatorTest, ResolveBoolean_Success) { +TEST_F(EvaluatorTest, ResolveBooleanSuccess) { nlohmann::json flags = {{"flags", {{"my-bool-flag", {{"state", "ENABLED"}, @@ -52,7 +52,7 @@ TEST_F(EvaluatorTest, ResolveBoolean_Success) { EXPECT_FALSE(result->GetErrorCode().has_value()); } -TEST_F(EvaluatorTest, ResolveBoolean_FlagNotFound) { +TEST_F(EvaluatorTest, ResolveBooleanFlagNotFound) { nlohmann::json flags = {{"flags", nlohmann::json::object()}}; sync_->TriggerUpdate(flags); @@ -66,7 +66,7 @@ TEST_F(EvaluatorTest, ResolveBoolean_FlagNotFound) { EXPECT_EQ(result->GetErrorCode(), openfeature::ErrorCode::kFlagNotFound); } -TEST_F(EvaluatorTest, ResolveBoolean_TypeMismatch) { +TEST_F(EvaluatorTest, ResolveBooleanTypeMismatch) { nlohmann::json flags = { {"flags", {{"my-string-flag", @@ -86,7 +86,7 @@ TEST_F(EvaluatorTest, ResolveBoolean_TypeMismatch) { EXPECT_EQ(result->GetErrorCode(), openfeature::ErrorCode::kTypeMismatch); } -TEST_F(EvaluatorTest, ResolveBoolean_VariantNotFound) { +TEST_F(EvaluatorTest, ResolveBooleanVariantNotFound) { nlohmann::json flags = {{"flags", {{"my-broken-flag", {{"state", "ENABLED"}, @@ -108,7 +108,7 @@ TEST_F(EvaluatorTest, ResolveBoolean_VariantNotFound) { "missing-variant."); } -TEST_F(EvaluatorTest, ResolveString_Success) { +TEST_F(EvaluatorTest, ResolveStringSuccess) { nlohmann::json flags = { {"flags", {{"my-string-flag", @@ -128,7 +128,7 @@ TEST_F(EvaluatorTest, ResolveString_Success) { EXPECT_EQ(result->GetVariant(), "v1"); } -TEST_F(EvaluatorTest, ResolveInteger_Success) { +TEST_F(EvaluatorTest, ResolveIntegerSuccess) { nlohmann::json flags = {{"flags", {{"my-int-flag", {{"state", "ENABLED"}, @@ -147,11 +147,12 @@ TEST_F(EvaluatorTest, ResolveInteger_Success) { EXPECT_EQ(result->GetVariant(), "two"); } -TEST_F(EvaluatorTest, ResolveDouble_Success) { +TEST_F(EvaluatorTest, ResolveDoubleSuccess) { + float d1 = 1.1, d2 = 2.2; nlohmann::json flags = {{"flags", {{"my-double-flag", {{"state", "ENABLED"}, - {"variants", {{"d1", 1.1}, {"d2", 2.2}}}, + {"variants", {{"d1", d1}, {"d2", d2}}}, {"defaultVariant", "d1"}}}}}}; sync_->TriggerUpdate(flags); @@ -161,12 +162,12 @@ TEST_F(EvaluatorTest, ResolveDouble_Success) { std::unique_ptr result = evaluator_->ResolveDouble("my-double-flag", 0.0, ctx); - EXPECT_DOUBLE_EQ(result->GetValue(), 1.1); + EXPECT_DOUBLE_EQ(result->GetValue(), d1); EXPECT_EQ(result->GetReason(), openfeature::Reason::kStatic); EXPECT_EQ(result->GetVariant(), "d1"); } -TEST_F(EvaluatorTest, ResolveObject_Success) { +TEST_F(EvaluatorTest, ResolveObjectSuccess) { nlohmann::json flags = {{"flags", {{"my-object-flag", {{"state", "ENABLED"}, @@ -198,7 +199,7 @@ TEST_F(EvaluatorTest, ResolveObject_Success) { EXPECT_EQ(structure->at("baz").AsInt().value(), 123); } -TEST_F(EvaluatorTest, ResolveString_TypeMismatch) { +TEST_F(EvaluatorTest, ResolveStringTypeMismatch) { nlohmann::json flags = {{"flags", {{"my-int-flag", {{"state", "ENABLED"}, @@ -217,7 +218,7 @@ TEST_F(EvaluatorTest, ResolveString_TypeMismatch) { EXPECT_EQ(result->GetErrorCode(), openfeature::ErrorCode::kTypeMismatch); } -TEST_F(EvaluatorTest, ResolveInteger_TypeMismatch) { +TEST_F(EvaluatorTest, ResolveIntegerTypeMismatch) { nlohmann::json flags = {{"flags", {{"my-string-flag", {{"state", "ENABLED"}, @@ -236,7 +237,7 @@ TEST_F(EvaluatorTest, ResolveInteger_TypeMismatch) { EXPECT_EQ(result->GetErrorCode(), openfeature::ErrorCode::kTypeMismatch); } -TEST_F(EvaluatorTest, ResolveDouble_TypeMismatch) { +TEST_F(EvaluatorTest, ResolveDoubleTypeMismatch) { nlohmann::json flags = {{"flags", {{"my-string-flag", {{"state", "ENABLED"}, @@ -255,7 +256,7 @@ TEST_F(EvaluatorTest, ResolveDouble_TypeMismatch) { EXPECT_EQ(result->GetErrorCode(), openfeature::ErrorCode::kTypeMismatch); } -TEST_F(EvaluatorTest, ResolveBoolean_MissingDefaultVariant) { +TEST_F(EvaluatorTest, ResolveBooleanMissingDefaultVariant) { nlohmann::json flags = {{"flags", {{"my-bool-flag", {{"state", "ENABLED"}, @@ -273,7 +274,7 @@ TEST_F(EvaluatorTest, ResolveBoolean_MissingDefaultVariant) { EXPECT_FALSE(result->GetErrorCode().has_value()); } -TEST_F(EvaluatorTest, ResolveBoolean_NullDefaultVariant) { +TEST_F(EvaluatorTest, ResolveBooleanNullDefaultVariant) { nlohmann::json flags = {{"flags", {{"my-bool-flag", {{"state", "ENABLED"}, @@ -292,7 +293,7 @@ TEST_F(EvaluatorTest, ResolveBoolean_NullDefaultVariant) { EXPECT_FALSE(result->GetErrorCode().has_value()); } -TEST_F(EvaluatorTest, ResolveBoolean_TargetingNull_MissingDefaultVariant) { +TEST_F(EvaluatorTest, ResolveBooleanTargetingNullMissingDefaultVariant) { nlohmann::json flags = { {"flags", {{"my-bool-flag", From edb5de56dd0ee3f25f46e53d588b2ba45d99b4cd Mon Sep 17 00:00:00 2001 From: Marcin Olko Date: Fri, 6 Mar 2026 15:39:59 +0000 Subject: [PATCH 3/3] Fixed linter and improved tests Signed-off-by: Marcin Olko --- providers/flagd/tests/evaluator_test.cpp | 77 +++++++++--------------- 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/providers/flagd/tests/evaluator_test.cpp b/providers/flagd/tests/evaluator_test.cpp index d2ffa84..feadafa 100644 --- a/providers/flagd/tests/evaluator_test.cpp +++ b/providers/flagd/tests/evaluator_test.cpp @@ -148,11 +148,12 @@ TEST_F(EvaluatorTest, ResolveIntegerSuccess) { } TEST_F(EvaluatorTest, ResolveDoubleSuccess) { - float d1 = 1.1, d2 = 2.2; + const double double1 = 1.1; + const double double2 = 2.2; nlohmann::json flags = {{"flags", {{"my-double-flag", {{"state", "ENABLED"}, - {"variants", {{"d1", d1}, {"d2", d2}}}, + {"variants", {{"d1", double1}, {"d2", double2}}}, {"defaultVariant", "d1"}}}}}}; sync_->TriggerUpdate(flags); @@ -162,17 +163,18 @@ TEST_F(EvaluatorTest, ResolveDoubleSuccess) { std::unique_ptr result = evaluator_->ResolveDouble("my-double-flag", 0.0, ctx); - EXPECT_DOUBLE_EQ(result->GetValue(), d1); + EXPECT_DOUBLE_EQ(result->GetValue(), double1); EXPECT_EQ(result->GetReason(), openfeature::Reason::kStatic); EXPECT_EQ(result->GetVariant(), "d1"); } TEST_F(EvaluatorTest, ResolveObjectSuccess) { + const int baz = 123; nlohmann::json flags = {{"flags", {{"my-object-flag", {{"state", "ENABLED"}, {"variants", - {{"obj1", {{"foo", "bar"}, {"baz", 123}}}, + {{"obj1", {{"foo", "bar"}, {"baz", baz}}}, {"obj2", {{"key", true}}}}}, {"defaultVariant", "obj1"}}}}}}; @@ -196,7 +198,7 @@ TEST_F(EvaluatorTest, ResolveObjectSuccess) { EXPECT_TRUE(structure->at("foo").IsString()); EXPECT_EQ(structure->at("foo").AsString().value(), "bar"); - EXPECT_EQ(structure->at("baz").AsInt().value(), 123); + EXPECT_EQ(structure->at("baz").AsInt().value(), baz); } TEST_F(EvaluatorTest, ResolveStringTypeMismatch) { @@ -256,12 +258,12 @@ TEST_F(EvaluatorTest, ResolveDoubleTypeMismatch) { EXPECT_EQ(result->GetErrorCode(), openfeature::ErrorCode::kTypeMismatch); } -TEST_F(EvaluatorTest, ResolveBooleanMissingDefaultVariant) { - nlohmann::json flags = {{"flags", - {{"my-bool-flag", - {{"state", "ENABLED"}, - {"variants", {{"on", true}, {"off", false}}}}}}}}; +class EvaluatorDefaultVariantTest + : public EvaluatorTest, + public ::testing::WithParamInterface {}; +TEST_P(EvaluatorDefaultVariantTest, ResolveBooleanReturnsDefault) { + nlohmann::json flags = {{"flags", GetParam()}}; sync_->TriggerUpdate(flags); openfeature::EvaluationContext ctx = @@ -274,41 +276,20 @@ TEST_F(EvaluatorTest, ResolveBooleanMissingDefaultVariant) { EXPECT_FALSE(result->GetErrorCode().has_value()); } -TEST_F(EvaluatorTest, ResolveBooleanNullDefaultVariant) { - nlohmann::json flags = {{"flags", - {{"my-bool-flag", - {{"state", "ENABLED"}, - {"variants", {{"on", true}, {"off", false}}}, - {"defaultVariant", nullptr}}}}}}; - - sync_->TriggerUpdate(flags); - - openfeature::EvaluationContext ctx = - openfeature::EvaluationContext::Builder().build(); - std::unique_ptr result = - evaluator_->ResolveBoolean("my-bool-flag", false, ctx); - - EXPECT_EQ(result->GetValue(), false); - EXPECT_EQ(result->GetReason(), openfeature::Reason::kDefault); - EXPECT_FALSE(result->GetErrorCode().has_value()); -} - -TEST_F(EvaluatorTest, ResolveBooleanTargetingNullMissingDefaultVariant) { - nlohmann::json flags = { - {"flags", - {{"my-bool-flag", - {{"state", "ENABLED"}, - {"variants", {{"on", true}, {"off", false}}}, - {"targeting", {{"if", {{false}, "on", nullptr}}}}}}}}}; - - sync_->TriggerUpdate(flags); - - openfeature::EvaluationContext ctx = - openfeature::EvaluationContext::Builder().build(); - std::unique_ptr result = - evaluator_->ResolveBoolean("my-bool-flag", false, ctx); - - EXPECT_EQ(result->GetValue(), false); - EXPECT_EQ(result->GetReason(), openfeature::Reason::kDefault); - EXPECT_FALSE(result->GetErrorCode().has_value()); -} +INSTANTIATE_TEST_SUITE_P( + DefaultVariantHandling, EvaluatorDefaultVariantTest, + ::testing::Values( + // Missing default variant + nlohmann::json{{"my-bool-flag", + {{"state", "ENABLED"}, + {"variants", {{"on", true}, {"off", false}}}}}}, + // Null default variant + nlohmann::json{{"my-bool-flag", + {{"state", "ENABLED"}, + {"variants", {{"on", true}, {"off", false}}}, + {"defaultVariant", nullptr}}}}, + // Targeting returns null, missing default variant + nlohmann::json{{"my-bool-flag", + {{"state", "ENABLED"}, + {"variants", {{"on", true}, {"off", false}}}, + {"targeting", {{"if", {{false}, "on", nullptr}}}}}}}));