leifcr-refile 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/javascripts/refile.js +125 -0
  3. data/config/locales/en.yml +10 -0
  4. data/config/routes.rb +5 -0
  5. data/lib/refile.rb +510 -0
  6. data/lib/refile/app.rb +186 -0
  7. data/lib/refile/attacher.rb +190 -0
  8. data/lib/refile/attachment.rb +108 -0
  9. data/lib/refile/attachment/active_record.rb +133 -0
  10. data/lib/refile/attachment_definition.rb +83 -0
  11. data/lib/refile/backend/file_system.rb +120 -0
  12. data/lib/refile/backend/s3.rb +1 -0
  13. data/lib/refile/backend_macros.rb +45 -0
  14. data/lib/refile/custom_logger.rb +48 -0
  15. data/lib/refile/file.rb +102 -0
  16. data/lib/refile/file_double.rb +13 -0
  17. data/lib/refile/image_processing.rb +1 -0
  18. data/lib/refile/rails.rb +54 -0
  19. data/lib/refile/rails/attachment_helper.rb +121 -0
  20. data/lib/refile/random_hasher.rb +11 -0
  21. data/lib/refile/signature.rb +36 -0
  22. data/lib/refile/simple_form.rb +17 -0
  23. data/lib/refile/type.rb +28 -0
  24. data/lib/refile/version.rb +3 -0
  25. data/spec/refile/active_record_helper.rb +35 -0
  26. data/spec/refile/app_spec.rb +424 -0
  27. data/spec/refile/attachment/active_record_spec.rb +568 -0
  28. data/spec/refile/attachment_helper_spec.rb +78 -0
  29. data/spec/refile/attachment_spec.rb +589 -0
  30. data/spec/refile/backend/file_system_spec.rb +5 -0
  31. data/spec/refile/backend_examples.rb +228 -0
  32. data/spec/refile/backend_macros_spec.rb +83 -0
  33. data/spec/refile/custom_logger_spec.rb +21 -0
  34. data/spec/refile/features/direct_upload_spec.rb +63 -0
  35. data/spec/refile/features/multiple_upload_spec.rb +122 -0
  36. data/spec/refile/features/normal_upload_spec.rb +144 -0
  37. data/spec/refile/features/presigned_upload_spec.rb +31 -0
  38. data/spec/refile/features/simple_form_spec.rb +8 -0
  39. data/spec/refile/fixtures/hello.txt +1 -0
  40. data/spec/refile/fixtures/image.jpg +0 -0
  41. data/spec/refile/fixtures/large.txt +44 -0
  42. data/spec/refile/fixtures/monkey.txt +1 -0
  43. data/spec/refile/fixtures/world.txt +1 -0
  44. data/spec/refile/spec_helper.rb +72 -0
  45. data/spec/refile_spec.rb +355 -0
  46. metadata +143 -0
@@ -0,0 +1,5 @@
1
+ RSpec.describe Refile::Backend::FileSystem do
2
+ let(:backend) { Refile::Backend::FileSystem.new(File.expand_path("tmp/store1", Dir.pwd), max_size: 100) }
3
+
4
+ it_behaves_like :backend
5
+ end
@@ -0,0 +1,228 @@
1
+ RSpec.shared_examples_for :backend do
2
+ def uploadable(data = "hello")
3
+ Refile::FileDouble.new(data)
4
+ end
5
+
6
+ describe "#upload" do
7
+ it "raises ArgumentError when invalid object is uploaded" do
8
+ expect { backend.upload(double(size: 123)) }.to raise_error(Refile::InvalidFile)
9
+ expect { backend.upload("hello") }.to raise_error(Refile::InvalidFile)
10
+ end
11
+
12
+ it "raises Refile::Invalid when object is too large" do
13
+ expect { backend.upload(uploadable("a" * 200)) }.to raise_error(Refile::Invalid)
14
+ end
15
+
16
+ it "stores file for later retrieval" do
17
+ file = backend.upload(uploadable)
18
+ retrieved = backend.get(file.id)
19
+
20
+ expect(retrieved.read).to eq("hello")
21
+ expect(retrieved.size).to eq(5)
22
+ expect(retrieved.exists?).to be_truthy
23
+ end
24
+
25
+ it "can store an uploaded file" do
26
+ file = backend.upload(uploadable)
27
+ file2 = backend.upload(file)
28
+
29
+ retrieved = backend.get(file2.id)
30
+
31
+ expect(retrieved.read).to eq("hello")
32
+ expect(retrieved.size).to eq(5)
33
+ expect(retrieved.exists?).to be_truthy
34
+ end
35
+ end
36
+
37
+ describe "#delete" do
38
+ it "removes a stored file" do
39
+ file = backend.upload(uploadable)
40
+
41
+ backend.delete(file.id)
42
+
43
+ expect(backend.get(file.id).exists?).to be_falsy
44
+ end
45
+
46
+ it "does not affect other files" do
47
+ file = backend.upload(uploadable)
48
+ other = backend.upload(uploadable)
49
+
50
+ backend.delete(file.id)
51
+
52
+ expect(backend.get(file.id).exists?).to be_falsy
53
+ expect(backend.get(other.id).exists?).to be_truthy
54
+ end
55
+
56
+ it "does nothing when file doesn't exist" do
57
+ file = backend.upload(uploadable)
58
+
59
+ backend.delete(file.id)
60
+ backend.delete(file.id)
61
+ end
62
+
63
+ it "can be called through file" do
64
+ file = backend.upload(uploadable)
65
+
66
+ file.delete
67
+
68
+ expect(backend.get(file.id).exists?).to be_falsy
69
+ end
70
+
71
+ it "raises error when called with invalid ID" do
72
+ expect { backend.delete("/evil") }.to raise_error(Refile::InvalidID)
73
+ end
74
+ end
75
+
76
+ describe "#read" do
77
+ it "returns file contents" do
78
+ file = backend.upload(uploadable)
79
+ expect(backend.read(file.id)).to eq("hello")
80
+ end
81
+
82
+ it "returns nil when file doesn't exist" do
83
+ expect(backend.read("nosuchfile")).to be_nil
84
+ end
85
+
86
+ it "can be called through file" do
87
+ file = backend.upload(uploadable)
88
+ expect(file.read).to eq("hello")
89
+ end
90
+
91
+ it "raises error when called with invalid ID" do
92
+ expect { backend.read("/evil") }.to raise_error(Refile::InvalidID)
93
+ end
94
+ end
95
+
96
+ describe "#size" do
97
+ it "returns file size" do
98
+ file = backend.upload(uploadable)
99
+ expect(backend.size(file.id)).to eq(5)
100
+ end
101
+
102
+ it "returns nil when file doesn't exist" do
103
+ expect(backend.size("nosuchfile")).to be_nil
104
+ end
105
+
106
+ it "can be called through file" do
107
+ file = backend.upload(uploadable)
108
+ expect(file.size).to eq(5)
109
+ end
110
+
111
+ it "raises error when called with invalid ID" do
112
+ expect { backend.size("/evil") }.to raise_error(Refile::InvalidID)
113
+ end
114
+ end
115
+
116
+ describe "#exists?" do
117
+ it "returns true when file exists" do
118
+ file = backend.upload(uploadable)
119
+ expect(backend.exists?(file.id)).to eq(true)
120
+ end
121
+
122
+ it "returns false when file doesn't exist" do
123
+ expect(backend.exists?("nosuchfile")).to eq(false)
124
+ end
125
+
126
+ it "can be called through file" do
127
+ expect(backend.upload(uploadable).exists?).to eq(true)
128
+ expect(backend.get("nosuchfile").exists?).to eq(false)
129
+ end
130
+
131
+ it "raises error when called with invalid ID" do
132
+ expect { backend.exists?("/evil") }.to raise_error(Refile::InvalidID)
133
+ end
134
+ end
135
+
136
+ describe "#clear!" do
137
+ it "removes files when called with :confirm" do
138
+ file = backend.upload(uploadable)
139
+
140
+ backend.clear!(:confirm)
141
+
142
+ expect(backend.get(file.id).exists?).to be_falsy
143
+ end
144
+
145
+ it "complains when called without confirm" do
146
+ file = backend.upload(uploadable)
147
+
148
+ expect { backend.clear! }.to raise_error(Refile::Confirm)
149
+
150
+ expect(backend.get(file.id).exists?).to be_truthy
151
+ end
152
+ end
153
+
154
+ describe "#max_size" do
155
+ it "returns the given max size" do
156
+ expect(backend.max_size).to eq(100)
157
+ end
158
+ end
159
+
160
+ describe "File" do
161
+ it "is an io-like object" do
162
+ file = backend.upload(uploadable)
163
+
164
+ buffer = ""
165
+ result = ""
166
+
167
+ until file.eof?
168
+ chunk = file.read(2, buffer)
169
+ result << chunk
170
+ expect("hello").to include(buffer)
171
+ end
172
+
173
+ expect(result).to eq("hello")
174
+
175
+ file.close
176
+
177
+ expect { file.read(1, buffer) }.to raise_error
178
+ end
179
+
180
+ describe "#rewind" do
181
+ it "rewinds file to beginning" do
182
+ file = backend.upload(uploadable)
183
+
184
+ expect(file.read(2)).to eq("he")
185
+ expect(file.read(2)).to eq("ll")
186
+ file.rewind
187
+ expect(file.read(2)).to eq("he")
188
+ end
189
+ end
190
+
191
+ describe "#read" do
192
+ it "can read file contents" do
193
+ file = backend.upload(uploadable)
194
+
195
+ expect(file.read).to eq("hello")
196
+ end
197
+
198
+ it "can read entire file contents into buffer" do
199
+ file = backend.upload(uploadable)
200
+
201
+ buffer = ""
202
+
203
+ file.read(nil, buffer)
204
+
205
+ expect(buffer).to eq("hello")
206
+ end
207
+ end
208
+
209
+ describe "#download" do
210
+ it "returns a downloaded tempfile" do
211
+ file = backend.upload(uploadable)
212
+ download = file.download
213
+
214
+ expect(download).to be_an_instance_of(Tempfile)
215
+ expect(File.read(download.path)).to eq("hello")
216
+ end
217
+ end
218
+
219
+ describe "#as_json" do
220
+ it "returns id and stringified backend as a Hash" do
221
+ file = backend.upload(uploadable)
222
+ hash = file.as_json
223
+
224
+ expect(hash.keys).to eq [:id, :backend]
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,83 @@
1
+ RSpec.describe Refile::BackendMacros do
2
+ let(:klass) do
3
+ Class.new do
4
+ extend Refile::BackendMacros
5
+
6
+ attr_accessor :max_size
7
+
8
+ verify_uploadable def upload(uploadable)
9
+ uploadable
10
+ end
11
+
12
+ verify_id def get(id)
13
+ id
14
+ end
15
+ end
16
+ end
17
+ let(:instance) { klass.new }
18
+
19
+ describe "#verify_uploadable" do
20
+ let(:io) { StringIO.new("hello") }
21
+
22
+ it "works if it conforms to required API" do
23
+ expect(instance.upload(double(size: 444, read: io, eof?: true, rewind: true, close: nil))).to be_truthy
24
+ end
25
+
26
+ it "raises ArgumentError if argument does not respond to `size`" do
27
+ expect { instance.upload(double(read: io, eof?: true, rewind: true, close: nil)) }.to raise_error(Refile::InvalidFile)
28
+ end
29
+
30
+ it "raises ArgumentError if argument does not respond to `read`" do
31
+ expect { instance.upload(double(size: 444, eof?: true, rewind: true, close: nil)) }.to raise_error(Refile::InvalidFile)
32
+ end
33
+
34
+ it "raises ArgumentError if argument does not respond to `eof?`" do
35
+ expect { instance.upload(double(size: 444, read: true, rewind: true, close: nil)) }.to raise_error(Refile::InvalidFile)
36
+ end
37
+
38
+ it "raises ArgumentError if argument does not respond to `rewind`" do
39
+ expect { instance.upload(double(size: 444, read: true, eof?: true, close: nil)) }.to raise_error(Refile::InvalidFile)
40
+ end
41
+
42
+ it "raises ArgumentError if argument does not respond to `close`" do
43
+ expect { instance.upload(double(size: 444, read: true, rewind: true, eof?: true)) }.to raise_error(Refile::InvalidFile)
44
+ end
45
+
46
+ it "returns true if size is respeced" do
47
+ instance.max_size = 8
48
+ expect(instance.upload(Refile::FileDouble.new("hello"))).to be_truthy
49
+ end
50
+
51
+ it "raises Refile::Invalid if size is exceeded" do
52
+ instance.max_size = 8
53
+ expect { instance.upload(Refile::FileDouble.new("hello world")) }.to raise_error(Refile::InvalidMaxSize)
54
+ end
55
+ end
56
+
57
+ describe "#verify_id" do
58
+ it "works if it has a valid ID" do
59
+ expect(instance.get("1234aBCde123aee")).to be_truthy
60
+ end
61
+
62
+ it "raises ArgumentError if argument does not respond to `size`" do
63
+ expect { instance.get("ev/il") }.to raise_error(Refile::InvalidID)
64
+ end
65
+ end
66
+
67
+ describe "#valid_id?" do
68
+ it "returns true for valid ID" do
69
+ expect(klass.valid_id?("1234aBCde123aee")).to be_truthy
70
+ end
71
+
72
+ it "returns false for invalid ID" do
73
+ expect(klass.valid_id?("ev/il")).to be_falsey
74
+ end
75
+ end
76
+
77
+ describe "#decode_id" do
78
+ it "returns to_s" do
79
+ expect(klass.decode_id("1234aBCde123aee")).to eq "1234aBCde123aee"
80
+ expect(klass.decode_id(1234)).to eq "1234"
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,21 @@
1
+ require "refile/custom_logger"
2
+
3
+ describe Refile::CustomLogger do
4
+ let(:rack_app) do
5
+ ->(_) { [200, {}, ["Success"]] }
6
+ end
7
+ let(:io) { StringIO.new }
8
+ let(:env) do
9
+ { "QUERY_STRING" => "",
10
+ "REQUEST_METHOD" => "POST",
11
+ "PATH_INFO" => "/" }
12
+ end
13
+
14
+ let(:expected_format) { %r{Prefix: \[[^\]]+\] POST "/" 200 \d+\.\d+ms\n\n$} }
15
+
16
+ it "uses a dynamic logger" do
17
+ _, _, body = described_class.new(rack_app, "Prefix", -> { Logger.new(io) }).call(env)
18
+ body.close
19
+ expect(io.tap(&:rewind).read).to match(expected_format)
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ require "refile/test_app"
2
+
3
+ feature "Direct HTTP post file uploads", :js do
4
+ scenario "Successfully upload a file" do
5
+ visit "/direct/posts/new"
6
+ fill_in "Title", with: "A cool post"
7
+ attach_file "Document", path("hello.txt")
8
+
9
+ expect(page).to have_content("Upload started")
10
+ expect(page).to have_content("Upload success")
11
+ expect(page).to have_content("Upload complete")
12
+ expect(page).to have_content("All uploads complete")
13
+
14
+ click_button "Create"
15
+
16
+ expect(page).to have_selector("h1", text: "A cool post")
17
+ expect(page).to have_selector(".content-type", text: "text/plain")
18
+ expect(page).to have_selector(".size", text: "6")
19
+ expect(page).to have_selector(".filename", text: "hello.txt")
20
+ expect(download_link("Document")).to eq("hello")
21
+ end
22
+
23
+ scenario "Fail to upload a file that is too large" do
24
+ visit "/direct/posts/new"
25
+ fill_in "Title", with: "A cool post"
26
+ attach_file "Document", path("large.txt")
27
+
28
+ expect(page).to have_content("Upload started")
29
+ expect(page).to have_content("Upload failure error")
30
+ end
31
+
32
+ scenario "Upload a file after validation failure" do
33
+ visit "/direct/posts/new"
34
+ fill_in "Title", with: "A cool post"
35
+ check "Requires document"
36
+ click_button "Create"
37
+
38
+ attach_file "Document", path("hello.txt")
39
+
40
+ expect(page).to have_content("Upload started")
41
+ expect(page).to have_content("Upload success")
42
+ expect(page).to have_content("Upload complete")
43
+
44
+ click_button "Create"
45
+
46
+ expect(download_link("Document")).to eq("hello")
47
+ end
48
+
49
+ scenario "Fail to upload a file that has wrong format" do
50
+ visit "/direct/posts/new"
51
+ fill_in "Title", with: "A cool post"
52
+ attach_file "Image", path("large.txt")
53
+
54
+ expect(page).to have_content("Upload started")
55
+ expect(page).to have_content("Upload success")
56
+ expect(page).to have_content("Upload complete")
57
+
58
+ click_button "Create"
59
+
60
+ expect(page).to have_selector(".field_with_errors")
61
+ expect(page).to have_content("You are not allowed to upload text/plain file format. Allowed types: image/jpeg, image/gif, and image/png.")
62
+ end
63
+ end
@@ -0,0 +1,122 @@
1
+ require "refile/test_app"
2
+
3
+ feature "Multiple file uploads", :js do
4
+ scenario "Upload multiple files" do
5
+ visit "/multiple/posts/new"
6
+ fill_in "Title", with: "A cool post"
7
+ attach_file "Documents", [path("hello.txt"), path("world.txt")]
8
+ click_button "Create"
9
+
10
+ expect(download_link("Document: hello.txt")).to eq("hello")
11
+ expect(download_link("Document: world.txt")).to eq("world")
12
+ end
13
+
14
+ scenario "Fail to upload a file that is too large" do
15
+ visit "/multiple/posts/new"
16
+ fill_in "Title", with: "A cool post"
17
+ attach_file "Documents", [path("hello.txt"), path("large.txt")]
18
+ click_button "Create"
19
+
20
+ expect(page).to have_content("Documents is invalid")
21
+ end
22
+
23
+ scenario "Fail to upload a file that has the wrong format then submit" do
24
+ visit "/multiple/posts/new"
25
+ fill_in "Title", with: "A cool post"
26
+ attach_file "Documents", [path("hello.txt"), path("image.jpg")]
27
+ click_button "Create"
28
+
29
+ expect(page).to have_content("Documents is invalid")
30
+ click_button "Create"
31
+ expect(page).to have_selector("h1", text: "A cool post")
32
+ expect(page).not_to have_link("Document")
33
+ end
34
+
35
+ scenario "Upload files via form redisplay", js: true do
36
+ visit "/multiple/posts/new"
37
+ attach_file "Documents", [path("hello.txt"), path("world.txt")]
38
+ click_button "Create"
39
+ fill_in "Title", with: "A cool post"
40
+ click_button "Create"
41
+
42
+ expect(download_link("Document: hello.txt")).to eq("hello")
43
+ expect(download_link("Document: world.txt")).to eq("world")
44
+ end
45
+
46
+ scenario "Edit with changes" do
47
+ visit "/multiple/posts/new"
48
+ fill_in "Title", with: "A cool post"
49
+ attach_file "Documents", [path("hello.txt"), path("world.txt")]
50
+ click_button "Create"
51
+
52
+ click_link "Edit multiple"
53
+ attach_file "Documents", [path("monkey.txt")]
54
+ click_button "Update"
55
+
56
+ expect(download_link("Document: monkey.txt")).to eq("monkey")
57
+ expect(page).not_to have_link("Document: hello.txt")
58
+ expect(page).not_to have_link("Document: world.txt")
59
+ end
60
+
61
+ scenario "Edit without changes" do
62
+ visit "/multiple/posts/new"
63
+ fill_in "Title", with: "A cool post"
64
+ attach_file "Documents", [path("hello.txt"), path("world.txt")]
65
+ click_button "Create"
66
+
67
+ click_link "Edit multiple"
68
+ click_button "Update"
69
+
70
+ expect(download_link("Document: hello.txt")).to eq("hello")
71
+ expect(download_link("Document: world.txt")).to eq("world")
72
+ end
73
+
74
+ describe "with direct upload" do
75
+ scenario "Successfully upload a file" do
76
+ visit "/direct/posts/new"
77
+ fill_in "Title", with: "A cool post"
78
+ attach_file "Documents", [path("hello.txt"), path("world.txt")]
79
+
80
+ expect(page).to have_content("Upload started")
81
+ expect(page).to have_content("Upload success")
82
+ expect(page).to have_content("Upload complete")
83
+ expect(page).to have_content("All uploads complete")
84
+
85
+ click_button "Create"
86
+
87
+ expect(download_link("Document: hello.txt")).to eq("hello")
88
+ expect(download_link("Document: world.txt")).to eq("world")
89
+ end
90
+ end
91
+
92
+ describe "with presigned upload" do
93
+ scenario "Successfully upload a file" do
94
+ visit "/presigned/posts/new"
95
+ fill_in "Title", with: "A cool post"
96
+ attach_file "Documents", [path("hello.txt"), path("world.txt")]
97
+
98
+ expect(page).to have_content("Presign start")
99
+ expect(page).to have_content("Presign complete")
100
+ expect(page).to have_content("Upload started")
101
+ expect(page).to have_content("Upload complete token accepted")
102
+ expect(page).to have_content("Upload success token accepted")
103
+
104
+ click_button "Create"
105
+
106
+ expect(page).to have_selector("h1", text: "A cool post")
107
+ expect(download_link("Document: hello.txt")).to eq("hello")
108
+ expect(download_link("Document: world.txt")).to eq("world")
109
+ end
110
+
111
+ scenario "Fail to upload a file that is too large" do
112
+ visit "/presigned/posts/new"
113
+ fill_in "Title", with: "A cool post"
114
+ attach_file "Documents", [path("large.txt"), path("world.txt")]
115
+
116
+ expect(page).to have_content("Presign start")
117
+ expect(page).to have_content("Presign complete")
118
+ expect(page).to have_content("Upload started")
119
+ expect(page).to have_content("Upload failure too large")
120
+ end
121
+ end
122
+ end