tessa 0.9.1 → 1.0.0.pre.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +5 -5
  2. data/.babelrc +8 -0
  3. data/.circleci/config.yml +42 -0
  4. data/.eslintrc +19 -0
  5. data/.gitignore +5 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +12 -4
  8. data/README.md +4 -4
  9. data/Rakefile +8 -0
  10. data/app/assets/javascripts/.keep +0 -0
  11. data/app/assets/javascripts/tessa.esm.js +672 -0
  12. data/app/assets/javascripts/tessa.js +662 -0
  13. data/app/javascript/activestorage/file_checksum.js +53 -0
  14. data/app/javascript/tessa/index.js.coffee +183 -0
  15. data/config/routes.rb +3 -0
  16. data/lib/tessa/active_storage/asset_wrapper.rb +24 -0
  17. data/lib/tessa/engine.rb +5 -0
  18. data/lib/tessa/model/dynamic_extensions.rb +151 -0
  19. data/lib/tessa/model.rb +40 -36
  20. data/lib/tessa/rack_upload_proxy.rb +18 -10
  21. data/lib/tessa/version.rb +1 -1
  22. data/lib/tessa/view_helpers.rb +4 -2
  23. data/lib/tessa.rb +30 -0
  24. data/package.json +43 -0
  25. data/rollup.config.js +44 -0
  26. data/spec/dummy/.gitignore +27 -0
  27. data/spec/dummy/README.md +24 -0
  28. data/spec/dummy/Rakefile +6 -0
  29. data/spec/dummy/app/assets/config/manifest.js +2 -0
  30. data/spec/dummy/app/assets/images/.keep +0 -0
  31. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  32. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  33. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  34. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  35. data/spec/dummy/app/jobs/application_job.rb +2 -0
  36. data/spec/dummy/app/models/concerns/.keep +0 -0
  37. data/spec/dummy/app/models/multiple_asset_model.rb +8 -0
  38. data/spec/dummy/app/models/single_asset_model.rb +5 -0
  39. data/spec/dummy/app/models/single_asset_model_form.rb +25 -0
  40. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  41. data/spec/dummy/bin/bundle +3 -0
  42. data/spec/dummy/bin/rails +4 -0
  43. data/spec/dummy/bin/rake +4 -0
  44. data/spec/dummy/bin/setup +28 -0
  45. data/spec/dummy/bin/update +28 -0
  46. data/spec/dummy/bin/yarn +11 -0
  47. data/spec/dummy/config/application.rb +33 -0
  48. data/spec/dummy/config/boot.rb +3 -0
  49. data/spec/dummy/config/credentials.yml.enc +1 -0
  50. data/spec/dummy/config/database.yml +25 -0
  51. data/spec/dummy/config/environment.rb +5 -0
  52. data/spec/dummy/config/environments/development.rb +47 -0
  53. data/spec/dummy/config/environments/production.rb +76 -0
  54. data/spec/dummy/config/environments/test.rb +43 -0
  55. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  56. data/spec/dummy/config/initializers/assets.rb +14 -0
  57. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  58. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  59. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  60. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/spec/dummy/config/initializers/inflections.rb +16 -0
  62. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  63. data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
  64. data/spec/dummy/config/locales/en.yml +33 -0
  65. data/spec/dummy/config/puma.rb +37 -0
  66. data/spec/dummy/config/routes.rb +3 -0
  67. data/spec/dummy/config/storage.yml +3 -0
  68. data/spec/dummy/config.ru +5 -0
  69. data/spec/dummy/db/migrate/20220606221557_create_active_storage_tables.active_storage.rb +27 -0
  70. data/spec/dummy/db/migrate/20220606221900_create_single_asset_models.rb +10 -0
  71. data/spec/dummy/db/migrate/20220607191519_create_multiple_asset_models.rb +10 -0
  72. data/spec/dummy/db/schema.rb +50 -0
  73. data/spec/dummy/lib/assets/.keep +0 -0
  74. data/spec/dummy/lib/tasks/.keep +0 -0
  75. data/spec/dummy/log/.keep +0 -0
  76. data/spec/dummy/package.json +5 -0
  77. data/spec/dummy/public/404.html +67 -0
  78. data/spec/dummy/public/422.html +67 -0
  79. data/spec/dummy/public/500.html +66 -0
  80. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  81. data/spec/dummy/public/apple-touch-icon.png +0 -0
  82. data/spec/dummy/public/favicon.ico +0 -0
  83. data/spec/dummy/public/robots.txt +1 -0
  84. data/spec/dummy/tmp/.keep +0 -0
  85. data/spec/dummy/vendor/.keep +0 -0
  86. data/spec/rails_helper.rb +14 -0
  87. data/spec/spec_helper.rb +6 -0
  88. data/spec/tessa/model_spec.rb +195 -244
  89. data/spec/tessa/rack_upload_proxy_spec.rb +23 -81
  90. data/spec/tessa/upload/uploads_file_spec.rb +12 -8
  91. data/tessa.gemspec +10 -2
  92. data/yarn.lock +1661 -0
  93. metadata +196 -18
  94. data/circle.yml +0 -3
@@ -1,23 +1,20 @@
1
- require 'spec_helper'
1
+ require 'rails_helper'
2
2
 
3
3
  RSpec.describe Tessa::Model do
4
4
  subject(:described_module) { described_class }
5
- let(:model) { Class.new.tap { |c| c.send(:include, described_module) } }
5
+ let(:model) {
6
+ SingleAssetModel
7
+ }
6
8
 
7
9
  it { is_expected.to be_a(Module) }
8
10
 
9
11
  describe "::asset" do
10
12
  it "creates ModelField and sets it by name to @tessa_fields" do
11
- model.asset :new_field
12
- expect(model.tessa_fields[:new_field]).to be_a(Tessa::Model::Field)
13
+ expect(model.tessa_fields[:avatar]).to be_a(Tessa::Model::Field)
13
14
  end
14
15
 
15
16
  context "with a field named :avatar" do
16
17
  subject(:instance) { model.new }
17
- before do
18
- model.send :attr_accessor, :avatar_id
19
- model.asset :avatar
20
- end
21
18
 
22
19
  it "creates an #avatar method" do
23
20
  expect(instance).to respond_to(:avatar)
@@ -41,290 +38,225 @@ RSpec.describe Tessa::Model do
41
38
  end
42
39
 
43
40
  context "with customized field" do
44
- before do
45
- model.asset :custom_field, multiple: true, id_field: "another_place"
46
- end
41
+ let(:model) {
42
+ MultipleAssetModel
43
+ }
47
44
 
48
45
  it "sets all attributes on ModelField properly" do
49
- field = model.tessa_fields[:custom_field]
50
- expect(field.name).to eq("custom_field")
46
+ field = model.tessa_fields[:multiple_field]
47
+ expect(field.name).to eq("multiple_field")
51
48
  expect(field.multiple).to eq(true)
52
49
  expect(field.id_field).to eq("another_place")
53
50
  end
54
51
  end
55
52
 
56
53
  context "with inheritance hierarchy" do
57
- let(:submodel) { Class.new(model) }
58
- before do
59
- model.asset :field1
60
- submodel.asset :field2
61
- model.asset :field3
62
- end
54
+ let(:submodel) {
55
+ Class.new(model) do
56
+ asset :field2
57
+ end
58
+ }
63
59
 
64
60
  it "submodel has its own list of fields" do
65
- expect(submodel.tessa_fields.keys).to eq([:field1, :field2])
61
+ expect(submodel.tessa_fields.keys).to eq([:avatar, :field2])
66
62
  end
67
63
 
68
64
  it "does not alter parent class fields" do
69
- expect(model.tessa_fields.keys).to eq([:field1, :field3])
65
+ expect(model.tessa_fields.keys).to eq([:avatar])
70
66
  end
71
67
  end
72
- end
73
-
74
- describe "#asset_setter" do
75
- let(:instance) { model.new }
76
-
77
- context "with change set present for a field" do
78
- let(:a) { Tessa::Asset.new(id: 1) }
79
- let(:b) { Tessa::Asset.new(id: 2) }
80
- let(:set) { instance.pending_tessa_change_sets[:field] }
81
- before do
82
- model.send :attr_accessor, :field_id
83
- model.asset :field
84
- instance.field = a
85
- end
86
-
87
- it "combines the changes with the new set" do
88
- instance.field = b
89
- instance.field = nil
90
- changes = set.changes.map { |change| [change.id, change.action.to_sym] }
91
- expect(changes).to eq([
92
- [1, :add],
93
- [2, :add],
94
- [1, :remove],
95
- [2, :remove],
96
- ])
97
- end
98
- end
99
-
100
- context "with a multiple typed field" do
101
- before do
102
- model.send(:attr_accessor, :file_ids)
103
- model.asset :file, multiple: true
104
- end
105
-
106
- context "when set with array of assets" do
107
- let(:assets) {
108
- [
109
- Tessa::Asset.new(id: 1),
110
- Tessa::Asset.new(id: 2),
111
- ]
112
- }
113
68
 
114
- it "sets field to list of ids from assets" do
115
- instance.file = assets
116
- expect(instance.file_ids).to eq([1, 2])
117
- end
118
-
119
- it "removes any ids that aren't in list" do
120
- instance.file_ids = [3]
121
- instance.file = assets
122
- expect(instance.file_ids).to eq([1, 2])
123
- end
124
-
125
- it "adds an AssetChangeSet to pending queue for this field" do
126
- instance.file = assets
127
- expect(instance.pending_tessa_change_sets).to be_a(Hash)
128
- expect(instance.pending_tessa_change_sets[:file]).to be_a(Tessa::AssetChangeSet)
129
- end
130
-
131
- describe "the added change set" do
132
- subject(:set) { instance.pending_tessa_change_sets[:file] }
133
-
134
- it "has an 'add' action for each new asset" do
135
- instance.file = assets
136
- ids = set.changes.select { |c| c.action == 'add' }.collect(&:id)
137
- expect(ids).to eq([1, 2])
138
- end
139
-
140
- it "has a 'remove' action for each missing asset" do
141
- instance.file_ids = [3]
142
- instance.file = assets
143
- ids = set.changes.select { |c| c.action == 'remove' }.collect(&:id)
144
- expect(ids).to eq([3])
145
- end
146
-
147
- it "adds each of the ids to the scoped_ids list" do
148
- instance.file_ids = [3]
149
- instance.file = assets
150
- expect(set.scoped_ids).to eq([1, 2, 3])
151
- end
152
- end
153
- end
154
-
155
- context "when set with an AssetChangeSet" do
156
- let(:set) { Tessa::AssetChangeSet.new }
157
- before do
158
- set.add(1)
159
- set.add(2)
160
- set.changes << Tessa::AssetChange.new(id: 0, action: "add")
161
- end
162
-
163
- it "sets field to list of ids from scoped_changes" do
164
- instance.file = set
165
- expect(instance.file_ids).to eq([1, 2])
166
- end
167
-
168
- it "leaves any ids that are not touched by the set" do
169
- instance.file_ids = [3]
170
- instance.file = set
171
- expect(instance.file_ids).to include(1, 2, 3)
172
- end
173
-
174
- it "removes any ids from field that are scoped as removals" do
175
- instance.file_ids = [3]
176
- set.remove(3)
177
- instance.file = set
178
- expect(instance.file_ids).to eq([1, 2])
179
- end
180
-
181
- it "adds the AssetChangeSet to pending queue for this field" do
182
- instance.file = set
183
- new_set = instance.pending_tessa_change_sets[:file]
184
- expect(new_set.changes).to eq(set.changes)
185
- expect(new_set.scoped_ids).to eq(set.scoped_ids)
186
- end
187
- end
188
- end
189
-
190
- context "with a singular typed field" do
191
- before do
192
- model.send(:attr_accessor, :file_id)
193
- model.asset :file
194
- end
195
-
196
- context "when set with an Asset" do
197
- let(:asset) { Tessa::Asset.new(id: 1) }
198
-
199
- it "sets field to id from asset" do
200
- instance.file = asset
201
- expect(instance.file_id).to eq(1)
202
- end
203
-
204
- it "adds an AssetChangeSet to pending queue for this field" do
205
- instance.file = asset
206
- expect(instance.pending_tessa_change_sets[:file]).to be_a(Tessa::AssetChangeSet)
207
- end
208
-
209
- describe "the added change set" do
210
- let(:set) { instance.pending_tessa_change_sets[:file] }
211
-
212
- it "has an 'add' action for the new asset" do
213
- instance.file = asset
214
- expect(set.changes.select(&:add?).first.id).to eq(1)
215
- end
69
+ context "on a form object" do
70
+ let(:model) {
71
+ SingleAssetModelForm
72
+ }
73
+ subject(:instance) { model.new }
216
74
 
217
- it "has a 'remove' action for the previous asset" do
218
- instance.file_id = 2
219
- instance.file = asset
220
- expect(set.changes.select(&:remove?).first.id).to eq(2)
221
- end
222
- end
75
+ it "creates an #avatar method" do
76
+ expect(instance).to respond_to(:avatar)
223
77
  end
224
78
 
225
- context "when set with an AssetChangeSet" do
226
- let(:set) { Tessa::AssetChangeSet.new }
227
- before do
228
- set.changes << Tessa::AssetChange.new(id: 0, action: "add")
229
- set.add(1)
230
- end
231
-
232
- it "sets field to asset id of first 'add' action in scoped_changes" do
233
- instance.file = set
234
- expect(instance.file_id).to eq(1)
235
- end
236
-
237
- it "adds the AssetChangeSet to pending queue for this field" do
238
- instance.file = set
239
- new_set = instance.pending_tessa_change_sets[:file]
240
- expect(new_set.changes).to eq(set.changes)
241
- expect(new_set.scoped_ids).to eq(set.scoped_ids)
242
- end
243
-
244
- context "with no remove in change set" do
245
- it "ensures there is a 'remove' action for previous value" do
246
- instance.file_id = 2
247
- instance.file = set
248
- expect(set.scoped_changes.select(&:remove?).map(&:id)).to eq([2])
249
- end
250
- end
79
+ it "creates an #avatar= method" do
80
+ expect(instance).to respond_to(:avatar=)
251
81
  end
252
82
  end
253
83
  end
254
84
 
255
85
  describe "#asset_getter" do
256
86
  let(:instance) { model.new }
257
- subject(:getter) { instance.file }
258
87
 
259
88
  context "with a multiple typed field" do
260
- before do
261
- model.send(:attr_accessor, :file_ids)
262
- model.asset :file, multiple: true
263
- end
89
+ let(:model) {
90
+ MultipleAssetModel
91
+ }
92
+ subject(:getter) { instance.multiple_field }
264
93
 
265
94
  it "calls find for each of the file_ids and returns result" do
266
- instance.file_ids = [1, 2, 3]
95
+ instance.another_place = [1, 2, 3]
267
96
  expect(Tessa::Asset).to receive(:find).with([1, 2, 3]).and_return([:a1, :a2, :a3])
268
97
  expect(getter).to eq([:a1, :a2, :a3])
269
98
  end
270
99
 
271
100
  it "caches the result" do
272
- instance.file_ids = [1]
101
+ instance.another_place = [1]
273
102
  expect(Tessa::Asset).to receive(:find).and_return(:val).once
274
- instance.file
275
- instance.file
103
+ instance.multiple_field
104
+ instance.multiple_field
276
105
  end
277
106
 
278
107
  context "with no values" do
279
108
  it "does not call find" do
280
- instance.file_ids = []
109
+ instance.another_place = []
281
110
  expect(Tessa::Asset).not_to receive(:find)
282
- expect(instance.file).to eq([])
111
+ expect(instance.multiple_field).to eq([])
283
112
  end
284
113
  end
285
114
  end
286
115
 
287
116
  context "with a singular typed field" do
288
- before do
289
- model.send(:attr_accessor, :file_id)
290
- model.asset :file
291
- end
117
+ let(:model) {
118
+ SingleAssetModel
119
+ }
120
+ subject(:getter) { instance.avatar }
292
121
 
293
122
  it "calls find for file_id and returns result" do
294
- instance.file_id = 1
123
+ instance.avatar_id = 1
295
124
  expect(Tessa::Asset).to receive(:find).with(1).and_return(:a1)
296
125
  expect(getter).to eq(:a1)
297
126
  end
298
127
 
299
128
  it "caches the result" do
300
- instance.file_id = 1
129
+ instance.avatar_id = 1
301
130
  expect(Tessa::Asset).to receive(:find).and_return(:val).once
302
- instance.file
303
- instance.file
131
+ instance.avatar
132
+ instance.avatar
133
+ end
134
+
135
+ it "wraps ActiveStorage uploads with AssetWrapper" do
136
+ file = Rack::Test::UploadedFile.new("README.md")
137
+ instance.avatar = file
138
+
139
+ asset = instance.avatar
140
+ expect(asset).to be_a(Tessa::ActiveStorage::AssetWrapper)
141
+ # This goes to the blobs URL, which then redirects to the backend service URL
142
+ expect(asset.public_url).to start_with('https://www.example.com/rails/active_storage/blobs/')
143
+ # This is a direct download to the service URL (in test mode that is "disk")
144
+ expect(asset.private_url).to start_with('https://www.example.com/rails/active_storage/disk/')
145
+ expect(asset.private_download_url).to start_with('https://www.example.com/rails/active_storage/disk/')
146
+ expect(asset.private_download_url).to include('&disposition=attachment')
304
147
  end
305
148
 
306
149
  context "with nil value" do
307
150
  it "does not call find" do
308
- instance.file_id = nil
151
+ instance.avatar_id = nil
309
152
  expect(Tessa::Asset).not_to receive(:find)
310
- expect(instance.file).to be_nil
153
+ expect(instance.avatar).to be_nil
311
154
  end
312
155
  end
313
156
  end
157
+
158
+ context "on a form object" do
159
+ let(:model) {
160
+ SingleAssetModelForm
161
+ }
162
+ subject(:getter) { instance.avatar }
163
+
164
+ it 'returns nil when empty' do
165
+ expect(getter).to be_nil
166
+ end
167
+
168
+ it 'returns assigned upload object' do
169
+ file = Rack::Test::UploadedFile.new("README.md")
170
+ instance.avatar = file
171
+ expect(getter).to eq(file)
172
+ end
173
+ end
174
+ end
175
+
176
+ describe "#asset_setter" do
177
+ let(:instance) { model.new }
178
+
179
+ context "with a singular typed field" do
180
+ let(:model) {
181
+ SingleAssetModel
182
+ }
183
+ subject(:getter) { instance.avatar }
184
+
185
+ it 'attaches uploaded file' do
186
+ file = Rack::Test::UploadedFile.new("README.md")
187
+ instance.avatar = file
188
+
189
+ expect(getter.name).to eq('avatar')
190
+ expect(getter.filename).to eq('README.md')
191
+ expect(getter.content_type).to eq('text/plain')
192
+ expect(getter.service_url)
193
+ .to start_with('https://www.example.com/rails/active_storage/disk/')
194
+ end
195
+
196
+ it 'sets the ID to be the ActiveStorage key' do
197
+ file = Rack::Test::UploadedFile.new("README.md")
198
+ instance.avatar = file
199
+
200
+ expect(instance.avatar_id).to eq(instance.avatar_attachment.key)
201
+ end
202
+
203
+ it 'sets the ID in the attributes' do
204
+ file = Rack::Test::UploadedFile.new("README.md")
205
+ instance.avatar = file
206
+
207
+ expect(instance.attributes['avatar_id']).to eq(instance.avatar_attachment.key)
208
+ end
209
+ end
210
+
211
+ context "with a multiple typed field" do
212
+ let(:model) {
213
+ MultipleAssetModel
214
+ }
215
+
216
+ it 'attaches uploaded files' do
217
+ file = Rack::Test::UploadedFile.new("README.md")
218
+ file2 = Rack::Test::UploadedFile.new("LICENSE.txt")
219
+ instance.multiple_field = [file, file2]
220
+
221
+ expect(instance.multiple_field[0].name).to eq('multiple_field')
222
+ expect(instance.multiple_field[0].filename).to eq('README.md')
223
+ expect(instance.multiple_field[0].content_type).to eq('text/plain')
224
+ expect(instance.multiple_field[0].service_url)
225
+ .to start_with('https://www.example.com/rails/active_storage/disk/')
226
+ expect(instance.multiple_field[1].name).to eq('multiple_field')
227
+ expect(instance.multiple_field[1].filename).to eq('LICENSE.txt')
228
+ expect(instance.multiple_field[1].content_type).to eq('text/plain')
229
+ expect(instance.multiple_field[1].service_url)
230
+ .to start_with('https://www.example.com/rails/active_storage/disk/')
231
+ end
232
+
233
+ it 'sets the ID to be the ActiveStorage key' do
234
+ file = Rack::Test::UploadedFile.new("README.md")
235
+ file2 = Rack::Test::UploadedFile.new("LICENSE.txt")
236
+ instance.multiple_field = [file, file2]
237
+
238
+ expect(instance.another_place).to eq(instance.multiple_field_attachments.map(&:key))
239
+ end
240
+
241
+ it 'sets the ID in the attributes' do
242
+ file = Rack::Test::UploadedFile.new("README.md")
243
+ file2 = Rack::Test::UploadedFile.new("LICENSE.txt")
244
+ instance.multiple_field = [file, file2]
245
+
246
+ expect(instance.attributes['another_place']).to eq(instance.multiple_field_attachments.map(&:key))
247
+ end
248
+ end
314
249
  end
315
250
 
316
251
  describe "#apply_tessa_change_sets" do
317
252
  let(:instance) { model.new }
318
- let(:sets) { Array.new(2) { instance_spy(Tessa::AssetChangeSet) } }
253
+ let(:sets) { [ instance_spy(Tessa::AssetChangeSet) ] }
319
254
 
320
255
  before do
321
- model.asset :field1
322
- model.asset :field2
323
256
  instance.instance_variable_set(
324
257
  :@pending_tessa_change_sets,
325
258
  {
326
- field1: sets[0],
327
- field2: sets[1],
259
+ avatar: sets[0],
328
260
  }
329
261
  )
330
262
  end
@@ -332,7 +264,6 @@ RSpec.describe Tessa::Model do
332
264
  it "iterates over all pending changesets calling apply" do
333
265
  instance.apply_tessa_change_sets
334
266
  expect(sets[0]).to have_received(:apply)
335
- expect(sets[1]).to have_received(:apply)
336
267
  end
337
268
 
338
269
  it "removes all changesets from list" do
@@ -357,10 +288,6 @@ RSpec.describe Tessa::Model do
357
288
  describe "#fetch_tessa_remote_assets" do
358
289
  subject(:result) { model.new.fetch_tessa_remote_assets(arg) }
359
290
 
360
- before do
361
- model.asset :avatar
362
- end
363
-
364
291
  context "argument is `nil`" do
365
292
  let(:arg) { nil }
366
293
 
@@ -431,25 +358,49 @@ RSpec.describe Tessa::Model do
431
358
 
432
359
  describe "#remove_all_tessa_assets" do
433
360
  let(:instance) { model.new }
434
- before do
435
- model.send :attr_accessor, :field1_id, :field2_ids
436
- model.asset :field1
437
- model.asset :field2, multiple: true
438
- instance.field1_id = 1
439
- instance.field2_ids = [2, 3]
361
+
362
+ context "with a single typed field" do
363
+ let(:model) {
364
+ SingleAssetModel
365
+ }
366
+
367
+ before do
368
+ instance.avatar_id = 1
369
+ end
370
+
371
+ it "adds pending change sets for each field removing all current assets" do
372
+ instance.remove_all_tessa_assets
373
+ changes = instance.pending_tessa_change_sets.values
374
+ .reduce(Tessa::AssetChangeSet.new, :+)
375
+ .changes
376
+ .map { |change| [change.id, change.action.to_sym] }
377
+ expect(changes).to eq([
378
+ [1, :remove]
379
+ ])
380
+ end
440
381
  end
441
382
 
442
- it "adds pending change sets for each field removing all current assets" do
443
- instance.remove_all_tessa_assets
444
- changes = instance.pending_tessa_change_sets.values
445
- .reduce(Tessa::AssetChangeSet.new, :+)
446
- .changes
447
- .map { |change| [change.id, change.action.to_sym] }
448
- expect(changes).to eq([
449
- [1, :remove],
450
- [2, :remove],
451
- [3, :remove],
452
- ])
383
+
384
+ context "with a multiple typed field" do
385
+ let(:model) {
386
+ MultipleAssetModel
387
+ }
388
+
389
+ before do
390
+ instance.another_place = [2, 3]
391
+ end
392
+
393
+ it "adds pending change sets for each field removing all current assets" do
394
+ instance.remove_all_tessa_assets
395
+ changes = instance.pending_tessa_change_sets.values
396
+ .reduce(Tessa::AssetChangeSet.new, :+)
397
+ .changes
398
+ .map { |change| [change.id, change.action.to_sym] }
399
+ expect(changes).to eq([
400
+ [2, :remove],
401
+ [3, :remove],
402
+ ])
403
+ end
453
404
  end
454
405
  end
455
406
 
@@ -16,28 +16,29 @@ RSpec.describe Tessa::RackUploadProxy do
16
16
  }
17
17
  let(:params) { {} }
18
18
  let(:session) { {} }
19
- let(:upload) {
20
- Tessa::Upload.new(
21
- asset_id: 123,
22
- upload_url: "a-url",
23
- upload_method: "a-method",
24
- )
25
- }
19
+
20
+ let(:blob) { ActiveStorage::Blob.last }
21
+
26
22
  before do
27
- allow(Tessa::Upload).to receive(:create).and_return(upload)
23
+ allow(ActiveStorage.verifier).to receive(:generate)
24
+ .and_return('some-consistent-token')
28
25
  end
29
26
 
30
27
  shared_examples_for "proper json return" do
31
28
  it "returns asset_id" do
32
- expect(result.json['asset_id']).to eq(123)
29
+ expect(result.json['asset_id']).to eq(blob.signed_id)
33
30
  end
34
31
 
35
32
  it "returns upload_url" do
36
- expect(result.json['upload_url']).to eq("a-url")
33
+ expect(result.json['upload_url']).to eq('https://www.example.com/rails/active_storage/disk/some-consistent-token')
37
34
  end
38
35
 
39
36
  it "returns upload_method" do
40
- expect(result.json['upload_method']).to eq("a-method")
37
+ expect(result.json['upload_method']).to eq('PUT')
38
+ end
39
+
40
+ it "returns upload_method" do
41
+ expect(result.json['upload_headers']).to eq(blob.service_headers_for_direct_upload)
41
42
  end
42
43
 
43
44
  it "returns a 200 response" do
@@ -47,38 +48,12 @@ RSpec.describe Tessa::RackUploadProxy do
47
48
  it "sets the mime type to application/json" do
48
49
  expect(result.headers).to include("Content-Type" => "application/json")
49
50
  end
50
-
51
- context "when Tessa::Upload.create raises RequestFailed" do
52
- before do
53
- allow(Tessa::Upload).to receive(:create).and_raise(Tessa::RequestFailed)
54
- end
55
-
56
- it "returns a proper 500" do
57
- expect(result.status).to eq(500)
58
- end
59
-
60
- it "sets the mime type to application/json" do
61
- expect(result.headers).to include("Content-Type" => "application/json")
62
- end
63
-
64
- it "includes an error message in JSON response" do
65
- expect(result.json['error']).to be_truthy
66
- end
67
- end
68
51
  end
69
52
 
70
53
  context "with no params and no session" do
71
- it "calls Tessa::Upload.create" do
72
- expect(Tessa::Upload).to receive(:create).and_return(upload)
73
- result
74
- end
75
-
76
- it "pushes the asset_id onto session" do
77
- result
78
- expect(session[:tessa_upload_asset_ids]).to eq([123])
54
+ it "raises a bad request error" do
55
+ expect(result.status).to eq(400)
79
56
  end
80
-
81
- it_behaves_like "proper json return"
82
57
  end
83
58
 
84
59
  context "with params" do
@@ -86,53 +61,20 @@ RSpec.describe Tessa::RackUploadProxy do
86
61
  {
87
62
  "name" => "my-name",
88
63
  "size" => 456,
89
- "date" => "2020-01-01",
90
64
  "mime_type" => "plain/text",
65
+ "checksum" => '1234'
91
66
  }
92
67
  }
93
68
 
94
- it "calls Tessa::Upload.create with 'name'" do
95
- expect(Tessa::Upload).to receive(:create).with(hash_including(name: "my-name"))
96
- result
97
- end
98
-
99
- it "calls Tessa::Upload.create with 'size'" do
100
- expect(Tessa::Upload).to receive(:create).with(hash_including(size: 456))
101
- result
102
- end
103
-
104
- it "calls Tessa::Upload.create with 'date'" do
105
- expect(Tessa::Upload).to receive(:create).with(hash_including(date: "2020-01-01"))
106
- result
107
- end
108
-
109
- it "calls Tessa::Upload.create with 'mime_type'" do
110
- expect(Tessa::Upload).to receive(:create).with(hash_including(mime_type: "plain/text"))
111
- result
112
- end
113
-
114
- it_behaves_like "proper json return"
115
- end
116
-
117
- context "with no date param" do
118
- let(:params) { {} }
119
-
120
- it "calls Tessa::Upload.create without 'date'" do
121
- expect(Tessa::Upload).to_not receive(:create).with(hash_including(:date))
122
- result
123
- end
124
- end
125
-
126
- context "with existing session" do
127
- let(:session) {
128
- {
129
- tessa_upload_asset_ids: [:existing_id],
130
- }
131
- }
69
+ it "creates the ActiveStorage blob" do
70
+ expect {
71
+ described_class.call(env)
72
+ }.to change { ActiveStorage::Blob.count }.by(1)
132
73
 
133
- it "does not remove existing ids" do
134
- result
135
- expect(session[:tessa_upload_asset_ids]).to eq([:existing_id, 123])
74
+ expect(blob.filename).to eq('my-name')
75
+ expect(blob.byte_size).to eq(456)
76
+ expect(blob.content_type).to eq('plain/text')
77
+ expect(blob.checksum).to eq('1234')
136
78
  end
137
79
 
138
80
  it_behaves_like "proper json return"