csv_model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,297 @@
1
+ require 'spec_helper'
2
+
3
+ describe CSVModel::ObjectWithStatusSnapshot do
4
+ let(:model) { double("model", changed?: false, marked_for_destruction?: false, new_record?: false, valid?: false) }
5
+ let(:subject) { described_class.new(model) }
6
+
7
+ describe "#assign_attributes" do
8
+ it "does not raise when model is nil" do
9
+ subject = described_class.new(nil)
10
+ expect { subject.assign_attributes({}) }.to_not raise_exception
11
+ end
12
+ end
13
+
14
+ describe "#errors" do
15
+ it "does not raise when model is nil" do
16
+ subject = described_class.new(nil)
17
+ expect { subject.errors }.to_not raise_exception
18
+ end
19
+
20
+ it "returns an error when model is nil" do
21
+ subject = described_class.new(nil)
22
+ expect(subject.errors).to eq(["Record could not be created or updated"])
23
+ end
24
+
25
+ it "delegates to errors when model is not nil" do
26
+ model = double("model", errors: :some_errors)
27
+ subject = described_class.new(model)
28
+ expect(subject.errors).to eq(:some_errors)
29
+ end
30
+
31
+ it "uses to errors#full_messages when available" do
32
+ errors = double(full_messages: :some_errors)
33
+ model = double("model", errors: errors)
34
+ subject = described_class.new(model)
35
+ expect(subject.errors).to eq(:some_errors)
36
+ end
37
+ end
38
+
39
+ describe "#mark_as_duplicate" do
40
+ describe "internals" do
41
+ it "sets is_duplicate flag to true" do
42
+ subject.mark_as_duplicate
43
+ expect(subject.instance_variable_get("@is_duplicate")).to eq(true)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#save" do
49
+ context "with an invalid model" do
50
+ let(:model) { double("model", changed?: true, marked_for_destruction?: false, new_record?: true, valid?: false) }
51
+
52
+ it "does not calls save on underlying model" do
53
+ expect(model).to_not receive(:save)
54
+ subject.save
55
+ end
56
+ end
57
+
58
+ context "on a normal-run with an editable, valid model" do
59
+ let(:model) { double("model", changed?: true, marked_for_destruction?: false, new_record?: false, valid?: true) }
60
+
61
+ it "calls save on underlying model" do
62
+ expect(model).to receive(:save)
63
+ subject.save
64
+ end
65
+ end
66
+
67
+ context "on a normal-run with an editable, valid model that is marked for destruction" do
68
+ let(:model) { double("model", changed?: true, marked_for_destruction?: true, new_record?: false, valid?: true) }
69
+
70
+ it "calls destroy on underlying model" do
71
+ expect(model).to receive(:destroy)
72
+ subject.save
73
+ end
74
+ end
75
+
76
+ describe "internals" do
77
+ before do
78
+ allow(model).to receive(:valid?).and_return(true)
79
+ allow(model).to receive(:save).and_return(true)
80
+ subject.save
81
+ end
82
+
83
+ it "sets was_saved flag to true when editable and valid" do
84
+ allow(model).to receive(:valid?).and_return(true)
85
+ subject.save(dry_run: true)
86
+ expect(subject.instance_variable_get("@was_saved")).to eq(true)
87
+ end
88
+
89
+ it "sets was_saved flag to false when not editable" do
90
+ allow(model).to receive(:editable?).and_return(false)
91
+ allow(model).to receive(:valid?).and_return(true)
92
+ subject.save(dry_run: true)
93
+ expect(subject.instance_variable_get("@was_saved")).to eq(false)
94
+ end
95
+
96
+ it "sets was_saved flag to false when not valid" do
97
+ allow(model).to receive(:valid?).and_return(false)
98
+ subject.save(dry_run: true)
99
+ expect(subject.instance_variable_get("@was_saved")).to eq(false)
100
+ end
101
+
102
+ it "capture dry run flag to false" do
103
+ expect(subject.instance_variable_get("@is_dry_run")).to eq(false)
104
+ end
105
+
106
+ it "captures changed flag" do
107
+ expect(subject.instance_variable_get("@was_changed")).to eq(false)
108
+ end
109
+
110
+ it "captures deleted flag" do
111
+ expect(subject.instance_variable_get("@was_deleted")).to eq(false)
112
+ end
113
+
114
+ it "captures editable flag" do
115
+ expect(subject.instance_variable_get("@was_editable")).to eq(true)
116
+ end
117
+
118
+ it "captures new flag" do
119
+ expect(subject.instance_variable_get("@was_new")).to eq(false)
120
+ end
121
+
122
+ it "captures save flag" do
123
+ expect(subject.instance_variable_get("@was_saved")).to eq(true)
124
+ end
125
+
126
+ it "captures valid flag" do
127
+ expect(subject.instance_variable_get("@was_valid")).to eq(true)
128
+ end
129
+ end
130
+ end
131
+
132
+ describe "#status" do
133
+ it "is ERROR_ON_READ when model is nil" do
134
+ subject = described_class.new(nil)
135
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_READ)
136
+ end
137
+
138
+ context "during a dry-run" do
139
+ it "is DUPLICATE when record is marked as a duplicate" do
140
+ subject.mark_as_duplicate
141
+ subject.save(dry_run: true)
142
+ expect(subject.status).to eq(CSVModel::RecordStatus::DUPLICATE)
143
+ end
144
+
145
+ it "is NOT_CHANGED when record exists and was not changed" do
146
+ subject.save(dry_run: true)
147
+ expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
148
+ end
149
+
150
+ it "is CREATE when record was new and valid" do
151
+ expect(model).to receive(:new_record?).and_return(true)
152
+ expect(model).to receive(:valid?).and_return(true)
153
+ subject.save(dry_run: true)
154
+ expect(subject.status).to eq(CSVModel::RecordStatus::CREATE)
155
+ end
156
+
157
+ it "is ERROR_ON_CREATE when record was new and not valid" do
158
+ expect(model).to receive(:new_record?).and_return(true)
159
+ subject.save(dry_run: true)
160
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_CREATE)
161
+ end
162
+
163
+ it "is DELETE when record exists and is marked for destruction" do
164
+ expect(model).to receive(:marked_for_destruction?).and_return(true)
165
+ subject.save(dry_run: true)
166
+ expect(subject.status).to eq(CSVModel::RecordStatus::DELETE)
167
+ end
168
+
169
+ it "is ERROR_ON_DELETE when record was new and marked for destruction" do
170
+ expect(model).to receive(:new_record?).and_return(true)
171
+ expect(model).to receive(:marked_for_destruction?).and_return(true)
172
+ subject.save(dry_run: true)
173
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_DELETE)
174
+ end
175
+
176
+ it "is UPDATE when record exists, was changed and was valid" do
177
+ expect(model).to receive(:changed?).and_return(true)
178
+ expect(model).to receive(:valid?).and_return(true)
179
+ subject.save(dry_run: true)
180
+ expect(subject.status).to eq(CSVModel::RecordStatus::UPDATE)
181
+ end
182
+
183
+ it "is ERROR_ON_UPDATE when record exists but was changed and not editable" do
184
+ expect(model).to receive(:changed?).and_return(true)
185
+ expect(model).to receive(:editable?).and_return(false)
186
+ subject.save(dry_run: true)
187
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
188
+ end
189
+
190
+ it "is ERROR_ON_UPDATE when record exists, was changed and was not valid" do
191
+ expect(model).to receive(:changed?).and_return(true)
192
+ subject.save(dry_run: true)
193
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
194
+ end
195
+ end
196
+
197
+ context "during normal processing" do
198
+ it "is not DUPLICATE when record is marked as a duplicate" do
199
+ subject.mark_as_duplicate
200
+ subject.save
201
+ expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
202
+ end
203
+
204
+ it "is NOT_CHANGED when record exists and is not changed" do
205
+ subject.save
206
+ expect(subject.status).to eq(CSVModel::RecordStatus::NOT_CHANGED)
207
+ end
208
+
209
+ it "is CREATE when record is new, valid and saves" do
210
+ expect(model).to receive(:new_record?).and_return(true)
211
+ expect(model).to receive(:valid?).and_return(true)
212
+ expect(model).to receive(:save).and_return(true)
213
+ subject.save
214
+ expect(subject.status).to eq(CSVModel::RecordStatus::CREATE)
215
+ end
216
+
217
+ it "is ERROR_ON_CREATE when record is new and not valid" do
218
+ expect(model).to receive(:new_record?).and_return(true)
219
+ subject.save
220
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_CREATE)
221
+ end
222
+
223
+ it "is DELETE when record exists, is marked for destruction and saves" do
224
+ expect(model).to receive(:marked_for_destruction?).and_return(true)
225
+ subject.save
226
+ expect(subject.status).to eq(CSVModel::RecordStatus::DELETE)
227
+ end
228
+
229
+ it "is ERROR_ON_DELETE when record is new and marked for destruction" do
230
+ expect(model).to receive(:new_record?).and_return(true)
231
+ expect(model).to receive(:marked_for_destruction?).and_return(true)
232
+ subject.save
233
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_DELETE)
234
+ end
235
+
236
+ it "is UPDATE when record exists, is changed, valid and saves" do
237
+ expect(model).to receive(:changed?).and_return(true)
238
+ expect(model).to receive(:save).and_return(true)
239
+ expect(model).to receive(:valid?).and_return(true)
240
+ subject.save
241
+ expect(subject.status).to eq(CSVModel::RecordStatus::UPDATE)
242
+ end
243
+
244
+ it "is ERROR_ON_UPDATE when record exists, is changed and is not valid" do
245
+ expect(model).to receive(:changed?).and_return(true)
246
+ subject.save
247
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
248
+ end
249
+
250
+ it "is ERROR_ON_UPDATE when record exists, is changed, is valid and does not save" do
251
+ expect(model).to receive(:changed?).and_return(true)
252
+ subject.save
253
+ expect(subject.status).to eq(CSVModel::RecordStatus::ERROR_ON_UPDATE)
254
+ end
255
+ end
256
+ end
257
+
258
+ describe "#valid?" do
259
+ it "does not raise when model is nil" do
260
+ subject = described_class.new(nil)
261
+ expect { subject.valid? }.to_not raise_exception
262
+ end
263
+ end
264
+
265
+ describe "internals" do
266
+ describe "#is_duplicate?" do
267
+ it "doesn't respond to is_duplicate?" do
268
+ expect(subject.respond_to?(:is_duplicate?)).to eq(false)
269
+ end
270
+
271
+ it "defaults to nil" do
272
+ expect(subject.send(:is_duplicate?)).to eq(nil)
273
+ end
274
+
275
+ it "displays @is_duplicate" do
276
+ subject.instance_variable_set("@is_duplicate", true)
277
+ expect(subject.send(:is_duplicate?)).to eq(true)
278
+ end
279
+ end
280
+
281
+ describe "#was_saved?" do
282
+ it "doesn't respond to was_saved?" do
283
+ expect(subject.respond_to?(:was_saved?)).to eq(false)
284
+ end
285
+
286
+ it "defaults to nil" do
287
+ expect(subject.send(:was_saved?)).to eq(nil)
288
+ end
289
+
290
+ it "displays @was_saved" do
291
+ subject.instance_variable_set("@was_saved", true)
292
+ expect(subject.send(:was_saved?)).to eq(true)
293
+ end
294
+ end
295
+ end
296
+
297
+ end
@@ -0,0 +1,351 @@
1
+ require 'spec_helper'
2
+
3
+ describe CSVModel::Row do
4
+
5
+ let(:header) { double("header") }
6
+ let(:column) { double("column", key: "column one", model_attribute: :column_one, name: "Column One") }
7
+ let(:columns) { [column] }
8
+ let(:primary_key_columns) { [] }
9
+
10
+ let(:data) { ["Value One"] }
11
+ let(:subject) { described_class.new(header, data) }
12
+
13
+ before do
14
+ allow(header).to receive(:columns).and_return(columns)
15
+ allow(header).to receive(:column_index).and_return(nil)
16
+ allow(header).to receive(:column_index).with("column one").and_return(0)
17
+ allow(header).to receive(:primary_key_columns).and_return(primary_key_columns)
18
+ end
19
+
20
+ describe "#errors" do
21
+ context "when no errors" do
22
+ let(:model) { double("model") }
23
+ let(:subject) { described_class.new(header, data, model: model) }
24
+ let(:model_instance) { double("model-instance", changed?: false, marked_for_destruction?: false, new_record?: false, valid?: true) }
25
+
26
+ before do
27
+ allow(model).to receive(:find_row_model).and_return(model_instance)
28
+ allow(model_instance).to receive(:save)
29
+ end
30
+
31
+ it "returns an empty array when no errors" do
32
+ expect(subject.errors).to eq([])
33
+ end
34
+ end
35
+
36
+ context "when subject is a duplicate" do
37
+ before do
38
+ subject.mark_as_duplicate
39
+ end
40
+
41
+ context "during normal processing" do
42
+ it "does not return an error" do
43
+ expect(subject.errors).to_not include("Duplicate row")
44
+ end
45
+ end
46
+
47
+ context "during a dry-run" do
48
+ let(:subject) { described_class.new(header, data, dry_run: true) }
49
+
50
+ it "returns an error" do
51
+ expect(subject.errors).to include("Duplicate row")
52
+ end
53
+
54
+ context "when the row has a primary key" do
55
+ let(:primary_key_columns) { [column] }
56
+
57
+ it "returns an error specific to the column" do
58
+ expect(subject.errors).to include("Duplicate Column One")
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when the model instance has errors" do
65
+ let(:model) { double("model") }
66
+ let(:subject) { described_class.new(header, data, model: model) }
67
+ let(:model_instance) { double("model-instance", changed?: false, errors: errors, marked_for_destruction?: false, new_record?: false, valid?: false) }
68
+ let(:errors) { ["Message one", "Message two"] }
69
+
70
+ before do
71
+ allow(model).to receive(:find_row_model).and_return(model_instance)
72
+ end
73
+
74
+ it "includes the model instance errors" do
75
+ expect(subject.errors).to eq(errors)
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "#index, #[]" do
81
+ it "returns nil when index is before than first element" do
82
+ expect(subject.index(-1)).to eq(nil)
83
+ expect(subject[-1]).to eq(nil)
84
+ end
85
+
86
+ it "returns nil when index is after than the last element" do
87
+ expect(subject.index(data.size)).to eq(nil)
88
+ expect(subject[data.size]).to eq(nil)
89
+ end
90
+
91
+ it "returns the value at the specified index" do
92
+ expect(subject.index(0)).to eq("Value One")
93
+ expect(subject[0]).to eq("Value One")
94
+ end
95
+
96
+ it "returns the value for the specified column key" do
97
+ expect(subject.index("column one")).to eq("Value One")
98
+ expect(subject["column one"]).to eq("Value One")
99
+ end
100
+
101
+ it "returns nil when the specified column key not exist" do
102
+ expect(subject.index("column two")).to eq(nil)
103
+ expect(subject["column two"]).to eq(nil)
104
+ end
105
+ end
106
+
107
+ describe "#key" do
108
+ it "returns the entire row if no primary key is defined" do
109
+ expect(subject.key).to eq(data)
110
+ end
111
+
112
+ context "when the row has a single primary key column" do
113
+ let(:primary_key_columns) { [column] }
114
+
115
+ it "returns the value for the key column" do
116
+ expect(subject.key).to eq(data.first)
117
+ end
118
+ end
119
+
120
+ context "when the row has a composite key" do
121
+ let(:columns) { [
122
+ double("column", key: "column one"),
123
+ double("column", key: "column two"),
124
+ double("column", key: "column three")
125
+ ] }
126
+ let(:primary_key_columns) { [
127
+ double("column", key: "column one"),
128
+ double("column", key: "column two")
129
+ ] }
130
+ let(:data) { ["Value One", "Value Two", "Value Three"] }
131
+
132
+ before do
133
+ allow(header).to receive(:column_index).with("column two").and_return(1)
134
+ end
135
+
136
+ it "returns the value for the key columns" do
137
+ expect(subject.key).to eq(["Value One", "Value Two"])
138
+ end
139
+ end
140
+ end
141
+
142
+ describe "#mark_as_duplicate" do
143
+ it "returns true" do
144
+ expect(subject.mark_as_duplicate).to eq(true)
145
+ end
146
+ end
147
+
148
+ describe "#marked_as_duplicate?" do
149
+ it "returns false when not marked as duplicate" do
150
+ expect(subject.marked_as_duplicate?).to eq(false)
151
+ end
152
+
153
+ it "returns true when marked as duplicate" do
154
+ subject.mark_as_duplicate
155
+ expect(subject.marked_as_duplicate?).to eq(true)
156
+ end
157
+ end
158
+
159
+ describe "#status" do
160
+ let(:model) { double("model") }
161
+ let(:wrapper) { double("wrapper", status: :some_status) }
162
+
163
+ before do
164
+ allow(subject).to receive(:find).and_return(model)
165
+ allow(wrapper).to receive(:assign_attributes)
166
+ allow(wrapper).to receive(:save)
167
+ end
168
+
169
+ it "is delegates status to the model" do
170
+ expect(CSVModel::ObjectWithStatusSnapshot).to receive(:new).and_return(wrapper)
171
+ expect(subject.status).to eq(:some_status)
172
+ end
173
+ end
174
+
175
+ describe "#valid?" do
176
+ it "returns true if no errors" do
177
+ expect(subject).to receive(:errors).and_return([])
178
+ expect(subject.valid?).to eq(true)
179
+ end
180
+
181
+ it "returns false if errors" do
182
+ expect(subject).to receive(:errors).and_return(["error"])
183
+ expect(subject.valid?).to eq(false)
184
+ end
185
+ end
186
+
187
+ describe "internals" do
188
+ describe "#all_attributes" do
189
+ it "doesn't respond to all_attributes" do
190
+ expect(subject.respond_to?(:all_attributes)).to eq(false)
191
+ end
192
+
193
+ it "returns all attributes and values" do
194
+ expect(subject.send(:all_attributes)).to eq(column_one: "Value One")
195
+ end
196
+
197
+ context "with multiple columns" do
198
+ let(:columns) { [
199
+ double("column", key: "column one", model_attribute: :column_one),
200
+ double("column", key: "column two", model_attribute: :column_two),
201
+ double("column", key: "column three", model_attribute: :column_three)
202
+ ] }
203
+ let(:data) { ["Value One", "", "Value Three"] }
204
+
205
+ before do
206
+ allow(header).to receive(:column_index).with("column two").and_return(1)
207
+ allow(header).to receive(:column_index).with("column three").and_return(2)
208
+ end
209
+
210
+ it "returns all attributes and values" do
211
+ expect(subject.send(:all_attributes)).to eq({ column_one: "Value One", column_two: "", column_three: "Value Three" })
212
+ end
213
+ end
214
+ end
215
+
216
+ describe "#inherit_or_delegate" do
217
+ let(:model) { double("model") }
218
+ let(:subject) { described_class.new(header, data, model: model) }
219
+
220
+ it "doesn't respond to inherit_or_delegate" do
221
+ expect(subject.respond_to?(:inherit_or_delegate)).to eq(false)
222
+ end
223
+
224
+ it "returns nil when no method invoked" do
225
+ expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(nil)
226
+ end
227
+
228
+ it "invokes internal method if method is defined" do
229
+ allow(subject).to receive(:respond_to?).with(:some_method).and_return(true)
230
+ allow(subject).to receive(:respond_to?).with(:some_method, false).and_return(true)
231
+ expect(subject).to receive(:some_method).with(:multiple, :args).and_return(:some_value)
232
+ expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(:some_value)
233
+ end
234
+
235
+ it "invokes delegate method if delegate exists and internal method is not defined" do
236
+ expect(model).to receive(:some_method).with(:multiple, :args).and_return(:some_value)
237
+ expect(subject.send(:inherit_or_delegate, :some_method, :multiple, :args)).to eq(:some_value)
238
+ end
239
+ end
240
+
241
+ describe "#key_attributes" do
242
+ it "doesn't respond to key_attributes" do
243
+ expect(subject.respond_to?(:key_attributes)).to eq(false)
244
+ end
245
+
246
+ it "returns the entire row if no primary key is defined" do
247
+ expect(subject.send(:key_attributes)).to eq(column_one: "Value One")
248
+ end
249
+
250
+ context "when the row has a single primary key column" do
251
+ let(:primary_key_columns) { [column] }
252
+
253
+ it "returns the value for the key column" do
254
+ expect(subject.send(:key_attributes)).to eq(column_one: "Value One")
255
+ end
256
+ end
257
+
258
+ context "when the row has a composite key" do
259
+ let(:columns) { [
260
+ double("column", key: "column one", model_attribute: :column_one),
261
+ double("column", key: "column two", model_attribute: :column_two),
262
+ double("column", key: "column three", model_attribute: :column_three)
263
+ ] }
264
+ let(:primary_key_columns) { [
265
+ double("column", key: "column one", model_attribute: :column_one),
266
+ double("column", key: "column two", model_attribute: :column_two)
267
+ ] }
268
+ let(:data) { ["Value One", "Value Two", "Value Three"] }
269
+
270
+ before do
271
+ allow(header).to receive(:column_index).with("column two").and_return(1)
272
+ end
273
+
274
+ it "returns the value for the key columns" do
275
+ expect(subject.send(:key_attributes)).to eq(column_one: "Value One", column_two: "Value Two")
276
+ end
277
+ end
278
+ end
279
+
280
+ describe "#model_instance" do
281
+ let(:model) { double("model") }
282
+ let(:model_instance) { double("model-instance") }
283
+ let(:subject) { described_class.new(header, data, model: model) }
284
+
285
+ before do
286
+ allow(subject).to receive(:key_attributes).and_return(:key_attributes)
287
+ end
288
+
289
+ it "doesn't respond to model_instance" do
290
+ expect(subject.respond_to?(:model_instance)).to eq(false)
291
+ end
292
+
293
+ it "first tries to find an instance via #find_row_model" do
294
+ allow(subject).to receive(:respond_to?).with(:find_row_model).and_return(true)
295
+ allow(subject).to receive(:respond_to?).with(:find_row_model, false).and_return(true)
296
+ expect(subject).to receive(:find_row_model).with(:key_attributes).and_return(model_instance)
297
+ expect(subject.send(:model_instance)).to eq(model_instance)
298
+ end
299
+
300
+ it "tries to find an instance via model#find_row_model when #find_row_model does not exist" do
301
+ expect(model).to receive(:find_row_model).with(:key_attributes).and_return(model_instance)
302
+ expect(subject.send(:model_instance)).to eq(model_instance)
303
+ end
304
+
305
+ it "tries to instantiate a new model via #new_row_model when a model cannot be found" do
306
+ allow(subject).to receive(:respond_to?).with(:find_row_model).and_return(true)
307
+ allow(subject).to receive(:respond_to?).with(:find_row_model, false).and_return(true)
308
+ allow(subject).to receive(:respond_to?).with(:new_row_model).and_return(true)
309
+ allow(subject).to receive(:respond_to?).with(:new_row_model, false).and_return(true)
310
+ expect(subject).to receive(:find_row_model).with(:key_attributes).and_return(nil)
311
+ expect(subject).to receive(:new_row_model).with(:key_attributes).and_return(model_instance)
312
+ expect(subject.send(:model_instance)).to eq(model_instance)
313
+ end
314
+
315
+ it "tries to instantiate a new model via model#new_row_model when a model cannot be found and #new_row_model does not exist" do
316
+ expect(model).to receive(:find_row_model).with(:key_attributes).and_return(nil)
317
+ expect(model).to receive(:new_row_model).with(:key_attributes).and_return(model_instance)
318
+ expect(subject.send(:model_instance)).to eq(model_instance)
319
+ end
320
+ end
321
+
322
+ describe "#process_row" do
323
+ let(:model) { double("model") }
324
+ let(:model_instance) { double("model-instance", changed?: true, marked_for_destruction?: false, new_record?: false, valid?: true) }
325
+ let(:subject) { described_class.new(header, data, model: model) }
326
+
327
+ before do
328
+ allow(model).to receive(:find_row_model).and_return(model_instance)
329
+ allow(subject).to receive(:all_attributes).and_return(:all_attributes)
330
+ end
331
+
332
+ it "doesn't respond to process_row" do
333
+ expect(subject.respond_to?(:process_row)).to eq(false)
334
+ end
335
+
336
+ it "assigns attributes and saves the model instance" do
337
+ expect(model_instance).to receive(:assign_attributes).with(:all_attributes).ordered
338
+ expect(model_instance).to receive(:save)
339
+ subject.send(:process_row)
340
+ end
341
+
342
+ it "only processes a record once" do
343
+ expect(model_instance).to receive(:assign_attributes).with(:all_attributes).ordered
344
+ expect(model_instance).to receive(:save)
345
+ subject.send(:process_row)
346
+ subject.send(:process_row)
347
+ end
348
+ end
349
+ end
350
+
351
+ end