threatinator 0.1.1

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 (219) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/CONTRIBUTING.md +119 -0
  4. data/Gemfile +28 -0
  5. data/LICENSE +165 -0
  6. data/README.md +45 -0
  7. data/Rakefile +45 -0
  8. data/VERSION +1 -0
  9. data/bin/threatinator +5 -0
  10. data/lib/threatinator.rb +3 -0
  11. data/lib/threatinator/action.rb +14 -0
  12. data/lib/threatinator/actions/list.rb +2 -0
  13. data/lib/threatinator/actions/list/action.rb +53 -0
  14. data/lib/threatinator/actions/list/config.rb +10 -0
  15. data/lib/threatinator/actions/run.rb +2 -0
  16. data/lib/threatinator/actions/run/action.rb +45 -0
  17. data/lib/threatinator/actions/run/config.rb +32 -0
  18. data/lib/threatinator/actions/run/coverage_observer.rb +54 -0
  19. data/lib/threatinator/actions/run/output_config.rb +59 -0
  20. data/lib/threatinator/cli.rb +13 -0
  21. data/lib/threatinator/cli/action_builder.rb +33 -0
  22. data/lib/threatinator/cli/list_action_builder.rb +19 -0
  23. data/lib/threatinator/cli/parser.rb +113 -0
  24. data/lib/threatinator/cli/run_action_builder.rb +41 -0
  25. data/lib/threatinator/config.rb +6 -0
  26. data/lib/threatinator/config/base.rb +35 -0
  27. data/lib/threatinator/config/feed_search.rb +25 -0
  28. data/lib/threatinator/decoder.rb +24 -0
  29. data/lib/threatinator/decoders/gzip.rb +30 -0
  30. data/lib/threatinator/event.rb +27 -0
  31. data/lib/threatinator/event_builder.rb +41 -0
  32. data/lib/threatinator/exceptions.rb +61 -0
  33. data/lib/threatinator/feed.rb +82 -0
  34. data/lib/threatinator/feed_builder.rb +156 -0
  35. data/lib/threatinator/feed_registry.rb +47 -0
  36. data/lib/threatinator/feed_runner.rb +118 -0
  37. data/lib/threatinator/fetcher.rb +22 -0
  38. data/lib/threatinator/fetchers/http.rb +46 -0
  39. data/lib/threatinator/filter.rb +12 -0
  40. data/lib/threatinator/filters/block.rb +18 -0
  41. data/lib/threatinator/filters/comments.rb +16 -0
  42. data/lib/threatinator/filters/whitespace.rb +19 -0
  43. data/lib/threatinator/output.rb +50 -0
  44. data/lib/threatinator/parser.rb +23 -0
  45. data/lib/threatinator/parsers/csv.rb +7 -0
  46. data/lib/threatinator/parsers/csv/parser.rb +77 -0
  47. data/lib/threatinator/parsers/getline.rb +8 -0
  48. data/lib/threatinator/parsers/getline/parser.rb +45 -0
  49. data/lib/threatinator/parsers/json.rb +8 -0
  50. data/lib/threatinator/parsers/json/adapters/oj.rb +65 -0
  51. data/lib/threatinator/parsers/json/parser.rb +45 -0
  52. data/lib/threatinator/parsers/json/record.rb +20 -0
  53. data/lib/threatinator/parsers/xml.rb +8 -0
  54. data/lib/threatinator/parsers/xml/node.rb +79 -0
  55. data/lib/threatinator/parsers/xml/node_builder.rb +39 -0
  56. data/lib/threatinator/parsers/xml/parser.rb +44 -0
  57. data/lib/threatinator/parsers/xml/path.rb +70 -0
  58. data/lib/threatinator/parsers/xml/pattern.rb +53 -0
  59. data/lib/threatinator/parsers/xml/record.rb +14 -0
  60. data/lib/threatinator/parsers/xml/sax_document.rb +64 -0
  61. data/lib/threatinator/plugin_loader.rb +115 -0
  62. data/lib/threatinator/plugins/output/csv.rb +47 -0
  63. data/lib/threatinator/plugins/output/null.rb +17 -0
  64. data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
  65. data/lib/threatinator/property_definer.rb +101 -0
  66. data/lib/threatinator/record.rb +22 -0
  67. data/lib/threatinator/registry.rb +53 -0
  68. data/lib/threatinator/util.rb +15 -0
  69. data/spec/feeds/ET_compromised-ip_reputation_spec.rb +50 -0
  70. data/spec/feeds/alienvault-ip_reputation_spec.rb +50 -0
  71. data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +50 -0
  72. data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +50 -0
  73. data/spec/feeds/autoshun_shunlist_spec.rb +42 -0
  74. data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +50 -0
  75. data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +50 -0
  76. data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +50 -0
  77. data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +50 -0
  78. data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +50 -0
  79. data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +50 -0
  80. data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +50 -0
  81. data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +50 -0
  82. data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +50 -0
  83. data/spec/feeds/ciarmy-ip_reputation_spec.rb +50 -0
  84. data/spec/feeds/cruzit-ip_reputation_spec.rb +50 -0
  85. data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +50 -0
  86. data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
  87. data/spec/feeds/data/alienvault-ip_reputation.txt +18 -0
  88. data/spec/feeds/data/arbor_domainlist.txt +11 -0
  89. data/spec/feeds/data/arbor_ssh.txt +16 -0
  90. data/spec/feeds/data/autoshun_shunlist.csv +20 -0
  91. data/spec/feeds/data/blocklist_de_apache-ip-reputation.txt +17 -0
  92. data/spec/feeds/data/blocklist_de_bots-ip-reputation.txt +15 -0
  93. data/spec/feeds/data/blocklist_de_ftp-ip-reputation.txt +7 -0
  94. data/spec/feeds/data/blocklist_de_imap-ip-reputation.txt +8 -0
  95. data/spec/feeds/data/blocklist_de_pop3-ip-reputation.txt +11 -0
  96. data/spec/feeds/data/blocklist_de_proftpd-ip-reputation.txt +12 -0
  97. data/spec/feeds/data/blocklist_de_sip-ip-reputation.txt +9 -0
  98. data/spec/feeds/data/blocklist_de_ssh-ip-reputation.txt +10 -0
  99. data/spec/feeds/data/blocklist_de_strongips-ip-reputation.txt +11 -0
  100. data/spec/feeds/data/ciarmy-ip-reputation.txt +11 -0
  101. data/spec/feeds/data/cruzit-ip-reputation.txt +14 -0
  102. data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
  103. data/spec/feeds/data/dshield_topattackers.xml +4 -0
  104. data/spec/feeds/data/feodo_domainlist.txt +18 -0
  105. data/spec/feeds/data/feodo_iplist.txt +20 -0
  106. data/spec/feeds/data/infiltrated_iplist.txt +16 -0
  107. data/spec/feeds/data/malc0de_domainlist.txt +18 -0
  108. data/spec/feeds/data/malc0de_iplist.txt +14 -0
  109. data/spec/feeds/data/mirc_domainlist.txt +31 -0
  110. data/spec/feeds/data/nothink_irc_iplist.txt +14 -0
  111. data/spec/feeds/data/nothink_ssh_iplist.txt +10 -0
  112. data/spec/feeds/data/openbl_iplist.txt +12 -0
  113. data/spec/feeds/data/palevo_domainlist.txt +25 -0
  114. data/spec/feeds/data/palevo_iplist.txt +24 -0
  115. data/spec/feeds/data/phishtank-sample.json.gz +0 -0
  116. data/spec/feeds/data/spyeye_domainlist.txt +16 -0
  117. data/spec/feeds/data/spyeye_iplist.txt +19 -0
  118. data/spec/feeds/data/t-arend-de_ssh_iplist.txt +17 -0
  119. data/spec/feeds/data/the_haleys_ssh_iplist.txt +12 -0
  120. data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
  121. data/spec/feeds/data/zeus-ip_reputation.txt +285 -0
  122. data/spec/feeds/data/zeus_domainlist.txt +27 -0
  123. data/spec/feeds/dshield_attackers-top1000_spec.rb +43 -0
  124. data/spec/feeds/feodo-domain_reputation_spec.rb +50 -0
  125. data/spec/feeds/feodo-ip_reputation_spec.rb +50 -0
  126. data/spec/feeds/infiltrated-ip_reputation_spec.rb +50 -0
  127. data/spec/feeds/malc0de-domain_reputation_spec.rb +50 -0
  128. data/spec/feeds/malc0de-ip_reputation_spec.rb +50 -0
  129. data/spec/feeds/mirc-domain_reputation_spec.rb +50 -0
  130. data/spec/feeds/nothink_irc-ip_reputation_spec.rb +50 -0
  131. data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +50 -0
  132. data/spec/feeds/openbl-ip_reputation_spec.rb +50 -0
  133. data/spec/feeds/palevo-domain_reputation_spec.rb +50 -0
  134. data/spec/feeds/palevo-ip_reputation_spec.rb +50 -0
  135. data/spec/feeds/phishtank_spec.rb +45 -0
  136. data/spec/feeds/spyeye-domain_reputation_spec.rb +50 -0
  137. data/spec/feeds/spyeye-ip_reputation_spec.rb +50 -0
  138. data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +50 -0
  139. data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +50 -0
  140. data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +50 -0
  141. data/spec/feeds/zeus-domain_reputation_spec.rb +50 -0
  142. data/spec/feeds/zeus-ip_reputation_spec.rb +50 -0
  143. data/spec/fixtures/feed/provider1/feed1.feed +6 -0
  144. data/spec/fixtures/parsers/test.xml +13 -0
  145. data/spec/fixtures/parsers/test_self_closing.xml +20 -0
  146. data/spec/fixtures/plugins/bad/threatinator/plugins/test_error1/plugin.rb +1 -0
  147. data/spec/fixtures/plugins/bad/threatinator/plugins/test_missing1/plugin.rb +0 -0
  148. data/spec/fixtures/plugins/fake.rb +19 -0
  149. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_a.rb +8 -0
  150. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_b.rb +8 -0
  151. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_c.rb +8 -0
  152. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_d.rb +8 -0
  153. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_e.rb +8 -0
  154. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_f.rb +8 -0
  155. data/spec/spec_helper.rb +52 -0
  156. data/spec/support/bad_feeds/missing_fetcher.feed +7 -0
  157. data/spec/support/bad_feeds/missing_name.feed +6 -0
  158. data/spec/support/bad_feeds/missing_parser.feed +3 -0
  159. data/spec/support/bad_feeds/missing_provider.feed +5 -0
  160. data/spec/support/factories/event.rb +27 -0
  161. data/spec/support/factories/feed.rb +32 -0
  162. data/spec/support/factories/feed_builder.rb +65 -0
  163. data/spec/support/factories/feed_registry.rb +8 -0
  164. data/spec/support/factories/output.rb +11 -0
  165. data/spec/support/factories/record.rb +17 -0
  166. data/spec/support/factories/xml_node.rb +33 -0
  167. data/spec/support/helpers/io.rb +11 -0
  168. data/spec/support/helpers/models.rb +13 -0
  169. data/spec/support/shared/action_builder.rb +47 -0
  170. data/spec/support/shared/decoder.rb +70 -0
  171. data/spec/support/shared/feeds.rb +218 -0
  172. data/spec/support/shared/fetcher.rb +48 -0
  173. data/spec/support/shared/filter.rb +14 -0
  174. data/spec/support/shared/io-like.rb +7 -0
  175. data/spec/support/shared/output.rb +120 -0
  176. data/spec/support/shared/parsers.rb +51 -0
  177. data/spec/support/shared/record.rb +111 -0
  178. data/spec/threatinator/actions/list/action_spec.rb +93 -0
  179. data/spec/threatinator/actions/run/action_spec.rb +89 -0
  180. data/spec/threatinator/actions/run/config_spec.rb +39 -0
  181. data/spec/threatinator/actions/run/coverage_observer_spec.rb +116 -0
  182. data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
  183. data/spec/threatinator/cli/list_action_builder_spec.rb +57 -0
  184. data/spec/threatinator/cli/run_action_builder_spec.rb +133 -0
  185. data/spec/threatinator/cli_spec.rb +175 -0
  186. data/spec/threatinator/config/base_spec.rb +39 -0
  187. data/spec/threatinator/config/feed_search_spec.rb +76 -0
  188. data/spec/threatinator/decoders/gzip_spec.rb +75 -0
  189. data/spec/threatinator/event_builder_spec.rb +33 -0
  190. data/spec/threatinator/event_spec.rb +30 -0
  191. data/spec/threatinator/feed_builder_spec.rb +636 -0
  192. data/spec/threatinator/feed_registry_spec.rb +198 -0
  193. data/spec/threatinator/feed_runner_spec.rb +155 -0
  194. data/spec/threatinator/feed_spec.rb +169 -0
  195. data/spec/threatinator/fetcher_spec.rb +12 -0
  196. data/spec/threatinator/fetchers/http_spec.rb +32 -0
  197. data/spec/threatinator/filter_spec.rb +13 -0
  198. data/spec/threatinator/filters/block_spec.rb +16 -0
  199. data/spec/threatinator/filters/comments_spec.rb +13 -0
  200. data/spec/threatinator/filters/whitespace_spec.rb +12 -0
  201. data/spec/threatinator/parser_spec.rb +13 -0
  202. data/spec/threatinator/parsers/csv/parser_spec.rb +202 -0
  203. data/spec/threatinator/parsers/getline/parser_spec.rb +83 -0
  204. data/spec/threatinator/parsers/json/parser_spec.rb +106 -0
  205. data/spec/threatinator/parsers/json/record_spec.rb +30 -0
  206. data/spec/threatinator/parsers/xml/node_spec.rb +335 -0
  207. data/spec/threatinator/parsers/xml/parser_spec.rb +263 -0
  208. data/spec/threatinator/parsers/xml/path_spec.rb +209 -0
  209. data/spec/threatinator/parsers/xml/pattern_spec.rb +72 -0
  210. data/spec/threatinator/parsers/xml/record_spec.rb +27 -0
  211. data/spec/threatinator/plugin_loader_spec.rb +238 -0
  212. data/spec/threatinator/plugins/output/csv_spec.rb +46 -0
  213. data/spec/threatinator/plugins/output/null_spec.rb +17 -0
  214. data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -0
  215. data/spec/threatinator/property_definer_spec.rb +155 -0
  216. data/spec/threatinator/record_spec.rb +19 -0
  217. data/spec/threatinator/registry_spec.rb +97 -0
  218. data/spec/threatinator/runner_spec.rb +273 -0
  219. metadata +376 -0
@@ -0,0 +1,51 @@
1
+ shared_examples_for "a parser when compared to an identically configured parser" do
2
+ it "parser1 should eql? parser2" do
3
+ expect(parser1).to eql(parser2)
4
+ end
5
+ it "parser2 should eql? parser1" do
6
+ expect(parser2).to eql(parser1)
7
+ end
8
+ it "parser1 should == parser2" do
9
+ expect(parser1).to be == parser2
10
+ end
11
+ it "parser2 should == parser1" do
12
+ expect(parser2).to be == parser1
13
+ end
14
+ it "parser1 should not be parser2" do
15
+ expect(parser1).not_to be parser2
16
+ end
17
+ it "parser2 should not be parser1" do
18
+ expect(parser2).not_to be parser1
19
+ end
20
+ end
21
+
22
+ shared_examples_for "a parser when compared to a differently configured parser" do
23
+ it "parser1 should not eql? parser2" do
24
+ expect(parser1).not_to eql(parser2)
25
+ end
26
+ it "parser2 should not eql? parser1" do
27
+ expect(parser2).not_to eql(parser1)
28
+ end
29
+ it "parser1 should not == parser2" do
30
+ expect(parser1).not_to be == parser2
31
+ end
32
+ it "parser2 should not == parser1" do
33
+ expect(parser2).not_to be == parser1
34
+ end
35
+ it "parser1 should not be parser2" do
36
+ expect(parser1).not_to be parser2
37
+ end
38
+ it "parser2 should not be parser1" do
39
+ expect(parser2).not_to be parser1
40
+ end
41
+ end
42
+
43
+ module ParserHelpers
44
+ def parser_data(filename)
45
+ PARSER_FIXTURES.join(filename).to_s
46
+ end
47
+ end
48
+
49
+ shared_context "a parser", :parser do
50
+ include ParserHelpers
51
+ end
@@ -0,0 +1,111 @@
1
+ shared_examples_for 'a record when compared to an identically configured record' do
2
+ let(:record1) { described_class.new(data, opts) }
3
+ let(:record2) { described_class.new(data2, opts2) }
4
+ specify "record1 should == record2" do
5
+ expect(record1).to be == record2
6
+ end
7
+ specify "record2 should == record1" do
8
+ expect(record2).to be == record1
9
+ end
10
+ specify "record1 should eql? record2" do
11
+ expect(record1).to eql record2
12
+ end
13
+ specify "record2 should eql? record1" do
14
+ expect(record2).to eql record1
15
+ end
16
+ end
17
+
18
+ shared_examples_for 'a record when compared to a differently configured record' do
19
+ let(:record1) { described_class.new(data, opts) }
20
+ let(:record2) { described_class.new(data2, opts2) }
21
+ specify "record1 should not == record2" do
22
+ expect(record1).not_to be == record2
23
+ end
24
+ specify "record2 should not == record1" do
25
+ expect(record2).not_to be == record1
26
+ end
27
+ specify "record1 should not eql? record2" do
28
+ expect(record1).not_to eql record2
29
+ end
30
+ specify "record2 should not eql? record1" do
31
+ expect(record2).not_to eql record1
32
+ end
33
+ end
34
+
35
+ shared_examples_for 'a record' do
36
+ let(:record) { described_class.new(data, opts) }
37
+ context "when compared to itself" do
38
+ it_should_behave_like 'a record when compared to an identically configured record' do
39
+ let(:data2) { Marshal.load(Marshal.dump(data)) }
40
+ let(:opts2) { Marshal.load(Marshal.dump(opts)) }
41
+ end
42
+ end
43
+ describe "initialization options" do
44
+ describe "data" do
45
+ it "should be accessible via #data" do
46
+ expect(record.data).to eq(data)
47
+ end
48
+ end
49
+ describe ":line_number" do
50
+ context "when not set" do
51
+ it "should default to nil" do
52
+ expect(record.line_number).to be_nil
53
+ end
54
+ end
55
+ context "when set" do
56
+ before :each do
57
+ opts[:line_number] = 1234
58
+ end
59
+ it "should be readable via #line_number" do
60
+ expect(record.line_number).to eq(1234)
61
+ end
62
+
63
+ it_should_behave_like 'a record when compared to an identically configured record' do
64
+ let(:data2) { Marshal.load(Marshal.dump(data)) }
65
+ let(:opts2) { Marshal.load(Marshal.dump(opts)) }
66
+ end
67
+ end
68
+ end
69
+ describe ":pos_start" do
70
+ context "when not set" do
71
+ it "should default to nil" do
72
+ expect(record.pos_start).to be_nil
73
+ end
74
+ end
75
+ context "when set" do
76
+ before :each do
77
+ opts[:pos_start] = 999
78
+ end
79
+ it "should be readable via #pos_start" do
80
+ expect(record.pos_start).to eq(999)
81
+ end
82
+
83
+ it_should_behave_like 'a record when compared to an identically configured record' do
84
+ let(:data2) { Marshal.load(Marshal.dump(data)) }
85
+ let(:opts2) { Marshal.load(Marshal.dump(opts)) }
86
+ end
87
+ end
88
+ end
89
+ describe ":pos_end" do
90
+ context "when not set" do
91
+ it "should default to nil" do
92
+ expect(record.pos_end).to be_nil
93
+ end
94
+ end
95
+ context "when set" do
96
+ before :each do
97
+ opts[:pos_end] = 555
98
+ end
99
+
100
+ it "should be readable via #pos_end" do
101
+ expect(record.pos_end).to eq(555)
102
+ end
103
+
104
+ it_should_behave_like 'a record when compared to an identically configured record' do
105
+ let(:data2) { Marshal.load(Marshal.dump(data)) }
106
+ let(:opts2) { Marshal.load(Marshal.dump(opts)) }
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/actions/list/action'
3
+ require 'threatinator/actions/list/config'
4
+
5
+ describe Threatinator::Actions::List::Action do
6
+ let(:feed_registry) { build(:feed_registry) }
7
+ let(:config) { Threatinator::Actions::List::Config.new }
8
+ let(:action) { described_class.new(feed_registry, config) }
9
+
10
+ describe "the header row, header separator, and footer separator" do
11
+ it "should vary the width of 'provider' based on the longest provider name" do
12
+ feed_registry.register(build(:feed, :mini, provider: 'A' * 10))
13
+ feed_registry.register(build(:feed, :mini, provider: 'A' * 20))
14
+ feed_registry.register(build(:feed, :mini, provider: 'A' * 30))
15
+
16
+ output = temp_stdout do
17
+ action.exec
18
+ end
19
+ lines = output.lines.to_a
20
+ expect(lines[0]).to eq("provider name type link/path\n")
21
+ expect(lines[1]).to eq("------------------------------ ---- ---- ---------\n")
22
+ expect(lines[-2]).to eq("------------------------------ ---- ---- ---------\n")
23
+ end
24
+
25
+ it "should vary the width of 'name' based on the longest feed name" do
26
+ feed_registry.register(build(:feed, :mini, name: 'A' * 10))
27
+ feed_registry.register(build(:feed, :mini, name: 'A' * 20))
28
+ feed_registry.register(build(:feed, :mini, name: 'A' * 30))
29
+
30
+ output = temp_stdout do
31
+ action.exec
32
+ end
33
+ lines = output.lines.to_a
34
+ expect(lines[0]).to eq("provider name type link/path\n")
35
+ expect(lines[1]).to eq("-------- ------------------------------ ---- ---------\n")
36
+ expect(lines[-2]).to eq("-------- ------------------------------ ---- ---------\n")
37
+ end
38
+
39
+ it "should vary the width of 'link/path' based on the longest link name" do
40
+ feed_registry.register(build(:feed, :mini, url: 'http://' + ('A' * 10)))
41
+ feed_registry.register(build(:feed, :mini, url: 'http://' + ('A' * 20)))
42
+ feed_registry.register(build(:feed, :mini, url: 'http://' + ('A' * 30)))
43
+
44
+ output = temp_stdout do
45
+ action.exec
46
+ end
47
+ lines = output.lines.to_a
48
+ expect(lines[0]).to eq("provider name type link/path \n")
49
+ expect(lines[1]).to eq("-------- ---- ---- -------------------------------------\n")
50
+ expect(lines[-2]).to eq("-------- ---- ---- -------------------------------------\n")
51
+ end
52
+ end
53
+
54
+ describe "the list of feeds" do
55
+ it "should be sorted by provider name and then feed name" do
56
+ feed_registry.register(build(:feed, provider: 'provider_b', name: 'feed_c' ))
57
+ feed_registry.register(build(:feed, provider: 'provider_a', name: 'feed_d' ))
58
+ feed_registry.register(build(:feed, provider: 'provider_a', name: 'feed_a' ))
59
+ feed_registry.register(build(:feed, provider: 'provider_b', name: 'feed_d' ))
60
+ feed_registry.register(build(:feed, provider: 'provider_a', name: 'feed_c' ))
61
+ feed_registry.register(build(:feed, provider: 'provider_b', name: 'feed_a' ))
62
+ feed_registry.register(build(:feed, provider: 'provider_b', name: 'feed_b' ))
63
+ feed_registry.register(build(:feed, provider: 'provider_a', name: 'feed_b' ))
64
+
65
+ output = temp_stdout do
66
+ action.exec
67
+ end
68
+ lines = output.lines.to_a
69
+ expect(lines[2]).to match(/^provider_a feed_a .*$/)
70
+ expect(lines[3]).to match(/^provider_a feed_b .*$/)
71
+ expect(lines[4]).to match(/^provider_a feed_c .*$/)
72
+ expect(lines[5]).to match(/^provider_a feed_d .*$/)
73
+ expect(lines[6]).to match(/^provider_b feed_a .*$/)
74
+ expect(lines[7]).to match(/^provider_b feed_b .*$/)
75
+ expect(lines[8]).to match(/^provider_b feed_c .*$/)
76
+ expect(lines[9]).to match(/^provider_b feed_d .*$/)
77
+ end
78
+ end
79
+
80
+ describe "the footer" do
81
+ it "should indicate the number of feeds" do
82
+ 20.times do |i|
83
+ feed_registry.register(build(:feed))
84
+ end
85
+ output = temp_stdout do
86
+ action.exec
87
+ end
88
+ lines = output.lines.to_a
89
+ expect(lines[-1]).to eq("Total: 20\n")
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/actions/run/action'
3
+ require 'threatinator/actions/run/config'
4
+ require 'threatinator/plugin_loader'
5
+
6
+ describe Threatinator::Actions::Run::Action do
7
+ let(:feed_registry) { build(:feed_registry) }
8
+ let(:feed_runner) { double('feed runner') }
9
+ let(:plugin_loader) { Threatinator::PluginLoader.new }
10
+ let(:config_class) { Threatinator::Actions::Run::Config.generate(plugin_loader) }
11
+ let(:config) { config_class.new }
12
+ let(:action) { described_class.new(feed_registry, config) }
13
+ let(:feed) { build(:feed, provider: "my_provider", name: "my_name") }
14
+
15
+ before :each do
16
+ feed_registry.register(feed)
17
+ end
18
+
19
+ context "when configured with feed provider and name that exists within the registry" do
20
+ let(:output) { double('mock output') }
21
+ let(:observer) { double('observer') }
22
+ before :each do
23
+ config.feed_provider = "my_provider"
24
+ config.feed_name = "my_name"
25
+
26
+ allow(feed_registry).to receive(:get).and_call_original
27
+ allow(config.output).to receive(:build_output).and_return(output)
28
+ allow(Threatinator::FeedRunner).to receive(:new).and_return(feed_runner)
29
+ allow(feed_runner).to receive(:run)
30
+ allow(feed_runner).to receive(:add_observer)
31
+ end
32
+
33
+ describe "#exec" do
34
+ before :each do
35
+ @ret = action.exec
36
+ end
37
+
38
+ it "queries the feed registry for the provider and name" do
39
+ expect(feed_registry).to have_received(:get).with("my_provider", "my_name")
40
+ end
41
+
42
+ it "builds the output using config.output.build_output" do
43
+ expect(config.output).to have_received(:build_output)
44
+ end
45
+
46
+ it "runs the feed" do
47
+ expect(feed_runner).to have_received(:run)
48
+ end
49
+ end
50
+
51
+ context "when no observer is configured" do
52
+ before :each do
53
+ config.observers = [ ]
54
+ 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
58
+ end
59
+ end
60
+
61
+ context "when configured with an observer" do
62
+ before :each do
63
+ config.observers = [ observer ]
64
+ end
65
+
66
+ describe "#exec" do
67
+ it "adds the observer to FeedRunner" do
68
+ expect(feed_runner).to receive(:add_observer).with(observer)
69
+ action.exec
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ context "when the registry does not contain the configured feed_provider or feed_name" do
76
+ before :each do
77
+ config.feed_provider = "unknown_provider"
78
+ config.feed_name = "unknown_feed_name"
79
+ end
80
+ describe "#exec" do
81
+ it "raises Threatinator::Exceptions::UnknownFeed" do
82
+ expect {
83
+ action.exec
84
+ }.to raise_error(Threatinator::Exceptions::UnknownFeed)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/actions/run/config'
3
+ require 'threatinator/plugin_loader'
4
+ require 'fixtures/plugins/fake'
5
+
6
+ describe Threatinator::Actions::Run::Config do
7
+
8
+ let(:plugin_loader) { Threatinator::PluginLoader.new }
9
+
10
+ describe ".generate(plugin_loader)" do
11
+ before :each do
12
+ allow(Threatinator::Actions::Run::OutputConfig).to receive(:generate).and_call_original
13
+ plugin_loader.register_plugin(:output, :plugin1, FakeOutputPlugins::Plugin1)
14
+ plugin_loader.register_plugin(:output, :plugin2, FakeOutputPlugins::Plugin2)
15
+ plugin_loader.register_plugin(:output, :plugin3, FakeOutputPlugins::Plugin3)
16
+ @generated_class = described_class.generate(plugin_loader)
17
+ end
18
+
19
+ let(:generated_class) { @generated_class }
20
+
21
+ it "returns a subclass of Threatinator::Config::Base" do
22
+ expect(generated_class.superclass).to be(Threatinator::Config::Base)
23
+ end
24
+
25
+ it "generates a new Output config class using the plugin_loader" do
26
+ expect(Threatinator::Actions::Run::OutputConfig).to have_received(:generate).with(plugin_loader)
27
+ end
28
+
29
+ describe "an instance" do
30
+ let(:config) { generated_class.new }
31
+ describe "#output" do
32
+ specify "returns an instance of a subclass of Threatinator::Config::Base" do
33
+ expect(config.output.class.superclass).to be(Threatinator::Config::Base)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/actions/run/coverage_observer'
3
+
4
+ describe Threatinator::Actions::Run::CoverageObserver do
5
+ before :each do
6
+ @tmpdir = Dir.mktmpdir
7
+ end
8
+
9
+ after :each do
10
+ observer.update(:end)
11
+ FileUtils.remove_entry_secure @tmpdir
12
+ end
13
+
14
+ let(:filename) { File.join(@tmpdir, "coverage.csv") }
15
+ let(:observer) { described_class.new(filename) }
16
+
17
+ context "#update(:start)" do
18
+ it "creates the file specified by filename" do
19
+ expect(File.exist?(filename)).to eq(false)
20
+ observer.update(:start)
21
+ expect(File.exist?(filename)).to eq(true)
22
+ end
23
+ end
24
+
25
+ context "#update(:end)" do
26
+ before :each do
27
+ observer.update(:start)
28
+ end
29
+
30
+ context "when at least one record has been written" do
31
+ before :each do
32
+ observer.update(:record_parsed, build(:record), [ build(:event) ])
33
+ observer.update(:end)
34
+ end
35
+
36
+ specify "the first line is the header" do
37
+ data = File.read(filename)
38
+ expect(data.lines.to_a.first).to eq("status,event_count,line_number,pos_start,pos_end,data\n")
39
+ end
40
+
41
+ it "closes the file so that no more records will be written" do
42
+ data_before = File.read(filename)
43
+ 10.times do
44
+ observer.update(:record_missed, build(:record))
45
+ end
46
+ data_after = File.read(filename)
47
+ expect(data_before).to eq(data_after)
48
+ end
49
+ end
50
+ end
51
+
52
+ context "#update(:record_filtered, record)" do
53
+ before :each do
54
+ observer.update(:start)
55
+ end
56
+
57
+ it "writes a csv entry to the file indicating that it was filtered" do
58
+ record = build(:record, line_number: 23, pos_start: 99, pos_end: 105, data: "foobar\r\n")
59
+ observer.update(:record_filtered, record)
60
+ observer.update(:end)
61
+ csv = CSV.read(filename, headers: true, header_converters: :symbol)
62
+ expect(csv[-1].to_hash).to eq(
63
+ status: "filtered",
64
+ event_count: "0",
65
+ line_number: "23",
66
+ pos_start: "99",
67
+ pos_end: "105",
68
+ data: '"foobar\r\n"'
69
+ )
70
+ end
71
+ end
72
+
73
+ context "#update(:record_filtered, record)" do
74
+ before :each do
75
+ observer.update(:start)
76
+ end
77
+
78
+ it "writes a csv entry to the file indicating that it was missed" do
79
+ record = build(:record, line_number: 22, pos_start: 98, pos_end: 104, data: "blabla\r\n")
80
+ observer.update(:record_filtered, record)
81
+ observer.update(:end)
82
+ csv = CSV.read(filename, headers: true, header_converters: :symbol)
83
+ expect(csv[-1].to_hash).to eq(
84
+ status: "filtered",
85
+ event_count: "0",
86
+ line_number: "22",
87
+ pos_start: "98",
88
+ pos_end: "104",
89
+ data: '"blabla\r\n"'
90
+ )
91
+ end
92
+ end
93
+
94
+ context "#update(:record_parsed, record, events)" do
95
+ before :each do
96
+ observer.update(:start)
97
+ end
98
+
99
+ let(:record) { build(:record, line_number: 1, pos_start: 0, pos_end: 10, data: "woofwoof\r\n") }
100
+ let(:events) { [ build(:event), build(:event) ] }
101
+
102
+ it "writes a csv entry to the file indicating that it was parsed, with the number of events" do
103
+ observer.update(:record_parsed, record, events)
104
+ observer.update(:end)
105
+ csv = CSV.read(filename, headers: true, header_converters: :symbol)
106
+ expect(csv[-1].to_hash).to eq(
107
+ status: "parsed",
108
+ event_count: "2",
109
+ line_number: "1",
110
+ pos_start: "0",
111
+ pos_end: "10",
112
+ data: '"woofwoof\r\n"'
113
+ )
114
+ end
115
+ end
116
+ end