From 0b656027c6fe569354ac21a3abd9c320a06a6ff3 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 17 Feb 2026 15:24:57 +0000 Subject: [PATCH 01/13] Set up environment to write e2e tests Signed-off-by: NeaguGeorgiana23 --- .gitmodules | 3 ++ BUILD | 6 +++ MODULE.bazel | 1 + spec | 1 + test/e2e/BUILD | 78 +++++++++++++++++++++++++++ test/e2e/context_storing_provider.cpp | 29 ++++++++++ test/e2e/context_storing_provider.h | 29 ++++++++++ test/e2e/flag.h | 35 ++++++++++++ test/e2e/state.h | 30 +++++++++++ test/e2e/steps/BUILD | 27 ++++++++++ test/e2e/steps/minimal_steps.cpp | 47 ++++++++++++++++ 11 files changed, 286 insertions(+) create mode 100644 .gitmodules create mode 160000 spec create mode 100644 test/e2e/BUILD create mode 100644 test/e2e/context_storing_provider.cpp create mode 100644 test/e2e/context_storing_provider.h create mode 100644 test/e2e/flag.h create mode 100644 test/e2e/state.h create mode 100644 test/e2e/steps/BUILD create mode 100644 test/e2e/steps/minimal_steps.cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..85115b5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "spec"] + path = spec + url = https://github.com/open-feature/spec.git diff --git a/BUILD b/BUILD index ffd0fb0..759803c 100644 --- a/BUILD +++ b/BUILD @@ -1 +1,7 @@ package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "openfeature_gherkin_spec_features", + srcs = glob(["spec/specification/assets/gherkin/*.feature"]), + visibility = ["//visibility:public"], # Make it accessible to other packages +) \ No newline at end of file diff --git a/MODULE.bazel b/MODULE.bazel index 028d3db..4c15f03 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,6 +3,7 @@ module(name = "openfeature_cpp_sdk") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") bazel_dep(name = "abseil-cpp", version = "20250814.2") bazel_dep(name = "rules_cc", version = "0.2.16") +bazel_dep(name = "cucumber-cpp", version = "0.8.0.bcr.1") # Hedron's Compile Commands Extractor for Bazel # https://github.com/hedronvision/bazel-compile-commands-extractor diff --git a/spec b/spec new file mode 160000 index 0000000..eefdf43 --- /dev/null +++ b/spec @@ -0,0 +1 @@ +Subproject commit eefdf439c5a5b69ccde036c3c6959a4a6c17e08c diff --git a/test/e2e/BUILD b/test/e2e/BUILD new file mode 100644 index 0000000..6f2752b --- /dev/null +++ b/test/e2e/BUILD @@ -0,0 +1,78 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +package( + default_visibility = ["//visibility:public"], +) + +cc_library( + name = "context_storing_provider", + srcs = [ + "context_storing_provider.cpp", + ], + hdrs = [ + "context_storing_provider.h", + ], + testonly = True, + include_prefix = "test/e2e", + deps = [ + "//openfeature:evaluation_context", + "//openfeature:provider", + "//openfeature:resolution_details", + "//openfeature:metadata", + "//openfeature:flag_metadata", + "//openfeature:reason", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + ], +) + +cc_library( + name = "flag", + hdrs = [ + "flag.h", + ], + testonly = True, + include_prefix = "test/e2e", + deps = [ + "//openfeature:client_api", + "//openfeature:evaluation_context", + "//openfeature:provider", + "//openfeature:resolution_details", + "//openfeature:flag_metadata", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + ], +) + +cc_library( + name = "state", + hdrs = [ + "state.h", + ], + testonly = True, + include_prefix = "test/e2e", + deps = [ + "//openfeature:client_api", + "//openfeature:evaluation_context", + "//openfeature:provider", + "//openfeature:resolution_details", + ":flag", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + ], +) + +cc_test( + name = "openfeature_gherkin_e2e_tests", + deps = [ + "//test/e2e/steps:gherkin_step_definitions", + ":context_storing_provider", + ":flag", + ":state", + "@cucumber-cpp//:cucumber-cpp", + "@googletest//:gtest_main", # Required for the Cucumber-Cpp test runner + ], + data = [ + "//:openfeature_gherkin_spec_features", # Reference the filegroup from the root package + ], +) diff --git a/test/e2e/context_storing_provider.cpp b/test/e2e/context_storing_provider.cpp new file mode 100644 index 0000000..adcd9b6 --- /dev/null +++ b/test/e2e/context_storing_provider.cpp @@ -0,0 +1,29 @@ +#include "test/e2e/context_storing_provider.h" +#include "openfeature/reason.h" +#include "openfeature/flag_metadata.h" + +namespace openfeature_e2e { + + +openfeature::Metadata ContextStoringProvider::GetMetadata() const { + return openfeature::Metadata{"ContextStoringProvider"}; +} + +std::unique_ptr ContextStoringProvider::GetBooleanEvaluation( + std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) { + // Store a copy of the evaluation context. + // We need to copy because `ctx` is passed by const reference. + this->last_evaluation_context_ = ctx; + + return std::make_unique( + default_value, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata(), // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + + +} // namespace openfeature_e2e diff --git a/test/e2e/context_storing_provider.h b/test/e2e/context_storing_provider.h new file mode 100644 index 0000000..025e0cc --- /dev/null +++ b/test/e2e/context_storing_provider.h @@ -0,0 +1,29 @@ +#ifndef CPP_SDK_INCLUDE_TEST_E2E_CONTEXT_STORING_PROVIDER_H_ +#define CPP_SDK_INCLUDE_TEST_E2E_CONTEXT_STORING_PROVIDER_H_ + +#include "openfeature/provider.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/resolution_details.h" +#include "openfeature/metadata.h" + +#include +#include + +namespace openfeature_e2e { + +// A simple provider that stores the last evaluation context it received. +class ContextStoringProvider : public openfeature::FeatureProvider { +public: + ~ContextStoringProvider() override = default; + + openfeature::Metadata GetMetadata() const override; + + std::unique_ptr GetBooleanEvaluation( + std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) override; + +private: + openfeature::EvaluationContext last_evaluation_context_ = openfeature::EvaluationContext::Builder().build(); +}; + +} // namespace openfeature_e2e +#endif // CPP_SDK_INCLUDE_TEST_E2E_CONTEXT_STORING_PROVIDER_H_ diff --git a/test/e2e/flag.h b/test/e2e/flag.h new file mode 100644 index 0000000..be4b833 --- /dev/null +++ b/test/e2e/flag.h @@ -0,0 +1,35 @@ +#ifndef CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ +#define CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ + +#include "openfeature/client.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/provider.h" +#include "openfeature/resolution_details.h" // For ResolutionDetails +#include "openfeature/flag_metadata.h" + +#include +#include +#include + +namespace openfeature_e2e { + +class Flag{ + public: + const std::string name_; + const std::any default_value_; + const std::string type_; + const openfeature::FlagMetadata flag_metadata_; + + Flag(std::string type, std::string name, std::any default_value) + : Flag(std::move(type), std::move(name), std::move(default_value), openfeature::FlagMetadata{}) {} + + Flag(std::string type, std::string name, std::any default_value, openfeature::FlagMetadata flag_metadata) + : name_(std::move(name)), + default_value_(std::move(default_value)), + type_(std::move(type)), + flag_metadata_(std::move(flag_metadata)) {} + +}; + +} // namespace openfeature_e2e +#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ \ No newline at end of file diff --git a/test/e2e/state.h b/test/e2e/state.h new file mode 100644 index 0000000..673a751 --- /dev/null +++ b/test/e2e/state.h @@ -0,0 +1,30 @@ +#ifndef CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ +#define CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ + +#include "openfeature/client.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/provider.h" +#include "openfeature/resolution_details.h" // For ResolutionDetails +#include "test/e2e/flag.h" + +#include +#include +#include +#include + +namespace openfeature_e2e { + +struct ScenarioState { + std::shared_ptr client; + Flag flag; + openfeature::EvaluationContext ctx = openfeature::EvaluationContext::Builder().build(); + //FlagEvaluationDetails eval + //MockHook hook + std::shared_ptr provider; + openfeature::EvaluationContext invocation_ctx = openfeature::EvaluationContext::Builder().build(); + std::vector levels; + +}; + +} // namespace openfeature_e2e +#endif //CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ \ No newline at end of file diff --git a/test/e2e/steps/BUILD b/test/e2e/steps/BUILD new file mode 100644 index 0000000..ab2b77f --- /dev/null +++ b/test/e2e/steps/BUILD @@ -0,0 +1,27 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +package( + default_visibility = ["//visibility:public"], +) + +cc_library( + name = "gherkin_step_definitions", + srcs = [ + "minimal_steps.cpp" + ], + testonly = True, + deps = [ + "//openfeature:client_api", + "//openfeature:openfeature_api", + "//openfeature:evaluation_context", + "//openfeature:metadata", + "//openfeature:resolution_details", + "//test/e2e:context_storing_provider", # Explicitly add granular dependencies + "//test/e2e:flag", + "//test/e2e:state", + "@cucumber-cpp//:cucumber-cpp", + "@googletest//:gtest", + ], + includes = ["external/cucumber-cpp"], + visibility = ["//test/e2e:__subpackages__"], +) \ No newline at end of file diff --git a/test/e2e/steps/minimal_steps.cpp b/test/e2e/steps/minimal_steps.cpp new file mode 100644 index 0000000..6f6e992 --- /dev/null +++ b/test/e2e/steps/minimal_steps.cpp @@ -0,0 +1,47 @@ +#include +#include + +#include +#include +#include + +// This include might not be strictly necessary if using autodetect.hpp, +// but it's good for clarity if you need specific internal components. +// #include // Often not needed for basic World access + +// OpenFeature C++ SDK includes +#include "openfeature/openfeature_api.h" +#include "openfeature/client.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/provider.h" +#include "openfeature/provider_repository.h" // For SetProviderAndWait +#include "openfeature/resolution_details.h" + +// Custom e2e testing components +#include "test/e2e/state.h" +#include "test/e2e/context_storing_provider.h" + +// Define a "World" object that will be instantiated for each scenario. +// This allows sharing state between steps in a scenario. +struct ScenarioWorld { + openfeature_e2e::ScenarioState state; +}; + +// IMPORTANT: Declare and define your World object for Cucumber-Cpp +// This makes World::instance() available and correctly typed. +// Place this immediately after the ScenarioWorld struct definition. +CUCUMBER_CPP_DECLARE_AND_DEFINE_WORLD(ScenarioWorld); + +// --- GIVEN Steps --- + +GIVEN("^a stable provider with retrievable context is registered$") { + // Access the shared state for this scenario + auto& state = World::instance()->state; + + // Equivalent to Java's setup() + state.provider = std::make_shared(); + openfeature::OpenFeatureAPI::GetInstance().SetProviderAndWait(state.provider); + state.client = openfeature::OpenFeatureAPI::GetInstance().GetClient(); + // In C++, the transaction context propagation is usually handled differently or isn't a direct API call like in Java. + // For now, we omit the ThreadLocalTransactionContextPropagator equivalent. +} From becd9724e8646370d6e669ab8dd8264ca9b0f601 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 17 Feb 2026 15:30:46 +0000 Subject: [PATCH 02/13] fic clang Signed-off-by: NeaguGeorgiana23 --- test/e2e/context_storing_provider.cpp | 37 ++++++++++---------- test/e2e/context_storing_provider.h | 32 +++++++++-------- test/e2e/flag.h | 49 ++++++++++++++------------- test/e2e/state.h | 35 +++++++++---------- test/e2e/steps/minimal_steps.cpp | 34 ++++++++++--------- 5 files changed, 97 insertions(+), 90 deletions(-) diff --git a/test/e2e/context_storing_provider.cpp b/test/e2e/context_storing_provider.cpp index adcd9b6..ca54257 100644 --- a/test/e2e/context_storing_provider.cpp +++ b/test/e2e/context_storing_provider.cpp @@ -1,29 +1,30 @@ #include "test/e2e/context_storing_provider.h" -#include "openfeature/reason.h" + #include "openfeature/flag_metadata.h" +#include "openfeature/reason.h" namespace openfeature_e2e { - openfeature::Metadata ContextStoringProvider::GetMetadata() const { - return openfeature::Metadata{"ContextStoringProvider"}; + return openfeature::Metadata{"ContextStoringProvider"}; } -std::unique_ptr ContextStoringProvider::GetBooleanEvaluation( - std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) { - // Store a copy of the evaluation context. - // We need to copy because `ctx` is passed by const reference. - this->last_evaluation_context_ = ctx; +std::unique_ptr +ContextStoringProvider::GetBooleanEvaluation( + std::string_view key, bool default_value, + const openfeature::EvaluationContext& ctx) { + // Store a copy of the evaluation context. + // We need to copy because `ctx` is passed by const reference. + this->last_evaluation_context_ = ctx; - return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata(), // Empty metadata - std::nullopt, // No error code - "" // Empty error message - ); + return std::make_unique( + default_value, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata(), // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); } - -} // namespace openfeature_e2e +} // namespace openfeature_e2e diff --git a/test/e2e/context_storing_provider.h b/test/e2e/context_storing_provider.h index 025e0cc..7926f6c 100644 --- a/test/e2e/context_storing_provider.h +++ b/test/e2e/context_storing_provider.h @@ -1,29 +1,31 @@ #ifndef CPP_SDK_INCLUDE_TEST_E2E_CONTEXT_STORING_PROVIDER_H_ #define CPP_SDK_INCLUDE_TEST_E2E_CONTEXT_STORING_PROVIDER_H_ -#include "openfeature/provider.h" +#include +#include + #include "openfeature/evaluation_context.h" -#include "openfeature/resolution_details.h" #include "openfeature/metadata.h" - -#include -#include +#include "openfeature/provider.h" +#include "openfeature/resolution_details.h" namespace openfeature_e2e { // A simple provider that stores the last evaluation context it received. class ContextStoringProvider : public openfeature::FeatureProvider { -public: - ~ContextStoringProvider() override = default; + public: + ~ContextStoringProvider() override = default; + + openfeature::Metadata GetMetadata() const override; - openfeature::Metadata GetMetadata() const override; + std::unique_ptr GetBooleanEvaluation( + std::string_view key, bool default_value, + const openfeature::EvaluationContext& ctx) override; - std::unique_ptr GetBooleanEvaluation( - std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) override; - -private: - openfeature::EvaluationContext last_evaluation_context_ = openfeature::EvaluationContext::Builder().build(); + private: + openfeature::EvaluationContext last_evaluation_context_ = + openfeature::EvaluationContext::Builder().build(); }; -} // namespace openfeature_e2e -#endif // CPP_SDK_INCLUDE_TEST_E2E_CONTEXT_STORING_PROVIDER_H_ +} // namespace openfeature_e2e +#endif // CPP_SDK_INCLUDE_TEST_E2E_CONTEXT_STORING_PROVIDER_H_ diff --git a/test/e2e/flag.h b/test/e2e/flag.h index be4b833..e1543ca 100644 --- a/test/e2e/flag.h +++ b/test/e2e/flag.h @@ -1,35 +1,36 @@ #ifndef CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ #define CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ -#include "openfeature/client.h" -#include "openfeature/evaluation_context.h" -#include "openfeature/provider.h" -#include "openfeature/resolution_details.h" // For ResolutionDetails -#include "openfeature/flag_metadata.h" - #include #include #include -namespace openfeature_e2e { - -class Flag{ - public: - const std::string name_; - const std::any default_value_; - const std::string type_; - const openfeature::FlagMetadata flag_metadata_; - - Flag(std::string type, std::string name, std::any default_value) - : Flag(std::move(type), std::move(name), std::move(default_value), openfeature::FlagMetadata{}) {} +#include "openfeature/client.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/flag_metadata.h" +#include "openfeature/provider.h" +#include "openfeature/resolution_details.h" // For ResolutionDetails - Flag(std::string type, std::string name, std::any default_value, openfeature::FlagMetadata flag_metadata) - : name_(std::move(name)), - default_value_(std::move(default_value)), - type_(std::move(type)), - flag_metadata_(std::move(flag_metadata)) {} +namespace openfeature_e2e { +class Flag { + public: + const std::string name_; + const std::any default_value_; + const std::string type_; + const openfeature::FlagMetadata flag_metadata_; + + Flag(std::string type, std::string name, std::any default_value) + : Flag(std::move(type), std::move(name), std::move(default_value), + openfeature::FlagMetadata{}) {} + + Flag(std::string type, std::string name, std::any default_value, + openfeature::FlagMetadata flag_metadata) + : name_(std::move(name)), + default_value_(std::move(default_value)), + type_(std::move(type)), + flag_metadata_(std::move(flag_metadata)) {} }; -} // namespace openfeature_e2e -#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ \ No newline at end of file +} // namespace openfeature_e2e +#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ \ No newline at end of file diff --git a/test/e2e/state.h b/test/e2e/state.h index 673a751..a45ce31 100644 --- a/test/e2e/state.h +++ b/test/e2e/state.h @@ -1,30 +1,31 @@ #ifndef CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ #define CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ +#include +#include +#include +#include + #include "openfeature/client.h" #include "openfeature/evaluation_context.h" #include "openfeature/provider.h" -#include "openfeature/resolution_details.h" // For ResolutionDetails +#include "openfeature/resolution_details.h" // For ResolutionDetails #include "test/e2e/flag.h" -#include -#include -#include -#include - namespace openfeature_e2e { struct ScenarioState { - std::shared_ptr client; - Flag flag; - openfeature::EvaluationContext ctx = openfeature::EvaluationContext::Builder().build(); - //FlagEvaluationDetails eval - //MockHook hook - std::shared_ptr provider; - openfeature::EvaluationContext invocation_ctx = openfeature::EvaluationContext::Builder().build(); - std::vector levels; - + std::shared_ptr client; + Flag flag; + openfeature::EvaluationContext ctx = + openfeature::EvaluationContext::Builder().build(); + // FlagEvaluationDetails eval + // MockHook hook + std::shared_ptr provider; + openfeature::EvaluationContext invocation_ctx = + openfeature::EvaluationContext::Builder().build(); + std::vector levels; }; -} // namespace openfeature_e2e -#endif //CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ \ No newline at end of file +} // namespace openfeature_e2e +#endif // CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ \ No newline at end of file diff --git a/test/e2e/steps/minimal_steps.cpp b/test/e2e/steps/minimal_steps.cpp index 6f6e992..0334834 100644 --- a/test/e2e/steps/minimal_steps.cpp +++ b/test/e2e/steps/minimal_steps.cpp @@ -1,30 +1,31 @@ #include -#include +#include #include -#include #include +#include // This include might not be strictly necessary if using autodetect.hpp, // but it's good for clarity if you need specific internal components. -// #include // Often not needed for basic World access +// #include // Often not needed for basic +// World access // OpenFeature C++ SDK includes -#include "openfeature/openfeature_api.h" #include "openfeature/client.h" #include "openfeature/evaluation_context.h" +#include "openfeature/openfeature_api.h" #include "openfeature/provider.h" -#include "openfeature/provider_repository.h" // For SetProviderAndWait +#include "openfeature/provider_repository.h" // For SetProviderAndWait #include "openfeature/resolution_details.h" // Custom e2e testing components -#include "test/e2e/state.h" #include "test/e2e/context_storing_provider.h" +#include "test/e2e/state.h" // Define a "World" object that will be instantiated for each scenario. // This allows sharing state between steps in a scenario. struct ScenarioWorld { - openfeature_e2e::ScenarioState state; + openfeature_e2e::ScenarioState state; }; // IMPORTANT: Declare and define your World object for Cucumber-Cpp @@ -35,13 +36,14 @@ CUCUMBER_CPP_DECLARE_AND_DEFINE_WORLD(ScenarioWorld); // --- GIVEN Steps --- GIVEN("^a stable provider with retrievable context is registered$") { - // Access the shared state for this scenario - auto& state = World::instance()->state; - - // Equivalent to Java's setup() - state.provider = std::make_shared(); - openfeature::OpenFeatureAPI::GetInstance().SetProviderAndWait(state.provider); - state.client = openfeature::OpenFeatureAPI::GetInstance().GetClient(); - // In C++, the transaction context propagation is usually handled differently or isn't a direct API call like in Java. - // For now, we omit the ThreadLocalTransactionContextPropagator equivalent. + // Access the shared state for this scenario + auto& state = World::instance()->state; + + // Equivalent to Java's setup() + state.provider = std::make_shared(); + openfeature::OpenFeatureAPI::GetInstance().SetProviderAndWait(state.provider); + state.client = openfeature::OpenFeatureAPI::GetInstance().GetClient(); + // In C++, the transaction context propagation is usually handled differently + // or isn't a direct API call like in Java. For now, we omit the + // ThreadLocalTransactionContextPropagator equivalent. } From 296e01a6d3828bcd52720e4d7c6a277a9a844733 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 17 Feb 2026 15:58:05 +0000 Subject: [PATCH 03/13] add linter rule Signed-off-by: NeaguGeorgiana23 --- .github/workflows/clang-format-and-lint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/clang-format-and-lint.yml b/.github/workflows/clang-format-and-lint.yml index bb372fc..e066a40 100644 --- a/.github/workflows/clang-format-and-lint.yml +++ b/.github/workflows/clang-format-and-lint.yml @@ -23,6 +23,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + submodules: 'recursive' - uses: bazel-contrib/setup-bazel@0.18.0 with: From b78cef620ab29cf429fbf66e72beb7073fee383c Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 18 Feb 2026 15:10:00 +0000 Subject: [PATCH 04/13] Create communication with cucumber Signed-off-by: NeaguGeorgiana23 --- BUILD | 5 +-- Gemfile | 3 ++ Gemfile.lock | 53 ++++++++++++++++++++++++++++++++ MODULE.bazel | 19 ++++++++++++ test/e2e/BUILD | 19 +++--------- test/e2e/steps/BUILD | 18 ++++------- test/e2e/steps/minimal_steps.cpp | 37 +++------------------- 7 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 Gemfile create mode 100644 Gemfile.lock diff --git a/BUILD b/BUILD index 759803c..3b182c1 100644 --- a/BUILD +++ b/BUILD @@ -1,7 +1,8 @@ +load("@rules_gherkin//gherkin:defs.bzl", "gherkin_library") + package(default_visibility = ["//visibility:public"]) -filegroup( +gherkin_library( name = "openfeature_gherkin_spec_features", srcs = glob(["spec/specification/assets/gherkin/*.feature"]), - visibility = ["//visibility:public"], # Make it accessible to other packages ) \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..c0128f5 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' +gem 'cucumber', '~> 10.2.0' +gem 'cucumber-wire', '~> 8.0.0' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..8e43192 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,53 @@ +GEM + remote: https://rubygems.org/ + specs: + base64 (0.3.0) + bigdecimal (4.0.1) + builder (3.3.0) + cucumber (10.2.0) + base64 (~> 0.2) + builder (~> 3.2) + cucumber-ci-environment (> 9, < 12) + cucumber-core (> 15, < 17) + cucumber-cucumber-expressions (> 17, < 20) + cucumber-html-formatter (> 21, < 23) + diff-lcs (~> 1.5) + logger (~> 1.6) + mini_mime (~> 1.1) + multi_test (~> 1.1) + sys-uname (~> 1.3) + cucumber-ci-environment (11.0.0) + cucumber-core (15.4.0) + cucumber-gherkin (> 27, < 40) + cucumber-messages (> 26, < 33) + cucumber-tag-expressions (> 5, < 9) + cucumber-cucumber-expressions (19.0.0) + bigdecimal + cucumber-gherkin (38.0.0) + cucumber-messages (>= 31, < 33) + cucumber-html-formatter (22.3.0) + cucumber-messages (> 23, < 33) + cucumber-messages (32.0.1) + cucumber-tag-expressions (8.1.0) + cucumber-wire (8.0.0) + cucumber-core (> 11, < 16) + cucumber-cucumber-expressions (> 14, < 20) + diff-lcs (1.6.2) + ffi (1.17.3-x86_64-linux-gnu) + logger (1.7.0) + memoist3 (1.0.0) + mini_mime (1.1.5) + multi_test (1.1.0) + sys-uname (1.5.0) + ffi (~> 1.1) + memoist3 (~> 1.0.0) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + cucumber (~> 10.2.0) + cucumber-wire (~> 8.0.0) + +BUNDLED WITH + 2.4.19 diff --git a/MODULE.bazel b/MODULE.bazel index 4c15f03..598e25f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -5,6 +5,25 @@ bazel_dep(name = "abseil-cpp", version = "20250814.2") bazel_dep(name = "rules_cc", version = "0.2.16") bazel_dep(name = "cucumber-cpp", version = "0.8.0.bcr.1") +bazel_dep(name = "rules_gherkin", version = "0.2.0") +bazel_dep(name = "rules_ruby", version = "0.19.0") + +# Hermetic Ruby Toolchain Configuration +ruby = use_extension("@rules_ruby//ruby:extensions.bzl", "ruby") +ruby.toolchain( + name = "ruby", + version = "jruby-9.4.5.0", # JRuby or standard MRI version suitable for execution +) + +# Cucumber Gem provisioning via Bundler integration +ruby.bundle_fetch( + name = "cucumber", + gemfile = "//:Gemfile", + gemfile_lock = "//:Gemfile.lock", +) +use_repo(ruby, "cucumber", "ruby", "ruby_toolchains") +register_toolchains("@ruby_toolchains//:all") + # Hedron's Compile Commands Extractor for Bazel # https://github.com/hedronvision/bazel-compile-commands-extractor bazel_dep(name = "hedron_compile_commands", dev_dependency = True) diff --git a/test/e2e/BUILD b/test/e2e/BUILD index 6f2752b..fedcd09 100644 --- a/test/e2e/BUILD +++ b/test/e2e/BUILD @@ -1,4 +1,5 @@ -load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_gherkin//gherkin:defs.bzl", "gherkin_test") package( default_visibility = ["//visibility:public"], @@ -12,7 +13,6 @@ cc_library( hdrs = [ "context_storing_provider.h", ], - testonly = True, include_prefix = "test/e2e", deps = [ "//openfeature:evaluation_context", @@ -31,7 +31,6 @@ cc_library( hdrs = [ "flag.h", ], - testonly = True, include_prefix = "test/e2e", deps = [ "//openfeature:client_api", @@ -49,7 +48,6 @@ cc_library( hdrs = [ "state.h", ], - testonly = True, include_prefix = "test/e2e", deps = [ "//openfeature:client_api", @@ -62,17 +60,10 @@ cc_library( ], ) -cc_test( +gherkin_test( name = "openfeature_gherkin_e2e_tests", + steps = "//test/e2e/steps:gherkin_step_definitions", deps = [ - "//test/e2e/steps:gherkin_step_definitions", - ":context_storing_provider", - ":flag", - ":state", - "@cucumber-cpp//:cucumber-cpp", - "@googletest//:gtest_main", # Required for the Cucumber-Cpp test runner - ], - data = [ - "//:openfeature_gherkin_spec_features", # Reference the filegroup from the root package + "//:openfeature_gherkin_spec_features", ], ) diff --git a/test/e2e/steps/BUILD b/test/e2e/steps/BUILD index ab2b77f..fb6e83b 100644 --- a/test/e2e/steps/BUILD +++ b/test/e2e/steps/BUILD @@ -1,27 +1,21 @@ -load("@rules_cc//cc:defs.bzl", "cc_library") +load("@rules_gherkin//gherkin:defs.bzl", "cc_gherkin_steps") -package( - default_visibility = ["//visibility:public"], -) +package(default_visibility = ["//visibility:public"]) -cc_library( +cc_gherkin_steps( name = "gherkin_step_definitions", - srcs = [ - "minimal_steps.cpp" - ], - testonly = True, + srcs = ["minimal_steps.cpp"], deps = [ "//openfeature:client_api", "//openfeature:openfeature_api", "//openfeature:evaluation_context", "//openfeature:metadata", "//openfeature:resolution_details", - "//test/e2e:context_storing_provider", # Explicitly add granular dependencies + "//test/e2e:context_storing_provider", "//test/e2e:flag", "//test/e2e:state", "@cucumber-cpp//:cucumber-cpp", - "@googletest//:gtest", + "@googletest//:gtest", ], - includes = ["external/cucumber-cpp"], visibility = ["//test/e2e:__subpackages__"], ) \ No newline at end of file diff --git a/test/e2e/steps/minimal_steps.cpp b/test/e2e/steps/minimal_steps.cpp index 0334834..8fb4a79 100644 --- a/test/e2e/steps/minimal_steps.cpp +++ b/test/e2e/steps/minimal_steps.cpp @@ -1,49 +1,20 @@ #include - #include #include -#include +#include #include -// This include might not be strictly necessary if using autodetect.hpp, -// but it's good for clarity if you need specific internal components. -// #include // Often not needed for basic -// World access - -// OpenFeature C++ SDK includes #include "openfeature/client.h" #include "openfeature/evaluation_context.h" #include "openfeature/openfeature_api.h" #include "openfeature/provider.h" -#include "openfeature/provider_repository.h" // For SetProviderAndWait +#include "openfeature/provider_repository.h" #include "openfeature/resolution_details.h" -// Custom e2e testing components #include "test/e2e/context_storing_provider.h" #include "test/e2e/state.h" -// Define a "World" object that will be instantiated for each scenario. -// This allows sharing state between steps in a scenario. -struct ScenarioWorld { - openfeature_e2e::ScenarioState state; -}; - -// IMPORTANT: Declare and define your World object for Cucumber-Cpp -// This makes World::instance() available and correctly typed. -// Place this immediately after the ScenarioWorld struct definition. -CUCUMBER_CPP_DECLARE_AND_DEFINE_WORLD(ScenarioWorld); - -// --- GIVEN Steps --- GIVEN("^a stable provider with retrievable context is registered$") { - // Access the shared state for this scenario - auto& state = World::instance()->state; - - // Equivalent to Java's setup() - state.provider = std::make_shared(); - openfeature::OpenFeatureAPI::GetInstance().SetProviderAndWait(state.provider); - state.client = openfeature::OpenFeatureAPI::GetInstance().GetClient(); - // In C++, the transaction context propagation is usually handled differently - // or isn't a direct API call like in Java. For now, we omit the - // ThreadLocalTransactionContextPropagator equivalent. -} + std::cout<< "Registering provider..."; +} \ No newline at end of file From 8467f3ef520e3a16193e0e32b64539c7c4a67a13 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 18 Feb 2026 15:36:21 +0000 Subject: [PATCH 05/13] update Ruby version Signed-off-by: NeaguGeorgiana23 --- Gemfile.lock | 3 +++ MODULE.bazel | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8e43192..a0f4b35 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ GEM specs: base64 (0.3.0) bigdecimal (4.0.1) + bigdecimal (4.0.1-java) builder (3.3.0) cucumber (10.2.0) base64 (~> 0.2) @@ -33,6 +34,7 @@ GEM cucumber-core (> 11, < 16) cucumber-cucumber-expressions (> 14, < 20) diff-lcs (1.6.2) + ffi (1.17.3-java) ffi (1.17.3-x86_64-linux-gnu) logger (1.7.0) memoist3 (1.0.0) @@ -43,6 +45,7 @@ GEM memoist3 (~> 1.0.0) PLATFORMS + universal-java-11 x86_64-linux DEPENDENCIES diff --git a/MODULE.bazel b/MODULE.bazel index 598e25f..165c843 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,13 +6,13 @@ bazel_dep(name = "rules_cc", version = "0.2.16") bazel_dep(name = "cucumber-cpp", version = "0.8.0.bcr.1") bazel_dep(name = "rules_gherkin", version = "0.2.0") -bazel_dep(name = "rules_ruby", version = "0.19.0") +bazel_dep(name = "rules_ruby", version = "0.21.1") # Hermetic Ruby Toolchain Configuration ruby = use_extension("@rules_ruby//ruby:extensions.bzl", "ruby") ruby.toolchain( name = "ruby", - version = "jruby-9.4.5.0", # JRuby or standard MRI version suitable for execution + version = "3.2.2", # JRuby or standard MRI version suitable for execution ) # Cucumber Gem provisioning via Bundler integration From c83ef340f671b34f30c765fd20a0c3feca19d13a Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 18 Feb 2026 15:36:21 +0000 Subject: [PATCH 06/13] update Ruby version Signed-off-by: NeaguGeorgiana23 --- Gemfile.lock | 3 +++ MODULE.bazel | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8e43192..a0f4b35 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ GEM specs: base64 (0.3.0) bigdecimal (4.0.1) + bigdecimal (4.0.1-java) builder (3.3.0) cucumber (10.2.0) base64 (~> 0.2) @@ -33,6 +34,7 @@ GEM cucumber-core (> 11, < 16) cucumber-cucumber-expressions (> 14, < 20) diff-lcs (1.6.2) + ffi (1.17.3-java) ffi (1.17.3-x86_64-linux-gnu) logger (1.7.0) memoist3 (1.0.0) @@ -43,6 +45,7 @@ GEM memoist3 (~> 1.0.0) PLATFORMS + universal-java-11 x86_64-linux DEPENDENCIES diff --git a/MODULE.bazel b/MODULE.bazel index 598e25f..165c843 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,13 +6,13 @@ bazel_dep(name = "rules_cc", version = "0.2.16") bazel_dep(name = "cucumber-cpp", version = "0.8.0.bcr.1") bazel_dep(name = "rules_gherkin", version = "0.2.0") -bazel_dep(name = "rules_ruby", version = "0.19.0") +bazel_dep(name = "rules_ruby", version = "0.21.1") # Hermetic Ruby Toolchain Configuration ruby = use_extension("@rules_ruby//ruby:extensions.bzl", "ruby") ruby.toolchain( name = "ruby", - version = "jruby-9.4.5.0", # JRuby or standard MRI version suitable for execution + version = "3.2.2", # JRuby or standard MRI version suitable for execution ) # Cucumber Gem provisioning via Bundler integration From 3ce73524f5b0bda7348012a594c070b80532ffdc Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Mon, 23 Feb 2026 13:38:55 +0000 Subject: [PATCH 07/13] e2e tests for MVP. Signed-off-by: NeaguGeorgiana23 --- test/BUILD | 6 +- test/e2e/BUILD | 31 +-- test/e2e/context_storing_provider.cpp | 77 ++++++- test/e2e/context_storing_provider.h | 22 +- test/e2e/flag.h | 36 ---- test/e2e/flag_test.h | 16 ++ test/e2e/state.h | 19 +- test/e2e/steps/BUILD | 11 +- test/e2e/steps/minimal_steps.cpp | 290 +++++++++++++++++++++++++- 9 files changed, 420 insertions(+), 88 deletions(-) delete mode 100644 test/e2e/flag.h create mode 100644 test/e2e/flag_test.h diff --git a/test/BUILD b/test/BUILD index 72c9ef8..aff340c 100644 --- a/test/BUILD +++ b/test/BUILD @@ -1,10 +1,12 @@ load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +package( + default_visibility = ["//visibility:public"], +) + cc_library( name = "mock_feature_provider", - testonly = True, hdrs = ["mocks/mock_feature_provider.h"], - visibility = ["//test:__pkg__"], deps = [ "//openfeature:provider", "@googletest//:gtest", diff --git a/test/e2e/BUILD b/test/e2e/BUILD index fedcd09..9a3ef3b 100644 --- a/test/e2e/BUILD +++ b/test/e2e/BUILD @@ -16,31 +16,23 @@ cc_library( include_prefix = "test/e2e", deps = [ "//openfeature:evaluation_context", - "//openfeature:provider", - "//openfeature:resolution_details", - "//openfeature:metadata", "//openfeature:flag_metadata", + "//openfeature:metadata", + "//openfeature:provider", "//openfeature:reason", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", + "//openfeature:value", + "//openfeature:resolution_details", + "@cucumber-cpp//:cucumber-cpp", + "@googletest//:gtest", ], ) cc_library( - name = "flag", + name = "flag_test", hdrs = [ - "flag.h", + "flag_test.h", ], include_prefix = "test/e2e", - deps = [ - "//openfeature:client_api", - "//openfeature:evaluation_context", - "//openfeature:provider", - "//openfeature:resolution_details", - "//openfeature:flag_metadata", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", - ], ) cc_library( @@ -50,13 +42,12 @@ cc_library( ], include_prefix = "test/e2e", deps = [ - "//openfeature:client_api", + "//openfeature:client", "//openfeature:evaluation_context", "//openfeature:provider", "//openfeature:resolution_details", - ":flag", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", + "//openfeature:value", + ":flag_test", ], ) diff --git a/test/e2e/context_storing_provider.cpp b/test/e2e/context_storing_provider.cpp index ca54257..4f2adef 100644 --- a/test/e2e/context_storing_provider.cpp +++ b/test/e2e/context_storing_provider.cpp @@ -1,7 +1,14 @@ #include "test/e2e/context_storing_provider.h" +#include + +#include + #include "openfeature/flag_metadata.h" #include "openfeature/reason.h" +#include "openfeature/value.h" + +using cucumber::ScenarioScope; namespace openfeature_e2e { @@ -13,15 +20,77 @@ std::unique_ptr ContextStoringProvider::GetBooleanEvaluation( std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) { - // Store a copy of the evaluation context. - // We need to copy because `ctx` is passed by const reference. - this->last_evaluation_context_ = ctx; + last_ctx = ctx; return std::make_unique( default_value, // The default value openfeature::Reason::kDefault, // Reason for resolution "default-variant", // A generic variant identifier - openfeature::FlagMetadata(), // Empty metadata + openfeature::FlagMetadata{}, // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + +std::unique_ptr +ContextStoringProvider::GetStringEvaluation( + std::string_view key, std::string_view default_value, + const openfeature::EvaluationContext& ctx) { + last_ctx = ctx; + std::string default_str(default_value); + return std::make_unique( + default_str, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata{}, // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + +std::unique_ptr +ContextStoringProvider::GetIntegerEvaluation( + std::string_view key, int64_t default_value, + const openfeature::EvaluationContext& ctx) { + last_ctx = ctx; + + return std::make_unique( + default_value, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata{}, // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + +std::unique_ptr +ContextStoringProvider::GetDoubleEvaluation( + std::string_view key, double default_value, + const openfeature::EvaluationContext& ctx) { + last_ctx = ctx; + + return std::make_unique( + default_value, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata{}, // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + +std::unique_ptr +ContextStoringProvider::GetObjectEvaluation( + std::string_view key, const openfeature::Value default_value, + const openfeature::EvaluationContext& ctx) { + last_ctx = ctx; + + return std::make_unique( + default_value, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata{}, // Empty metadata std::nullopt, // No error code "" // Empty error message ); diff --git a/test/e2e/context_storing_provider.h b/test/e2e/context_storing_provider.h index 7926f6c..4422f41 100644 --- a/test/e2e/context_storing_provider.h +++ b/test/e2e/context_storing_provider.h @@ -3,6 +3,7 @@ #include #include +#include #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" @@ -14,6 +15,9 @@ namespace openfeature_e2e { // A simple provider that stores the last evaluation context it received. class ContextStoringProvider : public openfeature::FeatureProvider { public: + mutable openfeature::EvaluationContext last_ctx = + openfeature::EvaluationContext::Builder().build(); + ~ContextStoringProvider() override = default; openfeature::Metadata GetMetadata() const override; @@ -22,9 +26,21 @@ class ContextStoringProvider : public openfeature::FeatureProvider { std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) override; - private: - openfeature::EvaluationContext last_evaluation_context_ = - openfeature::EvaluationContext::Builder().build(); + std::unique_ptr GetStringEvaluation( + std::string_view key, std::string_view default_value, + const openfeature::EvaluationContext& ctx) override; + + std::unique_ptr GetIntegerEvaluation( + std::string_view key, int64_t default_value, + const openfeature::EvaluationContext& ctx) override; + + std::unique_ptr GetDoubleEvaluation( + std::string_view key, double default_value, + const openfeature::EvaluationContext& ctx) override; + + std::unique_ptr GetObjectEvaluation( + std::string_view key, openfeature::Value default_value, + const openfeature::EvaluationContext& ctx) override; }; } // namespace openfeature_e2e diff --git a/test/e2e/flag.h b/test/e2e/flag.h deleted file mode 100644 index e1543ca..0000000 --- a/test/e2e/flag.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ -#define CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ - -#include -#include -#include - -#include "openfeature/client.h" -#include "openfeature/evaluation_context.h" -#include "openfeature/flag_metadata.h" -#include "openfeature/provider.h" -#include "openfeature/resolution_details.h" // For ResolutionDetails - -namespace openfeature_e2e { - -class Flag { - public: - const std::string name_; - const std::any default_value_; - const std::string type_; - const openfeature::FlagMetadata flag_metadata_; - - Flag(std::string type, std::string name, std::any default_value) - : Flag(std::move(type), std::move(name), std::move(default_value), - openfeature::FlagMetadata{}) {} - - Flag(std::string type, std::string name, std::any default_value, - openfeature::FlagMetadata flag_metadata) - : name_(std::move(name)), - default_value_(std::move(default_value)), - type_(std::move(type)), - flag_metadata_(std::move(flag_metadata)) {} -}; - -} // namespace openfeature_e2e -#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ \ No newline at end of file diff --git a/test/e2e/flag_test.h b/test/e2e/flag_test.h new file mode 100644 index 0000000..39c90b0 --- /dev/null +++ b/test/e2e/flag_test.h @@ -0,0 +1,16 @@ +#ifndef CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ +#define CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ + +#include +#include + +namespace openfeature_e2e { + +struct FlagTest { + std::string type; + std::string name; + std::any default_value; +}; + +} // namespace openfeature_e2e +#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ \ No newline at end of file diff --git a/test/e2e/state.h b/test/e2e/state.h index a45ce31..7c42200 100644 --- a/test/e2e/state.h +++ b/test/e2e/state.h @@ -9,22 +9,23 @@ #include "openfeature/client.h" #include "openfeature/evaluation_context.h" #include "openfeature/provider.h" -#include "openfeature/resolution_details.h" // For ResolutionDetails -#include "test/e2e/flag.h" +#include "openfeature/resolution_details.h" +#include "openfeature/value.h" +#include "test/e2e/flag_test.h" namespace openfeature_e2e { -struct ScenarioState { +struct State { std::shared_ptr client; - Flag flag; - openfeature::EvaluationContext ctx = - openfeature::EvaluationContext::Builder().build(); + std::shared_ptr provider; + FlagTest flag; + std::unique_ptr context; // FlagEvaluationDetails eval // MockHook hook - std::shared_ptr provider; - openfeature::EvaluationContext invocation_ctx = - openfeature::EvaluationContext::Builder().build(); + std::unique_ptr invocation_context; std::vector levels; + + openfeature::Value last_evaluation_value; }; } // namespace openfeature_e2e diff --git a/test/e2e/steps/BUILD b/test/e2e/steps/BUILD index fb6e83b..302e522 100644 --- a/test/e2e/steps/BUILD +++ b/test/e2e/steps/BUILD @@ -6,14 +6,13 @@ cc_gherkin_steps( name = "gherkin_step_definitions", srcs = ["minimal_steps.cpp"], deps = [ - "//openfeature:client_api", - "//openfeature:openfeature_api", "//openfeature:evaluation_context", - "//openfeature:metadata", - "//openfeature:resolution_details", - "//test/e2e:context_storing_provider", - "//test/e2e:flag", + "//openfeature:openfeature_api", + "//openfeature/memory_provider:flag", + "//openfeature/memory_provider:in_memory_provider", + "//test/e2e:context_storing_provider", "//test/e2e:state", + "//test:mock_feature_provider", "@cucumber-cpp//:cucumber-cpp", "@googletest//:gtest", ], diff --git a/test/e2e/steps/minimal_steps.cpp b/test/e2e/steps/minimal_steps.cpp index 2ea2292..f1ac17c 100644 --- a/test/e2e/steps/minimal_steps.cpp +++ b/test/e2e/steps/minimal_steps.cpp @@ -1,19 +1,293 @@ +#include #include #include -#include +#include #include #include -#include "openfeature/client.h" #include "openfeature/evaluation_context.h" +#include "openfeature/memory_provider/flag.h" +#include "openfeature/memory_provider/in_memory_provider.h" #include "openfeature/openfeature_api.h" -#include "openfeature/provider.h" -#include "openfeature/provider_repository.h" -#include "openfeature/resolution_details.h" #include "test/e2e/context_storing_provider.h" #include "test/e2e/state.h" +#include "test/mocks/mock_feature_provider.h" -GIVEN("^a stable provider with retrievable context is registered$") { - std::cout << "Registering provider..."< +openfeature::Flag CreateStaticFlag(T value) { + return Flag( + {{"default", value}}, "default", + [value](const openfeature::Flag&, + const openfeature::EvaluationContext&) { return value; }, + openfeature::FlagMetadata{}); +} + +std::shared_ptr CreateStableProvider() { + std::unordered_map flags; + + // Set up the static flags expected by the basic evaluation tests + flags["boolean-flag"] = CreateStaticFlag(true); + flags["string-flag"] = CreateStaticFlag("hi"); + flags["integer-flag"] = CreateStaticFlag(10); + flags["float-flag"] = CreateStaticFlag(0.5); + + // Object flag setup + std::map obj_map; + obj_map["showImages"] = openfeature::Value(true); + obj_map["title"] = openfeature::Value("Check out these pics!"); + obj_map["imagesPerPage"] = openfeature::Value(100); + flags["object-flag"] = + CreateStaticFlag(openfeature::Value(obj_map)); + + // Context-aware flag setup + auto context_evaluator = [](const openfeature::Flag&, + const openfeature::EvaluationContext& ctx) + -> absl::StatusOr { + const std::any* customer = ctx.GetValue("customer"); + if (customer && std::any_cast(*customer) == "false") { + return "INTERNAL"; + } + return "EXTERNAL"; + }; + + flags["context-aware"] = openfeature::Flag( + {{"internal", "INTERNAL"}, {"external", "EXTERNAL"}}, "external", + context_evaluator, openfeature::FlagMetadata{}); + + return std::make_shared(std::move(flags)); +} + +std::shared_ptr CreateMockErrorProvider() { + std::shared_ptr mock = std::make_shared(); + EXPECT_CALL(*mock, GetMetadata()) + .WillRepeatedly(Return(openfeature::Metadata{"MockFeatureProvider"})); + EXPECT_CALL(*mock, Shutdown()).WillRepeatedly(Return(absl::OkStatus())); + EXPECT_CALL(*mock, Init(_)) + .WillOnce(Return(absl::UnknownError("Simulated Error"))); + return mock; +} + +std::shared_ptr CreateMockNotReadyProvider() { + std::shared_ptr mock = std::make_shared(); + EXPECT_CALL(*mock, GetMetadata()) + .WillRepeatedly(Return(openfeature::Metadata{"MockFeatureProvider"})); + EXPECT_CALL(*mock, Shutdown()).WillRepeatedly(Return(absl::OkStatus())); + EXPECT_CALL(*mock, Init(_)) + .WillOnce(testing::Invoke([](const openfeature::EvaluationContext&) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + return absl::OkStatus(); + })); + return mock; +} + +GIVEN("^a (.*) provider$") { + REGEX_PARAM(std::string, status_type); + ScenarioScope state; + + if (status_type == "stable" || status_type == "ready") { + state->provider = CreateStableProvider(); + openfeature::OpenFeatureAPI::GetInstance().SetProviderAndWait( + state->provider); + } else if (status_type == "error") { + state->provider = CreateMockErrorProvider(); + openfeature::OpenFeatureAPI::GetInstance().SetProviderAndWait( + state->provider); + } else if (status_type == "not ready") { + state->provider = CreateMockNotReadyProvider(); + openfeature::OpenFeatureAPI::GetInstance().SetProvider(state->provider); + } + + state->client = openfeature::OpenFeatureAPI::GetInstance().GetClient(); +} + +THEN("^the provider status should be \"([^\"]*)\"$") { + REGEX_PARAM(std::string, expected_status_str); + ScenarioScope state; + + openfeature::ProviderStatus actual_status = + state->client->GetProviderStatus(); + openfeature::ProviderStatus expected_status = + openfeature::ProviderStatus::kReady; + + if (expected_status_str == "READY") { + expected_status = openfeature::ProviderStatus::kReady; + } else if (expected_status_str == "NOT_READY") { + expected_status = openfeature::ProviderStatus::kNotReady; + } else if (expected_status_str == "ERROR") { + expected_status = openfeature::ProviderStatus::kError; + } else if (expected_status_str == "FATAL") { + expected_status = openfeature::ProviderStatus::kFatal; + } else if (expected_status_str == "STALE") { + expected_status = openfeature::ProviderStatus::kStale; + } + + EXPECT_EQ(actual_status, expected_status); +} + +WHEN( + "^a boolean flag with key \"([^\"]*)\" is evaluated with default value " + "\"([^\"]*)\"$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(std::string, default_val_str); + ScenarioScope state; + + bool default_val = (default_val_str == "true"); + state->last_evaluation_value = + openfeature::Value(state->client->GetBooleanValue(key, default_val)); +} + +THEN("^the resolved boolean value should be \"([^\"]*)\"$") { + REGEX_PARAM(std::string, expected_str); + ScenarioScope state; + bool expected = (expected_str == "true"); + EXPECT_EQ(state->last_evaluation_value.AsBool().value(), expected); +} + +WHEN( + "^a string flag with key \"([^\"]*)\" is evaluated with default value " + "\"([^\"]*)\"$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(std::string, default_val); + ScenarioScope state; + + state->last_evaluation_value = + openfeature::Value(state->client->GetStringValue(key, default_val)); +} + +THEN("^the resolved string value should be \"([^\"]*)\"$") { + REGEX_PARAM(std::string, expected); + ScenarioScope state; + EXPECT_EQ(state->last_evaluation_value.AsString().value(), expected); +} + +WHEN( + "^an integer flag with key \"([^\"]*)\" is evaluated with default value " + "(\\d+)$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(int64_t, default_val); + ScenarioScope state; + + state->last_evaluation_value = + openfeature::Value(state->client->GetIntegerValue(key, default_val)); +} + +THEN("^the resolved integer value should be (\\d+)$") { + REGEX_PARAM(int64_t, expected); + ScenarioScope state; + EXPECT_EQ(state->last_evaluation_value.AsInt().value(), expected); +} + +WHEN( + "^a float flag with key \"([^\"]*)\" is evaluated with default value " + "([\\d\\.]+)$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(double, default_val); + ScenarioScope state; + + state->last_evaluation_value = + openfeature::Value(state->client->GetDoubleValue(key, default_val)); +} + +THEN("^the resolved float value should be ([\\d\\.]+)$") { + REGEX_PARAM(double, expected); + ScenarioScope state; + EXPECT_DOUBLE_EQ(state->last_evaluation_value.AsDouble().value(), expected); +} + +WHEN( + "^an object flag with key \"([^\"]*)\" is evaluated with a null default " + "value$") { + REGEX_PARAM(std::string, key); + ScenarioScope state; + + state->last_evaluation_value = + state->client->GetObjectValue(key, openfeature::Value()); +} + +THEN( + "^the resolved object value should be contain fields \"([^\"]*)\", " + "\"([^\"]*)\", and \"([^\"]*)\", with values \"([^\"]*)\", \"([^\"]*)\" " + "and (\\d+), respectively$") { + REGEX_PARAM(std::string, f1); + REGEX_PARAM(std::string, f2); + REGEX_PARAM(std::string, f3); + REGEX_PARAM(std::string, v1_str); + REGEX_PARAM(std::string, v2); + REGEX_PARAM(int64_t, v3); + ScenarioScope state; + + const std::map* structure = state->last_evaluation_value.AsStructure(); + ASSERT_NE(structure, nullptr); + + EXPECT_EQ(structure->at(f1).AsBool().value(), (v1_str == "true")); + EXPECT_EQ(structure->at(f2).AsString().value(), v2); + EXPECT_EQ(structure->at(f3).AsInt().value(), v3); +} + +WHEN( + "^context contains keys \"([^\"]*)\", \"([^\"]*)\", \"([^\"]*)\", " + "\"([^\"]*)\" with values \"([^\"]*)\", \"([^\"]*)\", (\\d+), " + "\"([^\"]*)\"$") { + REGEX_PARAM(std::string, k1); + REGEX_PARAM(std::string, k2); + REGEX_PARAM(std::string, k3); + REGEX_PARAM(std::string, k4); + REGEX_PARAM(std::string, v1); + REGEX_PARAM(std::string, v2); + REGEX_PARAM(int64_t, v3); + REGEX_PARAM(std::string, v4); + ScenarioScope state; + + openfeature::EvaluationContext ctx = openfeature::EvaluationContext::Builder() + .WithAttribute(k1, v1) + .WithAttribute(k2, v2) + .WithAttribute(k3, v3) + .WithAttribute(k4, v4) + .build(); + + state->context = std::make_unique(ctx); +} + +WHEN( + "^a flag with key \"([^\"]*)\" is evaluated with default value " + "\"([^\"]*)\"$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(std::string, default_val); + ScenarioScope state; + + // Evaluate using the context built in the previous step + if (state->context) { + state->last_evaluation_value = openfeature::Value( + state->client->GetStringValue(key, default_val, *state->context)); + } else { + state->last_evaluation_value = + openfeature::Value(state->client->GetStringValue(key, default_val)); + } +} + +THEN("^the resolved string response should be \"([^\"]*)\"$") { + REGEX_PARAM(std::string, expected); + ScenarioScope state; + + EXPECT_EQ(state->last_evaluation_value.AsString().value(), expected); +} + +THEN("^the resolved flag value is \"([^\"]*)\" when the context is empty$") { + REGEX_PARAM(std::string, expected); + ScenarioScope state; + + // Evaluate context-aware flag with an empty context + openfeature::EvaluationContext empty_ctx = + openfeature::EvaluationContext::Builder().build(); + std::string actual = + state->client->GetStringValue("context-aware", "EXTERNAL", empty_ctx); + + EXPECT_EQ(actual, expected); +} From 7a760e1f94000547c90dfe2b86dd04f8b1a8e05d Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Mon, 23 Feb 2026 13:38:55 +0000 Subject: [PATCH 08/13] e2e tests for MVP. Signed-off-by: NeaguGeorgiana23 --- test/BUILD | 6 +- test/e2e/BUILD | 31 +-- test/e2e/context_storing_provider.cpp | 77 ++++++- test/e2e/context_storing_provider.h | 22 +- test/e2e/flag.h | 36 ---- test/e2e/flag_test.h | 16 ++ test/e2e/state.h | 19 +- test/e2e/steps/BUILD | 11 +- test/e2e/steps/minimal_steps.cpp | 289 +++++++++++++++++++++++++- 9 files changed, 419 insertions(+), 88 deletions(-) delete mode 100644 test/e2e/flag.h create mode 100644 test/e2e/flag_test.h diff --git a/test/BUILD b/test/BUILD index 72c9ef8..aff340c 100644 --- a/test/BUILD +++ b/test/BUILD @@ -1,10 +1,12 @@ load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +package( + default_visibility = ["//visibility:public"], +) + cc_library( name = "mock_feature_provider", - testonly = True, hdrs = ["mocks/mock_feature_provider.h"], - visibility = ["//test:__pkg__"], deps = [ "//openfeature:provider", "@googletest//:gtest", diff --git a/test/e2e/BUILD b/test/e2e/BUILD index fedcd09..9a3ef3b 100644 --- a/test/e2e/BUILD +++ b/test/e2e/BUILD @@ -16,31 +16,23 @@ cc_library( include_prefix = "test/e2e", deps = [ "//openfeature:evaluation_context", - "//openfeature:provider", - "//openfeature:resolution_details", - "//openfeature:metadata", "//openfeature:flag_metadata", + "//openfeature:metadata", + "//openfeature:provider", "//openfeature:reason", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", + "//openfeature:value", + "//openfeature:resolution_details", + "@cucumber-cpp//:cucumber-cpp", + "@googletest//:gtest", ], ) cc_library( - name = "flag", + name = "flag_test", hdrs = [ - "flag.h", + "flag_test.h", ], include_prefix = "test/e2e", - deps = [ - "//openfeature:client_api", - "//openfeature:evaluation_context", - "//openfeature:provider", - "//openfeature:resolution_details", - "//openfeature:flag_metadata", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", - ], ) cc_library( @@ -50,13 +42,12 @@ cc_library( ], include_prefix = "test/e2e", deps = [ - "//openfeature:client_api", + "//openfeature:client", "//openfeature:evaluation_context", "//openfeature:provider", "//openfeature:resolution_details", - ":flag", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", + "//openfeature:value", + ":flag_test", ], ) diff --git a/test/e2e/context_storing_provider.cpp b/test/e2e/context_storing_provider.cpp index ca54257..4f2adef 100644 --- a/test/e2e/context_storing_provider.cpp +++ b/test/e2e/context_storing_provider.cpp @@ -1,7 +1,14 @@ #include "test/e2e/context_storing_provider.h" +#include + +#include + #include "openfeature/flag_metadata.h" #include "openfeature/reason.h" +#include "openfeature/value.h" + +using cucumber::ScenarioScope; namespace openfeature_e2e { @@ -13,15 +20,77 @@ std::unique_ptr ContextStoringProvider::GetBooleanEvaluation( std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) { - // Store a copy of the evaluation context. - // We need to copy because `ctx` is passed by const reference. - this->last_evaluation_context_ = ctx; + last_ctx = ctx; return std::make_unique( default_value, // The default value openfeature::Reason::kDefault, // Reason for resolution "default-variant", // A generic variant identifier - openfeature::FlagMetadata(), // Empty metadata + openfeature::FlagMetadata{}, // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + +std::unique_ptr +ContextStoringProvider::GetStringEvaluation( + std::string_view key, std::string_view default_value, + const openfeature::EvaluationContext& ctx) { + last_ctx = ctx; + std::string default_str(default_value); + return std::make_unique( + default_str, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata{}, // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + +std::unique_ptr +ContextStoringProvider::GetIntegerEvaluation( + std::string_view key, int64_t default_value, + const openfeature::EvaluationContext& ctx) { + last_ctx = ctx; + + return std::make_unique( + default_value, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata{}, // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + +std::unique_ptr +ContextStoringProvider::GetDoubleEvaluation( + std::string_view key, double default_value, + const openfeature::EvaluationContext& ctx) { + last_ctx = ctx; + + return std::make_unique( + default_value, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata{}, // Empty metadata + std::nullopt, // No error code + "" // Empty error message + ); +} + +std::unique_ptr +ContextStoringProvider::GetObjectEvaluation( + std::string_view key, const openfeature::Value default_value, + const openfeature::EvaluationContext& ctx) { + last_ctx = ctx; + + return std::make_unique( + default_value, // The default value + openfeature::Reason::kDefault, // Reason for resolution + "default-variant", // A generic variant identifier + openfeature::FlagMetadata{}, // Empty metadata std::nullopt, // No error code "" // Empty error message ); diff --git a/test/e2e/context_storing_provider.h b/test/e2e/context_storing_provider.h index 7926f6c..4422f41 100644 --- a/test/e2e/context_storing_provider.h +++ b/test/e2e/context_storing_provider.h @@ -3,6 +3,7 @@ #include #include +#include #include "openfeature/evaluation_context.h" #include "openfeature/metadata.h" @@ -14,6 +15,9 @@ namespace openfeature_e2e { // A simple provider that stores the last evaluation context it received. class ContextStoringProvider : public openfeature::FeatureProvider { public: + mutable openfeature::EvaluationContext last_ctx = + openfeature::EvaluationContext::Builder().build(); + ~ContextStoringProvider() override = default; openfeature::Metadata GetMetadata() const override; @@ -22,9 +26,21 @@ class ContextStoringProvider : public openfeature::FeatureProvider { std::string_view key, bool default_value, const openfeature::EvaluationContext& ctx) override; - private: - openfeature::EvaluationContext last_evaluation_context_ = - openfeature::EvaluationContext::Builder().build(); + std::unique_ptr GetStringEvaluation( + std::string_view key, std::string_view default_value, + const openfeature::EvaluationContext& ctx) override; + + std::unique_ptr GetIntegerEvaluation( + std::string_view key, int64_t default_value, + const openfeature::EvaluationContext& ctx) override; + + std::unique_ptr GetDoubleEvaluation( + std::string_view key, double default_value, + const openfeature::EvaluationContext& ctx) override; + + std::unique_ptr GetObjectEvaluation( + std::string_view key, openfeature::Value default_value, + const openfeature::EvaluationContext& ctx) override; }; } // namespace openfeature_e2e diff --git a/test/e2e/flag.h b/test/e2e/flag.h deleted file mode 100644 index e1543ca..0000000 --- a/test/e2e/flag.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ -#define CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ - -#include -#include -#include - -#include "openfeature/client.h" -#include "openfeature/evaluation_context.h" -#include "openfeature/flag_metadata.h" -#include "openfeature/provider.h" -#include "openfeature/resolution_details.h" // For ResolutionDetails - -namespace openfeature_e2e { - -class Flag { - public: - const std::string name_; - const std::any default_value_; - const std::string type_; - const openfeature::FlagMetadata flag_metadata_; - - Flag(std::string type, std::string name, std::any default_value) - : Flag(std::move(type), std::move(name), std::move(default_value), - openfeature::FlagMetadata{}) {} - - Flag(std::string type, std::string name, std::any default_value, - openfeature::FlagMetadata flag_metadata) - : name_(std::move(name)), - default_value_(std::move(default_value)), - type_(std::move(type)), - flag_metadata_(std::move(flag_metadata)) {} -}; - -} // namespace openfeature_e2e -#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ \ No newline at end of file diff --git a/test/e2e/flag_test.h b/test/e2e/flag_test.h new file mode 100644 index 0000000..39c90b0 --- /dev/null +++ b/test/e2e/flag_test.h @@ -0,0 +1,16 @@ +#ifndef CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ +#define CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ + +#include +#include + +namespace openfeature_e2e { + +struct FlagTest { + std::string type; + std::string name; + std::any default_value; +}; + +} // namespace openfeature_e2e +#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ \ No newline at end of file diff --git a/test/e2e/state.h b/test/e2e/state.h index a45ce31..7c42200 100644 --- a/test/e2e/state.h +++ b/test/e2e/state.h @@ -9,22 +9,23 @@ #include "openfeature/client.h" #include "openfeature/evaluation_context.h" #include "openfeature/provider.h" -#include "openfeature/resolution_details.h" // For ResolutionDetails -#include "test/e2e/flag.h" +#include "openfeature/resolution_details.h" +#include "openfeature/value.h" +#include "test/e2e/flag_test.h" namespace openfeature_e2e { -struct ScenarioState { +struct State { std::shared_ptr client; - Flag flag; - openfeature::EvaluationContext ctx = - openfeature::EvaluationContext::Builder().build(); + std::shared_ptr provider; + FlagTest flag; + std::unique_ptr context; // FlagEvaluationDetails eval // MockHook hook - std::shared_ptr provider; - openfeature::EvaluationContext invocation_ctx = - openfeature::EvaluationContext::Builder().build(); + std::unique_ptr invocation_context; std::vector levels; + + openfeature::Value last_evaluation_value; }; } // namespace openfeature_e2e diff --git a/test/e2e/steps/BUILD b/test/e2e/steps/BUILD index fb6e83b..302e522 100644 --- a/test/e2e/steps/BUILD +++ b/test/e2e/steps/BUILD @@ -6,14 +6,13 @@ cc_gherkin_steps( name = "gherkin_step_definitions", srcs = ["minimal_steps.cpp"], deps = [ - "//openfeature:client_api", - "//openfeature:openfeature_api", "//openfeature:evaluation_context", - "//openfeature:metadata", - "//openfeature:resolution_details", - "//test/e2e:context_storing_provider", - "//test/e2e:flag", + "//openfeature:openfeature_api", + "//openfeature/memory_provider:flag", + "//openfeature/memory_provider:in_memory_provider", + "//test/e2e:context_storing_provider", "//test/e2e:state", + "//test:mock_feature_provider", "@cucumber-cpp//:cucumber-cpp", "@googletest//:gtest", ], diff --git a/test/e2e/steps/minimal_steps.cpp b/test/e2e/steps/minimal_steps.cpp index 2ea2292..3fb1389 100644 --- a/test/e2e/steps/minimal_steps.cpp +++ b/test/e2e/steps/minimal_steps.cpp @@ -1,19 +1,292 @@ +#include #include #include -#include +#include #include #include -#include "openfeature/client.h" #include "openfeature/evaluation_context.h" +#include "openfeature/memory_provider/flag.h" +#include "openfeature/memory_provider/in_memory_provider.h" #include "openfeature/openfeature_api.h" -#include "openfeature/provider.h" -#include "openfeature/provider_repository.h" -#include "openfeature/resolution_details.h" #include "test/e2e/context_storing_provider.h" #include "test/e2e/state.h" +#include "test/mocks/mock_feature_provider.h" -GIVEN("^a stable provider with retrievable context is registered$") { - std::cout << "Registering provider..."< +openfeature::Flag CreateStaticFlag(T value) { + return openfeature::Flag( + {{"default", value}}, "default", + [value](const openfeature::Flag&, + const openfeature::EvaluationContext&) { return value; }, + openfeature::FlagMetadata{}); +} + +std::shared_ptr CreateStableProvider() { + std::unordered_map flags; + + // Set up the static flags expected by the basic evaluation tests + flags["boolean-flag"] = CreateStaticFlag(true); + flags["string-flag"] = CreateStaticFlag("hi"); + flags["integer-flag"] = CreateStaticFlag(10); + flags["float-flag"] = CreateStaticFlag(0.5); + + // Object flag setup + std::map obj_map; + obj_map["showImages"] = openfeature::Value(true); + obj_map["title"] = openfeature::Value("Check out these pics!"); + obj_map["imagesPerPage"] = openfeature::Value(100); + flags["object-flag"] = + CreateStaticFlag(openfeature::Value(obj_map)); + + // Context-aware flag setup + auto context_evaluator = [](const openfeature::Flag&, + const openfeature::EvaluationContext& ctx) + -> absl::StatusOr { + const std::any* customer = ctx.GetValue("customer"); + if (customer && std::any_cast(*customer) == "false") { + return "INTERNAL"; + } + return "EXTERNAL"; + }; + + flags["context-aware"] = openfeature::Flag( + {{"internal", "INTERNAL"}, {"external", "EXTERNAL"}}, "external", + context_evaluator, openfeature::FlagMetadata{}); + + return std::make_shared(std::move(flags)); +} + +std::shared_ptr CreateMockErrorProvider() { + std::shared_ptr mock = std::make_shared(); + EXPECT_CALL(*mock, GetMetadata()) + .WillRepeatedly(Return(openfeature::Metadata{"MockFeatureProvider"})); + EXPECT_CALL(*mock, Shutdown()).WillRepeatedly(Return(absl::OkStatus())); + EXPECT_CALL(*mock, Init(_)) + .WillOnce(Return(absl::UnknownError("Simulated Error"))); + return mock; +} + +std::shared_ptr CreateMockNotReadyProvider() { + std::shared_ptr mock = std::make_shared(); + EXPECT_CALL(*mock, GetMetadata()) + .WillRepeatedly(Return(openfeature::Metadata{"MockFeatureProvider"})); + EXPECT_CALL(*mock, Shutdown()).WillRepeatedly(Return(absl::OkStatus())); + EXPECT_CALL(*mock, Init(_)) + .WillOnce(testing::Invoke([](const openfeature::EvaluationContext&) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + return absl::OkStatus(); + })); + return mock; +} + +GIVEN("^a (.*) provider$") { + REGEX_PARAM(std::string, status_type); + ScenarioScope state; + + if (status_type == "stable" || status_type == "ready") { + state->provider = CreateStableProvider(); + openfeature::OpenFeatureAPI::GetInstance().SetProviderAndWait( + state->provider); + } else if (status_type == "error") { + state->provider = CreateMockErrorProvider(); + openfeature::OpenFeatureAPI::GetInstance().SetProviderAndWait( + state->provider); + } else if (status_type == "not ready") { + state->provider = CreateMockNotReadyProvider(); + openfeature::OpenFeatureAPI::GetInstance().SetProvider(state->provider); + } + + state->client = openfeature::OpenFeatureAPI::GetInstance().GetClient(); +} + +THEN("^the provider status should be \"([^\"]*)\"$") { + REGEX_PARAM(std::string, expected_status_str); + ScenarioScope state; + + openfeature::ProviderStatus actual_status = + state->client->GetProviderStatus(); + openfeature::ProviderStatus expected_status = + openfeature::ProviderStatus::kReady; + + if (expected_status_str == "READY") { + expected_status = openfeature::ProviderStatus::kReady; + } else if (expected_status_str == "NOT_READY") { + expected_status = openfeature::ProviderStatus::kNotReady; + } else if (expected_status_str == "ERROR") { + expected_status = openfeature::ProviderStatus::kError; + } else if (expected_status_str == "FATAL") { + expected_status = openfeature::ProviderStatus::kFatal; + } else if (expected_status_str == "STALE") { + expected_status = openfeature::ProviderStatus::kStale; + } + + EXPECT_EQ(actual_status, expected_status); +} + +WHEN( + "^a boolean flag with key \"([^\"]*)\" is evaluated with default value " + "\"([^\"]*)\"$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(std::string, default_val_str); + ScenarioScope state; + + bool default_val = (default_val_str == "true"); + state->last_evaluation_value = + openfeature::Value(state->client->GetBooleanValue(key, default_val)); +} + +THEN("^the resolved boolean value should be \"([^\"]*)\"$") { + REGEX_PARAM(std::string, expected_str); + ScenarioScope state; + bool expected = (expected_str == "true"); + EXPECT_EQ(state->last_evaluation_value.AsBool().value(), expected); +} + +WHEN( + "^a string flag with key \"([^\"]*)\" is evaluated with default value " + "\"([^\"]*)\"$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(std::string, default_val); + ScenarioScope state; + + state->last_evaluation_value = + openfeature::Value(state->client->GetStringValue(key, default_val)); +} + +THEN("^the resolved string value should be \"([^\"]*)\"$") { + REGEX_PARAM(std::string, expected); + ScenarioScope state; + EXPECT_EQ(state->last_evaluation_value.AsString().value(), expected); +} + +WHEN( + "^an integer flag with key \"([^\"]*)\" is evaluated with default value " + "(\\d+)$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(int64_t, default_val); + ScenarioScope state; + + state->last_evaluation_value = + openfeature::Value(state->client->GetIntegerValue(key, default_val)); +} + +THEN("^the resolved integer value should be (\\d+)$") { + REGEX_PARAM(int64_t, expected); + ScenarioScope state; + EXPECT_EQ(state->last_evaluation_value.AsInt().value(), expected); +} + +WHEN( + "^a float flag with key \"([^\"]*)\" is evaluated with default value " + "([\\d\\.]+)$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(double, default_val); + ScenarioScope state; + + state->last_evaluation_value = + openfeature::Value(state->client->GetDoubleValue(key, default_val)); +} + +THEN("^the resolved float value should be ([\\d\\.]+)$") { + REGEX_PARAM(double, expected); + ScenarioScope state; + EXPECT_DOUBLE_EQ(state->last_evaluation_value.AsDouble().value(), expected); +} + +WHEN( + "^an object flag with key \"([^\"]*)\" is evaluated with a null default " + "value$") { + REGEX_PARAM(std::string, key); + ScenarioScope state; + + state->last_evaluation_value = + state->client->GetObjectValue(key, openfeature::Value()); +} + +THEN( + "^the resolved object value should be contain fields \"([^\"]*)\", " + "\"([^\"]*)\", and \"([^\"]*)\", with values \"([^\"]*)\", \"([^\"]*)\" " + "and (\\d+), respectively$") { + REGEX_PARAM(std::string, f1); + REGEX_PARAM(std::string, f2); + REGEX_PARAM(std::string, f3); + REGEX_PARAM(std::string, v1_str); + REGEX_PARAM(std::string, v2); + REGEX_PARAM(int64_t, v3); + ScenarioScope state; + + const std::map* structure = state->last_evaluation_value.AsStructure(); + ASSERT_NE(structure, nullptr); + + EXPECT_EQ(structure->at(f1).AsBool().value(), (v1_str == "true")); + EXPECT_EQ(structure->at(f2).AsString().value(), v2); + EXPECT_EQ(structure->at(f3).AsInt().value(), v3); +} + +WHEN( + "^context contains keys \"([^\"]*)\", \"([^\"]*)\", \"([^\"]*)\", " + "\"([^\"]*)\" with values \"([^\"]*)\", \"([^\"]*)\", (\\d+), " + "\"([^\"]*)\"$") { + REGEX_PARAM(std::string, k1); + REGEX_PARAM(std::string, k2); + REGEX_PARAM(std::string, k3); + REGEX_PARAM(std::string, k4); + REGEX_PARAM(std::string, v1); + REGEX_PARAM(std::string, v2); + REGEX_PARAM(int64_t, v3); + REGEX_PARAM(std::string, v4); + ScenarioScope state; + + openfeature::EvaluationContext ctx = openfeature::EvaluationContext::Builder() + .WithAttribute(k1, v1) + .WithAttribute(k2, v2) + .WithAttribute(k3, v3) + .WithAttribute(k4, v4) + .build(); + + state->context = std::make_unique(ctx); +} + +WHEN( + "^a flag with key \"([^\"]*)\" is evaluated with default value " + "\"([^\"]*)\"$") { + REGEX_PARAM(std::string, key); + REGEX_PARAM(std::string, default_val); + ScenarioScope state; + + // Evaluate using the context built in the previous step + if (state->context) { + state->last_evaluation_value = openfeature::Value( + state->client->GetStringValue(key, default_val, *state->context)); + } else { + state->last_evaluation_value = + openfeature::Value(state->client->GetStringValue(key, default_val)); + } +} + +THEN("^the resolved string response should be \"([^\"]*)\"$") { + REGEX_PARAM(std::string, expected); + ScenarioScope state; + + EXPECT_EQ(state->last_evaluation_value.AsString().value(), expected); +} + +THEN("^the resolved flag value is \"([^\"]*)\" when the context is empty$") { + REGEX_PARAM(std::string, expected); + ScenarioScope state; + + // Evaluate context-aware flag with an empty context + openfeature::EvaluationContext empty_ctx = + openfeature::EvaluationContext::Builder().build(); + std::string actual = + state->client->GetStringValue("context-aware", "EXTERNAL", empty_ctx); + + EXPECT_EQ(actual, expected); +} From cefcf9aeb05889656ec3913c21fbe9b2d4614216 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Tue, 24 Feb 2026 12:06:00 +0000 Subject: [PATCH 09/13] add recursive submodules checkout for bazel build Signed-off-by: NeaguGeorgiana23 --- .github/workflows/ci-format-test-lint.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-format-test-lint.yml b/.github/workflows/ci-format-test-lint.yml index 9d44f1e..a46ac98 100644 --- a/.github/workflows/ci-format-test-lint.yml +++ b/.github/workflows/ci-format-test-lint.yml @@ -11,6 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + submodules: 'recursive' - uses: bazel-contrib/setup-bazel@0.18.0 with: From 52c381428d5e4c5ff2ca529a4ee0e4a0bd389ee4 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 25 Feb 2026 10:37:44 +0000 Subject: [PATCH 10/13] resolve comments Signed-off-by: NeaguGeorgiana23 --- test/e2e/context_storing_provider.cpp | 60 +++++++++++++-------------- test/e2e/state.h | 3 +- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/test/e2e/context_storing_provider.cpp b/test/e2e/context_storing_provider.cpp index 9295fb2..a28c948 100644 --- a/test/e2e/context_storing_provider.cpp +++ b/test/e2e/context_storing_provider.cpp @@ -24,12 +24,12 @@ ContextStoringProvider::GetBooleanEvaluation( last_ctx = ctx; return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message + default_value, + openfeature::Reason::kDefault, + "default-variant", + openfeature::FlagMetadata{}, + std::nullopt, + "" ); } @@ -40,12 +40,12 @@ ContextStoringProvider::GetStringEvaluation( last_ctx = ctx; std::string default_str(default_value); return std::make_unique( - default_str, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message + default_str, + openfeature::Reason::kDefault, + "default-variant", + openfeature::FlagMetadata{}, + std::nullopt, + "" ); } @@ -56,12 +56,12 @@ ContextStoringProvider::GetIntegerEvaluation( last_ctx = ctx; return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message + default_value, + openfeature::Reason::kDefault, + "default-variant", + openfeature::FlagMetadata{}, + std::nullopt, + "" ); } @@ -72,12 +72,12 @@ ContextStoringProvider::GetDoubleEvaluation( last_ctx = ctx; return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message + default_value, + openfeature::Reason::kDefault, + "default-variant", + openfeature::FlagMetadata{}, + std::nullopt, + "" ); } @@ -88,12 +88,12 @@ ContextStoringProvider::GetObjectEvaluation( last_ctx = ctx; return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message + default_value, + openfeature::Reason::kDefault, + "default-variant", + openfeature::FlagMetadata{}, + std::nullopt, + "" ); } diff --git a/test/e2e/state.h b/test/e2e/state.h index 7c42200..907e4ce 100644 --- a/test/e2e/state.h +++ b/test/e2e/state.h @@ -20,13 +20,12 @@ struct State { std::shared_ptr provider; FlagTest flag; std::unique_ptr context; - // FlagEvaluationDetails eval - // MockHook hook std::unique_ptr invocation_context; std::vector levels; openfeature::Value last_evaluation_value; }; +// TODO: Update struct after implementing hooks and flag evaluation details. } // namespace openfeature_e2e #endif // CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ \ No newline at end of file From 5666dbccb5da371ebb66232f1c00fa3cc320ba42 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 25 Feb 2026 10:37:44 +0000 Subject: [PATCH 11/13] resolve comments Signed-off-by: NeaguGeorgiana23 --- test/e2e/context_storing_provider.cpp | 45 ++++++--------------------- test/e2e/flag_test.h | 6 ++-- test/e2e/state.h | 3 +- test/e2e/steps/BUILD | 3 +- 4 files changed, 16 insertions(+), 41 deletions(-) diff --git a/test/e2e/context_storing_provider.cpp b/test/e2e/context_storing_provider.cpp index 9295fb2..c07e110 100644 --- a/test/e2e/context_storing_provider.cpp +++ b/test/e2e/context_storing_provider.cpp @@ -24,13 +24,8 @@ ContextStoringProvider::GetBooleanEvaluation( last_ctx = ctx; return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message - ); + default_value, openfeature::Reason::kDefault, "default-variant", + openfeature::FlagMetadata{}, std::nullopt, ""); } std::unique_ptr @@ -40,13 +35,8 @@ ContextStoringProvider::GetStringEvaluation( last_ctx = ctx; std::string default_str(default_value); return std::make_unique( - default_str, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message - ); + default_str, openfeature::Reason::kDefault, "default-variant", + openfeature::FlagMetadata{}, std::nullopt, ""); } std::unique_ptr @@ -56,13 +46,8 @@ ContextStoringProvider::GetIntegerEvaluation( last_ctx = ctx; return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message - ); + default_value, openfeature::Reason::kDefault, "default-variant", + openfeature::FlagMetadata{}, std::nullopt, ""); } std::unique_ptr @@ -72,13 +57,8 @@ ContextStoringProvider::GetDoubleEvaluation( last_ctx = ctx; return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message - ); + default_value, openfeature::Reason::kDefault, "default-variant", + openfeature::FlagMetadata{}, std::nullopt, ""); } std::unique_ptr @@ -88,13 +68,8 @@ ContextStoringProvider::GetObjectEvaluation( last_ctx = ctx; return std::make_unique( - default_value, // The default value - openfeature::Reason::kDefault, // Reason for resolution - "default-variant", // A generic variant identifier - openfeature::FlagMetadata{}, // Empty metadata - std::nullopt, // No error code - "" // Empty error message - ); + default_value, openfeature::Reason::kDefault, "default-variant", + openfeature::FlagMetadata{}, std::nullopt, ""); } } // namespace openfeature_e2e diff --git a/test/e2e/flag_test.h b/test/e2e/flag_test.h index 39c90b0..5f64956 100644 --- a/test/e2e/flag_test.h +++ b/test/e2e/flag_test.h @@ -1,5 +1,5 @@ -#ifndef CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ -#define CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ +#ifndef CPP_SDK_INCLUDE_TEST_E2E_FLAG_TEST_H_ +#define CPP_SDK_INCLUDE_TEST_E2E_FLAG_TEST_H_ #include #include @@ -13,4 +13,4 @@ struct FlagTest { }; } // namespace openfeature_e2e -#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_H_ \ No newline at end of file +#endif // CPP_SDK_INCLUDE_TEST_E2E_FLAG_TEST_H_ \ No newline at end of file diff --git a/test/e2e/state.h b/test/e2e/state.h index 7c42200..907e4ce 100644 --- a/test/e2e/state.h +++ b/test/e2e/state.h @@ -20,13 +20,12 @@ struct State { std::shared_ptr provider; FlagTest flag; std::unique_ptr context; - // FlagEvaluationDetails eval - // MockHook hook std::unique_ptr invocation_context; std::vector levels; openfeature::Value last_evaluation_value; }; +// TODO: Update struct after implementing hooks and flag evaluation details. } // namespace openfeature_e2e #endif // CPP_SDK_INCLUDE_TEST_E2E_STATE_H_ \ No newline at end of file diff --git a/test/e2e/steps/BUILD b/test/e2e/steps/BUILD index 302e522..fcca01c 100644 --- a/test/e2e/steps/BUILD +++ b/test/e2e/steps/BUILD @@ -14,7 +14,8 @@ cc_gherkin_steps( "//test/e2e:state", "//test:mock_feature_provider", "@cucumber-cpp//:cucumber-cpp", - "@googletest//:gtest", + "@googletest//:gtest", + "@googletest//:gmock", ], visibility = ["//test/e2e:__subpackages__"], ) \ No newline at end of file From 839ea0083ea0593328924c8415fd88cb7fda863c Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Thu, 26 Feb 2026 08:12:25 +0000 Subject: [PATCH 12/13] only run gherkin scenarios relevant for the MVP. Signed-off-by: NeaguGeorgiana23 --- BUILD | 2 +- test/e2e/steps/minimal_steps.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/BUILD b/BUILD index fccff99..6066694 100644 --- a/BUILD +++ b/BUILD @@ -4,5 +4,5 @@ package(default_visibility = ["//visibility:public"]) gherkin_library( name = "openfeature_gherkin_spec_features", - srcs = glob(["spec/specification/assets/gherkin/*.feature"]), + srcs = glob(["spec/specification/assets/gherkin/evaluation.feature"]), ) \ No newline at end of file diff --git a/test/e2e/steps/minimal_steps.cpp b/test/e2e/steps/minimal_steps.cpp index fb83c7e..1194f72 100644 --- a/test/e2e/steps/minimal_steps.cpp +++ b/test/e2e/steps/minimal_steps.cpp @@ -18,11 +18,11 @@ #include "test/e2e/state.h" #include "test/mocks/mock_feature_provider.h" -using cucumber::ScenarioScope; +using ::cucumber::ScenarioScope; using ::testing::_; using ::testing::Return; -// Helper to create simple static flags for the InMemoryProvider +// Helper to create simple static flags for the InMemoryProvider. template openfeature::Flag CreateStaticFlag(T value) { return openfeature::Flag( @@ -35,13 +35,13 @@ openfeature::Flag CreateStaticFlag(T value) { std::shared_ptr CreateStableProvider() { std::unordered_map flags; - // Set up the static flags expected by the basic evaluation tests + // Set up the static flags expected by the basic evaluation tests. flags["boolean-flag"] = CreateStaticFlag(true); flags["string-flag"] = CreateStaticFlag("hi"); flags["integer-flag"] = CreateStaticFlag(10); flags["float-flag"] = CreateStaticFlag(0.5); - // Object flag setup + // Object flag setup. std::map obj_map; obj_map["showImages"] = openfeature::Value(true); obj_map["title"] = openfeature::Value("Check out these pics!"); @@ -49,7 +49,7 @@ std::shared_ptr CreateStableProvider() { flags["object-flag"] = CreateStaticFlag(openfeature::Value(obj_map)); - // Context-aware flag setup + // Context-aware flag setup. auto context_evaluator = [](const openfeature::Flag&, const openfeature::EvaluationContext& ctx) -> absl::StatusOr { @@ -268,7 +268,7 @@ WHEN( REGEX_PARAM(std::string, default_val); ScenarioScope state; - // Evaluate using the context built in the previous step + // Evaluate using the context built in the previous step. if (state->context) { state->last_evaluation_value = openfeature::Value( state->client->GetStringValue(key, default_val, *state->context)); @@ -289,7 +289,7 @@ THEN("^the resolved flag value is \"([^\"]*)\" when the context is empty$") { REGEX_PARAM(std::string, expected); ScenarioScope state; - // Evaluate context-aware flag with an empty context + // Evaluate context-aware flag with an empty context. openfeature::EvaluationContext empty_ctx = openfeature::EvaluationContext::Builder().build(); std::string actual = From 815f867fd5abf0ae6c79ddec14881ae98b670c41 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Thu, 26 Feb 2026 08:12:25 +0000 Subject: [PATCH 13/13] only run gherkin scenarios relevant for the MVP. Signed-off-by: NeaguGeorgiana23 --- BUILD | 2 +- test/e2e/steps/minimal_steps.cpp | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/BUILD b/BUILD index fccff99..6066694 100644 --- a/BUILD +++ b/BUILD @@ -4,5 +4,5 @@ package(default_visibility = ["//visibility:public"]) gherkin_library( name = "openfeature_gherkin_spec_features", - srcs = glob(["spec/specification/assets/gherkin/*.feature"]), + srcs = glob(["spec/specification/assets/gherkin/evaluation.feature"]), ) \ No newline at end of file diff --git a/test/e2e/steps/minimal_steps.cpp b/test/e2e/steps/minimal_steps.cpp index fb83c7e..7ebbc1a 100644 --- a/test/e2e/steps/minimal_steps.cpp +++ b/test/e2e/steps/minimal_steps.cpp @@ -18,11 +18,11 @@ #include "test/e2e/state.h" #include "test/mocks/mock_feature_provider.h" -using cucumber::ScenarioScope; +using ::cucumber::ScenarioScope; using ::testing::_; using ::testing::Return; -// Helper to create simple static flags for the InMemoryProvider +// Helper to create simple static flags for the InMemoryProvider. template openfeature::Flag CreateStaticFlag(T value) { return openfeature::Flag( @@ -35,13 +35,13 @@ openfeature::Flag CreateStaticFlag(T value) { std::shared_ptr CreateStableProvider() { std::unordered_map flags; - // Set up the static flags expected by the basic evaluation tests + // Set up the static flags expected by the basic evaluation tests. flags["boolean-flag"] = CreateStaticFlag(true); flags["string-flag"] = CreateStaticFlag("hi"); flags["integer-flag"] = CreateStaticFlag(10); flags["float-flag"] = CreateStaticFlag(0.5); - // Object flag setup + // Object flag setup. std::map obj_map; obj_map["showImages"] = openfeature::Value(true); obj_map["title"] = openfeature::Value("Check out these pics!"); @@ -49,7 +49,7 @@ std::shared_ptr CreateStableProvider() { flags["object-flag"] = CreateStaticFlag(openfeature::Value(obj_map)); - // Context-aware flag setup + // Context-aware flag setup. auto context_evaluator = [](const openfeature::Flag&, const openfeature::EvaluationContext& ctx) -> absl::StatusOr { @@ -268,7 +268,7 @@ WHEN( REGEX_PARAM(std::string, default_val); ScenarioScope state; - // Evaluate using the context built in the previous step + // Evaluate using the context built in the previous step. if (state->context) { state->last_evaluation_value = openfeature::Value( state->client->GetStringValue(key, default_val, *state->context)); @@ -289,7 +289,7 @@ THEN("^the resolved flag value is \"([^\"]*)\" when the context is empty$") { REGEX_PARAM(std::string, expected); ScenarioScope state; - // Evaluate context-aware flag with an empty context + // Evaluate context-aware flag with an empty context. openfeature::EvaluationContext empty_ctx = openfeature::EvaluationContext::Builder().build(); std::string actual = @@ -297,3 +297,5 @@ THEN("^the resolved flag value is \"([^\"]*)\" when the context is empty$") { EXPECT_EQ(actual, expected); } + +// TODO: Enable more Gherkin scenarios as the SDK functionality expands, e.g. around event hooks, detailed evaluation, etc. \ No newline at end of file