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