ignore 3rd party layouts
[coquelicot.git] / test_coquelicot.rb
index dcc673f..759dc37 100644 (file)
@@ -1,8 +1,4 @@
-$:.unshift File.join(File.dirname(__FILE__), '../rack-test/lib')
-$:.unshift File.join(File.dirname(__FILE__), '../timecop/lib')
-
-require 'sinatra'
-require 'coquelicot'
+require 'coquelicot_app'
 require 'spec'
 require 'rack/test'
 require 'timecop'
@@ -11,22 +7,37 @@ require 'tmpdir'
 
 UPLOAD_PASSWORD = 'secret'
 
-set :environment, :test
-set :upload_password, Digest::SHA1.hexdigest(UPLOAD_PASSWORD)
-
 describe 'Coquelicot' do
   include Rack::Test::Methods
 
   def app
-    Sinatra::Application
+    Coquelicot::Application
+  end
+
+  def upload(opts={})
+    opts = { :file => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
+             :upload_token => JSON.dump({ 'upload_password' => UPLOAD_PASSWORD})
+           }.merge(opts)
+    post '/upload', opts
+    return nil unless last_response.redirect?
+    follow_redirect!
+    last_response.should be_ok
+    doc = Hpricot(last_response.body)
+    return (doc/'a').collect { |a| a.attributes['href'] }.
+             select { |h| h.start_with? "http://#{last_request.host}/" }[0]
   end
 
   before do
-    Depot.instance.path = Dir.mktmpdir('coquelicot') #"#{Time.now.to_f}"
+    # set a special test password
+    app.set :upload_password, Digest::SHA1.hexdigest(UPLOAD_PASSWORD)
+
+    app.set :environment, :test
+
+    app.set :depot_path, Dir.mktmpdir('coquelicot')
   end
 
   after do
-    FileUtils.remove_entry_secure Depot.instance.path
+    FileUtils.remove_entry_secure Coquelicot.depot.path
   end
 
   it "should offer an upload form" do
@@ -36,153 +47,197 @@ describe 'Coquelicot' do
     (doc/"form#upload").should have(1).items
   end
 
-  it "should accept an uploaded file" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    last_response['Location'].start_with?('ready/').should be_true
+  context "after a successful upload" do
+    before(:each) do
+      @url = upload
+    end
+
+    it "should not store the file in cleartext" do
+      files = Dir.glob("#{Coquelicot.depot.path}/*")
+      files.should have(1).items
+      File.new(files[0]).read().should_not include('should not store an uploaded file')
+    end
+
+    it "should generate a random URL to download the file" do
+      @url.should_not include(File.basename(__FILE__))
+    end
+
+    it "should store the file with a different name than the one in URL" do
+      url_name = @url.split('/')[-1]
+      files = Dir.glob("#{Coquelicot.depot.path}/*")
+      files.should have(1).items
+      url_name.should_not eql(File.basename(files[0]))
+    end
+
+    it "should encode the encryption key in URL as no password has been specified" do
+      url_name = @url.split('/')[-1]
+      url_name.split('-').should have(2).items
+    end
+
+    it "should download when using extra Base32 characters in URL" do
+      splitted = @url.split('/')
+      name = splitted[-1].upcase.gsub(/O/, '0').gsub(/L/, '1')
+      get "#{splitted[0..-2].join '/'}/#{name}"
+      last_response.should be_ok
+      last_response['Content-Type'].should eql('text/x-script.ruby')
+    end
+
+    context "when the file has been downloaded" do
+      before(:each) do
+        get @url
+      end
+
+      it "should be the same file as the uploaded" do
+        last_response.should be_ok
+        last_response['Content-Type'].should eql('text/x-script.ruby')
+        last_response.body.should eql(File.new(__FILE__).read)
+      end
+
+      it "should always has the same Last-Modified header" do
+        last_modified = last_response['Last-Modified']
+        last_modified.should_not be_nil
+        get @url
+        last_response['Last-Modified'].should eql(last_modified)
+      end
+    end
   end
 
-  it "should allow retrieval of an uploaded file" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'upload_password' => UPLOAD_PASSWORD
-    follow_redirect!
-    last_response.should be_ok
-    doc = Hpricot(last_response.body)
-    url = (doc/'a').collect { |a| a.attributes['href'] }.
-      select { |h| h.start_with? "http://#{last_request.host}/" }[0]
-    get url
-    last_response.should be_ok
-    last_response['Content-Type'].should eql('text/x-script.ruby')
-    last_response.body.should eql(File.new(__FILE__).read)
+  context "given an empty file" do
+    before do
+      @empty_file = Tempfile.new('empty')
+    end
+    it "should not be accepted when uploaded" do
+      url = upload :file => Rack::Test::UploadedFile.new(@empty_file.path, 'text/plain')
+      url.should be_nil
+      last_response.should_not be_redirect
+    end
+    after do
+      @empty_file.close true
+    end
   end
 
   it "should prevent upload without a password" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby')
+    url = upload :upload_token => JSON.dump({'upload_password' => ''})
+    url.should be_nil
     last_response.status.should eql(403)
   end
 
   it "should prevent upload with a wrong password" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'upload_password' => "bad"
+    url = upload :upload_token => JSON.dump({'upload_password' => 'bad'})
+    url.should be_nil
     last_response.status.should eql(403)
   end
 
-  it "should not store an uploaded file in cleartext" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    files = Dir.glob("#{Depot.instance.path}/*")
-    files.should have(1).items
-    File.new(files[0]).read().should_not include('should not store an uploaded file')
+  it "should allow AJAX upload password verification" do
+    request "/authenticate", :method => "POST", :xhr => true,
+                             :params => { :upload_token => { 'upload_password' => UPLOAD_PASSWORD } }
+    last_response.should be_ok
+    request "/authenticate", :method => "POST", :xhr => true,
+                             :params => { :upload_token => '{}' }
+    last_response.status.should eql(403)
+    request "/authenticate", :method => "POST", :xhr => true,
+                             :params => { :upload_token => JSON.dump({'upload_password' => 'wrong'}) }
+    last_response.status.should eql(403)
   end
 
-  it "should generate a random URL to retrieve a file" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    last_response['Location'].should_not include(File.basename(__FILE__))
-  end
+  context "when a 'one time download' has been retrieved" do
+    before(:each) do
+      @url = upload :one_time => true
+      get @url
+    end
 
-  it "should store files with a different name than then one in URL" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    url_name = last_response['Location'].split('/')[-1]
-    files = Dir.glob("#{Depot.instance.path}/*")
-    files.should have(1).items
-    url_name.should_not eql(File.basename(files[0]))
-  end
+    it "should be the same as the uploaded file" do
+      last_response.should be_ok
+      last_response['Content-Type'].should eql('text/x-script.ruby')
+      last_response.body.should eql(File.new(__FILE__).read)
+    end
 
-  it "should encode the encryption key in URL when no password has been specified" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    url_name = last_response['Location'].split('/')[-1]
-    url_name.split('-').should have(2).items
-  end
+    it "should not be downloadable any more" do
+      get @url
+      last_response.status.should eql(410)
+    end
 
-  it "should not encode the encryption key in URL when a password has been specified" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'file_key' => 'somethingSecret',
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    url_name = last_response['Location'].split('/')[-1]
-    url_name.split('-').should have(1).items
+    it "should have zero'ed the file on the server" do
+      files = Dir.glob("#{Coquelicot.depot.path}/*")
+      files.should have(1).items
+      File.lstat(files[0]).size.should eql(0)
+    end
   end
 
-  it "should give a random password when asked"
+  context "after a password protected upload" do
+    before(:each) do
+      @url = upload :file_key => 'somethingSecret'
+    end
 
-  it "should allow retrieval of a password protected file" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'file_key' => 'somethingSecret',
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    follow_redirect!
-    last_response.should be_ok
-    doc = Hpricot(last_response.body)
-    url = (doc/'a').collect { |a| a.attributes['href'] }.
-      select { |h| h.start_with? "http://#{last_request.host}/" }[0]
-    get url
-    last_response.should be_ok
-    doc = Hpricot(last_response.body)
-    (doc/'input#file_key').should have(1).items
-    url = (doc/'form')[0].attributes['action']
-    post url, 'file_key' => 'somethingSecret'
-    last_response.should be_ok
-    last_response['Content-Type'].should eql('text/x-script.ruby')
-    last_response.body.should eql(File.new(__FILE__).read)
+    it "should not return an URL with the encryption key" do
+      url_name = @url.split('/')[-1]
+      url_name.split('-').should have(1).items
+    end
+
+    it "should offer a password form before download" do
+      get @url
+      last_response.should be_ok
+      last_response['Content-Type'].should eql('text/html')
+      doc = Hpricot(last_response.body)
+      (doc/'input#file_key').should have(1).items
+    end
+
+    context "when given the correct password" do
+      it "should download the same file" do
+        post @url, :file_key => 'somethingSecret'
+        last_response.should be_ok
+        last_response['Content-Type'].should eql('text/x-script.ruby')
+        last_response.body.should eql(File.new(__FILE__).read)
+      end
+    end
+
+    it "should prevent download without a password" do
+      post @url
+      last_response.status.should eql(403)
+    end
+
+    it "should prevent download with a wrong password" do
+      post @url, :file_key => 'BAD'
+      last_response.status.should eql(403)
+    end
   end
 
-  it "should not allow retrieval of a password protected file without the password" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'file_key' => 'somethingSecret',
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    follow_redirect!
-    last_response.should be_ok
-    doc = Hpricot(last_response.body)
-    url = (doc/'a').collect { |a| a.attributes['href'] }.
-      select { |h| h.start_with? "http://#{last_request.host}/" }[0]
-    get url
-    last_response.should be_ok
-    last_response['Content-Type'].should_not eql('text/x-script.ruby')
-    post url
-    last_response.status.should eql(403)
+  context "after an upload with a time limit" do
+    before(:each) do
+      @url = upload :expire => 60 # 1 hour
+    end
+
+    it "should prevent download after the time limit has expired" do
+      # let's be tomorrow
+      Timecop.travel(Date.today + 1) do
+        get @url
+        last_response.status.should eql(410)
+      end
+    end
   end
 
-  it "should not allow retrieval of a password protected file with a wrong password" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'file_key' => 'somethingSecret',
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    follow_redirect!
-    last_response.should be_ok
-    doc = Hpricot(last_response.body)
-    url = (doc/'a').collect { |a| a.attributes['href'] }.
-      select { |h| h.start_with? "http://#{last_request.host}/" }[0]
-    post url, 'file_key' => 'BAD'
+  it "should refuse an expiration time longer than the maximum" do
+    upload :expire => 60 * 24 * 31 * 12 # 1 year
     last_response.status.should eql(403)
   end
 
-  it "should not allow retrieval after the time limit has expired" do
-    post '/upload', 'file' => Rack::Test::UploadedFile.new(__FILE__, 'text/x-script.ruby'),
-                    'expire' => 60,  # 1 hour
-                    'upload_password' => UPLOAD_PASSWORD
-    last_response.redirect?.should be_true
-    follow_redirect!
-    last_response.should be_ok
-    doc = Hpricot(last_response.body)
-    url = (doc/'a').collect { |a| a.attributes['href'] }.
-      select { |h| h.start_with? "http://#{last_request.host}/" }[0]
+  it "should cleanup expired files" do
+    url = upload :expire => 60, :file_key => 'test' # 1 hour
+    url_name = url.split('/')[-1]
+    Dir.glob("#{Coquelicot.depot.path}/*").should have(1).items
     # let's be tomorrow
     Timecop.travel(Date.today + 1) do
-      get url
-      last_response.status.should eql(410)
+      Coquelicot.depot.gc!
+      files = Dir.glob("#{Coquelicot.depot.path}/*")
+      files.should have(1).items
+      File.lstat(files[0]).size.should eql(0)
+      Coquelicot.depot.get_file(url_name).expired?.should be_true
+    end
+    # let's be after 'gone' period
+    Timecop.travel(Time.now + (Coquelicot.settings.gone_period * 60)) do
+      Coquelicot.depot.gc!
+      Dir.glob("#{Coquelicot.depot.path}/*").should have(0).items
+      Coquelicot.depot.get_file(url_name).should be_nil
     end
   end
-
-  it "should cleanup expired files"
-
-  it "should map extra base32 characters to filenames"
 end