hidden-hippo 0.1.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +47 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +16 -0
  5. data/Gemfile +3 -0
  6. data/HACKING.md +78 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +31 -0
  9. data/Rakefile +5 -0
  10. data/Vagrantfile +24 -0
  11. data/bin/hh +4 -0
  12. data/ci/install-tshark.sh +21 -0
  13. data/config/gui-dev.ru +6 -0
  14. data/config/mongoid.yml +20 -0
  15. data/gui/public/favicon.png +0 -0
  16. data/gui/public/hidden-hippo.css +18 -0
  17. data/gui/public/user-placeholder.png +0 -0
  18. data/gui/views/dossier.rhtml +12 -0
  19. data/gui/views/index.rhtml +25 -0
  20. data/gui/views/layout.rhtml +31 -0
  21. data/gui/views/possibilities.rhtml +10 -0
  22. data/hidden-hippo.gemspec +31 -0
  23. data/hippo-small.png +0 -0
  24. data/hippo.png +0 -0
  25. data/lib/hidden_hippo/cli/app.rb +23 -0
  26. data/lib/hidden_hippo/cli/database.rb +47 -0
  27. data/lib/hidden_hippo/cli/gui.rb +51 -0
  28. data/lib/hidden_hippo/daemon.rb +90 -0
  29. data/lib/hidden_hippo/dossier.rb +23 -0
  30. data/lib/hidden_hippo/extractors/dhcp_hostname_extractor.rb +16 -0
  31. data/lib/hidden_hippo/extractors/dns_history_extractor.rb +25 -0
  32. data/lib/hidden_hippo/extractors/dns_llmnr_extractor.rb +18 -0
  33. data/lib/hidden_hippo/extractors/http_request_url_extractor.rb +15 -0
  34. data/lib/hidden_hippo/extractors/mdns_hostname_extractor.rb +18 -0
  35. data/lib/hidden_hippo/gui.rb +21 -0
  36. data/lib/hidden_hippo/packets/dhcp.rb +13 -0
  37. data/lib/hidden_hippo/packets/dns.rb +23 -0
  38. data/lib/hidden_hippo/packets/http.rb +13 -0
  39. data/lib/hidden_hippo/packets/packet.rb +73 -0
  40. data/lib/hidden_hippo/paths.rb +15 -0
  41. data/lib/hidden_hippo/possibilities.rb +63 -0
  42. data/lib/hidden_hippo/reader.rb +36 -0
  43. data/lib/hidden_hippo/scanner.rb +51 -0
  44. data/lib/hidden_hippo/update.rb +3 -0
  45. data/lib/hidden_hippo/updator.rb +49 -0
  46. data/lib/hidden_hippo/version.rb +3 -0
  47. data/lib/hidden_hippo.rb +23 -0
  48. data/spec/db_daemon_spec.rb +7 -0
  49. data/spec/dns_scanner_spec.rb +41 -0
  50. data/spec/dossier_spec.rb +72 -0
  51. data/spec/extractors/dhcp_hostname_extractor_spec.rb +43 -0
  52. data/spec/extractors/dns_history_extractor_spec.rb +52 -0
  53. data/spec/extractors/dns_llmnr_extractor_spec.rb +45 -0
  54. data/spec/extractors/http_request_url_extractor_spec.rb +23 -0
  55. data/spec/extractors/mdns_hostname_extractor_spec.rb +45 -0
  56. data/spec/fixtures/dns_elise.pcap +0 -0
  57. data/spec/fixtures/dns_reddit_eth.pcap +0 -0
  58. data/spec/fixtures/tcp_noise.pcap +0 -0
  59. data/spec/gui_daemon_spec.rb +7 -0
  60. data/spec/hidden_hippo_spec.rb +32 -0
  61. data/spec/packet_spec.rb +88 -0
  62. data/spec/possibilities_spec.rb +113 -0
  63. data/spec/spec_helper.rb +33 -0
  64. data/spec/support/cli_controller_examples.rb +136 -0
  65. data/spec/updator_spec.rb +37 -0
  66. metadata +274 -0
@@ -0,0 +1,52 @@
1
+ require 'thread'
2
+ require 'hidden_hippo/packets/dns'
3
+ require 'hidden_hippo/extractors/dns_history_extractor'
4
+
5
+ describe HiddenHippo::Extractors::DnsHistoryExtractor do
6
+ let(:queue) {Queue.new}
7
+ let(:extractor) {HiddenHippo::Extractors::DnsHistoryExtractor.new queue}
8
+
9
+ describe '#call' do
10
+ it 'should find the query_name/hostname in response' do
11
+ packet = HiddenHippo::Packets::Dns.new
12
+ packet.udp_dest_port = 53
13
+ packet.response = true
14
+ packet.eth_mac_src = 'some mac'
15
+ packet.eth_mac_dest = 'some other mac'
16
+ packet.dns_qry_name = 'dns resolution'
17
+
18
+ extractor.call packet
19
+
20
+ out = queue.pop true
21
+ expect(out).not_to be_nil
22
+ expect(out.mac_address).to eq 'some other mac'
23
+ expect(out.fields).to eq({history: 'dns resolution'})
24
+ end
25
+
26
+ it 'should find the query_name/hostname in query' do
27
+ packet = HiddenHippo::Packets::Dns.new
28
+ packet.udp_dest_port = 53
29
+ packet.response = false
30
+ packet.eth_mac_src = 'some mac'
31
+ packet.eth_mac_dest = 'some other mac'
32
+ packet.dns_qry_name = 'dns resolution'
33
+
34
+ extractor.call packet
35
+
36
+ out = queue.pop true
37
+ expect(out).not_to be_nil
38
+ expect(out.mac_address).to eq 'some mac'
39
+ expect(out.fields).to eq({history: 'dns resolution'})
40
+ end
41
+
42
+ it 'should ignore packets not on port 53' do
43
+ packet = HiddenHippo::Packets::Dns.new
44
+ packet.udp_dest_port = 42
45
+ packet.response = true
46
+
47
+ extractor.call packet
48
+
49
+ expect(queue.size).to eq 0
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ require 'thread'
2
+ require 'hidden_hippo/packets/dns'
3
+ require 'hidden_hippo/extractors/dns_llmnr_extractor'
4
+
5
+ describe HiddenHippo::Extractors::DnsLlmnrExtractor do
6
+ let(:queue) {Queue.new}
7
+ let(:extractor) {HiddenHippo::Extractors::DnsLlmnrExtractor.new queue}
8
+
9
+ describe '#call' do
10
+ it 'should find the query_name/hostname in response' do
11
+ packet = HiddenHippo::Packets::Dns.new
12
+ packet.udp_dest_port = 5355
13
+ packet.response = false
14
+ packet.eth_mac_src = 'some mac'
15
+ packet.dns_qry_name = 'the host name'
16
+
17
+ extractor.call packet
18
+
19
+ out = queue.pop true
20
+ expect(out).not_to be_nil
21
+ expect(out.mac_address).to eq 'some mac'
22
+ expect(out.fields).to eq({hostname: 'the host name'})
23
+ end
24
+
25
+ it 'should ignore packets not on port 5355' do
26
+ packet = HiddenHippo::Packets::Dns.new
27
+ packet.udp_dest_port = 42
28
+ packet.response = true
29
+
30
+ extractor.call packet
31
+
32
+ expect(queue.size).to eq 0
33
+ end
34
+
35
+ it 'should ignore non request' do
36
+ packet = HiddenHippo::Packets::Dns.new
37
+ packet.udp_dest_port = 5353
38
+ packet.response = true
39
+
40
+ extractor.call packet
41
+
42
+ expect(queue.size).to eq 0
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ require 'thread'
2
+ require 'hidden_hippo/packets/http'
3
+ require 'hidden_hippo/extractors/http_request_url_extractor'
4
+
5
+ describe HiddenHippo::Extractors::HttpRequestUrlExtractor do
6
+ let(:queue) {Queue.new}
7
+ let(:extractor) {HiddenHippo::Extractors::HttpRequestUrlExtractor.new queue}
8
+
9
+ describe '#call' do
10
+ it 'should have an url' do
11
+ packet = HiddenHippo::Packets::Http.new
12
+ packet.eth_mac_src = 'http some mac'
13
+ packet.full_uri = 'blob.com/totolatulipe?$=0.com'
14
+
15
+ extractor.call packet
16
+
17
+ out = queue.pop true
18
+ expect(out.mac_address).to eq 'http some mac'
19
+ expect(out.fields).to eq({history: 'blob.com/totolatulipe?$=0.com'})
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,45 @@
1
+ require 'thread'
2
+ require 'hidden_hippo/packets/dns'
3
+ require 'hidden_hippo/extractors/mdns_hostname_extractor'
4
+
5
+ describe HiddenHippo::Extractors::MdnsHostnameExtractor do
6
+ let(:queue) {Queue.new}
7
+ let(:extractor) {HiddenHippo::Extractors::MdnsHostnameExtractor.new queue}
8
+
9
+ describe '#call' do
10
+ it 'should find the hostname in a mdns ptr response' do
11
+ packet = HiddenHippo::Packets::Dns.new
12
+ packet.udp_dest_port = 5353
13
+ packet.response = true
14
+ packet.eth_mac_src = 'some mac'
15
+ packet.ptr = 'the host name'
16
+
17
+ extractor.call packet
18
+
19
+ out = queue.pop true
20
+ expect(out).not_to be_nil
21
+ expect(out.mac_address).to eq 'some mac'
22
+ expect(out.fields).to eq({hostname: 'the host name'})
23
+ end
24
+
25
+ it 'should ignore packets not on port 5353' do
26
+ packet = HiddenHippo::Packets::Dns.new
27
+ packet.udp_dest_port = 42
28
+ packet.response = true
29
+
30
+ extractor.call packet
31
+
32
+ expect(queue.size).to eq 0
33
+ end
34
+
35
+ it 'should ignore non responses' do
36
+ packet = HiddenHippo::Packets::Dns.new
37
+ packet.udp_dest_port = 5353
38
+ packet.response = false
39
+
40
+ extractor.call packet
41
+
42
+ expect(queue.size).to eq 0
43
+ end
44
+ end
45
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ require 'support/cli_controller_examples'
2
+
3
+ describe 'hh gui', :noisy do
4
+ let(:name) {'gui'}
5
+
6
+ it_behaves_like 'cli daemon controller'
7
+ end
@@ -0,0 +1,32 @@
1
+ require 'hidden_hippo'
2
+ require 'mongoid'
3
+
4
+ describe HiddenHippo do
5
+ it 'should have a version' do
6
+ expect(HiddenHippo::VERSION).not_to be_nil
7
+ end
8
+
9
+ describe '#pid_exists?' do
10
+ it 'should return true for an existing pid' do
11
+ expect(HiddenHippo.pid_exists? Process.pid).to be_truthy
12
+ end
13
+
14
+ it 'should return false for a non existing pid' do
15
+ expect(HiddenHippo.pid_exists? 99999).to be_falsey
16
+ end
17
+ end
18
+
19
+ describe '#gem_root' do
20
+ it 'should point to the root of the gem' do
21
+ expect(HiddenHippo.gem_root).to eq Pathname.new(__FILE__) + '../..'
22
+ end
23
+ end
24
+
25
+ describe '#configure_db!' do
26
+ it 'should configure mongoid' do
27
+ HiddenHippo.configure_db!
28
+
29
+ expect(Mongoid.configured?).to be_truthy
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,88 @@
1
+ require 'hidden_hippo/packets/packet'
2
+
3
+ describe HiddenHippo::Packets::Packet do
4
+
5
+ describe '#field' do
6
+ it 'should create accessors' do
7
+ packet_class = Class.new(HiddenHippo::Packets::Packet) do
8
+ field :foobar
9
+ end
10
+
11
+ instance = packet_class.new
12
+
13
+ expect(instance).to respond_to :foobar, :foobar=
14
+ end
15
+
16
+ it 'should add to the fields list' do
17
+ packet_class = Class.new(HiddenHippo::Packets::Packet) do
18
+ field :foobar
19
+ end
20
+
21
+ expect(packet_class.fields.map &:name).to include :foobar
22
+ end
23
+ end
24
+
25
+ describe '#parse' do
26
+ it 'should parse using the tshark fields' do
27
+ klass = Class.new HiddenHippo::Packets::Packet do
28
+ field :foobar, tshark: 'proto.foobar'
29
+ end
30
+
31
+ instance = klass.parse 'proto.foobar' => 'butthole'
32
+
33
+ expect(instance.foobar).to eq 'butthole'
34
+ end
35
+
36
+ it 'should convert when the converter is set' do
37
+ klass = Class.new HiddenHippo::Packets::Packet do
38
+ field :answer, tshark: 'magic.number', conv: ->(a) {a.to_i}
39
+ end
40
+
41
+ instance = klass.parse 'magic.number' => '42'
42
+
43
+ expect(instance.answer).to eq 42
44
+ end
45
+
46
+ it 'should convert with symbols' do
47
+ klass = Class.new HiddenHippo::Packets::Packet do
48
+ field :answer, tshark: 'magic.number', conv: :to_i
49
+ end
50
+
51
+ instance = klass.parse 'magic.number' => '42'
52
+
53
+ expect(instance.answer).to eq 42
54
+ end
55
+
56
+ it 'should ignore unknow fields' do
57
+ klass = Class.new HiddenHippo::Packets::Packet
58
+
59
+ expect{klass.parse 'not.there' => 'foobar'}.not_to raise_error
60
+ end
61
+ end
62
+
63
+ describe '#filter' do
64
+ it 'should return the filter set' do
65
+ klass = Class.new HiddenHippo::Packets::Packet do
66
+ filter 'foobarbaz'
67
+ end
68
+
69
+ expect(klass.filter).to eq 'foobarbaz'
70
+ end
71
+ end
72
+
73
+ describe '#tshark_fields' do
74
+ it 'should return the tshark fields for all the fields' do
75
+ klass = Class.new HiddenHippo::Packets::Packet do
76
+ field :thing, tshark: 'find.me'
77
+ end
78
+
79
+ expect(klass.tshark_fields).to include 'find.me'
80
+ end
81
+
82
+ it 'should have default mac address fields' do
83
+ klass = Class.new HiddenHippo::Packets::Packet
84
+
85
+ expect(klass.tshark_fields).to include 'eth.src', 'eth.dst', 'wlan.sa', 'wlan.da'
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,113 @@
1
+ require 'hidden_hippo'
2
+ require 'hidden_hippo/possibilities'
3
+
4
+ describe HiddenHippo::Possibilities do
5
+ let(:possibilities) {HiddenHippo::Possibilities.new}
6
+
7
+ describe '#initialize' do
8
+ it 'should set the base counts' do
9
+ possibilities = HiddenHippo::Possibilities.new 'foo' => 3
10
+
11
+ expect(possibilities['foo']).to eq 3
12
+ end
13
+
14
+ it 'should ignore base counts that are not strings' do
15
+ possibilities = HiddenHippo::Possibilities.new 'foo' => 'potato'
16
+
17
+ expect(possibilities['foo']).to eq 0
18
+ end
19
+ end
20
+
21
+ describe '#<<' do
22
+ it 'should add a possible value' do
23
+ possibilities << 'foo'
24
+ possibilities << 'biz'
25
+ possibilities << 'baz'
26
+
27
+ expect(possibilities).to contain_exactly 'foo', 'biz', 'baz'
28
+ end
29
+
30
+ it 'should increment the support count of an existing value' do
31
+ possibilities << 'foo'
32
+ possibilities << 'foo'
33
+
34
+ expect(possibilities['foo']).to eq 2
35
+ end
36
+
37
+ it 'should convert values to string' do
38
+ possibilities << 42
39
+
40
+ expect(possibilities).to contain_exactly '42'
41
+ end
42
+
43
+ it 'should replace dots with underscores' do
44
+ possibilities << 'dat.thing'
45
+
46
+ expect(possibilities['dat.thing']).to eq 0
47
+ expect(possibilities['dat_thing']).to eq 1
48
+ end
49
+
50
+ it 'should replace dollar signs with underscores' do
51
+ possibilities << 'dat$thing'
52
+
53
+ expect(possibilities['dat$thing']).to eq 0
54
+ expect(possibilities['dat_thing']).to eq 1
55
+ end
56
+ end
57
+
58
+ describe '#[]' do
59
+ it 'should return the support count' do
60
+ 5.times do
61
+ possibilities << 'foo'
62
+ end
63
+
64
+ expect(possibilities['foo']).to eq 5
65
+ end
66
+
67
+ it 'should return 0 when no support count' do
68
+ expect(possibilities['not there']).to eq 0
69
+ end
70
+ end
71
+
72
+ describe '#each' do
73
+ it 'should enumerate by support count' do
74
+ possibilities << 'fi'
75
+ possibilities << 'fo'
76
+ possibilities << 'fo'
77
+ possibilities << 'fum'
78
+ possibilities << 'fum'
79
+ possibilities << 'fum'
80
+
81
+ expect{|b| possibilities.each &b}.to yield_successive_args 'fum', 'fo', 'fi'
82
+ end
83
+
84
+ it 'should return an enumerator without a block' do
85
+ possibilities << 'fi'
86
+ possibilities << 'fo'
87
+ possibilities << 'fum'
88
+
89
+ expect(possibilities.each).to be_kind_of Enumerator
90
+ end
91
+ end
92
+
93
+ describe '#each_with_support' do
94
+ it 'should enumerate by and with support count' do
95
+ possibilities << 'fi'
96
+ possibilities << 'fo'
97
+ possibilities << 'fo'
98
+ possibilities << 'fum'
99
+ possibilities << 'fum'
100
+ possibilities << 'fum'
101
+
102
+ expect{|b| possibilities.each_with_support &b}.to yield_successive_args ['fum', 3], ['fo', 2], ['fi', 1]
103
+ end
104
+
105
+ it 'should return an enumerator without a block' do
106
+ possibilities << 'fi'
107
+ possibilities << 'fo'
108
+ possibilities << 'fum'
109
+
110
+ expect(possibilities.each_with_support).to be_kind_of Enumerator
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,33 @@
1
+ require 'mongoid'
2
+ require 'hidden_hippo'
3
+ require 'shoulda-matchers'
4
+ require 'rspec/retry'
5
+
6
+ RSpec.configure do |config|
7
+ config.include Shoulda::Matchers::ActiveModel
8
+
9
+ stdout = $stdout
10
+ stderr = $stderr
11
+
12
+ config.before :each, noisy: true do
13
+ $stdout = StringIO.new
14
+ $stderr = StringIO.new
15
+ end
16
+
17
+ config.after :each, noisy: true do
18
+ $stdout = stdout
19
+ $stderr = stderr
20
+ end
21
+
22
+ config.before :all, db: true do
23
+ HiddenHippo.configure_db! :test
24
+ end
25
+
26
+ config.expect_with :rspec do |expectations|
27
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
28
+ end
29
+
30
+ config.mock_with :rspec do |mocks|
31
+ mocks.verify_partial_doubles = true
32
+ end
33
+ end
@@ -0,0 +1,136 @@
1
+ require 'hidden_hippo'
2
+ require 'tmpdir'
3
+ require 'pathname'
4
+
5
+ # Shared examples for the daemon controller commands
6
+ # it expects to have `name` defined. For example:
7
+ #
8
+ # describe 'hh thing' do
9
+ # let(:name) {'thing'}
10
+ # it_behaves_like 'cli daemon controller'
11
+ # end
12
+ shared_examples 'cli daemon controller' do
13
+ let(:home) { Pathname.new(Dir.mktmpdir) }
14
+ let(:pid_file) { home + 'pid' + "#{name}.pid" }
15
+ let(:log_file) { home + 'log' + "#{name}.log" }
16
+
17
+ def start
18
+ HiddenHippo::Cli::App.start [name, 'start']
19
+ end
20
+
21
+ def stop
22
+ HiddenHippo::Cli::App.start [name, 'stop']
23
+ end
24
+
25
+ def status
26
+ HiddenHippo::Cli::App.start [name, 'status']
27
+ end
28
+
29
+ before do
30
+ ENV['HIDDEN_HIPPO_HOME'] = home.to_s
31
+ end
32
+
33
+ after do
34
+ if pid_file.exist?
35
+ begin
36
+ pid = pid_file.read.to_i
37
+ Process.detach pid # keep the zombies at bay
38
+ Process.kill 9, pid
39
+ rescue
40
+ # do nothing
41
+ end
42
+ end
43
+
44
+ home.rmtree
45
+ end
46
+
47
+ describe 'start' do
48
+ it 'should start the daemon' do
49
+ start
50
+ pid = pid_file.read.to_i
51
+
52
+ expect(HiddenHippo.pid_exists? pid).to be_truthy
53
+ end
54
+
55
+ it 'should create a pid file' do
56
+ start
57
+
58
+ expect(pid_file).to exist
59
+ end
60
+
61
+ it 'should log to file', retry: 3 do
62
+ start
63
+ sleep 0.5
64
+
65
+ expect(log_file).to exist
66
+ end
67
+
68
+ it 'replace stale pid file' do
69
+ pid_file.dirname.mkpath
70
+ File.write pid_file, '12345'
71
+
72
+ start
73
+
74
+ expect(pid_file.read.to_i).not_to eq 12345
75
+ end
76
+
77
+ it 'should complain if daemon is already running' do
78
+ start
79
+
80
+ expect{start}.to raise_error SystemExit do |error|
81
+ expect(error.status).not_to eq 0
82
+ end
83
+ end
84
+ end
85
+
86
+ describe 'stop' do
87
+ it 'should kill the process', retry: 3 do
88
+ start
89
+ pid = pid_file.read.to_i
90
+ Process.detach pid # make sure we don't get a zombie
91
+
92
+ stop
93
+ sleep 0.5
94
+
95
+ expect(HiddenHippo.pid_exists? pid).to be_falsey
96
+ end
97
+
98
+ it 'should delete the pid file' do
99
+ start
100
+ stop
101
+
102
+ expect(pid_file).not_to exist
103
+ end
104
+
105
+ it 'should complain if the pid file is missing' do
106
+ expect{stop}.to raise_error SystemExit do |error|
107
+ expect(error.status).not_to eq 0
108
+ end
109
+ end
110
+ end
111
+
112
+ describe 'status' do
113
+ it 'should return 0 if running' do
114
+ start
115
+
116
+ expect{status}.to raise_error SystemExit do |error|
117
+ expect(error.status).to eq 0
118
+ end
119
+ end
120
+
121
+ it 'should return 1 if not running' do
122
+ expect{status}.to raise_error SystemExit do |error|
123
+ expect(error.status).to eq 1
124
+ end
125
+ end
126
+
127
+ it 'should return 2 if not running but pid file is present' do
128
+ pid_file.dirname.mkpath
129
+ File.write pid_file, '99999'
130
+
131
+ expect{status}.to raise_error SystemExit do |error|
132
+ expect(error.status).to eq 2
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,37 @@
1
+ require 'hidden_hippo/dossier'
2
+ require 'hidden_hippo/update'
3
+ require 'hidden_hippo/updator'
4
+
5
+ describe HiddenHippo::Updator, :db do
6
+ let(:queue) {Queue.new}
7
+ let(:updator) {HiddenHippo::Updator.new queue}
8
+
9
+ before do
10
+ HiddenHippo::Dossier.delete_all
11
+ end
12
+
13
+ after do
14
+ updator.stop
15
+ end
16
+
17
+ it 'should add the fields to the database from the queue' do
18
+ HiddenHippo::Dossier.create mac_address: 'find me'
19
+ queue << HiddenHippo::Update.new('find me', {name: 'John Doe'})
20
+
21
+ updator.start
22
+ sleep 0.5
23
+
24
+ dossier = HiddenHippo::Dossier.find 'find me'
25
+ expect(dossier.name['John Doe']).to eq 1
26
+ end
27
+
28
+ it 'should create the dossier if it is missing' do
29
+ queue << HiddenHippo::Update.new('not there', {hostname: 'Bobby-PC'})
30
+
31
+ updator.start
32
+ sleep 0.5
33
+
34
+ dossier = HiddenHippo::Dossier.find 'not there'
35
+ expect(dossier.hostname['Bobby-PC']).to eq 1
36
+ end
37
+ end