shadowbq-threatinator 0.5.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 (389) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +66 -0
  3. data/CONTRIBUTING.md +119 -0
  4. data/Gemfile +38 -0
  5. data/LICENSE +165 -0
  6. data/README.md +101 -0
  7. data/Rakefile +47 -0
  8. data/VERSION +1 -0
  9. data/bin/threatinator +5 -0
  10. data/bin/threatinator_loader +21 -0
  11. data/feeds/ET_block-ip_reputation.feed +27 -0
  12. data/feeds/ET_compromised-ip_reputation.feed +20 -0
  13. data/feeds/ET_openbadlist-ip_reputation.feed +36 -0
  14. data/feeds/alienvault-ip_reputation.feed +39 -0
  15. data/feeds/arbor_fastflux-domain_reputation.feed +19 -0
  16. data/feeds/arbor_ssh-ip_reputation.feed +24 -0
  17. data/feeds/autoshun_shunlist.feed +17 -0
  18. data/feeds/bambenek_c2_masterlist-domain_reputation.feed +16 -0
  19. data/feeds/bambenek_c2_masterlist-ip_reputation.feed +16 -0
  20. data/feeds/bambenek_dga_feed-domain_reputation.feed +16 -0
  21. data/feeds/berkeley-ip_reputation.feed +25 -0
  22. data/feeds/bitcash_cz_blacklist.feed +22 -0
  23. data/feeds/blocklist_de_apache-ip_reputation.feed +26 -0
  24. data/feeds/blocklist_de_bots-ip_reputation.feed +26 -0
  25. data/feeds/blocklist_de_ftp-ip_reputation.feed +25 -0
  26. data/feeds/blocklist_de_imap-ip_reputation.feed +25 -0
  27. data/feeds/blocklist_de_pop3-ip_reputation.feed +26 -0
  28. data/feeds/blocklist_de_proftpd-ip_reputation.feed +26 -0
  29. data/feeds/blocklist_de_sip-ip_reputation.feed +25 -0
  30. data/feeds/blocklist_de_ssh-ip_reputation.feed +25 -0
  31. data/feeds/blocklist_de_strongips-ip_reputation.feed +25 -0
  32. data/feeds/botscout-ip_reputation.feed +25 -0
  33. data/feeds/cert_mxpoison-ip_reputation.feed +22 -0
  34. data/feeds/chaosreigns-ip_reputation.feed +37 -0
  35. data/feeds/ciarmy-ip_reputation.feed +20 -0
  36. data/feeds/cruzit-ip_reputation.feed +30 -0
  37. data/feeds/cydef_torexit-ip_reputation.feed +25 -0
  38. data/feeds/dan_me_uk_torlist-ip_reputation.feed +25 -0
  39. data/feeds/danger_bruteforce-ip_reputation.feed +24 -0
  40. data/feeds/dshield_attackers-top1000.feed +34 -0
  41. data/feeds/falconcrest-ip_reputation.feed +19 -0
  42. data/feeds/feodo-domain_reputation.feed +19 -0
  43. data/feeds/feodo-ip_reputation.feed +20 -0
  44. data/feeds/h3x_asprox.feed +18 -0
  45. data/feeds/hosts-file_hphostspartial-domain_reputation.feed +19 -0
  46. data/feeds/infiltrated-ip_reputation.feed +26 -0
  47. data/feeds/infiltrated_vabl-ip_reputation.feed +30 -0
  48. data/feeds/isc_suspicious_high-domain_reputation.feed +26 -0
  49. data/feeds/isc_suspicious_low-domain_reputation.feed +26 -0
  50. data/feeds/isc_suspicious_medium-domain_reputation.feed +26 -0
  51. data/feeds/malc0de-domain_reputation.feed +24 -0
  52. data/feeds/malc0de-ip_reputation.feed +26 -0
  53. data/feeds/malwaredomainlist-url_reputation.feed +18 -0
  54. data/feeds/malwaredomains-domain_reputation.feed +29 -0
  55. data/feeds/malwaredomains_dyndns-domain_reputation.feed +29 -0
  56. data/feeds/malwaredomains_justdomains-domain_reputation.feed +20 -0
  57. data/feeds/mirc-domain_reputation.feed +30 -0
  58. data/feeds/multiproxy-ip_reputation.feed +22 -0
  59. data/feeds/nothink_irc-ip_reputation.feed +23 -0
  60. data/feeds/nothink_ssh-ip_reputation.feed +21 -0
  61. data/feeds/openbl-ip_reputation.feed +21 -0
  62. data/feeds/openphish-url_reputation.feed +24 -0
  63. data/feeds/packetmail_perimeterbad-ip_reputation.feed +28 -0
  64. data/feeds/palevo-domain_reputation.feed +22 -0
  65. data/feeds/palevo-ip_reputation.feed +23 -0
  66. data/feeds/phishtank.feed +22 -0
  67. data/feeds/sigmaproject_atma.feed +27 -0
  68. data/feeds/sigmaproject_spyware.feed +28 -0
  69. data/feeds/sigmaproject_webexploit.feed +26 -0
  70. data/feeds/snort_bpf-ip_reputation.feed +19 -0
  71. data/feeds/spyeye-domain_reputation.feed +18 -0
  72. data/feeds/spyeye-ip_reputation.feed +19 -0
  73. data/feeds/steeman-ip_reputation.feed +20 -0
  74. data/feeds/t-arend-de_ssh-ip_reputation.feed +20 -0
  75. data/feeds/the_haleys_ssh-ip_reputation.feed +20 -0
  76. data/feeds/trustedsec-ip_reputation.feed +18 -0
  77. data/feeds/virbl-ip_reputation.feed +25 -0
  78. data/feeds/vxvault-url_reputation.feed +23 -0
  79. data/feeds/yourcmc_ssh-ip_reputation.feed +20 -0
  80. data/feeds/yoyo_adservers-domain_reputation.feed +17 -0
  81. data/feeds/zeus-domain_reputation.feed +19 -0
  82. data/feeds/zeus-ip_reputation.feed +21 -0
  83. data/lib/threatinator/action.rb +14 -0
  84. data/lib/threatinator/actions/list/action.rb +97 -0
  85. data/lib/threatinator/actions/list/config.rb +12 -0
  86. data/lib/threatinator/actions/list.rb +2 -0
  87. data/lib/threatinator/actions/run/action.rb +57 -0
  88. data/lib/threatinator/actions/run/config.rb +32 -0
  89. data/lib/threatinator/actions/run/coverage_observer.rb +59 -0
  90. data/lib/threatinator/actions/run/output_config.rb +59 -0
  91. data/lib/threatinator/actions/run/status_observer.rb +37 -0
  92. data/lib/threatinator/actions/run.rb +2 -0
  93. data/lib/threatinator/cli/action_builder.rb +33 -0
  94. data/lib/threatinator/cli/list_action_builder.rb +19 -0
  95. data/lib/threatinator/cli/parser.rb +123 -0
  96. data/lib/threatinator/cli/run_action_builder.rb +41 -0
  97. data/lib/threatinator/cli.rb +19 -0
  98. data/lib/threatinator/config/base.rb +35 -0
  99. data/lib/threatinator/config/feed_search.rb +25 -0
  100. data/lib/threatinator/config/logger.rb +14 -0
  101. data/lib/threatinator/config.rb +7 -0
  102. data/lib/threatinator/decoder.rb +24 -0
  103. data/lib/threatinator/decoders/gzip.rb +30 -0
  104. data/lib/threatinator/event.rb +63 -0
  105. data/lib/threatinator/event_builder.rb +70 -0
  106. data/lib/threatinator/exceptions.rb +58 -0
  107. data/lib/threatinator/feed.rb +88 -0
  108. data/lib/threatinator/feed_builder.rb +161 -0
  109. data/lib/threatinator/feed_registry.rb +47 -0
  110. data/lib/threatinator/feed_runner.rb +177 -0
  111. data/lib/threatinator/fetcher.rb +22 -0
  112. data/lib/threatinator/fetchers/http.rb +50 -0
  113. data/lib/threatinator/filter.rb +12 -0
  114. data/lib/threatinator/filters/block.rb +18 -0
  115. data/lib/threatinator/filters/comments.rb +16 -0
  116. data/lib/threatinator/filters/whitespace.rb +19 -0
  117. data/lib/threatinator/logger.rb +66 -0
  118. data/lib/threatinator/logging.rb +20 -0
  119. data/lib/threatinator/model/base.rb +23 -0
  120. data/lib/threatinator/model/collection.rb +89 -0
  121. data/lib/threatinator/model/observables/fqdn_collection.rb +15 -0
  122. data/lib/threatinator/model/observables/ipv4.rb +33 -0
  123. data/lib/threatinator/model/observables/ipv4_collection.rb +14 -0
  124. data/lib/threatinator/model/observables/url_collection.rb +16 -0
  125. data/lib/threatinator/model/validations/type.rb +21 -0
  126. data/lib/threatinator/model/validations.rb +1 -0
  127. data/lib/threatinator/output.rb +50 -0
  128. data/lib/threatinator/parser.rb +23 -0
  129. data/lib/threatinator/parsers/csv/parser.rb +77 -0
  130. data/lib/threatinator/parsers/csv.rb +7 -0
  131. data/lib/threatinator/parsers/getline/parser.rb +45 -0
  132. data/lib/threatinator/parsers/getline.rb +8 -0
  133. data/lib/threatinator/parsers/json/adapters/oj.rb +65 -0
  134. data/lib/threatinator/parsers/json/parser.rb +45 -0
  135. data/lib/threatinator/parsers/json/record.rb +20 -0
  136. data/lib/threatinator/parsers/json.rb +8 -0
  137. data/lib/threatinator/parsers/xml/node.rb +79 -0
  138. data/lib/threatinator/parsers/xml/node_builder.rb +39 -0
  139. data/lib/threatinator/parsers/xml/parser.rb +44 -0
  140. data/lib/threatinator/parsers/xml/path.rb +70 -0
  141. data/lib/threatinator/parsers/xml/pattern.rb +53 -0
  142. data/lib/threatinator/parsers/xml/record.rb +14 -0
  143. data/lib/threatinator/parsers/xml/sax_document.rb +64 -0
  144. data/lib/threatinator/parsers/xml.rb +8 -0
  145. data/lib/threatinator/plugin_loader.rb +115 -0
  146. data/lib/threatinator/plugins/output/amqp/config.rb +18 -0
  147. data/lib/threatinator/plugins/output/amqp.rb +41 -0
  148. data/lib/threatinator/plugins/output/csv.rb +58 -0
  149. data/lib/threatinator/plugins/output/json/config.rb +14 -0
  150. data/lib/threatinator/plugins/output/json.rb +53 -0
  151. data/lib/threatinator/plugins/output/null.rb +17 -0
  152. data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
  153. data/lib/threatinator/record.rb +22 -0
  154. data/lib/threatinator/registry.rb +53 -0
  155. data/lib/threatinator/util.rb +15 -0
  156. data/lib/threatinator.rb +3 -0
  157. data/spec/feeds/ET_block-ip_reputation_spec.rb +50 -0
  158. data/spec/feeds/ET_compromised-ip_reputation_spec.rb +47 -0
  159. data/spec/feeds/ET_openbadlist-ip_reputation_spec.rb +56 -0
  160. data/spec/feeds/alienvault-ip_reputation_spec.rb +46 -0
  161. data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +46 -0
  162. data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +46 -0
  163. data/spec/feeds/autoshun_shunlist_spec.rb +38 -0
  164. data/spec/feeds/bambenek_c2_masterlist-domain_reputation_spec.rb +38 -0
  165. data/spec/feeds/bambenek_c2_masterlist-ip_reputation_spec.rb +39 -0
  166. data/spec/feeds/bambenek_dga_feed-domain_reputation_spec.rb +39 -0
  167. data/spec/feeds/berkeley-ip_reputation_spec.rb +47 -0
  168. data/spec/feeds/bitcash_cz_blacklist-ip_reputation_spec.rb +50 -0
  169. data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +47 -0
  170. data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +47 -0
  171. data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +47 -0
  172. data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +47 -0
  173. data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +47 -0
  174. data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +47 -0
  175. data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +47 -0
  176. data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +47 -0
  177. data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +47 -0
  178. data/spec/feeds/botscout-ip_reputation_spec.rb +50 -0
  179. data/spec/feeds/cert_mxpoison-ip_reputation_spec.rb +47 -0
  180. data/spec/feeds/chaosreigns-ip_reputation_spec.rb +50 -0
  181. data/spec/feeds/ciarmy-ip_reputation_spec.rb +47 -0
  182. data/spec/feeds/cruzit-ip_reputation_spec.rb +47 -0
  183. data/spec/feeds/cydef_torexit-ip_reputation_spec.rb +47 -0
  184. data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +47 -0
  185. data/spec/feeds/danger_bruteforce-ip_reputation_spec.rb +47 -0
  186. data/spec/feeds/data/ET_block-ip_reputation.txt +80 -0
  187. data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
  188. data/spec/feeds/data/ET_openbadlist-ip_reputation.txt +62 -0
  189. data/spec/feeds/data/alienvault-ip_reputation.txt +18 -0
  190. data/spec/feeds/data/arbor_domainlist.txt +11 -0
  191. data/spec/feeds/data/arbor_ssh.txt +16 -0
  192. data/spec/feeds/data/autoshun_shunlist.csv +20 -0
  193. data/spec/feeds/data/bambenek_c2-dommasterlist.csv +30 -0
  194. data/spec/feeds/data/bambenek_c2-ipmasterlist.csv +27 -0
  195. data/spec/feeds/data/bambenek_dga_feed.csv +42 -0
  196. data/spec/feeds/data/berkeley.txt +29 -0
  197. data/spec/feeds/data/bitcash_cz_blacklist.txt +7 -0
  198. data/spec/feeds/data/blocklist_de_apache-ip-reputation.txt +17 -0
  199. data/spec/feeds/data/blocklist_de_bots-ip-reputation.txt +15 -0
  200. data/spec/feeds/data/blocklist_de_ftp-ip-reputation.txt +7 -0
  201. data/spec/feeds/data/blocklist_de_imap-ip-reputation.txt +8 -0
  202. data/spec/feeds/data/blocklist_de_pop3-ip-reputation.txt +11 -0
  203. data/spec/feeds/data/blocklist_de_proftpd-ip-reputation.txt +12 -0
  204. data/spec/feeds/data/blocklist_de_sip-ip-reputation.txt +9 -0
  205. data/spec/feeds/data/blocklist_de_ssh-ip-reputation.txt +10 -0
  206. data/spec/feeds/data/blocklist_de_strongips-ip-reputation.txt +11 -0
  207. data/spec/feeds/data/botscout-ip-reputation.txt +713 -0
  208. data/spec/feeds/data/cert_mxpoison-ip_reputation.txt +17 -0
  209. data/spec/feeds/data/chaosreigns-ip-reputation.txt +26 -0
  210. data/spec/feeds/data/ciarmy-ip-reputation.txt +11 -0
  211. data/spec/feeds/data/cruzit-ip-reputation.txt +14 -0
  212. data/spec/feeds/data/cydef_torexit-ip_reputation.txt +27 -0
  213. data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
  214. data/spec/feeds/data/danger_bruteforce-ip_reputation.txt +12 -0
  215. data/spec/feeds/data/dshield_topattackers.xml +4 -0
  216. data/spec/feeds/data/falconcrest_iplist.txt +345 -0
  217. data/spec/feeds/data/feodo_domainlist.txt +18 -0
  218. data/spec/feeds/data/feodo_iplist.txt +20 -0
  219. data/spec/feeds/data/h3x_asprox.txt +20 -0
  220. data/spec/feeds/data/hosts-file_hphostspartial_domainlist.txt +24 -0
  221. data/spec/feeds/data/infiltrated_iplist.txt +16 -0
  222. data/spec/feeds/data/infiltrated_vabl_iplist.txt +33 -0
  223. data/spec/feeds/data/isc_suspicious_high_domainlist.txt +26 -0
  224. data/spec/feeds/data/isc_suspicious_low_domainlist.txt +34 -0
  225. data/spec/feeds/data/isc_suspicious_medium_domainlist.txt +32 -0
  226. data/spec/feeds/data/malc0de_domainlist.txt +18 -0
  227. data/spec/feeds/data/malc0de_iplist.txt +14 -0
  228. data/spec/feeds/data/malwaredomainlist-url-reputation.txt +8 -0
  229. data/spec/feeds/data/malwaredomains_domainlist.txt +24 -0
  230. data/spec/feeds/data/malwaredomains_dyndns_domainlist.txt +34 -0
  231. data/spec/feeds/data/malwaredomains_justdomains_domainlist.txt +18 -0
  232. data/spec/feeds/data/mirc_domainlist.txt +31 -0
  233. data/spec/feeds/data/multiproxy_iplist.txt +15 -0
  234. data/spec/feeds/data/nothink_irc_iplist.txt +14 -0
  235. data/spec/feeds/data/nothink_ssh_iplist.txt +10 -0
  236. data/spec/feeds/data/openbl_iplist.txt +12 -0
  237. data/spec/feeds/data/openphish-url-reputation.txt +16 -0
  238. data/spec/feeds/data/packetmail_perimeterbad-ip_reputation.txt +44 -0
  239. data/spec/feeds/data/palevo_domainlist.txt +25 -0
  240. data/spec/feeds/data/palevo_iplist.txt +24 -0
  241. data/spec/feeds/data/phishtank-sample.json.gz +0 -0
  242. data/spec/feeds/data/sigmaproject_atma.return.gz +0 -0
  243. data/spec/feeds/data/sigmaproject_spyware.return.gz +0 -0
  244. data/spec/feeds/data/sigmaproject_webexploit.return.gz +0 -0
  245. data/spec/feeds/data/snort_bpf-ip_reputation.txt +16 -0
  246. data/spec/feeds/data/spyeye_domainlist.txt +16 -0
  247. data/spec/feeds/data/spyeye_iplist.txt +19 -0
  248. data/spec/feeds/data/steeman-ip-reputation.txt +13 -0
  249. data/spec/feeds/data/t-arend-de_ssh_iplist.txt +17 -0
  250. data/spec/feeds/data/the_haleys_ssh_iplist.txt +12 -0
  251. data/spec/feeds/data/trustedsec-ip-reputation.txt +12 -0
  252. data/spec/feeds/data/valid.json +2908 -0
  253. data/spec/feeds/data/virbl-ip_reputation.txt +14 -0
  254. data/spec/feeds/data/vxvault-url-reputation.txt +15 -0
  255. data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
  256. data/spec/feeds/data/yoyo_adservers.txt +25 -0
  257. data/spec/feeds/data/zeus-ip_reputation.txt +285 -0
  258. data/spec/feeds/data/zeus_domainlist.txt +27 -0
  259. data/spec/feeds/dshield_attackers-top1000_spec.rb +39 -0
  260. data/spec/feeds/falconcrest-ip_reputation_spec.rb +39 -0
  261. data/spec/feeds/feodo-domain_reputation_spec.rb +47 -0
  262. data/spec/feeds/feodo-ip_reputation_spec.rb +47 -0
  263. data/spec/feeds/h3x_asprox-ip_reputation_spec.rb +50 -0
  264. data/spec/feeds/hosts-file_hphostspartial-domain_reputation_spec.rb +47 -0
  265. data/spec/feeds/infiltrated-ip_reputation_spec.rb +47 -0
  266. data/spec/feeds/infiltrated_vabl-ip_reputation_spec.rb +47 -0
  267. data/spec/feeds/isc_suspicious_high-domain_reputation_spec.rb +47 -0
  268. data/spec/feeds/isc_suspicious_low-domain_reputation_spec.rb +47 -0
  269. data/spec/feeds/isc_suspicious_medium-domain_reputation_spec.rb +47 -0
  270. data/spec/feeds/malc0de-domain_reputation_spec.rb +47 -0
  271. data/spec/feeds/malc0de-ip_reputation_spec.rb +47 -0
  272. data/spec/feeds/malwaredomainlist_url_reputation_spec.rb +50 -0
  273. data/spec/feeds/malwaredomains-domain_reputation_spec.rb +47 -0
  274. data/spec/feeds/malwaredomains_dyndns-domain_reputation_spec.rb +47 -0
  275. data/spec/feeds/malwaredomains_justdomains-domain_reputation_spec.rb +47 -0
  276. data/spec/feeds/mirc-domain_reputation_spec.rb +47 -0
  277. data/spec/feeds/multiproxy-ip_reputation_spec.rb +47 -0
  278. data/spec/feeds/nothink_irc-ip_reputation_spec.rb +47 -0
  279. data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +47 -0
  280. data/spec/feeds/openbl-ip_reputation_spec.rb +47 -0
  281. data/spec/feeds/openphish_url_reputation_spec.rb +50 -0
  282. data/spec/feeds/packetmail_perimeterbad-ip_reputation_spec.rb +47 -0
  283. data/spec/feeds/palevo-domain_reputation_spec.rb +47 -0
  284. data/spec/feeds/palevo-ip_reputation_spec.rb +47 -0
  285. data/spec/feeds/phishtank_spec.rb +41 -0
  286. data/spec/feeds/sigmaproject_atma_spec.rb +62 -0
  287. data/spec/feeds/sigmaproject_spyware_spec.rb +63 -0
  288. data/spec/feeds/sigmaproject_webexploit_spec.rb +62 -0
  289. data/spec/feeds/snort_bpf-ip_reputation_spec.rb +47 -0
  290. data/spec/feeds/spyeye-domain_reputation_spec.rb +47 -0
  291. data/spec/feeds/spyeye-ip_reputation_spec.rb +47 -0
  292. data/spec/feeds/steeman-ip_reputation_spec.rb +50 -0
  293. data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +47 -0
  294. data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +47 -0
  295. data/spec/feeds/trustedsec-ip_reputation_spec.rb +47 -0
  296. data/spec/feeds/virbl-ip_reputation_spec.rb +47 -0
  297. data/spec/feeds/vxvault_url_reputation_spec.rb +50 -0
  298. data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +47 -0
  299. data/spec/feeds/yoyo_adservers_spec.rb +47 -0
  300. data/spec/feeds/zeus-domain_reputation_spec.rb +47 -0
  301. data/spec/feeds/zeus-ip_reputation_spec.rb +47 -0
  302. data/spec/fixtures/feed/provider1/feed1.feed +6 -0
  303. data/spec/fixtures/parsers/test.xml +13 -0
  304. data/spec/fixtures/parsers/test_self_closing.xml +20 -0
  305. data/spec/fixtures/plugins/bad/threatinator/plugins/test_error1/plugin.rb +1 -0
  306. data/spec/fixtures/plugins/bad/threatinator/plugins/test_missing1/plugin.rb +0 -0
  307. data/spec/fixtures/plugins/fake.rb +19 -0
  308. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_a.rb +8 -0
  309. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_b.rb +8 -0
  310. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_c.rb +8 -0
  311. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_d.rb +8 -0
  312. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_e.rb +8 -0
  313. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_f.rb +8 -0
  314. data/spec/spec_helper.rb +54 -0
  315. data/spec/support/bad_feeds/missing_fetcher.feed +7 -0
  316. data/spec/support/bad_feeds/missing_name.feed +6 -0
  317. data/spec/support/bad_feeds/missing_parser.feed +3 -0
  318. data/spec/support/bad_feeds/missing_provider.feed +5 -0
  319. data/spec/support/factories/event.rb +31 -0
  320. data/spec/support/factories/feed.rb +59 -0
  321. data/spec/support/factories/feed_builder.rb +65 -0
  322. data/spec/support/factories/feed_registry.rb +8 -0
  323. data/spec/support/factories/ipv4.rb +36 -0
  324. data/spec/support/factories/output.rb +11 -0
  325. data/spec/support/factories/record.rb +17 -0
  326. data/spec/support/factories/url.rb +34 -0
  327. data/spec/support/factories/xml_node.rb +33 -0
  328. data/spec/support/helpers/io.rb +11 -0
  329. data/spec/support/helpers/models.rb +13 -0
  330. data/spec/support/shared/action_builder.rb +47 -0
  331. data/spec/support/shared/decoder.rb +70 -0
  332. data/spec/support/shared/feed_runner_observer.rb +136 -0
  333. data/spec/support/shared/feeds.rb +233 -0
  334. data/spec/support/shared/fetcher.rb +48 -0
  335. data/spec/support/shared/filter.rb +14 -0
  336. data/spec/support/shared/io-like.rb +7 -0
  337. data/spec/support/shared/model/collection.rb +164 -0
  338. data/spec/support/shared/output.rb +120 -0
  339. data/spec/support/shared/parsers.rb +51 -0
  340. data/spec/support/shared/record.rb +111 -0
  341. data/spec/threatinator/actions/list/action_spec.rb +148 -0
  342. data/spec/threatinator/actions/run/action_spec.rb +106 -0
  343. data/spec/threatinator/actions/run/config_spec.rb +39 -0
  344. data/spec/threatinator/actions/run/coverage_observer_spec.rb +151 -0
  345. data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
  346. data/spec/threatinator/actions/run/status_observer_spec.rb +86 -0
  347. data/spec/threatinator/cli/list_action_builder_spec.rb +57 -0
  348. data/spec/threatinator/cli/run_action_builder_spec.rb +133 -0
  349. data/spec/threatinator/cli_spec.rb +175 -0
  350. data/spec/threatinator/config/base_spec.rb +39 -0
  351. data/spec/threatinator/config/feed_search_spec.rb +76 -0
  352. data/spec/threatinator/decoders/gzip_spec.rb +75 -0
  353. data/spec/threatinator/event_builder_spec.rb +123 -0
  354. data/spec/threatinator/event_spec.rb +254 -0
  355. data/spec/threatinator/event_spec.rb.new +319 -0
  356. data/spec/threatinator/feed_builder_spec.rb +633 -0
  357. data/spec/threatinator/feed_registry_spec.rb +198 -0
  358. data/spec/threatinator/feed_runner_spec.rb +372 -0
  359. data/spec/threatinator/feed_spec.rb +169 -0
  360. data/spec/threatinator/fetcher_spec.rb +12 -0
  361. data/spec/threatinator/fetchers/http_spec.rb +32 -0
  362. data/spec/threatinator/filter_spec.rb +13 -0
  363. data/spec/threatinator/filters/block_spec.rb +16 -0
  364. data/spec/threatinator/filters/comments_spec.rb +13 -0
  365. data/spec/threatinator/filters/whitespace_spec.rb +12 -0
  366. data/spec/threatinator/logger_spec.rb +29 -0
  367. data/spec/threatinator/model/observables/fqdn_collection_spec.rb +41 -0
  368. data/spec/threatinator/model/observables/ipv4_collection_spec.rb +36 -0
  369. data/spec/threatinator/model/observables/ipv4_spec.rb +75 -0
  370. data/spec/threatinator/model/observables/url_collection_spec.rb +45 -0
  371. data/spec/threatinator/model/validations/type_spec.rb +37 -0
  372. data/spec/threatinator/parser_spec.rb +13 -0
  373. data/spec/threatinator/parsers/csv/parser_spec.rb +202 -0
  374. data/spec/threatinator/parsers/getline/parser_spec.rb +83 -0
  375. data/spec/threatinator/parsers/json/parser_spec.rb +106 -0
  376. data/spec/threatinator/parsers/json/record_spec.rb +30 -0
  377. data/spec/threatinator/parsers/xml/node_spec.rb +335 -0
  378. data/spec/threatinator/parsers/xml/parser_spec.rb +263 -0
  379. data/spec/threatinator/parsers/xml/path_spec.rb +209 -0
  380. data/spec/threatinator/parsers/xml/pattern_spec.rb +72 -0
  381. data/spec/threatinator/parsers/xml/record_spec.rb +27 -0
  382. data/spec/threatinator/plugin_loader_spec.rb +238 -0
  383. data/spec/threatinator/plugins/output/csv_spec.rb +47 -0
  384. data/spec/threatinator/plugins/output/null_spec.rb +17 -0
  385. data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -0
  386. data/spec/threatinator/record_spec.rb +19 -0
  387. data/spec/threatinator/registry_spec.rb +97 -0
  388. data/spec/threatinator/runner_spec.rb +273 -0
  389. metadata +674 -0
@@ -0,0 +1,198 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/feed_registry'
3
+ require 'threatinator/config/feed_search'
4
+
5
+ describe Threatinator::FeedRegistry do
6
+ def generate_feedfile(filename, provider, name, url = "https://foobar/#{provider}/#{name}.data")
7
+ File.open(filename, "w") do |fio|
8
+ fio.write <<EOS
9
+ provider "#{provider}"
10
+ name "#{name}"
11
+ fetch_http('#{url}')
12
+
13
+ parse_eachline(:separator => "\n") do |builder, line|
14
+ end
15
+ EOS
16
+ end
17
+ end
18
+
19
+ let(:registry) { described_class.new }
20
+ let(:ten_feeds) { 1.upto(10).map { |i| build(:feed, provider: "prov#{i}", name: "name#{i}") } }
21
+
22
+ describe "#clear" do
23
+ it "should remove all existing registrations" do
24
+ ten_feeds.each do |feed|
25
+ registry.register(feed)
26
+ end
27
+ expect(registry.count).to eq(10)
28
+ registry.clear
29
+ expect(registry.count).to eq(0)
30
+
31
+ expect {
32
+ ten_feeds.each do |feed|
33
+ registry.register(feed)
34
+ end
35
+ }.not_to raise_error
36
+ expect(registry.count).to eq(10)
37
+ end
38
+ end
39
+
40
+ describe "#register" do
41
+ it "should register the provided feed" do
42
+ feed = build(:feed, provider: "my_provider", name: "my_name")
43
+ registry.register(feed)
44
+ expect(registry.get("my_provider", "my_name")).to be(feed)
45
+ end
46
+ it "should return the feed that was registered" do
47
+ feed = build(:feed, provider: "my_provider", name: "my_name")
48
+ expect(registry.register(feed)).to be(feed)
49
+ end
50
+ it "should raise a AlreadyRegisteredError if a feed is already registered with the provider and name" do
51
+ feed1 = build(:feed, provider: "my_provider", name: "my_name")
52
+ feed2 = build(:feed, provider: "my_provider", name: "my_name")
53
+ registry.register(feed1)
54
+ expect {
55
+ registry.register(feed2)
56
+ }.to raise_error(Threatinator::Exceptions::AlreadyRegisteredError)
57
+ end
58
+
59
+ it "should raise a AlreadyRegisteredError if the same feed is registered twice" do
60
+ feed = build(:feed, provider: "my_provider", name: "my_name")
61
+ registry.register(feed)
62
+ expect {
63
+ registry.register(feed)
64
+ }.to raise_error(Threatinator::Exceptions::AlreadyRegisteredError)
65
+ end
66
+ end
67
+
68
+ describe "#register_from_file" do
69
+ let(:feedfile) {FEED_FIXTURES.join("provider1", "feed1.feed").to_s}
70
+
71
+ it "should return the feed after parsing the file" do
72
+ ret = registry.register_from_file(feedfile)
73
+ expect(ret).to be_a(Threatinator::Feed)
74
+ expect(ret.provider).to eq("provider1")
75
+ expect(ret.name).to eq("feed1")
76
+ end
77
+
78
+ it "should have registered the feed" do
79
+ expect(registry.count).to eq(0)
80
+ feed = registry.register_from_file(feedfile)
81
+ expect(registry.count).to eq(1)
82
+ expect(registry.get(feed.provider, feed.name)).to be(feed)
83
+ end
84
+ end
85
+
86
+ describe "#each" do
87
+ it "should enumerate through each reigstered feed" do
88
+ ten_feeds.each do |feed|
89
+ registry.register(feed)
90
+ end
91
+ found_feeds = []
92
+ registry.each do |feed|
93
+ found_feeds << feed
94
+ end
95
+ expect(found_feeds).to match_array(ten_feeds)
96
+ end
97
+ end
98
+
99
+ describe "#count" do
100
+ it "should return the number of feeds contained within the registry" do
101
+ expect(registry.count).to eq(0)
102
+ ten_feeds.each do |feed|
103
+ registry.register(feed)
104
+ end
105
+ expect(registry.count).to eq(10)
106
+ end
107
+ end
108
+
109
+ describe "#get" do
110
+ it "should return nil if the provider/name isn't registered" do
111
+ registry.register(build(:feed, provider: "my_provider", name: "my_name"))
112
+ expect(registry.get("asdf", "1234")).to be_nil
113
+ end
114
+ it "should return the correct feed for the given provider and name" do
115
+ ten_feeds.each { |feed| registry.register(feed) }
116
+ feed = build(:feed, provider: "my_provider", name: "my_name")
117
+ registry.register(feed)
118
+ expect(registry.get("my_provider", "my_name")).to be(feed)
119
+ end
120
+ end
121
+
122
+ describe ".build(feed_search_config)" do
123
+ context "with no feed paths" do
124
+ let(:feed_search_config) {
125
+ Threatinator::Config::FeedSearch.new(exclude_default: true, path: [] )
126
+ }
127
+ let(:registry) { described_class.build(feed_search_config) }
128
+
129
+ it "should not have loaded any feeds" do
130
+ expect(registry.count).to eq(0)
131
+ end
132
+ end
133
+
134
+ context "with feed search paths" do
135
+ let(:feed_search_config) {
136
+ Threatinator::Config::FeedSearch.new(exclude_default: true, path: [
137
+ @feed_path1, @feed_path2
138
+ ] )
139
+ }
140
+ before :each do
141
+ @feed_path1 = Dir.mktmpdir
142
+ @feed_path2 = Dir.mktmpdir
143
+ end
144
+
145
+ after :each do
146
+ FileUtils.remove_entry_secure @feed_path1
147
+ FileUtils.remove_entry_secure @feed_path2
148
+ end
149
+
150
+ it "should load feeds from all of the configured paths" do
151
+ 5.times do |i|
152
+ generate_feedfile(File.join(@feed_path1, "feed#{i}.feed"), "provider1", "feed#{i}")
153
+ generate_feedfile(File.join(@feed_path2, "feed#{i}.feed"), "provider2", "feed#{i}")
154
+ end
155
+ registry = described_class.build(feed_search_config)
156
+ expect(registry.count).to eq(10)
157
+ end
158
+
159
+ it "should ignore files that don't end with .feed" do
160
+ generate_feedfile(File.join(@feed_path1, "feed1.fee"), "provider1", "feed1")
161
+ generate_feedfile(File.join(@feed_path1, "feed1.fed"), "provider1", "feed2")
162
+ generate_feedfile(File.join(@feed_path1, "feed1.rb"), "provider1", "feed3")
163
+ generate_feedfile(File.join(@feed_path1, "feed1.feed"), "real_provider", "my_feed")
164
+ registry = described_class.build(feed_search_config)
165
+ expect(registry.count).to eq(1)
166
+ expect(registry.get("real_provider", "my_feed")).to be_a(Threatinator::Feed)
167
+ expect(registry.get("provider1", "feed1")).to be_nil
168
+ expect(registry.get("provider1", "feed2")).to be_nil
169
+ expect(registry.get("provider1", "feed3")).to be_nil
170
+ end
171
+
172
+ it "should recurse subdirectories, loading feeds from there" do
173
+ level1 = File.join(@feed_path1, "level1")
174
+ level2 = File.join(@feed_path1, "level1", "level2")
175
+ level3 = File.join(@feed_path1, "level1", "level2", "level3")
176
+ FileUtils.mkdir_p level1
177
+ FileUtils.mkdir_p level2
178
+ FileUtils.mkdir_p level3
179
+ generate_feedfile(File.join(level1, "feed1.feed"), "provider1", "feed1")
180
+ generate_feedfile(File.join(level2, "feed2.feed"), "provider1", "feed2")
181
+ generate_feedfile(File.join(level3, "feed3.feed"), "provider1", "feed3")
182
+ registry = described_class.build(feed_search_config)
183
+ expect(registry.count).to eq(3)
184
+ end
185
+
186
+ it "should raise an exception if the same feed provider/name combination appears in multiple files" do
187
+ generate_feedfile(File.join(@feed_path1, "feed1.feed"), "provider1", "feed1")
188
+ generate_feedfile(File.join(@feed_path1, "feed2.feed"), "provider1", "feed1")
189
+ expect {
190
+ described_class.build(feed_search_config)
191
+ }.to raise_error(Threatinator::Exceptions::AlreadyRegisteredError)
192
+ end
193
+
194
+ end
195
+
196
+
197
+ end
198
+ end
@@ -0,0 +1,372 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/feed_runner'
3
+
4
+ describe Threatinator::FeedRunner do
5
+ class DummyParser < Threatinator::Parser
6
+ def initialize(records, opts = {})
7
+ @records = records
8
+ end
9
+
10
+ def run(io)
11
+ @records.each do |record|
12
+ yield(record)
13
+ end
14
+ end
15
+ end
16
+
17
+ class DummyFetcher < Threatinator::Fetcher
18
+ def initialize(io, opts = {})
19
+ @io = io
20
+ end
21
+
22
+ def fetch
23
+ return @io
24
+ end
25
+ end
26
+
27
+ class DummyDecoder < Threatinator::Decoder
28
+ def initialize(io)
29
+ @io = io
30
+ end
31
+
32
+ def decode(arg_io)
33
+ return @io
34
+ end
35
+ end
36
+
37
+ class TestObserver
38
+ attr_reader :updates
39
+ def initialize
40
+ @updates = []
41
+ end
42
+
43
+ def update(*args)
44
+ @updates << args
45
+ end
46
+ end
47
+
48
+ class DummyOutput < Threatinator::Output
49
+ def handle_event(event); end
50
+ def finish; end
51
+ end
52
+
53
+ def create_mock_io(name = "io")
54
+ ret = double(name)
55
+ allow(ret).to receive(:close)
56
+ allow(ret).to receive(:closed?).and_return(false)
57
+ ret
58
+ end
59
+
60
+ let(:output_formatter ) { DummyOutput.new(nil) }
61
+ let(:io) { create_mock_io("io") }
62
+ let(:fetcher) { DummyFetcher.new(io) }
63
+
64
+ let(:record1) { Threatinator::Record.new('a1') }
65
+ let(:record2) { Threatinator::Record.new('a2') }
66
+ let(:record3) { Threatinator::Record.new('a3') }
67
+ let(:records) { [ record1, record2, record3 ] }
68
+ let(:parser) { DummyParser.new(records) }
69
+ let(:parser_block) { lambda { |*args| } }
70
+
71
+ let(:filters) { [] }
72
+ let(:decoders) { [] }
73
+
74
+ let(:feed) {
75
+ build(:feed, fetcher: fetcher,
76
+ parser: parser, filters: filters,
77
+ decoders: decoders, parser_block: parser_block
78
+ )
79
+ }
80
+
81
+ describe ".run(feed, output_formatter)" do
82
+ before :each do
83
+ @feed_runner = double('feed_runner')
84
+ allow(described_class).to receive(:new).and_return(@feed_runner)
85
+ allow(@feed_runner).to receive(:run)
86
+ described_class.run(feed, output_formatter)
87
+ end
88
+
89
+ let(:feed_runner) {@feed_runner}
90
+
91
+ it "initializes a FeedRunner with the given feed and output_formatter" do
92
+ expect(described_class).to have_received(:new).with(feed,output_formatter)
93
+ end
94
+
95
+ it "calls #run on the feed_runner with an empty hash" do
96
+ expect(feed_runner).to have_received(:run).with({})
97
+ end
98
+ end
99
+
100
+ describe ".run(feed, output_formatter, run_opts)" do
101
+ before :each do
102
+ @feed_runner = double('feed_runner')
103
+ allow(described_class).to receive(:new).and_return(@feed_runner)
104
+ allow(@feed_runner).to receive(:run)
105
+ described_class.run(feed, output_formatter, run_opts)
106
+ end
107
+
108
+ let(:feed_runner) {@feed_runner}
109
+ let(:run_opts) { {foo: "bar"} }
110
+
111
+ it "initializes a FeedRunner with the given feed and output_formatter" do
112
+ expect(described_class).to have_received(:new).with(feed,output_formatter)
113
+ end
114
+
115
+ it "calls #run on the feed_runner with an empty hash" do
116
+ expect(feed_runner).to have_received(:run).with(run_opts)
117
+ end
118
+ end
119
+
120
+ context "an instance" do
121
+ let(:feed_runner) { described_class.new(feed, output_formatter) }
122
+ let(:observer) { TestObserver.new }
123
+
124
+ before :each do
125
+ feed_runner.add_observer(observer)
126
+ allow(observer).to receive(:update).and_call_original
127
+ end
128
+
129
+ describe "#run" do
130
+ it "notifies the observer with :start before anything else" do
131
+ expect(observer.updates.first).to be_nil
132
+ feed_runner.run()
133
+ expect(observer.updates.first).to eq([:start])
134
+ end
135
+
136
+ it "fetches, decodes, and then parses records" do
137
+ expect(observer).to receive(:update).with(:start).ordered
138
+ expect(observer).to receive(:update).with(:start_fetch).ordered
139
+ expect(observer).to receive(:update).with(:end_fetch).ordered
140
+ expect(observer).to receive(:update).with(:start_decode).ordered
141
+ expect(observer).to receive(:update).with(:end_decode).ordered
142
+ expect(observer).to receive(:update).with(:start_parse_record, record1).ordered
143
+ expect(observer).to receive(:update).with(:end_parse_record, record1).ordered
144
+ expect(observer).to receive(:update).with(:start_parse_record, record2).ordered
145
+ expect(observer).to receive(:update).with(:end_parse_record, record2).ordered
146
+ expect(observer).to receive(:update).with(:start_parse_record, record3).ordered
147
+ expect(observer).to receive(:update).with(:end_parse_record, record3).ordered
148
+ expect(observer).to receive(:update).with(:end).ordered
149
+ feed_runner.run()
150
+ end
151
+
152
+ it "notifies the observer with :end last" do
153
+ feed_runner.run()
154
+ expect(observer.updates.last).to eq([:end])
155
+ end
156
+
157
+ context "with :io => io" do
158
+ it "does not build or run the fetcher" do
159
+ expect(feed.fetcher_builder).not_to receive(:call)
160
+ feed_runner.run(:io => io)
161
+ end
162
+
163
+ it "does not notify the observer with :start_fetch or :end_fetch" do
164
+ expect(observer).not_to receive(:update).with(:start_fetch)
165
+ expect(observer).not_to receive(:update).with(:end_fetch)
166
+ feed_runner.run(:io => io)
167
+ end
168
+ end
169
+
170
+ context "without :io" do
171
+ it "should generate a new fetcher via fetcher_builder.call, and then fetch" do
172
+ expect(feed.fetcher_builder).to receive(:call).and_call_original
173
+ expect(fetcher).to receive(:fetch).and_call_original
174
+ feed_runner.run
175
+ end
176
+
177
+ it "notifies the observer with :start_fetch, then fetches, then notifies observer with :end_fetch" do
178
+ expect(observer).to receive(:update).with(:start_fetch).ordered
179
+ expect(fetcher).to receive(:fetch).and_call_original.ordered
180
+ expect(observer).to receive(:update).with(:end_fetch).ordered
181
+ feed_runner.run()
182
+ end
183
+ end
184
+
185
+ it "calls the feed.parser_block for each for each message data parsed" do
186
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
187
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record2).ordered
188
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record3).ordered
189
+ feed_runner.run()
190
+ end
191
+
192
+
193
+ context "when handling record" do
194
+ let(:records) { [ record1 ] }
195
+
196
+ it "notifies observer with :start_parse_record, and the record prior to handling" do
197
+ expect(observer).to receive(:update).with(:start_parse_record, record1).ordered
198
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
199
+ feed_runner.run()
200
+ end
201
+
202
+ it "notifies observer with :end_parse_record, and the record after handling" do
203
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
204
+ expect(observer).to receive(:update).with(:end_parse_record, record1).ordered
205
+ feed_runner.run()
206
+ end
207
+
208
+ context "when the record is parsed into one or more events" do
209
+ let(:parser_block) {
210
+ lambda do |cep, record|
211
+ cep.call do |eb|
212
+ eb.type = :c2
213
+ eb.add_ipv4('1.1.1.1')
214
+ end
215
+ cep.call do |eb|
216
+ eb.type = :c2
217
+ eb.add_ipv4('2.2.2.2')
218
+ end
219
+ end
220
+ }
221
+
222
+ it "notifies the observer with (:record_parsed, record, events) for each event" do
223
+ expect(observer).to receive(:update).with(
224
+ :record_parsed, record1, satisfy { |events|
225
+ expect(events[0].ipv4s).to contain_exactly(build(:ipv4, ipv4: '1.1.1.1'))
226
+ expect(events[1].ipv4s).to contain_exactly(build(:ipv4, ipv4: '2.2.2.2'))
227
+ })
228
+
229
+ feed_runner.run()
230
+ end
231
+ end
232
+
233
+ context "when no events have been parsed from the record" do
234
+ let(:parser_block) {
235
+ lambda do |cep, record|
236
+ end
237
+ }
238
+
239
+ it "notifies the observer with (:record_missed, record) for each event" do
240
+ expect(observer).to receive(:update).with(:record_missed, record1)
241
+ feed_runner.run()
242
+ end
243
+ end
244
+
245
+ context "when a record has been filtered" do
246
+ let(:filters) { [ lambda { |record| true } ] }
247
+ it "notifies the observer with (:record_filtered, record)" do
248
+ expect(observer).to receive(:update).with(:record_filtered, record1)
249
+ feed_runner.run()
250
+ end
251
+ end
252
+
253
+ context "when a record has an event that fails to build" do
254
+ let(:parser_block) {
255
+ lambda do |cep, record|
256
+ cep.call do |eb|
257
+ eb.type = :c2
258
+ end
259
+ cep.call do |eb|
260
+ eb.type = :asdf
261
+ end
262
+ end
263
+ }
264
+
265
+ it "notifies the observer with (:record_error, record, array_of_errors)" do
266
+ expect(observer).to receive(:update).with(:record_error, record1, a_collection_containing_exactly(
267
+ kind_of(Threatinator::Exceptions::EventBuildError)
268
+ ))
269
+ feed_runner.run()
270
+ end
271
+
272
+ it "does not notify the observer of any events that may have NOT have errors" do
273
+ expect(observer).not_to receive(:update).with(:record_parsed, record1, kind_of(Object))
274
+ feed_runner.run()
275
+ end
276
+ end
277
+
278
+ end
279
+
280
+ context "filtering" do
281
+ let(:filters) { [ lambda { |record| record.data == "a2" } ] }
282
+
283
+ before :each do
284
+ allow(fetcher).to receive(:fetch).and_return(io)
285
+ end
286
+
287
+ it "only calls the parser_block for data that was not filtered" do
288
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record1).ordered
289
+ expect(feed.parser_block).to receive(:call).with(kind_of(Proc), record3).ordered
290
+ feed_runner.run()
291
+ end
292
+ end
293
+
294
+ context "cleanup" do
295
+ it "closes the IO provided via :io" do
296
+ expect(io).to receive(:closed?).and_return(false)
297
+ expect(io).to receive(:close)
298
+ feed_runner.run(:io => io)
299
+ end
300
+
301
+ it "closes the IO returned by the fetcher" do
302
+ allow(fetcher).to receive(:fetch).and_return(io)
303
+ expect(io).to receive(:closed?).and_return(false)
304
+ expect(io).to receive(:close)
305
+ feed_runner.run
306
+ end
307
+
308
+ it "calls output_formatter.finish" do
309
+ expect(output_formatter).to receive(:finish)
310
+ feed_runner.run
311
+ end
312
+
313
+ context "when handling a chain of IOs created by decoders" do
314
+ let(:decoded_io1) { create_mock_io('decoded_io1') }
315
+ let(:decoded_io2) { create_mock_io('decoded_io2') }
316
+ let(:decoded_io3) { create_mock_io('decoded_io3') }
317
+ let(:decoder1) { DummyDecoder.new(decoded_io1) }
318
+ let(:decoder2) { DummyDecoder.new(decoded_io2) }
319
+ let(:decoder3) { DummyDecoder.new(decoded_io3) }
320
+ let(:decoders) { [ decoder1, decoder2, decoder3 ] }
321
+ it "closes each decoded IO at the end" do
322
+ expect(decoded_io1).to receive(:closed?).and_return(false)
323
+ expect(decoded_io1).to receive(:close)
324
+ expect(decoded_io2).to receive(:closed?).and_return(false)
325
+ expect(decoded_io2).to receive(:close)
326
+ expect(decoded_io3).to receive(:closed?).and_return(false)
327
+ expect(decoded_io3).to receive(:close)
328
+ feed_runner.run
329
+ end
330
+ end
331
+ end
332
+
333
+ context "decoding" do
334
+ let(:decoded_io1) { create_mock_io('decoded_io1') }
335
+ let(:decoded_io2) { create_mock_io('decoded_io2') }
336
+ let(:decoded_io3) { create_mock_io('decoded_io3') }
337
+ let(:decoder1) { DummyDecoder.new(decoded_io1) }
338
+ let(:decoder2) { DummyDecoder.new(decoded_io2) }
339
+ let(:decoder3) { DummyDecoder.new(decoded_io3) }
340
+ let(:decoders) { [ decoder1, decoder2, decoder3 ] }
341
+
342
+ context "without :skip_decoding" do
343
+ it "notifies the observer with :start_decode, decodes, and then notifies the observer with :end_decode" do
344
+ expect(observer).to receive(:update).with(:start_decode).ordered
345
+ expect(decoder1).to receive(:decode).with(io).and_call_original.ordered
346
+ expect(decoder2).to receive(:decode).with(decoded_io1).and_call_original.ordered
347
+ expect(decoder3).to receive(:decode).with(decoded_io2).and_call_original.ordered
348
+ expect(observer).to receive(:update).with(:end_decode).ordered
349
+ feed_runner.run
350
+ end
351
+
352
+ it "should run through each decoder in the order it was added to the feed" do
353
+ expect(decoder1).to receive(:decode).with(io).and_call_original
354
+ expect(decoder2).to receive(:decode).with(decoded_io1).and_call_original
355
+ expect(decoder3).to receive(:decode).with(decoded_io2).and_call_original
356
+ expect(parser).to receive(:run).with(decoded_io3)
357
+ feed_runner.run
358
+ end
359
+ end
360
+
361
+ context "with :skip_decoding => true" do
362
+ it "does not decode" do
363
+ expect(observer).not_to receive(:update).with(:start_decode)
364
+ expect(observer).not_to receive(:update).with(:end_decode)
365
+ expect(parser).to receive(:run).with(io)
366
+ feed_runner.run(skip_decoding: true)
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end