diff --git a/openfeature/BUILD b/openfeature/BUILD index fd06df3..8c2653c 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -64,6 +64,7 @@ cc_library( ":client", ":evaluation_context", ":features", + ":flag_evaluation_details", ":flag_metadata", ":general_hook", ":global_context_manager", @@ -121,6 +122,7 @@ cc_library( deps = [ ":evaluation_context", ":evaluation_options", + ":flag_evaluation_details", ":value", ], ) diff --git a/openfeature/client_api.cpp b/openfeature/client_api.cpp index 3747953..4071757 100644 --- a/openfeature/client_api.cpp +++ b/openfeature/client_api.cpp @@ -53,6 +53,35 @@ bool ClientAPI::GetBooleanValue(std::string_view flag_key, bool default_value, return EvaluateBooleanFlag(flag_key, default_value, ctx, options)->GetValue(); } +BoolFlagEvaluationDetails ClientAPI::GetBooleanDetails( + std::string_view flag_key, bool default_value) { + auto res = + EvaluateBooleanFlag(flag_key, default_value, std::nullopt, std::nullopt); + return {std::string(flag_key), *res}; +} + +BoolFlagEvaluationDetails ClientAPI::GetBooleanDetails( + std::string_view flag_key, bool default_value, + const EvaluationContext& ctx) { + auto res = EvaluateBooleanFlag(flag_key, default_value, ctx, std::nullopt); + return {std::string(flag_key), *res}; +} + +BoolFlagEvaluationDetails ClientAPI::GetBooleanDetails( + std::string_view flag_key, bool default_value, + const EvaluationOptions& options) { + auto res = + EvaluateBooleanFlag(flag_key, default_value, std::nullopt, options); + return {std::string(flag_key), *res}; +} + +BoolFlagEvaluationDetails ClientAPI::GetBooleanDetails( + std::string_view flag_key, bool default_value, const EvaluationContext& ctx, + const EvaluationOptions& options) { + auto res = EvaluateBooleanFlag(flag_key, default_value, ctx, options); + return {std::string(flag_key), *res}; +} + std::string ClientAPI::GetStringValue(std::string_view flag_key, std::string_view default_value) { return EvaluateStringFlag(flag_key, default_value, std::nullopt, std::nullopt) @@ -80,6 +109,34 @@ std::string ClientAPI::GetStringValue(std::string_view flag_key, return EvaluateStringFlag(flag_key, default_value, ctx, options)->GetValue(); } +StringFlagEvaluationDetails ClientAPI::GetStringDetails( + std::string_view flag_key, std::string_view default_value) { + auto res = + EvaluateStringFlag(flag_key, default_value, std::nullopt, std::nullopt); + return {std::string(flag_key), *res}; +} + +StringFlagEvaluationDetails ClientAPI::GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationContext& ctx) { + auto res = EvaluateStringFlag(flag_key, default_value, ctx, std::nullopt); + return {std::string(flag_key), *res}; +} + +StringFlagEvaluationDetails ClientAPI::GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationOptions& options) { + auto res = EvaluateStringFlag(flag_key, default_value, std::nullopt, options); + return {std::string(flag_key), *res}; +} + +StringFlagEvaluationDetails ClientAPI::GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) { + auto res = EvaluateStringFlag(flag_key, default_value, ctx, options); + return {std::string(flag_key), *res}; +} + int64_t ClientAPI::GetIntegerValue(std::string_view flag_key, int64_t default_value) { return EvaluateIntegerFlag(flag_key, default_value, std::nullopt, @@ -108,6 +165,35 @@ int64_t ClientAPI::GetIntegerValue(std::string_view flag_key, return EvaluateIntegerFlag(flag_key, default_value, ctx, options)->GetValue(); } +IntFlagEvaluationDetails ClientAPI::GetIntegerDetails(std::string_view flag_key, + int64_t default_value) { + auto res = + EvaluateIntegerFlag(flag_key, default_value, std::nullopt, std::nullopt); + return {std::string(flag_key), *res}; +} + +IntFlagEvaluationDetails ClientAPI::GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationContext& ctx) { + auto res = EvaluateIntegerFlag(flag_key, default_value, ctx, std::nullopt); + return {std::string(flag_key), *res}; +} + +IntFlagEvaluationDetails ClientAPI::GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationOptions& options) { + auto res = + EvaluateIntegerFlag(flag_key, default_value, std::nullopt, options); + return {std::string(flag_key), *res}; +} + +IntFlagEvaluationDetails ClientAPI::GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) { + auto res = EvaluateIntegerFlag(flag_key, default_value, ctx, options); + return {std::string(flag_key), *res}; +} + double ClientAPI::GetDoubleValue(std::string_view flag_key, double default_value) { return EvaluateDoubleFlag(flag_key, default_value, std::nullopt, std::nullopt) @@ -135,6 +221,34 @@ double ClientAPI::GetDoubleValue(std::string_view flag_key, return EvaluateDoubleFlag(flag_key, default_value, ctx, options)->GetValue(); } +DoubleFlagEvaluationDetails ClientAPI::GetDoubleDetails( + std::string_view flag_key, double default_value) { + auto res = + EvaluateDoubleFlag(flag_key, default_value, std::nullopt, std::nullopt); + return {std::string(flag_key), *res}; +} + +DoubleFlagEvaluationDetails ClientAPI::GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationContext& ctx) { + auto res = EvaluateDoubleFlag(flag_key, default_value, ctx, std::nullopt); + return {std::string(flag_key), *res}; +} + +DoubleFlagEvaluationDetails ClientAPI::GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationOptions& options) { + auto res = EvaluateDoubleFlag(flag_key, default_value, std::nullopt, options); + return {std::string(flag_key), *res}; +} + +DoubleFlagEvaluationDetails ClientAPI::GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) { + auto res = EvaluateDoubleFlag(flag_key, default_value, ctx, options); + return {std::string(flag_key), *res}; +} + Value ClientAPI::GetObjectValue(std::string_view flag_key, Value default_value) { return EvaluateObjectFlag(flag_key, default_value, std::nullopt, std::nullopt) @@ -159,6 +273,34 @@ Value ClientAPI::GetObjectValue(std::string_view flag_key, Value default_value, return EvaluateObjectFlag(flag_key, default_value, ctx, options)->GetValue(); } +ObjectFlagEvaluationDetails ClientAPI::GetObjectDetails( + std::string_view flag_key, Value default_value) { + auto res = + EvaluateObjectFlag(flag_key, default_value, std::nullopt, std::nullopt); + return {std::string(flag_key), *res}; +} + +ObjectFlagEvaluationDetails ClientAPI::GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationContext& ctx) { + auto res = EvaluateObjectFlag(flag_key, default_value, ctx, std::nullopt); + return {std::string(flag_key), *res}; +} + +ObjectFlagEvaluationDetails ClientAPI::GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationOptions& options) { + auto res = EvaluateObjectFlag(flag_key, default_value, std::nullopt, options); + return {std::string(flag_key), *res}; +} + +ObjectFlagEvaluationDetails ClientAPI::GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) { + auto res = EvaluateObjectFlag(flag_key, default_value, ctx, options); + return {std::string(flag_key), *res}; +} + std::unique_ptr ClientAPI::EvaluateBooleanFlag( std::string_view flag_key, bool default_value, const std::optional& ctx, diff --git a/openfeature/client_api.h b/openfeature/client_api.h index ca1574e..dec0ad3 100644 --- a/openfeature/client_api.h +++ b/openfeature/client_api.h @@ -11,6 +11,7 @@ #include "openfeature/client.h" #include "openfeature/evaluation_context.h" #include "openfeature/features.h" +#include "openfeature/flag_evaluation_details.h" #include "openfeature/general_hook.h" #include "openfeature/global_context_manager.h" #include "openfeature/metadata.h" @@ -53,6 +54,19 @@ class ClientAPI : public Client { const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Detailed boolean flag evaluation. + BoolFlagEvaluationDetails GetBooleanDetails(std::string_view flag_key, + bool default_value) override; + BoolFlagEvaluationDetails GetBooleanDetails( + std::string_view flag_key, bool default_value, + const EvaluationContext& ctx) override; + BoolFlagEvaluationDetails GetBooleanDetails( + std::string_view flag_key, bool default_value, + const EvaluationOptions& options) override; + BoolFlagEvaluationDetails GetBooleanDetails( + std::string_view flag_key, bool default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Evaluate a string flag. std::string GetStringValue(std::string_view flag_key, std::string_view default_value) override; @@ -67,6 +81,19 @@ class ClientAPI : public Client { const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Detailed string flag evaluation. + StringFlagEvaluationDetails GetStringDetails( + std::string_view flag_key, std::string_view default_value) override; + StringFlagEvaluationDetails GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationContext& ctx) override; + StringFlagEvaluationDetails GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationOptions& options) override; + StringFlagEvaluationDetails GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Evaluate an integer flag. int64_t GetIntegerValue(std::string_view flag_key, int64_t default_value) override; @@ -78,6 +105,19 @@ class ClientAPI : public Client { const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Detailed integer flag evaluation. + IntFlagEvaluationDetails GetIntegerDetails(std::string_view flag_key, + int64_t default_value) override; + IntFlagEvaluationDetails GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationContext& ctx) override; + IntFlagEvaluationDetails GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationOptions& options) override; + IntFlagEvaluationDetails GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Evaluate a double flag. double GetDoubleValue(std::string_view flag_key, double default_value) override; @@ -89,6 +129,19 @@ class ClientAPI : public Client { const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Detailed double flag evaluation. + DoubleFlagEvaluationDetails GetDoubleDetails(std::string_view flag_key, + double default_value) override; + DoubleFlagEvaluationDetails GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationContext& ctx) override; + DoubleFlagEvaluationDetails GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationOptions& options) override; + DoubleFlagEvaluationDetails GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Evaluate an object flag. Value GetObjectValue(std::string_view flag_key, Value default_value) override; Value GetObjectValue(std::string_view flag_key, Value default_value, @@ -99,6 +152,19 @@ class ClientAPI : public Client { const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Detailed object flag evaluation. + ObjectFlagEvaluationDetails GetObjectDetails(std::string_view flag_key, + Value default_value) override; + ObjectFlagEvaluationDetails GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationContext& ctx) override; + ObjectFlagEvaluationDetails GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationOptions& options) override; + ObjectFlagEvaluationDetails GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) override; + // Adds one or more hooks to the client-level hook repository. void AddHooks(std::vector> hooks) override; @@ -108,8 +174,6 @@ class ClientAPI : public Client { // Retrieves all configured client-level hooks. std::vector> GetHooks() const override; - // TODO: Add methods for detailed flag evaluation. - private: template diff --git a/openfeature/features.h b/openfeature/features.h index 7b51084..d553a01 100644 --- a/openfeature/features.h +++ b/openfeature/features.h @@ -5,6 +5,7 @@ #include "openfeature/evaluation_context.h" #include "openfeature/evaluation_options.h" +#include "openfeature/flag_evaluation_details.h" #include "openfeature/value.h" namespace openfeature { @@ -22,6 +23,17 @@ class Features { virtual bool GetBooleanValue(std::string_view flag_key, bool default_value, const EvaluationContext& ctx, const EvaluationOptions& options) = 0; + virtual BoolFlagEvaluationDetails GetBooleanDetails(std::string_view flag_key, + bool default_value) = 0; + virtual BoolFlagEvaluationDetails GetBooleanDetails( + std::string_view flag_key, bool default_value, + const EvaluationContext& ctx) = 0; + virtual BoolFlagEvaluationDetails GetBooleanDetails( + std::string_view flag_key, bool default_value, + const EvaluationOptions& options) = 0; + virtual BoolFlagEvaluationDetails GetBooleanDetails( + std::string_view flag_key, bool default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) = 0; virtual std::string GetStringValue(std::string_view flag_key, std::string_view default_value) = 0; @@ -35,6 +47,17 @@ class Features { std::string_view default_value, const EvaluationContext& ctx, const EvaluationOptions& options) = 0; + virtual StringFlagEvaluationDetails GetStringDetails( + std::string_view flag_key, std::string_view default_value) = 0; + virtual StringFlagEvaluationDetails GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationContext& ctx) = 0; + virtual StringFlagEvaluationDetails GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationOptions& options) = 0; + virtual StringFlagEvaluationDetails GetStringDetails( + std::string_view flag_key, std::string_view default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) = 0; virtual int64_t GetIntegerValue(std::string_view flag_key, int64_t default_value) = 0; @@ -48,6 +71,17 @@ class Features { int64_t default_value, const EvaluationContext& ctx, const EvaluationOptions& options) = 0; + virtual IntFlagEvaluationDetails GetIntegerDetails(std::string_view flag_key, + int64_t default_value) = 0; + virtual IntFlagEvaluationDetails GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationContext& ctx) = 0; + virtual IntFlagEvaluationDetails GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationOptions& options) = 0; + virtual IntFlagEvaluationDetails GetIntegerDetails( + std::string_view flag_key, int64_t default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) = 0; virtual double GetDoubleValue(std::string_view flag_key, double default_value) = 0; @@ -58,6 +92,17 @@ class Features { virtual double GetDoubleValue(std::string_view flag_key, double default_value, const EvaluationContext& ctx, const EvaluationOptions& options) = 0; + virtual DoubleFlagEvaluationDetails GetDoubleDetails( + std::string_view flag_key, double default_value) = 0; + virtual DoubleFlagEvaluationDetails GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationContext& ctx) = 0; + virtual DoubleFlagEvaluationDetails GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationOptions& options) = 0; + virtual DoubleFlagEvaluationDetails GetDoubleDetails( + std::string_view flag_key, double default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) = 0; virtual Value GetObjectValue(std::string_view flag_key, Value default_value) = 0; @@ -68,8 +113,17 @@ class Features { virtual Value GetObjectValue(std::string_view flag_key, Value default_value, const EvaluationContext& ctx, const EvaluationOptions& options) = 0; - - // TODO: Add detailed evaluation methods + virtual ObjectFlagEvaluationDetails GetObjectDetails( + std::string_view flag_key, Value default_value) = 0; + virtual ObjectFlagEvaluationDetails GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationContext& ctx) = 0; + virtual ObjectFlagEvaluationDetails GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationOptions& options) = 0; + virtual ObjectFlagEvaluationDetails GetObjectDetails( + std::string_view flag_key, Value default_value, + const EvaluationContext& ctx, const EvaluationOptions& options) = 0; }; } // namespace openfeature diff --git a/test/BUILD b/test/BUILD index 90d2282..70ba6f5 100644 --- a/test/BUILD +++ b/test/BUILD @@ -20,6 +20,7 @@ cc_test( deps = [ ":mock_feature_provider", "//openfeature:client_api", + "//openfeature:flag_evaluation_details", "//openfeature:hook", "@googletest//:gtest_main", ], diff --git a/test/client_api_test.cpp b/test/client_api_test.cpp index 8214211..28acf18 100644 --- a/test/client_api_test.cpp +++ b/test/client_api_test.cpp @@ -18,18 +18,23 @@ #include "openfeature/hook.h" #include "openfeature/provider_status.h" +using ::openfeature::BoolFlagEvaluationDetails; using ::openfeature::BoolResolutionDetails; using ::openfeature::ClientAPI; +using ::openfeature::DoubleFlagEvaluationDetails; using ::openfeature::ErrorCode; using ::openfeature::EvaluationContext; using ::openfeature::EvaluationOptions; using ::openfeature::FlagMetadata; using ::openfeature::GlobalContextManager; +using ::openfeature::IntFlagEvaluationDetails; using ::openfeature::Metadata; using ::openfeature::MockFeatureProvider; +using ::openfeature::ObjectFlagEvaluationDetails; using ::openfeature::ProviderRepository; using ::openfeature::ProviderStatus; using ::openfeature::Reason; +using ::openfeature::StringFlagEvaluationDetails; using ::openfeature::Value; using ::testing::_; using ::testing::DoAll; @@ -50,6 +55,9 @@ class ClientAPITest : public ::testing::Test { constexpr int kUnknownExceptionError = 43; constexpr int kSleepTimeMs = 10; +constexpr int64_t kDefaultIntValue = 42; +constexpr double kDefaultDoubleValue = 3.14; +constexpr int kDefaultObjectIntValue = 100; // Test that the constructor correctly sets the domain in the metadata. TEST_F(ClientAPITest, ConstructorSetsDomainMetadata) { @@ -99,7 +107,8 @@ TEST_F(ClientAPITest, GetStringValueReturnsDefaultWithNoopProvider) { TEST_F(ClientAPITest, GetIntegerValueReturnsDefaultWithNoopProvider) { ClientAPI client(repo_, "test-domain"); std::string flag_key = "my-integer-flag"; - EXPECT_EQ(client.GetIntegerValue(flag_key, 42), 42); + EXPECT_EQ(client.GetIntegerValue(flag_key, kDefaultIntValue), + kDefaultIntValue); } // Test that GetDoubleValue returns the default value when using the default @@ -107,7 +116,8 @@ TEST_F(ClientAPITest, GetIntegerValueReturnsDefaultWithNoopProvider) { TEST_F(ClientAPITest, GetDoubleValueReturnsDefaultWithNoopProvider) { ClientAPI client(repo_, "test-domain"); std::string flag_key = "my-double-flag"; - EXPECT_DOUBLE_EQ(client.GetDoubleValue(flag_key, 3.14), 3.14); + EXPECT_DOUBLE_EQ(client.GetDoubleValue(flag_key, kDefaultDoubleValue), + kDefaultDoubleValue); } // Test that GetObjectValue returns the default value when using the default @@ -141,7 +151,8 @@ TEST_F(ClientAPITest, GetIntegerValueWithContextReturnsDefault) { ClientAPI client(repo_, "test-domain"); EvaluationContext ctx = EvaluationContext::Builder().build(); std::string flag_key = "my-integer-flag"; - EXPECT_EQ(client.GetIntegerValue(flag_key, 42, ctx), 42); + EXPECT_EQ(client.GetIntegerValue(flag_key, kDefaultIntValue, ctx), + kDefaultIntValue); } // Test that GetDoubleValue with an EvaluationContext passed in. @@ -149,7 +160,8 @@ TEST_F(ClientAPITest, GetDoubleValueWithContextReturnsDefault) { ClientAPI client(repo_, "test-domain"); EvaluationContext ctx = EvaluationContext::Builder().build(); std::string flag_key = "my-double-flag"; - EXPECT_DOUBLE_EQ(client.GetDoubleValue(flag_key, 3.14, ctx), 3.14); + EXPECT_DOUBLE_EQ(client.GetDoubleValue(flag_key, kDefaultDoubleValue, ctx), + kDefaultDoubleValue); } // Test that GetObjectValue with an EvaluationContext passed in. @@ -193,10 +205,12 @@ TEST_F(ClientAPITest, GetIntegerValueWithOptionsReturnsDefault) { EvaluationOptions options; std::string flag_key = "my-integer-flag"; - EXPECT_EQ(client.GetIntegerValue(flag_key, 42, options), 42); + EXPECT_EQ(client.GetIntegerValue(flag_key, kDefaultIntValue, options), + kDefaultIntValue); EvaluationContext ctx = EvaluationContext::Builder().build(); - EXPECT_EQ(client.GetIntegerValue(flag_key, 42, ctx, options), 42); + EXPECT_EQ(client.GetIntegerValue(flag_key, kDefaultIntValue, ctx, options), + kDefaultIntValue); } // Test GetDoubleValue with EvaluationOptions. @@ -205,10 +219,14 @@ TEST_F(ClientAPITest, GetDoubleValueWithOptionsReturnsDefault) { EvaluationOptions options; std::string flag_key = "my-double-flag"; - EXPECT_DOUBLE_EQ(client.GetDoubleValue(flag_key, 3.14, options), 3.14); + EXPECT_DOUBLE_EQ( + client.GetDoubleValue(flag_key, kDefaultDoubleValue, options), + kDefaultDoubleValue); EvaluationContext ctx = EvaluationContext::Builder().build(); - EXPECT_DOUBLE_EQ(client.GetDoubleValue(flag_key, 3.14, ctx, options), 3.14); + EXPECT_DOUBLE_EQ( + client.GetDoubleValue(flag_key, kDefaultDoubleValue, ctx, options), + kDefaultDoubleValue); } // Test GetObjectValue with EvaluationOptions. @@ -223,6 +241,147 @@ TEST_F(ClientAPITest, GetObjectValueWithOptionsReturnsDefault) { EXPECT_EQ(client.GetObjectValue(flag_key, Value(1), ctx, options), Value(1)); } +// Test GetBooleanDetails returns proper details with NoopProvider. +TEST_F(ClientAPITest, GetBooleanDetailsReturnsDetailsWithNoopProvider) { + ClientAPI client(repo_, "test-domain"); + std::string flag_key = "my-boolean-flag"; + EvaluationContext ctx = EvaluationContext::Builder().build(); + EvaluationOptions options; + + auto details1 = client.GetBooleanDetails(flag_key, true); + EXPECT_EQ(details1.GetFlagKey(), flag_key); + EXPECT_TRUE(details1.GetValue()); + EXPECT_EQ(details1.GetReason(), Reason::kDefault); + EXPECT_EQ(details1.GetErrorCode(), std::nullopt); + EXPECT_EQ(details1.GetErrorMessage(), ""); + EXPECT_EQ(details1.GetVariant(), "default-variant"); + + auto details2 = client.GetBooleanDetails(flag_key, false, ctx); + EXPECT_EQ(details2.GetFlagKey(), flag_key); + EXPECT_FALSE(details2.GetValue()); + EXPECT_EQ(details2.GetReason(), Reason::kDefault); + + auto details3 = client.GetBooleanDetails(flag_key, true, options); + EXPECT_EQ(details3.GetFlagKey(), flag_key); + EXPECT_TRUE(details3.GetValue()); + + auto details4 = client.GetBooleanDetails(flag_key, false, ctx, options); + EXPECT_EQ(details4.GetFlagKey(), flag_key); + EXPECT_FALSE(details4.GetValue()); +} + +// Test GetStringDetails returns proper details with NoopProvider. +TEST_F(ClientAPITest, GetStringDetailsReturnsDetailsWithNoopProvider) { + ClientAPI client(repo_, "test-domain"); + std::string flag_key = "my-string-flag"; + EvaluationContext ctx = EvaluationContext::Builder().build(); + EvaluationOptions options; + + auto details1 = client.GetStringDetails(flag_key, "default_val"); + EXPECT_EQ(details1.GetFlagKey(), flag_key); + EXPECT_EQ(details1.GetValue(), "default_val"); + EXPECT_EQ(details1.GetReason(), Reason::kDefault); + EXPECT_EQ(details1.GetErrorCode(), std::nullopt); + + auto details2 = client.GetStringDetails(flag_key, "default_val", ctx); + EXPECT_EQ(details2.GetFlagKey(), flag_key); + EXPECT_EQ(details2.GetValue(), "default_val"); + + auto details3 = client.GetStringDetails(flag_key, "default_val", options); + EXPECT_EQ(details3.GetFlagKey(), flag_key); + EXPECT_EQ(details3.GetValue(), "default_val"); + + auto details4 = + client.GetStringDetails(flag_key, "default_val", ctx, options); + EXPECT_EQ(details4.GetFlagKey(), flag_key); + EXPECT_EQ(details4.GetValue(), "default_val"); +} + +// Test GetIntegerDetails returns proper details with NoopProvider. +TEST_F(ClientAPITest, GetIntegerDetailsReturnsDetailsWithNoopProvider) { + ClientAPI client(repo_, "test-domain"); + std::string flag_key = "my-integer-flag"; + EvaluationContext ctx = EvaluationContext::Builder().build(); + EvaluationOptions options; + + auto details1 = client.GetIntegerDetails(flag_key, kDefaultIntValue); + EXPECT_EQ(details1.GetFlagKey(), flag_key); + EXPECT_EQ(details1.GetValue(), kDefaultIntValue); + EXPECT_EQ(details1.GetReason(), Reason::kDefault); + EXPECT_EQ(details1.GetErrorCode(), std::nullopt); + + auto details2 = client.GetIntegerDetails(flag_key, kDefaultIntValue, ctx); + EXPECT_EQ(details2.GetFlagKey(), flag_key); + EXPECT_EQ(details2.GetValue(), kDefaultIntValue); + + auto details3 = client.GetIntegerDetails(flag_key, kDefaultIntValue, options); + EXPECT_EQ(details3.GetFlagKey(), flag_key); + EXPECT_EQ(details3.GetValue(), kDefaultIntValue); + + auto details4 = + client.GetIntegerDetails(flag_key, kDefaultIntValue, ctx, options); + EXPECT_EQ(details4.GetFlagKey(), flag_key); + EXPECT_EQ(details4.GetValue(), kDefaultIntValue); +} + +// Test GetDoubleDetails returns proper details with NoopProvider. +TEST_F(ClientAPITest, GetDoubleDetailsReturnsDetailsWithNoopProvider) { + ClientAPI client(repo_, "test-domain"); + std::string flag_key = "my-double-flag"; + EvaluationContext ctx = EvaluationContext::Builder().build(); + EvaluationOptions options; + + auto details1 = client.GetDoubleDetails(flag_key, kDefaultDoubleValue); + EXPECT_EQ(details1.GetFlagKey(), flag_key); + EXPECT_DOUBLE_EQ(details1.GetValue(), kDefaultDoubleValue); + EXPECT_EQ(details1.GetReason(), Reason::kDefault); + EXPECT_EQ(details1.GetErrorCode(), std::nullopt); + + auto details2 = client.GetDoubleDetails(flag_key, kDefaultDoubleValue, ctx); + EXPECT_EQ(details2.GetFlagKey(), flag_key); + EXPECT_DOUBLE_EQ(details2.GetValue(), kDefaultDoubleValue); + + auto details3 = + client.GetDoubleDetails(flag_key, kDefaultDoubleValue, options); + EXPECT_EQ(details3.GetFlagKey(), flag_key); + EXPECT_DOUBLE_EQ(details3.GetValue(), kDefaultDoubleValue); + + auto details4 = + client.GetDoubleDetails(flag_key, kDefaultDoubleValue, ctx, options); + EXPECT_EQ(details4.GetFlagKey(), flag_key); + EXPECT_DOUBLE_EQ(details4.GetValue(), kDefaultDoubleValue); +} + +// Test GetObjectDetails returns proper details with NoopProvider. +TEST_F(ClientAPITest, GetObjectDetailsReturnsDetailsWithNoopProvider) { + ClientAPI client(repo_, "test-domain"); + std::string flag_key = "my-object-flag"; + EvaluationContext ctx = EvaluationContext::Builder().build(); + EvaluationOptions options; + + auto details1 = + client.GetObjectDetails(flag_key, Value(kDefaultObjectIntValue)); + EXPECT_EQ(details1.GetFlagKey(), flag_key); + EXPECT_EQ(details1.GetValue(), Value(kDefaultObjectIntValue)); + EXPECT_EQ(details1.GetReason(), Reason::kDefault); + EXPECT_EQ(details1.GetErrorCode(), std::nullopt); + + auto details2 = + client.GetObjectDetails(flag_key, Value(kDefaultObjectIntValue), ctx); + EXPECT_EQ(details2.GetFlagKey(), flag_key); + EXPECT_EQ(details2.GetValue(), Value(kDefaultObjectIntValue)); + + auto details3 = + client.GetObjectDetails(flag_key, Value(kDefaultObjectIntValue), options); + EXPECT_EQ(details3.GetFlagKey(), flag_key); + EXPECT_EQ(details3.GetValue(), Value(kDefaultObjectIntValue)); + + auto details4 = client.GetObjectDetails( + flag_key, Value(kDefaultObjectIntValue), ctx, options); + EXPECT_EQ(details4.GetFlagKey(), flag_key); + EXPECT_EQ(details4.GetValue(), Value(kDefaultObjectIntValue)); +} + // Test context merging logic indirectly. TEST_F(ClientAPITest, ContextMergingPrecedence) { GlobalContextManager::GetInstance().SetGlobalEvaluationContext( @@ -333,6 +492,49 @@ TEST_F(ClientAPITest, EvaluateFlagHandlesProviderErrorStatus) { EXPECT_TRUE(client.GetBooleanValue("flag", true)); } +TEST_F(ClientAPITest, GetDetailsPropagatesProviderVariantAndMetadata) { + auto mock_provider = std::make_shared>(); + FlagMetadata metadata; + metadata.data["meta_key"] = std::string("meta_val"); + + EXPECT_CALL(*mock_provider, GetBooleanEvaluation("flag", false, _)) + .WillOnce(Return(std::make_unique( + true, Reason::kTargetingMatch, "variant_a", metadata))); + + repo_.SetProvider("test-domain", mock_provider, + EvaluationContext::Builder().build(), true); + ClientAPI client(repo_, "test-domain"); + + auto details = client.GetBooleanDetails("flag", false); + EXPECT_EQ(details.GetFlagKey(), "flag"); + EXPECT_TRUE(details.GetValue()); + EXPECT_EQ(details.GetReason(), Reason::kTargetingMatch); + EXPECT_EQ(details.GetVariant(), "variant_a"); + ASSERT_EQ(details.GetFlagMetadata().data.count("meta_key"), 1); + EXPECT_EQ( + std::get(details.GetFlagMetadata().data.at("meta_key")), + "meta_val"); + EXPECT_EQ(details.GetErrorCode(), std::nullopt); +} + +TEST_F(ClientAPITest, GetDetailsHandlesProviderErrors) { + auto mock_provider = std::make_shared>(); + + EXPECT_CALL(*mock_provider, GetBooleanEvaluation("flag", false, _)) + .WillOnce(Return(absl::InternalError("Provider failed"))); + + repo_.SetProvider("test-domain", mock_provider, + EvaluationContext::Builder().build(), true); + ClientAPI client(repo_, "test-domain"); + + auto details = client.GetBooleanDetails("flag", false); + EXPECT_EQ(details.GetFlagKey(), "flag"); + EXPECT_FALSE(details.GetValue()); + EXPECT_EQ(details.GetReason(), Reason::kError); + EXPECT_EQ(details.GetErrorCode(), ErrorCode::kGeneral); + EXPECT_EQ(details.GetErrorMessage(), "Provider failed"); +} + TEST_F(ClientAPITest, EvaluateFlagHandlesProviderNullResolutionDetails) { auto mock_provider = std::make_shared>();