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.
- 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
|