importu 0.1.0 → 0.2.0
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.
- checksums.yaml +7 -0
- data/.editorconfig +15 -0
- data/.github/workflows/ci.yml +48 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rubocop.yml +311 -0
- data/.simplecov +14 -0
- data/.yardstick.yml +36 -0
- data/Appraisals +22 -0
- data/CHANGELOG.md +51 -0
- data/CONTRIBUTING.md +86 -0
- data/Gemfile +5 -1
- data/LICENSE +21 -0
- data/README.md +435 -52
- data/Rakefile +71 -0
- data/UPGRADING.md +188 -0
- data/gemfiles/rails_7_2.gemfile +11 -0
- data/gemfiles/rails_7_2.gemfile.lock +268 -0
- data/gemfiles/rails_8_0.gemfile +11 -0
- data/gemfiles/rails_8_0.gemfile.lock +271 -0
- data/gemfiles/rails_8_1.gemfile +11 -0
- data/gemfiles/rails_8_1.gemfile.lock +269 -0
- data/gemfiles/standalone.gemfile +8 -0
- data/gemfiles/standalone.gemfile.lock +197 -0
- data/importu.gemspec +41 -22
- data/lib/importu/backends/active_record.rb +171 -0
- data/lib/importu/backends/middleware/duplicate_manager_proxy.rb +41 -0
- data/lib/importu/backends/middleware/enforce_allowed_actions.rb +52 -0
- data/lib/importu/backends/middleware.rb +11 -0
- data/lib/importu/backends.rb +103 -0
- data/lib/importu/config_dsl.rb +381 -0
- data/lib/importu/converter_context.rb +94 -0
- data/lib/importu/converters.rb +119 -64
- data/lib/importu/definition.rb +23 -0
- data/lib/importu/duplicate_manager.rb +88 -0
- data/lib/importu/exceptions.rb +135 -4
- data/lib/importu/importer.rb +183 -96
- data/lib/importu/record.rb +138 -102
- data/lib/importu/sources/csv.rb +122 -0
- data/lib/importu/sources/json.rb +106 -0
- data/lib/importu/sources/ruby.rb +46 -0
- data/lib/importu/sources/xml.rb +133 -0
- data/lib/importu/sources.rb +13 -0
- data/lib/importu/summary.rb +277 -0
- data/lib/importu/version.rb +3 -1
- data/lib/importu.rb +45 -9
- data/spec/fixtures/books-duplicates/README.md +7 -0
- data/spec/fixtures/books-duplicates/infile.csv +7 -0
- data/spec/fixtures/books-duplicates/model.json +23 -0
- data/spec/fixtures/books-duplicates/summary.json +10 -0
- data/spec/fixtures/books-valid/README.md +13 -0
- data/spec/fixtures/books-valid/infile.csv +4 -0
- data/spec/fixtures/books-valid/infile.json +23 -0
- data/spec/fixtures/books-valid/infile.xml +21 -0
- data/spec/fixtures/books-valid/model.json +23 -0
- data/spec/fixtures/books-valid/record.json +26 -0
- data/spec/fixtures/books-valid/summary.json +8 -0
- data/spec/fixtures/source-empty-file/infile.csv +0 -0
- data/spec/fixtures/source-empty-file/infile.json +0 -0
- data/spec/fixtures/source-empty-file/infile.xml +0 -0
- data/spec/fixtures/source-empty-records/infile.csv +3 -0
- data/spec/fixtures/source-empty-records/infile.json +1 -0
- data/spec/fixtures/source-empty-records/infile.xml +6 -0
- data/spec/fixtures/source-malformed/infile.csv +1 -0
- data/spec/fixtures/source-malformed/infile.json +1 -0
- data/spec/fixtures/source-malformed/infile.xml +3 -0
- data/spec/fixtures/source-no-records/infile.csv +1 -0
- data/spec/fixtures/source-no-records/infile.json +1 -0
- data/spec/fixtures/source-no-records/infile.xml +3 -0
- data/spec/lib/importu/backends/active_record_spec.rb +150 -0
- data/spec/lib/importu/backends/middleware/duplicate_manager_proxy_spec.rb +70 -0
- data/spec/lib/importu/backends/middleware/enforce_allowed_actions_spec.rb +70 -0
- data/spec/lib/importu/backends_spec.rb +170 -0
- data/spec/lib/importu/converters_spec.rb +184 -141
- data/spec/lib/importu/definition_spec.rb +248 -0
- data/spec/lib/importu/duplicate_manager_spec.rb +92 -0
- data/spec/lib/importu/exceptions_spec.rb +69 -16
- data/spec/lib/importu/import_context_spec.rb +199 -0
- data/spec/lib/importu/importer_spec.rb +95 -0
- data/spec/lib/importu/integration_spec.rb +221 -0
- data/spec/lib/importu/record_spec.rb +130 -80
- data/spec/lib/importu/sources/csv_spec.rb +29 -0
- data/spec/lib/importu/sources/importer_source_examples.rb +175 -0
- data/spec/lib/importu/sources/json_spec.rb +29 -0
- data/spec/lib/importu/sources/ruby_spec.rb +102 -0
- data/spec/lib/importu/sources/xml_spec.rb +70 -0
- data/spec/lib/importu/summary_spec.rb +186 -0
- data/spec/spec_helper.rb +91 -7
- data/spec/support/active_record.rb +20 -0
- data/spec/support/book_importer.rb +31 -0
- data/spec/support/dummy_backend.rb +50 -0
- data/spec/support/fixtures_helper.rb +43 -0
- data/spec/support/matchers/delegate_matcher.rb +14 -8
- metadata +173 -100
- data/lib/importu/core_ext/array/deep_freeze.rb +0 -7
- data/lib/importu/core_ext/deep_freeze.rb +0 -3
- data/lib/importu/core_ext/hash/deep_freeze.rb +0 -7
- data/lib/importu/core_ext/object/deep_freeze.rb +0 -6
- data/lib/importu/core_ext.rb +0 -3
- data/lib/importu/dsl.rb +0 -127
- data/lib/importu/importer/csv.rb +0 -52
- data/lib/importu/importer/json.rb +0 -45
- data/lib/importu/importer/xml.rb +0 -55
- data/spec/factories/importer.rb +0 -12
- data/spec/factories/importer_record.rb +0 -13
- data/spec/factories/json_importer.rb +0 -14
- data/spec/factories/xml_importer.rb +0 -12
- data/spec/lib/importu/dsl_spec.rb +0 -26
- data/spec/lib/importu/importer/json_spec.rb +0 -37
- data/spec/lib/importu/importer/xml_spec.rb +0 -14
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
require "importu/backends/active_record"
|
|
5
|
+
require "importu/importer"
|
|
6
|
+
require "importu/sources/csv"
|
|
7
|
+
|
|
8
|
+
RSpec.describe "ActiveRecord Backend", :active_record do
|
|
9
|
+
let(:source) { Importu::Sources::CSV.new(infile("books-valid", :csv)) }
|
|
10
|
+
subject(:importer) { importer_class.new(source) }
|
|
11
|
+
|
|
12
|
+
let!(:model) do
|
|
13
|
+
stub_const("Book", Class.new(ActiveRecord::Base) do
|
|
14
|
+
serialize :authors, type: Array
|
|
15
|
+
validates :title, :authors, :isbn10, :release_date, presence: true
|
|
16
|
+
validates :isbn10, length: { is: 10 }, uniqueness: true
|
|
17
|
+
end)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
let(:importer_class) do
|
|
21
|
+
Class.new(BookImporter) do
|
|
22
|
+
model "Book"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
around(:each) do |example|
|
|
27
|
+
require "database_cleaner-active_record"
|
|
28
|
+
DatabaseCleaner.cleaning { example.run }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#import!" do
|
|
32
|
+
let(:models_json) do
|
|
33
|
+
serialized = Book.all.to_json(except: [:id, :created_at, :updated_at])
|
|
34
|
+
JSON.parse(serialized)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "imports new book records" do
|
|
38
|
+
expect { importer.import! }.to change { Book.count }.by(3)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "correctly summarizes import statistics" do
|
|
42
|
+
summary = importer.import!
|
|
43
|
+
expected_summary_json!("books-valid", summary)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "correctly saves imported data in the model" do
|
|
47
|
+
importer.import!
|
|
48
|
+
expect(models_json).to match_array expected_model_json("books-valid")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context "when definition includes non-abstract fields not on model" do
|
|
52
|
+
let(:importer_class) { Class.new(super()) { fields(:foo, :bar) { "blah" } } }
|
|
53
|
+
|
|
54
|
+
it "raises an UnassignableFields error" do
|
|
55
|
+
expect { importer.import! }.to raise_error(Importu::UnassignableFields)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "when model has validation errors" do
|
|
60
|
+
let(:importer_class) { Class.new(super()) { field(:isbn10) { "foo" } } }
|
|
61
|
+
|
|
62
|
+
it "marks each record creation as invalid" do
|
|
63
|
+
summary = importer.import!
|
|
64
|
+
expect(summary.created).to eq 0
|
|
65
|
+
expect(summary.invalid).to eq 3
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "includes validation errors in summary" do
|
|
69
|
+
summary = importer.import!
|
|
70
|
+
expect(summary.validation_errors.any? {|msg, _| msg =~ /isbn10/i }).to be true
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context "when creating records" do
|
|
75
|
+
context "when create actions are not allowed" do
|
|
76
|
+
let(:importer_class) { Class.new(super()) { allow_actions :update } }
|
|
77
|
+
|
|
78
|
+
it "marks each record creation as inavlid" do
|
|
79
|
+
summary = importer.import!
|
|
80
|
+
expect(summary.created).to eq 0
|
|
81
|
+
expect(summary.invalid).to eq 3
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
context "when duplicate records in source file" do
|
|
86
|
+
let(:source) { Importu::Sources::CSV.new(infile("books-duplicates", :csv)) }
|
|
87
|
+
|
|
88
|
+
it "marks the three duplicate records as invalid" do
|
|
89
|
+
summary = importer.import!
|
|
90
|
+
expected_summary_json!("books-duplicates", summary)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context "when updating records" do
|
|
96
|
+
before { importer.import! }
|
|
97
|
+
|
|
98
|
+
context "and there are no changes" do
|
|
99
|
+
it "marks each record as unchanged" do
|
|
100
|
+
summary = importer.import!
|
|
101
|
+
expect(summary.unchanged).to eq 3
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context "when updates actions are not allowed" do
|
|
106
|
+
let(:importer_class) { Class.new(super()) { allow_actions :create } }
|
|
107
|
+
|
|
108
|
+
it "marks each record update as inavlid" do
|
|
109
|
+
summary = importer.import!
|
|
110
|
+
expect(summary.updated).to eq 0
|
|
111
|
+
expect(summary.invalid).to eq 3
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context "when a find_by block is used" do
|
|
116
|
+
let(:importer_class) do
|
|
117
|
+
Class.new(super()) do
|
|
118
|
+
find_by do |record|
|
|
119
|
+
find_by(title: record[:title])
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "executes the find_by block in context of the model" do
|
|
125
|
+
expect(Book)
|
|
126
|
+
.to receive(:find_by).with(title: anything)
|
|
127
|
+
.at_least(3).times
|
|
128
|
+
.and_call_original
|
|
129
|
+
|
|
130
|
+
summary = importer.import!
|
|
131
|
+
expect(summary.unchanged).to eq 3
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context "when a before_save callback is defined" do
|
|
137
|
+
let(:importer_class) do
|
|
138
|
+
Class.new(super()) do
|
|
139
|
+
before_save { object.title = object.title.upcase }
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "runs callback before saving" do
|
|
144
|
+
importer.import!
|
|
145
|
+
Book.first.title == Book.first.title.upcase
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
require "importu/backends/middleware/duplicate_manager_proxy"
|
|
5
|
+
require "importu/definition"
|
|
6
|
+
|
|
7
|
+
RSpec.describe Importu::Backends::Middleware::DuplicateManagerProxy do
|
|
8
|
+
subject(:middleware) { described_class.new(dummy_backend, **backend_config) }
|
|
9
|
+
|
|
10
|
+
let(:dummy_backend) { DummyBackend.new(**backend_config) }
|
|
11
|
+
let(:backend_config) { definition.config[:backend] }
|
|
12
|
+
|
|
13
|
+
let(:definition) do
|
|
14
|
+
Class.new(Importu::Definition) do
|
|
15
|
+
fields :foo, :bar, :baz
|
|
16
|
+
find_by :foo, :bar
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#create" do
|
|
21
|
+
it "returns the status and object from the backend" do
|
|
22
|
+
status, object = middleware.create(foo: 1, bar: 1, baz: 1)
|
|
23
|
+
expect(status).to eq :created
|
|
24
|
+
expect(object).to include(foo: 1, bar: 1, baz: 1)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "perform duplicate detection on the record" do
|
|
28
|
+
middleware.create(foo: 1, bar: 1, baz: 1)
|
|
29
|
+
expect { middleware.create(foo: 2, bar: 1, baz: 2) } # :baz is a dupe find_by
|
|
30
|
+
.to raise_error(Importu::DuplicateRecord)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "records created object for subsequent dupe detection" do
|
|
34
|
+
middleware.create(foo: 1, bar: 1, baz: 1)
|
|
35
|
+
expect { middleware.create(foo: 2, bar: 1, baz: 2) }
|
|
36
|
+
.to raise_error(Importu::DuplicateRecord)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "#update" do
|
|
41
|
+
it "returns the status and object from the backend" do
|
|
42
|
+
_, object = dummy_backend.create(foo: 1, bar: 1, baz: 1)
|
|
43
|
+
status, new_object = middleware.update({foo: 2, bar: 2, baz: 2}, object)
|
|
44
|
+
|
|
45
|
+
expect(status).to eq :updated
|
|
46
|
+
expect(new_object).to include(foo: 2, bar: 2, baz: 2)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "perform duplicate detection on the record" do
|
|
50
|
+
_status, object = middleware.create(foo: 1, bar: 1, baz: 1)
|
|
51
|
+
|
|
52
|
+
expect(dummy_backend).to_not receive(:update)
|
|
53
|
+
expect { middleware.update({foo: 1, bar: 2, baz: 2}, object) } # :foo is a dupe
|
|
54
|
+
.to raise_error(Importu::DuplicateRecord)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "performs duplicate detection on object" do
|
|
58
|
+
# Existing db record, never accessed through dupe detector
|
|
59
|
+
_status, object = dummy_backend.create(foo: 1, bar: 1, baz: 1)
|
|
60
|
+
|
|
61
|
+
# Update via :foo, first encounter of k/v and first encounter of object
|
|
62
|
+
expect { middleware.update({foo: 1, bar: 2, baz: 3}, object) }.to_not raise_error
|
|
63
|
+
|
|
64
|
+
# Update via :bar, first encounter of k/v, but second encounter of object
|
|
65
|
+
expect { middleware.update({foo: 2, bar: 1, baz: 3}, object) }
|
|
66
|
+
.to raise_error(Importu::DuplicateRecord)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
require "importu/backends/middleware/enforce_allowed_actions"
|
|
5
|
+
require "importu/definition"
|
|
6
|
+
|
|
7
|
+
RSpec.describe Importu::Backends::Middleware::EnforceAllowedActions do
|
|
8
|
+
subject(:middleware) { described_class.new(dummy_backend, **backend_config) }
|
|
9
|
+
|
|
10
|
+
let(:dummy_backend) { DummyBackend.new(**backend_config) }
|
|
11
|
+
let(:backend_config) { definition.config[:backend] }
|
|
12
|
+
|
|
13
|
+
let(:definition) do
|
|
14
|
+
Class.new(Importu::Definition) do
|
|
15
|
+
allow_actions nil
|
|
16
|
+
fields :foo, :bar, :baz
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "#create" do
|
|
21
|
+
context "when :create action is allowed" do
|
|
22
|
+
let(:definition) { Class.new(super()) { allow_actions :create } }
|
|
23
|
+
|
|
24
|
+
it "returns the status and object from the backend" do
|
|
25
|
+
status, object = middleware.create(foo: 1, bar: 1, baz: 1)
|
|
26
|
+
expect(status).to eq :created
|
|
27
|
+
expect(object).to include(foo: 1, bar: 1, baz: 1)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "when :create action is not allowed" do
|
|
32
|
+
it "raises an InvalidError exception" do
|
|
33
|
+
expect { middleware.create(foo: 1, bar: 1, baz: 1) }
|
|
34
|
+
.to raise_error(Importu::InvalidRecord)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "includes guidance in the error message" do
|
|
38
|
+
expect { middleware.create(foo: 1, bar: 1, baz: 1) }
|
|
39
|
+
.to raise_error(/allow_actions :create/)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "#update" do
|
|
45
|
+
let(:object) { _, object = dummy_backend.create(foo: 1, bar: 1, baz: 1); object }
|
|
46
|
+
|
|
47
|
+
context "when :update action is allowed" do
|
|
48
|
+
let(:definition) { Class.new(super()) { allow_actions :update } }
|
|
49
|
+
|
|
50
|
+
it "returns the status and object from the backend" do
|
|
51
|
+
status, new_object = middleware.update({foo: 2, bar: 2, baz: 2}, object)
|
|
52
|
+
expect(status).to eq :updated
|
|
53
|
+
expect(new_object).to include(foo: 2, bar: 2, baz: 2)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "when :update action is not allowed" do
|
|
58
|
+
it "raises an InvalidError exception" do
|
|
59
|
+
expect { middleware.update({foo: 2}, object) }
|
|
60
|
+
.to raise_error(Importu::InvalidRecord)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "includes guidance in the error message" do
|
|
64
|
+
expect { middleware.update({foo: 2}, object) }
|
|
65
|
+
.to raise_error(/allow_actions :create, :update/)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
require "importu/backends"
|
|
5
|
+
require "importu/definition"
|
|
6
|
+
|
|
7
|
+
RSpec.describe Importu::Backends do
|
|
8
|
+
subject(:registry) { described_class.new }
|
|
9
|
+
|
|
10
|
+
describe ".registry" do
|
|
11
|
+
it "returns a registry singleton" do
|
|
12
|
+
expect(described_class.registry).to be described_class.registry
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "returns a registry-like object" do
|
|
16
|
+
expect(described_class.registry).to respond_to("register")
|
|
17
|
+
expect(described_class.registry).to respond_to("lookup")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "#from_config!" do
|
|
22
|
+
let!(:model) { stub_const("MyModelGuest", Class.new) }
|
|
23
|
+
let(:config) { definition.config[:backend] }
|
|
24
|
+
|
|
25
|
+
context "when a model backend is specified" do
|
|
26
|
+
let(:definition) do
|
|
27
|
+
Class.new(Importu::Definition) do
|
|
28
|
+
model "MyModelGuest", backend: :cherry_scones
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "and the backend is registered" do
|
|
33
|
+
let!(:backend) { registry.register(:cherry_scones, Class.new) }
|
|
34
|
+
|
|
35
|
+
it "returns the backend" do
|
|
36
|
+
expect(registry.from_config!(**config)).to eq backend
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "and the backend is not registered" do
|
|
41
|
+
it "raises a BackendNotRegistered error" do
|
|
42
|
+
expect { registry.from_config!(**config) }
|
|
43
|
+
.to raise_error(Importu::BackendNotRegistered)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context "when backend is :auto" do
|
|
49
|
+
let(:supported) { Class.new { def self.supported_by_model?(*); true; end } }
|
|
50
|
+
|
|
51
|
+
let(:definition) do
|
|
52
|
+
Class.new(Importu::Definition) do
|
|
53
|
+
model "MyModelGuest", backend: :auto
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
before { registry.register(:backend1, supported) }
|
|
58
|
+
|
|
59
|
+
it "auto-detects the backend" do
|
|
60
|
+
expect(registry.from_config!(**config)).to eq supported
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context "when a model backend is not specified" do
|
|
65
|
+
let(:supported) { Class.new { def self.supported_by_model?(*); true; end } }
|
|
66
|
+
let(:unsupported) { Class.new { def self.supported_by_model?(*); false; end } }
|
|
67
|
+
|
|
68
|
+
let(:definition) do
|
|
69
|
+
Class.new(Importu::Definition) do
|
|
70
|
+
model "MyModelGuest"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context "and no backends support the model" do
|
|
75
|
+
before { registry.register(:backend1, unsupported) }
|
|
76
|
+
|
|
77
|
+
it "raises a BackendMatch error" do
|
|
78
|
+
expect { registry.from_config!(**config) }
|
|
79
|
+
.to raise_error(Importu::BackendMatchError)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context "and exactly one backend supports the model" do
|
|
84
|
+
before do
|
|
85
|
+
registry.register(:backend1, unsupported)
|
|
86
|
+
registry.register(:backend2, supported)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "returns the supported backend" do
|
|
90
|
+
expect(registry.from_config!(**config)).to eq supported
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "and multiple backends support the model" do
|
|
95
|
+
before do
|
|
96
|
+
registry.register(:backend1, unsupported)
|
|
97
|
+
registry.register(:backend2, supported)
|
|
98
|
+
registry.register(:backend3, supported)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "raises a BackendMatch error" do
|
|
102
|
+
expect { registry.from_config!(**config) }
|
|
103
|
+
.to raise_error(Importu::BackendMatchError)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context "and a backend raises an exception during checking" do
|
|
108
|
+
let(:broken) { Class.new { def self.supported_by_model?(*); raise :hell; end } }
|
|
109
|
+
|
|
110
|
+
before do
|
|
111
|
+
registry.register(:backend1, broken)
|
|
112
|
+
registry.register(:backend2, unsupported)
|
|
113
|
+
registry.register(:backend3, supported)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "ignores the backend" do
|
|
117
|
+
expect(registry.from_config!(**config)).to eq supported
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "#lookup" do
|
|
124
|
+
let(:marvelous_impl) { Class.new }
|
|
125
|
+
before { registry.register(:marvelous, marvelous_impl) }
|
|
126
|
+
|
|
127
|
+
it "raises a BackendNotRegistered exception if backend not found" do
|
|
128
|
+
expect { registry.lookup(:foo) }
|
|
129
|
+
.to raise_error(Importu::BackendNotRegistered)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "supports lookups by symbol-based name" do
|
|
133
|
+
expect(registry.lookup(:marvelous)).to eq marvelous_impl
|
|
134
|
+
end
|
|
135
|
+
it "supports lookups by string-based name" do
|
|
136
|
+
expect(registry.lookup("marvelous")).to eq marvelous_impl
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe "#names" do
|
|
141
|
+
it "returns the names of all registered backends" do
|
|
142
|
+
registry.register(:backend1, Class.new)
|
|
143
|
+
registry.register(:backend2, Class.new)
|
|
144
|
+
registry.register(:backend3, Class.new)
|
|
145
|
+
expect(registry.names).to include(:backend1, :backend2, :backend3)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "#register" do
|
|
150
|
+
let(:backend1) { Class.new }
|
|
151
|
+
let(:backend2) { Class.new }
|
|
152
|
+
|
|
153
|
+
it "registers the backend" do
|
|
154
|
+
registry.register(:backend1, backend1)
|
|
155
|
+
expect(registry.lookup(:backend1)).to eq backend1
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "returns the backend that was registered" do
|
|
159
|
+
expect(registry.register(:backend1, backend1)).to eq backend1
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "allows registering multiple backends" do
|
|
163
|
+
registry.register(:backend1, backend1)
|
|
164
|
+
registry.register(:backend2, backend2)
|
|
165
|
+
expect(registry.lookup(:backend1)).to eq backend1
|
|
166
|
+
expect(registry.lookup(:backend2)).to eq backend2
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|