@@ -20,6 +20,28 @@ module DataStore
2020 }
2121 end
2222
23+ let ( :segment_key ) { "test-segment" }
24+ let ( :segment ) do
25+ {
26+ key : segment_key ,
27+ version : 1 ,
28+ included : [ "user1" ] ,
29+ excluded : [ ] ,
30+ rules : [ ] ,
31+ }
32+ end
33+
34+ describe "#initialized?" do
35+ it "returns false before initialization" do
36+ expect ( subject . initialized? ) . to be false
37+ end
38+
39+ it "returns true after set_basis" do
40+ subject . set_basis ( { FEATURES => { } } )
41+ expect ( subject . initialized? ) . to be true
42+ end
43+ end
44+
2345 describe "#get with string/symbol key compatibility" do
2446 before do
2547 # Store items with symbol keys (as done by FDv2 protocol layer)
@@ -44,6 +66,280 @@ module DataStore
4466 it "returns nil for non-existent keys" do
4567 expect ( subject . get ( FEATURES , "nonexistent" ) ) . to be_nil
4668 end
69+
70+ it "returns nil for deleted items" do
71+ deleted_flag = flag . merge ( deleted : true )
72+ collections = { FEATURES => { flag_key . to_sym => deleted_flag } }
73+ subject . set_basis ( collections )
74+ expect ( subject . get ( FEATURES , flag_key ) ) . to be_nil
75+ end
76+ end
77+
78+ describe "#all" do
79+ it "returns empty hash when no data" do
80+ expect ( subject . all ( FEATURES ) ) . to eq ( { } )
81+ end
82+
83+ it "returns all non-deleted items" do
84+ collections = {
85+ FEATURES => {
86+ flag_key . to_sym => flag ,
87+ "deleted-flag" . to_sym => flag . merge ( key : "deleted-flag" , deleted : true ) ,
88+ } ,
89+ }
90+ subject . set_basis ( collections )
91+
92+ result = subject . all ( FEATURES )
93+ expect ( result . keys ) . to contain_exactly ( flag_key . to_sym )
94+ expect ( result [ flag_key . to_sym ] . key ) . to eq ( flag_key )
95+ end
96+
97+ it "returns items for both flags and segments" do
98+ collections = {
99+ FEATURES => { flag_key . to_sym => flag } ,
100+ SEGMENTS => { segment_key . to_sym => segment } ,
101+ }
102+ subject . set_basis ( collections )
103+
104+ expect ( subject . all ( FEATURES ) . keys ) . to contain_exactly ( flag_key . to_sym )
105+ expect ( subject . all ( SEGMENTS ) . keys ) . to contain_exactly ( segment_key . to_sym )
106+ end
107+ end
108+
109+ describe "#set_basis" do
110+ it "initializes the store with valid data" do
111+ collections = {
112+ FEATURES => { flag_key . to_sym => flag } ,
113+ SEGMENTS => { segment_key . to_sym => segment } ,
114+ }
115+
116+ result = subject . set_basis ( collections )
117+ expect ( result ) . to be true
118+ expect ( subject . initialized? ) . to be true
119+ expect ( subject . get ( FEATURES , flag_key ) ) . not_to be_nil
120+ expect ( subject . get ( SEGMENTS , segment_key ) ) . not_to be_nil
121+ end
122+
123+ it "replaces existing data" do
124+ # Set initial data
125+ initial_collections = {
126+ FEATURES => { flag_key . to_sym => flag } ,
127+ }
128+ subject . set_basis ( initial_collections )
129+
130+ # Replace with new data
131+ new_flag = flag . merge ( key : "new-flag" , version : 2 )
132+ new_collections = {
133+ FEATURES => { "new-flag" . to_sym => new_flag } ,
134+ }
135+ result = subject . set_basis ( new_collections )
136+
137+ expect ( result ) . to be true
138+ expect ( subject . get ( FEATURES , flag_key ) ) . to be_nil # Old flag gone
139+ expect ( subject . get ( FEATURES , "new-flag" ) ) . not_to be_nil
140+ end
141+
142+ it "clears all data before setting new data" do
143+ subject . set_basis ( {
144+ FEATURES => { flag_key . to_sym => flag } ,
145+ SEGMENTS => { segment_key . to_sym => segment } ,
146+ } )
147+
148+ # Replace with data that only has flags
149+ new_collections = {
150+ FEATURES => { "new-flag" . to_sym => flag . merge ( key : "new-flag" ) } ,
151+ SEGMENTS => { } ,
152+ }
153+ subject . set_basis ( new_collections )
154+
155+ expect ( subject . all ( SEGMENTS ) ) . to be_empty
156+ end
157+
158+ it "handles multiple flags and segments" do
159+ flag1 = flag . merge ( key : "flag-1" )
160+ flag2 = flag . merge ( key : "flag-2" , version : 2 )
161+ flag3 = flag . merge ( key : "flag-3" , version : 3 )
162+
163+ segment1 = segment . merge ( key : "segment-1" )
164+ segment2 = segment . merge ( key : "segment-2" , version : 2 )
165+
166+ collections = {
167+ FEATURES => {
168+ "flag-1" . to_sym => flag1 ,
169+ "flag-2" . to_sym => flag2 ,
170+ "flag-3" . to_sym => flag3 ,
171+ } ,
172+ SEGMENTS => {
173+ "segment-1" . to_sym => segment1 ,
174+ "segment-2" . to_sym => segment2 ,
175+ } ,
176+ }
177+
178+ result = subject . set_basis ( collections )
179+ expect ( result ) . to be true
180+
181+ expect ( subject . all ( FEATURES ) . size ) . to eq ( 3 )
182+ expect ( subject . all ( SEGMENTS ) . size ) . to eq ( 2 )
183+ end
184+
185+ it "returns false and logs error on deserialization failure" do
186+ allow ( LaunchDarkly ::Impl ::Model ) . to receive ( :deserialize ) . and_raise ( StandardError . new ( "test error" ) )
187+
188+ collections = { FEATURES => { flag_key . to_sym => flag } }
189+ result = subject . set_basis ( collections )
190+
191+ expect ( result ) . to be false
192+ expect ( subject . initialized? ) . to be false
193+ end
194+
195+ it "handles empty collections" do
196+ result = subject . set_basis ( { FEATURES => { } , SEGMENTS => { } } )
197+ expect ( result ) . to be true
198+ expect ( subject . initialized? ) . to be true
199+ end
200+ end
201+
202+ describe "#apply_delta" do
203+ before do
204+ # Set initial data
205+ collections = {
206+ FEATURES => { flag_key . to_sym => flag } ,
207+ SEGMENTS => { segment_key . to_sym => segment } ,
208+ }
209+ subject . set_basis ( collections )
210+ end
211+
212+ it "adds new items without clearing existing data" do
213+ new_flag = flag . merge ( key : "new-flag" , version : 2 )
214+ delta = {
215+ FEATURES => { "new-flag" . to_sym => new_flag } ,
216+ }
217+
218+ result = subject . apply_delta ( delta )
219+ expect ( result ) . to be true
220+
221+ # Original flag should still exist
222+ expect ( subject . get ( FEATURES , flag_key ) ) . not_to be_nil
223+ # New flag should be added
224+ expect ( subject . get ( FEATURES , "new-flag" ) ) . not_to be_nil
225+ # Segment should be unchanged
226+ expect ( subject . get ( SEGMENTS , segment_key ) ) . not_to be_nil
227+ end
228+
229+ it "updates existing items" do
230+ updated_flag = flag . merge ( version : 2 , on : false )
231+ delta = {
232+ FEATURES => { flag_key . to_sym => updated_flag } ,
233+ }
234+
235+ result = subject . apply_delta ( delta )
236+ expect ( result ) . to be true
237+
238+ result = subject . get ( FEATURES , flag_key )
239+ expect ( result . version ) . to eq ( 2 )
240+ expect ( result . on ) . to be false
241+ end
242+
243+ it "handles multiple updates in one delta" do
244+ flag2 = flag . merge ( key : "flag-2" , version : 2 )
245+ flag3 = flag . merge ( key : "flag-3" , version : 3 )
246+ segment2 = segment . merge ( key : "segment-2" , version : 2 )
247+
248+ delta = {
249+ FEATURES => {
250+ "flag-2" . to_sym => flag2 ,
251+ "flag-3" . to_sym => flag3 ,
252+ } ,
253+ SEGMENTS => {
254+ "segment-2" . to_sym => segment2 ,
255+ } ,
256+ }
257+
258+ result = subject . apply_delta ( delta )
259+ expect ( result ) . to be true
260+
261+ # Original items unchanged
262+ expect ( subject . get ( FEATURES , flag_key ) ) . not_to be_nil
263+ expect ( subject . get ( SEGMENTS , segment_key ) ) . not_to be_nil
264+
265+ # New items added
266+ expect ( subject . get ( FEATURES , "flag-2" ) ) . not_to be_nil
267+ expect ( subject . get ( FEATURES , "flag-3" ) ) . not_to be_nil
268+ expect ( subject . get ( SEGMENTS , "segment-2" ) ) . not_to be_nil
269+ end
270+
271+ it "handles delete operations" do
272+ deleted_flag = { key : flag_key , version : 2 , deleted : true }
273+ delta = {
274+ FEATURES => { flag_key . to_sym => deleted_flag } ,
275+ }
276+
277+ result = subject . apply_delta ( delta )
278+ expect ( result ) . to be true
279+
280+ # Deleted flag should return nil
281+ expect ( subject . get ( FEATURES , flag_key ) ) . to be_nil
282+ end
283+
284+ it "returns false and logs error on deserialization failure" do
285+ allow ( LaunchDarkly ::Impl ::Model ) . to receive ( :deserialize ) . and_raise ( StandardError . new ( "test error" ) )
286+
287+ delta = { FEATURES => { "new-flag" . to_sym => flag } }
288+ result = subject . apply_delta ( delta )
289+
290+ expect ( result ) . to be false
291+ # Original data should be intact
292+ expect ( subject . get ( FEATURES , flag_key ) ) . not_to be_nil
293+ end
294+
295+ it "handles empty delta" do
296+ result = subject . apply_delta ( { FEATURES => { } , SEGMENTS => { } } )
297+ expect ( result ) . to be true
298+
299+ # Original data unchanged
300+ expect ( subject . get ( FEATURES , flag_key ) ) . not_to be_nil
301+ expect ( subject . get ( SEGMENTS , segment_key ) ) . not_to be_nil
302+ end
303+ end
304+
305+ describe "thread safety" do
306+ it "handles concurrent reads and writes" do
307+ subject . set_basis ( { FEATURES => { flag_key . to_sym => flag } } )
308+
309+ threads = [ ]
310+ errors = [ ]
311+
312+ # Writer threads
313+ 5 . times do |i |
314+ threads << Thread . new do
315+ begin
316+ 10 . times do |j |
317+ new_flag = flag . merge ( key : "flag-#{ i } -#{ j } " , version : j + 1 )
318+ subject . apply_delta ( { FEATURES => { "flag-#{ i } -#{ j } " . to_sym => new_flag } } )
319+ end
320+ rescue => e
321+ errors << e
322+ end
323+ end
324+ end
325+
326+ # Reader threads
327+ 5 . times do
328+ threads << Thread . new do
329+ begin
330+ 20 . times do
331+ subject . get ( FEATURES , flag_key )
332+ subject . all ( FEATURES )
333+ end
334+ rescue => e
335+ errors << e
336+ end
337+ end
338+ end
339+
340+ threads . each ( &:join )
341+ expect ( errors ) . to be_empty
342+ end
47343 end
48344 end
49345 end
0 commit comments