patronus_fati 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,350 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe(PatronusFati::DataModels::Client) do
4
+ subject { described_class.new('00:11:22:33:44:55') }
5
+
6
+ it_behaves_like 'a common stateful model'
7
+
8
+ context '#add_access_point' do
9
+ it 'should not add an access point more than once' do
10
+ sample_mac = '33:33:33:44:44:44'
11
+ subject.access_point_bssids = [ sample_mac ]
12
+
13
+ expect { subject.add_access_point(sample_mac) }
14
+ .to_not change { subject.access_point_bssids }
15
+ end
16
+
17
+ it 'should add an access point if it\'s not presently in the list' do
18
+ sample_mac = '99:11:22:ff:23:00'
19
+
20
+ expect(subject.access_point_bssids).to be_empty
21
+ expect { subject.add_access_point(sample_mac) }
22
+ .to change { subject.access_point_bssids }.from([]).to([sample_mac])
23
+ end
24
+ end
25
+
26
+ context '#announce_changes' do
27
+ before(:each) do
28
+ PatronusFati::DataModels::AccessPoint.instance_variable_set(:@instances, nil)
29
+ PatronusFati::DataModels::Client.instance_variable_set(:@instances, nil)
30
+ PatronusFati::DataModels::Connection.instance_variable_set(:@instances, nil)
31
+ end
32
+
33
+ it 'should emit no events when the client isn\'t valid' do
34
+ expect(subject).to receive(:dirty?).and_return(true)
35
+ expect(subject).to receive(:valid?).and_return(false)
36
+
37
+ expect(PatronusFati.event_handler).to_not receive(:event)
38
+ subject.announce_changes
39
+ end
40
+
41
+ it 'should emit no events when the instance isn\'t dirty' do
42
+ expect(subject).to receive(:dirty?).and_return(false)
43
+
44
+ expect(PatronusFati.event_handler).to_not receive(:event)
45
+ subject.announce_changes
46
+ end
47
+
48
+ it 'should emit a new client event when dirty and unsynced' do
49
+ expect(subject).to receive(:dirty?).and_return(true)
50
+ expect(subject).to receive(:valid?).and_return(true)
51
+ subject.presence.mark_visible
52
+
53
+ expect(PatronusFati.event_handler)
54
+ .to receive(:event).with(:client, :new, anything, anything)
55
+ subject.announce_changes
56
+ end
57
+
58
+ it 'should emit a changed client event when dirty and synced as online' do
59
+ subject.presence.mark_visible
60
+ subject.mark_synced
61
+
62
+ expect(subject).to receive(:dirty?).and_return(true)
63
+ expect(subject).to receive(:valid?).and_return(true)
64
+
65
+ expect(PatronusFati.event_handler)
66
+ .to receive(:event).with(:client, :changed, anything, anything)
67
+ subject.announce_changes
68
+ end
69
+
70
+ it 'should emit a changed client event when dirty and synced as offline' do
71
+ subject.mark_synced
72
+ expect(subject.active?).to be_falsey
73
+
74
+ subject.presence.mark_visible
75
+
76
+ expect(subject).to receive(:valid?).and_return(true)
77
+ expect(PatronusFati.event_handler)
78
+ .to receive(:event).with(:client, :changed, anything, anything)
79
+ subject.announce_changes
80
+ end
81
+
82
+ it 'should emit an offline client event when the client becomes inactive' do
83
+ subject.presence.mark_visible
84
+ subject.mark_synced
85
+ expect(subject.active?).to be_truthy
86
+
87
+ expect(subject).to receive(:valid?).and_return(true)
88
+ expect(subject).to receive(:active?).and_return(false).exactly(3).times
89
+
90
+ expect(PatronusFati.event_handler)
91
+ .to receive(:event).with(:client, :offline, anything, anything)
92
+ subject.announce_changes
93
+ end
94
+
95
+ it 'should reset the presence first_seen value when announced offline' do
96
+ subject.presence.mark_visible
97
+ subject.mark_synced
98
+ expect(subject.active?).to be_truthy
99
+
100
+ expect(subject).to receive(:valid?).and_return(true)
101
+ expect(subject).to receive(:active?).and_return(false).exactly(3).times
102
+
103
+ expect { subject.announce_changes }
104
+ .to change { subject.presence.first_seen }.to(nil)
105
+ end
106
+
107
+ it 'should remove itself from access points as a connected client when announced offline' do
108
+ bssid = 'fa:eb:dc:45:23:67'
109
+ dbl = double(PatronusFati::DataModels::AccessPoint)
110
+ PatronusFati::DataModels::AccessPoint.instances[bssid] = dbl
111
+
112
+ subject.presence.mark_visible
113
+ subject.add_access_point(bssid)
114
+ subject.mark_synced
115
+
116
+ expect(dbl).to receive(:remove_client).with(subject.local_attributes[:mac])
117
+ expect(subject).to receive(:active?).and_return(false).exactly(3).times
118
+
119
+ subject.announce_changes
120
+ end
121
+
122
+ it 'should mark active connections with a lost link when announced offline' do
123
+ bssid = 'fa:eb:dc:45:23:67'
124
+ conn_key = "#{bssid}^#{subject.local_attributes[:mac]}"
125
+
126
+ dbl = double(PatronusFati::DataModels::Connection)
127
+ PatronusFati::DataModels::Connection.instances[conn_key] = dbl
128
+
129
+ subject.presence.mark_visible
130
+ subject.add_access_point(bssid)
131
+ subject.mark_synced
132
+
133
+ expect(dbl).to receive(:link_lost=).with(true)
134
+ expect(subject).to receive(:active?).and_return(false).exactly(3).times
135
+
136
+ subject.announce_changes
137
+ end
138
+
139
+ it 'short not be dirty after being synced' do
140
+ expect(subject).to receive(:valid?).and_return(true)
141
+ subject.update(channel: 8)
142
+
143
+ expect { subject.announce_changes }.to change { subject.dirty? }.from(true).to(false)
144
+ end
145
+
146
+ it 'should announce the full state of the client with online syncs' do
147
+ subject.presence.mark_visible
148
+ subject.mark_synced
149
+
150
+ data_sample = { data: 'test' }
151
+
152
+ expect(subject).to receive(:dirty?).and_return(true)
153
+ expect(subject).to receive(:valid?).and_return(true)
154
+ expect(subject).to receive(:full_state).and_return(data_sample)
155
+
156
+ expect(PatronusFati.event_handler)
157
+ .to receive(:event).with(:client, :changed, data_sample, anything)
158
+ subject.announce_changes
159
+ end
160
+
161
+ it 'should announce a minimal state of the client with offline syncs' do
162
+ subject.presence.mark_visible
163
+ subject.mark_synced
164
+
165
+ expect(subject).to receive(:valid?).and_return(true)
166
+ expect(subject).to receive(:active?).and_return(false).exactly(3).times
167
+
168
+ expect(subject.presence).to receive(:visible_time).and_return(1234)
169
+ min_data = {
170
+ 'bssid' => subject.local_attributes[:mac],
171
+ 'uptime' => 1234
172
+ }
173
+
174
+ expect(PatronusFati.event_handler)
175
+ .to receive(:event).with(:client, :offline, min_data, anything)
176
+ subject.announce_changes
177
+ end
178
+
179
+ it 'should announce the diagnostic data of the client' do
180
+ sample_data = { content: 'diagnostic' }
181
+
182
+ expect(subject).to receive(:dirty?).and_return(true)
183
+ expect(subject).to receive(:valid?).and_return(true)
184
+ expect(subject).to receive(:diagnostic_data).and_return(sample_data)
185
+
186
+ expect(PatronusFati.event_handler)
187
+ .to receive(:event).with(:client, :offline, anything, sample_data)
188
+ subject.announce_changes
189
+ end
190
+ end
191
+
192
+ context '#cleanup_probes' do
193
+ let(:probe) { PatronusFati::Presence.new }
194
+
195
+ it 'should not modify the children flag when there are no probes' do
196
+ expect(subject.probes).to be_empty
197
+ expect { subject.cleanup_probes }.to_not change { subject.sync_status }
198
+ end
199
+
200
+ it 'should not modify the children flag when there is only active probes' do
201
+ probe.mark_visible
202
+ subject.probes = { 'probe key' => probe }
203
+ expect { subject.cleanup_probes }.to_not change { subject.sync_status }
204
+ end
205
+
206
+ it 'should modify the children flag when there is a dead probe' do
207
+ expect(probe).to be_dead
208
+ subject.probes = { 'NETGEAR32' => probe }
209
+ expect { subject.cleanup_probes }
210
+ .to change { subject.sync_flag?(:dirtyChildren) }.from(false).to(true)
211
+ end
212
+
213
+ it 'should remove all dead probes from the list' do
214
+ expect(probe).to be_dead
215
+ subject.probes = { 'linksys' => probe }
216
+ expect { subject.cleanup_probes }.to change { subject.probes }.to({})
217
+ end
218
+ end
219
+
220
+ context '#full_state' do
221
+ it 'should return a hash' do
222
+ expect(subject.full_state).to be_kind_of(Hash)
223
+ end
224
+
225
+ it 'should include the keys expected by pulse' do
226
+ [:active, :bssid, :channel, :connected_access_points, :probes, :vendor].each do |k|
227
+ expect(subject.full_state.key?(k)).to be_truthy
228
+ end
229
+ end
230
+ end
231
+
232
+ context '#initialize' do
233
+ it 'should initialize probes to an empty hash' do
234
+ expect(subject.probes).to eql({})
235
+ end
236
+
237
+ it 'should initialize the local attributes with the client\'s mac' do
238
+ expect(subject.local_attributes.keys).to eql([:mac])
239
+ expect(subject.local_attributes[:mac]).to_not be_nil
240
+ end
241
+
242
+ it 'should initialize the APs it\'s connected to, to an empty array' do
243
+ expect(subject.access_point_bssids).to eql([])
244
+ end
245
+ end
246
+
247
+ context '#remove_access_point' do
248
+ it 'should make no change if the the bssid isn\'t present' do
249
+ subject.access_point_bssids = [ '78:2b:11:ae:00:12' ]
250
+ expect { subject.remove_access_point('00:11:22:33:44:55') }
251
+ .to_not change { subject.access_point_bssids }
252
+ end
253
+
254
+ it 'should remove just the bssid provided when it is present' do
255
+ test_mac = '00:11:22:33:44:55'
256
+ subject.access_point_bssids = [ '78:2b:11:ae:00:12', test_mac ]
257
+
258
+ expect { subject.remove_access_point(test_mac) }
259
+ .to change { subject.access_point_bssids }
260
+ expect(subject.access_point_bssids).to_not include(test_mac)
261
+ end
262
+ end
263
+
264
+ context '#track_probe' do
265
+ it 'should not change anything when provided a nil value' do
266
+ expect { subject.track_probe(nil) }.to_not change { subject.probes }
267
+ end
268
+
269
+ it 'should not change anything when provided an empty string' do
270
+ expect { subject.track_probe('') }.to_not change { subject.probes }
271
+ end
272
+
273
+ it 'should track new probes as a new presence instance' do
274
+ subject.track_probe('test')
275
+ expect(subject.probes['test']).to be_instance_of(PatronusFati::Presence)
276
+ expect(subject.probes['test']).to_not be_dead
277
+ end
278
+
279
+ it 'should mark existing probes as visisble' do
280
+ dbl = double(PatronusFati::Presence)
281
+ subject.probes['pineapple'] = dbl
282
+ expect(dbl).to receive(:mark_visible)
283
+ subject.track_probe('pineapple')
284
+ end
285
+ end
286
+
287
+ context '#update' do
288
+ it 'should not set invalid keys' do
289
+ expect { subject.update(bad: 'key') }
290
+ .to_not change { subject.local_attributes }
291
+ end
292
+
293
+ it 'shouldn\'t modify the sync flags on invalid keys' do
294
+ expect { subject.update(other: 'key') }
295
+ .to_not change { subject.sync_status }
296
+ end
297
+
298
+ it 'shouldn\'t modify the sync flags if the values haven\'t changed' do
299
+ expect { subject.update(subject.local_attributes) }
300
+ .to_not change { subject.sync_status }
301
+ end
302
+
303
+ it 'should set the dirty attribute flag when a value has changed' do
304
+ expect { subject.update(channel: 5) }
305
+ .to change { subject.sync_status }
306
+ expect(subject.sync_flag?(:dirtyAttributes)).to be_truthy
307
+ end
308
+ end
309
+
310
+ context '#valid?' do
311
+ it 'should be true when all required attributes are set' do
312
+ subject.local_attributes = { mac: 'testing' }
313
+ expect(subject).to be_valid
314
+ end
315
+
316
+ it 'should be false when missing a required attribute' do
317
+ subject.local_attributes.delete(:mac)
318
+ expect(subject).to_not be_valid
319
+ end
320
+ end
321
+
322
+ context '#vendor' do
323
+ it 'should short circuit if no MAC is available' do
324
+ expect(Louis).to_not receive(:lookup)
325
+
326
+ subject.update(mac: nil)
327
+ subject.vendor
328
+ end
329
+
330
+ it 'should use the Louis gem to perform it\'s lookup' do
331
+ inst = 'test string'
332
+ subject.update(mac: inst)
333
+
334
+ expect(Louis).to receive(:lookup).with(inst).and_return({})
335
+ subject.vendor
336
+ end
337
+
338
+ it 'should default the long vendor name if it\'s available' do
339
+ result = { 'long_vendor' => 'correct', 'short_vendor' => 'bad' }
340
+ expect(Louis).to receive(:lookup).and_return(result)
341
+ expect(subject.vendor).to eql('correct')
342
+ end
343
+
344
+ it 'should fallback on the short vendor name if long isn\'t available' do
345
+ result = { 'short_vendor' => 'short' }
346
+ expect(Louis).to receive(:lookup).and_return(result)
347
+ expect(subject.vendor).to eql('short')
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe(PatronusFati::DataModels::Connection) do
4
+ subject { described_class.new('00:00:00:00:00:00', '34:34:34:34:34:34') }
5
+
6
+ it_behaves_like 'a common stateful model'
7
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe(PatronusFati::DataModels::Ssid) do
4
+ subject { described_class.new('BillWiTheScienceFi') }
5
+
6
+ it_behaves_like 'a common stateful model'
7
+
8
+ context '#initialize' do
9
+ it 'should initialize the local attributes with the essid' do
10
+ expect(subject.local_attributes.keys).to include(:essid)
11
+ expect(subject.local_attributes[:essid]).to_not be_nil
12
+ end
13
+
14
+ it 'should initialize the local attributes with cloaked' do
15
+ expect(subject.local_attributes.keys).to include(:cloaked)
16
+ end
17
+
18
+ it 'should initialize the cloaked attribute to false if an essid is provided' do
19
+ expect(subject.local_attributes[:essid]).to_not be_nil
20
+ expect(subject.local_attributes[:essid].size).to be > 0
21
+ expect(subject.local_attributes[:cloaked]).to be_falsey
22
+ end
23
+
24
+ it 'should initialize the cloaked attribute to true if a nil essid is provided' do
25
+ subject = described_class.new(nil)
26
+ expect(subject.local_attributes[:cloaked]).to be_truthy
27
+ end
28
+
29
+ it 'should initialize the cloaked attribute to true if an empty essid is provided' do
30
+ subject = described_class.new('')
31
+ expect(subject.local_attributes[:cloaked]).to be_truthy
32
+ end
33
+ end
34
+
35
+ context '#update' do
36
+ it 'should not set invalid keys' do
37
+ expect { subject.update(bad: 'key') }
38
+ .to_not change { subject.local_attributes }
39
+ end
40
+
41
+ it 'shouldn\'t modify the sync flags on invalid keys' do
42
+ expect { subject.update(other: 'key') }
43
+ .to_not change { subject.sync_status }
44
+ end
45
+
46
+ it 'shouldn\'t modify the sync flags if the values haven\'t changed' do
47
+ expect { subject.update(subject.local_attributes) }
48
+ .to_not change { subject.sync_status }
49
+ end
50
+
51
+ it 'should set the dirty attribute flag when a value has changed' do
52
+ expect { subject.update(max_rate: 5) }
53
+ .to change { subject.sync_status }
54
+ expect(subject.sync_flag?(:dirtyAttributes)).to be_truthy
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,269 @@
1
+ RSpec.shared_examples_for('a common stateful model') do
2
+ context 'class methods' do
3
+ # Helper to ensure tests don't interfere with each other
4
+ before(:each) do
5
+ described_class.instance_variable_set(:@instances, nil)
6
+ end
7
+
8
+ context '#[]' do
9
+ it 'should create a new instance when the key doesn\'t exist' do
10
+ expect { described_class['test'] }.to change { described_class.instances.count }.from(0).to(1)
11
+ expect(described_class['test']).to be_instance_of(described_class)
12
+ end
13
+
14
+ it 'should return an existing instance when the key already exists' do
15
+ dbl = double(described_class)
16
+ described_class.instances['just^keyed'] = dbl
17
+ expect(described_class['just^keyed']).to eq(dbl)
18
+ end
19
+ end
20
+
21
+ context '#exists?' do
22
+ it 'should be true when an instance matching the key exists' do
23
+ described_class.instances['test'] = double(described_class)
24
+ expect(described_class.exists?('test')).to be_truthy
25
+ end
26
+
27
+ it 'should be false when there is no instance matching the key' do
28
+ expect(described_class.exists?('other')).to be_falsey
29
+ end
30
+ end
31
+
32
+ context '#instances' do
33
+ it 'should default to an empty hash' do
34
+ expect(described_class.instances).to be_kind_of(Hash)
35
+ expect(described_class.instances).to be_empty
36
+ end
37
+ end
38
+ end
39
+
40
+ context '#active?' do
41
+ it 'should use it\'s expiration time against the presence instance' do
42
+ expect(described_class).to receive(:current_expiration_threshold).and_return(137)
43
+ expect(subject.presence).to receive(:visible_since?).with(137)
44
+
45
+ subject.active?
46
+ end
47
+
48
+ it 'should pass the presence result back' do
49
+ expect(subject.presence).to receive(:visible_since?).and_return(false)
50
+ expect(subject.active?).to be_falsey
51
+
52
+ expect(subject.presence).to receive(:visible_since?).and_return(true)
53
+ expect(subject.active?).to be_truthy
54
+ end
55
+
56
+ it 'should have an expiration time' do
57
+ expect(described_class).to respond_to(:current_expiration_threshold)
58
+ expect(described_class.current_expiration_threshold).to be_kind_of(Numeric)
59
+ end
60
+
61
+ it 'should have an expiration in the past' do
62
+ expect(described_class.current_expiration_threshold).to be <= Time.now.to_i
63
+ end
64
+ end
65
+
66
+ context '#data_dirty?' do
67
+ it 'should be false when no status flags have been set' do
68
+ subject.sync_status = PatronusFati::SYNC_FLAGS[:unsynced]
69
+ expect(subject.data_dirty?).to be_falsey
70
+ end
71
+
72
+ it 'should be false when only sync status flags are set' do
73
+ subject.sync_status = PatronusFati::SYNC_FLAGS[:syncedOffline]
74
+ expect(subject.data_dirty?).to be_falsey
75
+
76
+ subject.sync_status = PatronusFati::SYNC_FLAGS[:syncedOnline]
77
+ expect(subject.data_dirty?).to be_falsey
78
+ end
79
+
80
+ it 'should be true when the attributes have been marked dirty' do
81
+ subject.sync_status = PatronusFati::SYNC_FLAGS[:dirtyAttributes]
82
+ expect(subject.data_dirty?).to be_truthy
83
+ end
84
+
85
+ it 'should be true when a child has been marked dirty' do
86
+ subject.sync_status = PatronusFati::SYNC_FLAGS[:dirtyChildren]
87
+ expect(subject.data_dirty?).to be_truthy
88
+ end
89
+ end
90
+
91
+ context '#diagnostic_data' do
92
+ it 'should be a hash' do
93
+ expect(subject.diagnostic_data).to be_kind_of(Hash)
94
+ end
95
+
96
+ it 'should include the raw sync_status value' do
97
+ data = subject.diagnostic_data
98
+ expect(data.keys).to include(:sync_status)
99
+ expect(data[:sync_status]).to eql(subject.sync_status)
100
+ end
101
+
102
+ it 'should include the raw presence information' do
103
+ data = subject.diagnostic_data
104
+ expect(data.keys).to include(:current_presence)
105
+ expect(data.keys).to include(:last_presence)
106
+ end
107
+ end
108
+
109
+ context '#dirty?' do
110
+ it 'should be dirty when it\'s new' do
111
+ expect(subject).to receive(:new?).and_return(true)
112
+
113
+ expect(subject.dirty?).to be_truthy
114
+ end
115
+
116
+ it 'should be dirty if data has changed' do
117
+ expect(subject).to receive(:new?).and_return(false)
118
+ expect(subject).to receive(:data_dirty?).and_return(true)
119
+
120
+ expect(subject.dirty?).to be_truthy
121
+ end
122
+
123
+ it 'should be dirty if the sync status doesn\'t match or current status' do
124
+ expect(subject).to receive(:new?).and_return(false)
125
+ expect(subject).to receive(:data_dirty?).and_return(false)
126
+ expect(subject).to receive(:status_dirty?).and_return(true)
127
+
128
+ expect(subject.dirty?).to be_truthy
129
+ end
130
+
131
+ it 'should not be dirty when nothing has changed' do
132
+ expect(subject).to receive(:new?).and_return(false)
133
+ expect(subject).to receive(:data_dirty?).and_return(false)
134
+ expect(subject).to receive(:status_dirty?).and_return(false)
135
+
136
+ expect(subject.dirty?).to be_falsey
137
+ end
138
+ end
139
+
140
+ context '#initialize' do
141
+ it 'should initialize sync_status to zero' do
142
+ expect(subject.sync_status).to eql(0)
143
+ end
144
+
145
+ it 'should initialize a new presence instance' do
146
+ expect(subject.presence).to be_kind_of(PatronusFati::Presence)
147
+ end
148
+ end
149
+
150
+ context '#mark_synced' do
151
+ it 'should set the sync status to syncedOnline when active' do
152
+ expect(subject).to receive(:active?).and_return(true)
153
+ expect { subject.mark_synced }
154
+ .to change { subject.sync_flag?(:syncedOnline) }.from(false).to(true)
155
+ end
156
+
157
+ it 'should set the sync status to syncedOffline when not active' do
158
+ expect(subject).to receive(:active?).and_return(false)
159
+ expect { subject.mark_synced }
160
+ .to change { subject.sync_flag?(:syncedOffline) }.from(false).to(true)
161
+ end
162
+
163
+ it 'should clear the dirty attribute flags' do
164
+ subject.set_sync_flag(:dirtyAttributes)
165
+ expect { subject.mark_synced }
166
+ .to change { subject.sync_flag?(:dirtyAttributes) }.from(true).to(false)
167
+
168
+ subject.set_sync_flag(:dirtyChildren)
169
+ expect { subject.mark_synced }
170
+ .to change { subject.sync_flag?(:dirtyChildren) }.from(true).to(false)
171
+ end
172
+ end
173
+
174
+ context 'new?' do
175
+ it 'should be true when unsynced' do
176
+ expect(subject.sync_status).to eql(PatronusFati::SYNC_FLAGS[:unsynced])
177
+ expect(subject.new?).to be_truthy
178
+ end
179
+
180
+ it 'should be false when syncedOnline is set' do
181
+ subject.set_sync_flag(:syncedOnline)
182
+ expect(subject.new?).to be_falsey
183
+ end
184
+
185
+ it 'should be false when syncedOffline is set' do
186
+ subject.set_sync_flag(:syncedOffline)
187
+ expect(subject.new?).to be_falsey
188
+ end
189
+
190
+ it 'should be true when just the dirty data attributes are set' do
191
+ expect(subject.sync_status).to eql(PatronusFati::SYNC_FLAGS[:unsynced])
192
+
193
+ subject.set_sync_flag(:dirtyAttributes)
194
+ subject.set_sync_flag(:dirtyChildren)
195
+
196
+ expect(subject.new?).to be_truthy
197
+ end
198
+ end
199
+
200
+ context '#presence' do
201
+ it { expect(subject).to respond_to(:presence) }
202
+ it { expect(subject.presence).to be_instance_of(PatronusFati::Presence) }
203
+ end
204
+
205
+ context '#set_sync_flag' do
206
+ it 'should not change the value when it\'s already set' do
207
+ subject.set_sync_flag(:syncedOnline)
208
+ expect { subject.set_sync_flag(:syncedOnline) }
209
+ .to_not change { subject.sync_status }
210
+ end
211
+
212
+ it 'should set the flag if it\'s not already set' do
213
+ expect(subject.sync_flag?(:dirtyAttributes)).to be_falsey
214
+ subject.set_sync_flag(:dirtyAttributes)
215
+ expect(subject.sync_flag?(:dirtyAttributes)).to be_truthy
216
+ end
217
+
218
+ it 'should not change other flags when being set' do
219
+ subject.set_sync_flag(:dirtyChildren)
220
+ subject.set_sync_flag(:syncedOnline)
221
+
222
+ expect(subject.sync_flag?(:dirtyChildren)).to be_truthy
223
+ end
224
+ end
225
+
226
+ context '#status_dirty?' do
227
+ it 'should be true when inactive and marked as active' do
228
+ expect(subject).to receive(:active?).and_return(false)
229
+ subject.set_sync_flag(:syncedOnline)
230
+ expect(subject.status_dirty?).to be_truthy
231
+ end
232
+
233
+ it 'should be true when active and marked as inactive' do
234
+ expect(subject).to receive(:active?).and_return(true)
235
+ subject.set_sync_flag(:syncedOffline)
236
+ expect(subject.status_dirty?).to be_truthy
237
+ end
238
+
239
+ it 'should be false when status and marking are active' do
240
+ expect(subject).to receive(:active?).and_return(true)
241
+ subject.set_sync_flag(:syncedOnline)
242
+ expect(subject.status_dirty?).to be_falsey
243
+ end
244
+
245
+ it 'should be false when status and marking are inactive' do
246
+ expect(subject).to receive(:active?).and_return(false)
247
+ subject.set_sync_flag(:syncedOffline)
248
+ expect(subject.status_dirty?).to be_falsey
249
+ end
250
+ end
251
+
252
+ context '#sync_flag?' do
253
+ it 'should be true when the provided flag is set' do
254
+ expect { subject.set_sync_flag(:dirtyAttributes) }
255
+ .to change { subject.sync_flag?(:dirtyAttributes) }.from(false).to(true)
256
+ end
257
+
258
+ it 'should be false when the provided flag isn\'t set' do
259
+ subject.sync_status = 0
260
+ expect(subject.sync_flag?(:dirtyChildren)).to be_falsey
261
+ end
262
+
263
+ it 'should be false when just another flag is set' do
264
+ subject.sync_status = 0
265
+ subject.set_sync_flag(:dirtyAttributes)
266
+ expect(subject.sync_flag?(:syncedOffline)).to be_falsey
267
+ end
268
+ end
269
+ end