diff --git a/lib/ldclient-rb/impl/data_source.rb b/lib/ldclient-rb/impl/data_source.rb index 1be1a46b..24015c5a 100644 --- a/lib/ldclient-rb/impl/data_source.rb +++ b/lib/ldclient-rb/impl/data_source.rb @@ -89,7 +89,7 @@ def init(all_data) monitor_store_update do if @flag_change_broadcaster.has_listeners? old_data = {} - Impl::DataStore::ALL_KINDS.each do |kind| + LaunchDarkly::Interfaces::DataStore::ALL_KINDS.each do |kind| old_data[kind] = @data_store.all(kind) end end @@ -193,7 +193,7 @@ def update_status(new_state, new_error) private def compute_changed_items_for_full_data_set(old_data, new_data) affected_items = Set.new - Impl::DataStore::ALL_KINDS.each do |kind| + LaunchDarkly::Interfaces::DataStore::ALL_KINDS.each do |kind| old_items = old_data[kind] || {} new_items = new_data[kind] || {} @@ -217,7 +217,7 @@ def update_status(new_state, new_error) # private def send_change_events(affected_items) affected_items.each do |item| - if item[:kind] == Impl::DataStore::FEATURES + if item[:kind] == LaunchDarkly::Interfaces::DataStore::FEATURES @flag_change_broadcaster.broadcast(LaunchDarkly::Interfaces::FlagChange.new(item[:key].to_s)) end end diff --git a/lib/ldclient-rb/impl/data_source/stream.rb b/lib/ldclient-rb/impl/data_source/stream.rb index 7e9d404b..d386f96b 100644 --- a/lib/ldclient-rb/impl/data_source/stream.rb +++ b/lib/ldclient-rb/impl/data_source/stream.rb @@ -17,8 +17,8 @@ module DataSource READ_TIMEOUT_SECONDS = 300 # 5 minutes; the stream should send a ping every 3 minutes KEY_PATHS = { - Impl::DataStore::FEATURES => "/flags/", - Impl::DataStore::SEGMENTS => "/segments/", + LaunchDarkly::Interfaces::DataStore::FEATURES => "/flags/", + LaunchDarkly::Interfaces::DataStore::SEGMENTS => "/segments/", } class StreamProcessor @@ -182,7 +182,7 @@ def process_message(message) @ready.set elsif method == PATCH data = JSON.parse(message.data, symbolize_names: true) - for kind in [Impl::DataStore::FEATURES, Impl::DataStore::SEGMENTS] + for kind in [LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Interfaces::DataStore::SEGMENTS] key = key_for_path(kind, data[:path]) if key item = Impl::Model.deserialize(kind, data[:data], @config.logger) @@ -192,7 +192,7 @@ def process_message(message) end elsif method == DELETE data = JSON.parse(message.data, symbolize_names: true) - for kind in [Impl::DataStore::FEATURES, Impl::DataStore::SEGMENTS] + for kind in [LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Interfaces::DataStore::SEGMENTS] key = key_for_path(kind, data[:path]) if key update_sink_or_data_store.delete(kind, key, data[:version]) diff --git a/lib/ldclient-rb/impl/data_store.rb b/lib/ldclient-rb/impl/data_store.rb deleted file mode 100644 index 1a99fbb3..00000000 --- a/lib/ldclient-rb/impl/data_store.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'concurrent' -require "ldclient-rb/interfaces" -require "ldclient-rb/impl/data_store/data_kind" - -module LaunchDarkly - module Impl - module DataStore - # These constants denote the types of data that can be stored in the feature store. If - # we add another storable data type in the future, as long as it follows the same pattern - # (having "key", "version", and "deleted" properties), we only need to add a corresponding - # constant here and the existing store should be able to handle it. - # - # The :priority and :get_dependency_keys properties are used by FeatureStoreDataSetSorter - # to ensure data consistency during non-atomic updates. - - FEATURES = DataKind.new(namespace: "features", priority: 1).freeze - - SEGMENTS = DataKind.new(namespace: "segments", priority: 0).freeze - - ALL_KINDS = [FEATURES, SEGMENTS].freeze - end - end -end \ No newline at end of file diff --git a/lib/ldclient-rb/impl/data_store/in_memory_feature_store.rb b/lib/ldclient-rb/impl/data_store/in_memory_feature_store.rb index cef3de7f..b393b4d5 100644 --- a/lib/ldclient-rb/impl/data_store/in_memory_feature_store.rb +++ b/lib/ldclient-rb/impl/data_store/in_memory_feature_store.rb @@ -2,7 +2,7 @@ require "concurrent" require "concurrent/atomics" -require "ldclient-rb/impl/data_store" +require "ldclient-rb/impl/data_store/status" require "ldclient-rb/interfaces/data_system" module LaunchDarkly @@ -58,7 +58,7 @@ def initialized? # # Initializes the store with a full set of data, replacing any existing data. # - # @param collections [Hash>] Hash of data kinds to collections of items + # @param collections [Hash>] Hash of data kinds to collections of items # @return [Boolean] true if successful, false otherwise # def set_basis(collections) @@ -80,7 +80,7 @@ def set_basis(collections) # # Applies a delta update to the store. # - # @param collections [Hash>] Hash of data kinds to collections with updates + # @param collections [Hash>] Hash of data kinds to collections with updates # @return [Boolean] true if successful, false otherwise # def apply_delta(collections) @@ -105,8 +105,8 @@ def apply_delta(collections) # # Decodes a collection of items. # - # @param collections [Hash>] Hash of data kinds to collections - # @return [Hash>, nil] Decoded collection with symbol keys, or nil on error + # @param collections [Hash>] Hash of data kinds to collections + # @return [Hash>, nil] Decoded collection with symbol keys, or nil on error # private def decode_collection(collections) all_decoded = {} diff --git a/lib/ldclient-rb/impl/data_store/data_kind.rb b/lib/ldclient-rb/impl/data_store/status.rb similarity index 53% rename from lib/ldclient-rb/impl/data_store/data_kind.rb rename to lib/ldclient-rb/impl/data_store/status.rb index c04a1b6f..e834e522 100644 --- a/lib/ldclient-rb/impl/data_store/data_kind.rb +++ b/lib/ldclient-rb/impl/data_store/status.rb @@ -4,55 +4,6 @@ module LaunchDarkly module Impl module DataStore - class DataKind - FEATURES = "features".freeze - SEGMENTS = "segments".freeze - - FEATURE_PREREQ_FN = lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } }.freeze - - attr_reader :namespace - attr_reader :priority - - # - # @param namespace [String] - # @param priority [Integer] - # - def initialize(namespace:, priority:) - @namespace = namespace - @priority = priority - end - - # - # Maintain the same behavior when these data kinds were standard ruby hashes. - # - # @param key [Symbol] - # @return [Object] - # - def [](key) - return priority if key == :priority - return namespace if key == :namespace - return get_dependency_keys_fn() if key == :get_dependency_keys - nil - end - - # - # Retrieve the dependency keys for a particular data kind. Right now, this is only defined for flags. - # - def get_dependency_keys_fn() - return nil unless @namespace == FEATURES - - FEATURE_PREREQ_FN - end - - def eql?(other) - other.is_a?(DataKind) && namespace == other.namespace && priority == other.priority - end - - def hash - [namespace, priority].hash - end - end - class StatusProvider include LaunchDarkly::Interfaces::DataStore::StatusProvider diff --git a/lib/ldclient-rb/impl/data_store/store.rb b/lib/ldclient-rb/impl/data_store/store.rb index becf7671..dc04bd35 100644 --- a/lib/ldclient-rb/impl/data_store/store.rb +++ b/lib/ldclient-rb/impl/data_store/store.rb @@ -2,7 +2,7 @@ require "concurrent" require "set" -require "ldclient-rb/impl/data_store" +require "ldclient-rb/impl/data_store/status" require "ldclient-rb/impl/data_store/in_memory_feature_store" require "ldclient-rb/impl/dependency_tracker" require "ldclient-rb/interfaces/data_system" @@ -148,7 +148,7 @@ def commit begin # Get all data from memory store and write to persistent store all_data = {} - [FEATURES, SEGMENTS].each do |kind| + [LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Interfaces::DataStore::SEGMENTS].each do |kind| all_data[kind] = @memory_store.all(kind) end @persistent_store.init(all_data) @@ -204,7 +204,7 @@ def get_data_store_status_provider old_data = nil if @flag_change_broadcaster.has_listeners? old_data = {} - [FEATURES, SEGMENTS].each do |kind| + [LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Interfaces::DataStore::SEGMENTS].each do |kind| old_data[kind] = @memory_store.all(kind) end end @@ -313,12 +313,12 @@ def get_data_store_status_provider # private def changes_to_store_data(changes) all_data = { - FEATURES => {}, - SEGMENTS => {}, + LaunchDarkly::Interfaces::DataStore::FEATURES => {}, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => {}, } changes.each do |change| - kind = change.kind == LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG ? FEATURES : SEGMENTS + kind = change.kind == LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG ? LaunchDarkly::Interfaces::DataStore::FEATURES : LaunchDarkly::Interfaces::DataStore::SEGMENTS if change.action == LaunchDarkly::Interfaces::DataSystem::ChangeType::PUT && !change.object.nil? all_data[kind][change.key] = change.object elsif change.action == LaunchDarkly::Interfaces::DataSystem::ChangeType::DELETE @@ -352,7 +352,7 @@ def get_data_store_status_provider # private def send_change_events(affected_items) affected_items.each do |item| - if item[:kind] == FEATURES + if item[:kind] == LaunchDarkly::Interfaces::DataStore::FEATURES @flag_change_broadcaster.broadcast(LaunchDarkly::Interfaces::FlagChange.new(item[:key].to_s)) end end @@ -368,7 +368,7 @@ def get_data_store_status_provider private def compute_changed_items_for_full_data_set(old_data, new_data) affected_items = Set.new - [FEATURES, SEGMENTS].each do |kind| + [LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Interfaces::DataStore::SEGMENTS].each do |kind| old_items = old_data[kind] || {} new_items = new_data[kind] || {} diff --git a/lib/ldclient-rb/impl/data_system/fdv1.rb b/lib/ldclient-rb/impl/data_system/fdv1.rb index b6ba6cbc..010f91d0 100644 --- a/lib/ldclient-rb/impl/data_system/fdv1.rb +++ b/lib/ldclient-rb/impl/data_system/fdv1.rb @@ -5,7 +5,7 @@ require 'ldclient-rb/impl/data_source/polling' require 'ldclient-rb/impl/data_source/requestor' require 'ldclient-rb/impl/data_source/stream' -require 'ldclient-rb/impl/data_store' +require 'ldclient-rb/impl/data_store/status' require 'ldclient-rb/impl/data_system' require 'ldclient-rb/impl/store_client_wrapper' diff --git a/lib/ldclient-rb/impl/dependency_tracker.rb b/lib/ldclient-rb/impl/dependency_tracker.rb index 7ae3c79d..59eea8d7 100644 --- a/lib/ldclient-rb/impl/dependency_tracker.rb +++ b/lib/ldclient-rb/impl/dependency_tracker.rb @@ -42,7 +42,7 @@ def update_dependencies_from(from_kind, from_key, from_item) def self.segment_keys_from_clauses(clauses) clauses.flat_map do |clause| if clause.op == :segmentMatch - clause.values.map { |value| {kind: DataStore::SEGMENTS, key: value }} + clause.values.map { |value| {kind: LaunchDarkly::Interfaces::DataStore::SEGMENTS, key: value }} else [] end @@ -66,13 +66,13 @@ def self.compute_dependencies_from(from_kind, from_item, logger = nil) from_item end - if from_kind == DataStore::FEATURES && from_item.is_a?(LaunchDarkly::Impl::Model::FeatureFlag) + if from_kind == LaunchDarkly::Interfaces::DataStore::FEATURES && from_item.is_a?(LaunchDarkly::Impl::Model::FeatureFlag) prereq_keys = from_item.prerequisites.map { |prereq| {kind: from_kind, key: prereq.key} } segment_keys = from_item.rules.flat_map { |rule| DependencyTracker.segment_keys_from_clauses(rule.clauses) } results = Set.new(prereq_keys) results.merge(segment_keys) - elsif from_kind == DataStore::SEGMENTS && from_item.is_a?(LaunchDarkly::Impl::Model::Segment) + elsif from_kind == LaunchDarkly::Interfaces::DataStore::SEGMENTS && from_item.is_a?(LaunchDarkly::Impl::Model::Segment) kind_and_keys = from_item.rules.flat_map do |rule| DependencyTracker.segment_keys_from_clauses(rule.clauses) end diff --git a/lib/ldclient-rb/impl/integrations/file_data_source.rb b/lib/ldclient-rb/impl/integrations/file_data_source.rb index 1b3cbf8b..d5a984ca 100644 --- a/lib/ldclient-rb/impl/integrations/file_data_source.rb +++ b/lib/ldclient-rb/impl/integrations/file_data_source.rb @@ -75,8 +75,8 @@ def stop def load_all all_data = { - Impl::DataStore::FEATURES => {}, - Impl::DataStore::SEGMENTS => {}, + LaunchDarkly::Interfaces::DataStore::FEATURES => {}, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => {}, } @paths.each do |path| begin @@ -105,14 +105,14 @@ def load_file(path, all_data) parsed = parse_content(File.read(path)) (parsed[:flags] || {}).each do |key, flag| flag[:version] = version - add_item(all_data, Impl::DataStore::FEATURES, flag) + add_item(all_data, LaunchDarkly::Interfaces::DataStore::FEATURES, flag) end (parsed[:flagValues] || {}).each do |key, value| - add_item(all_data, Impl::DataStore::FEATURES, make_flag_with_value(key.to_s, value, version)) + add_item(all_data, LaunchDarkly::Interfaces::DataStore::FEATURES, make_flag_with_value(key.to_s, value, version)) end (parsed[:segments] || {}).each do |key, segment| segment[:version] = version - add_item(all_data, Impl::DataStore::SEGMENTS, segment) + add_item(all_data, LaunchDarkly::Interfaces::DataStore::SEGMENTS, segment) end end diff --git a/lib/ldclient-rb/impl/model/feature_flag.rb b/lib/ldclient-rb/impl/model/feature_flag.rb index b53b13e3..4fda0f37 100644 --- a/lib/ldclient-rb/impl/model/feature_flag.rb +++ b/lib/ldclient-rb/impl/model/feature_flag.rb @@ -1,5 +1,6 @@ require "ldclient-rb/impl/evaluator_helpers" require "ldclient-rb/impl/model/clause" +require "ldclient-rb/context" require "set" # See serialization.rb for implementation notes on the data model classes. diff --git a/lib/ldclient-rb/impl/model/segment.rb b/lib/ldclient-rb/impl/model/segment.rb index 83c1dc90..a361e37f 100644 --- a/lib/ldclient-rb/impl/model/segment.rb +++ b/lib/ldclient-rb/impl/model/segment.rb @@ -1,5 +1,6 @@ require "ldclient-rb/impl/model/clause" require "ldclient-rb/impl/model/preprocessed_data" +require "ldclient-rb/context" require "set" # See serialization.rb for implementation notes on the data model classes. diff --git a/lib/ldclient-rb/impl/model/serialization.rb b/lib/ldclient-rb/impl/model/serialization.rb index 978111d9..f7df32ba 100644 --- a/lib/ldclient-rb/impl/model/serialization.rb +++ b/lib/ldclient-rb/impl/model/serialization.rb @@ -1,3 +1,5 @@ +require "json" + require "ldclient-rb/impl/model/feature_flag" require "ldclient-rb/impl/model/preprocessed_data" require "ldclient-rb/impl/model/segment" @@ -34,7 +36,7 @@ module Model # SDK code outside of Impl::Model should use this method instead of calling the model class # constructors directly, so as not to rely on implementation details. # - # @param kind [Hash] normally either Impl::DataStore::FEATURES or Impl::DataStore::SEGMENTS + # @param kind [Hash] normally either LaunchDarkly::Interfaces::DataStore::FEATURES or LaunchDarkly::Interfaces::DataStore::SEGMENTS # @param input [object] a JSON string or a parsed hash (or a data model object, in which case # we'll just return the original object) # @param logger [Logger|nil] logs errors if there are any data validation problems @@ -44,9 +46,9 @@ def self.deserialize(kind, input, logger = nil) return input unless input.is_a?(String) || input.is_a?(Hash) data = input.is_a?(Hash) ? input : JSON.parse(input, symbolize_names: true) case kind - when Impl::DataStore::FEATURES + when LaunchDarkly::Interfaces::DataStore::FEATURES FeatureFlag.new(data, logger) - when Impl::DataStore::SEGMENTS + when LaunchDarkly::Interfaces::DataStore::SEGMENTS Segment.new(data, logger) else data @@ -63,8 +65,8 @@ def self.serialize(kind, item) # Translates a { flags: ..., segments: ... } object received from LaunchDarkly to the data store format. def self.make_all_store_data(received_data, logger = nil) { - Impl::DataStore::FEATURES => (received_data[:flags] || {}).transform_values { |data| FeatureFlag.new(data, logger) }, - Impl::DataStore::SEGMENTS => (received_data[:segments] || {}).transform_values { |data| Segment.new(data, logger) }, + LaunchDarkly::Interfaces::DataStore::FEATURES => (received_data[:flags] || {}).transform_values { |data| FeatureFlag.new(data, logger) }, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => (received_data[:segments] || {}).transform_values { |data| Segment.new(data, logger) }, } end end diff --git a/lib/ldclient-rb/in_memory_store.rb b/lib/ldclient-rb/in_memory_store.rb index c0cd583e..d54e08ba 100644 --- a/lib/ldclient-rb/in_memory_store.rb +++ b/lib/ldclient-rb/in_memory_store.rb @@ -1,5 +1,5 @@ require "concurrent/atomics" -require "ldclient-rb/impl/data_store" +require "ldclient-rb/impl/data_store/status" module LaunchDarkly # diff --git a/lib/ldclient-rb/integrations/test_data.rb b/lib/ldclient-rb/integrations/test_data.rb index 880f4517..cbe3face 100644 --- a/lib/ldclient-rb/integrations/test_data.rb +++ b/lib/ldclient-rb/integrations/test_data.rb @@ -121,10 +121,10 @@ def update(flag_builder) if @current_flags[flag_key] version = @current_flags[flag_key][:version] end - new_flag = LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Impl::DataStore::FEATURES, flag_builder.build(version+1)) + new_flag = LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_builder.build(version+1)) @current_flags[flag_key] = new_flag end - update_item(LaunchDarkly::Impl::DataStore::FEATURES, new_flag) + update_item(LaunchDarkly::Interfaces::DataStore::FEATURES, new_flag) self end @@ -147,7 +147,7 @@ def update(flag_builder) # @return [TestData] the TestData instance # def use_preconfigured_flag(flag) - use_preconfigured_item(LaunchDarkly::Impl::DataStore::FEATURES, flag, @current_flags) + use_preconfigured_item(LaunchDarkly::Interfaces::DataStore::FEATURES, flag, @current_flags) end # @@ -167,7 +167,7 @@ def use_preconfigured_flag(flag) # @return [TestData] the TestData instance # def use_preconfigured_segment(segment) - use_preconfigured_item(LaunchDarkly::Impl::DataStore::SEGMENTS, segment, @current_segments) + use_preconfigured_item(LaunchDarkly::Interfaces::DataStore::SEGMENTS, segment, @current_segments) end private def use_preconfigured_item(kind, item, current) @@ -198,8 +198,8 @@ def use_preconfigured_segment(segment) def make_init_data @lock.with_read_lock do { - LaunchDarkly::Impl::DataStore::FEATURES => @current_flags.clone, - LaunchDarkly::Impl::DataStore::SEGMENTS => @current_segments.clone, + LaunchDarkly::Interfaces::DataStore::FEATURES => @current_flags.clone, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => @current_segments.clone, } end end diff --git a/lib/ldclient-rb/interfaces/data_store.rb b/lib/ldclient-rb/interfaces/data_store.rb index 0865dc70..f9d266dc 100644 --- a/lib/ldclient-rb/interfaces/data_store.rb +++ b/lib/ldclient-rb/interfaces/data_store.rb @@ -1,6 +1,139 @@ +require "ldclient-rb/impl/model/serialization" + module LaunchDarkly module Interfaces module DataStore + # + # A kind of data that the SDK keeps in a data store, such as feature flags or segments. + # + # The SDK gives a data kind to each data store operation. A persistent data store uses + # {#namespace} to keep the items of each kind apart. It can use {#deserialize} to get the + # version of an item that it has in serialized form. + # + # The SDK supplies the data kinds {FEATURES} and {SEGMENTS}. Application code does not + # usually create a data kind. + # + class DataKind + FEATURE_PREREQ_FN = lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } }.freeze + private_constant :FEATURE_PREREQ_FN + + # + # The name of the kind, for example "features". A data store uses this name to keep the + # items of each kind apart. + # + # @return [String] + # + attr_reader :namespace + + # + # The order in which the SDK writes this kind to a data store. The SDK writes a kind with + # a lower priority first. + # + # @return [Integer] + # + attr_reader :priority + + # + # @param namespace [String] + # @param priority [Integer] + # + def initialize(namespace:, priority:) + @namespace = namespace + @priority = priority + end + + # + # Converts an item of this kind to a string for a data store. + # + # A deleted item becomes a placeholder that has the item's key and version and the + # property "deleted" with the value true. + # + # @param item [Object] an item of this kind, or a deleted item placeholder + # @return [String] + # + def serialize(item) + LaunchDarkly::Impl::Model.serialize(self, item) + end + + # + # Converts a string from a data store to an item of this kind. + # + # @param serialized [String] the string that {#serialize} made + # @param logger [Logger, nil] logs data validation problems, if any + # @return [Object] the item, or a deleted item placeholder + # + def deserialize(serialized, logger = nil) + LaunchDarkly::Impl::Model.deserialize(self, serialized, logger) + end + + # + # Gives the properties of this kind by name, as when data kinds were hashes. + # + # @param key [Symbol] + # @return [Object] + # + def [](key) + return priority if key == :priority + return namespace if key == :namespace + return get_dependency_keys_fn() if key == :get_dependency_keys + nil + end + + # + # Gives a function that returns the keys of the items an item depends on. Only feature + # flags have dependencies. + # + # @return [Proc, nil] + # + def get_dependency_keys_fn() + return nil unless @namespace == FEATURES.namespace + + FEATURE_PREREQ_FN + end + + def ==(other) + other.is_a?(DataKind) && namespace == other.namespace && priority == other.priority + end + alias_method :eql?, :== + + def hash + [namespace, priority].hash + end + end + + # + # The data kind for feature flags. + # + FEATURES = DataKind.new(namespace: "features", priority: 1).freeze + + # + # The data kind for segments. The SDK writes segments before feature flags, because + # feature flags can refer to segments. + # + SEGMENTS = DataKind.new(namespace: "segments", priority: 0).freeze + + # + # All of the data kinds that the SDK keeps in a data store. + # + ALL_KINDS = [FEATURES, SEGMENTS].freeze + + # + # An item in the serialized form that a persistent data store keeps. + # + # The SDK serializes items before it gives them to a persistent data store, and + # deserializes the items that the store returns. The store keeps only the serialized string + # and the version. + # + # @!attribute [r] version + # @return [Integer] the version of the item. A store that does not keep the version + # separately returns 0, and the SDK gets the version from the serialized item. + # @!attribute [r] deleted + # @return [Boolean] true if the item is a deleted item placeholder + # @!attribute [r] serialized_item + # @return [String, nil] the serialized item, from {DataKind#serialize} + # + SerializedItemDescriptor = Data.define(:version, :deleted, :serialized_item) + # # An interface for querying the status of a persistent data store. # diff --git a/lib/ldclient-rb/interfaces/data_system.rb b/lib/ldclient-rb/interfaces/data_system.rb index ef517a85..75600e47 100644 --- a/lib/ldclient-rb/interfaces/data_system.rb +++ b/lib/ldclient-rb/interfaces/data_system.rb @@ -611,7 +611,7 @@ module ReadOnlyStore # # Retrieves an item by kind and key. # - # @param kind [LaunchDarkly::Impl::DataStore::DataKind] The data kind (e.g., LaunchDarkly::Impl::DataStore::FEATURES, LaunchDarkly::Impl::DataStore::SEGMENTS) + # @param kind [LaunchDarkly::Interfaces::DataStore::DataKind] The data kind (e.g., LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Interfaces::DataStore::SEGMENTS) # @param key [String] The item key # @return [Hash, nil] The item, or nil if not found or deleted # @@ -622,7 +622,7 @@ def get(kind, key) # # Retrieves all items of a given kind. # - # @param kind [LaunchDarkly::Impl::DataStore::DataKind] The data kind (e.g., LaunchDarkly::Impl::DataStore::FEATURES, LaunchDarkly::Impl::DataStore::SEGMENTS) + # @param kind [LaunchDarkly::Interfaces::DataStore::DataKind] The data kind (e.g., LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Interfaces::DataStore::SEGMENTS) # @return [Hash] Hash of keys to items (excluding deleted items) # def all(kind) diff --git a/lib/ldclient-rb/ldclient.rb b/lib/ldclient-rb/ldclient.rb index 63df8c9b..c155d1a3 100644 --- a/lib/ldclient-rb/ldclient.rb +++ b/lib/ldclient-rb/ldclient.rb @@ -2,7 +2,7 @@ require "ldclient-rb/impl/broadcaster" require "ldclient-rb/impl/context" require "ldclient-rb/impl/data_source" -require "ldclient-rb/impl/data_store" +require "ldclient-rb/impl/data_store/status" require "ldclient-rb/impl/data_system/fdv1" require "ldclient-rb/impl/data_system/fdv2" require "ldclient-rb/impl/diagnostic_events" @@ -181,8 +181,8 @@ def postfork(wait_for_sec = 5) @big_segment_store_manager = Impl::BigSegmentStoreManager.new(@config.big_segments, @config.logger) @big_segment_store_status_provider = @big_segment_store_manager.status_provider - get_flag = lambda { |key| @data_system.store.get(Impl::DataStore::FEATURES, key) } - get_segment = lambda { |key| @data_system.store.get(Impl::DataStore::SEGMENTS, key) } + get_flag = lambda { |key| @data_system.store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, key) } + get_segment = lambda { |key| @data_system.store.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, key) } get_big_segments_membership = lambda { |key| @big_segment_store_manager.get_context_membership(key) } @evaluator = LaunchDarkly::Impl::Evaluator.new(get_flag, get_segment, get_big_segments_membership, @config.logger) @@ -650,7 +650,7 @@ def all_flags_state(context, options={}) end begin - features = @data_system.store.all(Impl::DataStore::FEATURES) + features = @data_system.store.all(LaunchDarkly::Interfaces::DataStore::FEATURES) rescue => exn Impl::Util.log_exception(@config.logger, "Unable to read flags for all_flags_state", exn) return FeatureFlagsState.new(false) @@ -784,7 +784,7 @@ def flag_tracker end begin - feature = @data_system.store.get(Impl::DataStore::FEATURES, key) + feature = @data_system.store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, key) rescue # Ignored end diff --git a/spec/feature_store_spec_base.rb b/spec/feature_store_spec_base.rb index 4eb7b0a5..6f7a012e 100644 --- a/spec/feature_store_spec_base.rb +++ b/spec/feature_store_spec_base.rb @@ -195,8 +195,8 @@ def new_version_plus(f, delta_version, attrs = {}) deleted: false, } with_inited_store([]) do |store| - store.upsert(LaunchDarkly::Impl::DataStore::FEATURES, flag) - expect(store.get(LaunchDarkly::Impl::DataStore::FEATURES, flag[:key])).to eq flag + store.upsert(LaunchDarkly::Interfaces::DataStore::FEATURES, flag) + expect(store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag[:key])).to eq flag end end end diff --git a/spec/impl/data_source/polling_spec.rb b/spec/impl/data_source/polling_spec.rb index d3bb28c6..5337e005 100644 --- a/spec/impl/data_source/polling_spec.rb +++ b/spec/impl/data_source/polling_spec.rb @@ -36,10 +36,10 @@ def with_processor(store, initialize_to_valid = false) flag = Impl::Model::FeatureFlag.new({ key: 'flagkey', version: 1 }) segment = Impl::Model::Segment.new({ key: 'segkey', version: 1 }) all_data = { - Impl::DataStore::FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { flagkey: flag, }, - Impl::DataStore::SEGMENTS => { + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { segkey: segment, }, } @@ -50,8 +50,8 @@ def with_processor(store, initialize_to_valid = false) with_processor(store) do |processor| ready = processor.start ready.wait - expect(store.get(Impl::DataStore::FEATURES, "flagkey")).to eq(flag) - expect(store.get(Impl::DataStore::SEGMENTS, "segkey")).to eq(segment) + expect(store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "flagkey")).to eq(flag) + expect(store.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, "segkey")).to eq(segment) end end @@ -75,8 +75,8 @@ def with_processor(store, initialize_to_valid = false) with_processor(store) do |processor| ready = processor.start ready.wait - expect(store.get(Impl::DataStore::FEATURES, "flagkey")).to eq(flag) - expect(store.get(Impl::DataStore::SEGMENTS, "segkey")).to eq(segment) + expect(store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "flagkey")).to eq(flag) + expect(store.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, "segkey")).to eq(segment) expect(listener.statuses.count).to eq(1) expect(listener.statuses[0].state).to eq(Interfaces::DataSource::Status::VALID) @@ -106,8 +106,8 @@ def with_processor(store, initialize_to_valid = false) describe 'environment ID' do flag = Impl::Model::FeatureFlag.new({ key: 'flagkey', version: 1 }) all_data = { - Impl::DataStore::FEATURES => { flagkey: flag }, - Impl::DataStore::SEGMENTS => {}, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flagkey: flag }, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => {}, } it 'is recorded from the response headers' do @@ -236,7 +236,7 @@ def verify_recoverable_http_error(status) describe 'retry delay' do let(:logger) { double("logger").as_null_object } - let(:all_data) { { Impl::DataStore::FEATURES => {}, Impl::DataStore::SEGMENTS => {} } } + let(:all_data) { { LaunchDarkly::Interfaces::DataStore::FEATURES => {}, LaunchDarkly::Interfaces::DataStore::SEGMENTS => {} } } def make_processor config = Config.new(feature_store: InMemoryFeatureStore.new, logger: logger) @@ -339,8 +339,8 @@ def next_delay(processor) it 'closes requestor HTTP connections on stop' do requestor_with_stop = double("RequestorWithStop") allow(requestor_with_stop).to receive(:request_all_data).and_return({ - Impl::DataStore::FEATURES => {}, - Impl::DataStore::SEGMENTS => {}, + LaunchDarkly::Interfaces::DataStore::FEATURES => {}, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => {}, }) allow(requestor_with_stop).to receive(:stop) diff --git a/spec/impl/data_source/stream_spec.rb b/spec/impl/data_source/stream_spec.rb index 7a19e623..5438bd4b 100644 --- a/spec/impl/data_source/stream_spec.rb +++ b/spec/impl/data_source/stream_spec.rb @@ -28,26 +28,26 @@ module LaunchDarkly it "will accept PUT methods" do processor.send(:process_message, put_message) - expect(config.feature_store.get(Impl::DataStore::FEATURES, "asdf")).to eq(Flags.from_hash(key: "asdf")) - expect(config.feature_store.get(Impl::DataStore::SEGMENTS, "segkey")).to eq(Segments.from_hash(key: "segkey")) + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "asdf")).to eq(Flags.from_hash(key: "asdf")) + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, "segkey")).to eq(Segments.from_hash(key: "segkey")) end it "will accept PATCH methods for flags" do processor.send(:process_message, patch_flag_message) - expect(config.feature_store.get(Impl::DataStore::FEATURES, "asdf")).to eq(Flags.from_hash(key: "asdf", version: 1)) + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "asdf")).to eq(Flags.from_hash(key: "asdf", version: 1)) end it "will accept PATCH methods for segments" do processor.send(:process_message, patch_seg_message) - expect(config.feature_store.get(Impl::DataStore::SEGMENTS, "asdf")).to eq(Segments.from_hash(key: "asdf", version: 1)) + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, "asdf")).to eq(Segments.from_hash(key: "asdf", version: 1)) end it "will accept DELETE methods for flags" do processor.send(:process_message, patch_flag_message) processor.send(:process_message, delete_flag_message) - expect(config.feature_store.get(Impl::DataStore::FEATURES, "key")).to eq(nil) + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "key")).to eq(nil) end it "will accept DELETE methods for segments" do processor.send(:process_message, patch_seg_message) processor.send(:process_message, delete_seg_message) - expect(config.feature_store.get(Impl::DataStore::SEGMENTS, "key")).to eq(nil) + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, "key")).to eq(nil) end it "will log a warning if the method is not recognized" do expect(processor.instance_variable_get(:@config).logger).to receive :warn diff --git a/spec/impl/data_source_spec.rb b/spec/impl/data_source_spec.rb index 5e381300..a3a20b29 100644 --- a/spec/impl/data_source_spec.rb +++ b/spec/impl/data_source_spec.rb @@ -99,7 +99,7 @@ module Impl describe "simple flag change listener" do let(:all_data) { { - LaunchDarkly::Impl::DataStore::FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag1: LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag1', version: 1 }), flag2: LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag2', version: 1 }), flag3: LaunchDarkly::Impl::Model::FeatureFlag.new( @@ -125,7 +125,7 @@ module Impl } ), }, - LaunchDarkly::Impl::DataStore::SEGMENTS => { + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { segment1: LaunchDarkly::Impl::Model::Segment.new({ key: 'segment1', version: 1 }), segment2: LaunchDarkly::Impl::Model::Segment.new({ key: 'segment2', version: 1 }), }, @@ -139,7 +139,7 @@ module Impl flag_change_broadcaster.add_listener(listener) updated_data = { - LaunchDarkly::Impl::DataStore::FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag1: LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag1', version: 2 }), flag4: LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag4', version: 1 }), }, @@ -160,9 +160,9 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.upsert(LaunchDarkly::Impl::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag1', version: 2 })) + sink.upsert(LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag1', version: 2 })) # A duplicate update does not change the store, so it sends no event. - sink.upsert(LaunchDarkly::Impl::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag1', version: 2 })) + sink.upsert(LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag1', version: 2 })) expect(listener.statuses.count).to eq(1) expect(listener.statuses[0].key).to eq('flag1') @@ -174,9 +174,9 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.delete(LaunchDarkly::Impl::DataStore::FEATURES, "flag1", 2) + sink.delete(LaunchDarkly::Interfaces::DataStore::FEATURES, "flag1", 2) # A duplicate update does not change the store, so it sends no event. - sink.delete(LaunchDarkly::Impl::DataStore::FEATURES, "flag1", 2) + sink.delete(LaunchDarkly::Interfaces::DataStore::FEATURES, "flag1", 2) expect(listener.statuses.count).to eq(1) expect(listener.statuses[0].key).to eq("flag1") @@ -188,9 +188,9 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.upsert(LaunchDarkly::Impl::DataStore::SEGMENTS, LaunchDarkly::Impl::Model::Segment.new({ key: 'segment2', version: 2 })) + sink.upsert(LaunchDarkly::Interfaces::DataStore::SEGMENTS, LaunchDarkly::Impl::Model::Segment.new({ key: 'segment2', version: 2 })) # A duplicate update does not change the store, so it sends no event. - sink.upsert(LaunchDarkly::Impl::DataStore::SEGMENTS, LaunchDarkly::Impl::Model::Segment.new({ key: 'segment2', version: 2 })) + sink.upsert(LaunchDarkly::Interfaces::DataStore::SEGMENTS, LaunchDarkly::Impl::Model::Segment.new({ key: 'segment2', version: 2 })) expect(listener.statuses.count).to eq(1) expect(listener.statuses[0].key).to eq('flag3') @@ -200,7 +200,7 @@ module Impl describe "prerequisite flag change listener" do let(:all_data) { { - LaunchDarkly::Impl::DataStore::FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag1: LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag1', version: 1, prerequisites: [{key: 'flag2', variation: 0}] }), flag2: LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag2', version: 1, prerequisites: [{key: 'flag3', variation: 0}, {key: 'flag4', variation: 0}, {key: 'flag6', variation: 0}] }), @@ -230,7 +230,7 @@ module Impl } ), }, - LaunchDarkly::Impl::DataStore::SEGMENTS => { + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { segment1: LaunchDarkly::Impl::Model::Segment.new({ key: 'segment1', version: 1 }), segment2: LaunchDarkly::Impl::Model::Segment.new( { @@ -265,7 +265,7 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.upsert(LaunchDarkly::Impl::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag4', version: 2 })) + sink.upsert(LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag4', version: 2 })) expect(listener.statuses.count).to eq(3) expect(listener.statuses[0].key).to eq('flag4') expect(listener.statuses[1].key).to eq('flag2') @@ -278,7 +278,7 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.upsert(LaunchDarkly::Impl::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ + sink.upsert(LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag3', version: 2, prerequisities: [{key: 'flag4', variation: 0}] })) expect(listener.statuses.count).to eq(3) @@ -293,7 +293,7 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.upsert(LaunchDarkly::Impl::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ + sink.upsert(LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Impl::Model::FeatureFlag.new({ key: 'flag2', version: 2, prerequisities: [{key: 'flag3', variation: 0}] })) expect(listener.statuses.count).to eq(2) @@ -307,7 +307,7 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.delete(LaunchDarkly::Impl::DataStore::FEATURES, "flag4", 2) + sink.delete(LaunchDarkly::Interfaces::DataStore::FEATURES, "flag4", 2) expect(listener.statuses.count).to eq(3) expect(listener.statuses[0].key).to eq('flag4') expect(listener.statuses[1].key).to eq('flag2') @@ -320,9 +320,9 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.upsert(LaunchDarkly::Impl::DataStore::SEGMENTS, LaunchDarkly::Impl::Model::Segment.new({ key: 'segment1', version: 2 })) + sink.upsert(LaunchDarkly::Interfaces::DataStore::SEGMENTS, LaunchDarkly::Impl::Model::Segment.new({ key: 'segment1', version: 2 })) # A duplicate update does not change the store, so it sends no event. - sink.upsert(LaunchDarkly::Impl::DataStore::SEGMENTS, LaunchDarkly::Impl::Model::Segment.new({ key: 'segment1', version: 2 })) + sink.upsert(LaunchDarkly::Interfaces::DataStore::SEGMENTS, LaunchDarkly::Impl::Model::Segment.new({ key: 'segment1', version: 2 })) expect(listener.statuses.count).to eq(3) expect(listener.statuses[0].key).to eq('flag6') @@ -336,9 +336,9 @@ module Impl listener = ListenerSpy.new flag_change_broadcaster.add_listener(listener) - sink.delete(LaunchDarkly::Impl::DataStore::SEGMENTS, 'segment2', 2) + sink.delete(LaunchDarkly::Interfaces::DataStore::SEGMENTS, 'segment2', 2) # A duplicate update does not change the store, so it sends no event. - sink.delete(LaunchDarkly::Impl::DataStore::SEGMENTS, 'segment2', 2) + sink.delete(LaunchDarkly::Interfaces::DataStore::SEGMENTS, 'segment2', 2) expect(listener.statuses.count).to eq(3) expect(listener.statuses[0].key).to eq('flag6') diff --git a/spec/impl/data_store/in_memory_feature_store_v2_spec.rb b/spec/impl/data_store/in_memory_feature_store_v2_spec.rb index fb2d3918..f627df4e 100644 --- a/spec/impl/data_store/in_memory_feature_store_v2_spec.rb +++ b/spec/impl/data_store/in_memory_feature_store_v2_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" require "ldclient-rb/impl/data_store/in_memory_feature_store" -require "ldclient-rb/impl/data_store" +require "ldclient-rb/impl/data_store/status" module LaunchDarkly module Impl @@ -37,7 +37,7 @@ module DataStore end it "returns true after set_basis" do - subject.set_basis({ FEATURES => {} }) + subject.set_basis({ LaunchDarkly::Interfaces::DataStore::FEATURES => {} }) expect(subject.initialized?).to be true end end @@ -46,113 +46,113 @@ module DataStore before do # Store items with symbol keys (as done by FDv2 protocol layer) collections = { - FEATURES => { flag_key.to_sym => flag }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag }, } subject.set_basis(collections) end it "retrieves items with string keys (critical for variation calls)" do - result = subject.get(FEATURES, flag_key) + result = subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) expect(result).to be_a(LaunchDarkly::Impl::Model::FeatureFlag) expect(result.key).to eq(flag_key) end it "retrieves items with symbol keys" do - result = subject.get(FEATURES, flag_key.to_sym) + result = subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key.to_sym) expect(result).to be_a(LaunchDarkly::Impl::Model::FeatureFlag) expect(result.key).to eq(flag_key) end it "returns nil for non-existent keys" do - expect(subject.get(FEATURES, "nonexistent")).to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "nonexistent")).to be_nil end it "returns nil for deleted items" do deleted_flag = flag.merge(deleted: true) - collections = { FEATURES => { flag_key.to_sym => deleted_flag } } + collections = { LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => deleted_flag } } subject.set_basis(collections) - expect(subject.get(FEATURES, flag_key)).to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).to be_nil end end describe "#all" do it "returns empty hash when no data" do - expect(subject.all(FEATURES)).to eq({}) + expect(subject.all(LaunchDarkly::Interfaces::DataStore::FEATURES)).to eq({}) end it "returns all non-deleted items" do collections = { - FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag, "deleted-flag".to_sym => flag.merge(key: "deleted-flag", deleted: true), }, } subject.set_basis(collections) - result = subject.all(FEATURES) + result = subject.all(LaunchDarkly::Interfaces::DataStore::FEATURES) expect(result.keys).to contain_exactly(flag_key.to_sym) expect(result[flag_key.to_sym].key).to eq(flag_key) end it "returns items for both flags and segments" do collections = { - FEATURES => { flag_key.to_sym => flag }, - SEGMENTS => { segment_key.to_sym => segment }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag }, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { segment_key.to_sym => segment }, } subject.set_basis(collections) - expect(subject.all(FEATURES).keys).to contain_exactly(flag_key.to_sym) - expect(subject.all(SEGMENTS).keys).to contain_exactly(segment_key.to_sym) + expect(subject.all(LaunchDarkly::Interfaces::DataStore::FEATURES).keys).to contain_exactly(flag_key.to_sym) + expect(subject.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).keys).to contain_exactly(segment_key.to_sym) end end describe "#set_basis" do it "initializes the store with valid data" do collections = { - FEATURES => { flag_key.to_sym => flag }, - SEGMENTS => { segment_key.to_sym => segment }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag }, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { segment_key.to_sym => segment }, } result = subject.set_basis(collections) expect(result).to be true expect(subject.initialized?).to be true - expect(subject.get(FEATURES, flag_key)).not_to be_nil - expect(subject.get(SEGMENTS, segment_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, segment_key)).not_to be_nil end it "replaces existing data" do # Set initial data initial_collections = { - FEATURES => { flag_key.to_sym => flag }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag }, } subject.set_basis(initial_collections) # Replace with new data new_flag = flag.merge(key: "new-flag", version: 2) new_collections = { - FEATURES => { "new-flag".to_sym => new_flag }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { "new-flag".to_sym => new_flag }, } result = subject.set_basis(new_collections) expect(result).to be true - expect(subject.get(FEATURES, flag_key)).to be_nil # Old flag gone - expect(subject.get(FEATURES, "new-flag")).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).to be_nil # Old flag gone + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "new-flag")).not_to be_nil end it "clears all data before setting new data" do subject.set_basis({ - FEATURES => { flag_key.to_sym => flag }, - SEGMENTS => { segment_key.to_sym => segment }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag }, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { segment_key.to_sym => segment }, }) # Replace with data that only has flags new_collections = { - FEATURES => { "new-flag".to_sym => flag.merge(key: "new-flag") }, - SEGMENTS => {}, + LaunchDarkly::Interfaces::DataStore::FEATURES => { "new-flag".to_sym => flag.merge(key: "new-flag") }, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => {}, } subject.set_basis(new_collections) - expect(subject.all(SEGMENTS)).to be_empty + expect(subject.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS)).to be_empty end it "handles multiple flags and segments" do @@ -164,12 +164,12 @@ module DataStore segment2 = segment.merge(key: "segment-2", version: 2) collections = { - FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { "flag-1".to_sym => flag1, "flag-2".to_sym => flag2, "flag-3".to_sym => flag3, }, - SEGMENTS => { + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { "segment-1".to_sym => segment1, "segment-2".to_sym => segment2, }, @@ -178,14 +178,14 @@ module DataStore result = subject.set_basis(collections) expect(result).to be true - expect(subject.all(FEATURES).size).to eq(3) - expect(subject.all(SEGMENTS).size).to eq(2) + expect(subject.all(LaunchDarkly::Interfaces::DataStore::FEATURES).size).to eq(3) + expect(subject.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).size).to eq(2) end it "returns false and logs error on deserialization failure" do allow(LaunchDarkly::Impl::Model).to receive(:deserialize).and_raise(StandardError.new("test error")) - collections = { FEATURES => { flag_key.to_sym => flag } } + collections = { LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag } } result = subject.set_basis(collections) expect(result).to be false @@ -193,7 +193,7 @@ module DataStore end it "handles empty collections" do - result = subject.set_basis({ FEATURES => {}, SEGMENTS => {} }) + result = subject.set_basis({ LaunchDarkly::Interfaces::DataStore::FEATURES => {}, LaunchDarkly::Interfaces::DataStore::SEGMENTS => {} }) expect(result).to be true expect(subject.initialized?).to be true end @@ -203,8 +203,8 @@ module DataStore before do # Set initial data collections = { - FEATURES => { flag_key.to_sym => flag }, - SEGMENTS => { segment_key.to_sym => segment }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag }, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { segment_key.to_sym => segment }, } subject.set_basis(collections) end @@ -212,30 +212,30 @@ module DataStore it "adds new items without clearing existing data" do new_flag = flag.merge(key: "new-flag", version: 2) delta = { - FEATURES => { "new-flag".to_sym => new_flag }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { "new-flag".to_sym => new_flag }, } result = subject.apply_delta(delta) expect(result).to be true # Original flag should still exist - expect(subject.get(FEATURES, flag_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).not_to be_nil # New flag should be added - expect(subject.get(FEATURES, "new-flag")).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "new-flag")).not_to be_nil # Segment should be unchanged - expect(subject.get(SEGMENTS, segment_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, segment_key)).not_to be_nil end it "updates existing items" do updated_flag = flag.merge(version: 2, on: false) delta = { - FEATURES => { flag_key.to_sym => updated_flag }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => updated_flag }, } result = subject.apply_delta(delta) expect(result).to be true - result = subject.get(FEATURES, flag_key) + result = subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) expect(result.version).to eq(2) expect(result.on).to be false end @@ -246,11 +246,11 @@ module DataStore segment2 = segment.merge(key: "segment-2", version: 2) delta = { - FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { "flag-2".to_sym => flag2, "flag-3".to_sym => flag3, }, - SEGMENTS => { + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { "segment-2".to_sym => segment2, }, } @@ -259,52 +259,52 @@ module DataStore expect(result).to be true # Original items unchanged - expect(subject.get(FEATURES, flag_key)).not_to be_nil - expect(subject.get(SEGMENTS, segment_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, segment_key)).not_to be_nil # New items added - expect(subject.get(FEATURES, "flag-2")).not_to be_nil - expect(subject.get(FEATURES, "flag-3")).not_to be_nil - expect(subject.get(SEGMENTS, "segment-2")).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "flag-2")).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, "flag-3")).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, "segment-2")).not_to be_nil end it "handles delete operations" do deleted_flag = { key: flag_key, version: 2, deleted: true } delta = { - FEATURES => { flag_key.to_sym => deleted_flag }, + LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => deleted_flag }, } result = subject.apply_delta(delta) expect(result).to be true # Deleted flag should return nil - expect(subject.get(FEATURES, flag_key)).to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).to be_nil end it "returns false and logs error on deserialization failure" do allow(LaunchDarkly::Impl::Model).to receive(:deserialize).and_raise(StandardError.new("test error")) - delta = { FEATURES => { "new-flag".to_sym => flag } } + delta = { LaunchDarkly::Interfaces::DataStore::FEATURES => { "new-flag".to_sym => flag } } result = subject.apply_delta(delta) expect(result).to be false # Original data should be intact - expect(subject.get(FEATURES, flag_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).not_to be_nil end it "handles empty delta" do - result = subject.apply_delta({ FEATURES => {}, SEGMENTS => {} }) + result = subject.apply_delta({ LaunchDarkly::Interfaces::DataStore::FEATURES => {}, LaunchDarkly::Interfaces::DataStore::SEGMENTS => {} }) expect(result).to be true # Original data unchanged - expect(subject.get(FEATURES, flag_key)).not_to be_nil - expect(subject.get(SEGMENTS, segment_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).not_to be_nil + expect(subject.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, segment_key)).not_to be_nil end end describe "thread safety" do it "handles concurrent reads and writes" do - subject.set_basis({ FEATURES => { flag_key.to_sym => flag } }) + subject.set_basis({ LaunchDarkly::Interfaces::DataStore::FEATURES => { flag_key.to_sym => flag } }) threads = [] errors = [] @@ -315,7 +315,7 @@ module DataStore begin 10.times do |j| new_flag = flag.merge(key: "flag-#{i}-#{j}", version: j + 1) - subject.apply_delta({ FEATURES => { "flag-#{i}-#{j}".to_sym => new_flag } }) + subject.apply_delta({ LaunchDarkly::Interfaces::DataStore::FEATURES => { "flag-#{i}-#{j}".to_sym => new_flag } }) end rescue => e errors << e @@ -328,8 +328,8 @@ module DataStore threads << Thread.new do begin 20.times do - subject.get(FEATURES, flag_key) - subject.all(FEATURES) + subject.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) + subject.all(LaunchDarkly::Interfaces::DataStore::FEATURES) end rescue => e errors << e diff --git a/spec/impl/data_store/store_spec.rb b/spec/impl/data_store/store_spec.rb index 75cf6ff4..b8dcd54e 100644 --- a/spec/impl/data_store/store_spec.rb +++ b/spec/impl/data_store/store_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" require "ldclient-rb/impl/data_store/store" require "ldclient-rb/impl/broadcaster" -require "ldclient-rb/impl/data_store" +require "ldclient-rb/impl/data_store/status" require "ldclient-rb/interfaces/data_system" module LaunchDarkly @@ -45,8 +45,8 @@ class StubPersistentStore def initialize(should_fail: false) @data = { - LaunchDarkly::Impl::DataStore::FEATURES => {}, - LaunchDarkly::Impl::DataStore::SEGMENTS => {}, + LaunchDarkly::Interfaces::DataStore::FEATURES => {}, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => {}, } @initialized = false @init_called_count = 0 @@ -75,8 +75,8 @@ def init(all_data) @init_errors << error raise error end - @data[LaunchDarkly::Impl::DataStore::FEATURES] = (all_data[LaunchDarkly::Impl::DataStore::FEATURES] || {}).dup - @data[LaunchDarkly::Impl::DataStore::SEGMENTS] = (all_data[LaunchDarkly::Impl::DataStore::SEGMENTS] || {}).dup + @data[LaunchDarkly::Interfaces::DataStore::FEATURES] = (all_data[LaunchDarkly::Interfaces::DataStore::FEATURES] || {}).dup + @data[LaunchDarkly::Interfaces::DataStore::SEGMENTS] = (all_data[LaunchDarkly::Interfaces::DataStore::SEGMENTS] || {}).dup @initialized = true end @@ -171,7 +171,7 @@ def disable_cache expect(subject.initialized?).to be true # InMemoryFeatureStoreV2's get method handles both string and symbol keys - result = subject.get_active_store.get(FEATURES, flag_key) + result = subject.get_active_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) expect(result).not_to be_nil expect(result.key).to eq(flag_key.to_s) # key field is string end @@ -212,7 +212,7 @@ def disable_cache subject.apply(delta_change_set, false) - result = subject.get_active_store.get(FEATURES, flag_key) + result = subject.get_active_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) expect(result.version).to eq(2) expect(result.on).to be false end @@ -245,7 +245,7 @@ def disable_cache ) subject.apply(init_change_set, false) - expect(subject.get_active_store.get(FEATURES, flag_key)).not_to be_nil + expect(subject.get_active_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).not_to be_nil # Delete the flag delete_change = LaunchDarkly::Interfaces::DataSystem::Change.new( @@ -263,7 +263,7 @@ def disable_cache ) subject.apply(delete_change_set, false) - expect(subject.get_active_store.get(FEATURES, flag_key)).to be_nil + expect(subject.get_active_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key)).to be_nil end it "broadcasts changeset to listeners" do @@ -438,7 +438,7 @@ def changed_flags expect(persistent_store.init_called_count).to eq(1) # Verify data in persistent store - stored_flag = persistent_store.get(FEATURES, flag_key) + stored_flag = persistent_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) expect(stored_flag).not_to be_nil expect(stored_flag[:key]).to eq(flag_key.to_s) # key field is string end @@ -469,7 +469,7 @@ def changed_flags error = subject.commit expect(error).to be_nil - stored_flag = persistent_store.get(FEATURES, flag_key) + stored_flag = persistent_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) expect(stored_flag[:key]).to eq(flag_key.to_s) # key field is string expect(stored_flag[:version]).to eq(1) end @@ -558,7 +558,7 @@ def changed_flags subject.apply(change_set, true) expect(persistent_store.init_called_count).to be >= 1 - stored_flag = persistent_store.get(FEATURES, flag_key) + stored_flag = persistent_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) expect(stored_flag).not_to be_nil end @@ -641,7 +641,7 @@ def changed_flags expect(persistent_store.upsert_calls).not_to be_empty # Verify deleted flag was written - stored_flag = persistent_store.get(FEATURES, flag_key) + stored_flag = persistent_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag_key) expect(stored_flag).to be_nil # get returns nil for deleted items end end diff --git a/spec/impl/data_store_spec.rb b/spec/impl/data_store_spec.rb deleted file mode 100644 index 2a1a6789..00000000 --- a/spec/impl/data_store_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -require "spec_helper" - -module LaunchDarkly - module Impl - module DataStore - describe DataKind do - describe "eql?" do - it "constant instances are equal to themselves" do - expect(FEATURES.eql?(FEATURES)).to be true - expect(SEGMENTS.eql?(SEGMENTS)).to be true - end - - it "same constructions are equal" do - expect(FEATURES.eql?(DataKind.new(namespace: "features", priority: 1))).to be true - expect(DataKind.new(namespace: "features", priority: 1).eql?(DataKind.new(namespace: "features", priority: 1))).to be true - - expect(SEGMENTS.eql?(DataKind.new(namespace: "segments", priority: 0))).to be true - expect(DataKind.new(namespace: "segments", priority: 0).eql?(DataKind.new(namespace: "segments", priority: 0))).to be true - end - - it "distinct namespaces are not equal" do - expect(DataKind.new(namespace: "features", priority: 1).eql?(DataKind.new(namespace: "segments", priority: 1))).to be false - end - - it "distinct priorities are not equal" do - expect(DataKind.new(namespace: "features", priority: 1).eql?(DataKind.new(namespace: "features", priority: 2))).to be false - expect(DataKind.new(namespace: "segments", priority: 1).eql?(DataKind.new(namespace: "segments", priority: 2))).to be false - end - - it "handles non-DataKind objects" do - ["example", true, 1, 1.0, [], {}].each do |obj| - expect(FEATURES.eql?(obj)).to be false - end - end - end - - describe "hash" do - it "constant instances are equal to themselves" do - expect(FEATURES.hash).to eq FEATURES.hash - expect(SEGMENTS.hash).to eq SEGMENTS.hash - end - - it "same constructions are equal" do - expect(FEATURES.hash).to eq DataKind.new(namespace: "features", priority: 1).hash - expect(DataKind.new(namespace: "features", priority: 1).hash).to eq DataKind.new(namespace: "features", priority: 1).hash - - expect(SEGMENTS.hash).to eq DataKind.new(namespace: "segments", priority: 0).hash - expect(DataKind.new(namespace: "segments", priority: 0).hash).to eq DataKind.new(namespace: "segments", priority: 0).hash - end - - it "distinct namespaces are not equal" do - expect(DataKind.new(namespace: "features", priority: 1).hash).not_to eq DataKind.new(namespace: "segments", priority: 1).hash - end - - it "distinct priorities are not equal" do - expect(DataKind.new(namespace: "features", priority: 1).hash).not_to eq DataKind.new(namespace: "features", priority: 2).hash - expect(DataKind.new(namespace: "segments", priority: 1).hash).not_to eq DataKind.new(namespace: "segments", priority: 2).hash - end - end - end - end - end -end diff --git a/spec/impl/data_system/fdv2_datasystem_spec.rb b/spec/impl/data_system/fdv2_datasystem_spec.rb index b9bc4dd0..82d8e9ad 100644 --- a/spec/impl/data_system/fdv2_datasystem_spec.rb +++ b/spec/impl/data_system/fdv2_datasystem_spec.rb @@ -553,7 +553,7 @@ def with_data_system(initializers, synchronizers) # Verify FDv1 is serving data store = fdv2.store - flag = store.get(LaunchDarkly::Impl::DataStore::FEATURES, :fdv1flag) + flag = store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, :fdv1flag) expect(flag).not_to be_nil fdv2.stop diff --git a/spec/impl/data_system/fdv2_persistence_spec.rb b/spec/impl/data_system/fdv2_persistence_spec.rb index d629cd5b..d6807568 100644 --- a/spec/impl/data_system/fdv2_persistence_spec.rb +++ b/spec/impl/data_system/fdv2_persistence_spec.rb @@ -5,7 +5,7 @@ require "ldclient-rb/integrations/test_data_v2" require "ldclient-rb/data_system" require "ldclient-rb/impl/data_system" -require "ldclient-rb/impl/data_store" +require "ldclient-rb/impl/data_store/status" module LaunchDarkly module Impl @@ -31,8 +31,8 @@ class StubFeatureStore def initialize(initial_data = nil) @data = { - LaunchDarkly::Impl::DataStore::FEATURES => {}, - LaunchDarkly::Impl::DataStore::SEGMENTS => {}, + LaunchDarkly::Interfaces::DataStore::FEATURES => {}, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => {}, } @initialized = false @available = true @@ -46,8 +46,8 @@ def initialize(initial_data = nil) def init(all_data) @init_called_count += 1 if all_data - @data[LaunchDarkly::Impl::DataStore::FEATURES] = (all_data[LaunchDarkly::Impl::DataStore::FEATURES] || {}).dup - @data[LaunchDarkly::Impl::DataStore::SEGMENTS] = (all_data[LaunchDarkly::Impl::DataStore::SEGMENTS] || {}).dup + @data[LaunchDarkly::Interfaces::DataStore::FEATURES] = (all_data[LaunchDarkly::Interfaces::DataStore::FEATURES] || {}).dup + @data[LaunchDarkly::Interfaces::DataStore::SEGMENTS] = (all_data[LaunchDarkly::Interfaces::DataStore::SEGMENTS] || {}).dup end @initialized = true end @@ -109,8 +109,8 @@ def reset_operation_tracking def get_data_snapshot { - LaunchDarkly::Impl::DataStore::FEATURES => @data[LaunchDarkly::Impl::DataStore::FEATURES].dup, - LaunchDarkly::Impl::DataStore::SEGMENTS => @data[LaunchDarkly::Impl::DataStore::SEGMENTS].dup, + LaunchDarkly::Interfaces::DataStore::FEATURES => @data[LaunchDarkly::Interfaces::DataStore::FEATURES].dup, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => @data[LaunchDarkly::Interfaces::DataStore::SEGMENTS].dup, } end end @@ -135,8 +135,8 @@ def get_data_snapshot # Verify data from synchronizer is in the persistent store snapshot = persistent_store.get_data_snapshot - expect(snapshot[LaunchDarkly::Impl::DataStore::FEATURES]).to have_key(:flagkey) - expect(snapshot[LaunchDarkly::Impl::DataStore::FEATURES][:flagkey][:on]).to be true + expect(snapshot[LaunchDarkly::Interfaces::DataStore::FEATURES]).to have_key(:flagkey) + expect(snapshot[LaunchDarkly::Interfaces::DataStore::FEATURES][:flagkey][:on]).to be true # Reset tracking to isolate recovery behavior persistent_store.reset_operation_tracking @@ -166,8 +166,8 @@ def get_data_snapshot # Verify both flags are now in the persistent store snapshot = persistent_store.get_data_snapshot - expect(snapshot[LaunchDarkly::Impl::DataStore::FEATURES]).to have_key(:flagkey) - expect(snapshot[LaunchDarkly::Impl::DataStore::FEATURES]).to have_key(:newflag) + expect(snapshot[LaunchDarkly::Interfaces::DataStore::FEATURES]).to have_key(:flagkey) + expect(snapshot[LaunchDarkly::Interfaces::DataStore::FEATURES]).to have_key(:newflag) fdv2.stop end @@ -254,7 +254,7 @@ def get_data_snapshot # Verify the flag is in the persistent store snapshot = persistent_store.get_data_snapshot - expect(snapshot[LaunchDarkly::Impl::DataStore::FEATURES]).to have_key(:flagkey) + expect(snapshot[LaunchDarkly::Interfaces::DataStore::FEATURES]).to have_key(:flagkey) fdv2.stop end @@ -262,7 +262,7 @@ def get_data_snapshot it "works in READ_ONLY mode with persistent store" do # Pre-populate persistent store initial_data = { - LaunchDarkly::Impl::DataStore::FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { :existingflag => { key: "existingflag", version: 1, @@ -271,7 +271,7 @@ def get_data_snapshot fallthrough: { variation: 0 }, }, }, - LaunchDarkly::Impl::DataStore::SEGMENTS => {}, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => {}, } persistent_store = StubFeatureStore.new(initial_data) @@ -426,8 +426,8 @@ def get_data_snapshot # The synchronizer flag should be in the persistent store # (synchronizer does a full data set transfer, replacing initializer data) snapshot = persistent_store.get_data_snapshot - expect(snapshot[LaunchDarkly::Impl::DataStore::FEATURES]).not_to have_key(:"init-flag") - expect(snapshot[LaunchDarkly::Impl::DataStore::FEATURES]).to have_key(:"sync-flag") + expect(snapshot[LaunchDarkly::Interfaces::DataStore::FEATURES]).not_to have_key(:"init-flag") + expect(snapshot[LaunchDarkly::Interfaces::DataStore::FEATURES]).to have_key(:"sync-flag") fdv2.stop end diff --git a/spec/impl/evaluator_spec_base.rb b/spec/impl/evaluator_spec_base.rb index 17296fbe..62f3958f 100644 --- a/spec/impl/evaluator_spec_base.rb +++ b/spec/impl/evaluator_spec_base.rb @@ -17,7 +17,7 @@ def initialize(logger) end def with_flag(flag) - @flags[flag[:key]] = Model.deserialize(DataStore::FEATURES, flag) + @flags[flag[:key]] = Model.deserialize(LaunchDarkly::Interfaces::DataStore::FEATURES, flag) self end @@ -27,7 +27,7 @@ def with_unknown_flag(key) end def with_segment(segment) - @segments[segment[:key]] = Model.deserialize(DataStore::SEGMENTS, segment) + @segments[segment[:key]] = Model.deserialize(LaunchDarkly::Interfaces::DataStore::SEGMENTS, segment) self end diff --git a/spec/impl/model/preprocessed_data_spec.rb b/spec/impl/model/preprocessed_data_spec.rb index bda9c31d..654266f9 100644 --- a/spec/impl/model/preprocessed_data_spec.rb +++ b/spec/impl/model/preprocessed_data_spec.rb @@ -30,7 +30,7 @@ module DataModelPreprocessing ], } flag = Model::FeatureFlag.new(original_flag) - json = Model.serialize(DataStore::FEATURES, flag) + json = Model.serialize(LaunchDarkly::Interfaces::DataStore::FEATURES, flag) parsed = JSON.parse(json, symbolize_names: true) expect(parsed).to eq(original_flag) end diff --git a/spec/impl/model/serialization_spec.rb b/spec/impl/model/serialization_spec.rb index ca4ed8dd..1df0029b 100644 --- a/spec/impl/model/serialization_spec.rb +++ b/spec/impl/model/serialization_spec.rb @@ -7,34 +7,34 @@ module Model describe "model serialization" do it "serializes flag" do flag = FlagBuilder.new("flagkey").version(1).build - json = Model.serialize(Impl::DataStore::FEATURES, flag) + json = Model.serialize(LaunchDarkly::Interfaces::DataStore::FEATURES, flag) expect(JSON.parse(json, symbolize_names: true)).to eq flag.data end it "serializes segment" do segment = SegmentBuilder.new("segkey").version(1).build - json = Model.serialize(Impl::DataStore::SEGMENTS, segment) + json = Model.serialize(LaunchDarkly::Interfaces::DataStore::SEGMENTS, segment) expect(JSON.parse(json, symbolize_names: true)).to eq segment.data end it "deserializes flag with no rules or prerequisites" do flag_in = { key: "flagkey", version: 1 } json = flag_in.to_json - flag_out = Model.deserialize(Impl::DataStore::FEATURES, json, nil) + flag_out = Model.deserialize(LaunchDarkly::Interfaces::DataStore::FEATURES, json, nil) expect(flag_out.data).to eq flag_in end it "deserializes segment" do segment_in = { key: "segkey", version: 1 } json = segment_in.to_json - segment_out = Model.deserialize(Impl::DataStore::SEGMENTS, json, nil) + segment_out = Model.deserialize(LaunchDarkly::Interfaces::DataStore::SEGMENTS, json, nil) expect(segment_out.data).to eq segment_in end # Other LaunchDarkly SDKs write a deleted item to a persistent store with only a version, # and no key of its own. The store knows the key, because it is the key the item is stored # under, so a tombstone must deserialize without one. - [ Impl::DataStore::FEATURES, Impl::DataStore::SEGMENTS ].each do |kind| + [ LaunchDarkly::Interfaces::DataStore::FEATURES, LaunchDarkly::Interfaces::DataStore::SEGMENTS ].each do |kind| it "deserializes a tombstone with no key for #{kind[:namespace]}" do item_in = { version: 99, deleted: true } item_out = Model.deserialize(kind, item_in.to_json, nil) diff --git a/spec/integrations/dynamodb_stores_spec.rb b/spec/integrations/dynamodb_stores_spec.rb index 0b9a170c..bef0bfd6 100644 --- a/spec/integrations/dynamodb_stores_spec.rb +++ b/spec/integrations/dynamodb_stores_spec.rb @@ -197,29 +197,29 @@ def create_paging_store(page_size) it "reads every page in all" do ensure_stop(tester.create_feature_store) do |store| - store.init({ LaunchDarkly::Impl::DataStore::FEATURES => flags }) + store.init({ LaunchDarkly::Interfaces::DataStore::FEATURES => flags }) end store, queries = create_paging_store(2) ensure_stop(store) do - expect(store.all(LaunchDarkly::Impl::DataStore::FEATURES).keys).to match_array(flags.keys) + expect(store.all(LaunchDarkly::Interfaces::DataStore::FEATURES).keys).to match_array(flags.keys) end expect(queries.count { |q| q[:exclusive_start_key] }).to be >= 2 end it "reads every page of existing keys in init and deletes the keys that are not in the new data" do ensure_stop(tester.create_feature_store) do |store| - store.init({ LaunchDarkly::Impl::DataStore::FEATURES => flags }) + store.init({ LaunchDarkly::Interfaces::DataStore::FEATURES => flags }) end store, queries = create_paging_store(2) ensure_stop(store) do - store.init({ LaunchDarkly::Impl::DataStore::FEATURES => { flag1: flags[:flag1] } }) + store.init({ LaunchDarkly::Interfaces::DataStore::FEATURES => { flag1: flags[:flag1] } }) end expect(queries.count { |q| q[:exclusive_start_key] }).to be >= 2 ensure_stop(tester.create_feature_store) do |reader| - expect(reader.all(LaunchDarkly::Impl::DataStore::FEATURES).keys).to eq([:flag1]) + expect(reader.all(LaunchDarkly::Interfaces::DataStore::FEATURES).keys).to eq([:flag1]) end end end diff --git a/spec/integrations/file_data_source_spec.rb b/spec/integrations/file_data_source_spec.rb index 410d8e4e..d15f122f 100644 --- a/spec/integrations/file_data_source_spec.rb +++ b/spec/integrations/file_data_source_spec.rb @@ -146,8 +146,8 @@ def with_data_source(options, initialize_to_valid = false) file = make_temp_file('{"flagValues":{"key":"value"}}') with_data_source({ paths: [ file.path ] }) do |_| expect(@store.initialized?).to eq(false) - expect(@store.all(LaunchDarkly::Impl::DataStore::FEATURES)).to eq({}) - expect(@store.all(LaunchDarkly::Impl::DataStore::SEGMENTS)).to eq({}) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::FEATURES)).to eq({}) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS)).to eq({}) end end @@ -159,8 +159,8 @@ def with_data_source(options, initialize_to_valid = false) ds.start expect(@store.initialized?).to eq(true) - expect(@store.all(LaunchDarkly::Impl::DataStore::FEATURES).keys).to eq(all_flag_keys) - expect(@store.all(LaunchDarkly::Impl::DataStore::SEGMENTS).keys).to eq(all_segment_keys) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::FEATURES).keys).to eq(all_flag_keys) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).keys).to eq(all_segment_keys) expect(listener.statuses.count).to eq(1) expect(listener.statuses[0].state).to eq(LaunchDarkly::Interfaces::DataSource::Status::VALID) @@ -172,8 +172,8 @@ def with_data_source(options, initialize_to_valid = false) with_data_source({ paths: [ file.path ] }) do |ds| ds.start expect(@store.initialized?).to eq(true) - expect(@store.all(LaunchDarkly::Impl::DataStore::FEATURES).keys).to eq(all_flag_keys) - expect(@store.all(LaunchDarkly::Impl::DataStore::SEGMENTS).keys).to eq(all_segment_keys) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::FEATURES).keys).to eq(all_flag_keys) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).keys).to eq(all_segment_keys) end end @@ -242,8 +242,8 @@ def with_data_source(options, initialize_to_valid = false) with_data_source({ paths: [ file1.path, file2.path ] }) do |ds| ds.start expect(@store.initialized?).to eq(true) - expect(@store.all(LaunchDarkly::Impl::DataStore::FEATURES).keys).to eq([ full_flag_1_key.to_sym ]) - expect(@store.all(LaunchDarkly::Impl::DataStore::SEGMENTS).keys).to eq([ full_segment_1_key.to_sym ]) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::FEATURES).keys).to eq([ full_flag_1_key.to_sym ]) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).keys).to eq([ full_segment_1_key.to_sym ]) end end @@ -267,7 +267,7 @@ def with_data_source(options, initialize_to_valid = false) with_data_source({ paths: [ file1.path, file2.path ] }) do |ds| ds.start expect(@store.initialized?).to eq(false) - expect(@store.all(LaunchDarkly::Impl::DataStore::FEATURES).keys).to eq([]) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::FEATURES).keys).to eq([]) end end @@ -277,11 +277,11 @@ def with_data_source(options, initialize_to_valid = false) with_data_source({ paths: [ file.path ] }) do |ds| event = ds.start expect(event.set?).to eq(true) - expect(@store.all(LaunchDarkly::Impl::DataStore::SEGMENTS).keys).to eq([]) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).keys).to eq([]) IO.write(file, all_properties_json) sleep(0.5) - expect(@store.all(LaunchDarkly::Impl::DataStore::SEGMENTS).keys).to eq([]) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).keys).to eq([]) end end @@ -292,13 +292,13 @@ def test_auto_reload(options) with_data_source(options) do |ds| event = ds.start expect(event.set?).to eq(true) - expect(@store.all(LaunchDarkly::Impl::DataStore::SEGMENTS).keys).to eq([]) + expect(@store.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).keys).to eq([]) sleep(1) IO.write(file, all_properties_json) max_time = 10 - ok = wait_for_condition(10) { @store.all(LaunchDarkly::Impl::DataStore::SEGMENTS).keys == all_segment_keys } + ok = wait_for_condition(10) { @store.all(LaunchDarkly::Interfaces::DataStore::SEGMENTS).keys == all_segment_keys } expect(ok).to eq(true), "Waited #{max_time}s after modifying file and it did not reload" end end diff --git a/spec/integrations/redis_stores_spec.rb b/spec/integrations/redis_stores_spec.rb index dd38198b..117dc584 100644 --- a/spec/integrations/redis_stores_spec.rb +++ b/spec/integrations/redis_stores_spec.rb @@ -111,11 +111,11 @@ def make_concurrent_modifier_test_hook(other_client, flag, start_version, end_ve tester = RedisStoreTester.new({ test_hook: test_hook, logger: $null_logger }) ensure_stop(tester.create_feature_store) do |store| - store.init(LaunchDarkly::Impl::DataStore::FEATURES => { flag[:key] => flag }) + store.init(LaunchDarkly::Interfaces::DataStore::FEATURES => { flag[:key] => flag }) my_ver = { key: "foo", version: 10 } - store.upsert(LaunchDarkly::Impl::DataStore::FEATURES, my_ver) - result = store.get(LaunchDarkly::Impl::DataStore::FEATURES, flag[:key]) + store.upsert(LaunchDarkly::Interfaces::DataStore::FEATURES, my_ver) + result = store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag[:key]) expect(result[:version]).to eq 10 end end @@ -128,11 +128,11 @@ def make_concurrent_modifier_test_hook(other_client, flag, start_version, end_ve tester = RedisStoreTester.new({ test_hook: test_hook, logger: $null_logger }) ensure_stop(tester.create_feature_store) do |store| - store.init(LaunchDarkly::Impl::DataStore::FEATURES => { flag[:key] => flag }) + store.init(LaunchDarkly::Interfaces::DataStore::FEATURES => { flag[:key] => flag }) my_ver = { key: "foo", version: 2 } - store.upsert(LaunchDarkly::Impl::DataStore::FEATURES, my_ver) - result = store.get(LaunchDarkly::Impl::DataStore::FEATURES, flag[:key]) + store.upsert(LaunchDarkly::Interfaces::DataStore::FEATURES, my_ver) + result = store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, flag[:key]) expect(result[:version]).to eq 3 end end @@ -144,7 +144,7 @@ def make_concurrent_modifier_test_hook(other_client, flag, start_version, end_ve store = tester.create_feature_store begin - store.init(LaunchDarkly::Impl::DataStore::FEATURES => { }) + store.init(LaunchDarkly::Interfaces::DataStore::FEATURES => { }) store.stop expect { unowned_pool.with {} }.to raise_error(ConnectionPool::PoolShuttingDownError) @@ -159,7 +159,7 @@ def make_concurrent_modifier_test_hook(other_client, flag, start_version, end_ve store = tester.create_feature_store begin - store.init(LaunchDarkly::Impl::DataStore::FEATURES => { }) + store.init(LaunchDarkly::Interfaces::DataStore::FEATURES => { }) store.stop expect { unowned_pool.with {} }.not_to raise_error diff --git a/spec/integrations/test_data_spec.rb b/spec/integrations/test_data_spec.rb index 3c28fd4e..d4d323a5 100644 --- a/spec/integrations/test_data_spec.rb +++ b/spec/integrations/test_data_spec.rb @@ -7,7 +7,7 @@ module Integrations td = Integrations::TestData.data_source config = Config.new(send_events: false, data_source: td) client = LDClient.new('sdkKey', config) - expect(config.feature_store.all(LaunchDarkly::Impl::DataStore::FEATURES)).to eql({}) + expect(config.feature_store.all(LaunchDarkly::Interfaces::DataStore::FEATURES)).to eql({}) client.close end @@ -16,7 +16,7 @@ module Integrations td.update(td.flag('flag')) config = Config.new(send_events: false, data_source: td) client = LDClient.new('sdkKey', config) - expect(config.feature_store.get(LaunchDarkly::Impl::DataStore::FEATURES, 'flag').data).to eql({ + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, 'flag').data).to eql({ key: 'flag', variations: [true, false], fallthrough: { variation: 0 }, @@ -35,7 +35,7 @@ module Integrations config2 = Config.new(send_events: false, data_source: td) client2 = LDClient.new('sdkKey', config2) - expect(config.feature_store.get(LaunchDarkly::Impl::DataStore::FEATURES, 'flag').data).to eql({ + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, 'flag').data).to eql({ key: 'flag', variations: [true, false], fallthrough: { variation: 0 }, @@ -43,7 +43,7 @@ module Integrations on: true, version: 1, }) - expect(config2.feature_store.get(LaunchDarkly::Impl::DataStore::FEATURES, 'flag').data).to eql({ + expect(config2.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, 'flag').data).to eql({ key: 'flag', variations: [true, false], fallthrough: { variation: 0 }, @@ -54,7 +54,7 @@ module Integrations td.update(td.flag('flag').variation_for_all(false)) - expect(config.feature_store.get(LaunchDarkly::Impl::DataStore::FEATURES, 'flag').data).to eql({ + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, 'flag').data).to eql({ key: 'flag', variations: [true, false], fallthrough: { variation: 1 }, @@ -62,7 +62,7 @@ module Integrations on: true, version: 2, }) - expect(config2.feature_store.get(LaunchDarkly::Impl::DataStore::FEATURES, 'flag').data).to eql({ + expect(config2.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, 'flag').data).to eql({ key: 'flag', variations: [true, false], fallthrough: { variation: 1 }, @@ -83,22 +83,22 @@ module Integrations config = Config.new(send_events: false, data_source: td) client = LDClient.new('sdkKey', config) - expect(config.feature_store.get(LaunchDarkly::Impl::DataStore::FEATURES, 'my-flag').data).to eql({ + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, 'my-flag').data).to eql({ key: 'my-flag', version: 1000, on: true }) - expect(config.feature_store.get(LaunchDarkly::Impl::DataStore::SEGMENTS, 'my-segment').data).to eql({ + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, 'my-segment').data).to eql({ key: 'my-segment', version: 2000 }) td.use_preconfigured_flag({ key: 'my-flag', on: false }) - expect(config.feature_store.get(LaunchDarkly::Impl::DataStore::FEATURES, 'my-flag').data).to eql({ + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::FEATURES, 'my-flag').data).to eql({ key: 'my-flag', version: 1001, on: false }) td.use_preconfigured_segment({ key: 'my-segment', included: [ 'x' ] }) - expect(config.feature_store.get(LaunchDarkly::Impl::DataStore::SEGMENTS, 'my-segment').data).to eql({ + expect(config.feature_store.get(LaunchDarkly::Interfaces::DataStore::SEGMENTS, 'my-segment').data).to eql({ key: 'my-segment', version: 2001, included: [ 'x' ] }) diff --git a/spec/interfaces/data_store_spec.rb b/spec/interfaces/data_store_spec.rb new file mode 100644 index 00000000..7cce5557 --- /dev/null +++ b/spec/interfaces/data_store_spec.rb @@ -0,0 +1,155 @@ +require "spec_helper" +require "open3" + +module LaunchDarkly + module Interfaces + module DataStore + describe DataKind do + describe "serialize and deserialize" do + it "round-trips a feature flag" do + json = '{"key":"flag","version":3,"on":true}' + flag = FEATURES.deserialize(json) + expect(flag).to be_a(LaunchDarkly::Impl::Model::FeatureFlag) + expect(flag[:version]).to eq 3 + expect(JSON.parse(FEATURES.serialize(flag))).to eq JSON.parse(json) + end + + it "round-trips a segment" do + json = '{"key":"segment","version":2,"included":["a"]}' + segment = SEGMENTS.deserialize(json) + expect(segment).to be_a(LaunchDarkly::Impl::Model::Segment) + expect(segment[:version]).to eq 2 + expect(JSON.parse(SEGMENTS.serialize(segment))).to eq JSON.parse(json) + end + + it "round-trips a deleted item placeholder" do + json = '{"key":"flag","version":4,"deleted":true}' + item = FEATURES.deserialize(json) + expect(item[:deleted]).to be true + expect(item[:version]).to eq 4 + expect(JSON.parse(FEATURES.serialize(item))).to eq JSON.parse(json) + end + + it "deserializes nil to nil" do + expect(FEATURES.deserialize(nil)).to be_nil + end + + it "works when the application requires only this file" do + script = <<~RUBY + require "ldclient-rb/interfaces/data_store" + kinds = LaunchDarkly::Interfaces::DataStore + flag = { key: "f", version: 1, on: true, variations: [true, false], offVariation: 1, + fallthrough: { variation: 0 }, targets: [{ values: ["a"], variation: 0 }], + contextTargets: [{ contextKind: "org", values: ["b"], variation: 1 }], + prerequisites: [{ key: "p", variation: 0 }], + rules: [{ id: "r", clauses: [{ attribute: "name", op: "in", values: ["x"] }], variation: 0 }] } + segment = { key: "s", version: 1, included: ["a"], + includedContexts: [{ contextKind: "org", values: ["b"] }], + rules: [{ id: "r", clauses: [{ contextKind: "user", attribute: "name", op: "in", values: ["x"] }] }] } + puts kinds::FEATURES.deserialize(kinds::FEATURES.serialize(flag)).class + puts kinds::SEGMENTS.deserialize(kinds::SEGMENTS.serialize(segment)).class + RUBY + lib = File.expand_path("../../lib", __dir__) + out, status = Open3.capture2e(RbConfig.ruby, "-I", lib, "-e", script) + expect(status.success?).to be(true), out + expect(out.split("\n")).to eq ["LaunchDarkly::Impl::Model::FeatureFlag", "LaunchDarkly::Impl::Model::Segment"] + end + end + + describe "eql?" do + it "constant instances are equal to themselves" do + expect(FEATURES.eql?(FEATURES)).to be true + expect(SEGMENTS.eql?(SEGMENTS)).to be true + end + + it "same constructions are equal" do + expect(FEATURES.eql?(DataKind.new(namespace: "features", priority: 1))).to be true + expect(DataKind.new(namespace: "features", priority: 1).eql?(DataKind.new(namespace: "features", priority: 1))).to be true + + expect(SEGMENTS.eql?(DataKind.new(namespace: "segments", priority: 0))).to be true + expect(DataKind.new(namespace: "segments", priority: 0).eql?(DataKind.new(namespace: "segments", priority: 0))).to be true + end + + it "distinct namespaces are not equal" do + expect(DataKind.new(namespace: "features", priority: 1).eql?(DataKind.new(namespace: "segments", priority: 1))).to be false + end + + it "distinct priorities are not equal" do + expect(DataKind.new(namespace: "features", priority: 1).eql?(DataKind.new(namespace: "features", priority: 2))).to be false + expect(DataKind.new(namespace: "segments", priority: 1).eql?(DataKind.new(namespace: "segments", priority: 2))).to be false + end + + it "handles non-DataKind objects" do + ["example", true, 1, 1.0, [], {}].each do |obj| + expect(FEATURES.eql?(obj)).to be false + end + end + end + + describe "==" do + it "same constructions are equal" do + expect(DataKind.new(namespace: "features", priority: 1)).to eq FEATURES + expect(DataKind.new(namespace: "segments", priority: 0)).to eq SEGMENTS + end + + it "a kind that went through Marshal is equal" do + expect(Marshal.load(Marshal.dump(FEATURES))).to eq FEATURES + end + + it "distinct kinds are not equal" do + expect(DataKind.new(namespace: "features", priority: 2)).not_to eq FEATURES + expect(FEATURES).not_to eq SEGMENTS + expect(FEATURES).not_to eq "features" + end + + it "ALL_KINDS includes an equal kind" do + expect(ALL_KINDS).to include(DataKind.new(namespace: "segments", priority: 0)) + end + + it "an equal kind deserializes to the model class" do + kind = DataKind.new(namespace: "features", priority: 1) + expect(kind.deserialize('{"key":"flag","version":1}')).to be_a(LaunchDarkly::Impl::Model::FeatureFlag) + end + end + + describe "hash" do + it "constant instances are equal to themselves" do + expect(FEATURES.hash).to eq FEATURES.hash + expect(SEGMENTS.hash).to eq SEGMENTS.hash + end + + it "same constructions are equal" do + expect(FEATURES.hash).to eq DataKind.new(namespace: "features", priority: 1).hash + expect(DataKind.new(namespace: "features", priority: 1).hash).to eq DataKind.new(namespace: "features", priority: 1).hash + + expect(SEGMENTS.hash).to eq DataKind.new(namespace: "segments", priority: 0).hash + expect(DataKind.new(namespace: "segments", priority: 0).hash).to eq DataKind.new(namespace: "segments", priority: 0).hash + end + + it "distinct namespaces are not equal" do + expect(DataKind.new(namespace: "features", priority: 1).hash).not_to eq DataKind.new(namespace: "segments", priority: 1).hash + end + + it "distinct priorities are not equal" do + expect(DataKind.new(namespace: "features", priority: 1).hash).not_to eq DataKind.new(namespace: "features", priority: 2).hash + expect(DataKind.new(namespace: "segments", priority: 1).hash).not_to eq DataKind.new(namespace: "segments", priority: 2).hash + end + end + end + + describe SerializedItemDescriptor do + it "keeps its values" do + descriptor = SerializedItemDescriptor.new(version: 5, deleted: false, serialized_item: "{}") + expect(descriptor.version).to eq 5 + expect(descriptor.deleted).to be false + expect(descriptor.serialized_item).to eq "{}" + end + + it "compares by value" do + expect(SerializedItemDescriptor.new(version: 1, deleted: true, serialized_item: nil)) + .to eq SerializedItemDescriptor.new(version: 1, deleted: true, serialized_item: nil) + end + end + end + end +end diff --git a/spec/ldclient_cached_data_warning_spec.rb b/spec/ldclient_cached_data_warning_spec.rb index de0c983b..ec0ecdaa 100644 --- a/spec/ldclient_cached_data_warning_spec.rb +++ b/spec/ldclient_cached_data_warning_spec.rb @@ -21,7 +21,7 @@ def uninitialized_source def initialized_store store = InMemoryFeatureStore.new - store.init({ Impl::DataStore::FEATURES => {}, Impl::DataStore::SEGMENTS => {} }) + store.init({ LaunchDarkly::Interfaces::DataStore::FEATURES => {}, LaunchDarkly::Interfaces::DataStore::SEGMENTS => {} }) store end diff --git a/spec/ldclient_spec.rb b/spec/ldclient_spec.rb index a97f8371..dc4906cb 100644 --- a/spec/ldclient_spec.rb +++ b/spec/ldclient_spec.rb @@ -74,7 +74,7 @@ module LaunchDarkly context "feature store data ordering" do let(:dependency_ordering_test_data) { { - Impl::DataStore::FEATURES => { + LaunchDarkly::Interfaces::DataStore::FEATURES => { a: { key: "a", prerequisites: [ { key: "b" }, { key: "c" } ] }, b: { key: "b", prerequisites: [ { key: "c" }, { key: "e" } ] }, c: { key: "c" }, @@ -82,7 +82,7 @@ module LaunchDarkly e: { key: "e" }, f: { key: "f" }, }, - Impl::DataStore::SEGMENTS => { + LaunchDarkly::Interfaces::DataStore::SEGMENTS => { o: { key: "o" }, }, } @@ -91,8 +91,8 @@ module LaunchDarkly it "passes data set to feature store in correct order on init" do store = CapturingFeatureStore.new td = Integrations::TestData.data_source - dependency_ordering_test_data[Impl::DataStore::FEATURES].each { |_, flag| td.use_preconfigured_flag(flag) } - dependency_ordering_test_data[Impl::DataStore::SEGMENTS].each { |_, segment| td.use_preconfigured_segment(segment) } + dependency_ordering_test_data[LaunchDarkly::Interfaces::DataStore::FEATURES].each { |_, flag| td.use_preconfigured_flag(flag) } + dependency_ordering_test_data[LaunchDarkly::Interfaces::DataStore::SEGMENTS].each { |_, segment| td.use_preconfigured_segment(segment) } with_client(test_config(feature_store: store, data_source: td)) do |_| data = store.received_data @@ -100,14 +100,14 @@ module LaunchDarkly expect(data.count).to eq(2) # Segments should always come first - expect(data.keys[0]).to be(Impl::DataStore::SEGMENTS) - expect(data.values[0].count).to eq(dependency_ordering_test_data[Impl::DataStore::SEGMENTS].count) + expect(data.keys[0]).to be(LaunchDarkly::Interfaces::DataStore::SEGMENTS) + expect(data.values[0].count).to eq(dependency_ordering_test_data[LaunchDarkly::Interfaces::DataStore::SEGMENTS].count) # Features should be ordered so that a flag always appears after its prerequisites, if any - expect(data.keys[1]).to be(Impl::DataStore::FEATURES) + expect(data.keys[1]).to be(LaunchDarkly::Interfaces::DataStore::FEATURES) flags_map = data.values[1] flags_list = flags_map.values - expect(flags_list.count).to eq(dependency_ordering_test_data[Impl::DataStore::FEATURES].count) + expect(flags_list.count).to eq(dependency_ordering_test_data[LaunchDarkly::Interfaces::DataStore::FEATURES].count) flags_list.each_with_index do |item, item_index| (item[:prerequisites] || []).each do |prereq| prereq = flags_map[prereq[:key].to_sym] diff --git a/spec/model_builders.rb b/spec/model_builders.rb index fd0114f3..58cf15b8 100644 --- a/spec/model_builders.rb +++ b/spec/model_builders.rb @@ -4,7 +4,7 @@ class Flags def self.from_hash(data) - LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Impl::DataStore::FEATURES, data) + LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Interfaces::DataStore::FEATURES, data) end def self.boolean_flag_with_rules(*rules) @@ -20,7 +20,7 @@ def self.boolean_flag_with_clauses(*clauses) class Segments def self.from_hash(data) - LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Impl::DataStore::SEGMENTS, data) + LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Interfaces::DataStore::SEGMENTS, data) end end @@ -226,21 +226,21 @@ def initialize end def flag(data) - f = LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Impl::DataStore::FEATURES, data) + f = LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Interfaces::DataStore::FEATURES, data) @flags[f.key.to_sym] = f self end def segment(data) - s = LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Impl::DataStore::SEGMENTS, data) + s = LaunchDarkly::Impl::Model.deserialize(LaunchDarkly::Interfaces::DataStore::SEGMENTS, data) @segments[s.key.to_sym] = s self end def to_store_data { - LaunchDarkly::Impl::DataStore::FEATURES => @flags, - LaunchDarkly::Impl::DataStore::SEGMENTS => @segments, + LaunchDarkly::Interfaces::DataStore::FEATURES => @flags, + LaunchDarkly::Interfaces::DataStore::SEGMENTS => @segments, } end