Merge branch '6605-arv-copy-http' closes #6605
[arvados.git] / apps / workbench / test / test_helper.rb
1 ENV["RAILS_ENV"] = "test" if (ENV["RAILS_ENV"] != "diagnostics" and ENV["RAILS_ENV"] != "performance")
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     was = Thread.current[:arvados_api_token]
36     auth = api_fixture('api_client_authorizations')[token_name.to_s]
37     Thread.current[:arvados_api_token] = auth['api_token']
38     if block_given?
39       begin
40         yield
41       ensure
42         Thread.current[:arvados_api_token] = was
43       end
44     end
45   end
46
47   teardown do
48     Thread.current[:arvados_api_token] = nil
49     Thread.current[:user] = nil
50     Thread.current[:reader_tokens] = nil
51     # Diagnostics suite doesn't run a server, so there's no cache to clear.
52     Rails.cache.clear unless (Rails.env == "diagnostics")
53     # Restore configuration settings changed during tests
54     self.class.reset_application_config
55   end
56
57   def self.reset_application_config
58     $application_config.each do |k,v|
59       if k.match /^[^.]*$/
60         Rails.configuration.send (k + '='), v
61       end
62     end
63   end
64 end
65
66 module ApiFixtureLoader
67   def self.included(base)
68     base.extend(ClassMethods)
69   end
70
71   module ClassMethods
72     @@api_fixtures = {}
73     def api_fixture(name, *keys)
74       # Returns the data structure from the named API server test fixture.
75       @@api_fixtures[name] ||= \
76       begin
77         path = File.join(ApiServerForTests::ARV_API_SERVER_DIR,
78                          'test', 'fixtures', "#{name}.yml")
79         file = IO.read(path)
80         trim_index = file.index('# Test Helper trims the rest of the file')
81         file = file[0, trim_index] if trim_index
82         YAML.load(file)
83       end
84       keys.inject(@@api_fixtures[name]) { |hash, key| hash[key] }
85     end
86   end
87   def api_fixture(name, *keys)
88     self.class.api_fixture(name, *keys)
89   end
90
91   def find_fixture(object_class, name)
92     object_class.find(api_fixture(object_class.to_s.pluralize.underscore,
93                                   name, "uuid"))
94   end
95 end
96
97 module ApiMockHelpers
98   def fake_api_response body, status_code, headers
99     resp = mock
100     resp.responds_like_instance_of HTTP::Message
101     resp.stubs(:headers).returns headers
102     resp.stubs(:content).returns body
103     resp.stubs(:status_code).returns status_code
104     resp
105   end
106
107   def stub_api_calls_with_body body, status_code=200, headers={}
108     stub_api_calls
109     resp = fake_api_response body, status_code, headers
110     stub_api_client.stubs(:post).returns resp
111   end
112
113   def stub_api_calls
114     @stubbed_client = ArvadosApiClient.new
115     @stubbed_client.instance_eval do
116       @api_client = HTTPClient.new
117     end
118     ArvadosApiClient.stubs(:new_or_current).returns(@stubbed_client)
119   end
120
121   def stub_api_calls_with_invalid_json
122     stub_api_calls_with_body ']"omg,bogus"['
123   end
124
125   # Return the HTTPClient mock used by the ArvadosApiClient mock. You
126   # must have called stub_api_calls first.
127   def stub_api_client
128     @stubbed_client.instance_eval do
129       @api_client
130     end
131   end
132 end
133
134 class ActiveSupport::TestCase
135   include ApiMockHelpers
136 end
137
138 class ActiveSupport::TestCase
139   include ApiFixtureLoader
140   def session_for api_client_auth_name
141     {
142       arvados_api_token: api_fixture('api_client_authorizations')[api_client_auth_name.to_s]['api_token']
143     }
144   end
145   def json_response
146     Oj.load(@response.body)
147   end
148 end
149
150 class ApiServerForTests
151   PYTHON_TESTS_DIR = File.expand_path('../../../../sdk/python/tests', __FILE__)
152   ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
153   SERVER_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
154   WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
155   @main_process_pid = $$
156   @@server_is_running = false
157
158   def check_output *args
159     output = nil
160     Bundler.with_clean_env do
161       output = IO.popen *args do |io|
162         io.read
163       end
164       if not $?.success?
165         raise RuntimeError, "Command failed (#{$?}): #{args.inspect}"
166       end
167     end
168     output
169   end
170
171   def run_test_server
172     env_script = nil
173     Dir.chdir PYTHON_TESTS_DIR do
174       # These are no-ops if we're running within run-tests.sh (except
175       # that we do get a useful env_script back from "start", even
176       # though it doesn't need to start up a new server).
177       env_script = check_output %w(python ./run_test_server.py start --auth admin)
178       check_output %w(python ./run_test_server.py start_arv-git-httpd)
179       check_output %w(python ./run_test_server.py start_keep-web)
180       check_output %w(python ./run_test_server.py start_nginx)
181       # This one isn't a no-op, even under run-tests.sh.
182       check_output %w(python ./run_test_server.py start_keep)
183     end
184     test_env = {}
185     env_script.each_line do |line|
186       line = line.chomp
187       if 0 == line.index('export ')
188         toks = line.sub('export ', '').split '=', 2
189         $stderr.puts "run_test_server.py: #{toks[0]}=#{toks[1]}"
190         test_env[toks[0]] = toks[1]
191       end
192     end
193     test_env
194   end
195
196   def stop_test_server
197     Dir.chdir PYTHON_TESTS_DIR do
198       check_output %w(python ./run_test_server.py stop_keep)
199       # These are no-ops if we're running within run-tests.sh
200       check_output %w(python ./run_test_server.py stop_nginx)
201       check_output %w(python ./run_test_server.py stop_arv-git-httpd)
202       check_output %w(python ./run_test_server.py stop_keep-web)
203       check_output %w(python ./run_test_server.py stop)
204     end
205     @@server_is_running = false
206   end
207
208   def run args=[]
209     return if @@server_is_running
210
211     # Stop server left over from interrupted previous run
212     stop_test_server
213
214     ::MiniTest.after_run do
215       stop_test_server
216     end
217
218     test_env = run_test_server
219     $application_config['arvados_login_base'] = "https://#{test_env['ARVADOS_API_HOST']}/login"
220     $application_config['arvados_v1_base'] = "https://#{test_env['ARVADOS_API_HOST']}/arvados/v1"
221     $application_config['arvados_insecure_host'] = true
222     ActiveSupport::TestCase.reset_application_config
223
224     @@server_is_running = true
225   end
226
227   def run_rake_task task_name, arg_string
228     Dir.chdir ARV_API_SERVER_DIR do
229       check_output ['bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]"]
230     end
231   end
232 end
233
234 class ActionController::TestCase
235   setup do
236     @counter = 0
237   end
238
239   def check_counter action
240     @counter += 1
241     if @counter == 2
242       assert_equal 1, 2, "Multiple actions in controller test"
243     end
244   end
245
246   [:get, :post, :put, :patch, :delete].each do |method|
247     define_method method do |action, *args|
248       check_counter action
249       super action, *args
250     end
251   end
252 end
253
254 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
255 # override the default. Example:
256 #
257 # class MySuite < ActionDispatch::IntegrationTest
258 #   reset_api_fixtures :after_each_test, false
259 #   reset_api_fixtures :after_suite, true
260 #   ...
261 # end
262 #
263 # The default behavior is reset_api_fixtures(:after_each_test,true).
264 #
265 class ActiveSupport::TestCase
266
267   def self.inherited subclass
268     subclass.class_eval do
269       class << self
270         attr_accessor :want_reset_api_fixtures
271       end
272       @want_reset_api_fixtures = {
273         after_each_test: true,
274         after_suite: false,
275         before_suite: false,
276       }
277     end
278     super
279   end
280   # Existing subclasses of ActiveSupport::TestCase (ones that already
281   # existed before we set up the self.inherited hook above) will not
282   # get their own instance variable. They're not real test cases
283   # anyway, so we give them a "don't reset anywhere" stub.
284   def self.want_reset_api_fixtures
285     {}
286   end
287
288   def self.reset_api_fixtures where, t=true
289     if not want_reset_api_fixtures.has_key? where
290       raise ArgumentError, "There is no #{where.inspect} hook"
291     end
292     self.want_reset_api_fixtures[where] = t
293   end
294
295   def self.run *args
296     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
297     result = super
298     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
299     result
300   end
301
302   def after_teardown
303     if self.class.want_reset_api_fixtures[:after_each_test] and
304         @want_reset_api_fixtures != false
305       self.class.reset_api_fixtures_now
306     end
307     super
308   end
309
310   def reset_api_fixtures_after_test t=true
311     @want_reset_api_fixtures = t
312   end
313
314   protected
315   def self.reset_api_fixtures_now
316     # Never try to reset fixtures when we're just using test
317     # infrastructure to run performance/diagnostics suites.
318     return unless Rails.env == 'test'
319
320     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
321     Thread.current[:arvados_api_token] = auth['api_token']
322     ArvadosApiClient.new.api(nil, '../../database/reset', {})
323     Thread.current[:arvados_api_token] = nil
324   end
325 end
326
327 # If it quacks like a duck, it must be a HTTP request object.
328 class RequestDuck
329   def self.host
330     "localhost"
331   end
332
333   def self.port
334     8080
335   end
336
337   def self.protocol
338     "http"
339   end
340 end
341
342 # Example:
343 #
344 # apps/workbench$ RAILS_ENV=test bundle exec irb -Ilib:test
345 # > load 'test/test_helper.rb'
346 # > singletest 'integration/collection_upload_test.rb', 'Upload two empty files'
347 #
348 def singletest test_class_file, test_name
349   load File.join('test', test_class_file)
350   Minitest.run ['-v', '-n', "test_#{test_name.gsub ' ', '_'}"]
351   Object.send(:remove_const,
352               test_class_file.gsub(/.*\/|\.rb$/, '').camelize.to_sym)
353   ::Minitest::Runnable.runnables.reject! { true }
354 end
355
356 if ENV["RAILS_ENV"].eql? 'test'
357   ApiServerForTests.new.run
358   ApiServerForTests.new.run ["--websockets"]
359 end
360
361 # Reset fixtures now (i.e., before any tests run).
362 ActiveSupport::TestCase.reset_api_fixtures_now