4533: Move all controller tests into controllers/, merge overlapping class defs.
[arvados.git] / apps / workbench / test / test_helper.rb
1 ENV["RAILS_ENV"] = "test" if (ENV["RAILS_ENV"] != "diagnostics")
2
3 unless ENV["NO_COVERAGE_TEST"]
4   begin
5     require 'simplecov'
6     require 'simplecov-rcov'
7     class SimpleCov::Formatter::MergedFormatter
8       def format(result)
9         SimpleCov::Formatter::HTMLFormatter.new.format(result)
10         SimpleCov::Formatter::RcovFormatter.new.format(result)
11       end
12     end
13     SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
14     SimpleCov.start do
15       add_filter '/test/'
16       add_filter 'initializers/secret_token'
17     end
18   rescue Exception => e
19     $stderr.puts "SimpleCov unavailable (#{e}). Proceeding without."
20   end
21 end
22
23 require File.expand_path('../../config/environment', __FILE__)
24 require 'rails/test_help'
25 require 'mocha/mini_test'
26
27 class ActiveSupport::TestCase
28   # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in
29   # alphabetical order.
30   #
31   # Note: You'll currently still have to declare fixtures explicitly
32   # in integration tests -- they do not yet inherit this setting
33   fixtures :all
34   def use_token token_name
35     auth = api_fixture('api_client_authorizations')[token_name.to_s]
36     Thread.current[:arvados_api_token] = auth['api_token']
37   end
38
39   teardown do
40     Thread.current[:arvados_api_token] = nil
41     Thread.current[:user] = nil
42     Thread.current[:reader_tokens] = nil
43     # Diagnostics suite doesn't run a server, so there's no cache to clear.
44     Rails.cache.clear unless (Rails.env == "diagnostics")
45     # Restore configuration settings changed during tests
46     $application_config.each do |k,v|
47       if k.match /^[^.]*$/
48         Rails.configuration.send (k + '='), v
49       end
50     end
51   end
52 end
53
54 module ApiFixtureLoader
55   def self.included(base)
56     base.extend(ClassMethods)
57   end
58
59   module ClassMethods
60     @@api_fixtures = {}
61     def api_fixture(name, *keys)
62       # Returns the data structure from the named API server test fixture.
63       @@api_fixtures[name] ||= \
64       begin
65         path = File.join(ApiServerForTests::ARV_API_SERVER_DIR,
66                          'test', 'fixtures', "#{name}.yml")
67         file = IO.read(path)
68         trim_index = file.index('# Test Helper trims the rest of the file')
69         file = file[0, trim_index] if trim_index
70         YAML.load(file)
71       end
72       keys.inject(@@api_fixtures[name]) { |hash, key| hash[key] }
73     end
74   end
75   def api_fixture(name, *keys)
76     self.class.api_fixture(name, *keys)
77   end
78
79   def find_fixture(object_class, name)
80     object_class.find(api_fixture(object_class.to_s.pluralize.underscore,
81                                   name, "uuid"))
82   end
83 end
84
85 class ActiveSupport::TestCase
86   include ApiFixtureLoader
87   def session_for api_client_auth_name
88     {
89       arvados_api_token: api_fixture('api_client_authorizations')[api_client_auth_name.to_s]['api_token']
90     }
91   end
92   def json_response
93     Oj.load(@response.body)
94   end
95 end
96
97 class ApiServerForTests
98   ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
99   SERVER_PID_PATH = File.expand_path('tmp/pids/wbtest-server.pid', ARV_API_SERVER_DIR)
100   WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/wstest-server.pid', ARV_API_SERVER_DIR)
101   @main_process_pid = $$
102
103   def _system(*cmd)
104     $stderr.puts "_system #{cmd.inspect}"
105     Bundler.with_clean_env do
106       if not system({'RAILS_ENV' => 'test', "ARVADOS_WEBSOCKETS" => (if @websocket then "ws-only" end)}, *cmd)
107         raise RuntimeError, "#{cmd[0]} returned exit code #{$?.exitstatus}"
108       end
109     end
110   end
111
112   def make_ssl_cert
113     unless File.exists? './self-signed.key'
114       _system('openssl', 'req', '-new', '-x509', '-nodes',
115               '-out', './self-signed.pem',
116               '-keyout', './self-signed.key',
117               '-days', '3650',
118               '-subj', '/CN=localhost')
119     end
120   end
121
122   def kill_server
123     if (pid = find_server_pid)
124       $stderr.puts "Sending TERM to API server, pid #{pid}"
125       Process.kill 'TERM', pid
126     end
127   end
128
129   def find_server_pid
130     pid = nil
131     begin
132       pid = IO.read(@pidfile).to_i
133       $stderr.puts "API server is running, pid #{pid.inspect}"
134     rescue Errno::ENOENT
135     end
136     return pid
137   end
138
139   def run(args=[])
140     ::MiniTest.after_run do
141       self.kill_server
142     end
143
144     @websocket = args.include?("--websockets")
145
146     @pidfile = if @websocket
147                  WEBSOCKET_PID_PATH
148                else
149                  SERVER_PID_PATH
150                end
151
152     # Kill server left over from previous test run
153     self.kill_server
154
155     Capybara.javascript_driver = :poltergeist
156     Dir.chdir(ARV_API_SERVER_DIR) do |apidir|
157       ENV["NO_COVERAGE_TEST"] = "1"
158       if @websocket
159         _system('bundle', 'exec', 'passenger', 'start', '-d', '-p3333',
160                 '--pid-file', @pidfile)
161       else
162         make_ssl_cert
163         _system('bundle', 'exec', 'rake', 'db:test:load')
164         _system('bundle', 'exec', 'rake', 'db:fixtures:load')
165         _system('bundle', 'exec', 'passenger', 'start', '-d', '-p3000',
166                 '--pid-file', @pidfile,
167                 '--ssl',
168                 '--ssl-certificate', 'self-signed.pem',
169                 '--ssl-certificate-key', 'self-signed.key')
170       end
171       timeout = Time.now.tv_sec + 10
172       good_pid = false
173       while (not good_pid) and (Time.now.tv_sec < timeout)
174         sleep 0.2
175         server_pid = find_server_pid
176         good_pid = (server_pid and
177                     (server_pid > 0) and
178                     (Process.kill(0, server_pid) rescue false))
179       end
180       if not good_pid
181         raise RuntimeError, "could not find API server Rails pid"
182       end
183     end
184   end
185 end
186
187 class ActionController::TestCase
188   setup do
189     @counter = 0
190   end
191
192   def check_counter action
193     @counter += 1
194     if @counter == 2
195       assert_equal 1, 2, "Multiple actions in controller test"
196     end
197   end
198
199   [:get, :post, :put, :patch, :delete].each do |method|
200     define_method method do |action, *args|
201       check_counter action
202       super action, *args
203     end
204   end
205 end
206
207 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
208 # override the default. Example:
209 #
210 # class MySuite < ActionDispatch::IntegrationTest
211 #   reset_api_fixtures :after_each_test, false
212 #   reset_api_fixtures :after_suite, true
213 #   ...
214 # end
215 #
216 # The default behavior is reset_api_fixtures(:after_each_test,true).
217 #
218 class ActiveSupport::TestCase
219
220   def self.inherited subclass
221     subclass.class_eval do
222       class << self
223         attr_accessor :want_reset_api_fixtures
224       end
225       @want_reset_api_fixtures = {
226         after_each_test: true,
227         after_suite: false,
228         before_suite: false,
229       }
230     end
231     super
232   end
233   # Existing subclasses of ActiveSupport::TestCase (ones that already
234   # existed before we set up the self.inherited hook above) will not
235   # get their own instance variable. They're not real test cases
236   # anyway, so we give them a "don't reset anywhere" stub.
237   def self.want_reset_api_fixtures
238     {}
239   end
240
241   def self.reset_api_fixtures where, t=true
242     if not want_reset_api_fixtures.has_key? where
243       raise ArgumentError, "There is no #{where.inspect} hook"
244     end
245     self.want_reset_api_fixtures[where] = t
246   end
247
248   def self.run *args
249     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
250     result = super
251     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
252     result
253   end
254
255   def after_teardown
256     if self.class.want_reset_api_fixtures[:after_each_test]
257       self.class.reset_api_fixtures_now
258     end
259     super
260   end
261
262   protected
263   def self.reset_api_fixtures_now
264     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
265     Thread.current[:arvados_api_token] = auth['api_token']
266     ArvadosApiClient.new.api(nil, '../../database/reset', {})
267     Thread.current[:arvados_api_token] = nil
268   end
269 end
270
271 # If it quacks like a duck, it must be a HTTP request object.
272 class RequestDuck
273   def self.host
274     "localhost"
275   end
276
277   def self.port
278     8080
279   end
280
281   def self.protocol
282     "http"
283   end
284 end
285
286 if ENV["RAILS_ENV"].eql? 'test'
287   ApiServerForTests.new.run
288   ApiServerForTests.new.run ["--websockets"]
289 end