diff --git a/Gemfile b/Gemfile index 029f295..eef05b7 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ source :gemcutter gem "rails", "= 3.0.0.beta" - +gem "thoughtbot-factory_girl", "= 1.2.2" +gem "delorean", :version => ">= 0.1.1" diff --git a/test/import_test.rb b/test/import_test.rb index 29a6bc7..1fac348 100644 --- a/test/import_test.rb +++ b/test/import_test.rb @@ -1,6 +1,17 @@ require File.expand_path(File.dirname(__FILE__) + '/test_helper') describe "#import" do + + it "should return the number of inserts performed" do + assert_difference "Topic.count", +10 do + result = Topic.import Build(3, :topics) + assert_equal 3, result.num_inserts + + result = Topic.import Build(7, :topics) + assert_equal 7, result.num_inserts + end + end + context "with :validation option" do let(:columns) { %w(title author_name) } let(:valid_values) { [[ "LDAP", "Jerry Carter"], ["Rails Recipes", "Chad Fowler"]] } @@ -9,13 +20,15 @@ describe "#import" do context "with validation checks turned off" do it "should import valid data" do assert_difference "Topic.count", +2 do - Topic.import columns, valid_values, :validate => false + result = Topic.import columns, valid_values, :validate => false + assert_equal 2, result.num_inserts end end it "should import invalid data" do assert_difference "Topic.count", +2 do - Topic.import columns, invalid_values, :validate => false + result = Topic.import columns, invalid_values, :validate => false + assert_equal 2, result.num_inserts end end end @@ -23,68 +36,187 @@ describe "#import" do context "with validation checks turned on" do it "should import valid data" do assert_difference "Topic.count", +2 do - Topic.import columns, valid_values, :validate => true + result = Topic.import columns, valid_values, :validate => true + assert_equal 2, result.num_inserts end end it "should not import invalid data" do assert_no_difference "Topic.count" do - Topic.import columns, invalid_values, :validate => true + result = Topic.import columns, invalid_values, :validate => true + assert_equal 0, result.num_inserts end end - + + it "should report the failed instances" do + results = Topic.import columns, invalid_values, :validate => true + assert_equal invalid_values.size, results.failed_instances.size + results.failed_instances.each{ |e| assert_kind_of Topic, e } + end + it "should import valid data when mixed with invalid data" do assert_difference "Topic.count", +2 do - Topic.import columns, valid_values + invalid_values, :validate => true + result = Topic.import columns, valid_values + invalid_values, :validate => true + assert_equal 2, result.num_inserts end assert_equal 0, Topic.find_all_by_title(invalid_values.map(&:first)).count end end end -end - # - # context "with an array of model instances" do - # it "should import attributes from those model instances" - # - # it "should import unsaved model instances" - # end - # - # context "ActiveRecord model niceties" do - # context "created_on columns" do - # it "should set the created_on column" - # - # it "should set the created_on column respecting the time zone" - # end - # - # context "created_at columns" do - # it "should set the created_at column" - # - # it "should set the created_at column respecting the time zone" - # end - # - # context "updated_on columns" do - # it "should set the updated_on column" - # - # it "should set the updated_on column respecting the time zone" - # end - # - # context "updated_at columns" do - # it "should set the updated_at column" - # - # it "should set the updated_at column respecting the time zone" - # end - # end - # - # context "importing over existing records" do - # it "should not add duplicate records" - # - # it "should not overwrite existing records" - # end - # - # it "should import models with attribute fields that are database reserved words" - # - # it "should return the number of inserts performed" - # end + + context "with an array of unsaved model instances" do + let(:topic) { Build(:topic, :title => "The RSpec Book", :author_name => "David Chelimsky")} + let(:topics) { Build(9, :topics) } + let(:invalid_topics){ Build(7, :invalid_topics)} + + it "should import records based on those model's attributes" do + assert_difference "Topic.count", +9 do + result = Topic.import topics + assert_equal 9, result.num_inserts + end + + Topic.import [topic] + assert Topic.find_by_title_and_author_name("The RSpec Book", "David Chelimsky") + end + + it "should not overwrite existing records" do + topic = Generate(:topic, :title => "foobar") + assert_no_difference "Topic.count" do + begin + topic.title = "baz" + Topic.import [topic] + rescue Exception + # no-op + end + end + assert_equal "foobar", topic.reload.title + end + + context "with validation checks turned on" do + it "should import valid models" do + assert_difference "Topic.count", +9 do + result = Topic.import topics, :validate => true + assert_equal 9, result.num_inserts + end + end + + it "should not import invalid models" do + assert_no_difference "Topic.count" do + result = Topic.import invalid_topics, :validate => true + assert_equal 0, result.num_inserts + end + end + end + + context "with validation checks turned off" do + it "should import invalid models" do + assert_difference "Topic.count", +7 do + result = Topic.import invalid_topics, :validate => false + assert_equal 7, result.num_inserts + end + end + end + end + + context "with an array of columns and an array of unsaved model instances" do + let(:topics) { Build(2, :topics) } + + it "should import records populating the supplied columns with the corresponding model instance attributes" do + assert_difference "Topic.count", +2 do + result = Topic.import [:author_name, :title], topics + assert_equal 2, result.num_inserts + end + + # imported topics should be findable by their imported attributes + assert Topic.find_by_author_name(topics.first.author_name) + assert Topic.find_by_author_name(topics.last.author_name) + end + + it "should not populate fields for columns not imported" do + topics.first.author_email_address = "zach.dennis@gmail.com" + assert_difference "Topic.count", +2 do + result = Topic.import [:author_name, :title], topics + assert_equal 2, result.num_inserts + end + + assert !Topic.find_by_author_email_address("zach.dennis@gmail.com") + end + end + + context "ActiveRecord timestamps" do + context "when the timestamps columns are present" do + setup do + Delorean.time_travel_to("5 minutes ago") do + assert_difference "Book.count", +1 do + result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]] + assert_equal 1, result.num_inserts + end + @book = Book.first + end + end + + it "should set the created_at column for new records" do + assert_equal 5.minutes.ago.strftime("%H:%m"), @book.created_at.strftime("%H:%m") + end + + it "should set the created_on column for new records" do + assert_equal 5.minutes.ago.strftime("%H:%m"), @book.created_on.strftime("%H:%m") + end + + it "should set the updated_at column for new records" do + assert_equal 5.minutes.ago.strftime("%H:%m"), @book.updated_at.strftime("%H:%m") + end + + it "should set the updated_on column for new records" do + assert_equal 5.minutes.ago.strftime("%H:%m"), @book.updated_on.strftime("%H:%m") + end + end + + context "when a custom time zone is set" do + setup do + original_timezone = ActiveRecord::Base.default_timezone + ActiveRecord::Base.default_timezone = :utc + Delorean.time_travel_to("5 minutes ago") do + assert_difference "Book.count", +1 do + result = Book.import [:title, :author_name, :publisher], [["LDAP", "Big Bird", "Del Rey"]] + assert_equal 1, result.num_inserts + end + end + ActiveRecord::Base.default_timezone = original_timezone + @book = Book.first + end + + it "should set the created_at column for new records respecting the time zone" do + assert_equal 5.minutes.ago.utc.strftime("%H:%m"), @book.created_at.strftime("%H:%m") + end + + it "should set the created_on column for new records respecting the time zone" do + assert_equal 5.minutes.ago.utc.strftime("%H:%m"), @book.created_on.strftime("%H:%m") + end + + it "should set the updated_at column for new records respecting the time zone" do + assert_equal 5.minutes.ago.utc.strftime("%H:%m"), @book.updated_at.strftime("%H:%m") + end + + it "should set the updated_on column for new records respecting the time zone" do + assert_equal 5.minutes.ago.utc.strftime("%H:%m"), @book.updated_on.strftime("%H:%m") + end + end + end + + context "importing with database reserved words" do + let(:group) { Build(:group, :order => "superx") } + + it "should import just fine" do + assert_difference "Group.count", +1 do + result = Group.import [group] + assert_equal 1, result.num_inserts + end + assert_equal "superx", Group.first.order + end + end + + # # describe "computing insert value sets" do # context "when the max allowed bytes is 33 and the base SQL is 26 bytes" do @@ -111,4 +243,4 @@ end # it "should return 3 value sets when given 1 value sets of 7 bytes a piece" # end # end -# end \ No newline at end of file +end \ No newline at end of file diff --git a/test/models/book.rb b/test/models/book.rb new file mode 100644 index 0000000..9e5ad90 --- /dev/null +++ b/test/models/book.rb @@ -0,0 +1,3 @@ +class Book < ActiveRecord::Base + belongs_to :topic +end diff --git a/test/models/group.rb b/test/models/group.rb new file mode 100644 index 0000000..97cca4f --- /dev/null +++ b/test/models/group.rb @@ -0,0 +1,3 @@ +class Group < ActiveRecord::Base + self.table_name = 'group' +end diff --git a/test/schema/generic_schema.rb b/test/schema/generic_schema.rb index 5f22442..93ee116 100644 --- a/test/schema/generic_schema.rb +++ b/test/schema/generic_schema.rb @@ -11,7 +11,7 @@ ActiveRecord::Schema.define do end create_table :topics, :force=>true do |t| - t.column :title, :string, :null=>false + t.column :title, :string, :null => false t.column :author_name, :string t.column :author_email_address, :string t.column :written_on, :datetime diff --git a/test/support/factories.rb b/test/support/factories.rb new file mode 100644 index 0000000..a7b737a --- /dev/null +++ b/test/support/factories.rb @@ -0,0 +1,13 @@ +Factory.define :group do |m| + m.sequence(:order) { |n| "Order #{n}" } +end + +Factory.define :invalid_topic, :class => "Topic" do |m| + m.sequence(:title){ |n| "Title #{n}"} + m.author_name nil +end + +Factory.define :topic do |m| + m.sequence(:title){ |n| "Title #{n}"} + m.sequence(:author_name){ |n| "Author #{n}"} +end \ No newline at end of file diff --git a/test/support/generate.rb b/test/support/generate.rb new file mode 100644 index 0000000..b624ce2 --- /dev/null +++ b/test/support/generate.rb @@ -0,0 +1,29 @@ +class ActiveSupport::TestCase + def Build(*args) + n = args.shift if args.first.is_a?(Numeric) + factory = args.shift + factory_girl_args = args.shift || {} + + if n + returning Array.new do |collection| + n.times.each { collection << Factory.build(factory.to_s.singularize.to_sym, factory_girl_args) } + end + else + Factory.build(factory.to_s.singularize.to_sym, factory_girl_args) + end + end + + def Generate(*args) + n = args.shift if args.first.is_a?(Numeric) + factory = args.shift + factory_girl_args = args.shift || {} + + if n + returning Array.new do |collection| + n.times.each { collection << Factory.create(factory.to_s.singularize.to_sym, factory_girl_args) } + end + else + Factory.create(factory.to_s.singularize.to_sym, factory_girl_args) + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 405d5d7..5b4423d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -8,6 +8,10 @@ gem "rails", "3.0.0.beta" require "rails" require "rails/test_help" require "active_record/fixtures" +require "active_support/test_case" +require 'active_support/core_ext/object/returning' + +require "delorean" require "ar-extensions" require "logger" @@ -32,7 +36,11 @@ class ActiveSupport::TestCase alias_method :context, :describe def let(name, &blk) - define_method(name, &blk) + values = {} + define_method(name) do + return values[name] if values.has_key?(name) + values[name] = instance_eval(&blk) + end end def it(description, &blk) @@ -46,12 +54,18 @@ def describe(description, &blk) ActiveSupport::TestCase.describe(description, true, &blk) end +# load test helpers +require "rails" +class MyApplication < Rails::Application ; end adapter = "sqlite3" -ActiveRecord::Base.logger = Logger.new("foo.log") +ActiveRecord::Base.logger = Logger.new("log/test.log") ActiveRecord::Base.configurations["test"] = YAML.load(this_dir.join("database.yml").open)[adapter] ActiveRecord::Base.establish_connection "test" +require "factory_girl" +Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each{ |file| require file } + # Load base/generic schema require this_dir.join("schema/version") require this_dir.join("schema/generic_schema") @@ -60,4 +74,5 @@ require this_dir.join("schema/generic_schema") adapter_schema = this_dir.join("schema/#{adapter}_schema") require adapter_schema if File.exists?(adapter_schema) -Dir[File.dirname(__FILE__) + "/models/*.rb"].each{ |file| require file } \ No newline at end of file +Dir[File.dirname(__FILE__) + "/models/*.rb"].each{ |file| require file } +