Fix name-typo in NEWS
[coquelicot.git] / spec / coquelicot / rack / multipart_parser_spec.rb
1 # -*- coding: UTF-8 -*-
2 # Coquelicot: "one-click" file sharing with a focus on users' privacy.
3 # Copyright © 2012-2013 potager.org <jardiniers@potager.org>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Affero General Public License for more details.
14 #
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 require 'spec_helper'
19
20 module Coquelicot::Rack
21   describe MultipartParser do
22     let(:env) { { 'SERVER_NAME' => 'example.org',
23                   'SERVER_PORT' => 80,
24                   'REQUEST_METHOD' => 'POST',
25                   'PATH_INFO' => '/upload',
26                   'CONTENT_TYPE' => "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}",
27                   'CONTENT_LENGTH' => "#{defined?(input) ? input.size : 0}",
28                   'rack.input' => StringIO.new(defined?(input) ? input : '')
29                 } }
30     describe '.parse' do
31       context 'when given a block taking one argument' do
32         it 'should run the block with a new parser as argument' do
33           MultipartParser.parse(env) do |p|
34             expect(p).to be_a(MultipartParser)
35           end
36         end
37       end
38     end
39
40     describe '#start' do
41       context 'when given no block' do
42         it 'should raise an error' do
43           MultipartParser.parse(env) do |p|
44             expect { p.start }.to raise_exception(ArgumentError)
45           end
46         end
47       end
48       context 'when used once' do
49         it 'should call the block on start' do
50           mock = double
51           expect(mock).to receive(:act)
52           MultipartParser.parse(env) do |p|
53             p.start { mock.act }
54           end
55         end
56       end
57       context 'when used twice in a row' do
58         it 'should call both blocks on start' do
59           mock = double
60           expect(mock).to receive(:run).ordered
61           expect(mock).to receive(:walk).ordered
62           MultipartParser.parse(env) do |p|
63             p.start { mock.run }
64             p.start { mock.walk }
65           end
66         end
67       end
68       context 'when used twice with steps inbetween' do
69         it 'should call both blocks on start' do
70           mock = double
71           expect(mock).to receive(:run).ordered
72           expect(mock).to receive(:walk).ordered
73           MultipartParser.parse(env) do |p|
74             p.start { mock.run }
75             p.many_fields
76             p.start { mock.walk }
77           end
78         end
79       end
80     end
81
82     describe '#many_fields' do
83       let(:input) do <<-MULTIPART_DATA.gsub(/^ */, '').gsub(/\n/, "\r\n")
84           --AaB03x
85           Content-Disposition: form-data; name="one"
86
87           1
88           --AaB03x
89           Content-Disposition: form-data; name="two"
90
91           2
92           --AaB03x
93           Content-Disposition: form-data; name="three"
94
95           3
96           --AaB03x--
97         MULTIPART_DATA
98       end
99       context 'when used alone' do
100         it 'should call the given block only once' do
101           mock = double
102           expect(mock).to receive(:act).once
103           MultipartParser.parse(env) do |p|
104             p.many_fields do |params|
105               mock.act
106             end
107           end
108         end
109         it 'should call the given block for all fields' do
110           MultipartParser.parse(env) do |p|
111             p.many_fields do |params|
112               expect(params).to be == { 'one' => '1', 'two' => '2', 'three' => '3' }
113             end
114           end
115         end
116       end
117       context 'positioned after "field"' do
118         it 'should call the given block only once' do
119           mock = double
120           expect(mock).to receive(:act).once
121           MultipartParser.parse(env) do |p|
122             p.field :one
123             p.many_fields do |params|
124               mock.act
125             end
126           end
127         end
128         it 'should call the given block for the remaning fields' do
129           MultipartParser.parse(env) do |p|
130             p.field :one
131             p.many_fields do |params|
132               expect(params).to be == { 'two' => '2', 'three' => '3' }
133             end
134           end
135         end
136       end
137       context 'positioned before "field"' do
138         it 'should call the given block only once' do
139           mock = double
140           expect(mock).to receive(:act).once
141           MultipartParser.parse(env) do |p|
142             p.many_fields do |params|
143               mock.act
144             end
145             p.field :three
146           end
147         end
148         it 'should call the given block for the first two fields' do
149           MultipartParser.parse(env) do |p|
150             p.many_fields do |params|
151               expect(params).to be == { 'one' => '1', 'two' => '2' }
152             end
153             p.field :three
154           end
155         end
156       end
157       context 'before and after "field"' do
158         it 'should call each given block only once' do
159           mock = double
160           expect(mock).to receive(:run).ordered
161           expect(mock).to receive(:walk).ordered
162           MultipartParser.parse(env) do |p|
163             p.many_fields do |params|
164               mock.run
165             end
166             p.field :two
167             p.many_fields do |params|
168               mock.walk
169             end
170           end
171         end
172         it 'should call each given block for the first and last fields, respectively' do
173           MultipartParser.parse(env) do |p|
174             p.many_fields do |params|
175               expect(params).to be == { 'one' => '1' }
176             end
177             p.field :two
178             p.many_fields do |params|
179               expect(params).to be == { 'three' => '3' }
180             end
181           end
182         end
183       end
184     end
185     describe '#field' do
186       let(:input) do <<-MULTIPART_DATA.gsub(/^ */, '').gsub(/\n/, "\r\n")
187           --AaB03x
188           Content-Disposition: form-data; name="one"
189
190           1
191           --AaB03x
192           Content-Disposition: form-data; name="two"
193
194           2
195           --AaB03x
196           Content-Disposition: form-data; name="three"
197
198           3
199           --AaB03x--
200         MULTIPART_DATA
201       end
202       context 'when positioned like the request' do
203         it 'should call a block for each field' do
204           mock = double
205           expect(mock).to receive(:first).with('1').ordered
206           expect(mock).to receive(:second).with('2').ordered
207           expect(mock).to receive(:third).with('3').ordered
208           MultipartParser.parse(env) do |p|
209             p.field(:one)   { |value| mock.first  value }
210             p.field(:two)   { |value| mock.second value }
211             p.field(:three) { |value| mock.third  value }
212           end
213         end
214       end
215       context 'when request field does not match' do
216         it 'should issue an error' do
217           expect {
218             MultipartParser.parse(env) do |p|
219               p.field(:whatever)
220             end
221           }.to raise_exception(EOFError)
222         end
223       end
224       context 'when request field does not match after many_fields' do
225         it 'should not call the field block' do
226           mock = double
227           expect(mock).not_to receive(:foo)
228           MultipartParser.parse(env) do |p|
229             p.many_fields
230             p.field(:whatever) { mock.foo }
231           end
232         end
233       end
234       context 'when request field  match after many_fields' do
235         it 'should call the field block' do
236           mock = double
237           expect(mock).to receive(:foo).with('3')
238           MultipartParser.parse(env) do |p|
239             p.many_fields
240             p.field(:three) { |value| mock.foo(value) }
241           end
242         end
243       end
244     end
245     describe '#file' do
246       context 'when file is at the end of the request' do
247         let(:file) { __FILE__ }
248         let(:input) { Rack::Multipart::Generator.new(
249             'field1' => '1',
250             'field2' => '2',
251             'field3' => Rack::Multipart::UploadedFile.new(file)
252           ).dump }
253         context 'when positioned like the request' do
254           it 'should call the given block in the right order' do
255             mock = double
256             expect(mock).to receive(:first).ordered
257             expect(mock).to receive(:second).ordered
258             expect(mock).to receive(:third).ordered
259             MultipartParser.parse(env) do |p|
260               p.field(:field1) { |value| mock.first }
261               p.field(:field2) { |value| mock.second }
262               p.file(:field3) do |filename, content_type, reader|
263                 mock.third
264                 while reader.call; end # flush file data
265               end
266             end
267           end
268           it 'should call the block passing the filename' do
269             filename = File.basename(file)
270             MultipartParser.parse(env) do |p|
271               p.many_fields
272               p.file(:field3) do |filename, content_type, reader|
273                 expect(filename).to be == filename
274                 while reader.call; end # flush file data
275               end
276             end
277           end
278           it 'should call the block passing the content type' do
279             MultipartParser.parse(env) do |p|
280               p.many_fields
281               p.file(:field3) do |filename, content_type, reader|
282                 expect(content_type).to be == 'text/plain'
283                 while reader.call; end # flush file data
284               end
285             end
286           end
287           it 'should read the whole file with multiple reader.call' do
288             data = ''
289             MultipartParser.parse(env) do |p|
290               p.many_fields
291               p.file(:field3) do |filename, content_type, reader|
292                 buf = ''
293                 data << buf until (buf = reader.call).nil?
294               end
295             end
296             expect(data).to be == slurp(file)
297           end
298         end
299       end
300
301       context 'when file is at the middle of the request' do
302         let(:file) { __FILE__ }
303         let(:input) { Rack::Multipart::Generator.new(
304             'field1' => '1',
305             'field2' => Rack::Multipart::UploadedFile.new(file),
306             'field3' => '3'
307           ).dump }
308         context 'when positioned like the request' do
309           it 'should call the given block in the right order' do
310             mock = double
311             expect(mock).to receive(:first).ordered
312             expect(mock).to receive(:second).ordered
313             expect(mock).to receive(:third).ordered
314             MultipartParser.parse(env) do |p|
315               p.field(:field1) { |value| mock.first }
316               p.file(:field2) do |filename, content_type, reader|
317                 mock.second
318                 while reader.call; end # flush file data
319               end
320               p.field(:field3) { |value| mock.third }
321             end
322           end
323           it 'should read the whole file with multiple reader.call' do
324             data = ''
325             MultipartParser.parse(env) do |p|
326               p.field(:field1)
327               p.file(:field2) do |filename, content_type, reader|
328                 buf = ''
329                 data << buf until (buf = reader.call).nil?
330               end
331               p.field(:field3)
332             end
333             expect(data).to be == slurp(file)
334           end
335         end
336       end
337       context 'when there two files follow each others in the request' do
338         let(:file1) { __FILE__ }
339         let(:file2) { File.expand_path('../../../spec_helper.rb', __FILE__) }
340         let(:input) { Rack::Multipart::Generator.new(
341             'field1' => Rack::Multipart::UploadedFile.new(file1),
342             'field2' => Rack::Multipart::UploadedFile.new(file2)
343           ).dump }
344         context 'when positioned like the request' do
345           it 'should call the given block in the right order' do
346             mock = double
347             expect(mock).to receive(:first).ordered
348             expect(mock).to receive(:second).ordered
349             MultipartParser.parse(env) do |p|
350               p.file(:field1) do |filename, content_type, reader|
351                 mock.first
352                 while reader.call; end # flush file data
353               end
354               p.file(:field2) do |filename, content_type, reader|
355                 mock.second
356                 buf = ''
357                 while reader.call; end # flush file data
358               end
359             end
360           end
361           it 'should read the files correctly' do
362             filename1 = File.basename(file1)
363             filename2 = File.basename(file2)
364             data1 = ''
365             data2 = ''
366             MultipartParser.parse(env) do |p|
367               p.file(:field1) do |filename, content_type, reader|
368                 expect(filename).to be == filename1
369                 buf = ''
370                 data1 << buf until (buf = reader.call).nil?
371               end
372               p.file(:field2) do |filename, content_type, reader|
373                 expect(filename).to be == filename2
374                 buf = ''
375                 data2 << buf until (buf = reader.call).nil?
376               end
377             end
378             expect(data1).to be == slurp(file1)
379             expect(data2).to be == slurp(file2)
380           end
381         end
382       end
383     end
384
385     describe '#finish' do
386       context 'when given no block' do
387         it 'should raise an error' do
388           MultipartParser.parse(env) do |p|
389             expect { p.finish }.to raise_exception(ArgumentError)
390           end
391         end
392       end
393       context 'when used once' do
394         it 'should call the block on finish' do
395           mock = double
396           expect(mock).to receive(:act)
397           MultipartParser.parse(env) do |p|
398             p.finish { mock.act }
399           end
400         end
401       end
402       context 'when used twice in a row' do
403         it 'should call both blocks on finish (in reverse order)' do
404           mock = double
405           expect(mock).to receive(:run).ordered
406           expect(mock).to receive(:walk).ordered
407           MultipartParser.parse(env) do |p|
408             p.finish { mock.walk }
409             p.finish { mock.run }
410           end
411         end
412       end
413       context 'when used twice with steps inbetween' do
414         it 'should call both blocks on finish (in reverse order)' do
415           mock = double
416           expect(mock).to receive(:run).ordered
417           expect(mock).to receive(:walk).ordered
418           MultipartParser.parse(env) do |p|
419             p.finish { mock.walk }
420             p.many_fields
421             p.finish { mock.run }
422           end
423         end
424       end
425     end
426   end
427 end