# encoding: UTF-8 require File.expand_path('../../test_helper', __FILE__) class SchemaTest < MiniTest::Spec include Spontaneous def setup @site = setup_site @site.schema_loader_class = Spontaneous::Schema::PersistentMap end def teardown teardown_site end context "Configurable names" do setup do class ::FunkyContent < Content; end class ::MoreFunkyContent < FunkyContent; end class ::ABCDifficultName < Content; end class ::CustomName < ABCDifficultName title "Some Name" end end teardown do [:FunkyContent, :MoreFunkyContent, :ABCDifficultName, :CustomName].each do |klass| Object.send(:remove_const, klass) end end should "default to generated version" do FunkyContent.default_title.should == "Funky Content" FunkyContent.title.should == "Funky Content" MoreFunkyContent.title.should == "More Funky Content" ABCDifficultName.default_title.should == "ABC Difficult Name" ABCDifficultName.title.should == "ABC Difficult Name" end should "be settable" do CustomName.title.should == "Some Name" FunkyContent.title "Content Class" FunkyContent.title.should == "Content Class" end should "be settable using =" do FunkyContent.title = "Content Class" FunkyContent.title.should == "Content Class" end should "not inherit from superclass" do FunkyContent.title = "Custom Name" MoreFunkyContent.title.should == "More Funky Content" end end context "Persistent maps" do context "Schema UIDs" do setup do @site.schema.schema_map_file = File.expand_path('../../fixtures/schema/schema.yml', __FILE__) class SchemaClass < Page field :description style :simple layout :clean box :posts end @instance = SchemaClass.new @uids = @site.uid end teardown do SchemaTest.send(:remove_const, :SchemaClass) rescue nil end # should "be 12 characters long" do # Schema::UID.generate.to_s.length.should == 12 # end should "be unique" do ids = (0..10000).map { Schema::UIDMap.generate } ids.uniq.length.should == ids.length end should "be singletons" do a = @uids["xxxxxxxxxxxx"] b = @uids["xxxxxxxxxxxx"] c = @uids["ffffffffffff"] a.object_id.should == b.object_id a.should == b c.object_id.should_not == b.object_id c.should_not == b end # should "not be creatable" do # lambda { UID.new('sadf') }.must_raise(NoMethodError) # end should "return nil if passed nil" do @uids[nil].should be_nil end should "return nil if passed an empty string" do @uids[""].should be_nil end should "return the same UID if passed one" do a = @uids["xxxxxxxxxxxx"] @uids[a].should == a end should "test as equal to its string representation" do @uids["llllllllllll"].should == "llllllllllll" end should "test as eql? if they have the same id" do a = @uids["llllllllllll"] b = a.dup assert a.eql?(b), "Identical IDs should pass eql? test" end should "be readable by content classes" do SchemaClass.schema_id.should == @uids["xxxxxxxxxxxx"] end should "be readable by fields" do @instance.fields[:description].schema_id.should == @uids["ffffffffffff"] end should "be readable by boxes" do @instance.boxes[:posts].schema_id.should == @uids["bbbbbbbbbbbb"] end should "be readable by styles" do @instance.styles[:simple].schema_id.should == @uids["ssssssssssss"] end should "be readable by layouts" do @instance.layout.name.should == :clean @instance.layout.schema_id.should == @uids["llllllllllll"] end context "lookups" do should "return classes" do Site.schema["xxxxxxxxxxxx"].should == SchemaClass end should "return fields" do Site.schema["ffffffffffff"].should == SchemaClass.field_prototypes[:description] end should "return boxes" do Site.schema["bbbbbbbbbbbb"].should == SchemaClass.box_prototypes[:posts] end should "return styles" do Site.schema["ssssssssssss"].should == SchemaClass.style_prototypes[:simple] end should "return layouts" do Site.schema["llllllllllll"].should == SchemaClass.layout_prototypes[:clean] end end end context "schema verification" do setup do @site.schema.schema_map_file = File.expand_path('../../fixtures/schema/before.yml', __FILE__) class ::Page < Spontaneous::Page field :title end class B < ::Page; end class C < Content; end class D < Content; end class O < Box; end B.field :description B.field :author B.box :promotions do field :field1 field :field2 style :style1 style :style2 end B.box :publishers, :type => O B.style :inline B.style :outline B.layout :thin B.layout :fat O.field :ofield1 O.field :ofield2 O.style :ostyle1 O.style :ostyle2 # have to use mocking because schema class list is totally fecked up # after running other tests # TODO: look into reliable, non-harmful way of clearing out the schema state # between tests # Schema.stubs(:classes).returns([B, C, D, O]) # Schema.classes.should == [B, C, D, O] @uids = @site.schema.uids ::Page.schema_id.should == @uids["tttttttttttt"] B.schema_id.should == @uids["bbbbbbbbbbbb"] C.schema_id.should == @uids["cccccccccccc"] D.schema_id.should == @uids["dddddddddddd"] O.schema_id.should == @uids["oooooooooooo"] end teardown do Object.send(:remove_const, :Page) rescue nil SchemaTest.send(:remove_const, :B) rescue nil SchemaTest.send(:remove_const, :C) rescue nil SchemaTest.send(:remove_const, :D) rescue nil SchemaTest.send(:remove_const, :E) rescue nil SchemaTest.send(:remove_const, :F) rescue nil SchemaTest.send(:remove_const, :O) rescue nil end should "return the right schema anme for inherited box fields" do f = B.boxes[:publishers].instance_class.field :newfield B.boxes[:publishers].instance_class.fields.first.schema_name.should == "field/oooooooooooo/ofield1" f.schema_name.should == "field/publishers00/newfield" end should "detect addition of classes" do class E < Content; end @site.schema.stubs(:classes).returns([B, C, D, E]) exception = nil begin @site.schema.validate_schema flunk("Validation should raise an exception") rescue Spontaneous::SchemaModificationError => e exception = e end exception.added_classes.should == [E] # need to explicitly define solution to validation error # Schema.expects(:generate).returns('dddddddddddd') # D.schema_id.should == 'dddddddddddd' end should "detect removal of classes" do SchemaTest.send(:remove_const, :C) rescue nil SchemaTest.send(:remove_const, :D) rescue nil @site.schema.stubs(:classes).returns([::Page, B, O]) begin @site.schema.validate_schema flunk("Validation should raise an exception") rescue Spontaneous::SchemaModificationError => e exception = e end exception.removed_classes.map { |c| c.name }.sort.should == ["SchemaTest::C", "SchemaTest::D"] end should "detect multiple removals & additions of classes" do SchemaTest.send(:remove_const, :C) rescue nil SchemaTest.send(:remove_const, :D) rescue nil class E < Content; end class F < Content; end @site.schema.stubs(:classes).returns([::Page, B, E, F, O]) begin @site.schema.validate_schema flunk("Validation should raise an exception if schema is modified") rescue Spontaneous::SchemaModificationError => e exception = e end exception.added_classes.should == [E, F] exception.removed_classes.map {|c| c.name}.sort.should == ["SchemaTest::C", "SchemaTest::D"] end should "detect addition of fields" do B.field :name C.field :location C.field :description begin @site.schema.validate_schema flunk("Validation should raise an exception if new fields are added") rescue Spontaneous::SchemaModificationError => e exception = e end exception.added_fields.should == [B.field_prototypes[:name], C.field_prototypes[:location], C.field_prototypes[:description]] end should "detect removal of fields" do field = B.field_prototypes[:author] B.stubs(:field_prototypes).returns({:author => field}) B.stubs(:fields).returns([field]) begin @site.schema.validate_schema flunk("Validation should raise an exception if fields are removed") rescue Spontaneous::SchemaModificationError => e exception = e end exception.removed_fields.length == 1 exception.removed_fields[0].name.should == "description" exception.removed_fields[0].owner.should == SchemaTest::B exception.removed_fields[0].category.should == :field end should "detect addition of boxes" do B.box :changes B.box :updates begin @site.schema.validate_schema flunk("Validation should raise an exception if new boxes are added") rescue Spontaneous::SchemaModificationError => e exception = e end exception.added_boxes.should == [B.boxes[:changes], B.boxes[:updates]] end should "detect removal of boxes" do boxes = S::Collections::PrototypeSet.new boxes[:promotions] = B.boxes[:promotions] B.stubs(:box_prototypes).returns(boxes) begin @site.schema.validate_schema flunk("Validation should raise an exception if fields are removed") rescue Spontaneous::SchemaModificationError => e exception = e end exception.removed_boxes.length.should == 1 exception.removed_boxes[0].name.should == "publishers" exception.removed_boxes[0].owner.should == SchemaTest::B exception.removed_boxes[0].category.should == :box end should "detect addition of styles" do B.style :fancy B.style :dirty begin @site.schema.validate_schema flunk("Validation should raise an exception if new styles are added") rescue Spontaneous::SchemaModificationError => e exception = e end exception.added_styles.should == [B.styles.detect{ |s| s.name == :fancy }, B.styles.detect{ |s| s.name == :dirty }] end should "detect removal of styles" do style = B.styles[:inline] B.styles.expects(:order).returns([:inline]) B.styles.stubs(:[]).with(:inline).returns(style) B.styles.stubs(:[]).with(:outline).returns(nil) begin @site.schema.validate_schema flunk("Validation should raise an exception if styles are removed") rescue Spontaneous::SchemaModificationError => e exception = e end exception.removed_styles.length.should == 1 exception.removed_styles[0].name.should == "outline" exception.removed_styles[0].owner.should == SchemaTest::B exception.removed_styles[0].category.should == :style end should "detect addition of layouts" do B.layout :fancy B.layout :dirty begin @site.schema.validate_schema flunk("Validation should raise an exception if new layouts are added") rescue Spontaneous::SchemaModificationError => e exception = e end exception.added_layouts.should == [B.layouts.detect{ |s| s.name == :fancy }, B.layouts.detect{ |s| s.name == :dirty }] end should "detect removal of layouts" do layout = B.layouts[:thin] B.layouts.expects(:order).returns([:thin]) B.layouts.stubs(:[]).with(:thin).returns(layout) B.layouts.stubs(:[]).with(:fat).returns(nil) begin @site.schema.validate_schema flunk("Validation should raise an exception if fields are removed") rescue Spontaneous::SchemaModificationError => e exception = e end exception.removed_layouts.length.should == 1 exception.removed_layouts[0].name.should == "fat" exception.removed_layouts[0].owner.should == SchemaTest::B exception.removed_layouts[0].category.should == :layout end should "detect addition of fields to anonymous boxes" do f1 = B.boxes[:publishers].instance_class.field :field3 f2 = B.boxes[:promotions].instance_class.field :field3 begin @site.schema.validate_schema flunk("Validation should raise an exception if new fields are added to anonymous boxes") rescue Spontaneous::SchemaModificationError => e exception = e end assert_same_elements exception.added_fields, [f2, f1] end should "detect removal of fields from anonymous boxes" do f2 = B.boxes[:promotions].instance_class.field_prototypes[:field2] B.boxes[:promotions].instance_class.stubs(:field_prototypes).returns({:field2 => f2}) B.boxes[:promotions].instance_class.stubs(:fields).returns([f2]) begin @site.schema.validate_schema flunk("Validation should raise an exception if fields are removed from anonymous boxes") rescue Spontaneous::SchemaModificationError => e exception = e end exception.removed_fields.length.should == 1 exception.removed_fields[0].name.should == "field1" exception.removed_fields[0].owner.instance_class.should == SchemaTest::B.boxes[:promotions].instance_class exception.removed_fields[0].category.should == :field end should "detect addition of fields to box types" do O.field :name begin @site.schema.validate_schema flunk("Validation should raise an exception if new fields are added to boxes") rescue Spontaneous::SchemaModificationError => e exception = e end exception.added_fields.should == [O.field_prototypes[:name]] end # should "detect removal of fields from box types" do # skip "stubbing is messing up the field hierarchy in weird ways" # fields = [O.field_prototypes[:ofield1]] # O.stubs(:fields).returns(fields) # begin # @site.schema.validate_schema # flunk("Validation should raise an exception if fields are removed") # rescue Spontaneous::SchemaModificationError => e # exception = e # end # exception.removed_fields.length == 1 # exception.removed_fields[0].name.should == "ofield2" # exception.removed_fields[0].owner.should == SchemaTest::O # exception.removed_fields[0].category.should == :field # end should "detect addition of styles to box types" should "detect removal of styles from box types" should "detect addition of styles to anonymous boxes" do s1 = B.boxes[:publishers].instance_class.style :style3 s2 = B.boxes[:promotions].instance_class.style :style3 begin @site.schema.validate_schema flunk("Validation should raise an exception if new fields are added to anonymous boxes") rescue Spontaneous::SchemaModificationError => e exception = e end assert_same_elements exception.added_styles, [s2, s1] end should "detect removal of styles from anonymous boxes" do klass = B.boxes[:promotions].instance_class style = klass.styles.first klass.styles.expects(:order).returns([style.name]) klass.styles.stubs(:[]).with(style.name).returns(style) klass.styles.stubs(:[]).with(:style2).returns(nil) begin @site.schema.validate_schema flunk("Validation should raise an exception if styles are removed") rescue Spontaneous::SchemaModificationError => e exception = e end exception.removed_styles.length.should == 1 exception.removed_styles[0].name.should == "style2" exception.removed_styles[0].owner.instance_class.should == SchemaTest::B.boxes[:promotions].instance_class exception.removed_styles[0].category.should == :style end end end context "Transient (testing) maps" do setup do @site.schema.schema_loader_class = Spontaneous::Schema::TransientMap class V < Spontaneous::Piece; end class W < Spontaneous::Piece; end end teardown do self.class.send(:remove_const, :V) self.class.send(:remove_const, :W) end should "create uids on demand" do V.schema_id.should_not be_nil W.schema_id.should_not be_nil V.schema_id.should_not == W.schema_id end should "return consistent ids within a session" do a = V.schema_id b = V.schema_id a.should equal?(b) end should "return UID objects" do V.schema_id.must_be_instance_of(Spontaneous::Schema::UID) end context "for inherited boxes" do setup do class ::A < Spontaneous::Piece box :a end class ::B < ::A box :a end class ::C < ::B box :a end end teardown do Object.send(:remove_const, :A) rescue nil Object.send(:remove_const, :B) rescue nil Object.send(:remove_const, :C) rescue nil end should "be the same as the box in the supertype" do B.boxes[:a].schema_id.should == A.boxes[:a].schema_id C.boxes[:a].schema_id.should == A.boxes[:a].schema_id B.boxes[:a].instance_class.schema_id.should == A.boxes[:a].instance_class.schema_id C.boxes[:a].instance_class.schema_id.should == A.boxes[:a].instance_class.schema_id end end end context "Schema groups" do setup do class ::A < Spontaneous::Page group :a, :b, :c box :cgroup do allow_group :c end end class ::B < Spontaneous::Piece group :b, :c style :fish style :frog box :agroup do allow_groups :a, :c end end class ::C < Spontaneous::Piece group :c box :bgroup do allow_group :b, :style => "fish" end box :cgroup do allow_group :c, :level => :root end end end teardown do Object.send(:remove_const, :A) rescue nil Object.send(:remove_const, :B) rescue nil Object.send(:remove_const, :C) rescue nil end should "let boxes allow a list of content types" do A.boxes.cgroup.allowed_types(nil).should == [A, B, C] C.boxes.bgroup.allowed_types(nil).should == [A, B] C.boxes.cgroup.allowed_types(nil).should == [A, B, C] B.boxes.agroup.allowed_types(nil).should == [A, B, C] end should "apply the options to all the included classes" do user = mock() S::Permissions.stubs(:has_level?).with(user, S::Permissions::UserLevel.root).returns(true) A.boxes.cgroup.allowed_types(user).should == [A, B, C] S::Permissions.stubs(:has_level?).with(user, S::Permissions::UserLevel.root).returns(false) A.boxes.cgroup.allowed_types(user).should == [] end should "allow for configuring styles" do c = C.new b = B.new styles = c.bgroup.available_styles(b) styles.length.should == 1 styles.first.name.should == :fish end should "reload correctly" do FileUtils.mkdir(@site.root / "config") @site.schema.write_schema @site.schema.delete(::B) Object.send(:remove_const, :B) class ::B < Spontaneous::Piece group :b style :fish style :frog box :agroup do allow_groups :a, :c end end @site.schema.validate! A.boxes.cgroup.allowed_types(nil).should == [A, C] C.boxes.bgroup.allowed_types(nil).should == [A, B] end end context "Map writing" do context "Non-existant maps" do setup do @map_file = File.expand_path('../../../tmp/schema.yml', __FILE__) ::File.exists?(@map_file).should be_false @site.schema.schema_map_file = @map_file class ::A < Spontaneous::Page field :title field :introduction layout :sparse box :posts do field :description end end class ::B < Spontaneous::Piece field :location style :daring end end teardown do Object.send(:remove_const, :A) rescue nil Object.send(:remove_const, :B) rescue nil FileUtils.rm(@map_file) if ::File.exists?(@map_file) end should "get created with verification" do S.schema.validate! classes = [ ::A, ::B] @inheritance_map = nil # would like to do all of this using mocks, but don't know how to do that # without fecking up the whole schema id creation process expected = Hash[ classes.map { |klass| [ klass.schema_id.to_s, klass.schema_name ] } ] expected.merge!({ A.field_prototypes[:title].schema_id.to_s => A.field_prototypes[:title].schema_name, A.field_prototypes[:introduction].schema_id.to_s => A.field_prototypes[:introduction].schema_name, A.layout_prototypes[:sparse].schema_id.to_s => A.layout_prototypes[:sparse].schema_name, A.boxes[:posts].schema_id.to_s => A.boxes[:posts].schema_name, A.boxes[:posts].field_prototypes[:description].schema_id.to_s => A.boxes[:posts].field_prototypes[:description].schema_name, B.field_prototypes[:location].schema_id.to_s => B.field_prototypes[:location].schema_name, B.style_prototypes[:daring].schema_id.to_s => B.style_prototypes[:daring].schema_name, }) File.exists?(@map_file).should be_true YAML.load_file(@map_file).should == expected end end context "change resolution" do setup do @map_file = File.expand_path('../../../tmp/schema.yml', __FILE__) FileUtils.mkdir_p(File.dirname(@map_file)) FileUtils.cp(File.expand_path('../../fixtures/schema/resolvable.yml', __FILE__), @map_file) @site.schema.schema_map_file = @map_file class ::A < Spontaneous::Page field :title field :introduction layout :sparse box :posts do field :description end end class ::B < Spontaneous::Piece field :location field :duration style :daring end @site.schema.validate! A.schema_id.should == S.schema.uids["qLcxinA008"] end teardown do Object.send(:remove_const, :A) rescue nil Object.send(:remove_const, :B) rescue nil Object.send(:remove_const, :X) rescue nil Object.send(:remove_const, :Y) rescue nil S::Content.delete FileUtils.rm(@map_file) if ::File.exists?(@map_file) rescue nil end should "update the STI map after addition of classes" do ::A.sti_subclasses_array.should == [::A.schema_id.to_s] class ::X < ::A field :wild box :monkeys do field :banana end layout :rich end S.schema.validate! ::X.schema_id.should_not be_nil ::X.sti_key_array.should == [::X.schema_id.to_s] ::A.sti_subclasses_array.should == [::A.schema_id.to_s, ::X.schema_id.to_s] end should "be done automatically if only additions are found" do A.field :moose class ::X < ::A field :wild box :monkeys do field :banana end layout :rich end class ::Y < ::B style :risky end S.schema.validate! ::X.schema_id.should_not be_nil ::Y.schema_id.should_not be_nil ::A.field_prototypes[:moose].schema_id.should_not be_nil m = YAML.load_file(@map_file) m[::A.field_prototypes[:moose].schema_id.to_s].should == ::A.field_prototypes[:moose].schema_name m[::X.schema_id.to_s].should == ::X.schema_name m[::Y.schema_id.to_s].should == ::Y.schema_name m[::X.field_prototypes[:wild].schema_id.to_s].should == ::X.field_prototypes[:wild].schema_name m[::X.boxes[:monkeys].schema_id.to_s].should == ::X.boxes[:monkeys].schema_name m[::X.boxes[:monkeys].field_prototypes[:banana].schema_id.to_s].should == ::X.boxes[:monkeys].field_prototypes[:banana].schema_name m[::X.layout_prototypes[:rich].schema_id.to_s].should == ::X.layout_prototypes[:rich].schema_name end should "be done automatically if only classes have been removed" do uid = B.schema_id.to_s Object.send(:remove_const, :B) S.schema.stubs(:classes).returns([::A]) S.schema.reload! S.schema.validate! m = YAML.load_file(@map_file) m.key?(uid).should be_false end should "be done automatically if only boxes have been removed" do uid = A.boxes[:posts].schema_id.to_s A.stubs(:box_prototypes).returns(S::Collections::PrototypeSet.new) S.schema.stubs(:classes).returns([A, B]) S.schema.reload! S.schema.validate! m = YAML.load_file(@map_file) m.key?(uid).should be_false end should "be done automatically if only fields have been removed" do f1 = A.field_prototypes[:title] uid = f1.schema_id.to_s f2 = A.field_prototypes[:introduction] A.stubs(:field_prototypes).returns({:introduction => f2}) A.stubs(:fields).returns([f2]) S.schema.reload! S.schema.validate! m = YAML.load_file(@map_file) m.key?(uid).should be_false end should "be done automatically in presence of independent addition inside type and of type" do A.field :moose uid = B.schema_id.to_s Object.send(:remove_const, :B) S.schema.stubs(:classes).returns([::A]) S.schema.reload! S.schema.validate! ::A.field_prototypes[:moose].schema_id.should_not be_nil m = YAML.load_file(@map_file) m[::A.field_prototypes[:moose].schema_id.to_s].should == ::A.field_prototypes[:moose].schema_name m.key?(uid).should be_false end should "be done automatically in presence of independent addition & removal of fields" do A.field :moose f1 = B.field_prototypes[:location] uid = f1.schema_id.to_s f2 = B.field_prototypes[:duration] B.stubs(:field_prototypes).returns({:duration => f2}) B.stubs(:fields).returns([f2]) S.schema.reload! S.schema.validate! ::A.field_prototypes[:moose].schema_id.should_not be_nil m = YAML.load_file(@map_file) m[::A.field_prototypes[:moose].schema_id.to_s].should == ::A.field_prototypes[:moose].schema_name m.key?(uid).should be_false end should "be done automatically in presence of independent changes to boxes & fields" do B.field :crisis uid = A.boxes[:posts].schema_id.to_s A.stubs(:box_prototypes).returns(S::Collections::PrototypeSet.new) S.schema.stubs(:classes).returns([A, B]) S.schema.reload! S.schema.validate! ::B.field_prototypes[:crisis].schema_id.should_not be_nil m = YAML.load_file(@map_file) m.key?(uid).should be_false end should "be done automatically in presence of independent changes to classes, boxes & fields" do class ::X < B; end uid = A.boxes[:posts].schema_id.to_s A.stubs(:box_prototypes).returns(S::Collections::PrototypeSet.new) B.field :crisis B.box :circus A.field :crisis S.schema.stubs(:classes).returns([::A, ::B, ::X]) S.schema.reload! S.schema.validate! ::A.field_prototypes[:crisis].schema_id.should_not be_nil m = YAML.load_file(@map_file) box = ::B.boxes[:circus] m[box.schema_id.to_s].should == box.schema_name field = ::A.field_prototypes[:crisis] m[field.schema_id.to_s].should == field.schema_name field = ::B.field_prototypes[:crisis] m[field.schema_id.to_s].should == field.schema_name m.key?(uid).should be_false end # sanity check should "still raise error in case of addition & deletion" do A.field :added f1 = A.field_prototypes[:title] f2 = A.field_prototypes[:added] uid = f1.schema_id.to_s f3 = A.field_prototypes[:introduction] A.stubs(:field_prototypes).returns({:added => f2, :introduction => f3}) A.stubs(:fields).returns([f2, f3]) S.schema.reload! lambda { S.schema.validate! }.must_raise(Spontaneous::SchemaModificationError) end should "still raise error in case of addition & deletion of classes" do class ::X < A; end uid = B.schema_id.to_s Object.send(:remove_const, :B) S.schema.stubs(:classes).returns([::A, ::X]) S.schema.reload! lambda { S.schema.validate! }.must_raise(Spontaneous::SchemaModificationError) end should "delete box content when a box is removed" do instance = A.new piece1 = B.new piece2 = B.new instance.posts << piece1 instance.posts << piece2 instance.save instance = S::Content[instance.id] instance.posts.contents.length.should == 2 Content.count.should == 3 uid = A.boxes[:posts].schema_id.to_s A.stubs(:box_prototypes).returns(S::Collections::PrototypeSet.new) S.schema.stubs(:classes).returns([A, B]) S.schema.reload! S.schema.validate! Content.count.should == 1 S::Content[instance.id].should == instance end context "which isn't automatically resolvable" do context "with one field removed" do setup do A.field :a A.field :b @df1 = A.field_prototypes[:title] @af1 = A.field_prototypes[:a] @af2 = A.field_prototypes[:b] @uid = @df1.schema_id.to_s @f3 = A.field_prototypes[:introduction] A.stubs(:field_prototypes).returns({:a => @af1, :b => @af2, :introduction => @f3}) A.stubs(:fields).returns([@af1, @af2, @f3]) S.schema.reload! begin S.schema.validate! flunk("Validation should raise error when adding & deleting fields") rescue Spontaneous::SchemaModificationError => e @modification = e.modification end end should "return list of solutions for removal of one field" do # add :a, :b, delete :title # add :b, rename :title => :a # add :a, rename :title => :b @modification.actions.description.should =~ /field 'title'/ @modification.actions.length.should == 3 action = @modification.actions[0] action.action.should == :delete action.source.should == @df1.schema_id action.description.should =~ /delete field 'title'/i action = @modification.actions[1] action.action.should == :rename action.source.should == @df1.schema_id action.description.should =~ /rename field 'title' to 'a'/i action = @modification.actions[2] action.action.should == :rename action.source.should == @df1.schema_id action.description.should =~ /rename field 'title' to 'b'/i end should "enable fixing the problem by deleting field from schema" do action = @modification.actions[0] begin S.schema.apply(action) rescue Spontaneous::SchemaModificationError => e flunk("Deletion of field should have resolved schema error") end m = YAML.load_file(@map_file) m.key?(@uid).should be_false end should "enable fixing the problem by renaming field 'a'" do action = @modification.actions[1] begin S.schema.apply(action) rescue Spontaneous::SchemaModificationError => e flunk("Renaming of field should have resolved schema error") end m = YAML.load_file(@map_file) m[@uid].should == @af1.schema_name end should "enable fixing the problem by renaming field 'b'" do action = @modification.actions[2] begin S.schema.apply(action) rescue Spontaneous::SchemaModificationError => e flunk("Renaming of field should have resolved schema error") end m = YAML.load_file(@map_file) m[@uid].should == @af2.schema_name end end context "with two fields removed" do setup do A.field :a A.field :b A.field :c @df1 = A.field_prototypes[:title] @df2 = A.field_prototypes[:introduction] @af1 = A.field_prototypes[:a] @af2 = A.field_prototypes[:b] @af3 = A.field_prototypes[:c] @uid1 = @df1.schema_id.to_s @uid2 = @df2.schema_id.to_s A.stubs(:field_prototypes).returns({:a => @af1, :b => @af2, :c => @af3}) A.stubs(:fields).returns([@af1, @af2, @af3]) S.schema.reload! begin S.schema.validate! flunk("Validation should raise error when adding & deleting fields") rescue Spontaneous::SchemaModificationError => e @modification = e.modification end end should "return list of solutions" do # add :a, :b; delete :title, :introduction # rename :title => :a, :introduction => :b # rename :introduction => :a, :title => :b # add :a; delete :introduction; rename :title => :b # add :a; delete :title; rename :introduction => :b # add :b; delete :introduction; rename :title => :a # add :b; delete :title; rename :introduction => :a @modification.actions.description.should =~ /field 'title'/ @modification.actions.length.should == 4 action = @modification.actions[0] action.action.should == :delete action.source.should == @df1.schema_id action.description.should =~ /delete field 'title'/i action = @modification.actions[1] action.action.should == :rename action.source.should == @df1.schema_id action.description.should =~ /rename field 'title' to 'a'/i action = @modification.actions[2] action.action.should == :rename action.source.should == @df1.schema_id action.description.should =~ /rename field 'title' to 'b'/i action = @modification.actions[3] action.action.should == :rename action.source.should == @df1.schema_id action.description.should =~ /rename field 'title' to 'c'/i end should "enable fixing the problem by deleting both fields" do action = @modification.actions[0] begin S.schema.apply(action) flunk("Deletion of field should not have resolved schema error") rescue Spontaneous::SchemaModificationError => e modification = e.modification end action = modification.actions[0] begin S.schema.apply(action) rescue Spontaneous::SchemaModificationError => e flunk("Deletion of field should have resolved schema error") end m = YAML.load_file(@map_file) m.key?(@uid1).should be_false m.key?(@uid2).should be_false end should "enable fixing the problem by deleting one field and renaming other as 'a'" do action = @modification.actions[0] begin S.schema.apply(action) flunk("Deletion of field should not have resolved schema error") rescue Spontaneous::SchemaModificationError => e modification = e.modification end action = modification.actions[1] begin S.schema.apply(action) rescue Spontaneous::SchemaModificationError => e flunk("Deletion of field should have resolved schema error") end m = YAML.load_file(@map_file) m.key?(@uid1).should be_false m.key?(@uid2).should be_true m[@uid2].should == @af1.schema_name end should "enable fixing the problem by renaming one field as 'c' and deleting other" do action = @modification.actions[3] begin S.schema.apply(action) flunk("Renaming of field should not have resolved schema error") rescue Spontaneous::SchemaModificationError => e modification = e.modification end action = modification.actions[0] begin S.schema.apply(action) rescue Spontaneous::SchemaModificationError => e flunk("Deletion of field should have resolved schema error") end m = YAML.load_file(@map_file) m.key?(@uid1).should be_true m.key?(@uid2).should be_false m[@uid1].should == @af3.schema_name end should "enable fixing the problem by renaming one field as 'c' and renaming other as 'b'" do action = @modification.actions[3] begin S.schema.apply(action) flunk("Renaming of field should not have resolved schema error") rescue Spontaneous::SchemaModificationError => e modification = e.modification end action = modification.actions[2] begin S.schema.apply(action) rescue Spontaneous::SchemaModificationError => e flunk("Deletion of field should have resolved schema error") end m = YAML.load_file(@map_file) m.key?(@uid1).should be_true m.key?(@uid2).should be_true m[@uid1].should == @af3.schema_name m[@uid2].should == @af2.schema_name end context "and two boxes removed" do setup do @db1 = A.boxes[:posts] A.box :added1 A.box :added2 @ab1 = A.boxes[:added1] @ab2 = A.boxes[:added2] boxes = S::Collections::PrototypeSet.new boxes[:added1] = @ab1 boxes[:added2] = @ab2 A.stubs(:box_prototypes).returns(boxes) classes = S.schema.classes.dup classes.delete(A::PostsBox) S.schema.stubs(:classes).returns(classes) S.schema.reload! begin S.schema.validate! flunk("Validation should raise error when adding & deleting fields") rescue Spontaneous::SchemaModificationError => e @modification = e.modification end end should "enable fixing by deleting both fields and renaming a box" do action = @modification.actions[0] begin S.schema.apply(action) flunk("Deleting of field should not have resolved schema error") rescue Spontaneous::SchemaModificationError => e modification = e.modification end action = modification.actions[0] begin S.schema.apply(action) flunk("Deleting of field should not have resolved schema error") rescue Spontaneous::SchemaModificationError => e modification = e.modification end action = modification.actions[1] begin S.schema.apply(action) rescue Spontaneous::SchemaModificationError => e flunk("Schema changes should have resolved error") end # p modification.actions end end end end end end end