support multiple languages for about_text
[coquelicot.git] / spec / coquelicot / app_spec.rb
1 # -*- coding: UTF-8 -*-
2 # Coquelicot: "one-click" file sharing with a focus on users' privacy.
3 # Copyright © 2010-2013 potager.org <jardiniers@potager.org>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Affero General Public License for more details.
14 #
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 require 'spec_helper'
19 require 'coquelicot/jyraphe_migrator'
20 require 'capybara/dsl'
21 require 'tempfile'
22
23 describe Coquelicot::Application do
24   include Rack::Test::Methods
25   include Capybara::DSL
26   Capybara.app = Coquelicot::Application
27
28   include_context 'with Coquelicot::Application'
29
30   def upload_password
31     'secret'
32   end
33
34   before(:each) do
35     app.set :authentication_method, :name => :simplepass,
36                                     :upload_password => Digest::SHA1.hexdigest(upload_password)
37   end
38
39   shared_context 'browser prefers french' do
40     around do |example|
41       begin
42         page.driver.header 'Accept-Language',  'fr-fr;q=1.0, en-gb;q=0.8, en;q=0.7'
43         example.run
44       ensure
45         page.driver.header 'Accept-Language', nil
46         reset_session!
47       end
48     end
49   end
50
51   describe 'get /' do
52     context 'using the default language' do
53       it 'should display the maximum file size' do
54         visit '/'
55         find(:xpath, '//label[@for="file"]').
56             should have_content("max. size: #{Coquelicot.settings.max_file_size.as_size}")
57       end
58       context 'when I explicitly request french' do
59         it 'should display a page in french' do
60           visit '/'
61           click_link 'fr'
62           page.should have_content('Partager')
63           reset_session!
64         end
65         # will fail without ordered Hash, see:
66         # <https://github.com/jnicklas/capybara/issues/670>
67         context 'when I upload an empty file', :if => RUBY_VERSION >= '1.9' do
68           around do |example|
69             file = Tempfile.new('coquelicot')
70             begin
71               visit '/'
72               click_link 'fr'
73               fill_in 'upload_password', :with => upload_password
74               attach_file 'file', file.path
75               click_button 'submit'
76               example.run
77             ensure
78               file.close!
79               reset_session!
80             end
81           end
82           it 'should display an error in french' do
83             page.should have_content('Le fichier est vide')
84           end
85         end
86       end
87     end
88     context 'when my browser prefers french' do
89       include_context 'browser prefers french'
90       context 'when I do nothing special' do
91         it 'should display a page in french' do
92           visit '/'
93           page.should have_content('Partager')
94         end
95         context 'when the max upload size is 1 KiB' do
96           around do |example|
97             begin
98               max_file_size = app.max_file_size
99               app.set :max_file_size, 1024
100               example.run
101             ensure
102               app.set :max_file_size, max_file_size
103             end
104           end
105           it 'should display "1 Kio" as the max upload size' do
106             visit '/'
107             page.should have_content('1 Kio')
108           end
109           # will fail without ordered Hash, see:
110           # <https://github.com/jnicklas/capybara/issues/670>
111           context 'when I upload something bigger', :if => RUBY_VERSION >= '1.9' do
112             before do
113               visit '/'
114               fill_in 'upload_password', :with => upload_password
115               attach_file 'file', __FILE__
116               click_button 'submit'
117             end
118             it 'should display an error in french' do
119               page.should have_content('plus gros que la taille maximale')
120             end
121           end
122         end
123         # will fail without ordered Hash, see:
124         # <https://github.com/jnicklas/capybara/issues/670>
125         context 'when I upload an empty file', :if => RUBY_VERSION >= '1.9' do
126           around do |example|
127             file = Tempfile.new('coquelicot')
128             begin
129               visit '/'
130               fill_in 'upload_password', :with => upload_password
131               attach_file 'file', file.path
132               click_button 'submit'
133               example.run
134             ensure
135               file.close!
136             end
137           end
138           it 'should display an error in french' do
139             page.should have_content('Le fichier est vide')
140           end
141         end
142       end
143       context 'when I explicitly request german' do
144         around(:each) do |example|
145           visit '/'
146           click_link 'de'
147           example.run
148           reset_session!
149         end
150         it 'should display a page in german' do
151           page.should have_content('Verteile')
152         end
153         # will fail without ordered Hash, see:
154         # <https://github.com/jnicklas/capybara/issues/670>
155         context 'after an upload', :if => RUBY_VERSION >= '1.9' do
156           before do
157             fill_in 'upload_password', :with => upload_password
158             attach_file 'file', __FILE__
159             click_button 'submit'
160           end
161           it 'should display a page in german' do
162             page.should have_content('Verteile eine weitere Datei')
163           end
164         end
165       end
166     end
167     context 'when an "about text" is set for English and French"' do
168       before(:each) do
169         app.set :about_text, 'en' => 'This is an about text',
170                              'fr' => 'Ceci est un texte'
171       end
172       context 'using the default language' do
173         it 'should display the "about text" in English' do
174           visit '/'
175           find('.about').text.should == 'This is an about text'
176         end
177       end
178       context 'when I explicitly request French' do
179         it 'should display the "about text" in French' do
180           visit '/'
181           click_link 'fr'
182           find('.about').text.should == 'Ceci est un texte'
183           reset_session!
184         end
185       end
186       context 'when my browser prefers french' do
187         include_context 'browser prefers french'
188         it 'should display the "about text" in French' do
189           visit '/'
190           find('.about').text.should == 'Ceci est un texte'
191         end
192       end
193     end
194   end
195
196   describe 'get /README' do
197     before do
198       visit '/README'
199     end
200     it 'should display the README file' do
201       title = File.open(File.expand_path('../../../README', __FILE__)) { |f| f.readline.strip }
202       find('h1').should have_content(title)
203     end
204   end
205
206   describe 'get /about-your-data' do
207     it 'should display some info about data retention' do
208       visit '/about-your-data'
209       find('h1').should have_content('About your data…')
210     end
211     context 'when using SSL' do
212       it 'should notice the connection is encrypted' do
213         visit 'https://example.com/about-your-data'
214         page.should have_content('Exchanges between your computer and example.com are encrypted.')
215       end
216     end
217     context 'when not using SSL' do
218       it 'should notice the connection is encrypted' do
219         visit 'http://example.com/about-your-data'
220         page.should_not have_content('Exchanges between your computer and example.org are encrypted.')
221       end
222     end
223   end
224
225   describe 'post /authenticate' do
226     context 'when given a request with too much input' do
227       before do
228         # README is bigger than 5 kiB
229         path = File.expand_path('../../../README', __FILE__)
230         post '/authenticate', :file => Rack::Test::UploadedFile.new(path, 'text/plain')
231       end
232       it 'should get status 413 (Request entity too large)' do
233         last_response.status.should == 413
234       end
235     end
236   end
237 end
238
239 describe Coquelicot, '.run!' do
240   include_context 'with Coquelicot::Application'
241
242   context 'when given no option' do
243     it 'should display help and exit' do
244       stderr = capture(:stderr) do
245         expect { Coquelicot.run! %w{} }.to raise_error(SystemExit)
246       end
247       stderr.should =~ /Usage:/
248     end
249   end
250   context 'when using "-h"' do
251     it 'should display help and exit' do
252       stderr = capture(:stderr) do
253         expect { Coquelicot.run! %w{-h} }.to raise_error(SystemExit)
254       end
255       stderr.should =~ /Usage:/
256     end
257   end
258   context 'when using "-c <settings.yml>"' do
259     it 'should use the given setting file' do
260       settings_file = File.expand_path('../../../conf/settings-default.yml', __FILE__)
261       Coquelicot::Application.should_receive(:config_file).with(settings_file)
262       stderr = capture(:stderr) do
263         expect { Coquelicot.run! ['-c', settings_file] }.to raise_error(SystemExit)
264       end
265     end
266     context 'when the given settings file exists' do
267       around(:each) do |example|
268         settings = Tempfile.new('coquelicot')
269         begin
270           settings.write(YAML.dump({ 'depot_path' => '/nonexistent' }))
271           settings.close
272           @settings_path = settings.path
273           example.run
274         ensure
275           settings.unlink
276         end
277       end
278       it 'should use the depot path defined in the given settings' do
279         # We don't give a command, so exit is expected
280         stderr = capture(:stderr) do
281           expect { Coquelicot.run! ['-c', @settings_path] }.to raise_error(SystemExit)
282         end
283         Coquelicot.settings.depot_path.should == '/nonexistent'
284       end
285     end
286     context 'when the given settings file does not exist' do
287       it 'should display an error' do
288         stderr = capture(:stderr) do
289           expect { Coquelicot.run! %w{-c non-existent.yml} }.to raise_error(SystemExit)
290         end
291         stderr.should =~ /cannot access/
292       end
293     end
294   end
295   context 'when given an invalid option' do
296     it 'should display an error' do
297       stderr = capture(:stderr) do
298         expect { Coquelicot.run! %w{--invalid-option} }.to raise_error(SystemExit)
299       end
300       stderr.should =~ /not a valid option/
301     end
302   end
303   context 'when given "whatever"' do
304     it 'should display an error' do
305       stderr = capture(:stderr) do
306         expect { Coquelicot.run! %w{whatever} }.to raise_error(SystemExit)
307       end
308       stderr.should =~ /not a valid command/
309     end
310   end
311   shared_context 'command accepts options' do
312     context 'when given "--help" option' do
313       it 'should display help and exit' do
314         stderr = capture(:stderr) do
315           expect { Coquelicot.run!([command, '--help']) }.to raise_error(SystemExit)
316         end
317         stderr.should =~ /Usage:/
318       end
319     end
320     context 'when given an invalid option' do
321       it 'should display an error' do
322         stderr = capture(:stderr) do
323           expect { Coquelicot.run!([command, '--invalid-option']) }.to raise_error(SystemExit)
324         end
325         stderr.should =~ /not a valid option/
326       end
327     end
328   end
329   context 'when given "start"' do
330     let(:command) { 'start' }
331     include_context 'command accepts options'
332
333     before(:each) do
334       # :stdout_path and :stderr_path should not be set, otherwise RSpec will break!
335       app.set :log, nil
336     end
337     context 'with default options' do
338       it 'should daemonize' do
339         ::Unicorn::Launcher.should_receive(:daemonize!)
340         ::Rainbows::HttpServer.stub(:new).and_return(double('HttpServer').as_null_object)
341         Coquelicot.run! %w{start}
342       end
343       it 'should start the web server' do
344         ::Unicorn::Launcher.stub(:daemonize!)
345         server = double('HttpServer')
346         server.should_receive(:start).and_return(double('Thread').as_null_object)
347         ::Rainbows::HttpServer.stub(:new).and_return(server)
348         Coquelicot.run! %w{start}
349       end
350     end
351     context 'when given the --no-daemon option' do
352       it 'should not daemonize' do
353         ::Unicorn::Launcher.should_receive(:daemonize!).never
354         ::Rainbows::HttpServer.stub(:new).and_return(double('HttpServer').as_null_object)
355         Coquelicot.run! %w{start --no-daemon}
356       end
357       it 'should set the default configuration' do
358         app.set :pid, @depot_path
359         app.set :listen, ['127.0.0.1:42']
360         ::Rainbows::HttpServer.any_instance.stub(:start) do
361           server = ::Rainbows.server
362           server.config.set[:pid].should == @depot_path
363           server.config.set[:listeners].should == ['127.0.0.1:42']
364           double('Thread').as_null_object
365         end
366         Coquelicot.run! %w{start --no-daemon}
367       end
368       it 'should start the web server' do
369         server = double('HttpServer')
370         server.should_receive(:start).and_return(double('Thread').as_null_object)
371         ::Rainbows::HttpServer.stub(:new).and_return(server)
372         Coquelicot.run! %w{start --no-daemon}
373       end
374     end
375   end
376   context 'when given "stop"' do
377     let(:command) { 'stop' }
378     include_context 'command accepts options'
379
380     context 'when the pid file is correct' do
381       let(:pid) { 42 }
382       before(:each) do
383         File.open("#{@depot_path}/pid", 'w') do |f|
384           f.write(pid.to_s)
385         end
386         app.set :pid, "#{@depot_path}/pid"
387       end
388       it 'should stop the web server' do
389         Process.should_receive(:kill).with(:TERM, pid)
390         Coquelicot.run! %w{stop}
391       end
392     end
393     context 'when the pid file does not exist' do
394       it 'should error out' do
395         app.set :pid, '/nonexistent'
396         stderr = capture(:stderr) do
397           expect { Coquelicot.run! %w{stop} }.to raise_error(SystemExit)
398         end
399         stderr.should =~ /Unable to read/
400       end
401     end
402     context 'when the pid file contains garbage' do
403       before(:each) do
404         File.open("#{@depot_path}/pid", 'w') do |f|
405           f.write('The queerest of the queer')
406         end
407         app.set :pid, "#{@depot_path}/pid"
408       end
409       it 'should errour out' do
410         stderr = capture(:stderr) do
411           expect { Coquelicot.run! %w{stop} }.to raise_error(SystemExit)
412         end
413         stderr.should =~ /Bad PID file/
414       end
415     end
416   end
417   context 'when given "gc"' do
418     let(:command) { 'gc' }
419     include_context 'command accepts options'
420
421     it 'should call gc!' do
422       Coquelicot.depot.should_receive(:gc!).once
423       Coquelicot.run! %w{gc}
424     end
425   end
426   context 'when given "migrate-jyraphe"' do
427     let(:args) { %w{all args} }
428     it 'should call the migrator' do
429       Coquelicot::JyrapheMigrator.should_receive(:run!).with(args)
430       Coquelicot.run!(%w{migrate-jyraphe} + args)
431     end
432   end
433 end