cms_scanner 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +14 -0
  6. data/Gemfile +6 -0
  7. data/README.md +20 -0
  8. data/Rakefile +9 -0
  9. data/app/app.rb +4 -0
  10. data/app/controllers.rb +2 -0
  11. data/app/controllers/core.rb +46 -0
  12. data/app/controllers/core/cli_options.rb +68 -0
  13. data/app/controllers/interesting_files.rb +12 -0
  14. data/app/finders.rb +1 -0
  15. data/app/finders/interesting_files.rb +21 -0
  16. data/app/finders/interesting_files/fantastico_fileslist.rb +23 -0
  17. data/app/finders/interesting_files/headers.rb +15 -0
  18. data/app/finders/interesting_files/robots_txt.rb +22 -0
  19. data/app/finders/interesting_files/search_replace_db_2.rb +28 -0
  20. data/app/finders/interesting_files/xml_rpc.rb +62 -0
  21. data/app/formatters.rb +3 -0
  22. data/app/formatters/cli.rb +18 -0
  23. data/app/formatters/cli_no_colour.rb +15 -0
  24. data/app/formatters/json.rb +12 -0
  25. data/app/models.rb +5 -0
  26. data/app/models/fantastico_fileslist.rb +20 -0
  27. data/app/models/headers.rb +37 -0
  28. data/app/models/interesting_file.rb +30 -0
  29. data/app/models/robots_txt.rb +20 -0
  30. data/app/models/xml_rpc.rb +35 -0
  31. data/app/views/cli/core/finished.erb +4 -0
  32. data/app/views/cli/core/started.erb +3 -0
  33. data/app/views/cli/interesting_files/findings.erb +19 -0
  34. data/app/views/cli/scan_aborted.erb +4 -0
  35. data/app/views/json/core/finished.erb +3 -0
  36. data/app/views/json/core/started.erb +3 -0
  37. data/app/views/json/interesting_files/findings.erb +1 -0
  38. data/app/views/json/scan_aborted.erb +4 -0
  39. data/cms_scanner.gemspec +37 -0
  40. data/examples/views/cli/wp_custom/test.erb +1 -0
  41. data/examples/views/json/wp_custom/test.erb +1 -0
  42. data/examples/wpscan.rb +29 -0
  43. data/lib/cms_scanner.rb +71 -0
  44. data/lib/cms_scanner/browser.rb +68 -0
  45. data/lib/cms_scanner/browser/actions.rb +48 -0
  46. data/lib/cms_scanner/browser/options.rb +53 -0
  47. data/lib/cms_scanner/cache/file_store.rb +75 -0
  48. data/lib/cms_scanner/cache/typhoeus.rb +21 -0
  49. data/lib/cms_scanner/controller.rb +90 -0
  50. data/lib/cms_scanner/controllers.rb +34 -0
  51. data/lib/cms_scanner/errors/auth_errors.rb +15 -0
  52. data/lib/cms_scanner/finders.rb +5 -0
  53. data/lib/cms_scanner/finders/finder.rb +27 -0
  54. data/lib/cms_scanner/finders/finding.rb +32 -0
  55. data/lib/cms_scanner/finders/findings.rb +25 -0
  56. data/lib/cms_scanner/finders/independent_finder.rb +30 -0
  57. data/lib/cms_scanner/finders/independent_finders.rb +41 -0
  58. data/lib/cms_scanner/formatter.rb +118 -0
  59. data/lib/cms_scanner/formatter/buffer.rb +15 -0
  60. data/lib/cms_scanner/target.rb +33 -0
  61. data/lib/cms_scanner/target/platform.rb +2 -0
  62. data/lib/cms_scanner/target/platform/php.rb +39 -0
  63. data/lib/cms_scanner/target/platform/wordpress.rb +35 -0
  64. data/lib/cms_scanner/target/platform/wordpress/custom_directories.rb +62 -0
  65. data/lib/cms_scanner/target/server.rb +3 -0
  66. data/lib/cms_scanner/target/server/apache.rb +43 -0
  67. data/lib/cms_scanner/target/server/generic.rb +34 -0
  68. data/lib/cms_scanner/target/server/iis.rb +48 -0
  69. data/lib/cms_scanner/version.rb +4 -0
  70. data/lib/cms_scanner/web_site.rb +68 -0
  71. data/lib/helper.rb +24 -0
  72. data/spec/app/controllers/core_spec.rb +152 -0
  73. data/spec/app/controllers/interesting_files_spec.rb +50 -0
  74. data/spec/app/finders/interesting_files/fantastico_fileslist_spec.rb +68 -0
  75. data/spec/app/finders/interesting_files/headers_spec.rb +38 -0
  76. data/spec/app/finders/interesting_files/robots_txt_spec.rb +56 -0
  77. data/spec/app/finders/interesting_files/search_replace_db_2_spec.rb +55 -0
  78. data/spec/app/finders/interesting_files/xml_rpc_spec.rb +138 -0
  79. data/spec/app/finders/interesting_files_spec.rb +13 -0
  80. data/spec/app/formatters/cli_no_colour_spec.rb +17 -0
  81. data/spec/app/formatters/cli_spec.rb +21 -0
  82. data/spec/app/formatters/json_spec.rb +33 -0
  83. data/spec/app/models/fantastico_fileslist_spec.rb +32 -0
  84. data/spec/app/models/headers_spec.rb +52 -0
  85. data/spec/app/models/interesting_file_spec.rb +51 -0
  86. data/spec/app/models/robots_txt_spec.rb +28 -0
  87. data/spec/app/models/xml_rpc_spec.rb +47 -0
  88. data/spec/cache/.gitignore +4 -0
  89. data/spec/dummy_finders.rb +41 -0
  90. data/spec/fixtures/interesting_files/fantastico_fileslist/fantastico_fileslist.txt +12 -0
  91. data/spec/fixtures/interesting_files/file.txt +4 -0
  92. data/spec/fixtures/interesting_files/headers/interesting.txt +14 -0
  93. data/spec/fixtures/interesting_files/headers/no_interesting.txt +12 -0
  94. data/spec/fixtures/interesting_files/robots_txt/robots.txt +10 -0
  95. data/spec/fixtures/interesting_files/search_replace_db_2/searchreplacedb2.php +188 -0
  96. data/spec/fixtures/interesting_files/xml_rpc/homepage_in_scope_pingback.html +7 -0
  97. data/spec/fixtures/interesting_files/xml_rpc/homepage_out_of_scope_pingback.html +7 -0
  98. data/spec/fixtures/interesting_files/xml_rpc/xmlrpc.php +1 -0
  99. data/spec/fixtures/output.txt +0 -0
  100. data/spec/fixtures/target/platform/php/debug_log/debug.log +2 -0
  101. data/spec/fixtures/target/platform/php/fpd/wp_rss_functions.php +2 -0
  102. data/spec/fixtures/target/platform/wordpress/custom_directories/custom_w_spaces.html +10 -0
  103. data/spec/fixtures/target/platform/wordpress/custom_directories/default.html +14 -0
  104. data/spec/fixtures/target/platform/wordpress/custom_directories/https.html +12 -0
  105. data/spec/fixtures/target/platform/wordpress/detection/default.html +4 -0
  106. data/spec/fixtures/target/platform/wordpress/detection/not_wp.html +8 -0
  107. data/spec/fixtures/target/platform/wordpress/detection/wp_includes.html +3 -0
  108. data/spec/fixtures/target/server/apache/directory_listing/2.2.16.html +15 -0
  109. data/spec/fixtures/target/server/generic/server/apache/basic.txt +5 -0
  110. data/spec/fixtures/target/server/generic/server/iis/basic.txt +6 -0
  111. data/spec/fixtures/target/server/generic/server/not_detected.txt +3 -0
  112. data/spec/fixtures/target/server/iis/directory_listing/no_parent.html +3 -0
  113. data/spec/fixtures/target/server/iis/directory_listing/with_parent.html +3 -0
  114. data/spec/fixtures/views/base/ctrl/local.erb +1 -0
  115. data/spec/fixtures/views/base/ctrl/test.erb +3 -0
  116. data/spec/fixtures/views/base/global.erb +1 -0
  117. data/spec/fixtures/views/base/test.erb +2 -0
  118. data/spec/fixtures/views/based_format/test.erb +1 -0
  119. data/spec/fixtures/views/json/render_me.erb +4 -0
  120. data/spec/lib/browser_spec.rb +141 -0
  121. data/spec/lib/cache/file_store_spec.rb +101 -0
  122. data/spec/lib/cache/typhoeus_spec.rb +30 -0
  123. data/spec/lib/cms_scanner_spec.rb +45 -0
  124. data/spec/lib/controller_spec.rb +23 -0
  125. data/spec/lib/controllers_spec.rb +52 -0
  126. data/spec/lib/finders/findings_spec.rb +49 -0
  127. data/spec/lib/finders/independent_finders_spec.rb +98 -0
  128. data/spec/lib/formatter_spec.rb +136 -0
  129. data/spec/lib/sub_scanner_spec.rb +27 -0
  130. data/spec/lib/target/platforms_spec.rb +13 -0
  131. data/spec/lib/target/servers_spec.rb +13 -0
  132. data/spec/lib/target_spec.rb +50 -0
  133. data/spec/lib/web_site_spec.rb +124 -0
  134. data/spec/shared_examples.rb +11 -0
  135. data/spec/shared_examples/browser_actions.rb +32 -0
  136. data/spec/shared_examples/finding.rb +20 -0
  137. data/spec/shared_examples/formatter_buffer.rb +8 -0
  138. data/spec/shared_examples/formatter_class_methods.rb +26 -0
  139. data/spec/shared_examples/independent_finder.rb +33 -0
  140. data/spec/shared_examples/target/platform/php.rb +58 -0
  141. data/spec/shared_examples/target/platform/wordpress.rb +41 -0
  142. data/spec/shared_examples/target/platform/wordpress/custom_directories.rb +50 -0
  143. data/spec/shared_examples/target/server/apache.rb +33 -0
  144. data/spec/shared_examples/target/server/generic.rb +34 -0
  145. data/spec/shared_examples/target/server/iis.rb +38 -0
  146. data/spec/spec_helper.rb +41 -0
  147. metadata +432 -0
@@ -0,0 +1,5 @@
1
+ Date: Sun, 12 Oct 2014 19:44:42 GMT
2
+ Server: Apache/2.2.16 (Debian)
3
+ X-Powered-By: PHP/5.3.3-7+squeeze19
4
+ Vary: Accept-Encoding
5
+ Content-Type: text/html
@@ -0,0 +1,6 @@
1
+ Content-Length: 1027
2
+ Content-Type: text/html; charset=UTF-8
3
+ Server: Microsoft-IIS/7.5
4
+ X-Powered-By: ASP.NET
5
+ X-UA-Compatible: IE=EmulateIE7
6
+ Date: Sun, 12 Oct 2014 20:15:14 GMT
@@ -0,0 +1,3 @@
1
+ Date: Sun, 12 Oct 2014 19:44:42 GMT
2
+ Vary: Accept-Encoding
3
+ Content-Type: text/html
@@ -0,0 +1,3 @@
1
+ <html><head><title>ex.lo - /dir/</title></head><body><H1>ex.lo - /dir/</H1><hr>
2
+
3
+ <pre>10/8/2014 11:00 PM &lt;dir&gt; <A HREF="/sub-dir/">sub-dir</A>10/10/2014 10:00 PM 168 <A HREF="/web.config">web.config</A><br></pre><hr></body></html>
@@ -0,0 +1,3 @@
1
+ <html><head><title>ex.lo - /dir/</title></head><body><H1>ex.lo - /dir/</H1><hr>
2
+
3
+ <pre><A HREF="/">[To Parent Directory]</A><br><br> 10/8/2014 11:00 PM &lt;dir&gt; <A HREF="/sub-dir/">sub-dir</A>10/10/2014 10:00 PM 168 <A HREF="/web.config">web.config</A><br></pre><hr></body></html>
@@ -0,0 +1 @@
1
+ Local View
@@ -0,0 +1,3 @@
1
+ Test: <%= @var %>
2
+ <%= render('local') %>
3
+ <%= render('@global') %>
@@ -0,0 +1 @@
1
+ Global View
@@ -0,0 +1,2 @@
1
+ It <%= @test %>
2
+ Views Dirs: <%= @views_directories %>
@@ -0,0 +1 @@
1
+ Override the base/test.erb
@@ -0,0 +1,4 @@
1
+ "test": <%= @test.to_json %>,
2
+ <% if @var %>
3
+ "var": <%= @var.to_json %>
4
+ <% end %>
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe CMSScanner::Browser do
4
+
5
+ it_behaves_like described_class::Actions
6
+
7
+ subject(:browser) { described_class.instance(options) }
8
+ before { described_class.reset }
9
+ let(:options) { {} }
10
+ let(:default) do
11
+ {
12
+ ssl_verifypeer: false, ssl_verifyhost: 2,
13
+ headers: { 'User-Agent' => "CMSScanner v#{CMSScanner::VERSION}" }
14
+ }
15
+ end
16
+
17
+ describe '#forge_request' do
18
+ it 'returns a Typhoeus::Request' do
19
+ expect(browser.forge_request('http://example.com')).to be_a Typhoeus::Request
20
+ end
21
+ end
22
+
23
+ describe '#default_request_params' do
24
+ its(:default_request_params) { should eq default }
25
+
26
+ context 'when some attributes are set' do
27
+ let(:options) do
28
+ {
29
+ cache_ttl: 200, connect_timeout: 10,
30
+ http_auth: { username: 'log', password: 'pwd' },
31
+ cookie_jar: '/tmp/cookie_jar.txt'
32
+ }
33
+ end
34
+
35
+ let(:expected) do
36
+ default.merge(
37
+ cache_ttl: 200, connecttimeout: 10, userpwd: 'log:pwd',
38
+ cookiejar: options[:cookie_jar], cookiefile: options[:cookie_jar]
39
+ )
40
+ end
41
+
42
+ its(:default_request_params) { should eq expected }
43
+ end
44
+ end
45
+
46
+ describe '#request_params' do
47
+ context 'when no param is given' do
48
+ its(:request_params) { should eq default }
49
+ end
50
+
51
+ context 'when params are supplied' do
52
+ let(:params) { { another_param: true, headers: { 'Accept' => 'None' } } }
53
+
54
+ it 'merges them (headers should be correctly merged)' do
55
+ expect(browser.request_params(params)).to eq default
56
+ .merge(params) { |key, oldval, newval| key == :headers ? oldval.merge(newval) : newval }
57
+ end
58
+
59
+ context 'when browser options' do
60
+ let(:options) { { proxy: 'http://127.0.0.1:8080' } }
61
+
62
+ it 'returns the correct hash' do
63
+ expect(browser.request_params(params)).to eq default
64
+ .merge(options)
65
+ .merge(params) { |key, oldval, newval| key == :headers ? oldval.merge(newval) : newval }
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '#load_options' do
72
+ context 'when no options' do
73
+ it 'does not load anything' do
74
+ described_class::OPTIONS.each do |sym|
75
+ expected = sym == :user_agent ? "CMSScanner v#{CMSScanner::VERSION}" : nil
76
+
77
+ expect(browser.send(sym)).to eq expected
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'when options are supplied' do
83
+ module CMSScanner
84
+ # Test accessor
85
+ class Browser
86
+ attr_accessor :test
87
+ end
88
+ end
89
+
90
+ let(:options) do
91
+ { cache_ttl: 200, max_threads: 10, test: 'should not be set',
92
+ user_agent: 'UA', proxy: false }
93
+ end
94
+
95
+ it 'merges the browser options only' do
96
+ described_class::OPTIONS.each do |sym|
97
+ expected = options.key?(sym) ? options[sym] : nil
98
+
99
+ expect(browser.send(sym)).to eq expected
100
+ end
101
+
102
+ expect(browser.test).to be nil
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#hydra' do
108
+ context 'when #max_threads is nil' do
109
+ its('hydra.max_concurrency') { should eq 1 }
110
+ end
111
+
112
+ context 'when #max_threads' do
113
+ let(:options) { { max_threads: 20 } }
114
+
115
+ its('hydra.max_concurrency') { should eq options[:max_threads] }
116
+ end
117
+ end
118
+
119
+ describe '#max_threads=' do
120
+ after do
121
+ browser.max_threads = @threads
122
+
123
+ expect(browser.max_threads).to eq @expected
124
+ expect(browser.hydra.max_concurrency).to eq @expected
125
+ end
126
+
127
+ context 'when <= 0' do
128
+ it 'sets the @threads to 1' do
129
+ @threads = -2
130
+ @expected = 1
131
+ end
132
+ end
133
+
134
+ context 'when > 0' do
135
+ it 'sets the @threads' do
136
+ @threads = 20
137
+ @expected = @threads
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe CMSScanner::Cache::FileStore do
4
+
5
+ let(:cache_dir) { File.join(CACHE, 'cache_file_store') }
6
+ subject(:cache) { described_class.new(cache_dir) }
7
+
8
+ before { FileUtils.rm_r(cache_dir, secure: true) if Dir.exist?(cache_dir) }
9
+ after { cache.clean }
10
+
11
+ describe '#new, #storage_path, #serializer' do
12
+ its(:serializer) { should be Marshal }
13
+ its(:storage_path) { should eq cache_dir }
14
+ end
15
+
16
+ describe '#clean' do
17
+ it 'removes all files from the cache dir' do
18
+ # let's create some files into the directory first
19
+ (0..5).each do |i|
20
+ File.new(File.join(cache.storage_path, "file_#{i}.txt"), File::CREAT)
21
+ end
22
+
23
+ expect(count_files_in_dir(cache.storage_path, 'file_*.txt')).to eq 6
24
+ cache.clean
25
+ expect(count_files_in_dir(cache.storage_path)).to eq 0
26
+ end
27
+ end
28
+
29
+ describe '#read_entry?' do
30
+ let(:key) { 'key1' }
31
+
32
+ after do
33
+ File.write(cache.entry_expiration_path(key), @expiration) if @expiration
34
+
35
+ expect(cache.read_entry(key)).to eq @expected
36
+ end
37
+
38
+ context 'when the entry does not exists' do
39
+ it 'returns nil' do
40
+ @expected = nil
41
+ end
42
+ end
43
+
44
+ context 'when the file is empty (marshal data too short error)' do
45
+ it 'returns nil' do
46
+ File.new(cache.entry_path(key), File::CREAT)
47
+
48
+ @expiration = Time.now.to_i + 200
49
+ @expected = nil
50
+ end
51
+ end
52
+
53
+ context 'when the entry has expired' do
54
+ it 'returns nil' do
55
+ @expiration = Time.now.to_i - 200
56
+ @expected = nil
57
+ end
58
+ end
59
+
60
+ context 'when the entry has not expired' do
61
+ it 'returns the entry' do
62
+ File.write(cache.entry_path(key), cache.serializer.dump('testing data'))
63
+
64
+ @expiration = Time.now.to_i + 600
65
+ @expected = 'testing data'
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#write_entry' do
71
+ after do
72
+ cache.write_entry(@key, @data, @ttl)
73
+ expect(cache.read_entry(@key)).to eq @expected
74
+ end
75
+
76
+ it 'should get the correct entry (string)' do
77
+ @ttl = 10
78
+ @key = 'some_key'
79
+ @data = 'Hello World !'
80
+ @expected = @data
81
+ end
82
+
83
+ context 'when cache_ttl <= 0' do
84
+ it 'does not write the entry' do
85
+ @ttl = 0
86
+ @key = 'another_key'
87
+ @data = 'Another Hello World !'
88
+ @expected = nil
89
+ end
90
+ end
91
+
92
+ context 'when cache_ttl is nil' do
93
+ it 'does not write the entry' do
94
+ @ttl = nil
95
+ @key = 'test'
96
+ @data = 'test'
97
+ @expected = nil
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe CMSScanner::Cache::Typhoeus do
4
+
5
+ subject(:cache) { described_class.new(cache_dir) }
6
+
7
+ let(:cache_dir) { File.join(CACHE, 'typhoeus_cache') }
8
+ let(:url) { 'http://example.com' }
9
+ let(:request) { Typhoeus::Request.new(url, cache_ttl: 20) }
10
+ let(:key) { request.hash.to_s }
11
+
12
+ describe '#get' do
13
+ it 'calls #read_entry' do
14
+ expect(cache).to receive(:read_entry).with(key)
15
+
16
+ cache.get(request)
17
+ end
18
+ end
19
+
20
+ describe '#set' do
21
+ let(:response) { Typhoeus::Response.new }
22
+
23
+ it 'calls #write_entry' do
24
+ expect(cache).to receive(:write_entry).with(key, response, request.cache_ttl)
25
+
26
+ cache.set(request, response)
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ module CMSScanner
4
+ module Controller
5
+ # Failure class for testing
6
+ class SpecFailure < Base
7
+ def before_scan
8
+ fail 'error spotted'
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ describe CMSScanner::Scan do
15
+
16
+ subject(:scanner) { described_class.new }
17
+ let(:controller) { CMSScanner::Controller }
18
+
19
+ describe '#new, #controllers' do
20
+ its(:controllers) { should eq([controller::Core.new]) }
21
+ end
22
+
23
+ describe '#run' do
24
+ it 'runs the controlllers and calls the formatter#beautify' do
25
+ expect(scanner.controllers).to receive(:run)
26
+ expect(scanner.formatter).to receive(:beautify)
27
+ scanner.run
28
+ end
29
+
30
+ context 'when an error is raised during the #run' do
31
+ it 'aborts the scan with the associated output' do
32
+ scanner.controllers[0] = controller::SpecFailure.new
33
+
34
+ expect(scanner.formatter).to receive(:output)
35
+ .with('@scan_aborted', hash_including(:reason, :trace, :verbose))
36
+
37
+ scanner.run
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#datastore' do
43
+ its(:datastore) { should eq({}) }
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe CMSScanner::Controller do
4
+
5
+ subject(:controller) { described_class::Base.new }
6
+
7
+ context 'when parsed_options' do
8
+ before { described_class::Base.parsed_options = parsed_options }
9
+ let(:parsed_options) { { url: 'http://example.com/' } }
10
+
11
+ its(:parsed_options) { should eq(parsed_options) }
12
+ its(:formatter) { should be_a CMSScanner::Formatter::Cli }
13
+ its(:target) { should be_a CMSScanner::Target }
14
+
15
+ describe '#render' do
16
+ it 'calls the formatter#render' do
17
+ expect(controller.formatter).to receive(:render).with('test', { verbose: nil }, 'base')
18
+ controller.render('test')
19
+ end
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ module CMSScanner
4
+ module Controller
5
+ class Spec < Base
6
+ end
7
+ end
8
+ end
9
+
10
+ describe CMSScanner::Controllers do
11
+
12
+ subject(:controllers) { described_class.new }
13
+ let(:controller_mod) { CMSScanner::Controller }
14
+
15
+ describe '#<<' do
16
+ its(:size) { should be 0 }
17
+
18
+ context 'when controllers are added' do
19
+ before { controllers << controller_mod::Spec.new << controller_mod::Base.new }
20
+
21
+ its(:size) { should be 2 }
22
+ end
23
+
24
+ context 'when a controller is added twice' do
25
+ before { 2.times { controllers << controller_mod::Spec.new } }
26
+
27
+ its(:size) { should be 1 }
28
+ end
29
+
30
+ it 'returns self' do
31
+ expect(controllers << controller_mod::Spec.new).to be_a described_class
32
+ end
33
+ end
34
+
35
+ describe '#run' do
36
+ it 'runs the before_scan, run and after_scan methods of each controller' do
37
+ spec = controller_mod::Spec.new
38
+ base = controller_mod::Base.new
39
+
40
+ controllers << spec << base
41
+
42
+ # TODO: Any way to test the orders ? (after_scan should reverse the order)
43
+ [base, spec].each do |c|
44
+ expect(c).to receive(:before_scan)
45
+ expect(c).to receive(:run)
46
+ expect(c).to receive(:after_scan)
47
+ end
48
+ controllers.run
49
+ end
50
+ end
51
+
52
+ end