properly half-close both sides of the HTTP connection
[coquelicot.git] / spec / coquelicot / depot_spec.rb
1 # Coquelicot: "one-click" file sharing with a focus on users' privacy.
2 # Copyright © 2010-2012 potager.org <jardiniers@potager.org>
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as
6 # published by the Free Software Foundation, either version 3 of the
7 # License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 require 'spec_helper'
18 require 'timecop'
19
20 module Coquelicot
21   describe Depot do
22     describe '.new' do
23       it 'should record the given path' do
24         depot = Depot.new('/test')
25         depot.path.should == '/test'
26       end
27     end
28
29     around do |example|
30       Dir.mktmpdir('coquelicot') do |tmpdir|
31         @tmpdir = tmpdir
32         example.run
33       end
34     end
35     let(:depot)  { Depot.new(@tmpdir) }
36     let(:pass)   { 'secret'}
37     let(:expire) { 60 }
38
39     def add_file
40       content = 'Content'
41       depot.add_file(pass, { 'Expire-at' => Time.now + expire }) do
42         buf, content = content, nil
43         buf
44       end
45     end
46
47     describe '#add_file' do
48       it 'should generate a random file name' do
49         depot.should_receive(:gen_random_file_name).
50           and_return('file', 'link')
51         add_file
52       end
53       context 'when it generates a name that is already in use' do
54         it 'should find another name' do
55           FileUtils.touch File.expand_path('file', @tmpdir)
56           depot.should_receive(:gen_random_file_name).
57             and_return('file', 'another', 'link')
58           add_file
59         end
60       end
61       context 'when it generates the same name with another client' do
62         it 'should find another name' do
63           depot.should_receive(:gen_random_file_name).
64             and_return('file', 'another', 'link')
65           raised = false
66           StoredFile.should_receive(:create).ordered.
67               with(/\/file$/, pass, instance_of(Hash)).
68               and_raise(Errno::EEXIST.new(File.expand_path('file', @tmpdir)))
69           StoredFile.should_receive(:create).ordered.
70               with(/\/another$/, pass, instance_of(Hash))
71           add_file
72         end
73       end
74       context 'when the given block raises an error' do
75         it 'should not add a file to the depot' do
76           expect {
77             begin
78               depot.add_file(pass, {}) { raise StandardError.new }
79             rescue StandardError
80               # pass
81             end
82           }.to_not change { depot.size }
83         end
84         it 'should cleanup any created files' do
85           expect {
86             begin
87               depot.add_file(pass, {}) { raise StandardError.new }
88             rescue StandardError
89               # pass
90             end
91           }.to_not change { Dir.entries(@tmpdir) }
92         end
93       end
94       context 'when given a fine content provider block' do
95         it 'should add a new file in the depot' do
96           expect {
97             add_file
98           }.to change { depot.size }.by(1)
99         end
100         it 'should return a randomly generated link name' do
101           depot.should_receive(:gen_random_file_name).
102             and_return('file', 'link')
103           link = add_file
104           link.should == 'link'
105         end
106         it 'should add the new link to the ".links" file' do
107           depot.should_receive(:gen_random_file_name).
108             and_return('file', 'link')
109           add_file
110           File.read(File.expand_path('.links', @tmpdir)).should =~ /^link\s+file$/
111         end
112         context 'when it generates a link name that is already taken' do
113           before(:each) do
114             depot.stub(:gen_random_file_name).
115               and_return('file', 'link', 'another', 'link', 'another_link')
116             # add 'link' pointing to 'file'
117             add_file
118             # now it should add 'another_link' -> 'another'
119             @link = add_file
120           end
121           it 'should not overwrite the existing link' do
122             depot.size.should == 2
123           end
124           it 'should find another name' do
125             @link.should == 'another_link'
126           end
127         end
128       end
129     end
130
131     describe '#get_file' do
132       context 'when there is no link with the given name' do
133         it 'should return nil' do
134           depot.get_file('link').should be_nil
135         end
136       end
137       context 'when there is a link with the given name' do
138         before(:each) do
139           @link = add_file
140         end
141         it 'should return a StoredFile' do
142           depot.get_file(@link).should be_a(StoredFile)
143         end
144         it 'should return the right file' do
145           stored_file = depot.get_file(@link, pass)
146           buf = ''
147           stored_file.each { |data| buf << data }
148           buf.should == 'Content'
149         end
150       end
151       context 'when there is a link with no matching file' do
152         before(:each) do
153           depot.should_receive(:gen_random_file_name).
154             and_return('file', 'link')
155           @link = add_file
156           File.unlink File.expand_path('file', @tmpdir)
157         end
158         it 'should return nil' do
159           depot.get_file(@link).should be_nil
160         end
161       end
162     end
163
164     describe '#file_exists?' do
165       subject { depot.file_exists?('link') }
166       context 'when there is no link with the given name' do
167         it { should_not be_true }
168       end
169       context 'when there is a link with the given name' do
170         before(:each) do
171           depot.should_receive(:gen_random_file_name).
172             and_return('file', 'link')
173           add_file
174         end
175         it { should be_true }
176       end
177       context 'when there is a link with no matching file' do
178         before(:each) do
179           depot.should_receive(:gen_random_file_name).
180             and_return('file', 'link')
181           add_file
182           File.unlink File.expand_path('file', @tmpdir)
183         end
184         it { should_not be_true }
185       end
186     end
187
188     describe '#gc!' do
189       context 'when there is no files' do
190         it 'should do nothing' do
191           expect { depot.gc! }.to_not change { Dir.entries(@tmpdir) }
192         end
193       end
194       context 'when there is a file' do
195         before(:each) do
196           depot.should_receive(:gen_random_file_name).
197             and_return('file', 'link')
198           add_file
199         end
200         context 'before it is expired' do
201           subject { depot.gc! }
202           it 'should not remove links' do
203             expect { subject }.to_not change { depot.size }
204           end
205           it 'should not remove files' do
206             expect { subject }.to_not change { Dir.entries(@tmpdir) }
207           end
208           it 'should not empty the file' do
209             expect {
210               subject
211             }.to_not change { File.stat(File.expand_path('file', @tmpdir)).size }
212           end
213         end
214         context 'after it is expired' do
215           subject { Timecop.travel(Date.today + 2) { depot.gc! } }
216           it 'should not remove links' do
217             expect { subject }.to_not change { depot.size }
218           end
219           it 'should not remove files' do
220             expect { subject }.
221                 to_not change { Dir.entries(@tmpdir) }
222           end
223           it 'should empty the file' do
224             expect { subject }.
225                 to change { File.stat(File.expand_path('file', @tmpdir)).size }.to(0)
226           end
227         end
228         context 'after the gone period' do
229           let(:now) { Time.now + Coquelicot.settings.gone_period * 61 }
230           subject { Timecop.travel(now) { depot.gc! } }
231           it 'should remove links' do
232             expect { subject }.to change { depot.size }.from(1).to(0)
233           end
234           it 'should remove files' do
235             subject
236             Dir.glob("#{@tmpdir}/file*").should be_empty
237           end
238         end
239       end
240       context 'when there is a link but no associated file' do
241         before(:each) do
242           depot.should_receive(:gen_random_file_name).
243             and_return('file', 'link')
244           add_file
245           File.unlink File.expand_path('file', @tmpdir)
246         end
247         it 'should remove the link' do
248           expect { depot.gc! }.to change { depot.size }.from(1).to(0)
249         end
250       end
251     end
252
253     describe '#size' do
254       subject { depot.size }
255       context 'when there is no files' do
256         it { should == 0 }
257       end
258       context 'when there is a file' do
259         before(:each) { add_file }
260         it { should == 1 }
261       end
262       context 'when there is two files' do
263         before(:each) { add_file ; add_file }
264         it { should == 2 }
265       end
266     end
267   end
268 end