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.
- checksums.yaml +4 -4
- data/bin/patronus_fati +10 -2
- data/lib/patronus_fati/data_models/access_point.rb +24 -24
- data/lib/patronus_fati/data_models/client.rb +12 -23
- data/lib/patronus_fati/data_models/common_state.rb +37 -2
- data/lib/patronus_fati/data_models/connection.rb +3 -8
- data/lib/patronus_fati/data_models/ssid.rb +6 -4
- data/lib/patronus_fati/message_processor.rb +3 -2
- data/lib/patronus_fati/presence.rb +6 -2
- data/lib/patronus_fati/version.rb +1 -1
- data/spec/patronus_fati/data_models/access_point_spec.rb +282 -0
- data/spec/patronus_fati/data_models/client_spec.rb +350 -0
- data/spec/patronus_fati/data_models/connection_spec.rb +7 -0
- data/spec/patronus_fati/data_models/ssid_spec.rb +57 -0
- data/spec/shared_examples/common_model_state.rb +269 -0
- data/spec/spec_helper.rb +2 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9a7acfd177a783e5557a8e81a9dfe4eee777437
|
4
|
+
data.tar.gz: 5a8a3889d839f81386008de1f76e4bd68662e62a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81a6b3e81d3634847fafaa5da8db3b07c3bcb41e02de0746b74e2d9b55194854302cd12d341b02d828d5a61bb1276d25632373d9147250cef9289473e995d432
|
7
|
+
data.tar.gz: c69f576ed4fdec47eb42560f9137cca80488b6ae0f79855b41b01800f47efdbe8a7361644693fcf9ef70c91636e88f1262016f9e150d1e32d439bb3ff19845f1
|
data/bin/patronus_fati
CHANGED
@@ -39,8 +39,16 @@ exception_logger('process') do
|
|
39
39
|
connection = PatronusFati.setup(options['server'], options['port'])
|
40
40
|
connection.connect
|
41
41
|
|
42
|
-
PatronusFati.event_handler.on(:any) do |asset_type, event_type, msg,
|
43
|
-
PatronusFati.logger.info(JSON.generate(
|
42
|
+
PatronusFati.event_handler.on(:any) do |asset_type, event_type, msg, diagnostics|
|
43
|
+
PatronusFati.logger.info(JSON.generate(
|
44
|
+
{
|
45
|
+
asset_type: asset_type,
|
46
|
+
event_type: event_type,
|
47
|
+
data: msg,
|
48
|
+
diagnostics: diagnostics,
|
49
|
+
timestamp: Time.now.to_i
|
50
|
+
}
|
51
|
+
))
|
44
52
|
end
|
45
53
|
|
46
54
|
while (line = connection.read_queue.pop)
|
@@ -3,25 +3,16 @@ module PatronusFati
|
|
3
3
|
class AccessPoint
|
4
4
|
include CommonState
|
5
5
|
|
6
|
-
attr_accessor :client_macs, :local_attributes, :
|
7
|
-
:sync_status
|
6
|
+
attr_accessor :client_macs, :local_attributes, :ssids
|
8
7
|
|
9
8
|
LOCAL_ATTRIBUTE_KEYS = [ :bssid, :channel, :type ].freeze
|
10
9
|
|
11
|
-
def self.[](bssid)
|
12
|
-
instances[bssid] ||= new(bssid)
|
13
|
-
end
|
14
|
-
|
15
10
|
def self.current_expiration_threshold
|
16
11
|
Time.now.to_i - AP_EXPIRATION
|
17
12
|
end
|
18
13
|
|
19
|
-
def self.instances
|
20
|
-
@instances ||= {}
|
21
|
-
end
|
22
|
-
|
23
14
|
def active_ssids
|
24
|
-
ssids.select { |_, v| v.active? }
|
15
|
+
ssids.select { |_, v| v.active? } if ssids
|
25
16
|
end
|
26
17
|
|
27
18
|
def add_client(mac)
|
@@ -37,14 +28,16 @@ module PatronusFati
|
|
37
28
|
PatronusFati.event_handler.event(
|
38
29
|
:access_point,
|
39
30
|
status,
|
40
|
-
full_state
|
31
|
+
full_state,
|
32
|
+
diagnostic_data
|
41
33
|
)
|
42
34
|
else
|
43
35
|
PatronusFati.event_handler.event(
|
44
36
|
:access_point, :offline, {
|
45
37
|
'bssid' => local_attributes[:bssid],
|
46
38
|
'uptime' => presence.visible_time
|
47
|
-
}
|
39
|
+
},
|
40
|
+
diagnostic_data
|
48
41
|
)
|
49
42
|
|
50
43
|
# We need to reset the first seen so we get fresh duration
|
@@ -61,34 +54,40 @@ module PatronusFati
|
|
61
54
|
end
|
62
55
|
|
63
56
|
def cleanup_ssids
|
64
|
-
return if ssids.select { |_, v| v.presence.dead? }.empty?
|
57
|
+
return if ssids.nil? || ssids.select { |_, v| v.presence.dead? }.empty?
|
65
58
|
|
66
|
-
# When an AP is offline we don't care about it's SSIDs
|
67
|
-
#
|
68
|
-
set_sync_flag(:dirtyChildren) if active?
|
69
|
-
|
59
|
+
# When an AP is offline we don't care about announcing that it's SSIDs
|
60
|
+
# have expired, but we do want to remove them.
|
61
|
+
set_sync_flag(:dirtyChildren) if active?
|
62
|
+
|
63
|
+
ssids.reject! { |_, v| v.presence.dead? }
|
64
|
+
end
|
65
|
+
|
66
|
+
def diagnostic_data
|
67
|
+
dd = super
|
68
|
+
dd.merge!(ssids: Hash[ssids.map { |k, s| [k, s.diagnostic_data] }]) if ssids
|
69
|
+
dd
|
70
70
|
end
|
71
71
|
|
72
72
|
def full_state
|
73
|
-
local_attributes.merge({
|
73
|
+
state = local_attributes.merge({
|
74
74
|
active: active?,
|
75
75
|
connected_clients: client_macs,
|
76
|
-
ssids: active_ssids.map(&:local_attributes),
|
77
76
|
vendor: vendor
|
78
77
|
})
|
78
|
+
state[:ssids] = active_ssids.values.map(&:local_attributes) if ssids
|
79
|
+
state
|
79
80
|
end
|
80
81
|
|
81
82
|
def initialize(bssid)
|
83
|
+
super
|
82
84
|
self.local_attributes = { bssid: bssid }
|
83
85
|
self.client_macs = []
|
84
|
-
self.presence = Presence.new
|
85
|
-
self.ssids = {}
|
86
|
-
self.sync_status = 0
|
87
86
|
end
|
88
87
|
|
89
88
|
def mark_synced
|
90
89
|
super
|
91
|
-
ssids.each { |_, v| v.mark_synced }
|
90
|
+
ssids.each { |_, v| v.mark_synced } if ssids
|
92
91
|
end
|
93
92
|
|
94
93
|
def remove_client(mac)
|
@@ -96,6 +95,7 @@ module PatronusFati
|
|
96
95
|
end
|
97
96
|
|
98
97
|
def track_ssid(ssid_data)
|
98
|
+
self.ssids ||= {}
|
99
99
|
ssids[ssid_data[:essid]] ||= DataModels::Ssid.new(ssid_data[:essid])
|
100
100
|
|
101
101
|
ssid = ssids[ssid_data[:essid]]
|
@@ -3,25 +3,16 @@ module PatronusFati
|
|
3
3
|
class Client
|
4
4
|
include CommonState
|
5
5
|
|
6
|
-
attr_accessor :access_point_bssids, :local_attributes, :
|
7
|
-
:probes, :sync_status
|
6
|
+
attr_accessor :access_point_bssids, :local_attributes, :probes
|
8
7
|
|
9
8
|
LOCAL_ATTRIBUTE_KEYS = [ :mac, :channel ].freeze
|
10
9
|
|
11
|
-
def self.[](mac)
|
12
|
-
instances[mac] ||= new(mac)
|
13
|
-
end
|
14
|
-
|
15
10
|
def self.current_expiration_threshold
|
16
11
|
Time.now.to_i - CLIENT_EXPIRATION
|
17
12
|
end
|
18
13
|
|
19
|
-
def
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.instances
|
24
|
-
@instances ||= {}
|
14
|
+
def add_access_point(bssid)
|
15
|
+
access_point_bssids << bssid unless access_point_bssids.include?(bssid)
|
25
16
|
end
|
26
17
|
|
27
18
|
def announce_changes
|
@@ -29,13 +20,14 @@ module PatronusFati
|
|
29
20
|
|
30
21
|
if active?
|
31
22
|
status = new? ? :new : :changed
|
32
|
-
PatronusFati.event_handler.event(:client, status, full_state)
|
23
|
+
PatronusFati.event_handler.event(:client, status, full_state, diagnostic_data)
|
33
24
|
else
|
34
25
|
PatronusFati.event_handler.event(
|
35
26
|
:client, :offline, {
|
36
27
|
'bssid' => local_attributes[:mac],
|
37
28
|
'uptime' => presence.visible_time
|
38
|
-
}
|
29
|
+
},
|
30
|
+
diagnostic_data
|
39
31
|
)
|
40
32
|
|
41
33
|
# We need to reset the first seen so we get fresh duration information
|
@@ -50,15 +42,13 @@ module PatronusFati
|
|
50
42
|
mark_synced
|
51
43
|
end
|
52
44
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
45
|
+
# Probes don't have an explicit visibility window so this will only
|
46
|
+
# remove probes that haven't been seen in the entire duration of the time
|
47
|
+
# we track any visibility.
|
57
48
|
def cleanup_probes
|
58
|
-
return if probes.select { |_,
|
59
|
-
|
49
|
+
return if probes.select { |_, pres| pres.dead? }.empty?
|
60
50
|
set_sync_flag(:dirtyChildren)
|
61
|
-
probes.reject { |_,
|
51
|
+
probes.reject! { |_, pres| pres.dead? }
|
62
52
|
end
|
63
53
|
|
64
54
|
def full_state
|
@@ -73,11 +63,10 @@ module PatronusFati
|
|
73
63
|
end
|
74
64
|
|
75
65
|
def initialize(mac)
|
66
|
+
super
|
76
67
|
self.access_point_bssids = []
|
77
68
|
self.local_attributes = { mac: mac }
|
78
|
-
self.presence = Presence.new
|
79
69
|
self.probes = {}
|
80
|
-
self.sync_status = 0
|
81
70
|
end
|
82
71
|
|
83
72
|
def remove_access_point(bssid)
|
@@ -1,6 +1,27 @@
|
|
1
1
|
module PatronusFati
|
2
2
|
module DataModels
|
3
3
|
module CommonState
|
4
|
+
module KlassMethods
|
5
|
+
def [](key)
|
6
|
+
instances[key] ||= new(key)
|
7
|
+
end
|
8
|
+
|
9
|
+
def exists?(mac)
|
10
|
+
instances.key?(mac)
|
11
|
+
end
|
12
|
+
|
13
|
+
def instances
|
14
|
+
@instances ||= {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(klass)
|
19
|
+
klass.extend(KlassMethods)
|
20
|
+
klass.class_eval do
|
21
|
+
attr_accessor :presence, :sync_status
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
4
25
|
def active?
|
5
26
|
presence.visible_since?(self.class.current_expiration_threshold)
|
6
27
|
end
|
@@ -9,12 +30,22 @@ module PatronusFati
|
|
9
30
|
sync_flag?(:dirtyAttributes) || sync_flag?(:dirtyChildren)
|
10
31
|
end
|
11
32
|
|
33
|
+
def diagnostic_data
|
34
|
+
{
|
35
|
+
sync_status: sync_status,
|
36
|
+
presence_bit_offset: presence.current_bit_offset,
|
37
|
+
current_presence: presence.current_presence.bits,
|
38
|
+
last_presence: presence.last_presence.bits
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
12
42
|
def dirty?
|
13
43
|
new? || data_dirty? || status_dirty?
|
14
44
|
end
|
15
45
|
|
16
|
-
def
|
17
|
-
|
46
|
+
def initialize(*_args)
|
47
|
+
self.presence = Presence.new
|
48
|
+
self.sync_status = 0
|
18
49
|
end
|
19
50
|
|
20
51
|
def mark_synced
|
@@ -22,6 +53,10 @@ module PatronusFati
|
|
22
53
|
self.sync_status = SYNC_FLAGS[flag]
|
23
54
|
end
|
24
55
|
|
56
|
+
def new?
|
57
|
+
!(sync_flag?(:syncedOnline) || sync_flag?(:syncedOffline))
|
58
|
+
end
|
59
|
+
|
25
60
|
def set_sync_flag(flag)
|
26
61
|
self.sync_status |= SYNC_FLAGS[flag]
|
27
62
|
end
|
@@ -3,7 +3,7 @@ module PatronusFati
|
|
3
3
|
class Connection
|
4
4
|
include CommonState
|
5
5
|
|
6
|
-
attr_accessor :bssid, :link_lost, :mac
|
6
|
+
attr_accessor :bssid, :link_lost, :mac
|
7
7
|
|
8
8
|
def self.[](key)
|
9
9
|
bssid, mac = key.split('^')
|
@@ -14,16 +14,12 @@ module PatronusFati
|
|
14
14
|
Time.now.to_i - CONNECTION_EXPIRATION
|
15
15
|
end
|
16
16
|
|
17
|
-
def self.instances
|
18
|
-
@instances ||= {}
|
19
|
-
end
|
20
|
-
|
21
17
|
def announce_changes
|
22
18
|
return unless dirty? && DataModels::AccessPoint[bssid].valid? &&
|
23
19
|
DataModels::Client[mac].valid?
|
24
20
|
|
25
21
|
state = active? ? :connect : :disconnect
|
26
|
-
PatronusFati.event_handler.event(:connection, state, full_state)
|
22
|
+
PatronusFati.event_handler.event(:connection, state, full_state, diagnostic_data)
|
27
23
|
|
28
24
|
# We need to reset the first seen so we get fresh duration information
|
29
25
|
presence.first_seen = nil
|
@@ -41,11 +37,10 @@ module PatronusFati
|
|
41
37
|
end
|
42
38
|
|
43
39
|
def initialize(bssid, mac)
|
40
|
+
super
|
44
41
|
self.bssid = bssid
|
45
42
|
self.link_lost = false
|
46
43
|
self.mac = mac
|
47
|
-
self.presence = Presence.new
|
48
|
-
self.sync_status = 0
|
49
44
|
end
|
50
45
|
|
51
46
|
def full_state
|
@@ -3,7 +3,7 @@ module PatronusFati
|
|
3
3
|
class Ssid
|
4
4
|
include CommonState
|
5
5
|
|
6
|
-
attr_accessor :local_attributes
|
6
|
+
attr_accessor :local_attributes
|
7
7
|
|
8
8
|
LOCAL_ATTRIBUTE_KEYS = [
|
9
9
|
:beacon_info, :beacon_rate, :cloaked, :crypt_set, :essid, :max_rate
|
@@ -14,9 +14,11 @@ module PatronusFati
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def initialize(essid)
|
17
|
-
|
18
|
-
self.
|
19
|
-
|
17
|
+
super
|
18
|
+
self.local_attributes = {
|
19
|
+
cloaked: essid.nil? || essid.empty?,
|
20
|
+
essid: essid
|
21
|
+
}
|
20
22
|
end
|
21
23
|
|
22
24
|
def update(attrs)
|
@@ -34,6 +34,7 @@ module PatronusFati
|
|
34
34
|
|
35
35
|
def self.offline_clients
|
36
36
|
DataModels::Client.instances.each do |_, client|
|
37
|
+
client.cleanup_probes
|
37
38
|
client.announce_changes
|
38
39
|
end
|
39
40
|
|
@@ -106,13 +107,13 @@ module PatronusFati
|
|
106
107
|
|
107
108
|
PatronusFati::DataModels::AccessPoint.instances.each do |bssid, access_point|
|
108
109
|
next unless access_point.active?
|
109
|
-
PatronusFati.event_handler.event(:access_point, :sync, access_point.full_state)
|
110
|
+
PatronusFati.event_handler.event(:access_point, :sync, access_point.full_state, access_point.diagnostic_data)
|
110
111
|
access_points << bssid
|
111
112
|
end
|
112
113
|
|
113
114
|
PatronusFati::DataModels::Client.instances.each do |mac, client|
|
114
115
|
next unless client.active?
|
115
|
-
PatronusFati.event_handler.event(:client, :sync, client.full_state)
|
116
|
+
PatronusFati.event_handler.event(:client, :sync, client.full_state, client.diagnostic_data)
|
116
117
|
clients << mac
|
117
118
|
end
|
118
119
|
|
@@ -116,9 +116,13 @@ module PatronusFati
|
|
116
116
|
end
|
117
117
|
|
118
118
|
# Returns the duration in seconds of how long the specific object was
|
119
|
-
# absolutely seen.
|
119
|
+
# absolutely seen. One additional interval duration is added to this length
|
120
|
+
# as we consider to have seen the tracked object for the entire duration of
|
121
|
+
# the interval not the length from the start of one interval to the start
|
122
|
+
# of the last interval, which makes logical sense (1 bit set is 1 interval
|
123
|
+
# duration, not zero seconds).
|
120
124
|
def visible_time
|
121
|
-
last_visible - first_seen if first_seen
|
125
|
+
(last_visible + INTERVAL_DURATION) - first_seen if first_seen
|
122
126
|
end
|
123
127
|
end
|
124
128
|
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe(PatronusFati::DataModels::AccessPoint) do
|
4
|
+
subject { described_class.new('66:77:88:99:aa:bb') }
|
5
|
+
|
6
|
+
it_behaves_like 'a common stateful model'
|
7
|
+
|
8
|
+
context '#active_ssids' do
|
9
|
+
it 'should return a hash when ssids have been populated' do
|
10
|
+
subject.track_ssid(essid: 'test')
|
11
|
+
expect(subject.active_ssids).to be_kind_of(Hash)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should not include inactive SSIDs' do
|
15
|
+
ssid = double(PatronusFati::DataModels::Ssid)
|
16
|
+
expect(ssid).to receive(:active?).and_return(false)
|
17
|
+
|
18
|
+
subject.ssids = { tmp: ssid }
|
19
|
+
expect(subject.active_ssids.values).to_not include(ssid)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should include active SSIDs' do
|
23
|
+
ssid = double(PatronusFati::DataModels::Ssid)
|
24
|
+
expect(ssid).to receive(:active?).and_return(true)
|
25
|
+
|
26
|
+
subject.ssids = { tmp: ssid }
|
27
|
+
expect(subject.active_ssids.values).to include(ssid)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context '#add_client' do
|
32
|
+
it 'should not add a client more than once' do
|
33
|
+
sample_mac = 'cc:bb:cc:bb:cc:bb'
|
34
|
+
subject.client_macs = [ sample_mac ]
|
35
|
+
|
36
|
+
expect { subject.add_client(sample_mac) }
|
37
|
+
.to_not change { subject.client_macs }
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should add a client if it\'s not presently in the list' do
|
41
|
+
sample_mac = 'ac:db:fc:4b:8c:0b'
|
42
|
+
subject.client_macs = []
|
43
|
+
|
44
|
+
expect { subject.add_client(sample_mac) }
|
45
|
+
.to change { subject.client_macs }.from([]).to([sample_mac])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# TODO:
|
50
|
+
context '#announce_changes'
|
51
|
+
|
52
|
+
context '#cleanup_ssids' do
|
53
|
+
it 'should not set the dirty children flag if there is nothing to change' do
|
54
|
+
subject.track_ssid(essid: 'test')
|
55
|
+
|
56
|
+
expect { subject.cleanup_ssids }.to_not change { subject.sync_status }
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should not set the dirty children flag when the AP is offline' do
|
60
|
+
allow(subject).to receive(:active?).and_return(false)
|
61
|
+
|
62
|
+
# Create a 'dead' SSID
|
63
|
+
subject.track_ssid(essid: 'test')
|
64
|
+
subject.mark_synced
|
65
|
+
subject.ssids['test'].presence = PatronusFati::Presence.new
|
66
|
+
|
67
|
+
expect { subject.cleanup_ssids }.to_not change { subject.sync_status }
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should remove dead SSIDs from the SSID list' do
|
71
|
+
allow(subject).to receive(:active?).and_return(true)
|
72
|
+
|
73
|
+
# Create a 'dead' SSID
|
74
|
+
subject.track_ssid(essid: 'test')
|
75
|
+
subject.mark_synced
|
76
|
+
subject.ssids['test'].presence = PatronusFati::Presence.new
|
77
|
+
|
78
|
+
expect(subject.ssids).to_not be_empty
|
79
|
+
subject.cleanup_ssids
|
80
|
+
expect(subject.ssids).to be_empty
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should set the dirty children flag when SSIDs have been removed while the AP is active' do
|
84
|
+
allow(subject).to receive(:active?).and_return(true)
|
85
|
+
|
86
|
+
# Create a 'dead' SSID
|
87
|
+
subject.track_ssid(essid: 'test')
|
88
|
+
subject.mark_synced
|
89
|
+
subject.ssids['test'].presence = PatronusFati::Presence.new
|
90
|
+
|
91
|
+
expect { subject.cleanup_ssids }.to change { subject.sync_status }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context '#diagnostic_data' do
|
96
|
+
it 'should include all SSID diagnostic data' do
|
97
|
+
ssid_dbl = double(PatronusFati::DataModels::Ssid)
|
98
|
+
expect(ssid_dbl).to receive(:diagnostic_data).and_return(:datum)
|
99
|
+
subject.ssids = { tmp: ssid_dbl }
|
100
|
+
expect(subject.diagnostic_data[:ssids]).to eql({tmp: :datum})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context '#full_state' do
|
105
|
+
it 'should return a hash' do
|
106
|
+
expect(subject.full_state).to be_kind_of(Hash)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should include the keys expected by pulse' do
|
110
|
+
subject.track_ssid(essid: 'test')
|
111
|
+
subject.update(channel: 45, type: 'adhoc')
|
112
|
+
|
113
|
+
[:active, :bssid, :channel, :connected_clients, :ssids, :type, :vendor].each do |k|
|
114
|
+
expect(subject.full_state.key?(k)).to be_truthy
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should include the attributes of active ssids' do
|
119
|
+
subject.ssids = {}
|
120
|
+
ssid_dbl = double(PatronusFati::DataModels::Ssid)
|
121
|
+
expect(subject).to receive(:active_ssids).and_return({ pnt: ssid_dbl })
|
122
|
+
expect(ssid_dbl).to receive(:local_attributes).and_return('data')
|
123
|
+
expect(subject.full_state[:ssids]).to eql(['data'])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context '#initialize' do
|
128
|
+
it 'should set the local attributes to an appropriate hash' do
|
129
|
+
subject = described_class.new('12:23:34:45:56:67')
|
130
|
+
expect(subject.local_attributes).to eql(bssid: '12:23:34:45:56:67')
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should initialize client_macs to an empty array' do
|
134
|
+
expect(subject.client_macs).to be_kind_of(Array)
|
135
|
+
expect(subject.client_macs).to be_empty
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context '#mark_synced' do
|
140
|
+
it 'should clear dirty flags' do
|
141
|
+
subject.update(channel: 8)
|
142
|
+
expect(subject.data_dirty?).to be_truthy
|
143
|
+
expect { subject.mark_synced }
|
144
|
+
.to change { subject.data_dirty? }.from(true).to(false)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'should call mark_synced on each SSID as well' do
|
148
|
+
dbl = double(PatronusFati::DataModels::Ssid)
|
149
|
+
subject.ssids = { test: dbl }
|
150
|
+
|
151
|
+
expect(dbl).to receive(:mark_synced)
|
152
|
+
subject.mark_synced
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
context '#remove_client' do
|
157
|
+
it 'should make no changes if the provided mac isn\'t present' do
|
158
|
+
subject.client_macs = [ 'one' ]
|
159
|
+
expect { subject.remove_client('test') }.to_not change { subject.client_macs }
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should remove only the provided mac if other macs are present' do
|
163
|
+
subject.client_macs = [ 'a', 'b', 'c' ]
|
164
|
+
subject.remove_client('b')
|
165
|
+
|
166
|
+
expect(subject.client_macs).to eql(['a', 'c'])
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context '#track_ssid' do
|
171
|
+
let(:valid_ssid_data) { { essid: 'test' } }
|
172
|
+
|
173
|
+
it 'should create a new SSID instance if one doesn\'t already exist' do
|
174
|
+
expect(subject.ssids).to be_nil
|
175
|
+
expect { subject.track_ssid(valid_ssid_data) }.to change { subject.ssids }
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should mark the SSID as visible' do
|
179
|
+
ssid_dbl = double(PatronusFati::DataModels::Ssid)
|
180
|
+
pres_dbl = double(PatronusFati::Presence)
|
181
|
+
|
182
|
+
subject.ssids = { 'test' => ssid_dbl }
|
183
|
+
|
184
|
+
allow(ssid_dbl).to receive(:dirty?)
|
185
|
+
allow(ssid_dbl).to receive(:update)
|
186
|
+
expect(ssid_dbl).to receive(:presence).and_return(pres_dbl)
|
187
|
+
expect(pres_dbl).to receive(:mark_visible)
|
188
|
+
|
189
|
+
subject.track_ssid(valid_ssid_data)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should update the SSID with the attributes provided' do
|
193
|
+
subject.track_ssid(valid_ssid_data)
|
194
|
+
expect(subject.ssids['test'].presence).to receive(:mark_visible)
|
195
|
+
subject.track_ssid(valid_ssid_data)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should set the dirty children attribute if the SSID changed' do
|
199
|
+
expect(subject.sync_flag?(:dirtyChildren)).to be_falsey
|
200
|
+
subject.track_ssid(max_rate: 100)
|
201
|
+
expect(subject.sync_flag?(:dirtyChildren)).to be_truthy
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should not set the dirty children attribute if the SSID info didn\'t change' do
|
205
|
+
subject.track_ssid(valid_ssid_data)
|
206
|
+
subject.mark_synced
|
207
|
+
|
208
|
+
expect(subject.sync_flag?(:dirtyChildren)).to be_falsey
|
209
|
+
subject.track_ssid(valid_ssid_data)
|
210
|
+
expect(subject.sync_flag?(:dirtyChildren)).to be_falsey
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context '#update' do
|
215
|
+
it 'should not set invalid keys' do
|
216
|
+
expect { subject.update(bad: 'key') }
|
217
|
+
.to_not change { subject.local_attributes }
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'shouldn\'t modify the sync flags on invalid keys' do
|
221
|
+
expect { subject.update(other: 'key') }
|
222
|
+
.to_not change { subject.sync_status }
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'shouldn\'t modify the sync flags if the values haven\'t changed' do
|
226
|
+
expect { subject.update(subject.local_attributes) }
|
227
|
+
.to_not change { subject.sync_status }
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should set the dirty attribute flag when a value has changed' do
|
231
|
+
expect { subject.update(channel: 9) }
|
232
|
+
.to change { subject.sync_status }
|
233
|
+
expect(subject.sync_flag?(:dirtyAttributes)).to be_truthy
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context '#valid?' do
|
238
|
+
it 'should be true when all required attributes are set' do
|
239
|
+
subject.local_attributes = { bssid: 'something', channel: 4, type: 'adhoc' }
|
240
|
+
expect(subject).to be_valid
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'should not be valid when the channel is 0' do
|
244
|
+
subject.local_attributes = { bssid: 'something', channel: 0, type: 'adhoc' }
|
245
|
+
expect(subject).to_not be_valid
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'should be false when missing a required attribute' do
|
249
|
+
subject.local_attributes = { bssid: 'something', channel: 4 }
|
250
|
+
expect(subject).to_not be_valid
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context '#vendor' do
|
255
|
+
it 'should short circuit if no BSSID is available' do
|
256
|
+
expect(Louis).to_not receive(:lookup)
|
257
|
+
|
258
|
+
subject.update(bssid: nil)
|
259
|
+
subject.vendor
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'should use the Louis gem to perform it\'s lookup' do
|
263
|
+
inst = 'test string'
|
264
|
+
subject.update(bssid: inst)
|
265
|
+
|
266
|
+
expect(Louis).to receive(:lookup).with(inst).and_return({})
|
267
|
+
subject.vendor
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'should default the long vendor name if it\'s available' do
|
271
|
+
result = { 'long_vendor' => 'correct', 'short_vendor' => 'bad' }
|
272
|
+
expect(Louis).to receive(:lookup).and_return(result)
|
273
|
+
expect(subject.vendor).to eql('correct')
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'should fallback on the short vendor name if long isn\'t available' do
|
277
|
+
result = { 'short_vendor' => 'short' }
|
278
|
+
expect(Louis).to receive(:lookup).and_return(result)
|
279
|
+
expect(subject.vendor).to eql('short')
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|