From a30fa81129a46852c1e9eecdc3cc4e2bcf595851 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 14 Jan 2026 14:17:08 +0000 Subject: [PATCH 1/5] Draft PR Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 35 ++++++ openfeature/client_api.cpp | 72 +++++++++++ openfeature/client_api.h | 68 ++++++++++ openfeature/openfeature.h | 26 ++-- openfeature/openfeature_api.cpp | 78 ++++++++++++ openfeature/openfeature_api.h | 85 +++++++++++++ openfeature/provider_repository.cpp | 10 ++ test/BUILD | 20 +++ test/openfeature_api_test.cpp | 187 ++++++++++++++++++++++++++++ test/provider_repository_test.cpp | 39 ++++-- 10 files changed, 597 insertions(+), 23 deletions(-) create mode 100644 openfeature/client_api.cpp create mode 100644 openfeature/client_api.h create mode 100644 openfeature/openfeature_api.cpp create mode 100644 openfeature/openfeature_api.h create mode 100644 test/openfeature_api_test.cpp diff --git a/openfeature/BUILD b/openfeature/BUILD index f05a514..e039702 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -2,6 +2,25 @@ package( default_visibility = ["//visibility:public"], ) +cc_library( + name = "client_api", + srcs = ["client_api.cpp"], + hdrs = ["client_api.h"], + include_prefix = "openfeature", + deps = [ + ":client", + ":evaluation_context", + ":features", + ":flag_metadata", + ":metadata", + ":openfeature_api", + ":provider", + ":provider_status", + ":reason", + ":resolution_details", + ], +) + cc_library( name = "client", hdrs = ["client.h"], @@ -74,6 +93,22 @@ cc_library( ], ) +cc_library( + name = "openfeature_api", + srcs = ["openfeature_api.cpp"], + hdrs = ["openfeature_api.h"], + include_prefix = "openfeature", + deps = [ + ":client", + ":client_api", + ":evaluation_context", + ":metadata", + ":openfeature", + ":provider", + ":provider_repository", + ], +) + cc_library( name = "openfeature", hdrs = ["openfeature.h"], diff --git a/openfeature/client_api.cpp b/openfeature/client_api.cpp new file mode 100644 index 0000000..62cc706 --- /dev/null +++ b/openfeature/client_api.cpp @@ -0,0 +1,72 @@ +#include "client_api.h" + +#include + +#include "openfeature/flag_metadata.h" +#include "openfeature/reason.h" + +namespace openfeature { + +ClientAPI::ClientAPI(std::shared_ptr api, + std::string_view domain) + : api_(std::move(api)), domain_(domain) {} + +Metadata ClientAPI::GetMetadata() { return Metadata{domain_}; } + +EvaluationContext ClientAPI::GetEvaluationContext() { + std::lock_guard lock(context_mutex_); + return evaluation_context_; +} + +void ClientAPI::SetEvaluationContext(const EvaluationContext& ctx) { + std::lock_guard lock(context_mutex_); + evaluation_context_ = ctx; +} + +ProviderStatus ClientAPI::GetProviderStatus() { + return api_->GetProviderStatus(domain_); +} + +bool ClientAPI::GetBooleanValue(std::string_view flag_key, bool default_value) { + return EvaluateBooleanFlag(flag_key, default_value, std::nullopt)->GetValue(); +} + +bool ClientAPI::GetBooleanValue(std::string_view flag_key, bool default_value, + const EvaluationContext& ctx) { + return EvaluateBooleanFlag(flag_key, default_value, ctx)->GetValue(); +} + +std::unique_ptr ClientAPI::EvaluateBooleanFlag( + std::string_view flag_key, bool default_value, + const std::optional& ctx) { + if (GetProviderStatus() != ProviderStatus::kReady) { + return std::make_unique( + default_value, Reason::kError, std::nullopt, FlagMetadata(), + ErrorCode::kProviderNotReady, "Provider is not ready"); + } + + std::shared_ptr provider = api_->GetProvider(domain_); + if (!provider) { + return std::make_unique( + default_value, Reason::kError, std::nullopt, FlagMetadata(), + ErrorCode::kProviderFatal, "Provider not found for domain"); + } + + EvaluationContext merged_context = MergeContexts(ctx); + return provider->GetBooleanEvaluation(flag_key, default_value, + merged_context); +} + +EvaluationContext ClientAPI::MergeContexts( + const std::optional& invocation_ctx) { + EvaluationContext api_context = api_->GetEvaluationContext(); + EvaluationContext client_context = this->GetEvaluationContext(); + // TODO: Add context merging logic after EvaluationContext is implemented + + if (invocation_ctx) { + return *invocation_ctx; + } + return GetEvaluationContext(); +} + +} // namespace openfeature \ No newline at end of file diff --git a/openfeature/client_api.h b/openfeature/client_api.h new file mode 100644 index 0000000..174ae74 --- /dev/null +++ b/openfeature/client_api.h @@ -0,0 +1,68 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_CLIENT_API_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_CLIENT_API_H_ + +#include +#include +#include +#include +#include + +#include "openfeature/client.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/features.h" +#include "openfeature/metadata.h" +#include "openfeature/openfeature_api.h" +#include "openfeature/provider.h" +#include "openfeature/provider_status.h" +#include "openfeature/resolution_details.h" + +namespace openfeature { + +// OpenFeature client implementation. +class ClientAPI : public Client { + public: + ClientAPI(std::shared_ptr api, std::string_view domain); + + ~ClientAPI() override = default; + + ClientAPI(const ClientAPI&) = delete; + ClientAPI& operator=(const ClientAPI&) = delete; + + Metadata GetMetadata() override; + + // Return an optional client-level evaluation context. + EvaluationContext GetEvaluationContext() override; + + // Set the client-level evaluation context. + void SetEvaluationContext(const EvaluationContext& ctx) override; + + // Returns the current status of the associated provider. + ProviderStatus GetProviderStatus(); + + bool GetBooleanValue(std::string_view flag_key, bool default_value) override; + bool GetBooleanValue(std::string_view flag_key, bool default_value, + const EvaluationContext& ctx) override; + + // TODO: Add methods to get and set Hooks + // TODO: Add methods for flag evaluation for other types (e.g. string, int, + // float, object) + // TODO: Add methods for detailed flag evaluation + // TODO: Overload method "GetBooleanValue" to accept "Evaluation Options" + + private: + std::unique_ptr EvaluateBooleanFlag( + std::string_view flag_key, bool default_value, + const std::optional& ctx); + + EvaluationContext MergeContexts( + const std::optional& invocation_ctx); + + std::shared_ptr api_; + std::string domain_; + EvaluationContext evaluation_context_; + mutable std::mutex context_mutex_; +}; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_CLIENT_H_ diff --git a/openfeature/openfeature.h b/openfeature/openfeature.h index ac3b24b..3f959d7 100644 --- a/openfeature/openfeature.h +++ b/openfeature/openfeature.h @@ -28,25 +28,15 @@ class OpenFeature { virtual void SetProviderAndWait( std::shared_ptr provider) = 0; - // Sets the default provider and blocks until it initializes or a timeout - // occurs. - virtual void SetProviderAndWait(std::shared_ptr provider, - std::chrono::milliseconds timeout) = 0; - // Sets a named provider and blocks until it successfully initializes. virtual void SetProviderAndWait( std::string_view domain, std::shared_ptr provider) = 0; - // Sets a named provider and blocks until it initializes or a timeout occurs. - virtual void SetProviderAndWait(std::string_view domain, - std::shared_ptr provider, - std::chrono::milliseconds timeout) = 0; - // If the domain is empty then GetProvider returns the default provider // otherwise it returns the provider for the domain. If this domain has no // provider bound, it returns the default provider. virtual std::shared_ptr GetProvider( - std::string_view domain = "") = 0; + std::string_view domain = "") const = 0; virtual std::shared_ptr GetClient() = 0; @@ -55,17 +45,21 @@ class OpenFeature { // Sets the global evaluation context. virtual void SetEvaluationContext(const EvaluationContext& ctx) = 0; - // Gets the global evaluation context - virtual EvaluationContext GetEvaluationContext( - std::shared_mutex& mutex, const EvaluationContext& ctx_src) = 0; + // Gets the global evaluation context. + virtual EvaluationContext GetEvaluationContext() const = 0; // Gets the metadata for a provider bound to a specific domain. - virtual Metadata GetProviderMetadata(std::string_view domain = "") = 0; + virtual Metadata GetProviderMetadata(std::string_view domain = "") const = 0; + + // Fetches the status of a provider for a domain. If the domain is not set or + // not found, it returns the default provider status. + virtual ProviderStatus GetProviderStatus( + std::string_view domain = "") const = 0; // Shuts down all providers and resets the API to its initial state. virtual void Shutdown() = 0; - // TODO: Add methods to add and get Hooks + // TODO: Add methods to add and get Hooks. }; } // namespace openfeature diff --git a/openfeature/openfeature_api.cpp b/openfeature/openfeature_api.cpp new file mode 100644 index 0000000..fa142e6 --- /dev/null +++ b/openfeature/openfeature_api.cpp @@ -0,0 +1,78 @@ +#include "openfeature/openfeature_api.h" + +#include "openfeature/client_api.h" + +namespace openfeature { + +OpenFeatureAPI::OpenFeatureAPI() { + // provider_repository_ is automatically constructed. + // It guarantees a NoopProvider is set by default. + // TODO: init hooks & events. +} + +OpenFeatureAPI::~OpenFeatureAPI() { Shutdown(); } + +OpenFeatureAPI& OpenFeatureAPI::GetInstance() { + static OpenFeatureAPI instance; + return instance; +} + +void OpenFeatureAPI::SetProvider(std::shared_ptr provider) { + provider_repository_.SetProvider(provider, evaluation_context_, false); +} + +void OpenFeatureAPI::SetProvider(std::string_view domain, + std::shared_ptr provider) { + provider_repository_.SetProvider(domain, provider, evaluation_context_, + false); +} + +void OpenFeatureAPI::SetProviderAndWait( + std::shared_ptr provider) { + provider_repository_.SetProvider(provider, evaluation_context_, true); +} + +void OpenFeatureAPI::SetProviderAndWait( + std::string_view domain, std::shared_ptr provider) { + provider_repository_.SetProvider(domain, provider, evaluation_context_, true); +} + +std::shared_ptr OpenFeatureAPI::GetProvider( + std::string_view domain) const { + return provider_repository_.GetProvider(domain); +} + +std::shared_ptr OpenFeatureAPI::GetClient() { return GetClient(""); } + +std::shared_ptr OpenFeatureAPI::GetClient(std::string_view domain) { + auto client = std::make_shared(this, domain); + return client; +} + +void OpenFeatureAPI::SetEvaluationContext(const EvaluationContext& ctx) { + std::unique_lock lock(context_mutex_); + evaluation_context_ = ctx; +} + +EvaluationContext OpenFeatureAPI::GetEvaluationContext() const { + std::shared_lock lock(context_mutex_); + return evaluation_context_; +} + +Metadata OpenFeatureAPI::GetProviderMetadata(std::string_view domain) const { + std::shared_ptr provider = + provider_repository_.GetProvider(domain); + if (provider) { + return provider->GetMetadata(); + } + return Metadata(); // Return empty metadata if provider not found +} + +ProviderStatus OpenFeatureAPI::GetProviderStatus( + std::string_view domain) const { + return provider_repository_.GetProviderStatus(domain); +} + +void OpenFeatureAPI::Shutdown() { provider_repository_.Shutdown(); } + +} // namespace openfeature \ No newline at end of file diff --git a/openfeature/openfeature_api.h b/openfeature/openfeature_api.h new file mode 100644 index 0000000..f389681 --- /dev/null +++ b/openfeature/openfeature_api.h @@ -0,0 +1,85 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_OPENFEATURE_API_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_OPENFEATURE_API_H_ + +#include +#include +#include + +#include "openfeature/client.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/metadata.h" +#include "openfeature/openfeature.h" +#include "openfeature/provider.h" +#include "openfeature/provider_repository.h" + +namespace openfeature { + +// A global singleton which holds base configuration for the OpenFeature +// library. +class OpenFeatureAPI : public OpenFeature { + public: + ~OpenFeatureAPI(); + + // Get the singleton instance of the OpenFeatureAPI. + static OpenFeatureAPI& GetInstance(); + + OpenFeatureAPI(const OpenFeatureAPI&) = delete; + OpenFeatureAPI& operator=(const OpenFeatureAPI&) = delete; + + // Set the default provider. + void SetProvider(std::shared_ptr provider) override; + + // Set a provider for a specific domain. + void SetProvider(std::string_view domain, + std::shared_ptr provider) override; + + // Set the default provider and blocks until it successfully initializes. + void SetProviderAndWait(std::shared_ptr provider) override; + + // Set a named provider and blocks until it successfully initializes. + void SetProviderAndWait(std::string_view domain, + std::shared_ptr provider) override; + // If the domain is empty then GetProvider returns the default provider + // otherwise it returns the provider for the domain. If this domain has no + // provider bound, it returns the default provider. + std::shared_ptr GetProvider( + std::string_view domain = "") const override; + + // Gets a client for the default domain. + std::shared_ptr GetClient() override; + + // Gets a client for a named domain. + std::shared_ptr GetClient(std::string_view domain) override; + + // Sets the global evaluation context. + void SetEvaluationContext(const EvaluationContext& ctx) override; + + // Gets the global evaluation context. + EvaluationContext GetEvaluationContext() const override; + + // Get metadata about the default provider if domain is empty + // or about a named provider if domain is provided. + Metadata GetProviderMetadata(std::string_view domain = "") const override; + + // Fetches the status of a provider for a domain. If the domain is not set or + // not found, it returns the default provider status. + ProviderStatus GetProviderStatus(std::string_view domain = "") const override; + + // Shuts down all providers and resets the API to its initial state. + void Shutdown() override; + + // TODO: Add methods to add and get Hooks. + // TODO: Add overload function for "GetClient()" to accept "Evaluation + // Options" + + private: + ProviderRepository provider_repository_; + EvaluationContext evaluation_context_; + mutable std::shared_mutex context_mutex_; + + OpenFeatureAPI(); +}; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_OPENFEATURE_API_H_ diff --git a/openfeature/provider_repository.cpp b/openfeature/provider_repository.cpp index 9ae404b..e15bb88 100644 --- a/openfeature/provider_repository.cpp +++ b/openfeature/provider_repository.cpp @@ -115,6 +115,16 @@ void ProviderRepository::Shutdown() { } } provider_manager_.clear(); + + // Re-initialize to the default state after shutting down + std::shared_ptr noop_provider = + std::make_shared(); + absl::StatusOr> status_manager = + FeatureProviderStatusManager::Create(noop_provider); + if (status_manager.ok()) { + default_manager_ = std::move(status_manager.value()); + default_manager_->SetStatus(ProviderStatus::kReady); + } } void ProviderRepository::PrepareAndInitializeProvider( diff --git a/test/BUILD b/test/BUILD index bddb116..5f3ce23 100644 --- a/test/BUILD +++ b/test/BUILD @@ -9,6 +9,16 @@ cc_library( ], ) +cc_test( + name = "client_api_test", + srcs = ["client_api_test.cpp"], + deps = [ + ":mock_feature_provider", + "//openfeature:client_api", + "@googletest//:gtest_main", + ], +) + cc_test( name = "feature_provider_status_manager_test", srcs = ["feature_provider_status_manager_test.cpp"], @@ -46,3 +56,13 @@ cc_test( "@googletest//:gtest_main", ], ) + +cc_test( + name = "openfeature_api_test", + srcs = ["openfeature_api_test.cpp"], + deps = [ + ":mock_feature_provider", + "//openfeature:openfeature_api", + "@googletest//:gtest_main", + ], +) diff --git a/test/openfeature_api_test.cpp b/test/openfeature_api_test.cpp new file mode 100644 index 0000000..78679c9 --- /dev/null +++ b/test/openfeature_api_test.cpp @@ -0,0 +1,187 @@ +#include "openfeature/openfeature_api.h" + +#include +#include + +#include +#include + +#include "mocks/mock_feature_provider.h" +#include "openfeature/noop_provider.h" + +using namespace openfeature; +using ::testing::_; +using ::testing::Return; + +class OpenFeatureAPITest : public ::testing::Test { + protected: + // To ensure test isolation for the singleton, we shut it down before and + // after each test, to reset it to its default state. + void SetUp() override {} + void TearDown() override { api.Shutdown(); } + + OpenFeatureAPI& api = OpenFeatureAPI::GetInstance(); +}; + +// Test that GetInstance always returns the same singleton instance. +TEST_F(OpenFeatureAPITest, GetInstanceReturnsSameInstance) { + OpenFeatureAPI& instance1 = OpenFeatureAPI::GetInstance(); + OpenFeatureAPI& instance2 = OpenFeatureAPI::GetInstance(); + ASSERT_EQ(&instance1, &instance2); +} + +// Test that the API is initialized with a NoopProvider by default. +TEST_F(OpenFeatureAPITest, InitialStateHasNoopProvider) { + std::shared_ptr provider = api.GetProvider(); + ASSERT_NE(provider, nullptr); + EXPECT_NE(dynamic_cast(provider.get()), nullptr); +} + +// Test setting the default provider and waiting for its initialization. +TEST_F(OpenFeatureAPITest, SetAndGetDefaultProviderAndWait) { + std::shared_ptr mock_provider = + std::make_shared(); + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + + api.SetProviderAndWait(mock_provider); + + EXPECT_EQ(api.GetProvider(), mock_provider); +} + +// Test setting a named provider and waiting for its initialization. +TEST_F(OpenFeatureAPITest, SetAndGetNamedProviderAndWait) { + std::shared_ptr mock_provider = + std::make_shared(); + std::string domain = "test-domain"; + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + + api.SetProviderAndWait(domain, mock_provider); + + EXPECT_EQ(api.GetProvider(domain), mock_provider); + EXPECT_NE(dynamic_cast(api.GetProvider().get()), nullptr); +} + +// Test that getting a provider for a non-existent domain falls back to the +// default. +TEST_F(OpenFeatureAPITest, GetProviderFallsBackToDefault) { + std::shared_ptr default_provider = api.GetProvider(); + std::shared_ptr unknown_domain_provider = + api.GetProvider("unknown-domain"); + EXPECT_EQ(default_provider, unknown_domain_provider); +} + +// Test getting metadata from the default provider. +TEST_F(OpenFeatureAPITest, GetProviderMetadataForDefault) { + Metadata metadata = api.GetProviderMetadata(); + EXPECT_EQ(metadata.name, "Noop Provider"); +} + +// Test getting metadata from a named provider. +TEST_F(OpenFeatureAPITest, GetProviderMetadataForNamed) { + std::shared_ptr mock_provider = + std::make_shared(); + std::string domain = "metadata-domain"; + Metadata expected_metadata; + expected_metadata.name = "Mock Provider"; + + EXPECT_CALL(*mock_provider, GetMetadata()) + .WillOnce(Return(expected_metadata)); + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + + api.SetProviderAndWait(domain, mock_provider); + Metadata actual_metadata = api.GetProviderMetadata(domain); + + EXPECT_EQ(actual_metadata.name, expected_metadata.name); +} + +// Test that the Shutdown method calls Shutdown on all registered providers. +TEST_F(OpenFeatureAPITest, ShutdownCallsProviderShutdown) { + std::shared_ptr mock_default_provider = + std::make_shared(); + std::shared_ptr mock_named_provider = + std::make_shared(); + std::string domain = "shutdown-domain"; + + EXPECT_CALL(*mock_default_provider, Init(_)) + .WillOnce(Return(absl::OkStatus())); + EXPECT_CALL(*mock_named_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + + api.SetProviderAndWait(mock_default_provider); + api.SetProviderAndWait(domain, mock_named_provider); + + EXPECT_CALL(*mock_default_provider, Shutdown()) + .WillOnce(Return(absl::OkStatus())); + EXPECT_CALL(*mock_named_provider, Shutdown()) + .WillOnce(Return(absl::OkStatus())); + + api.Shutdown(); +} + +// Test the asynchronous SetProvider to ensure it doesn't block. +TEST_F(OpenFeatureAPITest, SetProviderAsyncDoesNotBlock) { + std::shared_ptr mock_provider = + std::make_shared(); + std::promise init_can_start; + std::future init_started_future = init_can_start.get_future(); + std::promise init_can_complete; + std::future init_can_complete_future = init_can_complete.get_future(); + + EXPECT_CALL(*mock_provider, Init(_)).WillOnce([&](const auto&) { + init_can_start.set_value(); + init_can_complete_future.wait(); + return absl::OkStatus(); + }); + EXPECT_CALL(*mock_provider, Shutdown()).WillOnce(Return(absl::OkStatus())); + api.SetProvider(mock_provider); + + // Confirm the background task has started. + auto status = init_started_future.wait_for(std::chrono::seconds(1)); + ASSERT_EQ(status, std::future_status::ready) + << "Async initialization did not start."; + + // Allow the init to complete. + init_can_complete.set_value(); +} + +// Test the asynchronous SetProvider for a named provider to ensure it doesn't +// block. +TEST_F(OpenFeatureAPITest, SetNamedProviderAsyncDoesNotBlock) { + std::shared_ptr mock_provider = + std::make_shared(); + std::string domain = "async-domain"; + std::promise init_can_start; + std::future init_started_future = init_can_start.get_future(); + std::promise init_can_complete; + std::future init_can_complete_future = init_can_complete.get_future(); + + EXPECT_CALL(*mock_provider, Init(_)).WillOnce([&](const auto&) { + init_can_start.set_value(); + init_can_complete_future.wait(); + return absl::OkStatus(); + }); + EXPECT_CALL(*mock_provider, Shutdown()).WillOnce(Return(absl::OkStatus())); + api.SetProvider(domain, mock_provider); + + // Confirm the background task has started. + auto status = init_started_future.wait_for(std::chrono::seconds(1)); + ASSERT_EQ(status, std::future_status::ready) + << "Async initialization did not start for named provider."; + + // Allow the init to complete. + init_can_complete.set_value(); +} + +// TODO: refactor once ClientImpl is added. +// Test that GetClient methods return nullptr as per the current implementation. +TEST_F(OpenFeatureAPITest, GetClientReturnsNullptr) { + EXPECT_EQ(api.GetClient(), nullptr); + EXPECT_EQ(api.GetClient("some-domain"), nullptr); +} + +// TODO: Add tests for "GetEvaluationContext" and "SetEvaluationContext" once. +// EvaluationContext logic is implemented. + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/test/provider_repository_test.cpp b/test/provider_repository_test.cpp index 4f960f4..4be25b0 100644 --- a/test/provider_repository_test.cpp +++ b/test/provider_repository_test.cpp @@ -183,15 +183,24 @@ TEST_F(ProviderRepositoryTest, SetProviderWithFailedInitSetsErrorStatus) { EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kError); } -// Test that getting status after shutdown does not crash and returns a safe -// value. -TEST_F(ProviderRepositoryTest, GetProviderStatusAfterShutdownReturnsNotReady) { - ASSERT_NE(repo.GetProvider(), nullptr); - ASSERT_EQ(repo.GetProviderStatus(), ProviderStatus::kReady); +// Test that Shutdown resets the repository to its initial +// state. +TEST_F(ProviderRepositoryTest, ShutdownResetsToReadyNoopProvider) { + // Set a mock provider to ensure we're not in the initial state. + auto mock_provider = std::make_shared(); + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + repo.SetProvider(mock_provider, ctx, true); + ASSERT_EQ(repo.GetProvider(), mock_provider); + // Expect the mock provider to be shut down. + EXPECT_CALL(*mock_provider, Shutdown()).WillOnce(Return(absl::OkStatus())); repo.Shutdown(); - EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kNotReady); + // After shutdown, the repository should be reset to the default NoopProvider. + auto provider = repo.GetProvider(); + ASSERT_NE(provider, nullptr); + EXPECT_NE(dynamic_cast(provider.get()), nullptr); + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kReady); } // Test to verify the old provider is shutdown after a new one is ready. @@ -264,8 +273,24 @@ TEST_F(ProviderRepositoryTest, ShutdownAllProviders) { EXPECT_CALL(*mock_named_1, Shutdown()).WillOnce(Return(absl::OkStatus())); EXPECT_CALL(*mock_named_2, Shutdown()).WillOnce(Return(absl::OkStatus())); + // Keep a reference to the old manager. + std::shared_ptr old_manager = + repo.GetFeatureProviderStatusManager(); + repo.Shutdown(); - EXPECT_EQ(repo.GetFeatureProviderStatusManager(), nullptr); + + // Assert that a new default manager has been created and it's not the old + // one. + std::shared_ptr new_manager = + repo.GetFeatureProviderStatusManager(); + ASSERT_NE(new_manager, nullptr); + + ASSERT_NE(new_manager, old_manager); + + // Assert that the new provider is the default NoopProvider. + std::shared_ptr new_provider = new_manager->GetProvider(); + ASSERT_NE(new_provider, nullptr); + EXPECT_NE(dynamic_cast(new_provider.get()), nullptr); } // Test to verify that Shutdown waits for asynchronous initialization to finish. From 8b728558e4b7187e0d746fe7ae45e28c42108073 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Mon, 19 Jan 2026 10:59:36 +0000 Subject: [PATCH 2/5] Draft 2 Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 15 +++- openfeature/client_api.cpp | 14 ++-- openfeature/client_api.h | 14 ++-- openfeature/global_context_manager.cpp | 23 ++++++ openfeature/global_context_manager.h | 29 +++++++ openfeature/openfeature_api.cpp | 26 +++--- openfeature/openfeature_api.h | 3 +- test/BUILD | 9 +++ test/client_api_test.cpp | 105 +++++++++++++++++++++++++ test/global_context_manager_test.cpp | 86 ++++++++++++++++++++ 10 files changed, 298 insertions(+), 26 deletions(-) create mode 100644 openfeature/global_context_manager.cpp create mode 100644 openfeature/global_context_manager.h create mode 100644 test/client_api_test.cpp create mode 100644 test/global_context_manager_test.cpp diff --git a/openfeature/BUILD b/openfeature/BUILD index e039702..3242606 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -12,9 +12,10 @@ cc_library( ":evaluation_context", ":features", ":flag_metadata", + ":global_context_manager", ":metadata", - ":openfeature_api", ":provider", + ":provider_repository", ":provider_status", ":reason", ":resolution_details", @@ -74,6 +75,16 @@ cc_library( include_prefix = "openfeature", ) +cc_library( + name = "global_context_manager", + srcs = ["global_context_manager.cpp"], + hdrs = ["global_context_manager.h"], + include_prefix = "openfeature", + deps = [ + ":evaluation_context", + ], +) + cc_library( name = "metadata", hdrs = ["metadata.h"], @@ -92,7 +103,6 @@ cc_library( ":resolution_details", ], ) - cc_library( name = "openfeature_api", srcs = ["openfeature_api.cpp"], @@ -102,6 +112,7 @@ cc_library( ":client", ":client_api", ":evaluation_context", + ":global_context_manager", ":metadata", ":openfeature", ":provider", diff --git a/openfeature/client_api.cpp b/openfeature/client_api.cpp index 62cc706..f8c1364 100644 --- a/openfeature/client_api.cpp +++ b/openfeature/client_api.cpp @@ -3,13 +3,12 @@ #include #include "openfeature/flag_metadata.h" +#include "openfeature/global_context_manager.h" #include "openfeature/reason.h" namespace openfeature { -ClientAPI::ClientAPI(std::shared_ptr api, - std::string_view domain) - : api_(std::move(api)), domain_(domain) {} +ClientAPI::ClientAPI(std::string_view domain) : domain_(domain) {} Metadata ClientAPI::GetMetadata() { return Metadata{domain_}; } @@ -24,7 +23,7 @@ void ClientAPI::SetEvaluationContext(const EvaluationContext& ctx) { } ProviderStatus ClientAPI::GetProviderStatus() { - return api_->GetProviderStatus(domain_); + return provider_repository_.GetProviderStatus(domain_); } bool ClientAPI::GetBooleanValue(std::string_view flag_key, bool default_value) { @@ -45,7 +44,8 @@ std::unique_ptr ClientAPI::EvaluateBooleanFlag( ErrorCode::kProviderNotReady, "Provider is not ready"); } - std::shared_ptr provider = api_->GetProvider(domain_); + std::shared_ptr provider = + provider_repository_.GetProvider(domain_); if (!provider) { return std::make_unique( default_value, Reason::kError, std::nullopt, FlagMetadata(), @@ -59,7 +59,9 @@ std::unique_ptr ClientAPI::EvaluateBooleanFlag( EvaluationContext ClientAPI::MergeContexts( const std::optional& invocation_ctx) { - EvaluationContext api_context = api_->GetEvaluationContext(); + // EvaluationContext api_context = api_->GetEvaluationContext(); + EvaluationContext api_context = + GlobalContextManager::GetInstance().GetGlobalEvaluationContext(); EvaluationContext client_context = this->GetEvaluationContext(); // TODO: Add context merging logic after EvaluationContext is implemented diff --git a/openfeature/client_api.h b/openfeature/client_api.h index 174ae74..0fac88c 100644 --- a/openfeature/client_api.h +++ b/openfeature/client_api.h @@ -10,18 +10,20 @@ #include "openfeature/client.h" #include "openfeature/evaluation_context.h" #include "openfeature/features.h" +#include "openfeature/global_context_manager.h" #include "openfeature/metadata.h" -#include "openfeature/openfeature_api.h" #include "openfeature/provider.h" +#include "openfeature/provider_repository.h" #include "openfeature/provider_status.h" #include "openfeature/resolution_details.h" namespace openfeature { // OpenFeature client implementation. -class ClientAPI : public Client { +class ClientAPI : public Client, public Features { public: - ClientAPI(std::shared_ptr api, std::string_view domain); + // ClientAPI(std::shared_ptr api, std::string_view domain); + ClientAPI(std::string_view domain); ~ClientAPI() override = default; @@ -37,7 +39,7 @@ class ClientAPI : public Client { void SetEvaluationContext(const EvaluationContext& ctx) override; // Returns the current status of the associated provider. - ProviderStatus GetProviderStatus(); + ProviderStatus GetProviderStatus() override; bool GetBooleanValue(std::string_view flag_key, bool default_value) override; bool GetBooleanValue(std::string_view flag_key, bool default_value, @@ -57,7 +59,7 @@ class ClientAPI : public Client { EvaluationContext MergeContexts( const std::optional& invocation_ctx); - std::shared_ptr api_; + ProviderRepository provider_repository_; std::string domain_; EvaluationContext evaluation_context_; mutable std::mutex context_mutex_; @@ -65,4 +67,4 @@ class ClientAPI : public Client { } // namespace openfeature -#endif // CPP_SDK_INCLUDE_OPENFEATURE_CLIENT_H_ +#endif // CPP_SDK_INCLUDE_OPENFEATURE_CLIENT_API_H_ diff --git a/openfeature/global_context_manager.cpp b/openfeature/global_context_manager.cpp new file mode 100644 index 0000000..c356e22 --- /dev/null +++ b/openfeature/global_context_manager.cpp @@ -0,0 +1,23 @@ +#include "openfeature/global_context_manager.h" + +#include + +namespace openfeature { + +GlobalContextManager& GlobalContextManager::GetInstance() { + static GlobalContextManager instance; + return instance; +} + +void GlobalContextManager::SetGlobalEvaluationContext( + const EvaluationContext& ctx) { + std::unique_lock lock(context_mutex_); + global_evaluation_context_ = ctx; +} + +EvaluationContext GlobalContextManager::GetGlobalEvaluationContext() const { + std::shared_lock lock(context_mutex_); + return global_evaluation_context_; +} + +} // namespace openfeature \ No newline at end of file diff --git a/openfeature/global_context_manager.h b/openfeature/global_context_manager.h new file mode 100644 index 0000000..f5b84f4 --- /dev/null +++ b/openfeature/global_context_manager.h @@ -0,0 +1,29 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_CONTEXT_MANAGER_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_CONTEXT_MANAGER_H_ + +#include +#include + +#include "openfeature/evaluation_context.h" + +namespace openfeature { + +class GlobalContextManager { + public: + static GlobalContextManager& GetInstance(); + + GlobalContextManager(const GlobalContextManager&) = delete; + GlobalContextManager& operator=(const GlobalContextManager&) = delete; + + void SetGlobalEvaluationContext(const EvaluationContext& ctx); + EvaluationContext GetGlobalEvaluationContext() const; + + private: + GlobalContextManager() = default; + EvaluationContext global_evaluation_context_; + mutable std::shared_mutex context_mutex_; +}; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_CONTEXT_MANAGER_H_ \ No newline at end of file diff --git a/openfeature/openfeature_api.cpp b/openfeature/openfeature_api.cpp index fa142e6..4ba1623 100644 --- a/openfeature/openfeature_api.cpp +++ b/openfeature/openfeature_api.cpp @@ -1,6 +1,7 @@ #include "openfeature/openfeature_api.h" #include "openfeature/client_api.h" +#include "openfeature/global_context_manager.h" namespace openfeature { @@ -18,23 +19,30 @@ OpenFeatureAPI& OpenFeatureAPI::GetInstance() { } void OpenFeatureAPI::SetProvider(std::shared_ptr provider) { - provider_repository_.SetProvider(provider, evaluation_context_, false); + provider_repository_.SetProvider( + provider, + GlobalContextManager::GetInstance().GetGlobalEvaluationContext(), false); } void OpenFeatureAPI::SetProvider(std::string_view domain, std::shared_ptr provider) { - provider_repository_.SetProvider(domain, provider, evaluation_context_, - false); + provider_repository_.SetProvider( + domain, provider, + GlobalContextManager::GetInstance().GetGlobalEvaluationContext(), false); } void OpenFeatureAPI::SetProviderAndWait( std::shared_ptr provider) { - provider_repository_.SetProvider(provider, evaluation_context_, true); + provider_repository_.SetProvider( + provider, + GlobalContextManager::GetInstance().GetGlobalEvaluationContext(), true); } void OpenFeatureAPI::SetProviderAndWait( std::string_view domain, std::shared_ptr provider) { - provider_repository_.SetProvider(domain, provider, evaluation_context_, true); + provider_repository_.SetProvider( + domain, provider, + GlobalContextManager::GetInstance().GetGlobalEvaluationContext(), true); } std::shared_ptr OpenFeatureAPI::GetProvider( @@ -45,18 +53,16 @@ std::shared_ptr OpenFeatureAPI::GetProvider( std::shared_ptr OpenFeatureAPI::GetClient() { return GetClient(""); } std::shared_ptr OpenFeatureAPI::GetClient(std::string_view domain) { - auto client = std::make_shared(this, domain); + auto client = std::make_shared(domain); return client; } void OpenFeatureAPI::SetEvaluationContext(const EvaluationContext& ctx) { - std::unique_lock lock(context_mutex_); - evaluation_context_ = ctx; + GlobalContextManager::GetInstance().SetGlobalEvaluationContext(ctx); } EvaluationContext OpenFeatureAPI::GetEvaluationContext() const { - std::shared_lock lock(context_mutex_); - return evaluation_context_; + return GlobalContextManager::GetInstance().GetGlobalEvaluationContext(); } Metadata OpenFeatureAPI::GetProviderMetadata(std::string_view domain) const { diff --git a/openfeature/openfeature_api.h b/openfeature/openfeature_api.h index f389681..a572c4a 100644 --- a/openfeature/openfeature_api.h +++ b/openfeature/openfeature_api.h @@ -7,6 +7,7 @@ #include "openfeature/client.h" #include "openfeature/evaluation_context.h" +#include "openfeature/global_context_manager.h" #include "openfeature/metadata.h" #include "openfeature/openfeature.h" #include "openfeature/provider.h" @@ -74,8 +75,6 @@ class OpenFeatureAPI : public OpenFeature { private: ProviderRepository provider_repository_; - EvaluationContext evaluation_context_; - mutable std::shared_mutex context_mutex_; OpenFeatureAPI(); }; diff --git a/test/BUILD b/test/BUILD index 5f3ce23..a3f53cd 100644 --- a/test/BUILD +++ b/test/BUILD @@ -66,3 +66,12 @@ cc_test( "@googletest//:gtest_main", ], ) + +cc_test( + name = "global_context_manager_test", + srcs = ["global_context_manager_test.cpp"], + deps = [ + "//openfeature:global_context_manager", + "@googletest//:gtest_main", + ], +) \ No newline at end of file diff --git a/test/client_api_test.cpp b/test/client_api_test.cpp new file mode 100644 index 0000000..252a802 --- /dev/null +++ b/test/client_api_test.cpp @@ -0,0 +1,105 @@ +#include "openfeature/client_api.h" + +#include +#include + +#include +#include + +#include "absl/status/status.h" +#include "mocks/mock_feature_provider.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/global_context_manager.h" +#include "openfeature/provider_status.h" + +using namespace openfeature; +using ::testing::_; +using ::testing::Return; + +class ClientAPITest : public ::testing::Test { + protected: + void SetUp() override { + // Reset the Global Context to a clean state before each test. + GlobalContextManager::GetInstance().SetGlobalEvaluationContext( + EvaluationContext{}); + } +}; + +// Test that the constructor correctly sets the domain in the metadata. +TEST_F(ClientAPITest, ConstructorSetsDomainMetadata) { + std::string domain = "test-domain"; + ClientAPI client(domain); + + Metadata metadata = client.GetMetadata(); + EXPECT_EQ(metadata.name, domain); +} + +// Test that the provider status is Ready by default. +TEST_F(ClientAPITest, GetProviderStatusDefaultsToReady) { + ClientAPI client("test-domain"); + EXPECT_EQ(client.GetProviderStatus(), ProviderStatus::kReady); +} + +// Test setting and getting the EvaluationContext. +TEST_F(ClientAPITest, SetAndGetEvaluationContext) { + ClientAPI client("test-domain"); + EvaluationContext ctx; + + // Verify we can set the context without error. + EXPECT_NO_THROW(client.SetEvaluationContext(ctx)); + + // Verify we can retrieve it. + // Note: Since EvaluationContext is currently empty, + // we are primarily testing that the API calls function correctly. + EvaluationContext retrieved_ctx = client.GetEvaluationContext(); +} + +// Test that GetBooleanValue returns the default value when using the default +// provider. +TEST_F(ClientAPITest, GetBooleanValueReturnsDefaultWithNoopProvider) { + ClientAPI client("test-domain"); + std::string flag_key = "my-boolean-flag"; + + EXPECT_TRUE(client.GetBooleanValue(flag_key, true)); + + EXPECT_FALSE(client.GetBooleanValue(flag_key, false)); +} + +// Test GetBooleanValue with an EvaluationContext passed in. +TEST_F(ClientAPITest, GetBooleanValueWithContextReturnsDefault) { + ClientAPI client("test-domain"); + EvaluationContext ctx; + std::string flag_key = "my-boolean-flag"; + + EXPECT_TRUE(client.GetBooleanValue(flag_key, true, ctx)); + EXPECT_FALSE(client.GetBooleanValue(flag_key, false, ctx)); +} + +// Test context merging logic indirectly. +TEST_F(ClientAPITest, GetBooleanValueSafeWithMergedContexts) { + EvaluationContext global_ctx; + GlobalContextManager::GetInstance().SetGlobalEvaluationContext(global_ctx); + + ClientAPI client("test-domain"); + EvaluationContext client_ctx; + client.SetEvaluationContext(client_ctx); + + EvaluationContext invocation_ctx; + + // This call forces a merge of Global + Client + Invocation contexts. + // We expect the NoopProvider to handle the result gracefully (return + // default). + EXPECT_TRUE(client.GetBooleanValue("flag", true, invocation_ctx)); +} + +// Test behavior when the domain is empty. +TEST_F(ClientAPITest, WorksWithEmptyDomain) { + ClientAPI client(""); + EXPECT_EQ(client.GetMetadata().name, ""); + EXPECT_TRUE(client.GetBooleanValue("flag", true)); +} + +// TODO: If ClientAPI is refactored to allow injecting a MockFeatureProvider +// (e.g. by passing the OpenFeatureAPI's repository or via constructor), +// add tests here to verify that the MockProvider receives the correct +// flag key and merged EvaluationContext. \ No newline at end of file diff --git a/test/global_context_manager_test.cpp b/test/global_context_manager_test.cpp new file mode 100644 index 0000000..82911fc --- /dev/null +++ b/test/global_context_manager_test.cpp @@ -0,0 +1,86 @@ +#include "openfeature/global_context_manager.h" + +#include + +#include +#include +#include + +#include "openfeature/evaluation_context.h" + +using namespace openfeature; + +class GlobalContextManagerTest : public ::testing::Test { + protected: + // Reset to a clean state before every test. + void SetUp() override { + GlobalContextManager::GetInstance().SetGlobalEvaluationContext( + EvaluationContext{}); + } +}; + +TEST_F(GlobalContextManagerTest, ReturnsSameInstance) { + GlobalContextManager& instance1 = GlobalContextManager::GetInstance(); + GlobalContextManager& instance2 = GlobalContextManager::GetInstance(); + + EXPECT_EQ(&instance1, &instance2); +} + +TEST_F(GlobalContextManagerTest, SetAndGetContext) { + GlobalContextManager& manager = GlobalContextManager::GetInstance(); + EvaluationContext ctx; + + EXPECT_NO_THROW(manager.SetGlobalEvaluationContext(ctx)); + + EvaluationContext retrieved_ctx = manager.GetGlobalEvaluationContext(); + + // NOTE: Since EvaluationContext is currently an empty class, + // we cannot assert EQ or check member values. + // Currently, the test simply proves the API calls succeed and copy semantics + // work. +} + +TEST_F(GlobalContextManagerTest, ThreadSafetyStressTest) { + GlobalContextManager& manager = GlobalContextManager::GetInstance(); + std::atomic stop{false}; + + // Writer Thread: Continuously updates the context + std::thread writer([&]() { + while (!stop) { + EvaluationContext ctx; + // In a real scenario, we would populate ctx with different data here + manager.SetGlobalEvaluationContext(ctx); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }); + + // Reader Threads: Continuously read the context + std::vector readers; + for (int i = 0; i < 10; ++i) { + readers.emplace_back([&]() { + while (!stop) { + // Just calling the getter to ensure locks work and no race conditions + // occur resulting in a crash (segfault). + EvaluationContext ctx = manager.GetGlobalEvaluationContext(); + + // Prevent optimization from removing the call + volatile size_t size = sizeof(ctx); + (void)size; + } + }); + } + + // Let the chaos run for a short duration + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + stop = true; + writer.join(); + for (auto& t : readers) { + t.join(); + } +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file From aac3c5fe5e2957a2fcf09381e6430728eea9aa4a Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Thu, 22 Jan 2026 10:47:35 +0000 Subject: [PATCH 3/5] final Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 2 ++ openfeature/client_api.cpp | 9 +++------ openfeature/client_api.h | 15 +++++++-------- openfeature/global_context_manager.h | 5 +++++ openfeature/openfeature_api.cpp | 2 +- test/BUILD | 2 ++ test/client_api_test.cpp | 22 ++++++++++------------ test/global_context_manager_test.cpp | 19 +++++++++++-------- test/openfeature_api_test.cpp | 26 ++++++++++++++++++++------ 9 files changed, 61 insertions(+), 41 deletions(-) diff --git a/openfeature/BUILD b/openfeature/BUILD index 3242606..4517881 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -1,3 +1,5 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + package( default_visibility = ["//visibility:public"], ) diff --git a/openfeature/client_api.cpp b/openfeature/client_api.cpp index f8c1364..93ca102 100644 --- a/openfeature/client_api.cpp +++ b/openfeature/client_api.cpp @@ -8,7 +8,8 @@ namespace openfeature { -ClientAPI::ClientAPI(std::string_view domain) : domain_(domain) {} +ClientAPI::ClientAPI(ProviderRepository& repository, std::string_view domain) + : provider_repository_(repository), domain_(domain) {} Metadata ClientAPI::GetMetadata() { return Metadata{domain_}; } @@ -59,11 +60,7 @@ std::unique_ptr ClientAPI::EvaluateBooleanFlag( EvaluationContext ClientAPI::MergeContexts( const std::optional& invocation_ctx) { - // EvaluationContext api_context = api_->GetEvaluationContext(); - EvaluationContext api_context = - GlobalContextManager::GetInstance().GetGlobalEvaluationContext(); - EvaluationContext client_context = this->GetEvaluationContext(); - // TODO: Add context merging logic after EvaluationContext is implemented + // TODO: Add context merging logic after EvaluationContext is implemented. if (invocation_ctx) { return *invocation_ctx; diff --git a/openfeature/client_api.h b/openfeature/client_api.h index 0fac88c..4a3d1ed 100644 --- a/openfeature/client_api.h +++ b/openfeature/client_api.h @@ -20,10 +20,9 @@ namespace openfeature { // OpenFeature client implementation. -class ClientAPI : public Client, public Features { +class ClientAPI : public Client { public: - // ClientAPI(std::shared_ptr api, std::string_view domain); - ClientAPI(std::string_view domain); + ClientAPI(ProviderRepository& repository, std::string_view domain); ~ClientAPI() override = default; @@ -45,11 +44,11 @@ class ClientAPI : public Client, public Features { bool GetBooleanValue(std::string_view flag_key, bool default_value, const EvaluationContext& ctx) override; - // TODO: Add methods to get and set Hooks + // TODO: Add methods to get and set Hooks. // TODO: Add methods for flag evaluation for other types (e.g. string, int, - // float, object) - // TODO: Add methods for detailed flag evaluation - // TODO: Overload method "GetBooleanValue" to accept "Evaluation Options" + // float, object). + // TODO: Add methods for detailed flag evaluation. + // TODO: Overload method "GetBooleanValue" to accept "Evaluation Options". private: std::unique_ptr EvaluateBooleanFlag( @@ -59,7 +58,7 @@ class ClientAPI : public Client, public Features { EvaluationContext MergeContexts( const std::optional& invocation_ctx); - ProviderRepository provider_repository_; + ProviderRepository& provider_repository_; std::string domain_; EvaluationContext evaluation_context_; mutable std::mutex context_mutex_; diff --git a/openfeature/global_context_manager.h b/openfeature/global_context_manager.h index f5b84f4..53a24f2 100644 --- a/openfeature/global_context_manager.h +++ b/openfeature/global_context_manager.h @@ -8,6 +8,8 @@ namespace openfeature { +// Manages the global Evaluation Context for the OpenFeature SDK. +// This data is static across the application (unless explicitly changed) class GlobalContextManager { public: static GlobalContextManager& GetInstance(); @@ -15,7 +17,10 @@ class GlobalContextManager { GlobalContextManager(const GlobalContextManager&) = delete; GlobalContextManager& operator=(const GlobalContextManager&) = delete; + // Updates the global evaluation context. void SetGlobalEvaluationContext(const EvaluationContext& ctx); + + // Retrieves the current global evaluation context. EvaluationContext GetGlobalEvaluationContext() const; private: diff --git a/openfeature/openfeature_api.cpp b/openfeature/openfeature_api.cpp index 4ba1623..b100284 100644 --- a/openfeature/openfeature_api.cpp +++ b/openfeature/openfeature_api.cpp @@ -53,7 +53,7 @@ std::shared_ptr OpenFeatureAPI::GetProvider( std::shared_ptr OpenFeatureAPI::GetClient() { return GetClient(""); } std::shared_ptr OpenFeatureAPI::GetClient(std::string_view domain) { - auto client = std::make_shared(domain); + auto client = std::make_shared(provider_repository_, domain); return client; } diff --git a/test/BUILD b/test/BUILD index a3f53cd..969cc8d 100644 --- a/test/BUILD +++ b/test/BUILD @@ -1,3 +1,5 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + cc_library( name = "mock_feature_provider", testonly = True, diff --git a/test/client_api_test.cpp b/test/client_api_test.cpp index 252a802..8c5039a 100644 --- a/test/client_api_test.cpp +++ b/test/client_api_test.cpp @@ -14,7 +14,9 @@ using namespace openfeature; using ::testing::_; +using ::testing::NiceMock; using ::testing::Return; +using ::testing::StrictMock; class ClientAPITest : public ::testing::Test { protected: @@ -23,12 +25,13 @@ class ClientAPITest : public ::testing::Test { GlobalContextManager::GetInstance().SetGlobalEvaluationContext( EvaluationContext{}); } + ProviderRepository repo_; }; // Test that the constructor correctly sets the domain in the metadata. TEST_F(ClientAPITest, ConstructorSetsDomainMetadata) { std::string domain = "test-domain"; - ClientAPI client(domain); + ClientAPI client(repo_, domain); Metadata metadata = client.GetMetadata(); EXPECT_EQ(metadata.name, domain); @@ -36,28 +39,23 @@ TEST_F(ClientAPITest, ConstructorSetsDomainMetadata) { // Test that the provider status is Ready by default. TEST_F(ClientAPITest, GetProviderStatusDefaultsToReady) { - ClientAPI client("test-domain"); + ClientAPI client(repo_, "test-domain"); EXPECT_EQ(client.GetProviderStatus(), ProviderStatus::kReady); } // Test setting and getting the EvaluationContext. TEST_F(ClientAPITest, SetAndGetEvaluationContext) { - ClientAPI client("test-domain"); + ClientAPI client(repo_, "test-domain"); EvaluationContext ctx; // Verify we can set the context without error. EXPECT_NO_THROW(client.SetEvaluationContext(ctx)); - - // Verify we can retrieve it. - // Note: Since EvaluationContext is currently empty, - // we are primarily testing that the API calls function correctly. - EvaluationContext retrieved_ctx = client.GetEvaluationContext(); } // Test that GetBooleanValue returns the default value when using the default // provider. TEST_F(ClientAPITest, GetBooleanValueReturnsDefaultWithNoopProvider) { - ClientAPI client("test-domain"); + ClientAPI client(repo_, "test-domain"); std::string flag_key = "my-boolean-flag"; EXPECT_TRUE(client.GetBooleanValue(flag_key, true)); @@ -67,7 +65,7 @@ TEST_F(ClientAPITest, GetBooleanValueReturnsDefaultWithNoopProvider) { // Test GetBooleanValue with an EvaluationContext passed in. TEST_F(ClientAPITest, GetBooleanValueWithContextReturnsDefault) { - ClientAPI client("test-domain"); + ClientAPI client(repo_, "test-domain"); EvaluationContext ctx; std::string flag_key = "my-boolean-flag"; @@ -80,7 +78,7 @@ TEST_F(ClientAPITest, GetBooleanValueSafeWithMergedContexts) { EvaluationContext global_ctx; GlobalContextManager::GetInstance().SetGlobalEvaluationContext(global_ctx); - ClientAPI client("test-domain"); + ClientAPI client(repo_, "test-domain"); EvaluationContext client_ctx; client.SetEvaluationContext(client_ctx); @@ -94,7 +92,7 @@ TEST_F(ClientAPITest, GetBooleanValueSafeWithMergedContexts) { // Test behavior when the domain is empty. TEST_F(ClientAPITest, WorksWithEmptyDomain) { - ClientAPI client(""); + ClientAPI client(repo_, ""); EXPECT_EQ(client.GetMetadata().name, ""); EXPECT_TRUE(client.GetBooleanValue("flag", true)); } diff --git a/test/global_context_manager_test.cpp b/test/global_context_manager_test.cpp index 82911fc..9986f4e 100644 --- a/test/global_context_manager_test.cpp +++ b/test/global_context_manager_test.cpp @@ -28,33 +28,36 @@ TEST_F(GlobalContextManagerTest, ReturnsSameInstance) { TEST_F(GlobalContextManagerTest, SetAndGetContext) { GlobalContextManager& manager = GlobalContextManager::GetInstance(); - EvaluationContext ctx; + EvaluationContext input_ctx; - EXPECT_NO_THROW(manager.SetGlobalEvaluationContext(ctx)); + EXPECT_NO_THROW(manager.SetGlobalEvaluationContext(input_ctx)); - EvaluationContext retrieved_ctx = manager.GetGlobalEvaluationContext(); + EvaluationContext output_ctx = manager.GetGlobalEvaluationContext(); + EXPECT_NO_THROW(output_ctx = manager.GetGlobalEvaluationContext()); // NOTE: Since EvaluationContext is currently an empty class, // we cannot assert EQ or check member values. // Currently, the test simply proves the API calls succeed and copy semantics // work. + + // TODO: Add assertions. } TEST_F(GlobalContextManagerTest, ThreadSafetyStressTest) { GlobalContextManager& manager = GlobalContextManager::GetInstance(); std::atomic stop{false}; - // Writer Thread: Continuously updates the context + // Writer Thread: Continuously updates the context. std::thread writer([&]() { while (!stop) { EvaluationContext ctx; - // In a real scenario, we would populate ctx with different data here + // In a real scenario, we would populate ctx with different data here. manager.SetGlobalEvaluationContext(ctx); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }); - // Reader Threads: Continuously read the context + // Reader Threads: Continuously read the context. std::vector readers; for (int i = 0; i < 10; ++i) { readers.emplace_back([&]() { @@ -63,14 +66,14 @@ TEST_F(GlobalContextManagerTest, ThreadSafetyStressTest) { // occur resulting in a crash (segfault). EvaluationContext ctx = manager.GetGlobalEvaluationContext(); - // Prevent optimization from removing the call + // Prevent optimization from removing the call. volatile size_t size = sizeof(ctx); (void)size; } }); } - // Let the chaos run for a short duration + // Let the chaos run for a short duration. std::this_thread::sleep_for(std::chrono::milliseconds(100)); stop = true; diff --git a/test/openfeature_api_test.cpp b/test/openfeature_api_test.cpp index 78679c9..9a20082 100644 --- a/test/openfeature_api_test.cpp +++ b/test/openfeature_api_test.cpp @@ -18,7 +18,10 @@ class OpenFeatureAPITest : public ::testing::Test { // To ensure test isolation for the singleton, we shut it down before and // after each test, to reset it to its default state. void SetUp() override {} - void TearDown() override { api.Shutdown(); } + void TearDown() override { + api.Shutdown(); + api.SetEvaluationContext(EvaluationContext{}); + } OpenFeatureAPI& api = OpenFeatureAPI::GetInstance(); }; @@ -115,6 +118,9 @@ TEST_F(OpenFeatureAPITest, ShutdownCallsProviderShutdown) { .WillOnce(Return(absl::OkStatus())); api.Shutdown(); + + testing::Mock::VerifyAndClearExpectations(mock_default_provider.get()); + testing::Mock::VerifyAndClearExpectations(mock_named_provider.get()); } // Test the asynchronous SetProvider to ensure it doesn't block. @@ -171,11 +177,19 @@ TEST_F(OpenFeatureAPITest, SetNamedProviderAsyncDoesNotBlock) { init_can_complete.set_value(); } -// TODO: refactor once ClientImpl is added. -// Test that GetClient methods return nullptr as per the current implementation. -TEST_F(OpenFeatureAPITest, GetClientReturnsNullptr) { - EXPECT_EQ(api.GetClient(), nullptr); - EXPECT_EQ(api.GetClient("some-domain"), nullptr); +// Test that GetClient returns a valid default ClientAPI instance. +TEST_F(OpenFeatureAPITest, GetDefaultClient) { + std::shared_ptr client = api.GetClient(); + EXPECT_NE(client, nullptr) << "GetClient() should return a valid ptr"; + EXPECT_EQ(client->GetMetadata().name, ""); +} + +// Test that GetClient returns a valid named ClientAPI instance. +TEST_F(OpenFeatureAPITest, GetNamedClient) { + std::shared_ptr named_client = api.GetClient("some-domain"); + EXPECT_NE(named_client, nullptr) + << "GetClient(domain) should return a valid ptr"; + EXPECT_EQ(named_client->GetMetadata().name, "some-domain"); } // TODO: Add tests for "GetEvaluationContext" and "SetEvaluationContext" once. From fd9d8a036543f826e14432416df023de12b79174 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 27 Jan 2026 08:57:31 +0000 Subject: [PATCH 4/5] update bazel Signed-off-by: NeaguGeorgiana23 --- .bazelversion | 1 + 1 file changed, 1 insertion(+) create mode 100644 .bazelversion diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..6b0e58e --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +7.4.1 \ No newline at end of file From 875c7a1b38f465ed839a66aeffb1bbff53365fb8 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 27 Jan 2026 09:36:47 +0000 Subject: [PATCH 5/5] fix linter error Signed-off-by: NeaguGeorgiana23 --- openfeature/openfeature_api.cpp | 2 -- openfeature/openfeature_api.h | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/openfeature/openfeature_api.cpp b/openfeature/openfeature_api.cpp index b100284..e839764 100644 --- a/openfeature/openfeature_api.cpp +++ b/openfeature/openfeature_api.cpp @@ -11,8 +11,6 @@ OpenFeatureAPI::OpenFeatureAPI() { // TODO: init hooks & events. } -OpenFeatureAPI::~OpenFeatureAPI() { Shutdown(); } - OpenFeatureAPI& OpenFeatureAPI::GetInstance() { static OpenFeatureAPI instance; return instance; diff --git a/openfeature/openfeature_api.h b/openfeature/openfeature_api.h index a572c4a..840f31c 100644 --- a/openfeature/openfeature_api.h +++ b/openfeature/openfeature_api.h @@ -19,7 +19,7 @@ namespace openfeature { // library. class OpenFeatureAPI : public OpenFeature { public: - ~OpenFeatureAPI(); + ~OpenFeatureAPI() = default; // Get the singleton instance of the OpenFeatureAPI. static OpenFeatureAPI& GetInstance();