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