SFEley-acts_as_icontact 0.1.3 → 0.1.4
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/VERSION +1 -1
- data/acts_as_icontact.gemspec +4 -1
- data/lib/acts_as_icontact/exceptions.rb +6 -0
- data/lib/acts_as_icontact/resource.rb +59 -9
- data/lib/acts_as_icontact/resources/contact.rb +10 -2
- data/lib/acts_as_icontact/resources/list.rb +17 -0
- data/spec/resource_spec.rb +30 -0
- data/spec/resources/contact_spec.rb +20 -3
- data/spec/resources/list_spec.rb +32 -0
- data/spec/spec_fakeweb.rb +20 -11
- metadata +4 -1
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.4
|
data/acts_as_icontact.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{acts_as_icontact}
|
5
|
-
s.version = "0.1.
|
5
|
+
s.version = "0.1.4"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Stephen Eley"]
|
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
|
|
33
33
|
"lib/acts_as_icontact/resources/account.rb",
|
34
34
|
"lib/acts_as_icontact/resources/client.rb",
|
35
35
|
"lib/acts_as_icontact/resources/contact.rb",
|
36
|
+
"lib/acts_as_icontact/resources/list.rb",
|
36
37
|
"spec/config_spec.rb",
|
37
38
|
"spec/connection_spec.rb",
|
38
39
|
"spec/resource_collection_spec.rb",
|
@@ -40,6 +41,7 @@ Gem::Specification.new do |s|
|
|
40
41
|
"spec/resources/account_spec.rb",
|
41
42
|
"spec/resources/client_spec.rb",
|
42
43
|
"spec/resources/contact_spec.rb",
|
44
|
+
"spec/resources/list_spec.rb",
|
43
45
|
"spec/spec.opts",
|
44
46
|
"spec/spec_fakeweb.rb",
|
45
47
|
"spec/spec_helper.rb"
|
@@ -58,6 +60,7 @@ Gem::Specification.new do |s|
|
|
58
60
|
"spec/resources/account_spec.rb",
|
59
61
|
"spec/resources/client_spec.rb",
|
60
62
|
"spec/resources/contact_spec.rb",
|
63
|
+
"spec/resources/list_spec.rb",
|
61
64
|
"spec/spec_fakeweb.rb",
|
62
65
|
"spec/spec_helper.rb"
|
63
66
|
]
|
@@ -2,6 +2,12 @@ module ActsAsIcontact
|
|
2
2
|
# Thrown when a configuration value isn't provided or is invalid.
|
3
3
|
class ConfigError < StandardError; end
|
4
4
|
|
5
|
+
# Thrown when a bad parameter is passed to a resource find.
|
6
|
+
class QueryError < StandardError; end
|
7
|
+
|
8
|
+
# Thrown before saving if iContact validation rules are not met.
|
9
|
+
class ValidationError < StandardError; end
|
10
|
+
|
5
11
|
# Thrown when a resource calls save! and fails. Contains the +.errors+ array from
|
6
12
|
# the resource.
|
7
13
|
class RecordNotSaved < StandardError
|
@@ -33,10 +33,18 @@ module ActsAsIcontact
|
|
33
33
|
if property =~ /(.*)=$/ # It's a value assignment
|
34
34
|
@newvalues ||= []
|
35
35
|
@newvalues << $1
|
36
|
-
|
36
|
+
if self.class.boolean_fields.include?($1)
|
37
|
+
@properties[$1] = params[0] ? 1 : 0
|
38
|
+
else
|
39
|
+
@properties[$1] = params[0]
|
40
|
+
end
|
37
41
|
else
|
38
42
|
if @properties.has_key?(property)
|
39
|
-
|
43
|
+
if self.class.boolean_fields.include?(property)
|
44
|
+
(@properties[property] == 1)
|
45
|
+
else
|
46
|
+
@properties[property]
|
47
|
+
end
|
40
48
|
else
|
41
49
|
super
|
42
50
|
end
|
@@ -56,11 +64,15 @@ module ActsAsIcontact
|
|
56
64
|
# error, raises an exception with it.
|
57
65
|
def save
|
58
66
|
if new_record?
|
67
|
+
fields = create_fields
|
68
|
+
validate_on_create(fields)
|
59
69
|
result_type = self.class.collection_name
|
60
|
-
response = self.class.connection.post([
|
70
|
+
response = self.class.connection.post([fields].to_json)
|
61
71
|
else
|
72
|
+
fields = update_fields
|
73
|
+
validate_on_update(fields)
|
62
74
|
result_type = self.class.resource_name
|
63
|
-
response = connection.post(
|
75
|
+
response = connection.post(fields.to_json)
|
64
76
|
end
|
65
77
|
parsed = JSON.parse(response)
|
66
78
|
if parsed[result_type].empty?
|
@@ -97,7 +109,10 @@ module ActsAsIcontact
|
|
97
109
|
|
98
110
|
# Returns an array of resources starting at the base.
|
99
111
|
def self.find(type, options={})
|
100
|
-
|
112
|
+
query_options = default_options.merge(options)
|
113
|
+
query_options.merge(:limit => 1) if type == :first # Minor optimization
|
114
|
+
validate_options(query_options)
|
115
|
+
uri_extension = uri_component + build_query(query_options)
|
101
116
|
response = base[uri_extension].get
|
102
117
|
parsed = JSON.parse(response)
|
103
118
|
case type
|
@@ -109,13 +124,13 @@ module ActsAsIcontact
|
|
109
124
|
end
|
110
125
|
|
111
126
|
# Returns an array of resources starting at the base.
|
112
|
-
def self.all
|
113
|
-
find(:all)
|
127
|
+
def self.all(options={})
|
128
|
+
find(:all, options)
|
114
129
|
end
|
115
130
|
|
116
131
|
# Returns the first account associated with this username.
|
117
|
-
def self.first
|
118
|
-
find(:first)
|
132
|
+
def self.first(options={})
|
133
|
+
find(:first, options)
|
119
134
|
end
|
120
135
|
|
121
136
|
protected
|
@@ -173,6 +188,11 @@ module ActsAsIcontact
|
|
173
188
|
resource_name + "Id"
|
174
189
|
end
|
175
190
|
|
191
|
+
# Options that are always passed on 'find' requests unless overridden.
|
192
|
+
def self.default_options
|
193
|
+
{:limit => 500}
|
194
|
+
end
|
195
|
+
|
176
196
|
# Fields that _must_ be included for this resource upon creation.
|
177
197
|
def self.required_on_create
|
178
198
|
[]
|
@@ -193,11 +213,41 @@ module ActsAsIcontact
|
|
193
213
|
[]
|
194
214
|
end
|
195
215
|
|
216
|
+
# Fields that operate as 0/1 boolean toggles. Can be assigned to with true and false.
|
217
|
+
def self.boolean_fields
|
218
|
+
[]
|
219
|
+
end
|
220
|
+
|
221
|
+
# Validation rules that ensure proper parameters are passed to iContact on querying.
|
222
|
+
def self.validate_options(options)
|
223
|
+
# See: http://developer.icontact.com/forums/api-beta-moderated-support/there-upper-limit-result-sets#comment-136
|
224
|
+
raise ActsAsIcontact::QueryError, "Limit must be between 1 and 500" if options[:limit].to_i < 1 or options[:limit].to_i > 500
|
225
|
+
end
|
226
|
+
|
227
|
+
# Validation rules that ensure proper data is passed to iContact on resource creation.
|
228
|
+
def validate_on_create(fields)
|
229
|
+
check_required_fields(fields, self.class.required_on_create)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Validation rules that ensure proper data is passed to iContact on resource update.
|
233
|
+
def validate_on_update(fields)
|
234
|
+
check_required_fields(fields, self.class.required_on_update)
|
235
|
+
end
|
236
|
+
|
196
237
|
private
|
197
238
|
def self.build_query(options={})
|
198
239
|
return "" if options.empty?
|
199
240
|
terms = options.collect{|k,v| "#{k}=#{URI.escape(v.to_s)}"}
|
200
241
|
build = "?" + terms.join('&')
|
201
242
|
end
|
243
|
+
|
244
|
+
def check_required_fields(fields, required)
|
245
|
+
# Check that all required fields are filled in
|
246
|
+
missing = required.select{|f| fields[f].blank?}
|
247
|
+
unless missing.empty?
|
248
|
+
missing_fields = missing.join(', ')
|
249
|
+
raise ActsAsIcontact::ValidationError, "Missing required fields: #{missing_fields}"
|
250
|
+
end
|
251
|
+
end
|
202
252
|
end
|
203
253
|
end
|
@@ -1,11 +1,19 @@
|
|
1
1
|
module ActsAsIcontact
|
2
2
|
class Contact < Resource
|
3
|
+
|
4
|
+
# Email is required
|
3
5
|
def self.required_on_create
|
4
|
-
['email']
|
6
|
+
super << ['email']
|
5
7
|
end
|
6
8
|
|
9
|
+
# Derived from clientFolder
|
7
10
|
def self.base
|
8
|
-
ActsAsIcontact
|
11
|
+
ActsAsIcontact.client
|
12
|
+
end
|
13
|
+
|
14
|
+
# Defaults to status=total to return contacts on or off lists
|
15
|
+
def self.default_options
|
16
|
+
super.merge(:status=>:total)
|
9
17
|
end
|
10
18
|
end
|
11
19
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActsAsIcontact
|
2
|
+
class List < Resource
|
3
|
+
# Derives from clientFolder.
|
4
|
+
def self.base
|
5
|
+
ActsAsIcontact.client
|
6
|
+
end
|
7
|
+
|
8
|
+
# Requires name, emailOwnerOnChange, welcomeOnManualAdd, welcomeOnSignupAdd, and welcomeMessageId.
|
9
|
+
def self.required_on_create
|
10
|
+
super << "name" << "emailOwnerOnChange" << "welcomeOnManualAdd" << "welcomeOnSignupAdd" << "welcomeMessageId"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.boolean_fields
|
14
|
+
super << "emailOwnerOnChange" << "welcomeOnManualAdd" << "welcomeOnSignupAdd"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/spec/resource_spec.rb
CHANGED
@@ -51,6 +51,30 @@ describe ActsAsIcontact::Resource do
|
|
51
51
|
r.first.foo.should == "kar"
|
52
52
|
r[1].foo.should == "yar"
|
53
53
|
end
|
54
|
+
|
55
|
+
it "defaults to a limit of 500" do
|
56
|
+
ActsAsIcontact::Resource.base.expects(:[]).with(regexp_matches(/limit=500/)).returns(stub(:get => '{"resources":[]}'))
|
57
|
+
r = ActsAsIcontact::Resource.find(:all)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "throws an exception if a limit higher than 500 is attempted" do
|
61
|
+
lambda{r = ActsAsIcontact::Resource.find(:all, :limit => 501)}.should raise_error(ActsAsIcontact::QueryError, "Limit must be between 1 and 500")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "throws an exception if a limit lower than 500 is attempted" do
|
65
|
+
lambda{r = ActsAsIcontact::Resource.find(:all, :limit => 501)}.should raise_error(ActsAsIcontact::QueryError, "Limit must be between 1 and 500")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "maps the 'first' method to find(:first)" do
|
69
|
+
ActsAsIcontact::Resource.expects(:find).with(:first,{:foo=>:bar}).returns(nil)
|
70
|
+
ActsAsIcontact::Resource.first(:foo=>:bar)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "maps the 'all' method to find(:all)" do
|
74
|
+
ActsAsIcontact::Resource.expects(:find).with(:all,{:foo=>:bar}).returns(nil)
|
75
|
+
ActsAsIcontact::Resource.all(:foo=>:bar)
|
76
|
+
end
|
77
|
+
|
54
78
|
end
|
55
79
|
|
56
80
|
it "knows its properties" do
|
@@ -94,6 +118,7 @@ describe ActsAsIcontact::Resource do
|
|
94
118
|
ActsAsIcontact::Resource.never_on_create.should == ["resourceId"]
|
95
119
|
end
|
96
120
|
|
121
|
+
|
97
122
|
context "updating records" do
|
98
123
|
before(:each) do
|
99
124
|
@res = ActsAsIcontact::Resource.first
|
@@ -117,6 +142,11 @@ describe ActsAsIcontact::Resource do
|
|
117
142
|
@res.send(:update_fields).should == {"resourceId" => "1", "too" => "tar"}
|
118
143
|
end
|
119
144
|
|
145
|
+
it "throws an exception if required fields aren't included" do
|
146
|
+
@res.resourceId = nil
|
147
|
+
lambda{@res.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: resourceId")
|
148
|
+
end
|
149
|
+
|
120
150
|
context "with successful save" do
|
121
151
|
before(:each) do
|
122
152
|
@res.too = "sar"
|
@@ -1,8 +1,25 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
2
|
|
3
3
|
describe ActsAsIcontact::Contact do
|
4
|
-
it "defaults to a limit of 500"
|
5
|
-
it "defaults to searching on all contacts regardless of list status"
|
6
|
-
it "throws an exception if a limit higher than 500 is attempted"
|
7
4
|
|
5
|
+
it "defaults to searching on all contacts regardless of list status" do
|
6
|
+
ActsAsIcontact::Contact.base.expects(:[]).with(regexp_matches(/status=total/)).returns(stub(:get => '{"contacts":[]}'))
|
7
|
+
r = ActsAsIcontact::Contact.find(:all)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "requires email address" do
|
11
|
+
c = ActsAsIcontact::Contact.new
|
12
|
+
lambda{c.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: email")
|
13
|
+
end
|
14
|
+
|
15
|
+
context "associations" do
|
16
|
+
# We have _one_ really good contact set up here
|
17
|
+
before(:each) do
|
18
|
+
@john = ActsAsIcontact::Contact.first(:firstName => "John", :lastName => "Test")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "knows which lists it's subscribed to"
|
22
|
+
it "knows its history"
|
23
|
+
end
|
24
|
+
|
8
25
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe ActsAsIcontact::List do
|
4
|
+
it "requires name, emailOwnerOnChange, welcomeOnManualAdd, welcomeOnSignupAdd, welcomeMessageId" do
|
5
|
+
l = ActsAsIcontact::List.new
|
6
|
+
lambda{l.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: name, emailOwnerOnChange, welcomeOnManualAdd, welcomeOnSignupAdd, welcomeMessageId")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "uses true and false to assign boolean fields" do
|
10
|
+
l = ActsAsIcontact::List.new
|
11
|
+
l.emailOwnerOnChange = true
|
12
|
+
l.welcomeOnSignupAdd = false
|
13
|
+
l.instance_variable_get(:@properties)["emailOwnerOnChange"].should == 1
|
14
|
+
l.instance_variable_get(:@properties)["welcomeOnSignupAdd"].should == 0
|
15
|
+
end
|
16
|
+
|
17
|
+
it "uses true and false to retrieve boolean fields" do
|
18
|
+
l = ActsAsIcontact::List.new
|
19
|
+
l.instance_variable_set(:@properties,{"welcomeOnManualAdd" => 1, "emailOwnerOnChange" => 0})
|
20
|
+
l.emailOwnerOnChange.should be_false
|
21
|
+
l.welcomeOnManualAdd.should be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
context "associations" do
|
25
|
+
# Create one good list
|
26
|
+
before(:each) do
|
27
|
+
@list = ActsAsIcontact::List.first(:name => "First Test")
|
28
|
+
end
|
29
|
+
it "knows its subscribers"
|
30
|
+
it "knows its welcome message"
|
31
|
+
end
|
32
|
+
end
|
data/spec/spec_fakeweb.rb
CHANGED
@@ -2,22 +2,31 @@ require 'rubygems'
|
|
2
2
|
require 'fakeweb'
|
3
3
|
|
4
4
|
FakeWeb.allow_net_connect = false
|
5
|
+
i = "https://app.beta.icontact.com/icp"
|
6
|
+
ic = "#{i}/a/111111/c/222222"
|
5
7
|
|
6
8
|
# Resources (this one's a fake stub for pure testing)
|
7
|
-
FakeWeb.register_uri(:get, "
|
8
|
-
FakeWeb.register_uri(:get, "
|
9
|
-
FakeWeb.register_uri(:get, "
|
10
|
-
FakeWeb.register_uri(:get, "
|
11
|
-
FakeWeb.register_uri(:get, "
|
12
|
-
FakeWeb.register_uri(:post, "
|
13
|
-
FakeWeb.register_uri(:post, "
|
14
|
-
FakeWeb.register_uri(:post, "
|
9
|
+
FakeWeb.register_uri(:get, "#{i}/resources?limit=500", :body => %q<{"resources":[{"foo":"bar","resourceId":"1","too":"bar"},{"foo":"aar","resourceId":"2"},{"foo":"far","resourceId":"3"},{"foo":"car","resourceId":"4"},{"foo":"dar","resourceId":"5"},{"foo":"ear","resourceId":"6"},{"foo":"gar","resourceId":"7"},{"foo":"har","resourceId":"8"},{"foo":"iar","resourceId":"9"},{"foo":"jar","resourceId":"10"},{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":20,"offset":0}>)
|
10
|
+
FakeWeb.register_uri(:get, "#{i}/resources?limit=5", :body => %q<{"resources":[{"foo":"bar","resourceId":"1"},{"foo":"aar","resourceId":"2"},{"foo":"far","resourceId":"3"},{"foo":"car","resourceId":"4"},{"foo":"dar","resourceId":"5"}],"total":12,"limit":5,"offset":0}>)
|
11
|
+
FakeWeb.register_uri(:get, "#{i}/resources?limit=500&offset=5", :body => %q<{"resources":[{"foo":"ear","resourceId":"6"},{"foo":"gar","resourceId":"7"},{"foo":"har","resourceId":"8"},{"foo":"iar","resourceId":"9"},{"foo":"jar","resourceId":"10"},{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":20,"offset":5}>)
|
12
|
+
FakeWeb.register_uri(:get, "#{i}/resources?offset=5&limit=5", :body => %q<{"resources":[{"foo":"ear","resourceId":"6"},{"foo":"gar","resourceId":"7"},{"foo":"har","resourceId":"8"},{"foo":"iar","resourceId":"9"},{"foo":"jar","resourceId":"10"}],"total":12,"limit":5,"offset":5}>)
|
13
|
+
FakeWeb.register_uri(:get, "#{i}/resources?offset=10&limit=5", :body => %q<{"resources":[{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":5,"offset":10}>)
|
14
|
+
FakeWeb.register_uri(:post, "#{i}/resources/1", :body => %q<{"resource":{"foo":"bar","resourceId":"1","too":"sar"}}>)
|
15
|
+
FakeWeb.register_uri(:post, "#{i}/resources/2", :body => %q<{"resource":{},"warnings":["You did not provide a foo. foo is a required field. Please provide a foo","This was not a good record"]}>)
|
16
|
+
FakeWeb.register_uri(:post, "#{i}/resources/3", :status => ["400","Bad Request"], :body => %q<{"errors":["You did not provide a clue. Clue is a required field. Please provide a clue"]}>)
|
15
17
|
|
16
18
|
# Time
|
17
|
-
FakeWeb.register_uri(:get, "
|
19
|
+
FakeWeb.register_uri(:get, "#{i}/time", :body => %q<{"time":"2009-07-13T01:28:18-04:00","timestamp":1247462898}>)
|
18
20
|
|
19
21
|
# Accounts
|
20
|
-
FakeWeb.register_uri(:get, "
|
22
|
+
FakeWeb.register_uri(:get, "#{i}/a?limit=500", :body => %q<{"accounts":[{"billingStreet":"","billingCity":"","billingState":"","billingPostalCode":"","billingCountry":"","city":"Testville","accountId":"111111","companyName":"","country":"United States","email":"bob@example.org","enabled":1,"fax":"","firstName":"Bob","lastName":"Tester","multiClientFolder":"0","multiUser":"0","phone":"","postalCode":"12345","state":"TN","street":"123 Test Street","title":"","accountType":"0","subscriberLimit":"250000"}],"total":1,"limit":20,"offset":0}>)
|
21
23
|
|
22
24
|
# Clients
|
23
|
-
FakeWeb.register_uri(:get, "
|
25
|
+
FakeWeb.register_uri(:get, "#{i}/a/111111/c?limit=500", :body => %q<{"clientfolders":[{"clientFolderId":"222222","logoId":null,"emailRecipient":"bob@example.org"}],"total":1}>)
|
26
|
+
|
27
|
+
# Contacts
|
28
|
+
FakeWeb.register_uri(:get, "#{ic}/contacts?limit=500&status=total&firstName=John&lastName=Test", :body => %q<{"contacts":[{"email":"john@example.org","firstName":"John","lastName":"Test","status":"normal","contactId":"333333","createDate":"2009-07-24 01:00:00"}]}>)
|
29
|
+
|
30
|
+
# Lists
|
31
|
+
FakeWeb.register_uri(:get, "#{ic}/lists?limit=500&name=First%20Test", :body => %q<{"lists":[{"listId":"444444","name":"First Test","emailOwnerOnChange":"0","welcomeOnManualAdd":"0","welcomeOnSignupAdd":"0","welcomeMessageId":"555555","description":"Just a test list."}]}>)
|
32
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: SFEley-acts_as_icontact
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Eley
|
@@ -38,6 +38,7 @@ files:
|
|
38
38
|
- lib/acts_as_icontact/resources/account.rb
|
39
39
|
- lib/acts_as_icontact/resources/client.rb
|
40
40
|
- lib/acts_as_icontact/resources/contact.rb
|
41
|
+
- lib/acts_as_icontact/resources/list.rb
|
41
42
|
- spec/config_spec.rb
|
42
43
|
- spec/connection_spec.rb
|
43
44
|
- spec/resource_collection_spec.rb
|
@@ -45,6 +46,7 @@ files:
|
|
45
46
|
- spec/resources/account_spec.rb
|
46
47
|
- spec/resources/client_spec.rb
|
47
48
|
- spec/resources/contact_spec.rb
|
49
|
+
- spec/resources/list_spec.rb
|
48
50
|
- spec/spec.opts
|
49
51
|
- spec/spec_fakeweb.rb
|
50
52
|
- spec/spec_helper.rb
|
@@ -82,5 +84,6 @@ test_files:
|
|
82
84
|
- spec/resources/account_spec.rb
|
83
85
|
- spec/resources/client_spec.rb
|
84
86
|
- spec/resources/contact_spec.rb
|
87
|
+
- spec/resources/list_spec.rb
|
85
88
|
- spec/spec_fakeweb.rb
|
86
89
|
- spec/spec_helper.rb
|