threatinator 0.1.6 → 0.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.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -2
  3. data/Gemfile +18 -13
  4. data/Rakefile +1 -1
  5. data/VERSION +1 -1
  6. data/feeds/ET_block-ip_reputation.feed +26 -0
  7. data/feeds/ET_openbadlist-ip_reputation.feed +35 -0
  8. data/feeds/bambenek_c2_masterlist-domain_reputation.feed +15 -0
  9. data/feeds/bambenek_c2_masterlist-ip_reputation.feed +15 -0
  10. data/feeds/bambenek_dga_feed-domain_reputation.feed +15 -0
  11. data/feeds/berkeley-ip_reputation.feed +23 -0
  12. data/feeds/bitcash_cz_blacklist.feed +20 -0
  13. data/feeds/botscout-ip_reputation.feed +24 -0
  14. data/feeds/cert_mxpoison-ip_reputation.feed +21 -0
  15. data/feeds/chaosreigns-ip_reputation.feed +36 -0
  16. data/feeds/cydef_torexit-ip_reputation.feed +24 -0
  17. data/feeds/danger_bruteforce-ip_reputation.feed +23 -0
  18. data/feeds/falconcrest-ip_reputation.feed +18 -0
  19. data/feeds/h3x_asprox.feed +17 -0
  20. data/feeds/hosts-file_hphostspartial-domain_reputation.feed +18 -0
  21. data/feeds/infiltrated_vabl-ip_reputation.feed +29 -0
  22. data/feeds/isc_suspicious_high-domain_reputation.feed +25 -0
  23. data/feeds/isc_suspicious_low-domain_reputation.feed +25 -0
  24. data/feeds/isc_suspicious_medium-domain_reputation.feed +25 -0
  25. data/feeds/malwaredomainlist-url_reputation.feed +16 -0
  26. data/feeds/malwaredomains-domain_reputation.feed +27 -0
  27. data/feeds/malwaredomains_dyndns-domain_reputation.feed +27 -0
  28. data/feeds/malwaredomains_justdomains-domain_reputation.feed +18 -0
  29. data/feeds/multiproxy-ip_reputation.feed +20 -0
  30. data/feeds/openphish-url_reputation.feed +22 -0
  31. data/feeds/packetmail_perimeterbad-ip_reputation.feed +26 -0
  32. data/feeds/phishtank.feed +1 -1
  33. data/feeds/sigmaproject_atma.feed +25 -0
  34. data/feeds/sigmaproject_spyware.feed +24 -0
  35. data/feeds/sigmaproject_webexploit.feed +26 -0
  36. data/feeds/snort_bpf-ip_reputation.feed +19 -0
  37. data/feeds/steeman-ip_reputation.feed +19 -0
  38. data/feeds/trustedsec-ip_reputation.feed +17 -0
  39. data/feeds/virbl-ip_reputation.feed +24 -0
  40. data/feeds/vxvault-url_reputation.feed +22 -0
  41. data/feeds/yoyo_adservers-domain_reputation.feed +16 -0
  42. data/lib/threatinator/actions/run/action.rb +15 -3
  43. data/lib/threatinator/actions/run/coverage_observer.rb +12 -7
  44. data/lib/threatinator/actions/run/status_observer.rb +37 -0
  45. data/lib/threatinator/cli.rb +9 -3
  46. data/lib/threatinator/cli/parser.rb +14 -4
  47. data/lib/threatinator/config.rb +1 -0
  48. data/lib/threatinator/config/logger.rb +14 -0
  49. data/lib/threatinator/event.rb +28 -18
  50. data/lib/threatinator/event_builder.rb +52 -23
  51. data/lib/threatinator/exceptions.rb +3 -6
  52. data/lib/threatinator/feed.rb +1 -1
  53. data/lib/threatinator/feed_runner.rb +63 -7
  54. data/lib/threatinator/logger.rb +66 -0
  55. data/lib/threatinator/logging.rb +20 -0
  56. data/lib/threatinator/model/base.rb +23 -0
  57. data/lib/threatinator/model/collection.rb +64 -0
  58. data/lib/threatinator/model/observables/fqdn_collection.rb +13 -0
  59. data/lib/threatinator/model/observables/ipv4.rb +30 -0
  60. data/lib/threatinator/model/observables/ipv4_collection.rb +14 -0
  61. data/lib/threatinator/model/observables/url_collection.rb +16 -0
  62. data/lib/threatinator/model/validations.rb +1 -0
  63. data/lib/threatinator/model/validations/type.rb +21 -0
  64. data/lib/threatinator/plugins/output/csv.rb +20 -9
  65. data/spec/feeds/ET_block-ip_reputation_spec.rb +50 -0
  66. data/spec/feeds/ET_compromised-ip_reputation_spec.rb +2 -5
  67. data/spec/feeds/ET_openbadlist-ip_reputation_spec.rb +56 -0
  68. data/spec/feeds/alienvault-ip_reputation_spec.rb +2 -5
  69. data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +0 -3
  70. data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +2 -5
  71. data/spec/feeds/autoshun_shunlist_spec.rb +1 -4
  72. data/spec/feeds/bambenek_c2_masterlist-domain_reputation_spec.rb +39 -0
  73. data/spec/feeds/bambenek_c2_masterlist-ip_reputation_spec.rb +39 -0
  74. data/spec/feeds/bambenek_dga_feed-domain_reputation_spec.rb +39 -0
  75. data/spec/feeds/berkeley-ip_reputation_spec.rb +47 -0
  76. data/spec/feeds/bitcash_cz_blacklist-ip_reputation_spec.rb +50 -0
  77. data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +2 -5
  78. data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +2 -5
  79. data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +2 -5
  80. data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +2 -5
  81. data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +2 -5
  82. data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +2 -5
  83. data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +2 -5
  84. data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +2 -5
  85. data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +2 -5
  86. data/spec/feeds/botscout-ip_reputation_spec.rb +50 -0
  87. data/spec/feeds/cert_mxpoison-ip_reputation_spec.rb +47 -0
  88. data/spec/feeds/chaosreigns-ip_reputation_spec.rb +50 -0
  89. data/spec/feeds/ciarmy-ip_reputation_spec.rb +2 -5
  90. data/spec/feeds/cruzit-ip_reputation_spec.rb +2 -5
  91. data/spec/feeds/cydef_torexit-ip_reputation_spec.rb +47 -0
  92. data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +2 -5
  93. data/spec/feeds/danger_bruteforce-ip_reputation_spec.rb +47 -0
  94. data/spec/feeds/data/ET_block-ip_reputation.txt +80 -0
  95. data/spec/feeds/data/ET_openbadlist-ip_reputation.txt +62 -0
  96. data/spec/feeds/data/bambenek_c2-dommasterlist.csv +30 -0
  97. data/spec/feeds/data/bambenek_c2-ipmasterlist.csv +27 -0
  98. data/spec/feeds/data/bambenek_dga_feed.csv +42 -0
  99. data/spec/feeds/data/berkeley.txt +29 -0
  100. data/spec/feeds/data/bitcash_cz_blacklist.txt +7 -0
  101. data/spec/feeds/data/botscout-ip-reputation.txt +713 -0
  102. data/spec/feeds/data/cert_mxpoison-ip_reputation.txt +17 -0
  103. data/spec/feeds/data/chaosreigns-ip-reputation.txt +26 -0
  104. data/spec/feeds/data/cydef_torexit-ip_reputation.txt +27 -0
  105. data/spec/feeds/data/danger_bruteforce-ip_reputation.txt +12 -0
  106. data/spec/feeds/data/falconcrest_iplist.txt +345 -0
  107. data/spec/feeds/data/h3x_asprox.txt +20 -0
  108. data/spec/feeds/data/hosts-file_hphostspartial_domainlist.txt +24 -0
  109. data/spec/feeds/data/infiltrated_vabl_iplist.txt +33 -0
  110. data/spec/feeds/data/isc_suspicious_high_domainlist.txt +26 -0
  111. data/spec/feeds/data/isc_suspicious_low_domainlist.txt +34 -0
  112. data/spec/feeds/data/isc_suspicious_medium_domainlist.txt +32 -0
  113. data/spec/feeds/data/malwaredomainlist-url-reputation.txt +8 -0
  114. data/spec/feeds/data/malwaredomains_domainlist.txt +24 -0
  115. data/spec/feeds/data/malwaredomains_dyndns_domainlist.txt +34 -0
  116. data/spec/feeds/data/malwaredomains_justdomains_domainlist.txt +18 -0
  117. data/spec/feeds/data/multiproxy_iplist.txt +15 -0
  118. data/spec/feeds/data/openphish-url-reputation.txt +16 -0
  119. data/spec/feeds/data/packetmail_perimeterbad-ip_reputation.txt +44 -0
  120. data/spec/feeds/data/sigmaproject_atma.return.gz +0 -0
  121. data/spec/feeds/data/sigmaproject_spyware.return.gz +0 -0
  122. data/spec/feeds/data/sigmaproject_webexploit.return.gz +0 -0
  123. data/spec/feeds/data/snort_bpf-ip_reputation.txt +16 -0
  124. data/spec/feeds/data/steeman-ip-reputation.txt +13 -0
  125. data/spec/feeds/data/trustedsec-ip-reputation.txt +12 -0
  126. data/spec/feeds/data/virbl-ip_reputation.txt +14 -0
  127. data/spec/feeds/data/vxvault-url-reputation.txt +15 -0
  128. data/spec/feeds/data/yoyo_adservers.txt +25 -0
  129. data/spec/feeds/dshield_attackers-top1000_spec.rb +1 -4
  130. data/spec/feeds/falconcrest-ip_reputation_spec.rb +37 -0
  131. data/spec/feeds/feodo-domain_reputation_spec.rb +0 -3
  132. data/spec/feeds/feodo-ip_reputation_spec.rb +2 -5
  133. data/spec/feeds/h3x_asprox-ip_reputation_spec.rb +50 -0
  134. data/spec/feeds/hosts-file_hphostspartial-domain_reputation_spec.rb +47 -0
  135. data/spec/feeds/infiltrated-ip_reputation_spec.rb +2 -5
  136. data/spec/feeds/infiltrated_vabl-ip_reputation_spec.rb +47 -0
  137. data/spec/feeds/isc_suspicious_high-domain_reputation_spec.rb +47 -0
  138. data/spec/feeds/isc_suspicious_low-domain_reputation_spec.rb +47 -0
  139. data/spec/feeds/isc_suspicious_medium-domain_reputation_spec.rb +47 -0
  140. data/spec/feeds/malc0de-domain_reputation_spec.rb +0 -3
  141. data/spec/feeds/malc0de-ip_reputation_spec.rb +2 -5
  142. data/spec/feeds/malwaredomainlist_url_reputation_spec.rb +50 -0
  143. data/spec/feeds/malwaredomains-domain_reputation_spec.rb +47 -0
  144. data/spec/feeds/malwaredomains_dyndns-domain_reputation_spec.rb +47 -0
  145. data/spec/feeds/malwaredomains_justdomains-domain_reputation_spec.rb +47 -0
  146. data/spec/feeds/mirc-domain_reputation_spec.rb +0 -3
  147. data/spec/feeds/multiproxy-ip_reputation_spec.rb +47 -0
  148. data/spec/feeds/nothink_irc-ip_reputation_spec.rb +2 -5
  149. data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +2 -5
  150. data/spec/feeds/openbl-ip_reputation_spec.rb +2 -5
  151. data/spec/feeds/openphish_url_reputation_spec.rb +50 -0
  152. data/spec/feeds/packetmail_perimeterbad-ip_reputation_spec.rb +47 -0
  153. data/spec/feeds/palevo-domain_reputation_spec.rb +0 -3
  154. data/spec/feeds/palevo-ip_reputation_spec.rb +2 -5
  155. data/spec/feeds/phishtank_spec.rb +2 -5
  156. data/spec/feeds/sigmaproject_atma_spec.rb +63 -0
  157. data/spec/feeds/sigmaproject_spyware_spec.rb +64 -0
  158. data/spec/feeds/sigmaproject_webexploit_spec.rb +63 -0
  159. data/spec/feeds/snort_bpf-ip_reputation_spec.rb +47 -0
  160. data/spec/feeds/spyeye-domain_reputation_spec.rb +0 -3
  161. data/spec/feeds/spyeye-ip_reputation_spec.rb +2 -5
  162. data/spec/feeds/steeman-ip_reputation_spec.rb +50 -0
  163. data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +2 -5
  164. data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +2 -5
  165. data/spec/feeds/trustedsec-ip_reputation_spec.rb +47 -0
  166. data/spec/feeds/virbl-ip_reputation_spec.rb +47 -0
  167. data/spec/feeds/vxvault_url_reputation_spec.rb +50 -0
  168. data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +2 -5
  169. data/spec/feeds/yoyo_adservers_spec.rb +47 -0
  170. data/spec/feeds/zeus-domain_reputation_spec.rb +0 -3
  171. data/spec/feeds/zeus-ip_reputation_spec.rb +2 -5
  172. data/spec/spec_helper.rb +2 -0
  173. data/spec/support/factories/event.rb +11 -7
  174. data/spec/support/factories/feed.rb +28 -1
  175. data/spec/support/factories/ipv4.rb +36 -0
  176. data/spec/support/factories/url.rb +34 -0
  177. data/spec/support/shared/feed_runner_observer.rb +136 -0
  178. data/spec/support/shared/feeds.rb +19 -4
  179. data/spec/support/shared/model/collection.rb +164 -0
  180. data/spec/threatinator/actions/run/action_spec.rb +27 -10
  181. data/spec/threatinator/actions/run/coverage_observer_spec.rb +39 -4
  182. data/spec/threatinator/actions/run/status_observer_spec.rb +86 -0
  183. data/spec/threatinator/event_builder_spec.rb +111 -21
  184. data/spec/threatinator/event_spec.rb +237 -13
  185. data/spec/threatinator/event_spec.rb.new +319 -0
  186. data/spec/threatinator/feed_builder_spec.rb +0 -3
  187. data/spec/threatinator/feed_runner_spec.rb +254 -70
  188. data/spec/threatinator/logger_spec.rb +29 -0
  189. data/spec/threatinator/model/observables/fqdn_collection_spec.rb +42 -0
  190. data/spec/threatinator/model/observables/ipv4_collection_spec.rb +36 -0
  191. data/spec/threatinator/model/observables/ipv4_spec.rb +75 -0
  192. data/spec/threatinator/model/observables/url_collection_spec.rb +45 -0
  193. data/spec/threatinator/model/validations/type_spec.rb +37 -0
  194. data/spec/threatinator/plugins/output/csv_spec.rb +4 -3
  195. metadata +216 -19
  196. data/lib/threatinator/property_definer.rb +0 -101
  197. data/spec/threatinator/property_definer_spec.rb +0 -155
@@ -16,12 +16,21 @@ end
16
16
 
17
17
  shared_context 'a parsed feed' do
18
18
  # expects :observer
19
- let(:events) { observer.map { |status, record, events| events } }
20
- let(:records) { observer.map { |status, record, events| record } }
19
+ let(:events) { observer.events }
20
+ let(:records) { observer.records }
21
21
  let(:num_records) { observer.count }
22
22
  let(:num_records_filtered) { observer.num_records_filtered }
23
23
  let(:num_records_parsed) { observer.num_records_parsed }
24
24
  let(:num_records_missed) { observer.num_records_missed }
25
+ let(:num_records_errored) { observer.num_records_errored }
26
+
27
+ it "should have 0 error records" do
28
+ expect(num_records_errored).to eq(0)
29
+ end
30
+
31
+ it "should have missed 0 records" do
32
+ expect(num_records_missed).to eq(0)
33
+ end
25
34
  end
26
35
 
27
36
  shared_context 'for feeds', :feed => lambda { true } do
@@ -114,7 +123,7 @@ module FeedHelpers
114
123
  class FeedRunnerObserver
115
124
  include Enumerable
116
125
  attr_reader :records, :statuses, :events, :num_records_filtered,
117
- :num_records_missed, :num_records_parsed
126
+ :num_records_missed, :num_records_parsed, :num_records_errored
118
127
 
119
128
  def initialize
120
129
  @records = []
@@ -123,6 +132,7 @@ module FeedHelpers
123
132
  @num_records_filtered = 0
124
133
  @num_records_parsed = 0
125
134
  @num_records_missed = 0
135
+ @num_records_errored = 0
126
136
  end
127
137
 
128
138
  def each
@@ -149,6 +159,11 @@ module FeedHelpers
149
159
  @statuses << :parsed
150
160
  @events << args.shift
151
161
  @num_records_parsed += 1
162
+ when :record_error
163
+ @records << args.shift
164
+ @statuses << :error
165
+ @events << []
166
+ @num_records_errored += 1
152
167
  end
153
168
  end
154
169
  end
@@ -174,7 +189,7 @@ module FeedHelpers
174
189
  before :each do
175
190
  sio = StringIO.new(data)
176
191
  feed_runner.add_observer(observer)
177
- feed_runner.run(:io => sio)
192
+ feed_runner.run(:io => sio, :skip_decoding => true)
178
193
  @status, @record, @events = observer.first
179
194
  end
180
195
 
@@ -0,0 +1,164 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "a model collection" do
4
+ # expect generate_ten_valid_members, generate_invalid_members
5
+ let(:collection) { described_class.new }
6
+
7
+ describe "initialize" do
8
+ context "when no parameters are provided" do
9
+ specify "the collection is empty" do
10
+ x = described_class.new
11
+ expect(x).to be_empty
12
+ end
13
+ end
14
+
15
+ context "when an array of members is provided" do
16
+ it "calls #<< for each member, adding it to the collection" do
17
+ members = generate_ten_valid_members()
18
+ expect_any_instance_of(described_class).to receive(:<<).exactly(10).times.and_call_original
19
+ x = described_class.new(members)
20
+ expect(x.to_a).to match_array(members)
21
+ end
22
+ end
23
+ end
24
+ describe "#<<(member)" do
25
+ it "adds the member to the collection" do
26
+ member = generate_ten_valid_members()[0]
27
+ expect(collection.include?(member)).to eq(false)
28
+ collection << member
29
+ expect(collection.include?(member)).to eq(true)
30
+ end
31
+
32
+ it "does not add the member if it is equal to any member of the collection" do
33
+ member = generate_ten_valid_members()[0]
34
+ expect(collection.count).to eq(0)
35
+ collection << member
36
+ expect(collection.count).to eq(1)
37
+ collection << member
38
+ expect(collection.count).to eq(1)
39
+ end
40
+
41
+ it "raises InvalidAttributeError if the added member is not valid for the collection" do
42
+ expect {
43
+ collection << generate_invalid_members()[0]
44
+ }.to raise_error(Threatinator::Exceptions::InvalidAttributeError)
45
+ end
46
+ end
47
+
48
+ describe "#valid_member?(member)" do
49
+ it "returns true if the member is valid" do
50
+ expect(collection.valid_member?(generate_ten_valid_members()[0])).to eq(true)
51
+ end
52
+ it "returns false if the member is not valid" do
53
+ expect(collection.valid_member?(generate_invalid_members()[0])).to eq(false)
54
+ end
55
+ end
56
+
57
+ describe "#include?(member)" do
58
+ let(:member) { generate_ten_valid_members()[0] }
59
+ it "returns true if the member is in the collection" do
60
+ collection << member
61
+ expect(collection).to include(member)
62
+ end
63
+
64
+ it "returns false if the member is not in the collection" do
65
+ expect(collection).not_to include(member)
66
+ end
67
+ end
68
+
69
+ describe "#empty?" do
70
+ it "returns true when the collection is empty" do
71
+ expect(collection).to be_empty
72
+ end
73
+ it "returns false when something has been added to the collection" do
74
+ member = generate_ten_valid_members()[0]
75
+ collection << member
76
+ expect(collection).not_to be_empty
77
+ end
78
+ end
79
+
80
+ describe "#count" do
81
+ it "returns 0 when the collection is empty" do
82
+ expect(collection.count).to eq(0)
83
+ end
84
+
85
+ it "returns the number of members in the collection" do
86
+ members = generate_ten_valid_members()
87
+ members.each do |member|
88
+ collection << member
89
+ end
90
+ expect(collection.count).to eq(members.count)
91
+ end
92
+ end
93
+
94
+ [:to_ary, :to_a].each do |method_name|
95
+ describe "##{method_name}" do
96
+ it "returns an empty array instance when the collection is empty" do
97
+ expect(collection.send(method_name)).to eq([])
98
+ end
99
+
100
+ it "returns an array containing members that have been added" do
101
+ members = generate_ten_valid_members()
102
+ members.each do |member|
103
+ collection << member
104
+ end
105
+ expect(collection.send(method_name)).to match_array(members)
106
+ end
107
+ end
108
+ end
109
+
110
+ describe "#each" do
111
+ it "returns an Enumerator if no block is provided" do
112
+ expect(collection.each).to be_a(::Enumerator)
113
+ end
114
+
115
+ it "yields each member of the collection" do
116
+ members = generate_ten_valid_members()
117
+ members.each do |member|
118
+ collection << member
119
+ end
120
+
121
+ actual = []
122
+ collection.each do |member|
123
+ actual << member
124
+ end
125
+
126
+ expect(actual).to match_array(members)
127
+ end
128
+ end
129
+
130
+ describe "#==(other)" do
131
+ before :each do
132
+ end
133
+
134
+ it "returns true when compared to itself" do
135
+ collection = described_class.new(generate_ten_valid_members())
136
+ expect(collection).to be == collection
137
+ end
138
+
139
+ it "returns true when compared to a collection with equal members" do
140
+ collection = described_class.new(generate_ten_valid_members())
141
+ collection2 = described_class.new(generate_ten_valid_members())
142
+ expect(collection).to be == collection2
143
+ end
144
+
145
+ it "returns false if the number of members is different" do
146
+ collection = described_class.new(generate_ten_valid_members()[0..5])
147
+ collection2 = described_class.new(generate_ten_valid_members())
148
+ expect(collection).not_to be == collection2
149
+ end
150
+
151
+ it "returns false if the members are different" do
152
+ collection = described_class.new(generate_ten_valid_members()[0..4])
153
+ collection2 = described_class.new(generate_ten_valid_members()[5..9])
154
+ expect(collection).not_to be == collection2
155
+ end
156
+
157
+ it "returns false when compared to something other than a collection" do
158
+ members = generate_ten_valid_members()
159
+ collection = described_class.new(members)
160
+ expect(collection).not_to be == members
161
+ end
162
+ end
163
+ end
164
+
@@ -31,35 +31,52 @@ describe Threatinator::Actions::Run::Action do
31
31
  end
32
32
 
33
33
  describe "#exec" do
34
- before :each do
35
- @ret = action.exec
36
- end
37
34
 
38
35
  it "queries the feed registry for the provider and name" do
36
+ action.exec
39
37
  expect(feed_registry).to have_received(:get).with("my_provider", "my_name")
40
38
  end
41
39
 
42
40
  it "builds the output using config.output.build_output" do
41
+ action.exec
43
42
  expect(config.output).to have_received(:build_output)
44
43
  end
45
44
 
46
45
  it "runs the feed" do
46
+ action.exec
47
47
  expect(feed_runner).to have_received(:run)
48
48
  end
49
- end
50
49
 
51
- context "when no observer is configured" do
52
- before :each do
53
- config.observers = [ ]
50
+ context "when a record was missed" do
51
+ let(:status_observer) { Threatinator::Actions::Run::StatusObserver.new }
52
+ before :each do
53
+ allow(Threatinator::Actions::Run::StatusObserver).to receive(:new).and_return(status_observer)
54
+ allow(status_observer).to receive(:missed?).and_return(true)
55
+ end
56
+
57
+ it "logs an error message" do
58
+ expect(action.logger).to receive(:error).with(/records were MISSED/)
59
+ action.exec
60
+ end
54
61
  end
55
- it "does not add any observers to the feed runner" do
56
- expect(feed_runner).not_to receive(:add_observer)
57
- action.exec
62
+
63
+ context "when a record had an error" do
64
+ let(:status_observer) { Threatinator::Actions::Run::StatusObserver.new }
65
+ before :each do
66
+ allow(Threatinator::Actions::Run::StatusObserver).to receive(:new).and_return(status_observer)
67
+ allow(status_observer).to receive(:errors?).and_return(true)
68
+ end
69
+
70
+ it "logs an error message" do
71
+ expect(action.logger).to receive(:error).with(/records had errors/)
72
+ action.exec
73
+ end
58
74
  end
59
75
  end
60
76
 
61
77
  context "when configured with an observer" do
62
78
  before :each do
79
+ allow(feed_runner).to receive(:add_observer)
63
80
  config.observers = [ observer ]
64
81
  end
65
82
 
@@ -14,6 +14,8 @@ describe Threatinator::Actions::Run::CoverageObserver do
14
14
  let(:filename) { File.join(@tmpdir, "coverage.csv") }
15
15
  let(:observer) { described_class.new(filename) }
16
16
 
17
+ it_should_behave_like "a FeedRunner observer"
18
+
17
19
  context "#update(:start)" do
18
20
  it "creates the file specified by filename" do
19
21
  expect(File.exist?(filename)).to eq(false)
@@ -35,7 +37,7 @@ describe Threatinator::Actions::Run::CoverageObserver do
35
37
 
36
38
  specify "the first line is the header" do
37
39
  data = File.read(filename)
38
- expect(data.lines.to_a.first).to eq("status,event_count,line_number,pos_start,pos_end,data\n")
40
+ expect(data.lines.to_a.first).to eq("status,event_count,line_number,pos_start,pos_end,data,message\n")
39
41
  end
40
42
 
41
43
  it "closes the file so that no more records will be written" do
@@ -65,7 +67,8 @@ describe Threatinator::Actions::Run::CoverageObserver do
65
67
  line_number: "23",
66
68
  pos_start: "99",
67
69
  pos_end: "105",
68
- data: '"foobar\r\n"'
70
+ data: '"foobar\r\n"',
71
+ message: ''
69
72
  )
70
73
  end
71
74
  end
@@ -86,7 +89,8 @@ describe Threatinator::Actions::Run::CoverageObserver do
86
89
  line_number: "22",
87
90
  pos_start: "98",
88
91
  pos_end: "104",
89
- data: '"blabla\r\n"'
92
+ data: '"blabla\r\n"',
93
+ message: ''
90
94
  )
91
95
  end
92
96
  end
@@ -109,7 +113,38 @@ describe Threatinator::Actions::Run::CoverageObserver do
109
113
  line_number: "1",
110
114
  pos_start: "0",
111
115
  pos_end: "10",
112
- data: '"woofwoof\r\n"'
116
+ data: '"woofwoof\r\n"',
117
+ message: ''
118
+ )
119
+ end
120
+ end
121
+
122
+ context "#update(:record_error, record, errors)" do
123
+ before :each do
124
+ observer.update(:start)
125
+ end
126
+
127
+ let(:record) { build(:record, line_number: 1, pos_start: 0, pos_end: 10, data: "woofwoof\r\n") }
128
+ let(:errors) {
129
+ [
130
+ Threatinator::Exceptions::EventBuildError.new("error 1"),
131
+ Threatinator::Exceptions::EventBuildError.new("error 2"),
132
+ Threatinator::Exceptions::EventBuildError.new("error 3")
133
+ ]
134
+ }
135
+
136
+ it "writes a csv entry to the file indicating it encountered an error, with the error messages" do
137
+ observer.update(:record_error, record, errors)
138
+ observer.update(:end)
139
+ csv = CSV.read(filename, headers: true, header_converters: :symbol)
140
+ expect(csv[-1].to_hash).to eq(
141
+ status: "error",
142
+ event_count: "0",
143
+ line_number: "1",
144
+ pos_start: "0",
145
+ pos_end: "10",
146
+ data: '"woofwoof\r\n"',
147
+ message: 'error 1, error 2, error 3'
113
148
  )
114
149
  end
115
150
  end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/actions/run/status_observer'
3
+
4
+ describe Threatinator::Actions::Run::StatusObserver do
5
+ let(:observer) { described_class.new() }
6
+ let(:record) { build(:record, line_number: 23, pos_start: 99, pos_end: 105, data: "foobar\r\n") }
7
+
8
+ it_should_behave_like "a FeedRunner observer"
9
+
10
+ describe "#filtered" do
11
+ it "returns the number of records that have been filtered" do
12
+ expect(observer.filtered).to eq(0)
13
+ observer.update(:record_filtered, record)
14
+ expect(observer.filtered).to eq(1)
15
+ end
16
+ end
17
+
18
+ describe "#filtered?" do
19
+ it "returns true if any records were filtered, false otherwise" do
20
+ expect(observer.filtered?).to eq(false)
21
+ observer.update(:record_filtered, record)
22
+ expect(observer.filtered?).to eq(true)
23
+ end
24
+ end
25
+
26
+ describe "#missed" do
27
+ it "returns the number of records that have been missed" do
28
+ expect(observer.missed).to eq(0)
29
+ observer.update(:record_missed, record)
30
+ expect(observer.missed).to eq(1)
31
+ end
32
+ end
33
+
34
+ describe "#missed?" do
35
+ it "returns true if any records were missed, false otherwise" do
36
+ expect(observer.missed?).to eq(false)
37
+ observer.update(:record_missed, record)
38
+ expect(observer.missed?).to eq(true)
39
+ end
40
+ end
41
+
42
+ describe "#parsed" do
43
+ it "returns the number of records that have been parsed" do
44
+ expect(observer.parsed).to eq(0)
45
+ observer.update(:record_parsed, record)
46
+ expect(observer.parsed).to eq(1)
47
+ end
48
+ end
49
+
50
+ describe "#parsed?" do
51
+ it "returns true if any records were parsed, false otherwise" do
52
+ expect(observer.parsed?).to eq(false)
53
+ observer.update(:record_parsed, record)
54
+ expect(observer.parsed?).to eq(true)
55
+ end
56
+ end
57
+
58
+ describe "#errors" do
59
+ it "returns the number of records that had errors" do
60
+ expect(observer.errors).to eq(0)
61
+ observer.update(:record_error, record)
62
+ expect(observer.errors).to eq(1)
63
+ end
64
+ end
65
+
66
+ describe "#errors?" do
67
+ it "returns true if any records had errors, false otherwise" do
68
+ expect(observer.errors?).to eq(false)
69
+ observer.update(:record_error, record)
70
+ expect(observer.errors?).to eq(true)
71
+ end
72
+ end
73
+
74
+
75
+ describe "#total" do
76
+ it "returns the total number of records that were parsed, missed, filtered, and errored" do
77
+ expect(observer.total).to eq(0)
78
+ 10.times { observer.update(:record_parsed, record) }
79
+ 10.times { observer.update(:record_missed, record) }
80
+ 10.times { observer.update(:record_filtered, record) }
81
+ 10.times { observer.update(:record_error, record) }
82
+ expect(observer.total).to eq(40)
83
+ end
84
+ end
85
+ end
86
+