patronus_fati 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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