bulldog 0.0.1

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.
Files changed (77) hide show
  1. data/.gitignore +2 -0
  2. data/DESCRIPTION.txt +3 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +64 -0
  6. data/VERSION +1 -0
  7. data/bulldog.gemspec +157 -0
  8. data/lib/bulldog.rb +95 -0
  9. data/lib/bulldog/attachment.rb +49 -0
  10. data/lib/bulldog/attachment/base.rb +167 -0
  11. data/lib/bulldog/attachment/has_dimensions.rb +94 -0
  12. data/lib/bulldog/attachment/image.rb +63 -0
  13. data/lib/bulldog/attachment/maybe.rb +229 -0
  14. data/lib/bulldog/attachment/none.rb +37 -0
  15. data/lib/bulldog/attachment/pdf.rb +63 -0
  16. data/lib/bulldog/attachment/unknown.rb +11 -0
  17. data/lib/bulldog/attachment/video.rb +143 -0
  18. data/lib/bulldog/error.rb +5 -0
  19. data/lib/bulldog/has_attachment.rb +214 -0
  20. data/lib/bulldog/interpolation.rb +73 -0
  21. data/lib/bulldog/missing_file.rb +12 -0
  22. data/lib/bulldog/processor.rb +5 -0
  23. data/lib/bulldog/processor/argument_tree.rb +116 -0
  24. data/lib/bulldog/processor/base.rb +124 -0
  25. data/lib/bulldog/processor/ffmpeg.rb +172 -0
  26. data/lib/bulldog/processor/image_magick.rb +134 -0
  27. data/lib/bulldog/processor/one_shot.rb +19 -0
  28. data/lib/bulldog/reflection.rb +234 -0
  29. data/lib/bulldog/saved_file.rb +19 -0
  30. data/lib/bulldog/stream.rb +186 -0
  31. data/lib/bulldog/style.rb +38 -0
  32. data/lib/bulldog/style_set.rb +101 -0
  33. data/lib/bulldog/tempfile.rb +28 -0
  34. data/lib/bulldog/util.rb +92 -0
  35. data/lib/bulldog/validations.rb +68 -0
  36. data/lib/bulldog/vector2.rb +18 -0
  37. data/rails/init.rb +9 -0
  38. data/script/console +8 -0
  39. data/spec/data/empty.txt +0 -0
  40. data/spec/data/test.jpg +0 -0
  41. data/spec/data/test.mov +0 -0
  42. data/spec/data/test.pdf +0 -0
  43. data/spec/data/test.png +0 -0
  44. data/spec/data/test2.jpg +0 -0
  45. data/spec/helpers/image_creation.rb +8 -0
  46. data/spec/helpers/temporary_directory.rb +25 -0
  47. data/spec/helpers/temporary_models.rb +76 -0
  48. data/spec/helpers/temporary_values.rb +102 -0
  49. data/spec/helpers/test_upload_files.rb +108 -0
  50. data/spec/helpers/time_travel.rb +20 -0
  51. data/spec/integration/data/test.jpg +0 -0
  52. data/spec/integration/lifecycle_hooks_spec.rb +213 -0
  53. data/spec/integration/processing_image_attachments.rb +72 -0
  54. data/spec/integration/processing_video_attachments_spec.rb +82 -0
  55. data/spec/integration/saving_an_attachment_spec.rb +31 -0
  56. data/spec/matchers/file_operations.rb +159 -0
  57. data/spec/spec_helper.rb +76 -0
  58. data/spec/unit/attachment/base_spec.rb +311 -0
  59. data/spec/unit/attachment/image_spec.rb +128 -0
  60. data/spec/unit/attachment/maybe_spec.rb +126 -0
  61. data/spec/unit/attachment/pdf_spec.rb +137 -0
  62. data/spec/unit/attachment/video_spec.rb +176 -0
  63. data/spec/unit/attachment_spec.rb +61 -0
  64. data/spec/unit/has_attachment_spec.rb +700 -0
  65. data/spec/unit/interpolation_spec.rb +108 -0
  66. data/spec/unit/processor/argument_tree_spec.rb +159 -0
  67. data/spec/unit/processor/ffmpeg_spec.rb +467 -0
  68. data/spec/unit/processor/image_magick_spec.rb +260 -0
  69. data/spec/unit/processor/one_shot_spec.rb +70 -0
  70. data/spec/unit/reflection_spec.rb +338 -0
  71. data/spec/unit/stream_spec.rb +234 -0
  72. data/spec/unit/style_set_spec.rb +44 -0
  73. data/spec/unit/style_spec.rb +51 -0
  74. data/spec/unit/validations_spec.rb +491 -0
  75. data/spec/unit/vector2_spec.rb +27 -0
  76. data/tasks/bulldog_tasks.rake +4 -0
  77. metadata +193 -0
@@ -0,0 +1,234 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stream do
4
+ before do
5
+ @streams = []
6
+ end
7
+
8
+ after do
9
+ @streams.each(&:close)
10
+ end
11
+
12
+ def autoclose_stream(stream)
13
+ @streams << stream
14
+ stream
15
+ end
16
+
17
+ def self.it_should_behave_like_all_streams(options={})
18
+ class_eval do
19
+ def object(content)
20
+ raise 'example group must define #object'
21
+ end
22
+
23
+ def stream(content)
24
+ Stream.new( object(content) )
25
+ end
26
+ end
27
+
28
+ describe "#size" do
29
+ it "should return the number of bytes in the object" do
30
+ stream = stream('content')
31
+ stream.size.should == 'content'.size
32
+ end
33
+ end
34
+
35
+ describe "#path" do
36
+ it "should return the path of a file which contains the contents of the object" do
37
+ stream = stream('content')
38
+ File.read(stream.path).should == 'content'
39
+ end
40
+ end
41
+
42
+ describe "#content_type" do
43
+ it "should return the MIME type of the object" do
44
+ stream = stream("\xff\xd8")
45
+ stream.content_type.split(/;/).first.should == 'image/jpeg'
46
+ end
47
+ end
48
+
49
+ describe "#write_to" do
50
+ it "should write the contents of the file to the given path" do
51
+ stream = stream('content')
52
+ path = "#{temporary_directory}/written"
53
+ stream.write_to(path)
54
+ File.read(path).should == 'content'
55
+ end
56
+ end
57
+
58
+ describe "#file_name" do
59
+ case options[:file_name]
60
+ when :original_path
61
+ it "should return the original path" do
62
+ stream = stream('content')
63
+ stream.target.original_path = 'test.jpg'
64
+ stream.file_name.should == 'test.jpg'
65
+ end
66
+ when :file_name
67
+ it "should return the file name" do
68
+ stream = stream('content')
69
+ stream.target.stubs(:file_name).returns('test.jpg')
70
+ stream.file_name.should == 'test.jpg'
71
+ end
72
+ when :basename
73
+ it "should return the basename of the path" do
74
+ stream = stream('content')
75
+ basename = File.basename(stream.path)
76
+ stream.file_name.should == basename
77
+ end
78
+ else
79
+ it "should return nil" do
80
+ stream = stream('content')
81
+ stream.file_name.should be_nil
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ describe 'for a small uploaded file' do
88
+ it_should_behave_like_all_streams :file_name => :original_path
89
+
90
+ def object(content)
91
+ stringio = StringIO.new(content)
92
+ class << stringio
93
+ attr_accessor :original_path
94
+ end
95
+ stringio
96
+ end
97
+ end
98
+
99
+ describe 'for a large uploaded file' do
100
+ it_should_behave_like_all_streams :file_name => :original_path
101
+
102
+ def object(content)
103
+ stringio = StringIO.new(content)
104
+ class << stringio
105
+ attr_accessor :original_path
106
+ end
107
+ stringio
108
+ end
109
+ end
110
+
111
+ describe 'for a StringIO' do
112
+ it_should_behave_like_all_streams
113
+
114
+ def object(content)
115
+ tempfile = Tempfile.new('bulldog-spec')
116
+ tempfile.print(content)
117
+ class << tempfile
118
+ attr_accessor :original_path
119
+ end
120
+ tempfile
121
+ end
122
+ end
123
+
124
+ describe 'for a Tempfile' do
125
+ it_should_behave_like_all_streams
126
+
127
+ def object(content)
128
+ file = Tempfile.new('bulldog-spec')
129
+ file.print(content)
130
+ file
131
+ end
132
+ end
133
+
134
+ describe 'for a File opened for reading' do
135
+ it_should_behave_like_all_streams :file_name => :basename
136
+
137
+ def object(content)
138
+ path = "#{temporary_directory}/file"
139
+ File.open(path, 'w'){|f| f.print content}
140
+ file = File.open(path)
141
+ autoclose_stream(file)
142
+ end
143
+
144
+ it "should not do anything if we try to write to the original file" do
145
+ path = "#{temporary_directory}/file"
146
+ open(path, 'w'){|f| f.print 'content'}
147
+ open(path) do |file|
148
+ stream = Stream.new(file)
149
+ lambda{stream.write_to(path)}.should_not raise_error
150
+ File.read(path).should == 'content'
151
+ end
152
+ end
153
+ end
154
+
155
+ describe 'for a File opened for writing' do
156
+ it_should_behave_like_all_streams :file_name => :basename
157
+
158
+ def object(content)
159
+ file = File.open("#{temporary_directory}/file", 'w')
160
+ file.print content
161
+ autoclose_stream(file)
162
+ end
163
+ end
164
+
165
+ describe 'for an SavedFile' do
166
+ it_should_behave_like_all_streams :file_name => :file_name
167
+
168
+ def object(content)
169
+ path = "#{temporary_directory}/file"
170
+ open(path, 'w'){|f| f.print content}
171
+ SavedFile.new(path)
172
+ end
173
+ end
174
+
175
+ describe 'for a MissingFile' do
176
+ it_should_behave_like_all_streams :file_name => :file_name
177
+
178
+ def object(content)
179
+ path = "#{temporary_directory}/missing-file"
180
+ open(path, 'w'){|f| f.print content}
181
+ MissingFile.new(:path => path)
182
+ end
183
+
184
+ describe "for a default MissingFile" do
185
+ before do
186
+ @stream = Stream.new( MissingFile.new )
187
+ end
188
+
189
+ it "should have a size of 0" do
190
+ @stream.size.should == 0
191
+ end
192
+
193
+ it "should return a string for the path" do
194
+ @stream.path.should be_a(String)
195
+ end
196
+
197
+ it "should return a string for the content type" do
198
+ @stream.content_type.should be_a(String)
199
+ end
200
+
201
+ it "should default to a file_name that indicates it's a missing file" do
202
+ @stream.file_name.should == 'missing-file'
203
+ end
204
+
205
+ it "should write an empty file for #write_to" do
206
+ path = "#{temporary_directory}/missing_file"
207
+ @stream.write_to(path)
208
+ File.read(path).should == ''
209
+ end
210
+ end
211
+ end
212
+
213
+ describe 'for an IO' do
214
+ it_should_behave_like_all_streams
215
+
216
+ def object(content)
217
+ io = IO.popen("echo -n #{content}")
218
+ autoclose_stream(io)
219
+ end
220
+
221
+ describe "#path" do
222
+ it "should preserve the file extension if an #original_path is available" do
223
+ io = object('content')
224
+ class << io
225
+ def original_path
226
+ 'test.xyz'
227
+ end
228
+ end
229
+ stream = Stream.new(io)
230
+ File.extname(stream.path).should == '.xyz'
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe StyleSet do
4
+ before do
5
+ @one = Style.new(:one, {})
6
+ @two = Style.new(:two, {})
7
+ @original = Style.new(:original, {})
8
+ end
9
+
10
+ describe "#[]" do
11
+ it "should allow lookup by style name" do
12
+ style_set = StyleSet[@one, @two]
13
+ style_set[:one].should equal(@one)
14
+ style_set[:two].should equal(@two)
15
+ end
16
+
17
+ it "should still allow lookup by index" do
18
+ style_set = StyleSet[@one, @two]
19
+ style_set[0].should equal(@one)
20
+ style_set[1].should equal(@two)
21
+ end
22
+
23
+ it "should return a special, empty style for :original" do
24
+ style_set = StyleSet[]
25
+ style_set[:original].should == @original
26
+ end
27
+ end
28
+
29
+ describe "#clear" do
30
+ before do
31
+ @style_set = StyleSet[@one, @two]
32
+ @style_set.clear
33
+ end
34
+
35
+ it "should remove all non-original styles" do
36
+ @style_set[:one].should be_nil
37
+ @style_set[:two].should be_nil
38
+ end
39
+
40
+ it "should leave the original style" do
41
+ @style_set[:original].should == @original
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Style do
4
+ describe "#initialize" do
5
+ it "should create a Style with the given name and attributes" do
6
+ style = Style.new(:big, :size => '100x100')
7
+ style.name.should == :big
8
+ style.attributes.should == {:size => '100x100'}
9
+ end
10
+ end
11
+
12
+ describe "#[]" do
13
+ it "should return the value of the named attribute" do
14
+ style = Style.new(:big, :size => '100x100')
15
+ style[:size].should == '100x100'
16
+ end
17
+ end
18
+
19
+ describe "#==" do
20
+ it "should return true if the names and attributes are both equal" do
21
+ a = Style.new(:big, :size => '100x100')
22
+ b = Style.new(:big, :size => '100x100')
23
+ a.should == b
24
+ end
25
+
26
+ it "should return false if the names are not equal" do
27
+ a = Style.new(:big, :size => '100x100')
28
+ b = Style.new(:bad, :size => '100x100')
29
+ a.should_not == b
30
+ end
31
+
32
+ it "should return true if the attributes are not equal" do
33
+ a = Style.new(:big, :size => '100x100')
34
+ b = Style.new(:big, :size => '1x1')
35
+ a.should_not == b
36
+ end
37
+
38
+ it "should not blow up if the argument is not a Style" do
39
+ a = Style.new(:big, :size => '100x100')
40
+ b = Object.new
41
+ a.should_not == b
42
+ end
43
+ end
44
+
45
+ describe "#inspect" do
46
+ it "should show the name and attributes" do
47
+ style = Style.new(:big, :size => '100x100')
48
+ style.inspect.should == "#<Style :big {:size=>\"100x100\"}>"
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,491 @@
1
+ require 'spec_helper'
2
+
3
+ describe Validations do
4
+ use_model_class(:Thing, :photo_file_name => :string)
5
+
6
+ before do
7
+ Thing.has_attachment :photo
8
+ end
9
+
10
+ describe "an ActiveRecord validation", :shared => true do
11
+ # Includers must define:
12
+ # - validation
13
+ # - make_thing_pass
14
+ # - make_thing_fail
15
+ describe "when :on => :create is given" do
16
+ before do
17
+ Thing.send validation, :photo, validation_options.merge(:on => :create)
18
+ @thing = Thing.new
19
+ end
20
+
21
+ it "should run the validation when creating the record" do
22
+ make_thing_fail
23
+ @thing.should_not be_valid
24
+ end
25
+
26
+ it "should not run the validation when updating the record" do
27
+ make_thing_pass
28
+ @thing.save.should be_true # sanity check
29
+
30
+ make_thing_fail
31
+ @thing.should be_valid
32
+ end
33
+ end
34
+
35
+ describe "when :on => :update is given" do
36
+ before do
37
+ Thing.send validation, :photo, validation_options.merge(:on => :update)
38
+ @thing = Thing.new
39
+ end
40
+
41
+ it "should not run the validation when creating the record" do
42
+ make_thing_fail
43
+ @thing.should be_valid
44
+ end
45
+
46
+ it "should run the validation when updating the record" do
47
+ make_thing_pass
48
+ @thing.save.should be_true # sanity check
49
+
50
+ make_thing_fail
51
+ @thing.should_not be_valid
52
+ end
53
+ end
54
+
55
+ describe "when :on => :save is given" do
56
+ before do
57
+ Thing.send validation, :photo, validation_options.merge(:on => :save)
58
+ @thing = Thing.new
59
+ end
60
+
61
+ it "should run the validation when creating the record" do
62
+ make_thing_fail
63
+ @thing.should_not be_valid
64
+ end
65
+
66
+ it "should run the validation when updating the record" do
67
+ make_thing_pass
68
+ @thing.save.should be_true # sanity check
69
+
70
+ make_thing_fail
71
+ @thing.should_not be_valid
72
+ end
73
+ end
74
+
75
+ describe "when an :if option is given" do
76
+ before do
77
+ this = self
78
+ Thing.class_eval do
79
+ attr_accessor :flag
80
+ send this.validation, :photo, this.validation_options.merge(:if => :flag)
81
+ end
82
+ @thing = Thing.new
83
+ make_thing_fail
84
+ end
85
+
86
+ it "should run the validation if the condition is true" do
87
+ @thing.flag = true
88
+ @thing.should_not be_valid
89
+ end
90
+
91
+ it "should not run the validation if the condition is false" do
92
+ @thing.flag = false
93
+ @thing.should be_valid
94
+ end
95
+ end
96
+
97
+ describe "when an :unless option is given" do
98
+ before do
99
+ this = self
100
+ Thing.class_eval do
101
+ attr_accessor :flag
102
+ send this.validation, :photo, this.validation_options.merge(:unless => :flag)
103
+ end
104
+ @thing = Thing.new
105
+ make_thing_fail
106
+ end
107
+
108
+ it "should not run the validation if the condition is true" do
109
+ @thing.flag = true
110
+ @thing.should be_valid
111
+ end
112
+
113
+ it "should run the validation if the condition is false" do
114
+ @thing.flag = false
115
+ @thing.should_not be_valid
116
+ end
117
+ end
118
+
119
+ describe "when a :message option is given with a string" do
120
+ before do
121
+ Thing.send validation, :photo, validation_options.merge(:message => 'Bugger.')
122
+ @thing = Thing.new
123
+ end
124
+
125
+ it "should use a string given as a :message option as the error message" do
126
+ make_thing_fail
127
+ @thing.valid?.should be_false
128
+ @thing.errors[:photo].should == 'Bugger.'
129
+ end
130
+ end
131
+
132
+ def self.it_should_use_i18n_key(key, validation_options={}, &block)
133
+ describe "when the value is #{key.to_s.humanize.downcase}" do
134
+ use_model_class(:Subthing => :Thing)
135
+
136
+ before do
137
+ I18n.default_locale = :en
138
+ Thing.send validation, :photo, validation_options
139
+ @thing = Subthing.new
140
+ instance_eval(&block)
141
+ end
142
+
143
+ def define_translation(path, value)
144
+ hash = path.split(/\./).reverse.inject(value){|hash, key| {key => hash}}
145
+ path = "#{temporary_directory}/translations.yml"
146
+ open(path, 'w'){|f| f.write(hash.to_yaml)}
147
+ begin
148
+ I18n.backend.load_translations(path)
149
+ ensure
150
+ File.unlink(path)
151
+ end
152
+ end
153
+
154
+ it "should use the activerecord.errors.models.CLASS.attributes.ATTRIBUTE.#{key} translation if available" do
155
+ define_translation "en.activerecord.errors.models.subthing.attributes.photo.#{key}", "ERROR"
156
+ @thing.valid?.should be_false
157
+ @thing.errors[:photo].should == 'ERROR'
158
+ end
159
+
160
+ it "should fallback to activerecord.errors.models.CLASS.#{key} translation if available" do
161
+ define_translation "en.activerecord.errors.models.subthing.#{key}", "ERROR"
162
+ @thing.valid?.should be_false
163
+ @thing.errors[:photo].should == 'ERROR'
164
+ end
165
+
166
+ it "should fallback to the activerecord.errors.models.SUPERCLASS.attributes.ATTRIBUTE.#{key} if available" do
167
+ define_translation "en.activerecord.errors.models.thing.attributes.photo.#{key}", "ERROR"
168
+ @thing.valid?.should be_false
169
+ @thing.errors[:photo].should == 'ERROR'
170
+ end
171
+
172
+ it "should fallback to the activerecord.errors.models.SUPERCLASS.#{key} translation if available" do
173
+ define_translation "en.activerecord.errors.models.thing.#{key}", "ERROR"
174
+ @thing.valid?.should be_false
175
+ @thing.errors[:photo].should == 'ERROR'
176
+ end
177
+
178
+ it "should fallback to activerecord.errors.messages.#{key} translation if available" do
179
+ define_translation "en.activerecord.errors.messages.#{key}", "ERROR"
180
+ @thing.valid?.should be_false
181
+ @thing.errors[:photo].should == 'ERROR'
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ describe ".validates_attachment_presence_of" do
188
+ def validation
189
+ :validates_attachment_presence_of
190
+ end
191
+
192
+ def validation_options
193
+ {}
194
+ end
195
+
196
+ def make_thing_pass
197
+ @thing.photo = test_image_file
198
+ end
199
+
200
+ def make_thing_fail
201
+ @thing.photo = test_empty_file
202
+ end
203
+
204
+ it_should_behave_like "an ActiveRecord validation"
205
+
206
+ describe "validation" do
207
+ it "should fail if the value is blank" do
208
+ Thing.validates_attachment_presence_of :photo
209
+ @thing = Thing.new(:photo => nil)
210
+ @thing.should_not be_valid
211
+ end
212
+
213
+ it "should fail if the file is empty" do
214
+ Thing.validates_attachment_presence_of :photo
215
+ @thing = Thing.new(:photo => test_empty_file)
216
+ @thing.should_not be_valid
217
+ end
218
+
219
+ it "should pass if the file is not empty" do
220
+ Thing.validates_attachment_presence_of :photo
221
+ @thing = Thing.new(:photo => test_image_file)
222
+ @thing.should be_valid
223
+ end
224
+ end
225
+
226
+ it_should_use_i18n_key(:attachment_blank){@thing.photo = test_empty_file}
227
+ end
228
+
229
+ describe ".validates_attachment_file_size_of" do
230
+ def validation
231
+ :validates_attachment_file_size_of
232
+ end
233
+
234
+ def validation_options
235
+ {:in => 3..5}
236
+ end
237
+
238
+ def make_thing_pass
239
+ @thing.photo = uploaded_file_with_content('test.jpg', '....')
240
+ end
241
+
242
+ def make_thing_fail
243
+ @thing.photo = uploaded_file_with_content('test.jpg', '..')
244
+ end
245
+
246
+ it_should_behave_like "an ActiveRecord validation"
247
+
248
+ describe "validation" do
249
+ it "should not fail if the attachment is blank" do
250
+ Thing.validates_attachment_file_size_of :photo, :greater_than => 5
251
+ thing = Thing.new
252
+ thing.should be_valid
253
+ end
254
+
255
+ describe "when :greater_than is given" do
256
+ before do
257
+ Thing.validates_attachment_file_size_of :photo, :greater_than => 5
258
+ @thing = Thing.new
259
+ end
260
+
261
+ it "should fail if the file size is less than the limit" do
262
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*4)
263
+ @thing.should_not be_valid
264
+ end
265
+
266
+ it "should fail if the file size is equal to the limit" do
267
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*5)
268
+ @thing.should_not be_valid
269
+ end
270
+
271
+ it "should pass if the file size is greater than the limit" do
272
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*6)
273
+ @thing.should be_valid
274
+ end
275
+ end
276
+
277
+ describe "when :less_than is given" do
278
+ before do
279
+ Thing.validates_attachment_file_size_of :photo, :less_than => 5
280
+ @thing = Thing.new
281
+ end
282
+
283
+ it "should fail if the file size is greater than the limit" do
284
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*6)
285
+ @thing.should_not be_valid
286
+ end
287
+
288
+ it "should fail if the file size is equal to the limit" do
289
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*5)
290
+ @thing.should_not be_valid
291
+ end
292
+
293
+ it "should pass if the file size is less than the limit" do
294
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*4)
295
+ @thing.should be_valid
296
+ end
297
+ end
298
+
299
+ describe "when :in is given" do
300
+ before do
301
+ Thing.validates_attachment_file_size_of :photo, :in => 3..5
302
+ @thing = Thing.new
303
+ end
304
+
305
+ it "should fail if the file size is less than the lower bound" do
306
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*2)
307
+ @thing.should_not be_valid
308
+ end
309
+
310
+ it "should pass if the file size is equal to the lower bound" do
311
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*3)
312
+ @thing.should be_valid
313
+ end
314
+
315
+ it "should fail if the file size is greater than the upper bound" do
316
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*6)
317
+ @thing.should_not be_valid
318
+ end
319
+ end
320
+
321
+ describe "when :in is given with an inclusive range" do
322
+ before do
323
+ Thing.validates_attachment_file_size_of :photo, :in => 3..5
324
+ @thing = Thing.new
325
+ end
326
+
327
+ it "should pass if the file size is equal to the upper bound" do
328
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*5)
329
+ @thing.should be_valid
330
+ end
331
+ end
332
+
333
+ describe "when :in is given with an exclusive range" do
334
+ before do
335
+ Thing.validates_attachment_file_size_of :photo, :in => 3...5
336
+ @thing = Thing.new
337
+ end
338
+
339
+ it "should fail if the file size is equal to the upper bound" do
340
+ @thing.photo = uploaded_file_with_content('test.jpg', '.'*5)
341
+ @thing.should_not be_valid
342
+ end
343
+ end
344
+ end
345
+
346
+ it_should_use_i18n_key(:attachment_too_large, :in => 3..5){@thing.photo = uploaded_file_with_content('test.jpg', '......')}
347
+ it_should_use_i18n_key(:attachment_too_large, :less_than => 5){@thing.photo = uploaded_file_with_content('test.jpg', '......')}
348
+ it_should_use_i18n_key(:attachment_too_small, :in => 3..5){@thing.photo = uploaded_file_with_content('test.jpg', '..')}
349
+ it_should_use_i18n_key(:attachment_too_small, :greater_than => 5){@thing.photo = uploaded_file_with_content('test.jpg', '..')}
350
+ end
351
+
352
+ describe ".validates_attachment_type_of" do
353
+ def validation
354
+ :validates_attachment_type_of
355
+ end
356
+
357
+ def validation_options
358
+ {:matches => /\Aimage/}
359
+ end
360
+
361
+ def make_thing_pass
362
+ @thing.photo = uploaded_file_with_content('test.jpg', "\xff\xd8")
363
+ end
364
+
365
+ def make_thing_fail
366
+ @thing.photo = uploaded_file_with_content('test.avi', "RIFF AVI ")
367
+ end
368
+
369
+ it_should_behave_like "an ActiveRecord validation"
370
+
371
+ describe "validation" do
372
+ describe "when :matches is given" do
373
+ before do
374
+ Thing.validates_attachment_type_of :photo, :matches => /^image/
375
+ @thing = Thing.new(:photo => uploaded_file)
376
+ end
377
+
378
+ it "should pass if the attachment is nil" do
379
+ @thing.photo = nil
380
+ @thing.should be_valid
381
+ end
382
+
383
+ it "should pass if the content type matches the given pattern" do
384
+ @thing.photo.stubs(:content_type).returns('image/jpeg')
385
+ @thing.should be_valid
386
+ end
387
+
388
+ it "should fail if the content type does not match the given pattern" do
389
+ @thing.photo.stubs(:content_type).returns('video/x-msvideo')
390
+ @thing.should_not be_valid
391
+ @thing.errors.on(:photo).should_not be_blank
392
+ end
393
+ end
394
+
395
+ describe "when :is is given" do
396
+ describe "when the value is a symbol" do
397
+ before do
398
+ Thing.validates_attachment_type_of :photo, :is => :image
399
+ @thing = Thing.new(:photo => uploaded_file)
400
+ end
401
+
402
+ it "should pass if the symbol matches the Attachment type" do
403
+ @thing.photo = uploaded_file_with_content('test.jpg', "\xff\xd8")
404
+ @thing.should be_valid
405
+ end
406
+
407
+ it "should fail if the symbol does not match the Attachment type" do
408
+ @thing.photo = uploaded_file_with_content('test.avi', "RIFF AVI ")
409
+ @thing.should_not be_valid
410
+ @thing.errors.on(:photo).should_not be_blank
411
+ end
412
+ end
413
+
414
+ describe "when a mime-type string is given" do
415
+ before do
416
+ @thing = Thing.new(:photo => uploaded_file)
417
+ end
418
+
419
+ describe "when the string contains optional parameters" do
420
+ before do
421
+ Thing.validates_attachment_type_of :photo, :is => 'image/jpeg; a=1, b=2'
422
+ end
423
+
424
+ it "should fail if the type is different" do
425
+ @thing.photo.stubs(:content_type).returns('other/jpeg; a=1, b=2')
426
+ @thing.should_not be_valid
427
+ @thing.errors.on(:photo).should_not be_blank
428
+ end
429
+
430
+ it "should fail if the subtype is different" do
431
+ @thing.photo.stubs(:content_type).returns('image/png; a=1, b=2')
432
+ @thing.should_not be_valid
433
+ @thing.errors.on(:photo).should_not be_blank
434
+ end
435
+
436
+ it "should fail if a parameter is missing from the attachment" do
437
+ @thing.photo.stubs(:content_type).returns('image/jpeg; a=1')
438
+ @thing.should_not be_valid
439
+ @thing.errors.on(:photo).should_not be_blank
440
+ end
441
+
442
+ it "should pass if the type, subtype, and parameters are the same" do
443
+ @thing.photo.stubs(:content_type).returns('image/jpeg; a=1, b=2')
444
+ @thing.should be_valid
445
+ end
446
+
447
+ it "should pass if the attachment has additional parameters" do
448
+ @thing.photo.stubs(:content_type).returns('image/jpeg; a=1, b=2, c=3')
449
+ @thing.should be_valid
450
+ end
451
+
452
+ it "should pass if the attachment has the same parameters in a different order" do
453
+ @thing.photo.stubs(:content_type).returns('image/jpeg; b=2, a=1')
454
+ @thing.should be_valid
455
+ end
456
+ end
457
+
458
+ describe "when the string does not contain optional parameters" do
459
+ before do
460
+ Thing.validates_attachment_type_of :photo, :is => 'image/jpeg'
461
+ end
462
+
463
+ it "should fail if the type is different" do
464
+ @thing.photo.stubs(:content_type).returns('other/jpeg')
465
+ @thing.should_not be_valid
466
+ @thing.errors.on(:photo).should_not be_blank
467
+ end
468
+
469
+ it "should fail if the subtype is different" do
470
+ @thing.photo.stubs(:content_type).returns('image/png')
471
+ @thing.should_not be_valid
472
+ @thing.errors.on(:photo).should_not be_blank
473
+ end
474
+
475
+ it "should pass if the type and subtype are the same" do
476
+ @thing.photo.stubs(:content_type).returns('image/jpeg')
477
+ @thing.should be_valid
478
+ end
479
+
480
+ it "should pass if the type and subtype are the same, and the attachment has optional parameters" do
481
+ @thing.photo.stubs(:content_type).returns('image/jpeg; a=1')
482
+ @thing.should be_valid
483
+ end
484
+ end
485
+ end
486
+ end
487
+ end
488
+
489
+ it_should_use_i18n_key(:attachment_wrong_type, :matches => /\Aimage/){@thing.photo = uploaded_file_with_content('test.avi', "RIFF AVI ")}
490
+ end
491
+ end