...
1 package ldtestdata
2
3 import (
4 "sync"
5
6 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
7 "github.com/launchdarkly/go-server-sdk/v6/interfaces"
8 "github.com/launchdarkly/go-server-sdk/v6/subsystems"
9 "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoreimpl"
10 "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
11
12 "golang.org/x/exp/slices"
13 )
14
15
16
17
18
19 type TestDataSource struct {
20 currentFlags map[string]ldstoretypes.ItemDescriptor
21 currentBuilders map[string]*FlagBuilder
22 currentSegments map[string]ldstoretypes.ItemDescriptor
23 instances []*testDataSourceImpl
24 lock sync.Mutex
25 }
26
27 type testDataSourceImpl struct {
28 owner *TestDataSource
29 updates subsystems.DataSourceUpdateSink
30 }
31
32
33
34
35
36
37 func DataSource() *TestDataSource {
38 return &TestDataSource{
39 currentFlags: make(map[string]ldstoretypes.ItemDescriptor),
40 currentBuilders: make(map[string]*FlagBuilder),
41 currentSegments: make(map[string]ldstoretypes.ItemDescriptor),
42 }
43 }
44
45
46
47
48
49
50
51
52
53
54
55
56 func (t *TestDataSource) Flag(key string) *FlagBuilder {
57 t.lock.Lock()
58 defer t.lock.Unlock()
59 existingBuilder := t.currentBuilders[key]
60 if existingBuilder == nil {
61 return newFlagBuilder(key).BooleanFlag()
62 }
63 return copyFlagBuilder(existingBuilder)
64 }
65
66
67
68
69
70
71
72
73
74
75
76 func (t *TestDataSource) Update(flagBuilder *FlagBuilder) *TestDataSource {
77 key := flagBuilder.key
78 clonedBuilder := copyFlagBuilder(flagBuilder)
79 t.updateInternal(key, flagBuilder.createFlag, clonedBuilder)
80 return t
81 }
82
83
84
85
86
87
88
89
90 func (t *TestDataSource) UpdateStatus(
91 newState interfaces.DataSourceState,
92 newError interfaces.DataSourceErrorInfo,
93 ) *TestDataSource {
94 t.lock.Lock()
95 instances := slices.Clone(t.instances)
96 t.lock.Unlock()
97
98 for _, instance := range instances {
99 instance.updates.UpdateStatus(newState, newError)
100 }
101
102 return t
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 func (t *TestDataSource) UsePreconfiguredFlag(flag ldmodel.FeatureFlag) *TestDataSource {
122 t.updateInternal(
123 flag.Key,
124 func(version int) ldmodel.FeatureFlag {
125 f := flag
126 if f.Version < version {
127 f.Version = version
128 }
129 return f
130 },
131 nil,
132 )
133 return t
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 func (t *TestDataSource) UsePreconfiguredSegment(segment ldmodel.Segment) *TestDataSource {
151 t.lock.Lock()
152 oldItem := t.currentSegments[segment.Key]
153 newSegment := segment
154 newSegment.Version = oldItem.Version + 1
155 newItem := ldstoretypes.ItemDescriptor{Version: newSegment.Version, Item: &newSegment}
156 t.currentSegments[segment.Key] = newItem
157 instances := slices.Clone(t.instances)
158 t.lock.Unlock()
159
160 for _, instance := range instances {
161 instance.updates.Upsert(ldstoreimpl.Segments(), segment.Key, newItem)
162 }
163
164 return t
165 }
166
167 func (t *TestDataSource) updateInternal(
168 key string,
169 makeFlag func(int) ldmodel.FeatureFlag,
170 builder *FlagBuilder,
171 ) {
172 t.lock.Lock()
173 oldItem := t.currentFlags[key]
174 newVersion := oldItem.Version + 1
175 newFlag := makeFlag(newVersion)
176 newItem := ldstoretypes.ItemDescriptor{Version: newVersion, Item: &newFlag}
177 t.currentFlags[key] = newItem
178 t.currentBuilders[key] = builder
179 instances := slices.Clone(t.instances)
180 t.lock.Unlock()
181
182 for _, instance := range instances {
183 instance.updates.Upsert(ldstoreimpl.Features(), key, newItem)
184 }
185 }
186
187
188
189 func (t *TestDataSource) Build(context subsystems.ClientContext) (subsystems.DataSource, error) {
190 instance := &testDataSourceImpl{owner: t, updates: context.GetDataSourceUpdateSink()}
191 t.lock.Lock()
192 t.instances = append(t.instances, instance)
193 t.lock.Unlock()
194 return instance, nil
195 }
196
197 func (t *TestDataSource) makeInitData() []ldstoretypes.Collection {
198 t.lock.Lock()
199 defer t.lock.Unlock()
200 flags := make([]ldstoretypes.KeyedItemDescriptor, 0, len(t.currentFlags))
201 segments := make([]ldstoretypes.KeyedItemDescriptor, 0, len(t.currentSegments))
202 for key, item := range t.currentFlags {
203 flags = append(flags, ldstoretypes.KeyedItemDescriptor{Key: key, Item: item})
204 }
205 for key, item := range t.currentSegments {
206 segments = append(segments, ldstoretypes.KeyedItemDescriptor{Key: key, Item: item})
207 }
208 return []ldstoretypes.Collection{
209 {Kind: ldstoreimpl.Features(), Items: flags},
210 {Kind: ldstoreimpl.Segments(), Items: segments},
211 }
212 }
213
214 func (t *TestDataSource) closedInstance(instance *testDataSourceImpl) {
215 t.lock.Lock()
216 defer t.lock.Unlock()
217 for i, in := range t.instances {
218 if in == instance {
219 copy(t.instances[i:], t.instances[i+1:])
220 t.instances[len(t.instances)-1] = nil
221 t.instances = t.instances[:len(t.instances)-1]
222 break
223 }
224 }
225 }
226
227 func (d *testDataSourceImpl) Close() error {
228 d.owner.closedInstance(d)
229 return nil
230 }
231
232 func (d *testDataSourceImpl) IsInitialized() bool {
233 return true
234 }
235
236 func (d *testDataSourceImpl) Start(closeWhenReady chan<- struct{}) {
237 _ = d.updates.Init(d.owner.makeInitData())
238 d.updates.UpdateStatus(interfaces.DataSourceStateValid, interfaces.DataSourceErrorInfo{})
239 close(closeWhenReady)
240 }
241
View as plain text