4533: Add /database/reset API to roll everything back to fixtures.
[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 functional 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(:before_suite) or
208 # ...(:after_suite)
209 class ActiveSupport::TestCase
210   class << self
211     attr_accessor :want_reset_api_fixtures
212   end
213
214   def self.reset_api_fixtures where, t=true
215     raise unless [:before_suite, :after_suite].include? where
216     self.want_reset_api_fixtures ||= {}
217     self.want_reset_api_fixtures[where] = t
218   end
219
220   def self.run *args
221     self.want_reset_api_fixtures ||= {}
222     reset_api_fixtures_now if want_reset_api_fixtures[:before_suite]
223     super
224     reset_api_fixtures_now if want_reset_api_fixtures[:after_suite]
225   end
226
227   protected
228   def self.reset_api_fixtures_now
229     auth = api_fixture('api_client_authorizations')['admin_trustedclient']
230     Thread.current[:arvados_api_token] = auth['api_token']
231     ArvadosApiClient.new.api(nil, '../../database/reset', {})
232     Thread.current[:arvados_api_token] = nil
233   end
234 end
235
236 # If it quacks like a duck, it must be a HTTP request object.
237 class RequestDuck
238   def self.host
239     "localhost"
240   end
241
242   def self.port
243     8080
244   end
245
246   def self.protocol
247     "http"
248   end
249 end
250
251 if ENV["RAILS_ENV"].eql? 'test'
252   ApiServerForTests.new.run
253   ApiServerForTests.new.run ["--websockets"]
254 end