diff --git a/libs/server-sdk/src/data_components/memory_store/memory_store.cpp b/libs/server-sdk/src/data_components/memory_store/memory_store.cpp index 95cac8748..c71acf08a 100644 --- a/libs/server-sdk/src/data_components/memory_store/memory_store.cpp +++ b/libs/server-sdk/src/data_components/memory_store/memory_store.cpp @@ -1,5 +1,7 @@ #include "memory_store.hpp" +#include + namespace launchdarkly::server_side::data_components { std::shared_ptr MemoryStore::GetFlag( @@ -82,4 +84,34 @@ bool MemoryStore::RemoveSegment(std::string const& key) { return segments_.erase(key) == 1; } +void MemoryStore::Apply(data_model::FDv2ChangeSet changeSet) { + std::lock_guard lock{data_mutex_}; + + switch (changeSet.type) { + case data_model::FDv2ChangeSet::Type::kNone: + return; + case data_model::FDv2ChangeSet::Type::kPartial: + break; + case data_model::FDv2ChangeSet::Type::kFull: + initialized_ = true; + flags_.clear(); + segments_.clear(); + break; + default: + detail::unreachable(); + } + + for (auto& change : changeSet.changes) { + if (std::holds_alternative(change.object)) { + flags_[change.key] = std::make_shared( + std::move(std::get(change.object))); + } else if (std::holds_alternative( + change.object)) { + segments_[change.key] = + std::make_shared(std::move( + std::get(change.object))); + } + } +} + } // namespace launchdarkly::server_side::data_components diff --git a/libs/server-sdk/src/data_components/memory_store/memory_store.hpp b/libs/server-sdk/src/data_components/memory_store/memory_store.hpp index 93dfca485..e9a067881 100644 --- a/libs/server-sdk/src/data_components/memory_store/memory_store.hpp +++ b/libs/server-sdk/src/data_components/memory_store/memory_store.hpp @@ -3,6 +3,8 @@ #include "../../data_interfaces/destination/idestination.hpp" #include "../../data_interfaces/store/istore.hpp" +#include + #include #include #include @@ -44,6 +46,8 @@ class MemoryStore final : public data_interfaces::IStore, bool RemoveSegment(std::string const& key); + void Apply(data_model::FDv2ChangeSet changeSet); + MemoryStore() = default; ~MemoryStore() override = default; diff --git a/libs/server-sdk/tests/memory_store_apply_test.cpp b/libs/server-sdk/tests/memory_store_apply_test.cpp new file mode 100644 index 000000000..003285c53 --- /dev/null +++ b/libs/server-sdk/tests/memory_store_apply_test.cpp @@ -0,0 +1,223 @@ +#include + +#include +#include + +using namespace launchdarkly::data_model; +using namespace launchdarkly::server_side::data_components; + +// --------------------------------------------------------------------------- +// kNone tests +// --------------------------------------------------------------------------- + +TEST(MemoryStoreApplyTest, ApplyNone_IsNoOp) { + MemoryStore store; + Flag flag_a; + flag_a.version = 1; + flag_a.key = "flagA"; + + Segment seg_a; + seg_a.version = 1; + seg_a.key = "segA"; + + store.Init(SDKDataSet{ + std::unordered_map{ + {"flagA", FlagDescriptor(flag_a)}}, + std::unordered_map{ + {"segA", SegmentDescriptor(seg_a)}}, + }); + + store.Apply(FDv2ChangeSet{FDv2ChangeSet::Type::kNone, {}, Selector{}}); + + auto fetched_flag = store.GetFlag("flagA"); + ASSERT_TRUE(fetched_flag); + EXPECT_EQ(1u, fetched_flag->version); + auto fetched_seg = store.GetSegment("segA"); + ASSERT_TRUE(fetched_seg); + EXPECT_EQ(1u, fetched_seg->version); +} + +TEST(MemoryStoreApplyTest, ApplyNone_DoesNotInitialize) { + MemoryStore store; + store.Apply(FDv2ChangeSet{FDv2ChangeSet::Type::kNone, {}, Selector{}}); + EXPECT_FALSE(store.Initialized()); +} + +// --------------------------------------------------------------------------- +// kFull tests +// --------------------------------------------------------------------------- + +TEST(MemoryStoreApplyTest, ApplyFull_SetsInitialized) { + MemoryStore store; + ASSERT_FALSE(store.Initialized()); + store.Apply(FDv2ChangeSet{FDv2ChangeSet::Type::kFull, {}, Selector{}}); + EXPECT_TRUE(store.Initialized()); +} + +TEST(MemoryStoreApplyTest, ApplyFull_StoresItems) { + MemoryStore store; + Flag flag_a; + flag_a.version = 1; + flag_a.key = "flagA"; + + Segment seg_a; + seg_a.version = 1; + seg_a.key = "segA"; + + store.Apply(FDv2ChangeSet{ + FDv2ChangeSet::Type::kFull, + std::vector{{"flagA", FlagDescriptor(flag_a)}, + {"segA", SegmentDescriptor(seg_a)}}, + Selector{}, + }); + + auto fetched_flag = store.GetFlag("flagA"); + ASSERT_TRUE(fetched_flag); + EXPECT_TRUE(fetched_flag->item.has_value()); + EXPECT_EQ("flagA", fetched_flag->item->key); + EXPECT_EQ(1u, fetched_flag->version); + + auto fetched_seg = store.GetSegment("segA"); + ASSERT_TRUE(fetched_seg); + EXPECT_TRUE(fetched_seg->item.has_value()); + EXPECT_EQ("segA", fetched_seg->item->key); + EXPECT_EQ(1u, fetched_seg->version); +} + +TEST(MemoryStoreApplyTest, ApplyFull_ClearsExistingItems) { + MemoryStore store; + Flag flag_a; + flag_a.version = 1; + flag_a.key = "flagA"; + + Flag flag_b; + flag_b.version = 1; + flag_b.key = "flagB"; + + Segment seg_a; + seg_a.version = 1; + seg_a.key = "segA"; + + store.Init(SDKDataSet{ + std::unordered_map{ + {"flagA", FlagDescriptor(flag_a)}, + {"flagB", FlagDescriptor(flag_b)}}, + std::unordered_map{ + {"segA", SegmentDescriptor(seg_a)}}, + }); + + Flag flag_c; + flag_c.version = 1; + flag_c.key = "flagC"; + + Segment seg_b; + seg_b.version = 1; + seg_b.key = "segB"; + + store.Apply(FDv2ChangeSet{ + FDv2ChangeSet::Type::kFull, + std::vector{{"flagC", FlagDescriptor(flag_c)}, + {"segB", SegmentDescriptor(seg_b)}}, + Selector{}, + }); + + EXPECT_FALSE(store.GetFlag("flagA")); + EXPECT_FALSE(store.GetFlag("flagB")); + ASSERT_TRUE(store.GetFlag("flagC")); + EXPECT_FALSE(store.GetSegment("segA")); + ASSERT_TRUE(store.GetSegment("segB")); +} + +// --------------------------------------------------------------------------- +// kPartial tests +// --------------------------------------------------------------------------- + +TEST(MemoryStoreApplyTest, ApplyPartial_AppliesItems) { + MemoryStore store; + Flag flag_a; + flag_a.version = 5; + flag_a.key = "flagA"; + + Segment seg_a; + seg_a.version = 5; + seg_a.key = "segA"; + + store.Init(SDKDataSet{ + std::unordered_map{ + {"flagA", FlagDescriptor(flag_a)}}, + std::unordered_map{ + {"segA", SegmentDescriptor(seg_a)}}, + }); + + Flag flag_a_new; + flag_a_new.version = 6; + flag_a_new.key = "flagA"; + + Segment seg_a_new; + seg_a_new.version = 6; + seg_a_new.key = "segA"; + + store.Apply(FDv2ChangeSet{ + FDv2ChangeSet::Type::kPartial, + std::vector{{"flagA", FlagDescriptor(flag_a_new)}, + {"segA", SegmentDescriptor(seg_a_new)}}, + Selector{}, + }); + + ASSERT_TRUE(store.GetFlag("flagA")); + EXPECT_EQ(6u, store.GetFlag("flagA")->version); + ASSERT_TRUE(store.GetSegment("segA")); + EXPECT_EQ(6u, store.GetSegment("segA")->version); +} + +TEST(MemoryStoreApplyTest, ApplyPartial_PreservesUnchangedItems) { + MemoryStore store; + Flag flag_a; + flag_a.version = 1; + flag_a.key = "flagA"; + + Flag flag_b; + flag_b.version = 1; + flag_b.key = "flagB"; + + Segment seg_a; + seg_a.version = 1; + seg_a.key = "segA"; + + Segment seg_b; + seg_b.version = 1; + seg_b.key = "segB"; + + store.Init(SDKDataSet{ + std::unordered_map{ + {"flagA", FlagDescriptor(flag_a)}, + {"flagB", FlagDescriptor(flag_b)}}, + std::unordered_map{ + {"segA", SegmentDescriptor(seg_a)}, + {"segB", SegmentDescriptor(seg_b)}}, + }); + + Flag flag_b_new; + flag_b_new.version = 2; + flag_b_new.key = "flagB"; + + Segment seg_b_new; + seg_b_new.version = 2; + seg_b_new.key = "segB"; + + store.Apply(FDv2ChangeSet{ + FDv2ChangeSet::Type::kPartial, + std::vector{{"flagB", FlagDescriptor(flag_b_new)}, + {"segB", SegmentDescriptor(seg_b_new)}}, + Selector{}, + }); + + ASSERT_TRUE(store.GetFlag("flagA")); + EXPECT_EQ(1u, store.GetFlag("flagA")->version); + ASSERT_TRUE(store.GetFlag("flagB")); + EXPECT_EQ(2u, store.GetFlag("flagB")->version); + ASSERT_TRUE(store.GetSegment("segA")); + EXPECT_EQ(1u, store.GetSegment("segA")->version); + ASSERT_TRUE(store.GetSegment("segB")); + EXPECT_EQ(2u, store.GetSegment("segB")->version); +}