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