acts_as_icontact 0.1.1 → 0.1.5

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/README.markdown CHANGED
@@ -23,26 +23,26 @@ Using ActsAsIcontact is easy, but going through iContact's authorization process
23
23
 
24
24
  $ sudo gem install acts_as_icontact
25
25
 
26
- 2. _Optional but recommended:_ Go to <http://beta.icontact.com> and sign up for an iContact Beta account. This will let you test your app without risk of blowing away your production mailing lists.
26
+ 2. _Optional but recommended:_ Go to <http://sandbox.icontact.com> and sign up for an iContact Sandbox account. This will let you test your app without risk of blowing away your production mailing lists.
27
27
 
28
- 3. Enable the ActsAsIcontact gem for use with your iContact account. The URL and credentials you'll use are different between the beta and production environments:
28
+ 3. Enable the ActsAsIcontact gem for use with your iContact account. The URL and credentials you'll use are different between the sandbox and production environments:
29
29
 
30
- * **BETA:** Go to <http://app.beta.icontact.com/icp/core/externallogin> and enter `Ml5SnuFhnoOsuZeTOuZQnLUHTbzeUyhx` for the Application Id. Choose a password for ActsAsIcontact that's different from your account password.
30
+ * **Sandbox:** Go to <http://app.sandbox.icontact.com/icp/core/externallogin> and enter `Ml5SnuFhnoOsuZeTOuZQnLUHTbzeUyhx` for the Application Id. Choose a password for ActsAsIcontact that's different from your account password.
31
31
 
32
32
  * **PRODUCTION:** Go to <http://app.icontact.com/icp/core/externallogin> and enter `IYDOhgaZGUKNjih3hl1ItLln7zpAtWN2` for the Application Id. Choose a password for ActsAsIcontact that's different from your account password.
33
33
 
34
- 4. Set your _(beta, if applicable)_ account username and the password you just chose for API access. You can either set the environment variables `ICONTACT_MODE`, `ICONTACT_USERNAME`, and `ICONTACT_PASSWORD`, or you can explicitly do it with calls to the Config module:
34
+ 4. Set your _(sandbox, if applicable)_ account username and the password you just chose for API access. You can either set the environment variables `ICONTACT_MODE`, `ICONTACT_USERNAME`, and `ICONTACT_PASSWORD`, or you can explicitly do it with calls to the Config module:
35
35
 
36
36
  require 'rubygems'
37
37
  require 'acts_as_icontact'
38
38
 
39
- ActsAsIcontact::Config.mode = :beta
40
- ActsAsIcontact::Config.username = my_beta_username
39
+ ActsAsIcontact::Config.mode = :sandbox
40
+ ActsAsIcontact::Config.username = my_sandbox_username
41
41
  ActsAsIcontact::Config.password = my_api_password
42
42
 
43
43
  If you're using Rails, the recommended approach is to require the gem with `config.gem 'acts_as_icontact'` in your **config/environment.rb** file, and then set up an initializer (i.e. **config/initializers/acts\_as\_icontact.rb**) with the above code. See more about Rails below.
44
44
 
45
- 5. Rinse and repeat with production credentials when you're ready to move out of the beta environment.
45
+ 5. Rinse and repeat with production credentials when you're ready to move out of the sandbox environment.
46
46
 
47
47
  API Access
48
48
  ----------
@@ -120,20 +120,20 @@ Rails Integration
120
120
  The _real_ power of ActsAsIcontact is its automatic syncing with ActiveRecord. At this time this feature is focused entirely on Contacts.
121
121
 
122
122
  ### Activation
123
- First add the line `config.gem 'acts_as_icontact'` to your **config/environment.rb** file. Then create an initializer (e.g. **config/initializers/acts\_as\_icontact.rb**) and set it up with your username and password. If applicable, you can give it both the beta _and_ production credentials:
123
+ First add the line `config.gem 'acts_as_icontact'` to your **config/environment.rb** file. Then create an initializer (e.g. **config/initializers/acts\_as\_icontact.rb**) and set it up with your username and password. If applicable, you can give it both the sandbox _and_ production credentials:
124
124
 
125
125
  module ActsAsIcontact
126
126
  case Config.mode
127
- when :beta
128
- Config.username = my_beta_username
129
- Config.password = my_beta_password
127
+ when :sandbox
128
+ Config.username = my_sandbox_username
129
+ Config.password = my_sandbox_password
130
130
  when :production
131
131
  Config.username = my_production_username
132
132
  Config.password = my_production_password
133
133
  end
134
134
  end
135
135
 
136
- If ActsAsIcontact detects that it's running in a Rails app, the default behavior is to set the mode to `:production` if RAILS\_ENV is equal to "production" and `:beta` if RAILS\_ENV is set to anything else. (Incidentally, if you're _not_ in a Rails app but running Rack, the same logic applies for the RACK\_ENV environment variable.)
136
+ If ActsAsIcontact detects that it's running in a Rails app, the default behavior is to set the mode to `:production` if RAILS\_ENV is equal to "production" and `:sandbox` if RAILS\_ENV is set to anything else. (Incidentally, if you're _not_ in a Rails app but running Rack, the same logic applies for the RACK\_ENV environment variable.)
137
137
 
138
138
  Finally, enable one of your models to synchronize with iContact with a simple declaration:
139
139
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.5
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{acts_as_icontact}
5
- s.version = "0.1.1"
5
+ s.version = "0.1.5"
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"]
9
- s.date = %q{2009-07-24}
9
+ s.date = %q{2009-07-25}
10
10
  s.description = %q{ActsAsIcontact connects Ruby applications with the iContact e-mail marketing service using the iContact API v2.0. Building on the RestClient gem, it offers two significant feature sets:
11
11
 
12
12
  * Simple, consistent access to all resources in the iContact API; and
@@ -33,6 +33,8 @@ 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",
37
+ "lib/acts_as_icontact/resources/message.rb",
36
38
  "spec/config_spec.rb",
37
39
  "spec/connection_spec.rb",
38
40
  "spec/resource_collection_spec.rb",
@@ -40,6 +42,8 @@ Gem::Specification.new do |s|
40
42
  "spec/resources/account_spec.rb",
41
43
  "spec/resources/client_spec.rb",
42
44
  "spec/resources/contact_spec.rb",
45
+ "spec/resources/list_spec.rb",
46
+ "spec/resources/message_spec.rb",
43
47
  "spec/spec.opts",
44
48
  "spec/spec_fakeweb.rb",
45
49
  "spec/spec_helper.rb"
@@ -58,6 +62,8 @@ Gem::Specification.new do |s|
58
62
  "spec/resources/account_spec.rb",
59
63
  "spec/resources/client_spec.rb",
60
64
  "spec/resources/contact_spec.rb",
65
+ "spec/resources/list_spec.rb",
66
+ "spec/resources/message_spec.rb",
61
67
  "spec/spec_fakeweb.rb",
62
68
  "spec/spec_helper.rb"
63
69
  ]
@@ -3,15 +3,15 @@ module ActsAsIcontact
3
3
  # required by the iContact API for authentication.
4
4
  module Config
5
5
 
6
- # Sets :production or :beta. This changes the AppId and URL.
6
+ # Sets :production or :sandbox. This changes the AppId and URL.
7
7
  def self.mode=(val)
8
8
  @mode = val
9
9
  end
10
10
 
11
- # Determines whether to return the beta or production AppId and URL.
11
+ # Determines whether to return the sandbox or production AppId and URL.
12
12
  # If not explicitly set, it will first look for an ICONTACT_MODE environment variable.
13
13
  # If it doesn't find one, it will attempt to detect a Rails or Rack environment; in either
14
- # case it will default to :production if RAILS_ENV or RACK_ENV is 'production', and :beta
14
+ # case it will default to :production if RAILS_ENV or RACK_ENV is 'production', and :sandbox
15
15
  # otherwise. If none of these conditions apply, it assumes :production. (Because that
16
16
  # probably means someone's doing ad hoc queries.)
17
17
  def self.mode
@@ -19,9 +19,9 @@ module ActsAsIcontact
19
19
  when ENV["ICONTACT_MODE"]
20
20
  ENV["ICONTACT_MODE"].to_sym
21
21
  when Object.const_defined?(:Rails)
22
- (ENV["RAILS_ENV"] == 'production' ? :production : :beta)
22
+ (ENV["RAILS_ENV"] == 'production' ? :production : :sandbox)
23
23
  when Object.const_defined?(:Rack)
24
- (ENV["RACK_ENV"] == 'production' ? :production : :beta)
24
+ (ENV["RACK_ENV"] == 'production' ? :production : :sandbox)
25
25
  else
26
26
  :production
27
27
  end
@@ -32,7 +32,7 @@ module ActsAsIcontact
32
32
  # to change this. Ever.
33
33
  def self.app_id
34
34
  case mode
35
- when :beta
35
+ when :beta, :sandbox
36
36
  "Ml5SnuFhnoOsuZeTOuZQnLUHTbzeUyhx"
37
37
  when :production
38
38
  "IYDOhgaZGUKNjih3hl1ItLln7zpAtWN2"
@@ -45,14 +45,16 @@ module ActsAsIcontact
45
45
  end
46
46
 
47
47
  # Prefixed to the beginning of every API request. You can override this if you have some special
48
- # need (e.g. working against a testing server, or if iContact takes their API out of beta and
49
- # changes the URI before the gem gets updated), but for the most part you can leave it alone.
48
+ # need (e.g. working against a testing server, or if iContact changes their URL and you have to
49
+ # fix it before the gem gets updated), but for the most part you can leave it alone.
50
50
  def self.url
51
51
  @url ||= case mode
52
- when :beta
53
- "https://app.beta.icontact.com/icp/"
54
52
  when :production
55
53
  "https://app.icontact.com/icp/"
54
+ when :sandbox
55
+ "https://app.sandbox.icontact.com/icp/"
56
+ when :beta # The 'beta' environment still works as of 7/25/2009
57
+ "https://app.beta.icontact.com/icp/"
56
58
  end
57
59
  end
58
60
 
@@ -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
@@ -8,11 +8,10 @@ module ActsAsIcontact
8
8
 
9
9
  # Creates a new resource object from a values hash. (Which is passed to us via the magic of JSON.)
10
10
  def initialize(properties={})
11
- @properties = properties
11
+ @properties = clean_properties(properties)
12
12
  @new_record = !@properties.has_key?(self.class.primary_key)
13
13
  # Initialize other useful attributes
14
14
  @errors = []
15
-
16
15
  end
17
16
 
18
17
  # Returns the primary key ID for an existing resource. Returns nil if the resource is a new record.
@@ -33,10 +32,14 @@ module ActsAsIcontact
33
32
  if property =~ /(.*)=$/ # It's a value assignment
34
33
  @newvalues ||= []
35
34
  @newvalues << $1
36
- @properties[$1] = params[0]
37
- else
35
+ @properties[$1] = clean_value(params[0])
36
+ else
38
37
  if @properties.has_key?(property)
39
- @properties[property]
38
+ if self.class.boolean_fields.include?(property)
39
+ (@properties[property] == 1)
40
+ else
41
+ @properties[property]
42
+ end
40
43
  else
41
44
  super
42
45
  end
@@ -56,12 +59,15 @@ module ActsAsIcontact
56
59
  # error, raises an exception with it.
57
60
  def save
58
61
  if new_record?
62
+ fields = create_fields
63
+ validate_on_create(fields)
59
64
  result_type = self.class.collection_name
60
- payload = {self.class.collection_name => [create_fields]}
61
- response = self.class.connection.post(payload.to_json)
65
+ response = self.class.connection.post([fields].to_json)
62
66
  else
67
+ fields = update_fields
68
+ validate_on_update(fields)
63
69
  result_type = self.class.resource_name
64
- response = connection.post(update_fields.to_json)
70
+ response = connection.post(fields.to_json)
65
71
  end
66
72
  parsed = JSON.parse(response)
67
73
  if parsed[result_type].empty?
@@ -98,25 +104,46 @@ module ActsAsIcontact
98
104
 
99
105
  # Returns an array of resources starting at the base.
100
106
  def self.find(type, options={})
101
- uri_extension = uri_component + build_query(options)
102
- response = base[uri_extension].get
103
- parsed = JSON.parse(response)
104
107
  case type
105
- when :first then
106
- self.new(parsed[collection_name].first) if parsed[collection_name]
107
- when :all then
108
- ResourceCollection.new(self, parsed)
108
+ when :first
109
+ first(options)
110
+ when :all
111
+ all(options)
112
+ when Integer
113
+ find_by_id(type)
114
+ else
115
+ raise ActsAsIcontact::QueryError, "Don't know how to find '#{type.to_s}'"
109
116
  end
110
117
  end
111
118
 
112
119
  # Returns an array of resources starting at the base.
113
- def self.all
114
- find(:all)
120
+ def self.all(options={})
121
+ query_options = default_options.merge(options)
122
+ validate_options(query_options)
123
+ result = query_collection(query_options)
124
+ ResourceCollection.new(self, result)
115
125
  end
116
126
 
117
127
  # Returns the first account associated with this username.
118
- def self.first
119
- find(:first)
128
+ def self.first(options={})
129
+ query_options = default_options.merge(options).merge(:limit => 1) # Minor optimization
130
+ validate_options(query_options)
131
+ result = query_collection(query_options)
132
+ self.new(result[collection_name].first)
133
+ end
134
+
135
+ # Returns the single resource at the URL identified by the passed integer. Takes no options; this is
136
+ # not a search, this is a single record retrieval. Raises an exception if the record can't be found.
137
+ def self.find_by_id(id)
138
+ response = self.connection[id].get
139
+ parsed = JSON.parse(response)
140
+ raise ActsAsiContact::QueryError, "iContact's response did not contain a #{resource_name}!" unless parsed[resource_name]
141
+ self.new(parsed[resource_name])
142
+ end
143
+
144
+ # Two resources are identical if they have exactly the same property array.
145
+ def ==(obj)
146
+ properties == obj.properties
120
147
  end
121
148
 
122
149
  protected
@@ -139,10 +166,10 @@ module ActsAsIcontact
139
166
  @properties
140
167
  end
141
168
 
142
- # The base RestClient resource that this particular class nests from. Starts with
143
- # the resource connection at 'https://api.icontact.com/icp/' and works its way up.
169
+ # The base RestClient resource that this particular class nests from. Defaults to
170
+ # the clientFolders path since that's the most common case.
144
171
  def self.base
145
- ActsAsIcontact.connection
172
+ ActsAsIcontact.client
146
173
  end
147
174
 
148
175
  # The name of the singular resource type pulled from iContact. Defaults to the lowercase
@@ -174,6 +201,11 @@ module ActsAsIcontact
174
201
  resource_name + "Id"
175
202
  end
176
203
 
204
+ # Options that are always passed on 'find' requests unless overridden.
205
+ def self.default_options
206
+ {:limit => 500}
207
+ end
208
+
177
209
  # Fields that _must_ be included for this resource upon creation.
178
210
  def self.required_on_create
179
211
  []
@@ -194,11 +226,86 @@ module ActsAsIcontact
194
226
  []
195
227
  end
196
228
 
229
+ # Fields that operate as 0/1 boolean toggles. Can be assigned to with true and false.
230
+ def self.boolean_fields
231
+ []
232
+ end
233
+
234
+ # Validation rules that ensure proper parameters are passed to iContact on querying.
235
+ def self.validate_options(options)
236
+ # See: http://developer.icontact.com/forums/api-beta-moderated-support/there-upper-limit-result-sets#comment-136
237
+ raise ActsAsIcontact::QueryError, "Limit must be between 1 and 500" if options[:limit].to_i < 1 or options[:limit].to_i > 500
238
+ end
239
+
240
+ # Validation rules that ensure proper data is passed to iContact on resource creation.
241
+ def validate_on_create(fields)
242
+ check_required_fields(fields, self.class.required_on_create)
243
+ validate_on_save(fields)
244
+ end
245
+
246
+ # Validation rules that ensure proper data is passed to iContact on resource update.
247
+ def validate_on_update(fields)
248
+ check_required_fields(fields, self.class.required_on_update)
249
+ validate_on_save(fields)
250
+ end
251
+
252
+ # Validation rules that apply to both creates and updates. The method on the abstract Resource class is just a placeholder;
253
+ # this is intended to be used by resource subclasses.
254
+ def validate_on_save(fields)
255
+ end
256
+
257
+ # Finesses the properties hash passed in to make iContact and Ruby idioms compatible.
258
+ # Turns symbol keys into strings and runs the clean_value method on values.
259
+ # Subclasses may add additional conversions.
260
+ def clean_properties(properties)
261
+ newhash = {}
262
+ properties.each_pair do |key, value|
263
+ newhash[key.to_s] = clean_value(value)
264
+ end
265
+ newhash
266
+ end
267
+
268
+ # Finesses values passed in to properties to make iContact and Ruby idioms compatible.
269
+ # Turns symbols into strings, numbers into integers or floats, true/false into 1 and 0,
270
+ # and empty strings into nil. Subclasses may add additional conversions.
271
+ def clean_value(value)
272
+ case value
273
+ when Symbol then value.to_s
274
+ when TrueClass then 1
275
+ when FalseClass then 0
276
+ when /^\d+$/ then value.to_i # Integer
277
+ when /^\d+(\.\d+)?([eE]\d+)?$/ then value.to_f # Float
278
+ when blank? then nil
279
+ else value
280
+ end
281
+ end
282
+
283
+ # The properties array, for comparison against other resources or debugging.
284
+ def properties
285
+ @properties
286
+ end
287
+
197
288
  private
198
289
  def self.build_query(options={})
199
290
  return "" if options.empty?
200
291
  terms = options.collect{|k,v| "#{k}=#{URI.escape(v.to_s)}"}
201
292
  build = "?" + terms.join('&')
202
293
  end
294
+
295
+ def self.query_collection(options={})
296
+ uri_extension = uri_component + build_query(options)
297
+ response = base[uri_extension].get
298
+ parsed = JSON.parse(response)
299
+ parsed
300
+ end
301
+
302
+ def check_required_fields(fields, required)
303
+ # Check that all required fields are filled in
304
+ missing = required.select{|f| fields[f].blank?}
305
+ unless missing.empty?
306
+ missing_fields = missing.join(', ')
307
+ raise ActsAsIcontact::ValidationError, "Missing required fields: #{missing_fields}"
308
+ end
309
+ end
203
310
  end
204
311
  end
@@ -2,6 +2,11 @@ module ActsAsIcontact
2
2
  # The top-level Accounts resource from iContact. Currently only supports retrieval -- and is
3
3
  # highly targeted toward the _first_ account, since that seems to be the dominant use case.
4
4
  class Account < Resource
5
+ # This is the one major resource that comes directly from the main path.
6
+ def self.base
7
+ ActsAsIcontact.connection
8
+ end
9
+
5
10
  def self.uri_component
6
11
  'a'
7
12
  end
@@ -15,7 +20,7 @@ module ActsAsIcontact
15
20
  # The accountId retrieved from iContact. Can also be set manually for performance optimization,
16
21
  # but remembers it so that it won't be pulled more than once anyway.
17
22
  def self.account_id
18
- @account_id ||= Account.first.accountId.to_i
23
+ @account_id ||= Account.first.id.to_i
19
24
  end
20
25
 
21
26
  # Manually sets the accountId used in subsequent calls. Setting this in your initializer will save
@@ -2,6 +2,11 @@ module ActsAsIcontact
2
2
  # The nested Client Folder resource from iContact. Currently only supports retrieval -- and is
3
3
  # highly targeted toward the _first_ client folder, since that seems to be the dominant use case.
4
4
  class Client < Resource
5
+ # Derives from the Account resource.
6
+ def self.base
7
+ ActsAsIcontact.account
8
+ end
9
+
5
10
  def self.resource_name
6
11
  'clientfolder'
7
12
  end
@@ -1,7 +1,20 @@
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
8
+
9
+ # Derived from clientFolder
10
+ def self.base
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)
17
+ end
18
+
6
19
  end
7
20
  end
@@ -0,0 +1,24 @@
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
+
17
+ # The welcome message pointed to by the welcomeMessageId.
18
+ def welcomeMessage
19
+ return nil unless welcomeMessageId
20
+ ActsAsIcontact::Message.find(welcomeMessageId)
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module ActsAsIcontact
2
+ class Message < Resource
3
+ # Has a default messageType of "normal" if another isn't passed as an option.
4
+ def initialize(properties={})
5
+ super({:messageType => "normal"}.merge(properties))
6
+ end
7
+
8
+ # Requires messageType and subject
9
+ def self.required_on_create
10
+ super << "messageType" << "subject"
11
+ end
12
+
13
+ # messageType must be one of four values: normal, autoresponder, welcome, or confirmation
14
+ def validate_on_save(fields)
15
+ messageType = %w(normal autoresponder welcome confirmation)
16
+ raise ActsAsIcontact::ValidationError, "messageType must be one of: " + messageType.join(', ') unless messageType.include?(fields["messageType"])
17
+ end
18
+ end
19
+ end
data/spec/config_spec.rb CHANGED
@@ -41,9 +41,9 @@ describe "Configuration" do
41
41
  Object.expects(:const_defined?).with(:Rails).returns(true)
42
42
  end
43
43
 
44
- it "is beta if RAILS_ENV is not production" do
44
+ it "is sandbox if RAILS_ENV is not production" do
45
45
  ENV["RAILS_ENV"] = 'staging'
46
- ActsAsIcontact::Config.mode.should == :beta
46
+ ActsAsIcontact::Config.mode.should == :sandbox
47
47
  end
48
48
 
49
49
  it "is production if RAILS_ENV is production" do
@@ -65,9 +65,9 @@ describe "Configuration" do
65
65
  Object.expects(:const_defined?).with(:Rack).returns(true)
66
66
  end
67
67
 
68
- it "is beta if RACK_ENV is not production" do
68
+ it "is sandbox if RACK_ENV is not production" do
69
69
  ENV["RACK_ENV"] = 'staging'
70
- ActsAsIcontact::Config.mode.should == :beta
70
+ ActsAsIcontact::Config.mode.should == :sandbox
71
71
  end
72
72
 
73
73
  it "is production if RACK_ENV is production" do
@@ -81,17 +81,17 @@ describe "Configuration" do
81
81
  end
82
82
 
83
83
 
84
- context ":beta" do
84
+ context ":sandbox" do
85
85
  before(:each) do
86
- ActsAsIcontact::Config.mode = :beta
86
+ ActsAsIcontact::Config.mode = :sandbox
87
87
  end
88
88
 
89
- it "returns the beta AppId" do
89
+ it "returns the sandbox AppId" do
90
90
  ActsAsIcontact::Config.app_id.should == "Ml5SnuFhnoOsuZeTOuZQnLUHTbzeUyhx"
91
91
  end
92
92
 
93
- it "returns the beta URL" do
94
- ActsAsIcontact::Config.url.should == "https://app.beta.icontact.com/icp/"
93
+ it "returns the sandbox URL" do
94
+ ActsAsIcontact::Config.url.should == "https://app.sandbox.icontact.com/icp/"
95
95
  end
96
96
 
97
97
  end
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe ActsAsIcontact::Resource do
4
4
  it "has a RestClient connection" do
5
- ActsAsIcontact::Resource.connection.url.should == ActsAsIcontact.connection['resources'].url
5
+ ActsAsIcontact::Resource.connection.url.should == ActsAsIcontact.client['resources'].url
6
6
  end
7
7
 
8
8
  it "can return all resources for the given URL" do
@@ -51,6 +51,35 @@ 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(:first).with({:foo=>:bar}).returns(nil)
70
+ ActsAsIcontact::Resource.find(:first, :foo=>:bar)
71
+ end
72
+
73
+ it "maps the 'all' method to find(:all)" do
74
+ ActsAsIcontact::Resource.expects(:all).with({:foo=>:bar}).returns(nil)
75
+ ActsAsIcontact::Resource.find(:all, :foo=>:bar)
76
+ end
77
+
78
+ it "can find a single resource by ID" do
79
+ a = ActsAsIcontact::Resource.find(1)
80
+ a.too.should == "sar"
81
+ end
82
+
54
83
  end
55
84
 
56
85
  it "knows its properties" do
@@ -70,7 +99,7 @@ describe ActsAsIcontact::Resource do
70
99
 
71
100
  it "has its own connection if it's not new" do
72
101
  r = ActsAsIcontact::Resource.first
73
- r.connection.url.should == ActsAsIcontact.connection['resources/1'].url
102
+ r.connection.url.should == ActsAsIcontact.client['resources/1'].url
74
103
  end
75
104
 
76
105
  it "does not have a connection if it's new" do
@@ -79,7 +108,7 @@ describe ActsAsIcontact::Resource do
79
108
  end
80
109
 
81
110
  it "knows its REST base resource" do
82
- ActsAsIcontact::Resource.base.should == ActsAsIcontact.connection
111
+ ActsAsIcontact::Resource.base.should == ActsAsIcontact.client
83
112
  end
84
113
 
85
114
  it "knows its primary key" do
@@ -94,6 +123,16 @@ describe ActsAsIcontact::Resource do
94
123
  ActsAsIcontact::Resource.never_on_create.should == ["resourceId"]
95
124
  end
96
125
 
126
+ it "accepts symbols for properties on creation" do
127
+ a = ActsAsIcontact::Resource.new(:foofoo => "bunny")
128
+ a.foofoo.should == "bunny"
129
+ end
130
+
131
+ it "typecasts all integer property values if it can" do
132
+ a = ActsAsIcontact::Resource.new("indianaPi" => "3")
133
+ a.indianaPi.should == 3
134
+ end
135
+
97
136
  context "updating records" do
98
137
  before(:each) do
99
138
  @res = ActsAsIcontact::Resource.first
@@ -114,7 +153,12 @@ describe ActsAsIcontact::Resource do
114
153
 
115
154
  it "knows the minimum set of properties that changed or must be sent" do
116
155
  @res.too = "tar"
117
- @res.send(:update_fields).should == {"resourceId" => "1", "too" => "tar"}
156
+ @res.send(:update_fields).should == {"resourceId" => 1, "too" => "tar"}
157
+ end
158
+
159
+ it "throws an exception if required fields aren't included" do
160
+ @res.resourceId = nil
161
+ lambda{@res.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: resourceId")
118
162
  end
119
163
 
120
164
  context "with successful save" do
@@ -219,7 +263,7 @@ describe ActsAsIcontact::Resource do
219
263
 
220
264
  context "with successful save" do
221
265
  before(:each) do
222
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/resources", :body => %q<{"resources":[{"resourceId":"100","foo":"flar","kroo":"krar","too":"sar"}]}>)
266
+ FakeWeb.register_uri(:post, "https://app.sandbox.icontact.com/icp/a/111111/c/222222/resources", :body => %q<{"resources":[{"resourceId":"100","foo":"flar","kroo":"krar","too":"sar"}]}>)
223
267
  @res.too = "sar"
224
268
  end
225
269
 
@@ -249,7 +293,7 @@ describe ActsAsIcontact::Resource do
249
293
 
250
294
  context "with failed save but status 200" do
251
295
  before(:each) do
252
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/resources", :body => %q<{"resources":[],"warnings":["You did not provide a foo. foo is a required field. Please provide a foo","This was not a good record"]}>)
296
+ FakeWeb.register_uri(:post, "https://app.sandbox.icontact.com/icp/a/111111/c/222222/resources", :body => %q<{"resources":[],"warnings":["You did not provide a foo. foo is a required field. Please provide a foo","This was not a good record"]}>)
253
297
  @res = ActsAsIcontact::Resource.new
254
298
  @res.foo = nil
255
299
  @result = @res.save
@@ -274,7 +318,7 @@ describe ActsAsIcontact::Resource do
274
318
 
275
319
  context "with failed save on HTTP failure exception" do
276
320
  before(:each) do
277
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/resources", :status => ["400","Bad Request"], :body => %q<{"errors":["You did not provide a clue. Clue is a required field. Please provide a clue"]}>)
321
+ FakeWeb.register_uri(:post, "https://app.sandbox.icontact.com/icp/a/111111/c/222222/resources", :status => ["400","Bad Request"], :body => %q<{"errors":["You did not provide a clue. Clue is a required field. Please provide a clue"]}>)
278
322
  @res = ActsAsIcontact::Resource.new
279
323
  @res.foo = nil
280
324
  @result = @res.save
@@ -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,35 @@
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
+
31
+ it "knows its welcome message" do
32
+ @list.welcomeMessage.should == ActsAsIcontact::Message.find(555555)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ActsAsIcontact::Message do
4
+
5
+ it "defaults messageType to normal" do
6
+ m = ActsAsIcontact::Message.new
7
+ m.messageType.should == "normal"
8
+ end
9
+
10
+ it "requires messageType and subject" do
11
+ m = ActsAsIcontact::Message.new(:messageType => nil)
12
+ lambda{m.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: messageType, subject")
13
+ end
14
+
15
+ it "must have an acceptable messageType" do
16
+ m = ActsAsIcontact::Message.new(:messageType => "dummy", :subject => "test")
17
+ lambda{m.save}.should raise_error(ActsAsIcontact::ValidationError, "messageType must be one of: normal, autoresponder, welcome, confirmation")
18
+ end
19
+
20
+ context "associations" do
21
+ before(:each) do
22
+ @message = ActsAsIcontact::Message.first(:subject => "Test Message")
23
+ end
24
+
25
+ it "knows which campaign it has (if any)"
26
+ end
27
+
28
+ end
data/spec/spec_fakeweb.rb CHANGED
@@ -2,22 +2,41 @@ require 'rubygems'
2
2
  require 'fakeweb'
3
3
 
4
4
  FakeWeb.allow_net_connect = false
5
+ i = "https://app.sandbox.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, "https://app.beta.icontact.com/icp/resources", :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}>)
8
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/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}>)
9
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/resources?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}>)
10
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/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}>)
11
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/resources?offset=10&limit=5", :body => %q<{"resources":[{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":5,"offset":10}>)
12
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/resources/1", :body => %q<{"resource":{"foo":"bar","resourceId":"1","too":"sar"}}>)
13
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/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"]}>)
14
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/resources/3", :status => ["400","Bad Request"], :body => %q<{"errors":["You did not provide a clue. Clue is a required field. Please provide a clue"]}>)
9
+ FakeWeb.register_uri(:get, "#{ic}/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, "#{ic}/resources?limit=1", :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}>)
11
+ FakeWeb.register_uri(:get, "#{ic}/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}>)
12
+ FakeWeb.register_uri(:get, "#{ic}/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}>)
13
+ FakeWeb.register_uri(:get, "#{ic}/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}>)
14
+ FakeWeb.register_uri(:get, "#{ic}/resources?offset=10&limit=5", :body => %q<{"resources":[{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":5,"offset":10}>)
15
+ FakeWeb.register_uri(:get, "#{ic}/resources/1", :body => %q<{"resource":{"foo":"bar","resourceId":"1","too":"sar"}}>)
16
+ FakeWeb.register_uri(:post, "#{ic}/resources/1", :body => %q<{"resource":{"foo":"bar","resourceId":"1","too":"sar"}}>)
17
+ FakeWeb.register_uri(:post, "#{ic}/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"]}>)
18
+ FakeWeb.register_uri(:post, "#{ic}/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
19
 
16
20
  # Time
17
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/time", :body => %q<{"time":"2009-07-13T01:28:18-04:00","timestamp":1247462898}>)
21
+ FakeWeb.register_uri(:get, "#{i}/time", :body => %q<{"time":"2009-07-13T01:28:18-04:00","timestamp":1247462898}>)
18
22
 
19
23
  # Accounts
20
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/a", :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}>)
24
+ FakeWeb.register_uri(:get, "#{i}/a?limit=1", :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}>)
25
+ 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
26
 
22
27
  # Clients
23
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/a/111111/c", :body => %q<{"clientfolders":[{"clientFolderId":"222222","logoId":null,"emailRecipient":"bob@example.org"}],"total":1}>)
28
+ FakeWeb.register_uri(:get, "#{i}/a/111111/c?limit=1", :body => %q<{"clientfolders":[{"clientFolderId":"222222","logoId":null,"emailRecipient":"bob@example.org"}],"total":1}>)
29
+ FakeWeb.register_uri(:get, "#{i}/a/111111/c?limit=500", :body => %q<{"clientfolders":[{"clientFolderId":"222222","logoId":null,"emailRecipient":"bob@example.org"}],"total":1}>)
30
+
31
+ # Contacts
32
+ FakeWeb.register_uri(:get, "#{ic}/contacts?limit=1&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"}]}>)
33
+
34
+ # Lists
35
+ FakeWeb.register_uri(:get, "#{ic}/lists?limit=1&name=First%20Test", :body => %q<{"lists":[{"listId":"444444","name":"First Test","emailOwnerOnChange":"0","welcomeOnManualAdd":"0","welcomeOnSignupAdd":"0","welcomeMessageId":"555555","description":"Just a test list."}]}>)
36
+
37
+ # Message
38
+ #### Test message for List association
39
+ FakeWeb.register_uri(:get, "#{ic}/messages/555555", :body => %q<{"message":{"messageId":"555555","subject":"Welcome!","messageType":"welcome","textBody":"Welcome to the Test List!","htmlBody":"<p>Welcome to the <b>Test List</b>!</p>","createDate":"20090725 14:55:12"}}>)
40
+
41
+ #### Test message for associations originating from Message spec
42
+ FakeWeb.register_uri(:get, "#{ic}/messages?limit=1&subject=Test%20Message", :body => %q<{"messages":[{"messageId":"666666","subject":"Test Message","messageType":"normal","textBody":"Hi there!\nThis is just a test.","htmlBody":"<p><b>Hi there!</b></p><p>This is just a <i>test.</i></p>","createDate":"20090725 14:53:33"}]}>)
data/spec/spec_helper.rb CHANGED
@@ -14,5 +14,5 @@ Spec::Runner.configure do |config|
14
14
  config.mock_with :mocha
15
15
 
16
16
  # Set up some reasonable testing variables
17
- ActsAsIcontact::Config.mode = :beta
17
+ ActsAsIcontact::Config.mode = :sandbox
18
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_icontact
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Eley
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-24 00:00:00 -04:00
12
+ date: 2009-07-25 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -43,6 +43,8 @@ files:
43
43
  - lib/acts_as_icontact/resources/account.rb
44
44
  - lib/acts_as_icontact/resources/client.rb
45
45
  - lib/acts_as_icontact/resources/contact.rb
46
+ - lib/acts_as_icontact/resources/list.rb
47
+ - lib/acts_as_icontact/resources/message.rb
46
48
  - spec/config_spec.rb
47
49
  - spec/connection_spec.rb
48
50
  - spec/resource_collection_spec.rb
@@ -50,6 +52,8 @@ files:
50
52
  - spec/resources/account_spec.rb
51
53
  - spec/resources/client_spec.rb
52
54
  - spec/resources/contact_spec.rb
55
+ - spec/resources/list_spec.rb
56
+ - spec/resources/message_spec.rb
53
57
  - spec/spec.opts
54
58
  - spec/spec_fakeweb.rb
55
59
  - spec/spec_helper.rb
@@ -89,5 +93,7 @@ test_files:
89
93
  - spec/resources/account_spec.rb
90
94
  - spec/resources/client_spec.rb
91
95
  - spec/resources/contact_spec.rb
96
+ - spec/resources/list_spec.rb
97
+ - spec/resources/message_spec.rb
92
98
  - spec/spec_fakeweb.rb
93
99
  - spec/spec_helper.rb