667 lines
21 KiB
Ruby
667 lines
21 KiB
Ruby
require File.expand_path("../../../spec_helper", __FILE__)
|
|
|
|
class DesignViewModel < CouchRest::Model::Base
|
|
use_database DB
|
|
property :name
|
|
property :title
|
|
|
|
design do
|
|
view :by_name
|
|
end
|
|
end
|
|
|
|
describe "Design View" do
|
|
|
|
describe "(unit tests)" do
|
|
|
|
before :each do
|
|
@klass = CouchRest::Model::Designs::View
|
|
end
|
|
|
|
describe ".new" do
|
|
|
|
describe "with invalid parent model" do
|
|
it "should burn" do
|
|
lambda { @klass.new(String) }.should raise_exception
|
|
end
|
|
end
|
|
|
|
describe "with CouchRest Model" do
|
|
|
|
it "should setup attributes" do
|
|
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
|
@obj.model.should eql(DesignViewModel)
|
|
@obj.name.should eql('test_view')
|
|
@obj.query.should eql({:reduce => false})
|
|
end
|
|
|
|
it "should complain if there is no name" do
|
|
lambda { @klass.new(DesignViewModel, {}, nil) }.should raise_error
|
|
end
|
|
|
|
end
|
|
|
|
describe "with previous view instance" do
|
|
|
|
before :each do
|
|
first = @klass.new(DesignViewModel, {}, 'test_view')
|
|
@obj = @klass.new(first, {:foo => :bar})
|
|
end
|
|
|
|
it "should copy attributes" do
|
|
@obj.model.should eql(DesignViewModel)
|
|
@obj.name.should eql('test_view')
|
|
@obj.query.should eql({:reduce => false, :foo => :bar})
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
describe ".create" do
|
|
|
|
before :each do
|
|
@design_doc = {}
|
|
DesignViewModel.stub!(:design_doc).and_return(@design_doc)
|
|
end
|
|
|
|
it "should add a basic view" do
|
|
@klass.create(DesignViewModel, 'test_view', :map => 'foo')
|
|
@design_doc['views']['test_view'].should_not be_nil
|
|
end
|
|
|
|
it "should auto generate mapping from name" do
|
|
lambda { @klass.create(DesignViewModel, 'by_title') }.should_not raise_error
|
|
str = @design_doc['views']['by_title']['map']
|
|
str.should include("((doc['couchrest-type'] == 'DesignViewModel') && (doc['title'] != null))")
|
|
str.should include("emit(doc['title'], 1);")
|
|
str = @design_doc['views']['by_title']['reduce']
|
|
str.should include("return sum(values);")
|
|
end
|
|
|
|
it "should auto generate mapping from name with and" do
|
|
@klass.create(DesignViewModel, 'by_title_and_name')
|
|
str = @design_doc['views']['by_title_and_name']['map']
|
|
str.should include("(doc['title'] != null) && (doc['name'] != null)")
|
|
str.should include("emit([doc['title'], doc['name']], 1);")
|
|
str = @design_doc['views']['by_title_and_name']['reduce']
|
|
str.should include("return sum(values);")
|
|
end
|
|
|
|
end
|
|
|
|
describe "instance methods" do
|
|
|
|
before :each do
|
|
@obj = @klass.new(DesignViewModel, {}, 'test_view')
|
|
end
|
|
|
|
describe "#rows" do
|
|
it "should execute query" do
|
|
@obj.should_receive(:execute).and_return(true)
|
|
@obj.should_receive(:result).twice.and_return({'rows' => []})
|
|
@obj.rows.should be_empty
|
|
end
|
|
|
|
it "should wrap rows in ViewRow class" do
|
|
@obj.should_receive(:execute).and_return(true)
|
|
@obj.should_receive(:result).twice.and_return({'rows' => [{:foo => :bar}]})
|
|
CouchRest::Model::Designs::ViewRow.should_receive(:new).with({:foo => :bar}, @obj.model)
|
|
@obj.rows
|
|
end
|
|
end
|
|
|
|
describe "#all" do
|
|
it "should ensure docs included and call docs" do
|
|
@obj.should_receive(:include_docs!)
|
|
@obj.should_receive(:docs)
|
|
@obj.all
|
|
end
|
|
end
|
|
|
|
describe "#docs" do
|
|
it "should provide docs from rows" do
|
|
@obj.should_receive(:rows).and_return([])
|
|
@obj.docs
|
|
end
|
|
it "should cache the results" do
|
|
@obj.should_receive(:rows).once.and_return([])
|
|
@obj.docs
|
|
@obj.docs
|
|
end
|
|
end
|
|
|
|
describe "#first" do
|
|
it "should provide the first result of loaded query" do
|
|
@obj.should_receive(:result).and_return(true)
|
|
@obj.should_receive(:all).and_return([:foo])
|
|
@obj.first.should eql(:foo)
|
|
end
|
|
it "should perform a query if no results cached" do
|
|
view = mock('SubView')
|
|
@obj.should_receive(:result).and_return(nil)
|
|
@obj.should_receive(:limit).with(1).and_return(view)
|
|
view.should_receive(:all).and_return([:foo])
|
|
@obj.first.should eql(:foo)
|
|
end
|
|
end
|
|
|
|
describe "#last" do
|
|
it "should provide the last result of loaded query" do
|
|
@obj.should_receive(:result).and_return(true)
|
|
@obj.should_receive(:all).and_return([:foo, :bar])
|
|
@obj.first.should eql(:foo)
|
|
end
|
|
it "should perform a query if no results cached" do
|
|
view = mock('SubView')
|
|
@obj.should_receive(:result).and_return(nil)
|
|
@obj.should_receive(:limit).with(1).and_return(view)
|
|
view.should_receive(:descending).and_return(view)
|
|
view.should_receive(:all).and_return([:foo, :bar])
|
|
@obj.last.should eql(:bar)
|
|
end
|
|
end
|
|
|
|
describe "#count" do
|
|
it "should raise an error if view prepared for group" do
|
|
@obj.should_receive(:query).and_return({:group => true})
|
|
lambda { @obj.count }.should raise_error
|
|
end
|
|
|
|
it "should return first row value if reduce possible" do
|
|
view = mock("SubView")
|
|
row = mock("Row")
|
|
@obj.should_receive(:can_reduce?).and_return(true)
|
|
@obj.should_receive(:reduce).and_return(view)
|
|
view.should_receive(:rows).and_return([row])
|
|
row.should_receive(:value).and_return(2)
|
|
@obj.count.should eql(2)
|
|
end
|
|
it "should return 0 if no rows and reduce possible" do
|
|
view = mock("SubView")
|
|
@obj.should_receive(:can_reduce?).and_return(true)
|
|
@obj.should_receive(:reduce).and_return(view)
|
|
view.should_receive(:rows).and_return([])
|
|
@obj.count.should eql(0)
|
|
end
|
|
|
|
it "should perform limit request for total_rows" do
|
|
view = mock("SubView")
|
|
@obj.should_receive(:limit).with(0).and_return(view)
|
|
view.should_receive(:total_rows).and_return(4)
|
|
@obj.should_receive(:can_reduce?).and_return(false)
|
|
@obj.count.should eql(4)
|
|
end
|
|
end
|
|
|
|
describe "#empty?" do
|
|
it "should check the #all method for any results" do
|
|
all = mock("All")
|
|
all.should_receive(:empty?).and_return('win')
|
|
@obj.should_receive(:all).and_return(all)
|
|
@obj.empty?.should eql('win')
|
|
end
|
|
end
|
|
|
|
describe "#each" do
|
|
it "should call each method on all" do
|
|
@obj.should_receive(:all).and_return([])
|
|
@obj.each
|
|
end
|
|
it "should call each and pass block" do
|
|
set = [:foo, :bar]
|
|
@obj.should_receive(:all).and_return(set)
|
|
result = []
|
|
@obj.each do |s|
|
|
result << s
|
|
end
|
|
result.should eql(set)
|
|
end
|
|
end
|
|
|
|
describe "#offset" do
|
|
it "should excute" do
|
|
@obj.should_receive(:execute).and_return({'offset' => 3})
|
|
@obj.offset.should eql(3)
|
|
end
|
|
end
|
|
|
|
describe "#total_rows" do
|
|
it "should excute" do
|
|
@obj.should_receive(:execute).and_return({'total_rows' => 3})
|
|
@obj.total_rows.should eql(3)
|
|
end
|
|
end
|
|
|
|
describe "#keys" do
|
|
it "should request each row and provide key value" do
|
|
row = mock("Row")
|
|
row.should_receive(:key).twice.and_return('foo')
|
|
@obj.should_receive(:rows).and_return([row, row])
|
|
@obj.keys.should eql(['foo', 'foo'])
|
|
end
|
|
end
|
|
|
|
describe "#values" do
|
|
it "should request each row and provide value" do
|
|
row = mock("Row")
|
|
row.should_receive(:value).twice.and_return('foo')
|
|
@obj.should_receive(:rows).and_return([row, row])
|
|
@obj.values.should eql(['foo', 'foo'])
|
|
end
|
|
end
|
|
|
|
describe "#[]" do
|
|
it "should execute and provide requested field" do
|
|
@obj.should_receive(:execute).and_return({'total_rows' => 2})
|
|
@obj['total_rows'].should eql(2)
|
|
end
|
|
end
|
|
|
|
describe "#info" do
|
|
it "should raise error" do
|
|
lambda { @obj.info }.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "#database" do
|
|
it "should update query with value" do
|
|
@obj.should_receive(:update_query).with({:database => 'foo'})
|
|
@obj.database('foo')
|
|
end
|
|
end
|
|
|
|
describe "#key" do
|
|
it "should update query with value" do
|
|
@obj.should_receive(:update_query).with({:key => 'foo'})
|
|
@obj.key('foo')
|
|
end
|
|
it "should raise and error if startkey set" do
|
|
@obj.query[:startkey] = 'bar'
|
|
lambda { @obj.key('foo') }.should raise_error
|
|
end
|
|
it "should raise and error if endkey set" do
|
|
@obj.query[:endkey] = 'bar'
|
|
lambda { @obj.key('foo') }.should raise_error
|
|
end
|
|
it "should raise and error if both startkey and endkey set" do
|
|
@obj.query[:startkey] = 'bar'
|
|
@obj.query[:endkey] = 'bar'
|
|
lambda { @obj.key('foo') }.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "#startkey" do
|
|
it "should update query with value" do
|
|
@obj.should_receive(:update_query).with({:startkey => 'foo'})
|
|
@obj.startkey('foo')
|
|
end
|
|
it "should raise and error if key set" do
|
|
@obj.query[:key] = 'bar'
|
|
lambda { @obj.startkey('foo') }.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "#startkey_doc" do
|
|
it "should update query with value" do
|
|
@obj.should_receive(:update_query).with({:startkey_docid => 'foo'})
|
|
@obj.startkey_doc('foo')
|
|
end
|
|
it "should update query with object id if available" do
|
|
doc = mock("Document")
|
|
doc.should_receive(:id).and_return(44)
|
|
@obj.should_receive(:update_query).with({:startkey_docid => 44})
|
|
@obj.startkey_doc(doc)
|
|
end
|
|
end
|
|
|
|
describe "#endkey" do
|
|
it "should update query with value" do
|
|
@obj.should_receive(:update_query).with({:endkey => 'foo'})
|
|
@obj.endkey('foo')
|
|
end
|
|
it "should raise and error if key set" do
|
|
@obj.query[:key] = 'bar'
|
|
lambda { @obj.endkey('foo') }.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "#endkey_doc" do
|
|
it "should update query with value" do
|
|
@obj.should_receive(:update_query).with({:endkey_docid => 'foo'})
|
|
@obj.endkey_doc('foo')
|
|
end
|
|
it "should update query with object id if available" do
|
|
doc = mock("Document")
|
|
doc.should_receive(:id).and_return(44)
|
|
@obj.should_receive(:update_query).with({:endkey_docid => 44})
|
|
@obj.endkey_doc(doc)
|
|
end
|
|
end
|
|
|
|
describe "#descending" do
|
|
it "should update query" do
|
|
@obj.should_receive(:update_query).with({:descending => true})
|
|
@obj.descending
|
|
end
|
|
end
|
|
|
|
describe "#limit" do
|
|
it "should update query with value" do
|
|
@obj.should_receive(:update_query).with({:limit => 3})
|
|
@obj.limit(3)
|
|
end
|
|
end
|
|
|
|
describe "#skip" do
|
|
it "should update query with value" do
|
|
@obj.should_receive(:update_query).with({:skip => 3})
|
|
@obj.skip(3)
|
|
end
|
|
it "should update query with default value" do
|
|
@obj.should_receive(:update_query).with({:skip => 0})
|
|
@obj.skip
|
|
end
|
|
end
|
|
|
|
describe "#reduce" do
|
|
it "should update query" do
|
|
@obj.should_receive(:can_reduce?).and_return(true)
|
|
@obj.should_receive(:update_query).with({:reduce => true})
|
|
@obj.reduce
|
|
end
|
|
it "should raise error if query cannot be reduced" do
|
|
@obj.should_receive(:can_reduce?).and_return(false)
|
|
lambda { @obj.reduce }.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "#group" do
|
|
it "should update query" do
|
|
@obj.should_receive(:query).and_return({:reduce => true})
|
|
@obj.should_receive(:update_query).with({:group => true})
|
|
@obj.group
|
|
end
|
|
it "should raise error if query not prepared for reduce" do
|
|
@obj.should_receive(:query).and_return({:reduce => false})
|
|
lambda { @obj.group }.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "#group" do
|
|
it "should update query" do
|
|
@obj.should_receive(:query).and_return({:reduce => true})
|
|
@obj.should_receive(:update_query).with({:group => true})
|
|
@obj.group
|
|
end
|
|
it "should raise error if query not prepared for reduce" do
|
|
@obj.should_receive(:query).and_return({:reduce => false})
|
|
lambda { @obj.group }.should raise_error
|
|
end
|
|
end
|
|
|
|
describe "#group_level" do
|
|
it "should update query" do
|
|
@obj.should_receive(:group).and_return(@obj)
|
|
@obj.should_receive(:update_query).with({:group_level => 3})
|
|
@obj.group_level(3)
|
|
end
|
|
end
|
|
|
|
describe "#include_docs" do
|
|
it "should call include_docs! on new view" do
|
|
@obj.should_receive(:update_query).and_return(@obj)
|
|
@obj.should_receive(:include_docs!)
|
|
@obj.include_docs
|
|
end
|
|
end
|
|
|
|
describe "#reset!" do
|
|
it "should empty all cached data" do
|
|
@obj.should_receive(:result=).with(nil)
|
|
@obj.instance_exec { @rows = 'foo'; @docs = 'foo' }
|
|
@obj.reset!
|
|
@obj.instance_exec { @rows }.should be_nil
|
|
@obj.instance_exec { @docs }.should be_nil
|
|
end
|
|
end
|
|
|
|
#### PROTECTED METHODS
|
|
|
|
describe "#include_docs!" do
|
|
it "should set query value" do
|
|
@obj.should_receive(:result).and_return(false)
|
|
@obj.should_not_receive(:reset!)
|
|
@obj.send(:include_docs!)
|
|
@obj.query[:include_docs].should be_true
|
|
end
|
|
it "should reset if result and no docs" do
|
|
@obj.should_receive(:result).and_return(true)
|
|
@obj.should_receive(:include_docs?).and_return(false)
|
|
@obj.should_receive(:reset!)
|
|
@obj.send(:include_docs!)
|
|
@obj.query[:include_docs].should be_true
|
|
end
|
|
end
|
|
|
|
describe "#include_docs?" do
|
|
it "should return true if set" do
|
|
@obj.should_receive(:query).and_return({:include_docs => true})
|
|
@obj.send(:include_docs?).should be_true
|
|
end
|
|
it "should return false if not set" do
|
|
@obj.should_receive(:query).and_return({})
|
|
@obj.send(:include_docs?).should be_false
|
|
@obj.should_receive(:query).and_return({:include_docs => false})
|
|
@obj.send(:include_docs?).should be_false
|
|
end
|
|
end
|
|
|
|
describe "#update_query" do
|
|
it "returns a new instance of view" do
|
|
@obj.send(:update_query).object_id.should_not eql(@obj.object_id)
|
|
end
|
|
|
|
it "returns a new instance of view with extra parameters" do
|
|
new_obj = @obj.send(:update_query, {:foo => :bar})
|
|
new_obj.query[:foo].should eql(:bar)
|
|
end
|
|
end
|
|
|
|
describe "#design_doc" do
|
|
it "should call design_doc on model" do
|
|
@obj.model.should_receive(:design_doc)
|
|
@obj.send(:design_doc)
|
|
end
|
|
end
|
|
|
|
describe "#can_reduce?" do
|
|
it "should check and prove true" do
|
|
@obj.should_receive(:name).and_return('test_view')
|
|
@obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => 'foo'}}})
|
|
@obj.send(:can_reduce?).should be_true
|
|
end
|
|
it "should check and prove false" do
|
|
@obj.should_receive(:name).and_return('test_view')
|
|
@obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => nil}}})
|
|
@obj.send(:can_reduce?).should be_false
|
|
end
|
|
end
|
|
|
|
describe "#execute" do
|
|
before :each do
|
|
# disable real execution!
|
|
@design_doc = mock("DesignDoc")
|
|
@design_doc.stub!(:view_on)
|
|
@obj.model.stub!(:design_doc).and_return(@design_doc)
|
|
end
|
|
|
|
it "should return previous result if set" do
|
|
@obj.result = "foo"
|
|
@obj.send(:execute).should eql('foo')
|
|
end
|
|
|
|
it "should raise issue if no database" do
|
|
@obj.should_receive(:query).and_return({:database => nil})
|
|
model = mock("SomeModel")
|
|
model.should_receive(:database).and_return(nil)
|
|
@obj.should_receive(:model).and_return(model)
|
|
lambda { @obj.send(:execute) }.should raise_error
|
|
end
|
|
|
|
it "should delete the reduce option if not going to be used" do
|
|
@obj.should_receive(:can_reduce?).and_return(false)
|
|
@obj.query.should_receive(:delete).with(:reduce)
|
|
@obj.send(:execute)
|
|
end
|
|
|
|
it "should populate the results" do
|
|
@obj.should_receive(:can_reduce?).and_return(true)
|
|
@design_doc.should_receive(:view_on).and_return('foos')
|
|
@obj.send(:execute)
|
|
@obj.result.should eql('foos')
|
|
end
|
|
|
|
it "should retry once on a resource not found error" do
|
|
@obj.should_receive(:can_reduce?).and_return(true)
|
|
@obj.model.should_receive(:save_design_doc)
|
|
@design_doc.should_receive(:view_on).ordered.and_raise(RestClient::ResourceNotFound)
|
|
@design_doc.should_receive(:view_on).ordered.and_return('foos')
|
|
@obj.send(:execute)
|
|
@obj.result.should eql('foos')
|
|
end
|
|
|
|
it "should retry twice and fail on a resource not found error" do
|
|
@obj.should_receive(:can_reduce?).and_return(true)
|
|
@obj.model.should_receive(:save_design_doc)
|
|
@design_doc.should_receive(:view_on).twice.and_raise(RestClient::ResourceNotFound)
|
|
lambda { @obj.send(:execute) }.should raise_error(RestClient::ResourceNotFound)
|
|
end
|
|
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
describe "ViewRow" do
|
|
|
|
before :all do
|
|
@klass = CouchRest::Model::Designs::ViewRow
|
|
end
|
|
|
|
describe "intialize" do
|
|
it "should store reference to model" do
|
|
obj = @klass.new({}, "model")
|
|
obj.model.should eql('model')
|
|
end
|
|
it "should copy details from hash" do
|
|
obj = @klass.new({:foo => :bar, :test => :example}, "")
|
|
obj[:foo].should eql(:bar)
|
|
obj[:test].should eql(:example)
|
|
end
|
|
end
|
|
|
|
describe "running" do
|
|
before :each do
|
|
end
|
|
|
|
it "should provide id" do
|
|
obj = @klass.new({'id' => '123456'}, 'model')
|
|
obj.id.should eql('123456')
|
|
end
|
|
|
|
it "should provide key" do
|
|
obj = @klass.new({'key' => 'thekey'}, 'model')
|
|
obj.key.should eql('thekey')
|
|
end
|
|
|
|
it "should provide the value" do
|
|
obj = @klass.new({'value' => 'thevalue'}, 'model')
|
|
obj.value.should eql('thevalue')
|
|
end
|
|
|
|
it "should provide the raw document" do
|
|
obj = @klass.new({'doc' => 'thedoc'}, 'model')
|
|
obj.raw_doc.should eql('thedoc')
|
|
end
|
|
|
|
it "should instantiate a new document" do
|
|
hash = {'doc' => {'_id' => '12345', 'name' => 'sam'}}
|
|
obj = @klass.new(hash, DesignViewModel)
|
|
doc = mock('DesignViewModel')
|
|
obj.model.should_receive(:build_from_database).with(hash['doc']).and_return(doc)
|
|
obj.doc.should eql(doc)
|
|
end
|
|
|
|
it "should try to load from id if no document" do
|
|
hash = {'id' => '12345', 'value' => 5}
|
|
obj = @klass.new(hash, DesignViewModel)
|
|
doc = mock('DesignViewModel')
|
|
obj.model.should_receive(:get).with('12345').and_return(doc)
|
|
obj.doc.should eql(doc)
|
|
end
|
|
|
|
it "should try to load linked document if available" do
|
|
hash = {'id' => '12345', 'value' => {'_id' => '54321'}}
|
|
obj = @klass.new(hash, DesignViewModel)
|
|
doc = mock('DesignViewModel')
|
|
obj.model.should_receive(:get).with('54321').and_return(doc)
|
|
obj.doc.should eql(doc)
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
describe "scenarios" do
|
|
|
|
before :all do
|
|
@objs = [
|
|
{:name => "Judith"},
|
|
{:name => "Lorena"},
|
|
{:name => "Peter"},
|
|
{:name => "Sam"},
|
|
{:name => "Vilma"}
|
|
].map{|h| DesignViewModel.create(h)}
|
|
end
|
|
|
|
describe "loading documents" do
|
|
|
|
it "should return first" do
|
|
DesignViewModel.by_name.first.name.should eql("Judith")
|
|
end
|
|
|
|
it "should return last" do
|
|
DesignViewModel.by_name.last.name.should eql("Vilma")
|
|
end
|
|
|
|
it "should allow multiple results" do
|
|
view = DesignViewModel.by_name.limit(3)
|
|
view.total_rows.should eql(5)
|
|
view.last.name.should eql("Peter")
|
|
view.all.length.should eql(3)
|
|
end
|
|
|
|
end
|
|
|
|
describe "index information" do
|
|
it "should provide total_rows" do
|
|
DesignViewModel.by_name.total_rows.should eql(5)
|
|
end
|
|
it "should provide total_rows" do
|
|
DesignViewModel.by_name.total_rows.should eql(5)
|
|
end
|
|
it "should provide an offset" do
|
|
DesignViewModel.by_name.offset.should eql(0)
|
|
end
|
|
it "should provide a set of keys" do
|
|
DesignViewModel.by_name.limit(2).keys.should eql(["Judith", "Lorena"])
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
|
|
end
|