data-import 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataImport::Runner do
4
+
5
+ context 'with simple definitions' do
6
+ let(:people) { DataImport::Definition.new('People', 'tblPerson', 'people') }
7
+ let(:animals) { DataImport::Definition.new('Animals', 'tblAnimal', 'animals') }
8
+ let(:articles) { DataImport::Definition.new('Articles', 'tblNewsMessage', 'articles') }
9
+ let(:plan) { DataImport::ExecutionPlan.new }
10
+ before do
11
+ plan.add_definition(articles)
12
+ plan.add_definition(people)
13
+ plan.add_definition(animals)
14
+ end
15
+
16
+ subject { DataImport::Runner.new(plan) }
17
+
18
+ it 'runs a set of definitions' do
19
+ articles.should_receive(:run)
20
+ people.should_receive(:run)
21
+ animals.should_receive(:run)
22
+
23
+ subject.run
24
+ end
25
+
26
+ it ":only limits the definitions, which will be run" do
27
+ people.should_receive(:run)
28
+ articles.should_receive(:run)
29
+
30
+ subject.run :only => ['People', 'Articles']
31
+ end
32
+ end
33
+
34
+ context "dependent definitions" do
35
+ it 'executes leaf-definitions first and works to the top' do
36
+ a = DataImport::Definition.new 'A', :source, :target
37
+ b = DataImport::Definition.new 'B', :source, :target
38
+
39
+ a_1 = DataImport::Definition.new 'A1', :source, :target
40
+ a_1.add_dependency('A')
41
+
42
+ ab_1 = DataImport::Definition.new 'A-B-1', :source, :target
43
+ ab_1.add_dependency('A')
44
+ ab_1.add_dependency('B')
45
+
46
+ ab_a1_1 = DataImport::Definition.new 'AB-A1-1', :source, :target
47
+ ab_a1_1.add_dependency('A-B-1')
48
+ ab_a1_1.add_dependency('A1')
49
+
50
+ plan = DataImport::ExecutionPlan.new([ab_a1_1, ab_1, b, a, a_1])
51
+ importer = DataImport::Runner.new(plan)
52
+
53
+ call_order = []
54
+
55
+ a.should_receive(:run) { call_order << :a }
56
+ b.should_receive(:run) { call_order << :b }
57
+ ab_1.should_receive(:run) { call_order << :ab_1 }
58
+ a_1.should_receive(:run) { call_order << :a_1 }
59
+ ab_a1_1.should_receive(:run) { call_order << :ab_a1_1}
60
+
61
+ importer.run
62
+
63
+ call_order.should == [:a, :b, :ab_1, :a_1, :ab_a1_1]
64
+ end
65
+
66
+ it 'handles dependencies correctly when :only is present' do
67
+ a = DataImport::Definition.new 'A', :source, :target
68
+ ab = DataImport::Definition.new 'AB', :source, :target
69
+ ab.add_dependency('A')
70
+ abc = DataImport::Definition.new 'ABC', :source, :target
71
+ abc.add_dependency('AB')
72
+
73
+ plan = DataImport::ExecutionPlan.new([abc, a, ab])
74
+ importer = DataImport::Runner.new(plan)
75
+
76
+ call_order = []
77
+
78
+ a.should_receive(:run) { call_order << :a }
79
+ ab.should_receive(:run) { call_order << :ab }
80
+ abc.should_receive(:run) { call_order << :abc }
81
+
82
+ importer.run :only => ['ABC']
83
+
84
+ call_order.should == [:a, :ab, :abc]
85
+ end
86
+
87
+ it "raises an exception when the dependencies can't be resolved" do
88
+ a = DataImport::Definition.new 'A', :source, :target
89
+ b = DataImport::Definition.new 'B', :source, :target
90
+ a.add_dependency('B')
91
+ b.add_dependency('A')
92
+
93
+ plan = DataImport::ExecutionPlan.new([a, b])
94
+ importer = DataImport::Runner.new(plan)
95
+
96
+ lambda do
97
+ importer.run
98
+ end.should raise_error(RuntimeError, "ciruclar dependencies: 'B' <-> 'A'")
99
+ end
100
+
101
+ it 'raises an error when invalid dependencies are found' do
102
+ a = DataImport::Definition.new 'A', :source, :target
103
+ a.add_dependency('NOT_PRESENT')
104
+
105
+ plan = DataImport::ExecutionPlan.new([a])
106
+ importer = DataImport::Runner.new(plan)
107
+
108
+ lambda do
109
+ importer.run
110
+ end.should raise_error(RuntimeError, "no definition found for 'NOT_PRESENT'")
111
+ end
112
+
113
+ it 'can resolve dependencies which appear to be circular but are not' do
114
+ a = DataImport::Definition.new 'A', :source, :target
115
+ ab = DataImport::Definition.new 'AB', :source, :target
116
+ ab.add_dependency('A')
117
+ aba = DataImport::Definition.new 'AB-A', :source, :target
118
+ aba.add_dependency('AB')
119
+ aba.add_dependency('A')
120
+
121
+ plan = DataImport::ExecutionPlan.new([a, aba, ab])
122
+ importer = DataImport::Runner.new(plan)
123
+
124
+ call_order = []
125
+
126
+ a.should_receive(:run) { call_order << :a }
127
+ ab.should_receive(:run) { call_order << :ab }
128
+ aba.should_receive(:run) { call_order << :aba }
129
+
130
+ importer.run
131
+
132
+ call_order.should == [:a, :ab, :aba]
133
+ end
134
+ end
135
+
136
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataImport do
4
+
5
+ subject { DataImport }
6
+
7
+ describe ".run_definitions!" do
8
+ let(:runner) { stub }
9
+ let(:plan) { DataImport::ExecutionPlan.new(definitions) }
10
+ let(:definitions) { [stub, stub] }
11
+
12
+ it "can execute a configuration file" do
13
+ DataImport::Dsl.should_receive(:evaluate_import_config).with('my_file').and_return(plan)
14
+ DataImport::Runner.should_receive(:new).with(plan).and_return(runner)
15
+ runner.should_receive(:run).with(:only => ['C'])
16
+
17
+ subject.run_config! 'my_file', :only => ['C']
18
+ end
19
+
20
+ it "uses the DataImport::Runner to execute the plan" do
21
+ DataImport::Runner.should_receive(:new).with(plan).and_return(runner)
22
+ runner.should_receive(:run)
23
+
24
+ subject.run_plan!(plan)
25
+ end
26
+
27
+ it "passes options to the runner" do
28
+ DataImport::Runner.should_receive(:new).with(plan).and_return(runner)
29
+ runner.should_receive(:run).with(:only => ['A', 'B'])
30
+
31
+ subject.run_plan!(plan, :only => ['A', 'B'])
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ require 'data-import'
2
+
3
+ describe "before filter" do
4
+
5
+ let(:plan) do
6
+ DataImport::Dsl.define do
7
+ source :sequel, 'sqlite:/'
8
+ target :sequel, 'sqlite:/'
9
+
10
+ before_filter do |row|
11
+ row.each do |k, v|
12
+ row[k] = v.downcase if v.respond_to?(:downcase)
13
+ end
14
+ end
15
+
16
+ source_database.db.create_table :tblUser do
17
+ primary_key :sUserID
18
+ String :strEmail
19
+ String :strUsername
20
+ end
21
+
22
+ target_database.db.create_table :users do
23
+ primary_key :id
24
+ String :email
25
+ String :username
26
+ end
27
+
28
+ import 'Users' do
29
+ from 'tblUser', :primary_key => 'sUserID'
30
+ to 'users'
31
+
32
+ mapping 'sUserID' => 'id'
33
+ mapping 'strEmail' => 'email'
34
+ mapping 'strUsername' => 'username'
35
+ end
36
+ end
37
+ end
38
+
39
+ let(:source) { plan.definitions.first.source_database.db[:tblUser] }
40
+ let(:target) { plan.definitions.first.target_database.db[:users] }
41
+
42
+ before do
43
+ source.insert(:sUserID => 1,
44
+ :strEmail => 'JANE.MEIERS@GMX.NET',
45
+ :strUsername => 'JANEMRS')
46
+
47
+ source.insert(:sUserID => 2,
48
+ :strEmail => 'JOHN.DOE@GMAIL.COM',
49
+ :strUsername => 'JOHN_DOE')
50
+ end
51
+
52
+
53
+ it 'mapps columns to the new schema' do
54
+ DataImport.run_plan!(plan)
55
+ target.to_a.should == [{:id => 1, :email => "jane.meiers@gmx.net", :username => 'janemrs'},
56
+ {:id => 2, :email => "john.doe@gmail.com", :username => 'john_doe'}]
57
+ end
58
+
59
+ end
@@ -0,0 +1,68 @@
1
+ require 'data-import'
2
+
3
+ describe "simple mappings" do
4
+
5
+ let(:plan) do
6
+ DataImport::Dsl.define do
7
+ source :sequel, 'sqlite:/'
8
+ target :sequel, 'sqlite:/'
9
+
10
+ source_database.db.create_table :tblAnimal do
11
+ primary_key :sAnimalID
12
+ String :strAnimalTitleText
13
+ Integer :sAnimalAge
14
+ Integer :sDangerRating
15
+ String :strThreat
16
+ end
17
+
18
+ target_database.db.create_table :animals do
19
+ primary_key :id
20
+ String :name
21
+ Integer :age
22
+ Integer :danger_rating
23
+ end
24
+
25
+ import 'Animals' do
26
+ from 'tblAnimal', :primary_key => 'sAnimalID'
27
+ to 'animals'
28
+
29
+ mapping 'sAnimalID' => 'id'
30
+ mapping 'strAnimalTitleText' => 'name'
31
+ mapping 'sAnimalAge' => 'age'
32
+ mapping 'strThreat' do |context, threat|
33
+ rating = ['none', 'medium', 'big'].index(threat) + 1
34
+ {:danger_rating => rating}
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ let(:source) { plan.definitions.first.source_database.db[:tblAnimal] }
41
+ let(:target) { plan.definitions.first.target_database.db[:animals] }
42
+
43
+ before do
44
+ source.insert(:sAnimalID => 1,
45
+ :strAnimalTitleText => 'Tiger',
46
+ :sAnimalAge => 23,
47
+ :strThreat => 'big')
48
+
49
+ source.insert(:sAnimalID => 1293,
50
+ :strAnimalTitleText => 'Horse',
51
+ :sAnimalAge => 11,
52
+ :strThreat => 'medium')
53
+
54
+ source.insert(:sAnimalID => 99,
55
+ :strAnimalTitleText => 'Cat',
56
+ :sAnimalAge => 5,
57
+ :strThreat => 'none')
58
+ end
59
+
60
+
61
+ it 'mapps columns to the new schema' do
62
+ DataImport.run_plan!(plan)
63
+ target.to_a.should == [{:id => 1, :name => "Tiger", :age => 23, :danger_rating => 3},
64
+ {:id => 99, :name => "Cat", :age => 5, :danger_rating => 1},
65
+ {:id => 1293, :name => "Horse", :age => 11, :danger_rating => 2}]
66
+ end
67
+
68
+ end
@@ -0,0 +1,57 @@
1
+ require 'data-import'
2
+
3
+ describe "update existing records" do
4
+
5
+ let(:plan) do
6
+ DataImport::Dsl.define do
7
+ source :sequel, 'sqlite:/'
8
+ target :sequel, 'sqlite:/'
9
+
10
+ source_database.db.create_table :tblArticleAbout do
11
+ primary_key :sID
12
+ Integer :lArticleId
13
+ String :strWho
14
+ end
15
+
16
+ target_database.db.create_table :articles do
17
+ primary_key :id
18
+ String :title
19
+ String :author
20
+ end
21
+
22
+ import 'Article Authors' do
23
+ from 'tblArticleAbout', :primary_key => 'sID'
24
+ to 'articles', :mode => :update
25
+
26
+ mapping 'lArticleId' => 'id'
27
+ mapping 'strWho' => 'author'
28
+ end
29
+ end
30
+ end
31
+
32
+ let(:source) { plan.definitions.first.source_database.db[:tblArticleAbout] }
33
+ let(:target) { plan.definitions.first.target_database.db[:articles] }
34
+
35
+ before do
36
+ source.insert(:sID => 1,
37
+ :lArticleId => 12,
38
+ :strWho => 'Adam K.')
39
+ source.insert(:sID => 2,
40
+ :lArticleId => 145,
41
+ :strWho => 'James G.')
42
+
43
+ target.insert(:id => 12,
44
+ :title => 'The Book!')
45
+ target.insert(:id => 145,
46
+ :title => 'The other Book.')
47
+
48
+ end
49
+
50
+
51
+ it 'mapps columns to the new schema' do
52
+ DataImport.run_plan!(plan)
53
+ target.to_a.should == [{:id => 12, :title => "The Book!", :author => 'Adam K.'},
54
+ {:id => 145, :title => "The other Book.", :author => 'James G.'}]
55
+ end
56
+
57
+ end
@@ -0,0 +1,106 @@
1
+ # -*- encoding : utf-8 -*-
2
+ #
3
+ # Copyright (c) 2011, Diego Souza
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # * Redistributions of source code must retain the above copyright notice,
10
+ # this list of conditions and the following disclaimer.
11
+ # * Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ # * Neither the name of the <ORGANIZATION> nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software
16
+ # without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ require "time"
30
+ require "rspec/core/formatters/base_formatter"
31
+
32
+ class JUnitFormatter < RSpec::Core::Formatters::BaseFormatter
33
+
34
+ attr_reader :test_results
35
+
36
+ def initialize(output)
37
+ super(output)
38
+ @test_results = { :failures => [], :successes => [] }
39
+ end
40
+
41
+ def example_passed(example)
42
+ super(example)
43
+ @test_results[:successes].push(example)
44
+ end
45
+
46
+ def example_pending(example)
47
+ self.example_failed(example)
48
+ end
49
+
50
+ def example_failed(example)
51
+ super(example)
52
+ @test_results[:failures].push(example)
53
+ end
54
+
55
+ def read_failure(t)
56
+ exception = t.metadata[:execution_result][:exception_encountered] || t.metadata[:execution_result][:exception]
57
+ message = ""
58
+ unless (exception.nil?)
59
+ message = exception.message
60
+ message += "\n"
61
+ message += format_backtrace(exception.backtrace, t).join("\n")
62
+ end
63
+ return(message)
64
+ end
65
+
66
+ def dump_summary(duration, example_count, failure_count, pending_count)
67
+ super(duration, example_count, failure_count, pending_count)
68
+ output.puts("<?xml version=\"1.0\" encoding=\"utf-8\" ?>")
69
+ output.puts("<testsuite errors=\"0\" failures=\"#{failure_count+pending_count}\" tests=\"#{example_count}\" time=\"#{duration}\" timestamp=\"#{Time.now.iso8601}\">")
70
+ output.puts(" <properties />")
71
+ @test_results[:successes].each do |t|
72
+ md = t.metadata
73
+ runtime = md[:execution_result][:run_time]
74
+ output.puts(" <testcase classname=\"#{md[:file_path]}\" name=\"#{xml_escape(md[:full_description])}\" time=\"#{runtime}\" />")
75
+ end
76
+ @test_results[:failures].each do |t|
77
+ md = t.metadata
78
+ runtime = md[:execution_result][:run_time]
79
+ output.puts(" <testcase classname=\"#{md[:file_path]}\" name=\"#{xml_escape(md[:full_description])}\" time=\"#{runtime}\">")
80
+ output.puts(" <failure message=\"failure\" type=\"failure\">")
81
+ output.puts("<![CDATA[ #{read_failure(t)} ]]>")
82
+ output.puts(" </failure>")
83
+ output.puts(" </testcase>")
84
+ end
85
+ output.puts("</testsuite>")
86
+ end
87
+
88
+ def xml_escape(input)
89
+ result = input.dup
90
+
91
+ result.gsub!(/[&<>'"]/) do | match |
92
+ case match
93
+ when '&' then return '&amp;'
94
+ when '<' then return '&lt;'
95
+ when '>' then return '&gt;'
96
+ when "'" then return '&apos;'
97
+ when '"' then return '&quote;'
98
+ end
99
+ end
100
+
101
+ return result
102
+ end
103
+ private :xml_escape
104
+
105
+
106
+ end