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