context-io 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +129 -0
- data/Rakefile +121 -0
- data/SPEC.md +49 -0
- data/context-io.gemspec +96 -0
- data/lib/context-io.rb +14 -0
- data/lib/context-io/account.rb +254 -0
- data/lib/context-io/authentication.rb +23 -0
- data/lib/context-io/config.rb +103 -0
- data/lib/context-io/connection.rb +45 -0
- data/lib/context-io/core_ext/hash.rb +31 -0
- data/lib/context-io/error.rb +24 -0
- data/lib/context-io/error/bad_request.rb +12 -0
- data/lib/context-io/error/client_error.rb +10 -0
- data/lib/context-io/error/forbidden.rb +12 -0
- data/lib/context-io/error/internal_server_error.rb +10 -0
- data/lib/context-io/error/not_found.rb +12 -0
- data/lib/context-io/error/payment_required.rb +13 -0
- data/lib/context-io/error/server_error.rb +10 -0
- data/lib/context-io/error/service_unavailable.rb +13 -0
- data/lib/context-io/error/unauthorized.rb +12 -0
- data/lib/context-io/file.rb +234 -0
- data/lib/context-io/folder.rb +90 -0
- data/lib/context-io/message.rb +160 -0
- data/lib/context-io/oauth_provider.rb +84 -0
- data/lib/context-io/request.rb +70 -0
- data/lib/context-io/request/oauth.rb +44 -0
- data/lib/context-io/resource.rb +16 -0
- data/lib/context-io/response.rb +5 -0
- data/lib/context-io/response/parse_json.rb +30 -0
- data/lib/context-io/response/raise_client_error.rb +59 -0
- data/lib/context-io/response/raise_server_error.rb +32 -0
- data/lib/context-io/source.rb +193 -0
- data/lib/context-io/version.rb +7 -0
- data/spec/account_spec.rb +247 -0
- data/spec/contextio_spec.rb +45 -0
- data/spec/file_spec.rb +101 -0
- data/spec/fixtures/accounts.json +21 -0
- data/spec/fixtures/files.json +41 -0
- data/spec/fixtures/files_group.json +47 -0
- data/spec/fixtures/folders.json +1 -0
- data/spec/fixtures/messages.json +1 -0
- data/spec/fixtures/oauth_providers.json +12 -0
- data/spec/fixtures/sources.json +1 -0
- data/spec/folder_spec.rb +48 -0
- data/spec/message_spec.rb +294 -0
- data/spec/oauth_provider_spec.rb +88 -0
- data/spec/source_spec.rb +248 -0
- data/spec/spec_helper.rb +4 -0
- metadata +214 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'context-io/connection'
|
2
|
+
require 'context-io/request'
|
3
|
+
|
4
|
+
module ContextIO
|
5
|
+
# The superclass of all resources provided by the API
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class Resource
|
9
|
+
include ContextIO::Connection
|
10
|
+
include ContextIO::Request
|
11
|
+
|
12
|
+
extend ContextIO::Connection
|
13
|
+
extend ContextIO::Request
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module ContextIO
|
5
|
+
module Response
|
6
|
+
# Faraday middleware for parsing JSON in the response body
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class ParseJson < Faraday::Response::Middleware
|
10
|
+
# Parse the response body into JSON
|
11
|
+
#
|
12
|
+
# @param [#to_s] The response body, containing JSON data.
|
13
|
+
#
|
14
|
+
# @return [Object] The parsed JSON data, probably an Array or a Hash.
|
15
|
+
def parse(body)
|
16
|
+
case body
|
17
|
+
when ''
|
18
|
+
nil
|
19
|
+
when 'true'
|
20
|
+
true
|
21
|
+
when 'false'
|
22
|
+
false
|
23
|
+
else
|
24
|
+
::MultiJson.decode(body)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'context-io/error/bad_request'
|
3
|
+
require 'context-io/error/forbidden'
|
4
|
+
require 'context-io/error/not_found'
|
5
|
+
require 'context-io/error/unauthorized'
|
6
|
+
require 'context-io/error/payment_required'
|
7
|
+
|
8
|
+
module ContextIO
|
9
|
+
module Response
|
10
|
+
# Faraday middleware for raising errors on 4xx HTTP status codes
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
class RaiseClientError < Faraday::Response::Middleware
|
14
|
+
# Raise an error if the response has a 4xx status code
|
15
|
+
#
|
16
|
+
# @raise [ContextIO::Error::ClientError] If the response has a 4xx status
|
17
|
+
# code.
|
18
|
+
# @raise [ContextIO::Error::BadRequest] If the response has a 400 status
|
19
|
+
# code.
|
20
|
+
# @raise [ContextIO::Error::Unauthorized] If the response has a 401 status
|
21
|
+
# code.
|
22
|
+
# @raise [ContextIO::Error::PaymentRequired] If the response has a 402
|
23
|
+
# status code.
|
24
|
+
# @raise [ContextIO::Error::Forbidden] If the response has a 403 status
|
25
|
+
# code.
|
26
|
+
# @raise [ContextIO::Error::NotFound] If the response has a 404 status
|
27
|
+
# code.
|
28
|
+
#
|
29
|
+
# @return [void]
|
30
|
+
def on_complete(env)
|
31
|
+
case env[:status].to_i
|
32
|
+
when 400
|
33
|
+
raise ContextIO::Error::BadRequest.new(error_body(env[:body]), env[:response_headers])
|
34
|
+
when 401
|
35
|
+
raise ContextIO::Error::Unauthorized.new(error_body(env[:body]), env[:response_headers])
|
36
|
+
when 402
|
37
|
+
raise ContextIO::Error::PaymentRequired.new(error_body(env[:body]), env[:response_headers])
|
38
|
+
when 403
|
39
|
+
raise ContextIO::Error::Forbidden.new(error_body(env[:body]), env[:response_headers])
|
40
|
+
when 404
|
41
|
+
raise ContextIO::Error::NotFound.new(error_body(env[:body]), env[:response_headers])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @return [String] The error message if one is defines, or an empty
|
48
|
+
# string.
|
49
|
+
def error_body(body)
|
50
|
+
if body && body['type'] == 'error' && body['value']
|
51
|
+
body['value']
|
52
|
+
else
|
53
|
+
''
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'context-io/error/internal_server_error'
|
3
|
+
require 'context-io/error/service_unavailable'
|
4
|
+
|
5
|
+
module ContextIO
|
6
|
+
module Response
|
7
|
+
# Faraday middleware for raising errors on 5xx status codes
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class RaiseServerError < Faraday::Response::Middleware
|
11
|
+
# Raise an error if the response has a 5xx status code
|
12
|
+
#
|
13
|
+
# @raise [ContextIO::Error::ServerError] If the response has a 5xx status
|
14
|
+
# code.
|
15
|
+
# @raise [ContextIO::Error::InternalServerError] If the response has a 500
|
16
|
+
# status code.
|
17
|
+
# @raise [ContextIO::Error::ServiceUnavailable] If the response has a 503
|
18
|
+
# status code.
|
19
|
+
#
|
20
|
+
# @return [void]
|
21
|
+
def on_complete(env)
|
22
|
+
case env[:status].to_i
|
23
|
+
when 500
|
24
|
+
raise ContextIO::Error::InternalServerError.new('Something is technically wrong.', env[:response_headers])
|
25
|
+
when 503
|
26
|
+
raise ContextIO::Error::ServiceUnavailable.new('Could not connect to mail server.', env[:response_headers])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'context-io/resource'
|
2
|
+
|
3
|
+
module ContextIO
|
4
|
+
# A message source. Create one of these for each mailbox a user has
|
5
|
+
#
|
6
|
+
# @api public
|
7
|
+
class Source < ContextIO::Resource
|
8
|
+
attr_accessor :label, :authentication_type, :port, :service_level, :username,
|
9
|
+
:server, :source_type, :sync_period, :use_ssl, :status
|
10
|
+
attr_reader :account_id, :email
|
11
|
+
|
12
|
+
# Public: Get all sources for given account.
|
13
|
+
#
|
14
|
+
# query - An optional Hash (default: {}) containing a query to filter the
|
15
|
+
# responses. For possible values see Context.IO API documentation.
|
16
|
+
# Returns an Array of Source objects.
|
17
|
+
#
|
18
|
+
# account - Account object or ID
|
19
|
+
def self.all(account, query = {})
|
20
|
+
return [] if account.nil?
|
21
|
+
|
22
|
+
account_id = account.is_a?(Account) ? account.id : account.to_s
|
23
|
+
get("/2.0/accounts/#{account_id}/sources", query).map do |msg|
|
24
|
+
Source.from_json(account_id, msg)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Find a source for given ID
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
#
|
32
|
+
# @param [String] label The label of the source to look up.
|
33
|
+
#
|
34
|
+
# @example Find the source with the labe 'foobar'
|
35
|
+
# ContextIO::Source.find('abcdef012345', 'foobar')
|
36
|
+
#
|
37
|
+
# @return [Source] The source with the given label.
|
38
|
+
def self.find(account, label)
|
39
|
+
return nil if account.nil? or label.to_s.empty?
|
40
|
+
account_id = account.is_a?(Account) ? account.id : account.to_s
|
41
|
+
|
42
|
+
Source.from_json(account_id, get("/2.0/accounts/#{account_id}/sources/#{label.to_s}"))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create a Source instance from the JSON returned by the Context.IO server
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
#
|
49
|
+
# @param [Hash] The parsed JSON object returned by a Context.IO API request.
|
50
|
+
# See their documentation for what keys are possible.
|
51
|
+
#
|
52
|
+
# @return [Source] A source with the given attributes.
|
53
|
+
def self.from_json(account_id, json)
|
54
|
+
source = new(account_id, json)
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize(account_id, attributes = {})
|
58
|
+
raise ArgumentError if account_id.to_s.empty?
|
59
|
+
|
60
|
+
@account_id = account_id.to_s
|
61
|
+
@email = attributes['email']
|
62
|
+
@label = attributes['label'] || ''
|
63
|
+
@authentication_type = attributes['authentication_type']
|
64
|
+
@port = attributes['port'] || 143
|
65
|
+
@service_level = attributes['service_level']
|
66
|
+
@username = attributes['username']
|
67
|
+
@server = attributes['server']
|
68
|
+
@source_type = attributes['type'] || 'IMAP'
|
69
|
+
@sync_period = attributes['sync_period']
|
70
|
+
@use_ssl = attributes['use_ssl'] || false
|
71
|
+
@status = attributes['status']
|
72
|
+
@password = attributes['password']
|
73
|
+
@provider_token = attributes['provider_token']
|
74
|
+
@provider_token_secret = attributes['provider_token_secret']
|
75
|
+
@provider_consumer_key = attributes['provider_consumer_key']
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns all source's folders.
|
79
|
+
def folders
|
80
|
+
ContextIO::Folder.all(@account_id, @label)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Sends the source data to Context.IO
|
84
|
+
#
|
85
|
+
# If the source has been sent to Context.IO before, this will update
|
86
|
+
# allowed source attributes.
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
#
|
90
|
+
# @raise [ArgumentError] If required arguments are missing.
|
91
|
+
#
|
92
|
+
# @example Create a source
|
93
|
+
# source = ContextIO::Source.new(@account.id, {'email' => 'me@example.com', 'server' => 'imap@example.com',
|
94
|
+
# 'username' => "me", 'use_ssl' => true, 'port' => 143, 'type' => 'IMAP'})
|
95
|
+
# source.save
|
96
|
+
#
|
97
|
+
# @return [true, false] Whether the save succeeded or not.
|
98
|
+
def save
|
99
|
+
@label.to_s.empty? ? create_record : update_record
|
100
|
+
end
|
101
|
+
|
102
|
+
# Destroys current source object
|
103
|
+
def destroy
|
104
|
+
return false if @label.to_s.empty?
|
105
|
+
|
106
|
+
response = delete("/2.0/accounts/#{@account_id}/sources/#{@label}")
|
107
|
+
@label = '' if response['success']
|
108
|
+
|
109
|
+
response['success']
|
110
|
+
end
|
111
|
+
|
112
|
+
# Update attributes on the Source object and then send them to Context.IO
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
#
|
116
|
+
# @param [Hash] attributes The attributes to update. Allowed
|
117
|
+
# attributes are status, sync period, service level, password,
|
118
|
+
# provider token, provider token secret and provider consumer key
|
119
|
+
#
|
120
|
+
# @example Update the Source sync period to one day
|
121
|
+
# source.update_attributes('sync_period' => '1d')
|
122
|
+
#
|
123
|
+
# @return [true, false] Whether the update succeeded or not.
|
124
|
+
def update_attributes(attributes = {})
|
125
|
+
raise ArgumentError.new("Cannot set attributes on new record") if @label.to_s.empty?
|
126
|
+
|
127
|
+
attributes.each do |k,v|
|
128
|
+
if ["status", "sync_period", "service_level", "password", "provider_token", "provider_token_secret", "provider_consumer_key"].include? k
|
129
|
+
send("#{k}=", v)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
update_record
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# Create the Source on Context.IO
|
138
|
+
#
|
139
|
+
# @api private
|
140
|
+
#
|
141
|
+
# @return [true, false] Whether the creation succeeded or not.
|
142
|
+
def create_record
|
143
|
+
if @email.to_s.empty? or @server.to_s.empty? or @username.to_s.empty? or @port.to_s.empty?
|
144
|
+
raise ArgumentError.new("Mandatory arguments are not set")
|
145
|
+
end
|
146
|
+
|
147
|
+
params = {}
|
148
|
+
params['email'] = @email.to_s
|
149
|
+
params['server'] = @server.to_s
|
150
|
+
params['username'] = @username.to_s
|
151
|
+
params['use_ssl'] = @use_ssl.to_s
|
152
|
+
params['port'] = @port.to_s
|
153
|
+
params['type'] = @source_type.to_s
|
154
|
+
|
155
|
+
# Optional parameters
|
156
|
+
params['service_level'] = @service_level if @service_level
|
157
|
+
params['sync_period'] = @sync_period if @sync_period
|
158
|
+
params['password'] = @password if @password
|
159
|
+
params['provider_token'] = @provider_token if @provider_token
|
160
|
+
params['provider_token_secret'] = @provider_token_secret if @provider_token_secret
|
161
|
+
params['provider_consumer_key'] = @provider_consumer_key if @provider_consumer_key
|
162
|
+
|
163
|
+
response = post("/2.0/accounts/#{@account_id}/sources", params)
|
164
|
+
response['success']
|
165
|
+
end
|
166
|
+
|
167
|
+
# Update existing Source on Context.IO
|
168
|
+
#
|
169
|
+
# Only sends the status, sync period, service level, password,
|
170
|
+
# provider token, provider token secret and provider consumer key
|
171
|
+
# as they are the only attributes the Context.IO API allows to be updated.
|
172
|
+
#
|
173
|
+
# @api private
|
174
|
+
#
|
175
|
+
# @return [true, false] Whether the update succeeded or not.
|
176
|
+
def update_record
|
177
|
+
return false if @label.to_s.empty?
|
178
|
+
|
179
|
+
atts = {}
|
180
|
+
|
181
|
+
atts["status"] = @status if @status
|
182
|
+
atts["sync_period"] = @sync_period if @sync_period
|
183
|
+
atts["service_level"] = @service_level if @service_level
|
184
|
+
atts["password"] = @password if @password
|
185
|
+
atts["provider_token"] = @provider_token if @provider_token
|
186
|
+
atts["provider_token_secret"] = @provider_token_secret if @provider_token_secret
|
187
|
+
atts["provider_consumer_key"] = @provider_consumer_key if @provider_consumer_key
|
188
|
+
|
189
|
+
response = post("/2.0/accounts/#{@account_id}/sources/#{@label}", atts)
|
190
|
+
response['success']
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ContextIO::Account do
|
4
|
+
let(:existing_account) do
|
5
|
+
stub_request(:post, 'https://api.context.io/2.0/accounts').
|
6
|
+
to_return(
|
7
|
+
:body => '{
|
8
|
+
"success": true,
|
9
|
+
"id": "1234567890abcdef",
|
10
|
+
"resource_url": "https://api.context.io/2.0/accounts/1234567890abcdef"
|
11
|
+
}')
|
12
|
+
account = ContextIO::Account.new(:email => 'foo@bar.com')
|
13
|
+
account.save
|
14
|
+
|
15
|
+
account
|
16
|
+
end
|
17
|
+
describe '.all' do
|
18
|
+
before(:each) do
|
19
|
+
json_accs = File.read(File.expand_path(File.join(File.dirname(__FILE__), "fixtures", "accounts.json")))
|
20
|
+
@stub = stub_request(:get, 'https://api.context.io/2.0/accounts').to_return(:body => json_accs)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns an array of Account objects' do
|
24
|
+
accounts = ContextIO::Account.all
|
25
|
+
accounts.first.should be_a(ContextIO::Account)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'calls the API request' do
|
29
|
+
ContextIO::Account.all
|
30
|
+
|
31
|
+
@stub.should have_been_requested
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'sets the attributes on the Account objects' do
|
35
|
+
ContextIO::Account.all.first.id.should == 'abcdef0123456789'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'sends a query if one is given' do
|
39
|
+
@stub = @stub.with(:query => {
|
40
|
+
:email => 'me@example.com',
|
41
|
+
:status => 'OK',
|
42
|
+
:status_ok => '1',
|
43
|
+
:limit => '1',
|
44
|
+
:offset => '0'
|
45
|
+
})
|
46
|
+
|
47
|
+
ContextIO::Account.all(
|
48
|
+
:email => 'me@example.com',
|
49
|
+
:status => :ok,
|
50
|
+
:status_ok => true,
|
51
|
+
:limit => 1,
|
52
|
+
:offset => 0
|
53
|
+
)
|
54
|
+
|
55
|
+
@stub.should have_been_requested
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '.find' do
|
60
|
+
before(:each) do
|
61
|
+
@stub = stub_request(:get, 'https://api.context.io/2.0/accounts/abcdef0123456789').to_return(
|
62
|
+
:body => '{
|
63
|
+
"id": "abcdef0123456789",
|
64
|
+
"username": "me.example.com_1234567890abcdef",
|
65
|
+
"created": 1234567890,
|
66
|
+
"suspended": 0,
|
67
|
+
"email_addresses": [ "me@example.com" ],
|
68
|
+
"first_name": "John",
|
69
|
+
"last_name": "Doe",
|
70
|
+
"password_expired": 0,
|
71
|
+
"sources": [{
|
72
|
+
"server": "mail.example.com",
|
73
|
+
"label": "me::mail.example.com",
|
74
|
+
"username": "me",
|
75
|
+
"port": 993,
|
76
|
+
"authentication_type": "password",
|
77
|
+
"use_ssl": true,
|
78
|
+
"sync_period": "1d",
|
79
|
+
"status": "OK",
|
80
|
+
"service_level": "pro",
|
81
|
+
"type": "imap"
|
82
|
+
}]
|
83
|
+
}'
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns an instance of Account' do
|
88
|
+
ContextIO::Account.find('abcdef0123456789').should be_a(ContextIO::Account)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'calls the API request' do
|
92
|
+
ContextIO::Account.find('abcdef0123456789')
|
93
|
+
|
94
|
+
@stub.should have_been_requested
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'sets the attributes on the Account object' do
|
98
|
+
ContextIO::Account.find('abcdef0123456789').id.should == 'abcdef0123456789'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '.new' do
|
103
|
+
it 'returns an instance of Account' do
|
104
|
+
ContextIO::Account.new.should be_a(ContextIO::Account)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'sets the given attributes on the Account object' do
|
108
|
+
account = ContextIO::Account.new(:first_name => 'John')
|
109
|
+
account.first_name.should == 'John'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#save' do
|
114
|
+
|
115
|
+
|
116
|
+
it 'returns true if the save was successful' do
|
117
|
+
@stub = stub_request(:post, 'https://api.context.io/2.0/accounts').
|
118
|
+
with(:body => { :email => 'me@example.com' }).
|
119
|
+
to_return(
|
120
|
+
:body => '{
|
121
|
+
"success": true,
|
122
|
+
"id": "abcdef0123456789",
|
123
|
+
"resource_url": "https://api.context.io/2.0/accounts/abcdef0123456789"
|
124
|
+
}'
|
125
|
+
)
|
126
|
+
|
127
|
+
account = ContextIO::Account.new(:email => 'me@example.com')
|
128
|
+
|
129
|
+
account.save.should be_true
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'returns false if the save was unsuccessful' do
|
133
|
+
@stub = stub_request(:post, 'https://api.context.io/2.0/accounts').
|
134
|
+
with(:body => { :email => 'me@example.com' }).
|
135
|
+
to_return(
|
136
|
+
:body => '{
|
137
|
+
"success": false
|
138
|
+
}'
|
139
|
+
)
|
140
|
+
|
141
|
+
account = ContextIO::Account.new(:email => 'me@example.com')
|
142
|
+
|
143
|
+
account.save.should be_false
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'for a new account' do
|
147
|
+
before(:each) do
|
148
|
+
@stub = stub_request(:post, 'https://api.context.io/2.0/accounts').
|
149
|
+
with(:body => { :email => 'me@example.com' }).
|
150
|
+
to_return(
|
151
|
+
:body => '{
|
152
|
+
"success": true,
|
153
|
+
"id": "abcdef0123456789",
|
154
|
+
"resource_url": "https://api.context.io/2.0/accounts/abcdef0123456789"
|
155
|
+
}'
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'calls the API request' do
|
160
|
+
ContextIO::Account.new(:email => 'me@example.com').save
|
161
|
+
|
162
|
+
@stub.should have_been_requested
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'sets the ID of the account' do
|
166
|
+
account = ContextIO::Account.new(:email => 'me@example.com')
|
167
|
+
account.save
|
168
|
+
account.id.should == 'abcdef0123456789'
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'for an existing account' do
|
173
|
+
it 'calls the API request' do
|
174
|
+
@stub = stub_request(:put,
|
175
|
+
'https://api.context.io/2.0/accounts/1234567890abcdef').
|
176
|
+
with(:query => { :first_name => 'John' }).
|
177
|
+
to_return(
|
178
|
+
:body => '{
|
179
|
+
"success": true
|
180
|
+
}'
|
181
|
+
)
|
182
|
+
|
183
|
+
existing_account.first_name = 'John'
|
184
|
+
existing_account.save
|
185
|
+
|
186
|
+
@stub.should have_been_requested
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe '#update_attributes' do
|
192
|
+
it 'calls the API request' do
|
193
|
+
@stub = stub_request(:put,
|
194
|
+
'https://api.context.io/2.0/accounts/1234567890abcdef').
|
195
|
+
with(:query => { :first_name => 'John' }).
|
196
|
+
to_return(
|
197
|
+
:body => '{
|
198
|
+
"success": true
|
199
|
+
}'
|
200
|
+
)
|
201
|
+
|
202
|
+
existing_account.update_attributes(:first_name => 'John')
|
203
|
+
|
204
|
+
@stub.should have_been_requested
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'returns true if the update was successful' do
|
208
|
+
stub_request(:put, 'https://api.context.io/2.0/accounts/1234567890abcdef').
|
209
|
+
with(:query => { :first_name => 'John' }).
|
210
|
+
to_return(
|
211
|
+
:body => '{
|
212
|
+
"success": true
|
213
|
+
}'
|
214
|
+
)
|
215
|
+
|
216
|
+
existing_account.update_attributes(:first_name => 'John').should be_true
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'returns false if the update was unsuccessful' do
|
220
|
+
stub_request(:put, 'https://api.context.io/2.0/accounts/1234567890abcdef').
|
221
|
+
with(:query => { :first_name => 'John' }).
|
222
|
+
to_return(
|
223
|
+
:body => '{
|
224
|
+
"success": false
|
225
|
+
}'
|
226
|
+
)
|
227
|
+
|
228
|
+
existing_account.update_attributes(:first_name => 'John').should be_false
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
it 'sets the attributes on the account object' do
|
233
|
+
stub_request(:put, 'https://api.context.io/2.0/accounts/1234567890abcdef').
|
234
|
+
with(:query => { :first_name => 'John' }).
|
235
|
+
to_return(
|
236
|
+
:body => '{
|
237
|
+
"success": true
|
238
|
+
}'
|
239
|
+
)
|
240
|
+
|
241
|
+
existing_account.update_attributes(:first_name => 'John')
|
242
|
+
|
243
|
+
existing_account.first_name.should == 'John'
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|