bulldog 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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