require 'spec_helper'
describe Rubydora::DigitalObject do
before do
@mock_api = Rubydora::Fc3Service.new({})
@mock_api.stub(:repository_profile, {"repositoryVersion" => "3.4"})
@mock_repository = Rubydora::Repository.new({}, @mock_api)
end
describe "profile" do
before(:each) do
@object = Rubydora::DigitalObject.new 'pid', @mock_repository
end
it "should convert object profile to a simple hash" do
@mock_api.should_receive(:object).with(:pid => 'pid').and_return("1234")
h = @object.profile
h.should have_key("a")
h['a'].should == '1'
h.should have_key("b")
h['b'].should == '2'
h.should have_key("objModels")
h['objModels'].should == ['3', '4']
end
it "should be frozen (to prevent modification)" do
@mock_api.should_receive(:object).with(:pid => 'pid').and_return("1234")
h = @object.profile
expect { h['asdf'] = 'asdf' }.to raise_error
end
it "should return nil for empty profile fields" do
@mock_api.should_receive(:object).with(:pid => 'pid').and_return("")
@object.profile['a'].should be_nil
end
it "should throw exceptions that arise" do
@mock_api.should_receive(:object).with(:pid => 'pid').and_raise(Net::HTTPBadResponse)
expect { @object.profile }.to raise_error(Net::HTTPBadResponse)
end
end
describe "initialize" do
before(:each) do
@mock_api.stub(:object) { raise RestClient::ResourceNotFound }
end
subject { Rubydora::DigitalObject.new 'pid', @mock_api }
it "should load a DigitalObject instance" do
expect(subject).to be_a_kind_of(Rubydora::DigitalObject)
end
it "should be new" do
expect(subject).to be_new
end
it "should be new_record" do
expect(subject).to be_new_record
end
it "should call ingest on save" do
subject.stub(:datastreams) { {} }
expect(@mock_api).to receive(:ingest).with(hash_including(:pid => 'pid')).and_return('pid')
subject.save
end
describe "without a provided pid" do
subject { Rubydora::DigitalObject.new nil, @mock_api }
it "should create a new Fedora object with a generated PID if no PID is provided" do
@mock_api.should_receive(:ingest).with(hash_including(:pid => nil)).and_return('pid')
@mock_api.should_receive(:datastreams).with(hash_including(:pid => 'pid')).and_raise(RestClient::ResourceNotFound)
subject.save
subject.pid.should == 'pid'
end
end
end
describe "create" do
it "should call the Fedora REST API to create a new object" do
@mock_api.should_receive(:ingest).with(instance_of(Hash)).and_return("pid")
obj = Rubydora::DigitalObject.create "pid", { :a => 1, :b => 2}, @mock_api
obj.should be_a_kind_of(Rubydora::DigitalObject)
end
it "should return a new object with the Fedora response pid when no pid is provided" do
@mock_api.should_receive(:ingest).with(instance_of(Hash)).and_return("pid")
obj = Rubydora::DigitalObject.create "new", { :a => 1, :b => 2}, @mock_api
obj.should be_a_kind_of(Rubydora::DigitalObject)
obj.pid.should == "pid"
end
end
describe "retreive datastreams" do
describe "without profiles (fedora < 3.6)" do
before(:each) do
@mock_api.stub :datastreams do |hash|
""
end
@object = Rubydora::DigitalObject.new 'pid', @mock_api
@object.stub(:new_record? => false)
end
it "should provide a hash populated by the existing datastreams" do
@object.datastreams.should have_key("a")
@object.datastreams.should have_key("b")
@object.datastreams.should have_key("c")
end
it "should allow other datastreams to be added" do
@mock_api.should_receive(:datastream).with(:pid => 'pid', :dsid => 'z').and_raise(RestClient::ResourceNotFound)
@object.datastreams.length.should == 3
ds = @object.datastreams["z"]
ds.should be_a_kind_of(Rubydora::Datastream)
ds.new?.should == true
@object.datastreams.length.should == 4
end
it "should let datastreams be accessed via hash notation" do
@object['a'].should be_a_kind_of(Rubydora::Datastream)
@object['a'].should == @object.datastreams['a']
end
it "should provide a way to override the type of datastream object to use" do
class MyCustomDatastreamClass < Rubydora::Datastream; end
object = Rubydora::DigitalObject.new 'pid', @mock_api
object.stub(:datastream_object_for) do |dsid|
MyCustomDatastreamClass.new(self, dsid)
end
object.datastreams['asdf'].should be_a_kind_of(MyCustomDatastreamClass)
end
end
describe "with profiles (fedora >= 3.6)" do
before(:each) do
@mock_api.stub :datastreams do |hash|
"
Test label
"
end
@object = Rubydora::DigitalObject.new 'pid', @mock_api
@object.stub(:new_record? => false)
end
it "should provide a hash populated by the existing datastreams" do
@object.datastreams.should have_key("a")
@object.datastreams.should have_key("b")
@object.datastreams.should have_key("c")
end
it "should load the profile attributes" do
expect(@object['a'].label).to eq 'Test label'
end
it "should not set the new datastream as changed" do
expect(@object['a']).to_not be_changed
end
end
end
describe "retrieved with batch ds profiles" do
before(:each) do
@mock_api.stub(:datastreams).and_return <<-XML
some:uri
label
true
some:uri
label
true
some:uri
label
true
XML
@object = Rubydora::DigitalObject.new 'pid', @mock_api
@object.stub(:new_record? => false)
end
describe "datastreams" do
it "should provide a hash populated by the existing datastreams" do
@object.datastreams.should have_key("a")
@object.datastreams["a"].new?.should be_false
@object.datastreams["a"].changed?.should be_false
@object.datastreams.should have_key("b")
@object.datastreams["b"].new?.should be_false
@object.datastreams["b"].changed?.should be_false
@object.datastreams.should have_key("c")
@object.datastreams["c"].new?.should be_false
@object.datastreams["c"].changed?.should be_false
end
end
end
describe "update" do
before(:each) do
@mock_api.stub(:object) { <<-XML
label
XML
}
@object = Rubydora::DigitalObject.new 'pid', @mock_api
end
it "should not say changed if the value is set the same" do
@object.label = "label"
@object.should_not be_changed
end
end
describe "retrieve" do
end
describe "save" do
before(:each) do
@original_modified = "2011-01-02:05:15:45.1Z"
@mock_api.stub(:object) { <<-XML
2011-01-02:05:15:45.100Z
XML
}
@object = Rubydora::DigitalObject.new 'pid', @mock_api
end
describe "saving an object's datastreams" do
before do
@new_ds = double(Rubydora::Datastream)
@new_ds.stub(:new? => true, :changed? => true, :content_changed? => true, :content => 'XXX', :dsCreateDate => '12345')
@new_empty_ds = double(Rubydora::Datastream)
@new_empty_ds.stub(:new? => true, :changed? => false, :content_changed? => false, :content => nil, :dsCreateDate => '12345')
@existing_ds = double(Rubydora::Datastream)
@existing_ds.stub(:new? => false, :changed? => false, :content_changed? => false, :content => 'YYY', :dsCreateDate => '12345')
@changed_attr_ds = double(Rubydora::Datastream)
@changed_attr_ds.stub(:new? => false, :changed? => true, :content_changed? => false, :content => 'YYY', :dsCreateDate => '12345')
@changed_ds = double(Rubydora::Datastream)
@changed_ds.stub(:new? => false, :changed? => true, :content_changed? => true, :content => 'ZZZ', :dsCreateDate => '2012-01-02:05:15:45.100Z')
@changed_empty_ds = double(Rubydora::Datastream)
@changed_empty_ds.stub(:new? => false, :changed? => true, :content_changed? => true, :content => nil, :dsCreateDate => '12345')
end
it "should save a new datastream with content" do
@object.stub(:datastreams) { { :new_ds => @new_ds } }
@new_ds.should_receive(:save)
@object.save
end
it "should save a datastream whose content has changed" do
@object.stub(:datastreams) { { :changed_ds => @changed_ds } }
@changed_ds.should_receive(:save)
@object.save
# object date should be canonicalized and updated
@object.lastModifiedDate.should == '2012-01-02:05:15:45.1Z'
end
it "should not set lastModifiedDate if the before_save callback is false" do
@object.stub(:datastreams) { { :changed_ds => @changed_ds } }
@changed_ds.should_receive(:dsCreateDate).and_return(nil)
@changed_ds.should_receive(:save)
@object.should_not_receive(:lastModifiedDate=)
@object.save
# object date should be unchanged from its original value
@object.lastModifiedDate.should == '2011-01-02:05:15:45.1Z'
end
it "should save a datastream whose attributes have changed" do
@object.stub(:datastreams) { { :changed_attr_ds => @changed_attr_ds } }
@changed_attr_ds.should_receive(:save)
@object.save
end
it "should save an existing datastream whose content is nil" do
@object.stub(:datastreams) { { :changed_empty_ds => @changed_empty_ds } }
@changed_empty_ds.should_receive(:save)
@object.save
end
it "should not save a datastream that is unchanged" do
@object.stub(:datastreams) { { :existing_ds => @existing_ds } }
@existing_ds.should_not_receive(:save)
@object.save
end
it "should not save a new datastream that never received content" do
@object.stub(:datastreams) { { :new_empty_ds => @new_empty_ds } }
@new_empty_ds.should_not_receive(:save)
@object.save
end
end
it "should save all changed attributes" do
@object.label = "asdf"
@object.should_receive(:datastreams).and_return({})
@mock_api.should_receive(:modify_object).with(hash_including(:pid => 'pid'))
@object.save
expect(@object).to_not be_changed, "#{@object.changes.inspect}"
end
it "updates the modification time" do
ds = double(Rubydora::Datastream)
ds.stub(:changed? => false)
@object.stub(:datastreams) { { :ds => ds } }
@object.lastModifiedDate.should == @original_modified
mod_time = "2012-01-02:05:15:00.1Z"
@mock_api.should_receive(:modify_object).and_return(mod_time)
@object.label = "asdf"
@object.save
@object.lastModifiedDate.should == mod_time
expect(@object).to_not be_changed, "#{@object.changes.inspect}"
end
end
describe "delete" do
before(:each) do
@object = Rubydora::DigitalObject.new 'pid', @mock_api
end
it "should call the Fedora REST API" do
@mock_api.should_receive(:purge_object).with({:pid => 'pid'})
@object.delete
end
end
describe "models" do
before(:each) do
@mock_api.stub(:object) { <<-XML
XML
}
@object = Rubydora::DigitalObject.new 'pid', @mock_api
end
it "should add models to fedora" do
@mock_api.should_receive(:add_relationship) do |params|
params.should have_key(:subject)
params[:predicate].should == 'info:fedora/fedora-system:def/model#hasModel'
params[:object].should == 'asdf'
end
@object.models << "asdf"
end
it "should remove models from fedora" do
@object.stub(:profile).and_return({"objModels" => ['asdf']})
@mock_api.should_receive(:purge_relationship) do |params|
params.should have_key(:subject)
params[:predicate].should == 'info:fedora/fedora-system:def/model#hasModel'
params[:object].should == 'asdf'
end
@object.models.delete("asdf")
end
it "should be able to handle complete model replacemenet" do
@object.stub(:profile).and_return({"objModels" => ['asdf']})
@mock_api.should_receive(:add_relationship).with(instance_of(Hash))
@mock_api.should_receive(:purge_relationship).with(instance_of(Hash))
@object.models = '1234'
end
end
describe "relations" do
before(:each) do
@mock_api.stub(:object) { <<-XML
XML
}
@object = Rubydora::DigitalObject.new 'pid', @mock_api
end
it "should fetch related objects using sparql" do
@mock_api.should_receive(:find_by_sparql_relationship).with('info:fedora/pid', 'info:fedora/fedora-system:def/relations-external#hasPart').and_return([1])
@object.parts.should == [1]
end
it "should add related objects" do
@mock_api.should_receive(:add_relationship) do |params|
params.should have_key(:subject)
params[:predicate].should == 'info:fedora/fedora-system:def/relations-external#hasPart'
params[:object].should == 'asdf'
end
@mock_object = double(Rubydora::DigitalObject)
@mock_object.should_receive(:fqpid).and_return('asdf')
@mock_api.should_receive(:find_by_sparql_relationship).with('info:fedora/pid', 'info:fedora/fedora-system:def/relations-external#hasPart').and_return([])
@object.parts << @mock_object
end
it "should remove related objects" do
@mock_api.should_receive(:purge_relationship) do |params|
params.should have_key(:subject)
params[:predicate].should == 'info:fedora/fedora-system:def/relations-external#hasPart'
params[:object].should == 'asdf'
end
@mock_object = double(Rubydora::DigitalObject)
@mock_object.should_receive(:fqpid).and_return('asdf')
@mock_api.should_receive(:find_by_sparql_relationship).with('info:fedora/pid', 'info:fedora/fedora-system:def/relations-external#hasPart').and_return([@mock_object])
@object.parts.delete(@mock_object)
end
end
describe "versions" do
before(:each) do
@mock_api.stub(:object) { <<-XML
XML
}
@mock_api.stub(:object_versions) { <<-XML
2011-09-26T20:41:02.450Z
2011-10-11T21:17:48.124Z
XML
}
@object = Rubydora::DigitalObject.new 'pid', @mock_api
end
it "should have a list of previous versions" do
@object.versions.should have(2).items
@object.versions.first.asOfDateTime.should == '2011-09-26T20:41:02.450Z'
end
it "should access versions as read-only copies" do
expect { @object.versions.first.label = "asdf" }.to raise_error
end
it "should lookup content of datastream using the asOfDateTime parameter" do
@mock_api.should_receive(:datastreams).with(hash_including(:asOfDateTime => '2011-09-26T20:41:02.450Z')).and_return('')
Rubydora::Datastream.should_receive(:new).with(anything, 'my_ds', hash_including(:asOfDateTime => '2011-09-26T20:41:02.450Z'))
ds = @object.versions.first['my_ds']
end
end
describe "to_api_params" do
before(:each) do
@object = Rubydora::DigitalObject.new 'pid', @mock_api
end
it "should compile parameters to hash" do
@object.send(:to_api_params).should == {}
end
end
shared_examples "an object attribute" do
subject { Rubydora::DigitalObject.new 'pid', @mock_api }
describe "getter" do
it "should return the value" do
subject.instance_variable_set("@#{method}", 'asdf')
subject.send(method).should == 'asdf'
end
it "should look in the object profile" do
subject.should_receive(:profile) { { Rubydora::DigitalObject::OBJ_ATTRIBUTES[method.to_sym].to_s => 'qwerty' } }
subject.send(method).should == 'qwerty'
end
it "should fall-back to the set of default attributes" do
@mock_api.should_receive(:object).with(:pid=>"pid").and_raise(RestClient::ResourceNotFound)
Rubydora::DigitalObject::OBJ_DEFAULT_ATTRIBUTES.should_receive(:[]).with(method.to_sym) { 'zxcv'}
subject.send(method).should == 'zxcv'
end
end
describe "setter" do
before do
subject.stub(:datastreams => [])
end
it "should mark the object as changed after setting" do
@mock_api.should_receive(:object).with(:pid=>"pid").and_raise(RestClient::ResourceNotFound)
subject.send("#{method}=", 'new_value')
subject.should be_changed
end
it "should not mark the object as changed if the value does not change" do
subject.should_receive(method) { 'zxcv' }
subject.send("#{method}=", 'zxcv')
end
it "should appear in the save request" do
@mock_api.should_receive(:ingest).with(hash_including(method.to_sym => 'new_value'))
@mock_api.should_receive(:object).with(:pid=>"pid").and_raise(RestClient::ResourceNotFound)
subject.send("#{method}=", 'new_value')
subject.save
end
end
end
describe "#state" do
subject { Rubydora::DigitalObject.new 'pid', @mock_api }
describe "getter" do
it "should return the value" do
subject.instance_variable_set("@state", 'asdf')
subject.state.should == 'asdf'
end
it "should look in the object profile" do
subject.should_receive(:profile) { { Rubydora::DigitalObject::OBJ_ATTRIBUTES[:state].to_s => 'qwerty' } }
subject.state.should == 'qwerty'
end
it "should fall-back to the set of default attributes" do
@mock_api.should_receive(:object).with(:pid=>"pid").and_raise(RestClient::ResourceNotFound)
Rubydora::DigitalObject::OBJ_DEFAULT_ATTRIBUTES.should_receive(:[]).with(:state) { 'zxcv'}
subject.state.should == 'zxcv'
end
end
describe "setter" do
before do
subject.stub(:datastreams => [])
end
it "should mark the object as changed after setting" do
@mock_api.should_receive(:object).with(:pid=>"pid").and_raise(RestClient::ResourceNotFound)
subject.state= 'D'
subject.should be_changed
end
it "should raise an error when setting an invalid value" do
expect {subject.state= 'Q'}.to raise_error ArgumentError, "Allowed values for state are 'I', 'A' and 'D'. You provided 'Q'"
end
it "should not mark the object as changed if the value does not change" do
subject.should_receive(:state) { 'A' }
subject.state= 'A'
subject.should_not be_changed
end
it "should appear in the save request" do
@mock_api.should_receive(:ingest).with(hash_including(:state => 'A'))
@mock_api.should_receive(:object).with(:pid=>"pid").and_raise(RestClient::ResourceNotFound)
subject.state='A'
subject.save
end
end
end
describe "#ownerId" do
it_behaves_like "an object attribute"
let(:method) { 'ownerId' }
end
describe "#label" do
it_behaves_like "an object attribute"
let(:method) { 'label' }
end
describe "#logMessage" do
it_behaves_like "an object attribute"
let(:method) { 'logMessage' }
end
describe "#lastModifiedDate" do
it_behaves_like "an object attribute"
let(:method) { 'lastModifiedDate' }
end
describe "#createdDate" do
it_behaves_like "an object attribute"
let(:method) { 'createdDate' }
end
end