tessa 1.2.0 → 1.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 836d5b9c8929f9d9c4b7c2db37bce1d6a73be8dc8835fd439dab0f6230b72ec5
4
- data.tar.gz: c6f135eae7bf4d7e71b4ad9def77a5a356fe283054dbb5c778aef4c3279bc573
3
+ metadata.gz: 78e7edfe1685678714fdec2e58b81f476dc33be7c2affa35acf78a5375da25e1
4
+ data.tar.gz: aafa43aa6aae2012b1201b97ec1a29c6f35435f8426622c50ba3f9c8e8438546
5
5
  SHA512:
6
- metadata.gz: f08e31e74894827d96f34696959f24d9e286a86988ec53407d4e65828f657728661eeb54f85fd44e5f030b20f445a2aeeac170abf281a157edccb681dad169e5
7
- data.tar.gz: eba0b301158e3b2a477f97d9451cc83d2b652ddf30d91cae736b88ef033a75f02022f295a1ac943b24b1496524987d2e1f3a153a435afddb1353f892fdba8807
6
+ metadata.gz: e8448afb3467983a468e65b16bbda79c028c1e2a9d197d1fce48b6ade6fb5aedb8b1d0597fc823d133552eb206146ee7c6ebf7be0dcecc1bddc12cc0dd46d2d4
7
+ data.tar.gz: c61dc18d7bfc8b435ae760e1d8357aecddbdcb8122896bea63b636659ad76fc2bd1333214196a067928bd3b10da97d43cde22b7bcb400e153bb778ea683edc1d
data/Gemfile CHANGED
@@ -11,5 +11,4 @@ end
11
11
  group :test do
12
12
  gem 'rspec-rails'
13
13
  gem 'webmock'
14
- gem 'faraday'
15
14
  end
data/README.md CHANGED
@@ -1,27 +1,11 @@
1
- # [Tessa Client][0]
1
+ # [Tessa][0]
2
2
 
3
3
  [ ![Gem Version][1] ][2]
4
4
  [ ![Build Status][3] ][4]
5
5
  [ ![Coverage Status][5] ][6]
6
6
  [ ![Code Climate][7] ][8]
7
7
 
8
- This gem wraps ActiveStorage to provide some convenience helpers for direct uploading
9
- assets, as well as generating public and private asset URLs.
10
-
11
- ## History
12
-
13
- This gem was initially conceived as a client to the
14
- [Tessa asset management service](https://github.com/watermarkchurch/tessa). That service has been deprecated and is
15
- being phased out.
16
-
17
- In 2022, we moved away from using Tessa to manage our assets and got onto the official Rails ActiveStorage library.
18
- [Here is the issue tracking the effort](https://github.com/watermarkchurch/tessa/issues/27). As part of that process,
19
- we released the v1.x version of this gem which supports both ActiveStorage and Tessa but uploads all new assets to
20
- ActiveStorage.
21
-
22
- When that migration is completed, v2.x of this gem will remove all code that accesses the old Tessa service. This gem
23
- will transition to being simply a convenience wrapper around ActiveStorage, so that we don't have to re-write as much
24
- of our code in our frontend apps.
8
+ Manage your assets.
25
9
 
26
10
  ## Installation
27
11
 
@@ -41,65 +25,7 @@ Or install it yourself as:
41
25
 
42
26
  ## Usage
43
27
 
44
- We provide no guarantee of support for your use of this gem. It is for internal Watermark use only.
45
- **Use at your own risk!**
46
-
47
- The main thing this gem still gives us is direct-uploads using [Dropzone.js](https://github.com/dropzone/dropzone).
48
- Before using this gem it's worthwhile to understand how it works:
49
-
50
- ![Sequence Diagram](./docs/tessa-activestorage-sequence-diagram.drawio.png)
51
-
52
- When an Asset input is configured using the SimpleForm helper (TODO: https://github.com/watermarkchurch/tessa-client/issues/22),
53
- it creates a DropZone div with the css class `.tessa-upload`. The javascript
54
- in `app/javascript/tessa/index.js.coffee` scans for these divs and configures
55
- Dropzone.js.
56
-
57
- When a user initiates a file upload via Dropzone, it does an XHR POST to `/tessa/uploads`. This triggers
58
- the `lib/tessa/rack_upload_proxy.rb` middleware to create a new ActiveStorage blob, and returns the
59
- signed upload URL. Dropzone then uploads the file directly to S3 (or wherever ActiveStorage is configured)
60
- from the user's browser.
61
-
62
- To get all this working, follow these steps:
63
-
64
- 1. Mount the engine
65
- in your `config/routes.rb`, `mount Tessa::Engine, at: '/'`. It's important that it is mounted at root.
66
-
67
- You can use Authentication around the engine to prevent unauthorized uploads. With devise it's as simple as:
68
- ```rb
69
- authenticated :user do
70
- mount Tessa::Engine, at: '/'
71
- end
72
- ```
73
-
74
- 2. In your application.js, require the js libraries:
75
- ```js
76
- //= require dropzone
77
- //= require tessa
78
- ```
79
- Note that this only works if you are using Sprockets.
80
- If you are using another bundler, we don't support that yet.
81
-
82
- 3. In your model, use the Tessa `asset` declaration instead of `has_one_attached`. The SimpleForm helper only works
83
- with Tessa `asset` declarations so far.
84
-
85
- ```rb
86
- class Model < ApplicationRecord
87
- include Tessa::Model
88
-
89
- asset :image # Note: this essentially delegates to has_one_attached
90
- ```
91
-
92
- 4. When rendering your form, use the SimpleForm helper to render the dropzone div:
93
-
94
- ```erb
95
- <%= f.input :image,
96
- as: :asset,
97
- dropzone: { acceptedFiles: "image/*" },
98
- hint: "Use an image that is 1440 x 288 in size (5:1 aspect ratio)" %>
99
- ```
100
-
101
- 5. Configure your ActiveStorage service to accept direct uploads.
102
- The disk service does this automatically. The S3 service requires additional CORS configuration.
28
+ TODO: Write usage instructions here
103
29
 
104
30
  ## Contributing
105
31
 
data/lib/tasks/tessa.rake CHANGED
@@ -2,17 +2,6 @@
2
2
  namespace :tessa do
3
3
  desc "Begins the migration of all Tessa assets to ActiveStorage."
4
4
  task :migrate => :environment do
5
- abort "Tessa::MigrateAssetsJob can no longer be performed because the Tessa connection was removed. "\
6
- "Please downgrade to tessa ~>1.0 and try again."
7
- end
8
-
9
- desc "Verifies that the migration has completed"
10
- task :verify => :environment do
11
- unless Tessa::MigrateAssetsJob.complete?
12
- state = Tessa::MigrateAssetsJob::ProcessingState.initialize_from_models
13
-
14
- abort "Tessa::MigrateAssetsJob not yet complete! #{state.count} records remain to be migrated. "\
15
- "Please downgrade to tessa ~>1.0 and try again."
16
- end
5
+ Tessa::MigrateAssetsJob.perform_later
17
6
  end
18
7
  end
data/lib/tessa/config.rb CHANGED
@@ -10,7 +10,15 @@ module Tessa
10
10
  attribute :strategy, String, default: -> (*_) { ENV['TESSA_STRATEGY'] || DEFAULT_STRATEGY }
11
11
 
12
12
  def connection
13
- @connection ||= Tessa::FakeConnection.new
13
+ @connection ||= Faraday.new(url: url) do |conn|
14
+ if conn.respond_to?(:basic_auth)
15
+ conn.basic_auth username, password
16
+ else # Faraday >= 1.0
17
+ conn.request :authorization, :basic, username, password
18
+ end
19
+ conn.request :url_encoded
20
+ conn.adapter Faraday.default_adapter
21
+ end
14
22
  end
15
23
  end
16
24
  end
@@ -72,6 +72,14 @@ class Tessa::MigrateAssetsJob < ActiveJob::Base
72
72
  reupload(record, field_state)
73
73
  Rails.logger.info("#{record.class}#{record.id}##{field_state.field_name}: success")
74
74
  field_state.success_count += 1
75
+ rescue OpenURI::HTTPError => ex
76
+ if ex.message == "404 Not Found"
77
+ # clear out the field if the asset is missing
78
+ record.public_send("#{field_state.tessa_field.name}=", nil)
79
+ record.save!
80
+ else
81
+ Rails.logger.error("#{record.class}#{record.id}##{field_state.field_name}: error - #{ex}")
82
+ end
75
83
  rescue StandardError => ex
76
84
  Rails.logger.error("#{record.class}#{record.id}##{field_state.field_name}: error - #{ex}")
77
85
  field_state.offset += 1
@@ -127,24 +135,18 @@ class Tessa::MigrateAssetsJob < ActiveJob::Base
127
135
  end
128
136
 
129
137
  def load_models_from_registry
130
- # Initialize our Record Keeping object
131
- ProcessingState.initialize_from_models
132
- end
138
+ Rails.application.eager_load!
139
+
140
+ # Load all Tessa models that can have attachments (not form objects)
141
+ models = Tessa.model_registry
142
+ .select { |m| m.respond_to?(:has_one_attached) }
133
143
 
134
- # Determines whether the migrate asset job is completed. If true, running the
135
- # job again will not do anything.
136
- def self.complete?
137
- ProcessingState.initialize_from_models.fully_processed?
144
+ # Initialize our Record Keeping object
145
+ ProcessingState.initialize_from_models(models)
138
146
  end
139
147
 
140
148
  ProcessingState = Struct.new(:model_queue, :batch_count) do
141
- def self.initialize_from_models(models = nil)
142
- unless models
143
- # Load all Tessa models that can have attachments (not form objects)
144
- Rails.application.eager_load!
145
- models = Tessa.model_registry.select { |m| m.respond_to?(:has_one_attached) }
146
- end
147
-
149
+ def self.initialize_from_models(models)
148
150
  new(
149
151
  models.map do |model|
150
152
  ModelProcessingState.initialize_from_model(model)
@@ -22,11 +22,19 @@ class Tessa::DynamicExtensions
22
22
  if #{name}_attachment.present?
23
23
  return Tessa::ActiveStorage::AssetWrapper.new(#{name}_attachment)
24
24
  end
25
+
26
+ # fall back to old Tessa fetch if not present
27
+ if field = self.class.tessa_fields["#{name}".to_sym]
28
+ @#{name} ||= fetch_tessa_remote_assets(field.id(on: self))
29
+ end
25
30
  end
26
31
 
27
32
  def #{field.id_field}
28
33
  # Use the attachment's key
29
34
  return #{name}_attachment.key if #{name}_attachment.present?
35
+
36
+ # fallback to Tessa's database column
37
+ super
30
38
  end
31
39
 
32
40
  def #{name}=(attachable)
@@ -72,6 +80,7 @@ class Tessa::DynamicExtensions
72
80
 
73
81
  @#{name} ||= [
74
82
  *#{name}_attachments.map { |a| Tessa::ActiveStorage::AssetWrapper.new(a) },
83
+ *fetch_tessa_remote_assets(tessa_ids)
75
84
  ]
76
85
  end
77
86
 
@@ -79,6 +88,8 @@ class Tessa::DynamicExtensions
79
88
  [
80
89
  # Use the attachment's key
81
90
  *#{name}_attachments.map(&:key),
91
+ # include from Tessa's database column
92
+ *super
82
93
  ]
83
94
  end
84
95
 
@@ -94,8 +105,8 @@ class Tessa::DynamicExtensions
94
105
  existing.destroy
95
106
  else
96
107
  ids = self.#{field.id_field}
97
- ids&.delete(change.id.to_i)
98
- self.#{field.id_field} = ids&.any? ? ids : nil
108
+ ids.delete(change.id.to_i)
109
+ self.#{field.id_field} = ids.any? ? ids : nil
99
110
  end
100
111
  end
101
112
  attachables.changes.select(&:add?).each do |change|
@@ -127,7 +138,10 @@ class Tessa::DynamicExtensions
127
138
  def build(mod)
128
139
  mod.class_eval <<~CODE, __FILE__, __LINE__ + 1
129
140
  attr_accessor :#{name}_id
130
- attr_accessor :#{name}
141
+ attr_writer :#{name}
142
+ def #{name}
143
+ @#{name} ||= fetch_tessa_remote_assets(#{name}_id)
144
+ end
131
145
  CODE
132
146
  mod
133
147
  end
@@ -137,9 +151,12 @@ class Tessa::DynamicExtensions
137
151
  def build(mod)
138
152
  mod.class_eval <<~CODE, __FILE__, __LINE__ + 1
139
153
  attr_accessor :#{name}_ids
140
- attr_accessor :#{name}
154
+ attr_writer :#{name}
155
+ def #{name}
156
+ @#{name} ||= fetch_tessa_remote_assets(#{name}_ids)
157
+ end
141
158
  CODE
142
159
  mod
143
160
  end
144
161
  end
145
- end
162
+ end
data/lib/tessa/model.rb CHANGED
@@ -8,6 +8,8 @@ module Tessa
8
8
  def self.included(base)
9
9
  base.send :include, InstanceMethods
10
10
  base.extend ClassMethods
11
+ base.after_commit :apply_tessa_change_sets if base.respond_to?(:after_commit)
12
+ base.before_destroy :remove_all_tessa_assets if base.respond_to?(:before_destroy)
11
13
 
12
14
  Tessa.model_registry << base
13
15
  end
@@ -19,9 +21,9 @@ module Tessa
19
21
  end
20
22
 
21
23
  def apply_tessa_change_sets
22
- # Pretend like the application was successful but we didn't do anything
23
- # because everything is in ActiveStorage now
24
- pending_tessa_change_sets.clear
24
+ pending_tessa_change_sets.delete_if do |_, change_set|
25
+ change_set.apply
26
+ end
25
27
  end
26
28
 
27
29
  def remove_all_tessa_assets
@@ -35,9 +37,17 @@ module Tessa
35
37
  end
36
38
 
37
39
  def fetch_tessa_remote_assets(ids)
38
- # This should just always return Tessa::AssetFailure
39
40
  Tessa.find_assets(ids)
40
41
  end
42
+
43
+ private def reapplying_asset?(field, change_set)
44
+ additions = change_set.changes.select(&:add?)
45
+
46
+ return false if additions.none?
47
+ return false if change_set.changes.size > additions.size
48
+
49
+ additions.all? { |a| field.ids(on: self).include?(a.id) }
50
+ end
41
51
  end
42
52
 
43
53
  module ClassMethods
@@ -13,6 +13,8 @@ class Tessa::Upload::UploadsFile
13
13
  end
14
14
 
15
15
  def self.connection_factory
16
- Tessa::FakeConnection.new
16
+ Faraday.new do |conn|
17
+ conn.adapter Faraday.default_adapter
18
+ end
17
19
  end
18
20
  end
data/lib/tessa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Tessa
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.2"
3
3
  end
data/lib/tessa.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require "tessa/version"
2
2
 
3
+ require "faraday"
3
4
  require "virtus"
4
5
  require "json"
5
6
 
6
- require "tessa/fake_connection"
7
7
  require "tessa/config"
8
8
  require "tessa/response_factory"
9
9
  require "tessa/asset"
@@ -24,11 +24,16 @@ RSpec.shared_examples_for "remote call macro" do |method, path, return_type|
24
24
  end
25
25
 
26
26
  context "when response is not successful" do
27
- let(:connection) { Tessa::FakeConnection.new }
27
+ let(:faraday_stubs) {
28
+ Faraday::Adapter::Test::Stubs.new do |stub|
29
+ stub.send(method, path) { |env| [422, {}, { "error" => "error" }.to_json] }
30
+ end
31
+ }
28
32
 
29
33
  it "raises Tessa::RequestFailed" do
30
34
  expect{ call }.to raise_error { |error|
31
35
  expect(error).to be_a(Tessa::RequestFailed)
36
+ expect(error.response).to be_a(Faraday::Response)
32
37
  }
33
38
  end
34
39
  end
@@ -67,4 +67,46 @@ RSpec.describe Tessa::Config do
67
67
  expect(cfg.strategy).to eq("my-new-value")
68
68
  end
69
69
  end
70
+
71
+ describe "#connection" do
72
+ it "is a Faraday::Connection" do
73
+ expect(cfg.connection).to be_a(Faraday::Connection)
74
+ end
75
+
76
+ context "with values cfgured" do
77
+ subject { cfg.connection }
78
+ before { args.each { |k, v| cfg.send("#{k}=", v) } }
79
+ let(:args) {
80
+ {
81
+ url: "http://tessa.test",
82
+ username: "username",
83
+ password: "password",
84
+ }
85
+ }
86
+
87
+ it "sets faraday's url prefix to our url" do
88
+ expect(subject.url_prefix.to_s).to match(cfg.url)
89
+ end
90
+
91
+ context "with faraday spy" do
92
+ let(:spy) { instance_spy(Faraday::Connection) }
93
+ before do
94
+ expect(Faraday).to receive(:new).and_yield(spy)
95
+ subject
96
+ end
97
+
98
+ it "sets up url_encoded request handler" do
99
+ expect(spy).to have_received(:request).with(:url_encoded)
100
+ end
101
+
102
+ it "cfgures the default adapter" do
103
+ expect(spy).to have_received(:adapter).with(:net_http)
104
+ end
105
+ end
106
+ end
107
+
108
+ it "caches the result" do
109
+ expect(cfg.connection.object_id).to eq(cfg.connection.object_id)
110
+ end
111
+ end
70
112
  end
@@ -93,10 +93,17 @@ RSpec.describe Tessa::Model do
93
93
  let(:instance) { model.new(another_place: []) }
94
94
  subject(:getter) { instance.multiple_field }
95
95
 
96
- it "No longer calls Tessa::Asset#find" do
96
+ it "calls find for each of the file_ids and returns result" do
97
97
  instance.another_place = [1, 2, 3]
98
- expect(Tessa::Asset).to_not receive(:find)
99
- expect(getter).to eq([])
98
+ expect(Tessa::Asset).to receive(:find).with([1, 2, 3]).and_return([:a1, :a2, :a3])
99
+ expect(getter).to eq([:a1, :a2, :a3])
100
+ end
101
+
102
+ it "caches the result" do
103
+ instance.another_place = [1]
104
+ expect(Tessa::Asset).to receive(:find).and_return(:val).once
105
+ instance.multiple_field
106
+ instance.multiple_field
100
107
  end
101
108
 
102
109
  context "with no values" do
@@ -114,10 +121,17 @@ RSpec.describe Tessa::Model do
114
121
  }
115
122
  subject(:getter) { instance.avatar }
116
123
 
117
- it "No longer calls Tessa::Asset#find" do
124
+ it "calls find for file_id and returns result" do
125
+ instance.avatar_id = 1
126
+ expect(Tessa::Asset).to receive(:find).with(1).and_return(:a1)
127
+ expect(getter).to eq(:a1)
128
+ end
129
+
130
+ it "caches the result" do
118
131
  instance.avatar_id = 1
119
- expect(Tessa::Asset).to_not receive(:find)
120
- expect(getter).to eq(nil)
132
+ expect(Tessa::Asset).to receive(:find).and_return(:val).once
133
+ instance.avatar
134
+ instance.avatar
121
135
  end
122
136
 
123
137
  it "wraps ActiveStorage uploads with AssetWrapper" do
@@ -320,6 +334,245 @@ RSpec.describe Tessa::Model do
320
334
 
321
335
  expect(instance.another_place).to eq(keys)
322
336
  end
337
+
338
+ it 'replaces Tessa assets with ActiveStorage assets' do
339
+ # Before deploying this code, we previously had DB records with Tessa IDs
340
+ instance.update!(another_place: [1, 2, 3])
341
+
342
+ # In this HTTP POST, we removed one of the tessa assets and uploaded a
343
+ # new ActiveStorage asset
344
+ blob = ::ActiveStorage::Blob.create_before_direct_upload!({
345
+ filename: 'README.md',
346
+ byte_size: file.size,
347
+ content_type: file.content_type,
348
+ checksum: '1234'
349
+ })
350
+ changeset = Tessa::AssetChangeSet.new(
351
+ changes: [
352
+ { 'id' => 1, 'action' => 'add' },
353
+ { 'id' => 2, 'action' => 'remove' },
354
+ { 'id' => 3, 'action' => 'add' },
355
+ { 'id' => blob.signed_id, 'action' => 'add' },
356
+ ]
357
+ )
358
+
359
+ # We'll download these assets when we access #multiple_field
360
+ allow(Tessa.config.connection).to receive(:get)
361
+ .with("/assets/1,3")
362
+ .and_return(double("response",
363
+ success?: true,
364
+ body: [
365
+ { 'id' => 1, 'public_url' => 'test1' },
366
+ { 'id' => 2, 'public_url' => 'test2' }
367
+ ].to_json))
368
+
369
+ blob.upload(file)
370
+
371
+ # act
372
+ instance.multiple_field = changeset
373
+
374
+ expect(instance.another_place).to eq([
375
+ blob.key, 1, 3
376
+ ])
377
+
378
+ assets = instance.multiple_field
379
+ expect(assets[0].key).to eq(blob.key)
380
+ expect(assets[0].service_url)
381
+ .to start_with('https://www.example.com/rails/active_storage/disk/')
382
+
383
+ expect(assets[1].id).to eq(1)
384
+ expect(assets[1].public_url).to eq('test1')
385
+ expect(assets[2].id).to eq(2)
386
+ expect(assets[2].public_url).to eq('test2')
387
+ end
388
+ end
389
+ end
390
+
391
+ describe "#apply_tessa_change_sets" do
392
+ let(:instance) { model.new }
393
+ let(:sets) { [ instance_spy(Tessa::AssetChangeSet) ] }
394
+
395
+ before do
396
+ instance.instance_variable_set(
397
+ :@pending_tessa_change_sets,
398
+ {
399
+ avatar: sets[0],
400
+ }
401
+ )
402
+ end
403
+
404
+ it "iterates over all pending changesets calling apply" do
405
+ instance.apply_tessa_change_sets
406
+ expect(sets[0]).to have_received(:apply)
407
+ end
408
+
409
+ it "removes all changesets from list" do
410
+ instance.apply_tessa_change_sets
411
+ expect(instance.pending_tessa_change_sets).to be_empty
412
+ end
413
+
414
+ context "no @pending_tessa_change_sets ivar" do
415
+ before do
416
+ instance.instance_variable_set(
417
+ :@pending_tessa_change_sets,
418
+ nil
419
+ )
420
+ end
421
+
422
+ it "doesn't raise error" do
423
+ expect { instance.apply_tessa_change_sets }.to_not raise_error
424
+ end
323
425
  end
324
426
  end
427
+
428
+ describe "#fetch_tessa_remote_assets" do
429
+ subject(:result) { model.new.fetch_tessa_remote_assets(arg) }
430
+
431
+ context "argument is `nil`" do
432
+ let(:arg) { nil }
433
+
434
+ it "returns nil" do
435
+ expect(result).to be_nil
436
+ end
437
+ end
438
+
439
+ context "argument is `[]`" do
440
+ let(:arg) { [] }
441
+
442
+ it "returns []" do
443
+ expect(result).to be_a(Array)
444
+ expect(result).to be_empty
445
+ end
446
+ end
447
+
448
+ context "when argument is not blank" do
449
+ let(:id) { rand(100) }
450
+ let(:arg) { id }
451
+
452
+ it "calls Tessa::Asset.find with arguments" do
453
+ expect(Tessa::Asset).to receive(:find).with(arg)
454
+ result
455
+ end
456
+
457
+ context "when Tessa::Asset.find raises RequestFailed exception" do
458
+ let(:error) {
459
+ Tessa::RequestFailed.new("test exception", double(status: '500'))
460
+ }
461
+
462
+ before do
463
+ allow(Tessa::Asset).to receive(:find).and_raise(error)
464
+ end
465
+
466
+ context "argument is single id" do
467
+ let(:arg) { id }
468
+
469
+ it "returns Failure" do
470
+ expect(result).to be_a(Tessa::Asset::Failure)
471
+ end
472
+
473
+ it "returns asset with proper data" do
474
+ expect(result.id).to eq(arg)
475
+ end
476
+ end
477
+
478
+ context "argument is array" do
479
+ let(:arg) { [ id, id * 2 ] }
480
+
481
+ it "returns array" do
482
+ expect(result).to be_a(Array)
483
+ end
484
+
485
+ it "returns instances of Failure" do
486
+ expect(result).to all( be_a(Tessa::Asset::Failure) )
487
+ end
488
+
489
+ it "returns array with an asset for each id passed" do
490
+ arg.zip(result) do |a, r|
491
+ expect(r.id).to eq(a)
492
+ end
493
+ end
494
+ end
495
+ end
496
+ end
497
+ end
498
+
499
+ describe "#remove_all_tessa_assets" do
500
+ let(:instance) { model.new }
501
+
502
+ context "with a single typed field" do
503
+ let(:model) {
504
+ SingleAssetModel
505
+ }
506
+
507
+ before do
508
+ instance.avatar_id = 1
509
+ end
510
+
511
+ it "adds pending change sets for each field removing all current assets" do
512
+ instance.remove_all_tessa_assets
513
+ changes = instance.pending_tessa_change_sets.values
514
+ .reduce(Tessa::AssetChangeSet.new, :+)
515
+ .changes
516
+ .map { |change| [change.id, change.action.to_sym] }
517
+ expect(changes).to eq([
518
+ [1, :remove]
519
+ ])
520
+ end
521
+ end
522
+
523
+
524
+ context "with a multiple typed field" do
525
+ let(:model) {
526
+ MultipleAssetModel
527
+ }
528
+ let(:instance) { model.new(another_place: []) }
529
+
530
+ before do
531
+ instance.another_place = [2, 3]
532
+ end
533
+
534
+ it "adds pending change sets for each field removing all current assets" do
535
+ instance.remove_all_tessa_assets
536
+ changes = instance.pending_tessa_change_sets.values
537
+ .reduce(Tessa::AssetChangeSet.new, :+)
538
+ .changes
539
+ .map { |change| [change.id, change.action.to_sym] }
540
+ expect(changes).to eq([
541
+ [2, :remove],
542
+ [3, :remove],
543
+ ])
544
+ end
545
+ end
546
+ end
547
+
548
+ describe "adds callbacks" do
549
+ context "model responds to after_commit" do
550
+ let(:model) {
551
+ Class.new do
552
+ def self.after_commit(arg=nil)
553
+ @after_commit ||= arg
554
+ end
555
+ end.tap { |c| c.send(:include, described_module) }
556
+ }
557
+
558
+ it "calls it with :apply_tessa_change_sets" do
559
+ expect(model.after_commit).to eq(:apply_tessa_change_sets)
560
+ end
561
+ end
562
+
563
+ context "model responds to before_destroy" do
564
+ let(:model) {
565
+ Class.new do
566
+ def self.before_destroy(arg=nil)
567
+ @before_destroy ||= arg
568
+ end
569
+ end.tap { |c| c.send(:include, described_module) }
570
+ }
571
+
572
+ it "calls it with :remove_all_tessa_assets" do
573
+ expect(model.before_destroy).to eq(:remove_all_tessa_assets)
574
+ end
575
+ end
576
+ end
577
+
325
578
  end
data/tessa.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
24
  spec.require_paths = ["lib"]
25
25
 
26
+ spec.add_dependency "faraday"
26
27
  spec.add_dependency "virtus", "~>1.0.4"
27
28
 
28
29
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tessa
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Powell
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-02-09 00:00:00.000000000 Z
12
+ date: 2023-03-20 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: virtus
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -137,7 +151,6 @@ files:
137
151
  - app/javascript/tessa/index.js.coffee
138
152
  - bin/rspec
139
153
  - config/routes.rb
140
- - docs/tessa-activestorage-sequence-diagram.drawio.png
141
154
  - lib/tasks/tessa.rake
142
155
  - lib/tessa.rb
143
156
  - lib/tessa/active_storage/asset_wrapper.rb
@@ -148,7 +161,6 @@ files:
148
161
  - lib/tessa/config.rb
149
162
  - lib/tessa/controller_helpers.rb
150
163
  - lib/tessa/engine.rb
151
- - lib/tessa/fake_connection.rb
152
164
  - lib/tessa/jobs/migrate_assets_job.rb
153
165
  - lib/tessa/model.rb
154
166
  - lib/tessa/model/dynamic_extensions.rb
@@ -238,7 +250,6 @@ files:
238
250
  - spec/tessa/rack_upload_proxy_spec.rb
239
251
  - spec/tessa/upload/uploads_file_spec.rb
240
252
  - spec/tessa/upload_spec.rb
241
- - spec/tessa_spec.rb
242
253
  - tessa.gemspec
243
254
  - yarn.lock
244
255
  homepage: https://github.com/watermarkchurch/tessa-client
@@ -340,4 +351,3 @@ test_files:
340
351
  - spec/tessa/rack_upload_proxy_spec.rb
341
352
  - spec/tessa/upload/uploads_file_spec.rb
342
353
  - spec/tessa/upload_spec.rb
343
- - spec/tessa_spec.rb
@@ -1,29 +0,0 @@
1
- module Tessa
2
- # Since we no longer connect to the Tessa service, fake out the Tessa connection
3
- # so that it always returns 503
4
- class FakeConnection
5
-
6
- [:get, :head, :put, :post, :patch, :delete].each do |method|
7
- define_method(method) do |*args|
8
- if defined?(Bugsnag)
9
- Bugsnag.notify("Tessa::FakeConnection##{method} invoked")
10
- end
11
- Tessa::FakeConnection::Response.new()
12
- end
13
- end
14
-
15
- class Response
16
- def success?
17
- false
18
- end
19
-
20
- def status
21
- 503
22
- end
23
-
24
- def body
25
- '{ "error": "The Tessa connection is no longer implemented." }'
26
- end
27
- end
28
- end
29
- end
data/spec/tessa_spec.rb DELETED
@@ -1,23 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe Tessa do
4
-
5
- describe '#find_assets' do
6
- it 'returns AssetFailure for singular asset' do
7
- result = Tessa.find_assets(1)
8
-
9
- expect(result).to be_a(Tessa::Asset::Failure)
10
- expect(result.message).to eq("The service is unavailable at this time.")
11
- end
12
-
13
- it 'returns AssetFailure array for multiple assets' do
14
- result = Tessa.find_assets([1, 2, 3])
15
-
16
- expect(result.count).to eq(3)
17
- result.each do |r|
18
- expect(r).to be_a(Tessa::Asset::Failure)
19
- expect(r.message).to eq("The service is unavailable at this time.")
20
- end
21
- end
22
- end
23
- end