Get back to use Sinatra settings
[coquelicot.git] / test_coquelicot.rb
1 $:.unshift File.join(File.dirname(__FILE__), '../rack-test/lib')
2 $:.unshift File.join(File.dirname(__FILE__), '../timecop/lib')
3
4 require 'coquelicot_app'
5 require 'spec'
6 require 'rack/test'
7 require 'timecop'
8 require 'hpricot'
9 require 'tmpdir'
10
11 UPLOAD_PASSWORD = 'secret'
12
13 describe 'Coquelicot' do
14   include Rack::Test::Methods
15
16   def app
17     Coquelicot::Application
18   end
19
20   def upload(opts={})
21     opts = { :file => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
22              :upload_password => UPLOAD_PASSWORD
23            }.merge(opts)
24     post '/upload', opts
25     return nil unless last_response.redirect?
26     follow_redirect!
27     last_response.should be_ok
28     doc = Hpricot(last_response.body)
29     return (doc/'a').collect { |a| a.attributes['href'] }.
30              select { |h| h.start_with? "http://#{last_request.host}/" }[0]
31   end
32
33   before do
34     app.set :environment, :test
35     app.set :upload_password, Digest::SHA1.hexdigest(UPLOAD_PASSWORD)
36     app.set :depot_path, Dir.mktmpdir('coquelicot')
37   end
38
39   after do
40     FileUtils.remove_entry_secure Coquelicot.depot.path
41   end
42
43   it "should offer an upload form" do
44     get '/'
45     last_response.should be_ok
46     doc = Hpricot(last_response.body)
47     (doc/"form#upload").should have(1).items
48   end
49
50   it "should allow retrieval of an uploaded file" do
51     url = upload
52     get url
53     last_response.should be_ok
54     last_response['Content-Type'].should eql('text/x-script.ruby')
55     last_response.body.should eql(File.new(__FILE__).read)
56   end
57
58   it "should correctly set Last-Modified header when downloading" do
59     url = upload
60     get url
61     last_modified = last_response['Last-Modified']
62     last_modified.should_not be_nil
63     get url
64     last_response['Last-Modified'].should eql(last_modified)
65   end
66
67   it "should prevent upload without a password" do
68     url = upload :upload_password => ''
69     url.should be_nil
70     last_response.status.should eql(403)
71   end
72
73   it "should prevent upload with a wrong password" do
74     url = upload :upload_password => "bad"
75     url.should be_nil
76     last_response.status.should eql(403)
77   end
78
79   it "should allow AJAX upload password verification" do
80     request "/authenticate", :method => "POST", :xhr => true,
81                              :params => { :upload_password => UPLOAD_PASSWORD }
82     last_response.should be_ok
83     request "/authenticate", :method => "POST", :xhr => true,
84                              :params => { :upload_password => '' }
85     last_response.status.should eql(403)
86     request "/authenticate", :method => "POST", :xhr => true,
87                              :params => { :upload_password => 'wrong' }
88     last_response.status.should eql(403)
89   end
90
91   it "should not store an uploaded file in cleartext" do
92     upload
93     files = Dir.glob("#{Coquelicot.depot.path}/*")
94     files.should have(1).items
95     File.new(files[0]).read().should_not include('should not store an uploaded file')
96   end
97
98   it "should generate a random URL to retrieve a file" do
99     url = upload
100     url.should_not include(File.basename(__FILE__))
101   end
102
103   it "should store files with a different name than then one in URL" do
104     url = upload
105     url_name = url.split('/')[-1]
106     files = Dir.glob("#{Coquelicot.depot.path}/*")
107     files.should have(1).items
108     url_name.should_not eql(File.basename(files[0]))
109   end
110
111   it "should encode the encryption key in URL when no password has been specified" do
112     url = upload
113     url_name = url.split('/')[-1]
114     url_name.split('-').should have(2).items
115   end
116
117   it "should not encode the encryption key in URL when a password has been specified" do
118     url = upload :file_key => 'somethingSecret'
119     url_name = url.split('/')[-1]
120     url_name.split('-').should have(1).items
121   end
122
123   it "should only allow one time download to be retrieved once" do
124     url = upload :one_time => true
125     get url
126     last_response.should be_ok
127     last_response['Content-Type'].should eql('text/x-script.ruby')
128     last_response.body.should eql(File.new(__FILE__).read)
129     get url
130     last_response.status.should eql(410)
131   end
132
133   it "should have files zero'ed after 'one time' download" do
134     url = upload :one_time => true
135     get url
136     files = Dir.glob("#{Coquelicot.depot.path}/*")
137     files.should have(1).items
138     File.lstat(files[0]).size.should eql(0)
139   end
140
141   it "should allow retrieval of a password protected file" do
142     url = upload :file_key => 'somethingSecret'
143     get url
144     last_response.should be_ok
145     doc = Hpricot(last_response.body)
146     (doc/'input#file_key').should have(1).items
147     url = (doc/'form')[0].attributes['action']
148     post url, :file_key => 'somethingSecret'
149     last_response.should be_ok
150     last_response['Content-Type'].should eql('text/x-script.ruby')
151     last_response.body.should eql(File.new(__FILE__).read)
152   end
153
154   it "should not allow retrieval of a password protected file without the password" do
155     url = upload :file_key => 'somethingSecret'
156     get url
157     last_response.should be_ok
158     last_response['Content-Type'].should_not eql('text/x-script.ruby')
159     post url
160     last_response.status.should eql(403)
161   end
162
163   it "should not allow retrieval of a password protected file with a wrong password" do
164     url = upload :file_key => 'somethingSecret'
165     post url, :file_key => 'BAD'
166     last_response.status.should eql(403)
167   end
168
169   it "should not allow retrieval after the time limit has expired" do
170     url = upload :expire => 60 # 1 hour
171     # let's be tomorrow
172     Timecop.travel(Date.today + 1) do
173       get url
174       last_response.status.should eql(410)
175     end
176   end
177
178   it "should not allow an expiration time longer than the maximum" do
179     upload :expire => 60 * 24 * 31 * 12 # 1 year
180     last_response.status.should eql(403)
181   end
182
183   it "should cleanup expired files" do
184     url = upload :expire => 60, :file_key => 'test' # 1 hour
185     url_name = url.split('/')[-1]
186     Dir.glob("#{Coquelicot.depot.path}/*").should have(1).items
187     # let's be tomorrow
188     Timecop.travel(Date.today + 1) do
189       Coquelicot.depot.gc!
190       files = Dir.glob("#{Coquelicot.depot.path}/*")
191       files.should have(1).items
192       File.lstat(files[0]).size.should eql(0)
193       Coquelicot.depot.get_file(url_name).expired?.should be_true
194     end
195     # let's be after 'gone' period
196     Timecop.travel(Time.now + (Coquelicot.settings.gone_period * 60)) do
197       Coquelicot.depot.gc!
198       Dir.glob("#{Coquelicot.depot.path}/*").should have(0).items
199       Coquelicot.depot.get_file(url_name).should be_nil
200     end
201   end
202
203   it "should map extra base32 characters to filenames" do
204     url = upload :expire => 60 # 1 hour
205     splitted = url.split('/')
206     name = splitted[-1].upcase.gsub(/O/, '0').gsub(/L/, '1')
207     get "#{splitted[0..-2].join '/'}/#{name}"
208     last_response.should be_ok
209     last_response['Content-Type'].should eql('text/x-script.ruby')
210   end
211 end