tessa 2.0 → 6.0.0.rc2
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 +4 -4
- data/README.md +11 -18
- data/app/assets/javascripts/tessa.esm.js +212 -173
- data/app/assets/javascripts/tessa.js +206 -167
- data/app/javascript/activestorage/file_checksum.ts +76 -0
- data/app/javascript/tessa/index.ts +264 -0
- data/app/javascript/tessa/types.ts +34 -0
- data/config/routes.rb +0 -1
- data/lib/tessa/simple_form/asset_input.rb +24 -25
- data/lib/tessa/version.rb +1 -1
- data/lib/tessa.rb +0 -80
- data/package.json +7 -2
- data/rollup.config.js +5 -5
- data/spec/dummy/app/models/single_asset_model.rb +1 -1
- data/spec/dummy/app/models/single_asset_model_form.rb +3 -5
- data/spec/dummy/bin/rails +2 -2
- data/spec/dummy/bin/rake +2 -2
- data/spec/dummy/bin/setup +14 -6
- data/spec/dummy/bin/yarn +9 -3
- data/spec/dummy/config/application.rb +11 -9
- data/spec/dummy/config/boot.rb +1 -1
- data/spec/dummy/config/environment.rb +1 -1
- data/spec/dummy/config/environments/development.rb +34 -5
- data/spec/dummy/config/environments/production.rb +49 -10
- data/spec/dummy/config/environments/test.rb +28 -12
- data/spec/dummy/config/initializers/backtrace_silencers.rb +4 -3
- data/spec/dummy/config/initializers/content_security_policy.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +3 -1
- data/spec/dummy/config/initializers/new_framework_defaults_6_1.rb +67 -0
- data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +5 -0
- data/spec/dummy/config/locales/en.yml +1 -1
- data/spec/dummy/config/routes.rb +1 -1
- data/spec/dummy/config/storage.yml +31 -0
- data/spec/dummy/config.ru +2 -1
- data/spec/dummy/db/migrate/20230406194400_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
- data/spec/dummy/db/migrate/20230406194401_create_active_storage_variant_records.active_storage.rb +27 -0
- data/spec/dummy/db/schema.rb +15 -7
- data/tessa.gemspec +4 -5
- data/tsconfig.json +10 -0
- data/yarn.lock +74 -7
- metadata +36 -74
- data/app/javascript/activestorage/file_checksum.js +0 -53
- data/app/javascript/tessa/index.js.coffee +0 -183
- data/lib/tasks/tessa.rake +0 -18
- data/lib/tessa/active_storage/asset_wrapper.rb +0 -32
- data/lib/tessa/asset/failure.rb +0 -37
- data/lib/tessa/asset.rb +0 -47
- data/lib/tessa/asset_change.rb +0 -49
- data/lib/tessa/asset_change_set.rb +0 -49
- data/lib/tessa/config.rb +0 -16
- data/lib/tessa/controller_helpers.rb +0 -16
- data/lib/tessa/fake_connection.rb +0 -29
- data/lib/tessa/jobs/migrate_assets_job.rb +0 -222
- data/lib/tessa/model/dynamic_extensions.rb +0 -145
- data/lib/tessa/model/field.rb +0 -77
- data/lib/tessa/model.rb +0 -94
- data/lib/tessa/rack_upload_proxy.rb +0 -41
- data/lib/tessa/response_factory.rb +0 -15
- data/lib/tessa/upload/uploads_file.rb +0 -18
- data/lib/tessa/upload.rb +0 -31
- data/lib/tessa/view_helpers.rb +0 -23
- data/spec/dummy/app/models/multiple_asset_model.rb +0 -8
- data/spec/support/remote_call_macro.rb +0 -40
- data/spec/tessa/asset/failure_spec.rb +0 -48
- data/spec/tessa/asset_change_set_spec.rb +0 -198
- data/spec/tessa/asset_change_spec.rb +0 -86
- data/spec/tessa/asset_spec.rb +0 -196
- data/spec/tessa/config_spec.rb +0 -70
- data/spec/tessa/controller_helpers_spec.rb +0 -55
- data/spec/tessa/jobs/migrate_assets_job_spec.rb +0 -247
- data/spec/tessa/model_field_spec.rb +0 -72
- data/spec/tessa/model_spec.rb +0 -325
- data/spec/tessa/rack_upload_proxy_spec.rb +0 -83
- data/spec/tessa/upload/uploads_file_spec.rb +0 -72
- data/spec/tessa/upload_spec.rb +0 -125
- data/spec/tessa_spec.rb +0 -23
data/spec/tessa/asset_spec.rb
DELETED
@@ -1,196 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe Tessa::Asset do
|
4
|
-
subject(:asset) { described_class.new(args) }
|
5
|
-
let(:args) {
|
6
|
-
{
|
7
|
-
id: 123,
|
8
|
-
status: 'completed',
|
9
|
-
strategy: 'default',
|
10
|
-
meta: { name: "foo" },
|
11
|
-
public_url: "http://example.com/public",
|
12
|
-
private_url: "http://example.com/private",
|
13
|
-
private_download_url: "http://example.com/private?download",
|
14
|
-
delete_url: "http://example.com/delete",
|
15
|
-
}
|
16
|
-
}
|
17
|
-
|
18
|
-
describe "#initialize" do
|
19
|
-
context "with all arguments" do
|
20
|
-
it "sets id to attribute" do
|
21
|
-
expect(asset.id).to eq(args[:id])
|
22
|
-
end
|
23
|
-
|
24
|
-
it "sets status to attribute" do
|
25
|
-
expect(asset.status).to eq(args[:status])
|
26
|
-
end
|
27
|
-
|
28
|
-
it "sets strategy to attribute" do
|
29
|
-
expect(asset.strategy).to eq(args[:strategy])
|
30
|
-
end
|
31
|
-
|
32
|
-
it "sets meta to attribute" do
|
33
|
-
expect(asset.meta).to eq(args[:meta])
|
34
|
-
end
|
35
|
-
|
36
|
-
it "sets public_url to attribute" do
|
37
|
-
expect(asset.public_url).to eq(args[:public_url])
|
38
|
-
end
|
39
|
-
|
40
|
-
it "sets private_url to attribute" do
|
41
|
-
expect(asset.private_url).to eq(args[:private_url])
|
42
|
-
end
|
43
|
-
|
44
|
-
it "sets private_download_url to attribute" do
|
45
|
-
expect(asset.private_download_url).to eq(args[:private_download_url])
|
46
|
-
end
|
47
|
-
|
48
|
-
it "sets delete_url to attribute" do
|
49
|
-
expect(asset.delete_url).to eq(args[:delete_url])
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe "#complete!" do
|
56
|
-
subject(:call) { asset.complete!(call_args) }
|
57
|
-
|
58
|
-
include_examples "remote call macro", :patch, "/assets/123/completed", Tessa::Asset
|
59
|
-
end
|
60
|
-
|
61
|
-
describe "#cancel!" do
|
62
|
-
subject(:call) { asset.cancel!(call_args) }
|
63
|
-
|
64
|
-
include_examples "remote call macro", :patch, "/assets/123/cancelled", Tessa::Asset
|
65
|
-
end
|
66
|
-
|
67
|
-
describe "#delete!" do
|
68
|
-
subject(:call) { asset.delete!(call_args) }
|
69
|
-
|
70
|
-
include_examples "remote call macro", :delete, "/assets/123", Tessa::Asset
|
71
|
-
end
|
72
|
-
|
73
|
-
describe "#failure?" do
|
74
|
-
it "returns false" do
|
75
|
-
expect(asset.failure?).to be(false)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe ".find" do
|
80
|
-
let(:faraday_stubs) {
|
81
|
-
Faraday::Adapter::Test::Stubs.new do |stub|
|
82
|
-
stub.get("/assets/#{id_query}") { |env| [200, {}, remote_response.to_json] }
|
83
|
-
end
|
84
|
-
}
|
85
|
-
let(:connection) { Faraday.new { |f| f.adapter :test, faraday_stubs } }
|
86
|
-
|
87
|
-
context "with a single id" do
|
88
|
-
let(:id_query) { "ID" }
|
89
|
-
let(:remote_response) { {} }
|
90
|
-
|
91
|
-
it "calls get('/assets/ID') on connection" do
|
92
|
-
expect(connection).to receive(:get).with("/assets/ID").and_call_original
|
93
|
-
described_class.find("ID", connection: connection)
|
94
|
-
end
|
95
|
-
|
96
|
-
it "returns an instance of Asset" do
|
97
|
-
response = described_class.find("ID", connection: connection)
|
98
|
-
expect(response).to be_a(described_class)
|
99
|
-
end
|
100
|
-
|
101
|
-
it "defaults connection to Tessa.config.connection" do
|
102
|
-
expect(Tessa.config).to receive(:connection).and_return(connection)
|
103
|
-
expect(connection).to receive(:get).and_call_original
|
104
|
-
described_class.find("ID")
|
105
|
-
end
|
106
|
-
|
107
|
-
context "with attributes in response" do
|
108
|
-
subject(:response) { described_class.find("ID", connection: connection) }
|
109
|
-
let(:remote_response) { args }
|
110
|
-
|
111
|
-
it "sets attributes on models" do
|
112
|
-
expect(response.id).to eq(args[:id])
|
113
|
-
expect(response.status).to eq(args[:status])
|
114
|
-
expect(response.meta).to eq(args[:meta])
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
context "with multiple ids" do
|
120
|
-
subject(:response) {
|
121
|
-
described_class.find("ID1", "ID2", connection: connection)
|
122
|
-
}
|
123
|
-
let(:remote_response) { [{}] }
|
124
|
-
let(:id_query) { "ID1,ID2" }
|
125
|
-
|
126
|
-
it "calls get('/assets/ID1,ID2') on connection" do
|
127
|
-
expect(connection).to receive(:get).with("/assets/ID1,ID2").and_call_original
|
128
|
-
response
|
129
|
-
end
|
130
|
-
|
131
|
-
it "returns instances of Asset" do
|
132
|
-
expect(response).to all(be_a(described_class))
|
133
|
-
end
|
134
|
-
|
135
|
-
context "with attributes in response" do
|
136
|
-
let(:remote_response) { [args, args] }
|
137
|
-
|
138
|
-
it "sets attributes on models" do
|
139
|
-
expect(response[0].id).to eq(args[:id])
|
140
|
-
expect(response[1].id).to eq(args[:id])
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
describe ".create" do
|
147
|
-
let(:upload) { instance_double(Tessa::Upload) }
|
148
|
-
|
149
|
-
before :each do
|
150
|
-
allow(Tessa::Upload).to receive(:create).and_return(upload)
|
151
|
-
allow(upload).to receive(:upload_file)
|
152
|
-
end
|
153
|
-
|
154
|
-
it "requires a file param" do
|
155
|
-
expect { described_class.create }.to raise_error(ArgumentError)
|
156
|
-
expect { described_class.create(file: __FILE__) }.to_not raise_error
|
157
|
-
end
|
158
|
-
|
159
|
-
it "creates a new upload" do
|
160
|
-
expect(Tessa::Upload)
|
161
|
-
.to receive(:create).with(hash_including(foo: :bar, baz: :boom)).and_return(upload)
|
162
|
-
described_class.create(file: __FILE__, foo: :bar, baz: :boom)
|
163
|
-
end
|
164
|
-
|
165
|
-
it "uploads the passed file to the upload's URL" do
|
166
|
-
expect(Tessa::Upload).to receive(:create).and_return(upload)
|
167
|
-
expect(upload).to receive(:upload_file).with(__FILE__)
|
168
|
-
described_class.create(file: __FILE__)
|
169
|
-
end
|
170
|
-
|
171
|
-
it "infers the file size, name, and date" do
|
172
|
-
file = Tempfile.new("test")
|
173
|
-
file.write "abc"
|
174
|
-
file.close
|
175
|
-
expected_values = {
|
176
|
-
size: 3,
|
177
|
-
name: File.basename(file.path),
|
178
|
-
date: File.mtime(file.path),
|
179
|
-
}
|
180
|
-
expect(Tessa::Upload)
|
181
|
-
.to receive(:create).with(hash_including(expected_values)).and_return(upload)
|
182
|
-
described_class.create(file: file.path)
|
183
|
-
end
|
184
|
-
|
185
|
-
it "allows overriding inferred values" do
|
186
|
-
expected_values = {
|
187
|
-
size: 1,
|
188
|
-
name: "my-file",
|
189
|
-
date: "123",
|
190
|
-
}
|
191
|
-
expect(Tessa::Upload)
|
192
|
-
.to receive(:create).with(hash_including(expected_values)).and_return(upload)
|
193
|
-
described_class.create(file: __FILE__, **expected_values)
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
data/spec/tessa/config_spec.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe Tessa::Config do
|
4
|
-
let(:cfg) { Tessa::Config.new }
|
5
|
-
|
6
|
-
shared_examples_for "defaults to environment variable" do
|
7
|
-
around { |ex| swap_environment_var(variable_name, 'from-env') { ex.run } }
|
8
|
-
|
9
|
-
it { is_expected.to eq("from-env") }
|
10
|
-
|
11
|
-
def swap_environment_var(var, new_val)
|
12
|
-
old_val = ENV[var]
|
13
|
-
ENV[var] = new_val
|
14
|
-
yield
|
15
|
-
ENV[var] = old_val
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
describe "#username" do
|
20
|
-
it_behaves_like "defaults to environment variable" do
|
21
|
-
let(:variable_name) { 'TESSA_USERNAME' }
|
22
|
-
subject { cfg.username }
|
23
|
-
end
|
24
|
-
|
25
|
-
it "behaves like a normal accessor" do
|
26
|
-
cfg.username = "my-new-value"
|
27
|
-
expect(cfg.username).to eq("my-new-value")
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe "#password" do
|
32
|
-
it_behaves_like "defaults to environment variable" do
|
33
|
-
let(:variable_name) { 'TESSA_PASSWORD' }
|
34
|
-
subject { cfg.password }
|
35
|
-
end
|
36
|
-
|
37
|
-
it "behaves like a normal accessor" do
|
38
|
-
cfg.password = "my-new-value"
|
39
|
-
expect(cfg.password).to eq("my-new-value")
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe "#url" do
|
44
|
-
it_behaves_like "defaults to environment variable" do
|
45
|
-
let(:variable_name) { 'TESSA_URL' }
|
46
|
-
subject { cfg.url }
|
47
|
-
end
|
48
|
-
|
49
|
-
it "behaves like a normal accessor" do
|
50
|
-
cfg.url = "my-new-value"
|
51
|
-
expect(cfg.url).to eq("my-new-value")
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe "#strategy" do
|
56
|
-
it_behaves_like "defaults to environment variable" do
|
57
|
-
let(:variable_name) { 'TESSA_STRATEGY' }
|
58
|
-
subject { cfg.strategy }
|
59
|
-
end
|
60
|
-
|
61
|
-
it "uses the string 'default' when no envvar passed" do
|
62
|
-
expect(cfg.strategy).to eq("default")
|
63
|
-
end
|
64
|
-
|
65
|
-
it "behaves like a normal accessor" do
|
66
|
-
cfg.strategy = "my-new-value"
|
67
|
-
expect(cfg.strategy).to eq("my-new-value")
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe Tessa::ControllerHelpers do
|
4
|
-
let(:controller) {
|
5
|
-
Class.new do
|
6
|
-
attr_writer :session
|
7
|
-
|
8
|
-
def session
|
9
|
-
@session ||= {}
|
10
|
-
end
|
11
|
-
|
12
|
-
end.tap { |c| c.send :include, described_class }
|
13
|
-
}
|
14
|
-
subject(:instance) { controller.new }
|
15
|
-
|
16
|
-
describe "#tessa_upload_asset_ids" do
|
17
|
-
it "returns the value of session[:tessa_upload_asset_ids]" do
|
18
|
-
instance.session = { tessa_upload_asset_ids: :value }
|
19
|
-
expect(instance.tessa_upload_asset_ids).to eq(:value)
|
20
|
-
end
|
21
|
-
|
22
|
-
it "sets and returns session[:tessa_upload_asset_ids] to an empty array if nil" do
|
23
|
-
array = instance.tessa_upload_asset_ids
|
24
|
-
expect(array).to eq([])
|
25
|
-
array << 123
|
26
|
-
expect(instance.tessa_upload_asset_ids).to eq(array)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
describe "#params_for_asset" do
|
31
|
-
it "returns a Tessa::AssetChangeSet" do
|
32
|
-
expect(instance.params_for_asset({})).to be_a(Tessa::AssetChangeSet)
|
33
|
-
end
|
34
|
-
|
35
|
-
it "sets the value of changes to the argument passed" do
|
36
|
-
changes = [Tessa::AssetChange.new(id: 1)]
|
37
|
-
expect(instance.params_for_asset(changes).changes).to eq(changes)
|
38
|
-
end
|
39
|
-
|
40
|
-
it "sets the scoped_ids to the value of tessa_upload_asset_ids" do
|
41
|
-
scoped_ids = [1, 2, 3]
|
42
|
-
instance.session[:tessa_upload_asset_ids] = scoped_ids
|
43
|
-
expect(instance.params_for_asset([]).scoped_ids).to eq(scoped_ids)
|
44
|
-
end
|
45
|
-
|
46
|
-
it "ensures a unique array" do
|
47
|
-
scoped_ids = [1]
|
48
|
-
instance.session[:tessa_upload_asset_ids] = scoped_ids
|
49
|
-
set = instance.params_for_asset([])
|
50
|
-
set.scoped_ids << 2
|
51
|
-
expect(scoped_ids).to eq([1])
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
@@ -1,247 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
require 'tessa/jobs/migrate_assets_job'
|
4
|
-
|
5
|
-
RSpec.describe Tessa::MigrateAssetsJob do
|
6
|
-
it 'does nothing if no db rows' do
|
7
|
-
|
8
|
-
expect {
|
9
|
-
subject.perform
|
10
|
-
}.to_not change { ActiveStorage::Attachment.count }
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'creates attachments for old tessa assets' do
|
14
|
-
allow(Tessa::Asset).to receive(:find)
|
15
|
-
.with(1)
|
16
|
-
.and_return(
|
17
|
-
Tessa::Asset.new(id: 1,
|
18
|
-
meta: { name: 'README.md' },
|
19
|
-
private_download_url: 'https://test.com/README.md')
|
20
|
-
)
|
21
|
-
|
22
|
-
allow(Tessa::Asset).to receive(:find)
|
23
|
-
.with(2)
|
24
|
-
.and_return(
|
25
|
-
Tessa::Asset.new(id: 2,
|
26
|
-
meta: { name: 'LICENSE.txt' },
|
27
|
-
private_download_url: 'https://test.com/LICENSE.txt'),
|
28
|
-
)
|
29
|
-
|
30
|
-
stub_request(:get, 'https://test.com/README.md')
|
31
|
-
.to_return(body: File.new('README.md'))
|
32
|
-
stub_request(:get, 'https://test.com/LICENSE.txt')
|
33
|
-
.to_return(body: File.new('LICENSE.txt'))
|
34
|
-
|
35
|
-
# DB models with those
|
36
|
-
models = [
|
37
|
-
SingleAssetModel.create!(avatar_id: 1),
|
38
|
-
SingleAssetModel.create!(avatar_id: 2)
|
39
|
-
]
|
40
|
-
|
41
|
-
expect {
|
42
|
-
subject.perform
|
43
|
-
}.to change { ActiveStorage::Attachment.count }.by(2)
|
44
|
-
|
45
|
-
models.each do |m|
|
46
|
-
expect(m.reload.avatar_id).to eq(m.avatar.key)
|
47
|
-
# Now it's in activestorage
|
48
|
-
expect(m.avatar.public_url).to start_with(
|
49
|
-
'https://www.example.com/rails/active_storage/blobs/')
|
50
|
-
end
|
51
|
-
|
52
|
-
expect(SingleAssetModel.where.not('avatar_id' => nil).count)
|
53
|
-
.to eq(0)
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'preserves ActiveStorage blobs' do
|
57
|
-
allow(Tessa::Asset).to receive(:find)
|
58
|
-
.with([1, 2])
|
59
|
-
.and_return([
|
60
|
-
Tessa::Asset.new(id: 1,
|
61
|
-
meta: { name: 'README.md' },
|
62
|
-
private_download_url: 'https://test.com/README.md'),
|
63
|
-
Tessa::Asset.new(id: 2,
|
64
|
-
meta: { name: 'LICENSE.txt' },
|
65
|
-
private_download_url: 'https://test.com/LICENSE.txt'),
|
66
|
-
])
|
67
|
-
stub_request(:get, 'https://test.com/README.md')
|
68
|
-
.to_return(body: File.new('README.md'))
|
69
|
-
stub_request(:get, 'https://test.com/LICENSE.txt')
|
70
|
-
.to_return(body: File.new('LICENSE.txt'))
|
71
|
-
|
72
|
-
file2 = Rack::Test::UploadedFile.new("LICENSE.txt")
|
73
|
-
|
74
|
-
model = MultipleAssetModel.create!(
|
75
|
-
# The Tessa DB column has the one asset
|
76
|
-
another_place: [1, 2]
|
77
|
-
)
|
78
|
-
# But has already attached a second ActiveStorage blob
|
79
|
-
::ActiveStorage::Attached::Many.new("multiple_field", model, dependent: :purge_later)
|
80
|
-
.attach(file2)
|
81
|
-
model.save!
|
82
|
-
attachment = model.multiple_field_attachments.first
|
83
|
-
|
84
|
-
expect {
|
85
|
-
subject.perform
|
86
|
-
}.to change { ActiveStorage::Attachment.count }.by(2)
|
87
|
-
|
88
|
-
model = model.reload
|
89
|
-
# The IDs are now the keys of ActiveStorage objects
|
90
|
-
expect(model.another_place).to eq(
|
91
|
-
model.multiple_field.map(&:key))
|
92
|
-
# preserves the existing attachment
|
93
|
-
expect(model.multiple_field_attachments).to include(attachment)
|
94
|
-
expect(model.another_place).to include(attachment.key)
|
95
|
-
|
96
|
-
# all assets are in activestorage
|
97
|
-
model.multiple_field.each do |blob|
|
98
|
-
expect(blob.public_url).to start_with(
|
99
|
-
'https://www.example.com/rails/active_storage/blobs/')
|
100
|
-
end
|
101
|
-
|
102
|
-
# DB column is reset to nil
|
103
|
-
expect(MultipleAssetModel.where.not('another_place' => nil).count)
|
104
|
-
.to eq(0)
|
105
|
-
end
|
106
|
-
|
107
|
-
it 'Stops after hitting the batch size' do
|
108
|
-
1.upto(11).each do |i|
|
109
|
-
asset = Tessa::Asset.new(id: i,
|
110
|
-
meta: { name: 'README.md' },
|
111
|
-
private_download_url: 'https://test.com/README.md')
|
112
|
-
allow(Tessa::Asset).to receive(:find)
|
113
|
-
.with(i)
|
114
|
-
.and_return(asset)
|
115
|
-
allow(Tessa::Asset).to receive(:find)
|
116
|
-
.with([i])
|
117
|
-
.and_return([asset])
|
118
|
-
end
|
119
|
-
stub_request(:get, 'https://test.com/README.md')
|
120
|
-
.to_return(body: File.new('README.md'))
|
121
|
-
|
122
|
-
# Mix of the two models...
|
123
|
-
models =
|
124
|
-
1.upto(11).map do |i|
|
125
|
-
if i % 2 == 0
|
126
|
-
MultipleAssetModel.create!(another_place: [i])
|
127
|
-
else
|
128
|
-
SingleAssetModel.create!(avatar_id: i)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
final_state = nil
|
133
|
-
final_options = nil
|
134
|
-
dbl = double('set')
|
135
|
-
expect(dbl).to receive(:perform_later) do |state, options|
|
136
|
-
final_state = Marshal.load(state)
|
137
|
-
final_options = options
|
138
|
-
end
|
139
|
-
expect(Tessa::MigrateAssetsJob).to receive(:set)
|
140
|
-
.with(wait: 10.minutes)
|
141
|
-
.and_return(dbl)
|
142
|
-
|
143
|
-
expect {
|
144
|
-
subject.perform
|
145
|
-
}.to change { ActiveStorage::Attachment.count }.by(10)
|
146
|
-
|
147
|
-
expect(final_state.fully_processed?).to be false
|
148
|
-
expect(final_options).to eq({
|
149
|
-
batch_size: 10,
|
150
|
-
interval: 10.minutes.to_i
|
151
|
-
})
|
152
|
-
# One of the two models was fully processed
|
153
|
-
expect(final_state.model_queue.count(&:fully_processed?))
|
154
|
-
.to eq(1)
|
155
|
-
end
|
156
|
-
|
157
|
-
it 'Skips over Tessa errors' do
|
158
|
-
1.upto(11).each do |i|
|
159
|
-
if i % 2 == 0
|
160
|
-
allow(Tessa::Asset).to receive(:find)
|
161
|
-
.with(i)
|
162
|
-
.and_raise(Tessa::RequestFailed)
|
163
|
-
next
|
164
|
-
end
|
165
|
-
|
166
|
-
allow(Tessa::Asset).to receive(:find)
|
167
|
-
.with(i)
|
168
|
-
.and_return(
|
169
|
-
Tessa::Asset.new(id: i,
|
170
|
-
meta: { name: 'README.md' },
|
171
|
-
private_download_url: 'https://test.com/README.md')
|
172
|
-
)
|
173
|
-
end
|
174
|
-
stub_request(:get, 'https://test.com/README.md')
|
175
|
-
.to_return(body: File.new('README.md'))
|
176
|
-
|
177
|
-
# Mix of the two models...
|
178
|
-
models =
|
179
|
-
1.upto(11).map do |i|
|
180
|
-
SingleAssetModel.create!(avatar_id: i)
|
181
|
-
end
|
182
|
-
|
183
|
-
final_state = nil
|
184
|
-
final_options = nil
|
185
|
-
dbl = double('set')
|
186
|
-
expect(dbl).to receive(:perform_later) do |state, options|
|
187
|
-
final_state = Marshal.load(state)
|
188
|
-
final_options = options
|
189
|
-
end
|
190
|
-
expect(Tessa::MigrateAssetsJob).to receive(:set)
|
191
|
-
.with(wait: 10.minutes)
|
192
|
-
.and_return(dbl)
|
193
|
-
|
194
|
-
expect {
|
195
|
-
subject.perform
|
196
|
-
}.to change { ActiveStorage::Attachment.count }.by(5)
|
197
|
-
|
198
|
-
expect(final_state.fully_processed?).to be false
|
199
|
-
field_state = final_state.next_model.next_field
|
200
|
-
expect(field_state.offset).to eq(5)
|
201
|
-
end
|
202
|
-
|
203
|
-
it 'Resumes from marshalled state' do
|
204
|
-
|
205
|
-
file = Rack::Test::UploadedFile.new('README.md')
|
206
|
-
|
207
|
-
state = Tessa::MigrateAssetsJob::ProcessingState.initialize_from_models(
|
208
|
-
[SingleAssetModel])
|
209
|
-
field_state = state.model_queue
|
210
|
-
.detect { |m| m.class_name == 'SingleAssetModel' }
|
211
|
-
.field_queue
|
212
|
-
.detect { |m| m.field_name == :avatar }
|
213
|
-
|
214
|
-
1.upto(10).each do |i|
|
215
|
-
if i % 2 == 0
|
216
|
-
# This one failed
|
217
|
-
SingleAssetModel.create!(avatar_id: i).tap do |r|
|
218
|
-
field_state.offset += 1
|
219
|
-
end
|
220
|
-
else
|
221
|
-
# This one succeeded and is in ActiveStorage
|
222
|
-
SingleAssetModel.create!(avatar: file).tap do |r|
|
223
|
-
field_state.success_count += 1
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
# This one still needs to transition
|
229
|
-
model = SingleAssetModel.create!(avatar_id: 11)
|
230
|
-
|
231
|
-
asset = Tessa::Asset.new(id: 11,
|
232
|
-
meta: { name: 'README.md' },
|
233
|
-
private_download_url: 'https://test.com/README.md')
|
234
|
-
allow(Tessa::Asset).to receive(:find)
|
235
|
-
.with(11)
|
236
|
-
.and_return(asset)
|
237
|
-
stub_request(:get, 'https://test.com/README.md')
|
238
|
-
.to_return(body: File.new('README.md'))
|
239
|
-
|
240
|
-
# Doesn't reenqueue since we finished processing
|
241
|
-
expect(Tessa::MigrateAssetsJob).to_not receive(:set)
|
242
|
-
|
243
|
-
expect {
|
244
|
-
subject.perform(Marshal.dump(state), { batch_size: 2, interval: 3 })
|
245
|
-
}.to change { ActiveStorage::Attachment.count }.by(1)
|
246
|
-
end
|
247
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe Tessa::Model::Field do
|
4
|
-
subject(:field) { described_class.new(attrs) }
|
5
|
-
let(:attrs) { {} }
|
6
|
-
|
7
|
-
describe "#initialize" do
|
8
|
-
context "with all fields set" do
|
9
|
-
let(:attrs) {
|
10
|
-
{
|
11
|
-
model: :model,
|
12
|
-
name: "name",
|
13
|
-
multiple: true,
|
14
|
-
id_field: "my_field",
|
15
|
-
}
|
16
|
-
}
|
17
|
-
|
18
|
-
it "sets :model to attribute" do
|
19
|
-
expect(field.model).to eq(:model)
|
20
|
-
end
|
21
|
-
|
22
|
-
it "sets :name to attribute" do
|
23
|
-
expect(field.name).to eq("name")
|
24
|
-
end
|
25
|
-
|
26
|
-
it "sets :multiple to attribute" do
|
27
|
-
expect(field.multiple).to eq(true)
|
28
|
-
end
|
29
|
-
|
30
|
-
it "sets :id_field to attribute" do
|
31
|
-
expect(field.id_field).to eq("my_field")
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context "with no fields set" do
|
36
|
-
it "defaults model to nil" do
|
37
|
-
expect(field.model).to be_nil
|
38
|
-
end
|
39
|
-
|
40
|
-
it "defaults name to nil" do
|
41
|
-
expect(field.name).to be_nil
|
42
|
-
end
|
43
|
-
|
44
|
-
it "defaults multiple to false" do
|
45
|
-
expect(field.multiple).to eq(false)
|
46
|
-
end
|
47
|
-
|
48
|
-
context "when multiple true" do
|
49
|
-
before do
|
50
|
-
attrs[:name] = "my_name"
|
51
|
-
attrs[:multiple] = false
|
52
|
-
end
|
53
|
-
|
54
|
-
it "defaults id_field to name + '_id'" do
|
55
|
-
expect(field.id_field).to eq("my_name_id")
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
context "when multiple false" do
|
60
|
-
before do
|
61
|
-
attrs[:name] = "my_name"
|
62
|
-
attrs[:multiple] = true
|
63
|
-
end
|
64
|
-
|
65
|
-
it "defaults id_field to name + '_ids'" do
|
66
|
-
expect(field.id_field).to eq("my_name_ids")
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|