require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')

Aqua.set_storage_engine('CouchDB') # to initialize the Aqua::Store namespace
require File.dirname(__FILE__) + '/document_fixture' # Document ... a Mash with the collection of methods

# Conveniences for typing with tests ... 
CouchDB =   Aqua::Store::CouchDB unless defined?( CouchDB ) 
Database =  CouchDB::Database unless defined?( Database )
Server =    CouchDB::Server unless defined?( Server)

describe 'CouchDB::StorageMethods' do
  before(:each) do
    @params = {
      :id => 'my_slug/thaz-right',
      :rev => "shouldn't change yo!",
      :more => "my big stuff"
    }
    @doc = Document.new( @params )
  end  
  
  describe 'initialization' do
    it 'should initialize with a hash of values accessible by symbol or string' do 
      @doc[:more].should == 'my big stuff'
      @doc['more'].should == 'my big stuff'
    end
    
    it 'should set the id with the initialization hash' do 
      @doc.id.should == 'my_slug/thaz-right' 
    end
    
    it 'should escape the id' do  
      @doc[:_id].should == 'my_slug%2Fthaz-right'
    end
    
    it 'should not set the rev and it should discard those keys' do
      @doc.rev.should == nil 
      @doc[:rev].should == nil  
    end
  end 
  
  describe 'core attributes' do
    
    describe 'revisions' do
      before(:each) do
        CouchDB.server.delete_all  
      end  
    
      it 'should be an empty array for a new record' do 
        @doc.revisions.should == []
      end 
    
      it 'should have one value after the document is saved' do
        @doc.save!
        @doc.revisions.size.should == 1
        @doc.revisions.first.should == @doc[:_rev]
      end
    
      it 'should continue adding revisions with each save' do
        @doc.save!
        @doc['new_attr'] = 'my new attribute, yup!'
        @doc.save!
        @doc.revisions.size.should == 2
      end     
    end    
    
    it 'rev should not be publicly settable' do 
      lambda{ @doc.rev = 'my_rev' }.should raise_error
    end 
    
    describe 'changing the id, post save' do
      before(:each) do
        CouchDB.server.delete_all
        @doc.save!
        @doc.id = 'something/new_and_fresh'
      end  
      
      it 'should change the id' do 
        @doc.id.should == 'something/new_and_fresh'
      end
        
      it 'should change the _id' do
        @doc[:_id].should == 'something%2Fnew_and_fresh'
      end
        
      it 'should successfully save' do
        lambda{ @doc.save! }.should_not raise_error
        @doc.retrieve['id'].should == 'something/new_and_fresh'
      end
        
      it 'should delete earlier versions on save' do 
        @doc.save!
        lambda{ CouchDB.get( "#{@doc.database.uri}/aqua/my_slug%2Fthaz-right") }.should raise_error
      end  
    end   
  
  end  
  
  describe 'database' do 
    before(:each) do
      CouchDB.server.delete_all  
    end  
    
    it 'should not be nil' do
      @doc.database.should_not be_nil
    end
    
    it 'should have the default database uri by default'  do 
      @doc.database.uri.should == 'http://127.0.0.1:5984/aqua'
    end
    
    it 'should be settable' do
      @doc.database = Database.create('my_class')
      @doc.database.uri.should == 'http://127.0.0.1:5984/aqua_my_class'
    end
    
    it 'should depend on database strategies set for storage\'s parent class' do
      pending( 'Have to create some CouchDB database strategies. Also have to make parent classes configurable.')
    end       
  end 
  
  describe 'uri' do 
    before(:each) do
      CouchDB.server.delete_all  
    end  
    
    it 'should have use the default database uri by default with the document id'  do 
      @doc.uri.should == 'http://127.0.0.1:5984/aqua/my_slug%2Fthaz-right'
    end
    
    it 'should reflect the non-default database name' do
      @doc.database = Database.create('my_class')
      @doc.uri.should == 'http://127.0.0.1:5984/aqua_my_class/my_slug%2Fthaz-right'
    end
    
    it 'should use a server generated uuid for the id if an id is not provided' do
      params = @params.dup
      params.delete(:id)
      doc = Document.new( params )
      doc.uri.should match(/\A#{doc.database.uri}\/[a-f0-9]*\z/)
    end  
           
  end  
  
  describe 'save/create' do 
    before(:each) do
      CouchDB.server.delete_all  
    end  
    
    it 'saving should create a document in the database' do 
      @doc.save
      lambda{ Aqua::Store::CouchDB.get( @doc.uri ) }.should_not raise_error
    end
    
    it 'save should return itself if it worked' do
      return_value = @doc.save
      return_value.class.should == Document 
      return_value.id.should == @doc.id
    end
    
    it 'saving should return false if it did not work' do
      @doc.save
      @doc[:_rev] = nil # should cause a conflict error HTTP 409 in couchdb
      lambda{ @doc.save }.should_not raise_error
      @doc.save.should == false
    end
    
    it 'saving should update the "id" and "rev"' do
      doc_id = @doc.id
      doc_rev = @doc.rev
      @doc.save
      @doc[:_id].should_not == doc_id
      @doc.rev.should_not == doc_rev
    end      
     
    it 'save! should raise an error on failure when creating' do
      @doc.save
      @doc[:_rev] = nil # should cause a conflict error HTTP 409 in couchdb
      lambda{ @doc.save! }.should raise_error
    end
    
    it 'create should return itself when successful' do
      doc = Document.create(@params)
      doc.class.should == Document
      doc.rev.should_not be_nil
    end
    
    it '#new? should be true for unsaved documents' do
      @doc.should be_new
    end
    
    it '#new? should be false after a document has been saved' do
      @doc.save!
      @doc.should_not be_new
    end
    
    it 'should #exists? if it has been saved to CouchDB' do 
      @doc.save!
      @doc.should be_exists
    end
    
    it 'should not #exists? if the document is new' do
      @doc.should_not be_exists
    end    
  end 
  
  describe 'deleting' do
    before(:each) do
      CouchDB.server.delete_all  
    end  
    
    it 'should #delete a record' do
      @doc.save!
      @doc.delete
      @doc.should_not be_exists
    end
    
    it 'should return true on successful #delete' do
      @doc.save!
      @doc.delete.should == true
    end  
      
    it 'should return false when it fails' do 
      @doc.save!
      CouchDB.should_receive(:delete).and_raise( CouchDB::Conflict )
      @doc.delete.should == false
    end
      
    it 'should raise an error on failure when #delete! is used' do 
      @doc.save!
      CouchDB.should_receive(:delete).and_raise( CouchDB::Conflict )
      lambda { @doc.delete! }.should raise_error
    end  
  end  
  
  describe 'updating' do
    before(:each) do
      CouchDB.server.delete_all!
    end  
    
    it 'saving after a change should change the revision number' do 
      @doc.save 
      rev = @doc.rev
      _id = @doc[:_id]
      id = @doc[:id] 
      @doc['more'] = 'less ... really'
      @doc['newness'] = 'overrated'
      @doc.save
      @doc.id.should == id
      @doc[:_id].should == _id
      @doc.rev.should_not == rev
    end
      
    it 'saving after a change should retain changed data' do
      @doc.save 
      @doc['more'] = 'less ... really'
      @doc['newness'] = 'overrated'
      @doc.save
        
      @doc['more'].should == 'less ... really'
      @doc['newness'].should == 'overrated'
      @doc.retrieve['more'].should == 'less ... really'
    end  
  end

end