13497: Merge branch 'master' into 13497-controller
[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] = 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   def api_fixture(name, *keys)
96     self.class.api_fixture(name, *keys)
97   end
98
99   def find_fixture(object_class, name)
100     object_class.find(api_fixture(object_class.to_s.pluralize.underscore,
101                                   name, "uuid"))
102   end
103 end
104
105 module ApiMockHelpers
106   def fake_api_response body, status_code, headers
107     resp = mock
108     resp.responds_like_instance_of HTTP::Message
109     resp.stubs(:headers).returns headers
110     resp.stubs(:content).returns body
111     resp.stubs(:status_code).returns status_code
112     resp
113   end
114
115   def stub_api_calls_with_body body, status_code=200, headers={}
116     stub_api_calls
117     resp = fake_api_response body, status_code, headers
118     stub_api_client.stubs(:post).returns resp
119   end
120
121   def stub_api_calls
122     @stubbed_client = ArvadosApiClient.new
123     @stubbed_client.instance_eval do
124       @api_client = HTTPClient.new
125     end
126     ArvadosApiClient.stubs(:new_or_current).returns(@stubbed_client)
127   end
128
129   def stub_api_calls_with_invalid_json
130     stub_api_calls_with_body ']"omg,bogus"['
131   end
132
133   # Return the HTTPClient mock used by the ArvadosApiClient mock. You
134   # must have called stub_api_calls first.
135   def stub_api_client
136     @stubbed_client.instance_eval do
137       @api_client
138     end
139   end
140 end
141
142 class ActiveSupport::TestCase
143   include ApiMockHelpers
144 end
145
146 class ActiveSupport::TestCase
147   include ApiFixtureLoader
148   def session_for api_client_auth_name
149     {
150       arvados_api_token: api_fixture('api_client_authorizations')[api_client_auth_name.to_s]['api_token']
151     }
152   end
153   def json_response
154     Oj.load(@response.body)
155   end
156 end
157
158 class ApiServerForTests
159   PYTHON_TESTS_DIR = File.expand_path('../../../../sdk/python/tests', __FILE__)
160   ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__)
161   SERVER_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
162   WEBSOCKET_PID_PATH = File.expand_path('tmp/pids/test-server.pid', ARV_API_SERVER_DIR)
163   @main_process_pid = $$
164   @@server_is_running = false
165
166   def check_output *args
167     output = nil
168     Bundler.with_clean_env do
169       output = IO.popen *args do |io|
170         io.read
171       end
172       if not $?.success?
173         raise RuntimeError, "Command failed (#{$?}): #{args.inspect}"
174       end
175     end
176     output
177   end
178
179   def run_test_server
180     Dir.chdir PYTHON_TESTS_DIR do
181       check_output %w(python ./run_test_server.py start_keep)
182     end
183   end
184
185   def stop_test_server
186     Dir.chdir PYTHON_TESTS_DIR do
187       check_output %w(python ./run_test_server.py stop_keep)
188     end
189     @@server_is_running = false
190   end
191
192   def run args=[]
193     return if @@server_is_running
194
195     # Stop server left over from interrupted previous run
196     stop_test_server
197
198     ::MiniTest.after_run do
199       stop_test_server
200     end
201
202     run_test_server
203     $application_config['arvados_login_base'] = "https://#{ENV['ARVADOS_API_HOST']}/login"
204     $application_config['arvados_v1_base'] = "https://#{ENV['ARVADOS_API_HOST']}/arvados/v1"
205     $application_config['arvados_insecure_host'] = true
206     ActiveSupport::TestCase.reset_application_config
207
208     @@server_is_running = true
209   end
210
211   def run_rake_task task_name, arg_string
212     Dir.chdir ARV_API_SERVER_DIR do
213       check_output ['bundle', 'exec', 'rake', "#{task_name}[#{arg_string}]"]
214     end
215   end
216 end
217
218 class ActionController::TestCase
219   setup do
220     @test_counter = 0
221   end
222
223   def check_counter action
224     @test_counter += 1
225     if @test_counter == 2
226       assert_equal 1, 2, "Multiple actions in controller test"
227     end
228   end
229
230   [:get, :post, :put, :patch, :delete].each do |method|
231     define_method method do |action, *args|
232       check_counter action
233       super action, *args
234     end
235   end
236 end
237
238 # Test classes can call reset_api_fixtures(when_to_reset,flag) to
239 # override the default. Example:
240 #
241 # class MySuite < ActionDispatch::IntegrationTest
242 #   reset_api_fixtures :after_each_test, false
243 #   reset_api_fixtures :after_suite, true
244 #   ...
245 # end
246 #
247 # The default behavior is reset_api_fixtures(:after_each_test,true).
248 #
249 class ActiveSupport::TestCase
250
251   def self.inherited subclass
252     subclass.class_eval do
253       class << self
254         attr_accessor :want_reset_api_fixtures
255       end
256       @want_reset_api_fixtures = {
257         after_each_test: true,
258         after_suite: false,
259         before_suite: false,
260       }
261     end
262     super
263   end
264   # Existing subclasses of ActiveSupport::TestCase (ones that already
265   # existed before we set up the self.inherited hook above) will not
266   # get their own instance variable. They're not real test cases
267   # anyway, so we give them a "don't reset anywhere" stub.
268   def self.want_reset_api_fixtures
269     {}
270   end
271
272   def self.reset_api_fixtures where, t=true
273     if not want_reset_api_fixtures.has_key? where
274       raise ArgumentError, "There is no #{where.inspect} hook"
275     end
276     self.want_reset_api_fixtures[where] = t
277   end
278
279   def self.run *args
280     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
281     result = super
282     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
283     result
284   end
285
286   def after_teardown
287     if self.class.want_reset_api_fixtures[:after_each_test] and
288         @want_reset_api_fixtures != false
289       self.class.reset_api_fixtures_now
290     end
291     super
292   end
293
294   def reset_api_fixtures_after_test t=true
295     @want_reset_api_fixtures = t
296   end
297
298   protected
299   def self.reset_api_fixtures_now
300     # Never try to reset fixtures when we're just using test
301     # infrastructure to run performance/diagnostics suites.
302     return unless Rails.env == 'test'
303
304     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
305     Thread.current[:arvados_api_token] = auth['api_token']
306     ArvadosApiClient.new.api(nil, '../../database/reset', {})
307     Thread.current[:arvados_api_token] = nil
308   end
309 end
310
311 # If it quacks like a duck, it must be a HTTP request object.
312 class RequestDuck
313   def self.host
314     "localhost"
315   end
316
317   def self.port
318     8080
319   end
320
321   def self.protocol
322     "http"
323   end
324 end
325
326 # Example:
327 #
328 # apps/workbench$ RAILS_ENV=test bundle exec irb -Ilib:test
329 # > load 'test/test_helper.rb'
330 # > singletest 'integration/collection_upload_test.rb', 'Upload two empty files'
331 #
332 def singletest test_class_file, test_name
333   load File.join('test', test_class_file)
334   Minitest.run ['-v', '-n', "test_#{test_name.gsub ' ', '_'}"]
335   Object.send(:remove_const,
336               test_class_file.gsub(/.*\/|\.rb$/, '').camelize.to_sym)
337   ::Minitest::Runnable.runnables.reject! { true }
338 end
339
340 if ENV["RAILS_ENV"].eql? 'test'
341   ApiServerForTests.new.run
342   ApiServerForTests.new.run ["--websockets"]
343 end
344
345 # Reset fixtures now (i.e., before any tests run).
346 ActiveSupport::TestCase.reset_api_fixtures_now