ridley 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.
- data/.gitignore +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/Guardfile +20 -0
- data/LICENSE +201 -0
- data/README.md +273 -0
- data/Thorfile +48 -0
- data/lib/ridley.rb +48 -0
- data/lib/ridley/connection.rb +131 -0
- data/lib/ridley/context.rb +25 -0
- data/lib/ridley/dsl.rb +58 -0
- data/lib/ridley/errors.rb +82 -0
- data/lib/ridley/log.rb +10 -0
- data/lib/ridley/middleware.rb +19 -0
- data/lib/ridley/middleware/chef_auth.rb +45 -0
- data/lib/ridley/middleware/chef_response.rb +28 -0
- data/lib/ridley/middleware/parse_json.rb +107 -0
- data/lib/ridley/resource.rb +305 -0
- data/lib/ridley/resources/client.rb +75 -0
- data/lib/ridley/resources/cookbook.rb +27 -0
- data/lib/ridley/resources/data_bag.rb +75 -0
- data/lib/ridley/resources/data_bag_item.rb +186 -0
- data/lib/ridley/resources/environment.rb +45 -0
- data/lib/ridley/resources/node.rb +34 -0
- data/lib/ridley/resources/role.rb +33 -0
- data/lib/ridley/version.rb +3 -0
- data/ridley.gemspec +39 -0
- data/spec/acceptance/client_resource_spec.rb +135 -0
- data/spec/acceptance/cookbook_resource_spec.rb +46 -0
- data/spec/acceptance/data_bag_item_resource_spec.rb +171 -0
- data/spec/acceptance/data_bag_resource_spec.rb +51 -0
- data/spec/acceptance/environment_resource_spec.rb +171 -0
- data/spec/acceptance/node_resource_spec.rb +218 -0
- data/spec/acceptance/role_resource_spec.rb +200 -0
- data/spec/fixtures/reset.pem +27 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/each_matcher.rb +12 -0
- data/spec/support/shared_examples/ridley_resource.rb +237 -0
- data/spec/support/spec_helpers.rb +11 -0
- data/spec/unit/ridley/connection_spec.rb +167 -0
- data/spec/unit/ridley/errors_spec.rb +34 -0
- data/spec/unit/ridley/middleware/chef_auth_spec.rb +14 -0
- data/spec/unit/ridley/middleware/chef_response_spec.rb +213 -0
- data/spec/unit/ridley/middleware/parse_json_spec.rb +74 -0
- data/spec/unit/ridley/resource_spec.rb +214 -0
- data/spec/unit/ridley/resources/client_spec.rb +47 -0
- data/spec/unit/ridley/resources/cookbook_spec.rb +5 -0
- data/spec/unit/ridley/resources/data_bag_item_spec.rb +42 -0
- data/spec/unit/ridley/resources/data_bag_spec.rb +15 -0
- data/spec/unit/ridley/resources/environment_spec.rb +73 -0
- data/spec/unit/ridley/resources/node_spec.rb +5 -0
- data/spec/unit/ridley/resources/role_spec.rb +5 -0
- data/spec/unit/ridley_spec.rb +32 -0
- metadata +451 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Ridley::Middleware::ChefAuth do
|
4
|
+
let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" }
|
5
|
+
|
6
|
+
subject do
|
7
|
+
Faraday.new(server_url) do |conn|
|
8
|
+
conn.request :chef_auth, "reset", "/Users/reset/.chef/reset.pem"
|
9
|
+
conn.adapter Faraday.default_adapter
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
pending
|
14
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Ridley::Middleware::ChefResponse do
|
4
|
+
let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" }
|
5
|
+
|
6
|
+
describe "ClassMethods" do
|
7
|
+
subject { Ridley::Middleware::ChefResponse }
|
8
|
+
|
9
|
+
describe "::success?" do
|
10
|
+
let(:env) { double('env') }
|
11
|
+
|
12
|
+
it "returns true if response status between 200 and 210" do
|
13
|
+
(200..210).each do |code|
|
14
|
+
env.should_receive(:[]).with(:status).and_return(code)
|
15
|
+
|
16
|
+
subject.success?(env).should be_true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "returns false if response status is in the 300 range" do
|
21
|
+
(300..399).each do |code|
|
22
|
+
env.should_receive(:[]).with(:status).and_return(code)
|
23
|
+
|
24
|
+
subject.success?(env).should be_false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns false if response status is in the 400 range" do
|
29
|
+
(400..499).each do |code|
|
30
|
+
env.should_receive(:[]).with(:status).and_return(code)
|
31
|
+
|
32
|
+
subject.success?(env).should be_false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns false if response status is in the 500 range" do
|
37
|
+
(500..599).each do |code|
|
38
|
+
env.should_receive(:[]).with(:status).and_return(code)
|
39
|
+
|
40
|
+
subject.success?(env).should be_false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
subject do
|
47
|
+
Faraday.new(server_url) do |conn|
|
48
|
+
conn.response :chef_response
|
49
|
+
conn.response :json
|
50
|
+
conn.adapter Faraday.default_adapter
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:chef_ok) do
|
55
|
+
{
|
56
|
+
status: 200,
|
57
|
+
body: generate_normalized_json(name: "reset-role", description: "a new role"),
|
58
|
+
headers: {
|
59
|
+
content_type: "application/json; charset=utf8"
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
let(:chef_bad_request) do
|
65
|
+
{
|
66
|
+
status: 400,
|
67
|
+
body: generate_normalized_json(error: "400 - Bad Request: Valid X-CHEF-VERSION header is required."),
|
68
|
+
headers: {
|
69
|
+
content_type: "application/json; charset=utf8"
|
70
|
+
}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
let(:chef_unauthorized) do
|
75
|
+
{
|
76
|
+
status: 401,
|
77
|
+
body: generate_normalized_json(error: "401 - Unauthorized. You must properly authenticate your API requests!"),
|
78
|
+
headers: {
|
79
|
+
content_type: "application/json; charset=utf8"
|
80
|
+
}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
let(:chef_forbidden) do
|
85
|
+
{
|
86
|
+
status: 403,
|
87
|
+
body: generate_normalized_json(error: "403 - Forbidden."),
|
88
|
+
headers: {
|
89
|
+
content_type: "application/json; charset=utf8"
|
90
|
+
}
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
let(:chef_not_found) do
|
95
|
+
{
|
96
|
+
status: 404,
|
97
|
+
body: generate_normalized_json(error: [ "No routes match the request: /organizations/vialstudios/cookbookss/not_existant" ]),
|
98
|
+
headers: {
|
99
|
+
content_type: "application/json; charset=utf8"
|
100
|
+
}
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
let(:chef_conflict) do
|
105
|
+
{
|
106
|
+
status: 409,
|
107
|
+
body: generate_normalized_json(error: "409 - Conflict."),
|
108
|
+
headers: {
|
109
|
+
content_type: "application/json; charset=utf8"
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "400 Bad Request" do
|
115
|
+
before(:each) do
|
116
|
+
stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_bad_request)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "raises a Ridley::Errors::HTTPBadRequest" do
|
120
|
+
lambda {
|
121
|
+
subject.get('cookbooks')
|
122
|
+
}.should raise_error(Ridley::Errors::HTTPBadRequest)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should have the body of the response as the error's message" do
|
126
|
+
lambda {
|
127
|
+
subject.get('cookbooks')
|
128
|
+
}.should raise_error("errors: '400 - Bad Request: Valid X-CHEF-VERSION header is required.'")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "401 Unauthorized" do
|
133
|
+
before(:each) do
|
134
|
+
stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_unauthorized)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "raises a Ridley::Errors::HTTPUnauthorized" do
|
138
|
+
lambda {
|
139
|
+
subject.get('cookbooks')
|
140
|
+
}.should raise_error(Ridley::Errors::HTTPUnauthorized)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should have the body of the response as the error's message" do
|
144
|
+
lambda {
|
145
|
+
subject.get('cookbooks')
|
146
|
+
}.should raise_error("errors: '401 - Unauthorized. You must properly authenticate your API requests!'")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "403 Forbidden" do
|
151
|
+
before(:each) do
|
152
|
+
stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_forbidden)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "raises a Ridley::Errors::HTTPForbidden" do
|
156
|
+
lambda {
|
157
|
+
subject.get('cookbooks')
|
158
|
+
}.should raise_error(Ridley::Errors::HTTPForbidden)
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should have the body of the response as the error's message" do
|
162
|
+
lambda {
|
163
|
+
subject.get('cookbooks')
|
164
|
+
}.should raise_error("errors: '403 - Forbidden.'")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "404 Not Found" do
|
169
|
+
before(:each) do
|
170
|
+
stub_request(:get, File.join(server_url, 'not_existant_route')).to_return(chef_not_found)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "raises a Ridley::Errors::HTTPNotFound" do
|
174
|
+
lambda {
|
175
|
+
subject.get('not_existant_route')
|
176
|
+
}.should raise_error(Ridley::Errors::HTTPNotFound)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should have the body of the response as the error's message" do
|
180
|
+
lambda {
|
181
|
+
subject.get('not_existant_route')
|
182
|
+
}.should raise_error(Ridley::Errors::HTTPNotFound, "errors: 'No routes match the request: /organizations/vialstudios/cookbookss/not_existant'")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "409 Conflict" do
|
187
|
+
before(:each) do
|
188
|
+
stub_request(:get, File.join(server_url, 'cookbooks')).to_return(chef_conflict)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "raises a Ridley::Errors::HTTPForbidden" do
|
192
|
+
lambda {
|
193
|
+
subject.get('cookbooks')
|
194
|
+
}.should raise_error(Ridley::Errors::HTTPConflict)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should have the body of the response as the error's message" do
|
198
|
+
lambda {
|
199
|
+
subject.get('cookbooks')
|
200
|
+
}.should raise_error("errors: '409 - Conflict.'")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "200 OK" do
|
205
|
+
before(:each) do
|
206
|
+
stub_request(:get, File.join(server_url, 'roles/reset')).to_return(chef_ok)
|
207
|
+
end
|
208
|
+
|
209
|
+
it "returns a body containing a hash" do
|
210
|
+
subject.get('roles/reset').env[:body].should be_a(Hash)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Ridley::Middleware::ParseJson do
|
4
|
+
let(:server_url) { "https://api.opscode.com/organizations/vialstudios/" }
|
5
|
+
|
6
|
+
describe "ClassMethods" do
|
7
|
+
subject { Ridley::Middleware::ParseJson }
|
8
|
+
|
9
|
+
describe "::response_type" do
|
10
|
+
it "returns the first element of the response content-type" do
|
11
|
+
env = double('env')
|
12
|
+
env.stub(:[]).with(:response_headers).and_return(
|
13
|
+
'content-type' => 'text/html; charset=utf-8'
|
14
|
+
)
|
15
|
+
|
16
|
+
subject.response_type(env).should eql("text/html")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "::json_response?" do
|
21
|
+
it "returns true if the value of content-type includes 'application/json'" do
|
22
|
+
env = double('env')
|
23
|
+
env.stub(:[]).with(:response_headers).and_return(
|
24
|
+
'content-type' => 'application/json; charset=utf8'
|
25
|
+
)
|
26
|
+
|
27
|
+
subject.json_response?(env).should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns true if the value of content-type does not include 'application/json' and the body contains JSON" do
|
31
|
+
env = double('env')
|
32
|
+
env.stub(:[]).with(:response_headers).and_return(
|
33
|
+
'content-type' => 'text/plain'
|
34
|
+
)
|
35
|
+
subject.should_receive(:looks_like_json?).with(env).and_return(true)
|
36
|
+
|
37
|
+
subject.json_response?(env).should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns false if the value of content-type does not include 'application/json' and the body does not contain JSON" do
|
41
|
+
env = double('env')
|
42
|
+
env.stub(:[]).with(:response_headers).and_return(
|
43
|
+
'content-type' => 'text/plain'
|
44
|
+
)
|
45
|
+
subject.should_receive(:looks_like_json?).with(env).and_return(false)
|
46
|
+
|
47
|
+
subject.json_response?(env).should be_false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "::looks_like_json?" do
|
52
|
+
let(:env) { double('env') }
|
53
|
+
|
54
|
+
it "returns true if the given string contains JSON brackets" do
|
55
|
+
env.stub(:[]).with(:body).and_return("{\"name\":\"jamie\"}")
|
56
|
+
|
57
|
+
subject.looks_like_json?(env).should be_true
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns false if the given string does not contain JSON brackets" do
|
61
|
+
env.stub(:[]).with(:body).and_return("name")
|
62
|
+
|
63
|
+
subject.looks_like_json?(env).should be_false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
subject do
|
69
|
+
Faraday.new(server_url) do |conn|
|
70
|
+
conn.response :json
|
71
|
+
conn.adapter Faraday.default_adapter
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Ridley::Resource do
|
4
|
+
let(:connection) { double('connection') }
|
5
|
+
|
6
|
+
describe "ClassMethods" do
|
7
|
+
subject do
|
8
|
+
Class.new do
|
9
|
+
include Ridley::Resource
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it_behaves_like "a Ridley Resource", subject.call
|
14
|
+
|
15
|
+
describe "::initialize" do
|
16
|
+
it "has an empty Hash for attributes if no attributes have been defined" do
|
17
|
+
klass = subject.new(connection)
|
18
|
+
|
19
|
+
klass.attributes.should be_empty
|
20
|
+
end
|
21
|
+
|
22
|
+
it "assigns the given attributes to the attribute hash if the attribute is defined on the class" do
|
23
|
+
subject.attribute(:name)
|
24
|
+
klass = subject.new(connection, name: "a name")
|
25
|
+
|
26
|
+
klass.name.should eql("a name")
|
27
|
+
klass.attributes.should have_key(:name)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "skips attributes which are not defined on the class when assigning attributes" do
|
31
|
+
klass = subject.new(connection, fake: "not here")
|
32
|
+
|
33
|
+
klass.attributes.should_not have_key(:fake)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "merges the default values for attributes into the attributes hash" do
|
37
|
+
subject.stub(:attributes).and_return(Set.new([:name]))
|
38
|
+
subject.should_receive(:attribute_defaults).and_return(name: "whatever")
|
39
|
+
klass = subject.new(connection)
|
40
|
+
|
41
|
+
klass.attributes[:name].should eql("whatever")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "explicit attributes take precedence over defaults" do
|
45
|
+
subject.stub(:attributes).and_return(Set.new([:name]))
|
46
|
+
subject.stub(:attribute_defaults).and_return(name: "default")
|
47
|
+
|
48
|
+
klass = subject.new(connection, name: "explicit_name")
|
49
|
+
|
50
|
+
klass.attributes[:name].should eql("explicit_name")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "::attributes" do
|
55
|
+
it "returns a Set" do
|
56
|
+
subject.attributes.should be_a(Set)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "::attribute" do
|
61
|
+
it "adds the given symbol to the attributes Set" do
|
62
|
+
subject.attribute(:name)
|
63
|
+
|
64
|
+
subject.attributes.should include(:name)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "convers the given string into a symbol and adds it to the attributes Set" do
|
68
|
+
subject.attribute('last_name')
|
69
|
+
|
70
|
+
subject.attributes.should include(:last_name)
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "setting a default value for the attribute" do
|
74
|
+
it "allows a string as the default value for the attribute" do
|
75
|
+
subject.attribute(:name, default: "jamie")
|
76
|
+
|
77
|
+
subject.attribute_defaults[:name].should eql("jamie")
|
78
|
+
end
|
79
|
+
|
80
|
+
it "allows a false boolean as the default value for the attribute" do
|
81
|
+
subject.attribute(:admin, default: false)
|
82
|
+
|
83
|
+
subject.attribute_defaults[:admin].should eql(false)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "allows a true boolean as the default value for the attribute" do
|
87
|
+
subject.attribute(:admin, default: true)
|
88
|
+
|
89
|
+
subject.attribute_defaults[:admin].should eql(true)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "allows nil as the default value for the attribute" do
|
93
|
+
subject.attribute(:certificate, default: nil)
|
94
|
+
|
95
|
+
subject.attribute_defaults[:certificate].should be_nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "::set_chef_type" do
|
101
|
+
it "sets the chef_type attr on the class" do
|
102
|
+
subject.set_chef_type("environment")
|
103
|
+
|
104
|
+
subject.chef_type.should eql("environment")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "::set_resource_path" do
|
109
|
+
it "sets the resource_path attr on the class" do
|
110
|
+
subject.set_resource_path("environments")
|
111
|
+
|
112
|
+
subject.resource_path.should eql("environments")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "::set_chef_json_class" do
|
117
|
+
it "sets the chef_json_class attr on the class" do
|
118
|
+
subject.set_chef_json_class("Chef::Environment")
|
119
|
+
|
120
|
+
subject.chef_json_class.should eql("Chef::Environment")
|
121
|
+
end
|
122
|
+
|
123
|
+
it "sets the value of the :json_class attribute default to the given value" do
|
124
|
+
subject.set_chef_json_class("Chef::Environment")
|
125
|
+
|
126
|
+
subject.attribute_defaults.should have_key(:json_class)
|
127
|
+
subject.attribute_defaults[:json_class].should eql("Chef::Environment")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "::set_chef_id" do
|
132
|
+
it "sets the chef_id attribute on the class" do
|
133
|
+
subject.set_chef_id(:environment)
|
134
|
+
|
135
|
+
subject.chef_id.should eql(:environment)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "::resource_path" do
|
140
|
+
it "returns the underscored and plural name of the including class if nothing is set" do
|
141
|
+
subject.resource_path.should eql(subject.class.name.underscore.pluralize)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "::chef_type" do
|
146
|
+
it "returns the underscored name of the including class if nothing is set" do
|
147
|
+
subject.chef_type.should eql(subject.class.name.underscore)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "::chef_json_class" do
|
152
|
+
it "returns the chef_json if nothing has been set" do
|
153
|
+
subject.chef_json_class.should be_nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "::chef_id" do
|
158
|
+
it "returns nil if nothing is set" do
|
159
|
+
subject.chef_id.should be_nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
subject do
|
165
|
+
Class.new do
|
166
|
+
include Ridley::Resource
|
167
|
+
end.new(connection)
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "#attribute?" do
|
171
|
+
context "if the class has the attribute defined" do
|
172
|
+
before(:each) do
|
173
|
+
subject.class.attribute(:name)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "returns false if the attribute has no value" do
|
177
|
+
subject.name = nil
|
178
|
+
|
179
|
+
subject.attribute?(:name).should be_false
|
180
|
+
end
|
181
|
+
|
182
|
+
it "returns true if the attribute has a value" do
|
183
|
+
subject.name = "reset"
|
184
|
+
|
185
|
+
subject.attribute?(:name).should be_true
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "if the class has the attribute defined with a default value" do
|
190
|
+
before(:each) do
|
191
|
+
subject.class.attribute(:name, default: "exception")
|
192
|
+
end
|
193
|
+
|
194
|
+
it "returns true if the attribute has not been explicitly set" do
|
195
|
+
subject.attribute?(:name).should be_true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context "if the class does not have the attribute defined" do
|
200
|
+
it "returns false" do
|
201
|
+
subject.attribute?(:name).should be_false
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe "#attributes=" do
|
207
|
+
it "assigns the hash of attributes to the objects attributes" do
|
208
|
+
subject.class.attribute(:name)
|
209
|
+
subject.attributes = { name: "reset" }
|
210
|
+
|
211
|
+
subject.attributes[:name].should eql("reset")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|