opium 1.0.3 → 1.1.0

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.
@@ -0,0 +1,274 @@
1
+ require 'spec_helper'
2
+
3
+ describe Opium::File do
4
+ it { expect( described_class ).to be <= Opium::Model::Connectable }
5
+ let!(:gif_file) do
6
+ Tempfile.new(['test', '.gif']).tap do |f|
7
+ f.binmode
8
+ f.write Base64.decode64('R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7')
9
+ end
10
+ end
11
+
12
+ let(:location) { 'http://files.example.com/1234-file.gif' }
13
+
14
+ it { expect( described_class ).to respond_to(:upload, :to_ruby, :to_parse).with(1).argument }
15
+ it { is_expected.to respond_to( :delete, :name, :url, :mime_type, :to_parse ) }
16
+
17
+ describe '.upload' do
18
+ subject { described_class }
19
+ let(:upload_options) { {} }
20
+ let(:result) { subject.upload( gif_file, upload_options ) }
21
+
22
+ before do
23
+ stub_request( :post, %r{https://api\.parse\.com/1/files/.+} ).
24
+ with(
25
+ body: "9a\x15\x00\x04\x00\x80\x00\x00#-0\xFF\xFF\xFF!\xF9\x04\x01\x00\x00\x01\x00,\x00\x00\x00\x00\x15\x00\x04\x00\x00\x02\r\x8C\x8F\x01\xC9\xAD\xB0\x9C\x83T2\x8AY\x01\x00;".force_encoding('ASCII-8BIT'),
26
+ headers: {content_type: 'image/gif', x_parse_application_id: 'PARSE_APP_ID', x_parse_rest_api_key: 'PARSE_API_KEY'}
27
+ ).to_return(status: 201, body: ->(request) { { url: location, name: "1234-#{ ::File.basename(request.uri) }" }.to_json }, headers: { content_type: 'application/json', location: location })
28
+ end
29
+
30
+ context 'when the upload completes' do
31
+ it { expect { result }.to_not raise_exception }
32
+ it { expect( result ).to be_a Opium::File }
33
+ it { expect( result.url ).to_not be_nil }
34
+ it { expect( result.name ).to_not be_nil }
35
+ end
36
+
37
+ context 'with a :original_filename option' do
38
+ let(:upload_options) { { original_filename: 'chunky_bacon.jpg' } }
39
+
40
+ it { expect { result }.to_not raise_exception }
41
+ it 'preferentially uses the :original_filename' do
42
+ expect( result.name ).to end_with 'chunky_bacon.jpg'
43
+ end
44
+ end
45
+
46
+ context 'with a :content_type option' do
47
+ let(:upload_options) { { content_type: 'image/png', sent_headers: true } }
48
+
49
+ it { expect { result }.to_not raise_exception }
50
+ it { expect( result ).to have_key( 'Content-Type') }
51
+ it 'sends the given :content_type value' do
52
+ expect( result['Content-Type'] ).to eq 'image/png'
53
+ end
54
+ it { expect( result.keys ).to include( 'X-Parse-Rest-Api-Key' )}
55
+ it { expect( result.keys ).to_not include( 'X-Parse-Master-Key' )}
56
+ end
57
+
58
+ context 'without a :content_type option' do
59
+ let(:upload_options) { { sent_headers: true } }
60
+
61
+ it { expect { result }.to_not raise_exception }
62
+ it { expect( result ).to have_key( 'Content-Type' ) }
63
+ it 'sends the proper content_type' do
64
+ expect( result['Content-Type'] ).to eq 'image/gif'
65
+ end
66
+ it { expect( result.keys ).to include( 'X-Parse-Rest-Api-Key' )}
67
+ it { expect( result.keys ).to_not include( 'X-Parse-Master-Key' )}
68
+ end
69
+ end
70
+
71
+ describe '.to_ruby' do
72
+ subject { described_class }
73
+
74
+ let(:result) { subject.to_ruby( object ) }
75
+
76
+ context 'when given a hash with __type: "File"' do
77
+ let(:object) { { __type: 'File', url: location, name: 'chunky_bacon.jpg' } }
78
+
79
+ it { expect { result }.to_not raise_exception }
80
+ it { expect( result ).to be_a Opium::File }
81
+ end
82
+
83
+ context 'when given a hash with just a :url and :name' do
84
+ let(:object) { { url: location, name: 'chunky_bacon.jpg' } }
85
+
86
+ it { expect { result }.to_not raise_exception }
87
+ it { expect( result ).to be_a Opium::File }
88
+ end
89
+
90
+ context 'when given a hash with __type != "File"' do
91
+ let(:object) { { __type: 'Pointer' } }
92
+
93
+ it { expect { result }.to raise_exception }
94
+ end
95
+
96
+ context 'when given an Opium::File' do
97
+ let(:object) { Opium::File.new( url: location, name: 'chunky_bacon.jpg' ) }
98
+
99
+ it { expect { result }.to_not raise_exception }
100
+ it { expect( result ).to be_a Opium::File }
101
+ it { expect( result ).to eq object }
102
+ end
103
+
104
+ context 'when given nil' do
105
+ let(:object) { nil }
106
+
107
+ it { expect { result }.to_not raise_exception }
108
+ it { expect( result ).to be_nil }
109
+ end
110
+
111
+ context 'when not given a hash' do
112
+ let(:object) { 42 }
113
+
114
+ it { expect { result }.to raise_exception }
115
+ end
116
+ end
117
+
118
+ describe '.to_parse' do
119
+ subject { described_class }
120
+
121
+ let(:result) { described_class.to_parse( object ) }
122
+
123
+ context 'when given an Opium::File' do
124
+ let(:object) { subject.new( url: location, name: 'chunky_bacon.jpg' ) }
125
+
126
+ it { expect { result }.to_not raise_exception }
127
+ it { expect( result ).to be_a Hash }
128
+ it { expect( result ).to have_key '__type' }
129
+ it { expect( result ).to have_key 'name' }
130
+ it 'has the proper values for :__type and :name' do
131
+ expect( result ).to eq( '__type' => 'File', 'name' => 'chunky_bacon.jpg' )
132
+ end
133
+ end
134
+
135
+ context 'when given nil' do
136
+ let(:object) { nil }
137
+
138
+ it { expect { result }.to_not raise_exception }
139
+ it { expect( result ).to be_nil }
140
+ end
141
+
142
+ context 'when given anything else' do
143
+ let(:object) { 42 }
144
+
145
+ it { expect { result }.to raise_exception }
146
+ end
147
+ end
148
+
149
+ context '#initialize' do
150
+ let(:filename) { 'file.png' }
151
+ let(:location) { 'http://files.example.com/1234-file.png' }
152
+ subject { described_class.new( __type: 'File', url: location, name: filename ) }
153
+
154
+ it 'sets the url' do
155
+ expect( subject.url ).to eq location
156
+ end
157
+
158
+ it 'sets the name' do
159
+ expect( subject.name ).to eq filename
160
+ end
161
+
162
+ it 'infers the mime_type' do
163
+ expect( subject.mime_type ).to eq 'image/png'
164
+ end
165
+ end
166
+
167
+ describe '#delete' do
168
+ subject { described_class.new( url: location, name: 'chunky_bacon.jpg' ) }
169
+
170
+ before do
171
+ stub_request(:delete, "https://api.parse.com/1/files/chunky_bacon.jpg").
172
+ with(:headers => {'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Master-Key'=>'PARSE_MASTER_KEY'}).
173
+ to_return(:status => 200, :body => "{}", :headers => { content_type: 'application/json' })
174
+ end
175
+
176
+ let(:delete_options) { {} }
177
+ let(:result) { subject.delete( delete_options ) }
178
+
179
+ context 'with a name' do
180
+ it { expect { result }.to_not raise_exception }
181
+ it 'freezes the Opium::File' do
182
+ result
183
+ expect( subject ).to be_frozen
184
+ end
185
+ end
186
+
187
+ context 'without a name' do
188
+ subject { described_class.new }
189
+
190
+ it { expect { result }.to raise_exception }
191
+ end
192
+
193
+ context 'when executed' do
194
+ let(:delete_options) { { sent_headers: true } }
195
+
196
+ it { expect { result }.to_not raise_exception }
197
+ it { expect( result.keys ).to include( 'X-Parse-Master-Key' ) }
198
+ it { expect( result.keys ).to_not include( 'X-Parse-Rest-Api-Key' ) }
199
+ end
200
+ end
201
+
202
+ describe '#to_parse' do
203
+ let(:result) { subject.to_parse }
204
+
205
+ context 'when #name has a value' do
206
+ subject { described_class.new( url: location, name: 'chunky_bacon.jpg' ) }
207
+
208
+ it { expect { result }.to_not raise_exception }
209
+ it { expect( result ).to be_a Hash }
210
+ it { expect( result ).to have_key '__type' }
211
+ it { expect( result ).to have_key 'name' }
212
+ it 'has the proper values for :__type and :name' do
213
+ expect( result ).to eq( '__type' => 'File', 'name' => 'chunky_bacon.jpg' )
214
+ end
215
+ end
216
+
217
+ context 'when #name is empty' do
218
+ subject { described_class.new }
219
+
220
+ it { expect { result }.to raise_exception }
221
+ end
222
+ end
223
+
224
+ describe '#inspect' do
225
+ context 'with data' do
226
+ let(:filename) { 'file.gif' }
227
+ let(:mime) { 'image/gif' }
228
+
229
+ subject { described_class.new( url: location, name: filename ) }
230
+
231
+ it 'has the values for all components' do
232
+ expect( subject.inspect ).to eq %{#<Opium::File name="#{ filename }" url="#{ location }" mime_type="#{ mime }">}
233
+ end
234
+ end
235
+
236
+ context 'without data' do
237
+ subject { described_class.new }
238
+
239
+ it 'has nil values for all components' do
240
+ expect( subject.inspect ).to eq '#<Opium::File name=nil url=nil mime_type=nil>'
241
+ end
242
+ end
243
+ end
244
+
245
+ context 'when used as a field type' do
246
+ before do
247
+ stub_const( 'Game', Class.new do
248
+ include Opium::Model
249
+ field :cover_image, type: Opium::File
250
+ end )
251
+
252
+ stub_request(:get, "https://api.parse.com/1/classes/Game/abcd1234").
253
+ with(:headers => {'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Rest-Api-Key'=>'PARSE_API_KEY'}).
254
+ to_return(:status => 200, :body => { objectId: 'abcd1234', coverImage: { __type: 'File', name: 'chunky_bacon.jpg', url: location }, createdAt: '2015-01-01T12:00:00Z' }.to_json, :headers => { content_type: 'application/json' })
255
+
256
+ stub_request(:post, "https://api.parse.com/1/classes/Game").
257
+ with(
258
+ :body => "{\"coverImage\":{\"__type\":\"File\",\"name\":\"chunky_bacon.jpg\"}}",
259
+ :headers => {'Content-Type'=>'application/json', 'X-Parse-Application-Id'=>'PARSE_APP_ID', 'X-Parse-Rest-Api-Key'=>'PARSE_API_KEY'}).
260
+ to_return(:status => 200, :body => { objectId: 'abcd1234', createdAt: '2015-01-01T12:00:00Z' }.to_json, :headers => { content_type: 'application/json' })
261
+
262
+ end
263
+
264
+ it 'is retrievable as an Opium::File' do
265
+ game = Game.find('abcd1234')
266
+ expect( game.cover_image ).to be_a Opium::File
267
+ end
268
+
269
+ it 'is persistable as a Parse File object hash' do
270
+ game = Game.new cover_image: Opium::File.new( url: location, name: 'chunky_bacon.jpg' )
271
+ expect { game.save! }.to_not raise_exception
272
+ end
273
+ end
274
+ end
@@ -3,42 +3,56 @@ require 'spec_helper'
3
3
  describe Opium::Model::Attributable do
4
4
  let( :model ) { Class.new { include Opium::Model::Attributable } }
5
5
 
6
- describe 'instance' do
6
+ context 'within an instance' do
7
7
  subject { model.new }
8
8
 
9
- it { should respond_to( :attributes, :attributes= ) }
10
- it { should respond_to( :attributes_to_parse ) }
9
+ it { is_expected.to respond_to( :attributes, :attributes= ) }
10
+ it { is_expected.to respond_to( :attributes_to_parse ) }
11
11
  end
12
12
 
13
- describe 'when included in a model' do
13
+ context 'when included in a model' do
14
14
  before do
15
15
  stub_const( 'Book', Class.new do
16
16
  include Opium::Model
17
17
  field :title, type: String
18
+ field :genre, type: Symbol
19
+ attr_accessor :not_a_field
18
20
  end )
19
21
  end
20
22
 
21
- subject { Book.new( title: 'Little Brother', id: 'abc123', created_at: Time.now ) }
23
+ subject { Book.new( title: 'Little Brother', id: 'abc123', created_at: Time.now, genre: :sci_fi ) }
22
24
 
23
- describe 'attributes_to_parse' do
24
- it 'when called with no parameters, should have all fields present, with names and values converted to parse' do
25
- subject.attributes_to_parse.should =~ { 'objectId' => 'abc123', 'title' => 'Little Brother', 'createdAt' => subject.created_at.to_parse, 'updatedAt' => nil }
25
+ describe '#attributes_to_parse' do
26
+ context 'when called with no parameters' do
27
+ it 'has all fields present, with names and values converted to parse' do
28
+ expect( subject.attributes_to_parse ).to eq( 'objectId' => 'abc123', 'title' => 'Little Brother', 'genre' => 'sci_fi', 'createdAt' => subject.created_at.to_parse, 'updatedAt' => nil )
29
+ end
26
30
  end
27
31
 
28
- it 'when called with except, should exclude the excepted fields' do
29
- subject.attributes_to_parse( except: [:id, :updated_at] ).should =~ { 'title' => 'Little Brother', 'createdAt' => subject.created_at.to_parse }
32
+ context 'when called with except' do
33
+ it 'excludes the excepted fields' do
34
+ expect( subject.attributes_to_parse( except: [:id, :updated_at] ) ).to eq( 'title' => 'Little Brother', 'genre' => 'sci_fi', 'createdAt' => subject.created_at.to_parse )
35
+ end
30
36
  end
31
37
 
32
- it 'when called with not_readonly, should exclude all readonly fields' do
33
- subject.attributes_to_parse( not_readonly: true ).should =~ { 'title' => 'Little Brother' }
38
+ context 'when called with not_readonly' do
39
+ it 'excludes all readonly fields' do
40
+ expect( subject.attributes_to_parse( not_readonly: true ) ).to eq( 'title' => 'Little Brother', 'genre' => 'sci_fi' )
41
+ end
34
42
  end
35
43
  end
36
44
 
37
- describe ':attributes=' do
38
- it 'should still store unrecognized fields in the attributes hash' do
45
+ describe '#attributes=' do
46
+ it 'stores unrecognized fields in the attributes hash' do
39
47
  expect { subject.attributes = { unknownField: 42 } }.to_not raise_exception
40
- subject.attributes.should have_key('unknownField')
41
- subject.attributes['unknownField'].should == 42
48
+ expect( subject.attributes ).to have_key('unknownField')
49
+ expect( subject.attributes['unknownField'] ).to eq 42
50
+ end
51
+
52
+ it 'calls relevant setters rather for non fields if they exist' do
53
+ expect { subject.attributes = { not_a_field: 42 } }.to_not raise_exception
54
+ expect( subject.not_a_field ).to eq 42
55
+ expect( subject.attributes ).to_not have_key('not_a_field')
42
56
  end
43
57
  end
44
58
  end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+
3
+ describe Opium::Model::Batchable::Batch do
4
+ let(:update_path) { '/1/classes/Game/abcd1234' }
5
+ let(:body) { { title: 'Skyrim' } }
6
+
7
+ describe '#owner' do
8
+ it 'is a property' do
9
+ is_expected.to respond_to(:owner)
10
+ end
11
+ end
12
+
13
+ describe '#depth' do
14
+ let(:depth) { subject.depth }
15
+
16
+ context 'within a new batch' do
17
+ subject { described_class.new }
18
+
19
+ it { expect(depth).to eq 0 }
20
+ end
21
+ end
22
+
23
+ describe '#dive' do
24
+ after { subject.depth = 0 }
25
+
26
+ it { expect { subject.dive }.to change( subject, :depth ).by(1) }
27
+ end
28
+
29
+ describe '#ascend' do
30
+ after { subject.depth = 0 }
31
+
32
+ it { expect { subject.ascend }.to change( subject, :depth ).by(-1) }
33
+ end
34
+
35
+ describe '#queue' do
36
+ let(:queue) { subject.queue }
37
+
38
+ context 'within a new batch' do
39
+ subject { described_class.new }
40
+
41
+ it { expect(queue).to eq [] }
42
+ end
43
+ end
44
+
45
+ describe '#enqueue' do
46
+ let(:perform) { subject.enqueue( operation ) }
47
+
48
+ after { subject.queue.clear }
49
+
50
+ context 'with a valid operation hash' do
51
+ let(:operation) { { method: :put, path: update_path, body: body } }
52
+
53
+ it { expect { perform }.to_not raise_exception }
54
+ it { expect( perform ).to be_a Opium::Model::Batchable::Operation }
55
+ end
56
+
57
+ context 'with an Operation object' do
58
+ let(:operation) { Opium::Model::Batchable::Operation.new( method: :put, path: update_path, body: body ) }
59
+
60
+ it { expect { perform }.to_not raise_exception }
61
+ it { expect( perform ).to be_a Opium::Model::Batchable::Operation }
62
+ end
63
+
64
+ context 'with an invalid operation hash' do
65
+ let(:operation) { { path: update_path, body: body } }
66
+
67
+ it { expect { perform }.to raise_exception }
68
+ end
69
+ end
70
+
71
+ describe '#execute' do
72
+ let(:execute) { subject.execute }
73
+ let(:operation) { Opium::Model::Batchable::Operation.new( method: :put, path: update_path, body: body ) }
74
+
75
+ before do
76
+ subject.owner = double()
77
+ allow( subject.owner ).to receive(:http_post).and_return( [ { success: { objectId: 'abcd1234', createdAt: Time.now } } ] )
78
+ subject.enqueue( operation )
79
+ end
80
+
81
+ after do
82
+ subject.owner = nil
83
+ subject.queue.clear
84
+ end
85
+
86
+ context 'when #depth is 0' do
87
+ it { expect( subject.depth ).to eq 0 }
88
+ it { expect { execute }.to_not change( subject, :depth ) }
89
+ it 'performs a POST of the batch' do
90
+ expect( subject.owner ).to receive(:http_post).with( subject.to_parse.first )
91
+ execute
92
+ end
93
+ end
94
+
95
+ context 'when #depth is greater than 0' do
96
+ before { subject.dive }
97
+ after { subject.ascend }
98
+
99
+ it { expect( subject.depth ).to eq 1 }
100
+ it { expect { execute }.to change( subject, :depth ).by(-1) }
101
+ it 'does not POST the batch' do
102
+ expect( subject.owner ).to_not receive(:http_post)
103
+ execute
104
+ end
105
+ end
106
+
107
+ context 'when #queue is empty' do
108
+ before { subject.queue.clear }
109
+
110
+ it { expect { subject.execute }.to raise_exception }
111
+ end
112
+
113
+ context 'when #queue has more than MAX_BATCH_SIZE operations' do
114
+ before { subject.queue = [operation] * 51 }
115
+ after { subject.queue.clear }
116
+
117
+ it 'performs multiple POSTs' do
118
+ expect( subject.owner ).to receive(:http_post).twice
119
+ execute
120
+ end
121
+ end
122
+ end
123
+
124
+ describe '#to_parse' do
125
+ let(:result) { subject.to_parse }
126
+
127
+ context 'with Operations in the queue' do
128
+ before { subject.enqueue( method: :put, path: update_path, body: body ) }
129
+ after { subject.queue.clear }
130
+
131
+ it { expect { result }.to_not raise_exception }
132
+ it { expect( result ).to be_a Array }
133
+
134
+ it { expect( result.first ).to be_a Hash }
135
+ it { expect( result.first ).to have_key( :requests ) }
136
+ it { expect( result.first[:requests] ).to be_a Array }
137
+ it { expect( result.first[:requests] ).to_not be_empty }
138
+ end
139
+
140
+ context 'with an empty queue' do
141
+ it { expect { result }.to_not raise_exception }
142
+ it { expect( result ).to eq [] }
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Opium::Model::Batchable::Operation do
4
+ let(:update_path) { '/1/classes/Game/abcd1234' }
5
+ let(:body) { { title: 'Skyrim' } }
6
+
7
+ describe '#initialize' do
8
+ let(:result) { described_class.new( properties ) }
9
+
10
+ context 'with all properties' do
11
+ let(:properties) { { method: :put, path: update_path, body: body } }
12
+
13
+ it { expect { result }.to_not raise_exception }
14
+ it 'sets the method' do
15
+ expect( result.method ).to eq :put
16
+ end
17
+
18
+ it 'sets the path' do
19
+ expect( result.path ).to eq update_path
20
+ end
21
+
22
+ it 'sets the body' do
23
+ expect( result.body ).to eq body
24
+ end
25
+ end
26
+
27
+ context 'when :method is missing' do
28
+ let(:properties) { { path: update_path, body: body } }
29
+
30
+ it { expect { result }.to raise_exception }
31
+ end
32
+
33
+ context 'when :path is missing' do
34
+ let(:properties) { { method: :put, body: body } }
35
+
36
+ it { expect { result }.to raise_exception }
37
+ end
38
+
39
+ context 'when :body is missing' do
40
+ let(:properties) { { method: :put, path: update_path } }
41
+
42
+ it { expect { result }.to_not raise_exception }
43
+ it { expect( result.body ).to be_nil }
44
+ end
45
+ end
46
+
47
+ describe '#to_parse' do
48
+ let(:result) { subject.to_parse }
49
+
50
+ context 'when a body is present' do
51
+ subject { described_class.new( method: :put, path: update_path, body: body ) }
52
+
53
+ it { expect { result }.to_not raise_exception }
54
+ it { expect( result ).to be_a Hash }
55
+ it { expect( result.keys ).to include( :method, :path, :body ) }
56
+ it 'stringifies and upcases the method' do
57
+ expect( result[:method] ).to eq 'PUT'
58
+ end
59
+ end
60
+
61
+ context 'without a body' do
62
+ subject { described_class.new( method: :put, path: update_path ) }
63
+
64
+ it { expect { result }.to_not raise_exception }
65
+ it { expect( result ).to be_a Hash }
66
+ it { expect( result.keys ).to include( :method, :path ) }
67
+ it { expect( result ).to_not have_key( :body ) }
68
+ it 'stringifies and upcases the method' do
69
+ expect( result[:method] ).to eq 'PUT'
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Opium::Model::Batchable do
4
+ it { expect( described_class.constants ).to include( :Batch, :Operation ) }
5
+ end
@@ -215,4 +215,31 @@ describe Opium::Model::Connectable do
215
215
  subject.requires_heightened_privileges?.should == true
216
216
  end
217
217
  end
218
+
219
+ describe '.with_heightened_privileges' do
220
+ it { expect {|b| subject.with_heightened_privileges &b}.to yield_control }
221
+
222
+ it 'has heightened privileges within the block' do
223
+ subject.with_heightened_privileges do
224
+ expect( subject ).to have_heightened_privileges
225
+ end
226
+ end
227
+
228
+ it 'does not have heightened privileges outside the block' do
229
+ subject.with_heightened_privileges {}
230
+ expect( subject ).to_not have_heightened_privileges
231
+ end
232
+
233
+ it 'turns off heightened privileges on exceptions' do
234
+ expect { subject.with_heightened_privileges { raise 'expected' } }.to raise_exception
235
+ expect( subject ).to_not have_heightened_privileges
236
+ end
237
+
238
+ it 'keeps the previous value of requires_heightened_privileges? after the block' do
239
+ subject.requires_heightened_privileges!
240
+ subject.with_heightened_privileges {}
241
+ expect( subject ).to have_heightened_privileges
242
+ subject.instance_variable_set :@requires_heightened_privileges, nil
243
+ end
244
+ end
218
245
  end
@@ -130,4 +130,39 @@ describe Opium::Model::Fieldable do
130
130
  end
131
131
  end
132
132
  end
133
+
134
+ context 'when a model has fields' do
135
+ before do
136
+ stub_const( 'Model', Class.new do
137
+ include Opium::Model
138
+ field :symbolic_with_string_default, type: Symbol, default: 'value'
139
+ field :symbolic_with_symbol_default, type: Symbol, default: :value
140
+ end )
141
+ end
142
+
143
+ subject { Model }
144
+ let(:instance) { subject.new }
145
+
146
+ it 'converts default values correctly' do
147
+ expect( Model.default_attributes ).to include( 'symbolic_with_string_default' => :value, 'symbolic_with_symbol_default' => :value )
148
+ end
149
+
150
+ describe '.field' do
151
+ subject { Model.new }
152
+
153
+ it 'converts default values' do
154
+ expect( subject.symbolic_with_string_default ).to be_a Symbol
155
+ expect( subject.symbolic_with_symbol_default ).to be_a Symbol
156
+ end
157
+ end
158
+
159
+ describe '.field=' do
160
+ subject { Model.new }
161
+
162
+ it 'converts strings appropriately' do
163
+ expect { subject.symbolic_with_symbol_default = 'updated_value' }.to change( subject, :symbolic_with_symbol_default ).from(:value).to(:updated_value)
164
+ expect( subject.symbolic_with_symbol_default ).to be_a Symbol
165
+ end
166
+ end
167
+ end
133
168
  end