hashblue-api 0.0.8 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -17,7 +17,7 @@ spec = Dir.chdir(path) do
17
17
 
18
18
  # Change these as appropriate
19
19
  s.name = "hashblue-api"
20
- s.version = "0.0.8"
20
+ s.version = "0.1.1"
21
21
  s.summary = "It's the Hashblue API, stupid!"
22
22
  s.author = "FreeRange"
23
23
  s.email = "lets@gofreerange.com"
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{hashblue-api}
5
- s.version = "0.0.8"
5
+ s.version = "0.1.1"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["FreeRange"]
9
- s.date = %q{2010-05-06}
9
+ s.date = %q{2010-05-19}
10
10
  s.email = %q{lets@gofreerange.com}
11
11
  s.extra_rdoc_files = [
12
12
  "README"
@@ -58,22 +58,10 @@ module Hashblue
58
58
  autoload :Subscriber, 'hashblue/api/subscriber'
59
59
  autoload :Error, 'hashblue/api/error'
60
60
  autoload :TestHelper, 'hashblue/api/test_helper'
61
-
62
- # Raised when the API fails to respond with a timeout. The timeout
63
- # can be set using Hashblue::API.timeout
64
- class NotRespondingError < StandardError; end
65
- # Raised when the API responded in a way that we couldn't handle.
66
- class BadResponseError < Hashblue::API::Error; end
67
- # Raised when the object requested cannot be found.
68
- class NotFoundError < Hashblue::API::Error; end
69
- # Raised when the object requested is not available given the
70
- # credentials used; most often is is because a Contact or Message
71
- # belongs to a different Subscriber
72
- class AccessDeniedError < Hashblue::API::Error
73
- def status
74
- :unauthorized
75
- end
76
- end
61
+ autoload :NotRespondingError, 'hashblue/api/error'
62
+ autoload :BadResponseError, 'hashblue/api/error'
63
+ autoload :NotFoundError, 'hashblue/api/error'
64
+ autoload :AccessDeniedError, 'hashblue/api/error'
77
65
 
78
66
  # Sets the timeout used before raising an error.
79
67
  def self.timeout(value)
@@ -1,39 +1,77 @@
1
1
  # The base Hashblue Error class, which will be sent serialized
2
2
  # as JSON from the API itself.
3
- class Hashblue::API::Error < StandardError
4
- def self.from_response(response)
5
- if response.json? && response.keys == ["error"]
6
- response["error"].keys.first.constantize.new(response["error"].values.first)
7
- elsif response.code == 401
8
- Hashblue::API::AccessDeniedError.new(response)
9
- else
10
- Hashblue::API::BadResponseError.new(response)
11
- end
12
- end
3
+ module Hashblue
4
+ module API
5
+ class Error < StandardError
6
+ def self.status(return_status)
7
+ define_method(:status) { return_status }
8
+ end
13
9
 
14
- def initialize(response)
15
- if response.respond_to?(:body)
16
- @body = response.body
17
- @headers = response.headers
18
- else
19
- @response = response
20
- end
21
- end
10
+ def self.from_response(response)
11
+ if response.json? && response.keys == ["error"]
12
+ response["error"].keys.first.constantize.new(response["error"].values.first)
13
+ elsif response.code == 401
14
+ Hashblue::API::AccessDeniedError.new(response)
15
+ else
16
+ Hashblue::API::BadResponseError.new(response)
17
+ end
18
+ end
22
19
 
23
- def status
24
- :not_found
25
- end
20
+ def initialize(response)
21
+ if response.respond_to?(:body)
22
+ @body = response.body
23
+ @headers = response.headers
24
+ else
25
+ @response = response
26
+ end
27
+ end
28
+
29
+ def status
30
+ :not_found
31
+ end
26
32
 
27
- def to_s
28
- str = "<##{self.class.name}:#{self.object_id}"
29
- if @body && @headers
30
- str + "@headers=#{@headers.inspect} @body=#{@body.inspect}>"
31
- else
32
- str + "@resposne=#{@response.inspect}>"
33
+ def to_s
34
+ str = "<##{self.class.name}:#{self.object_id}"
35
+ if @body && @headers
36
+ str + "@headers=#{@headers.inspect} @body=#{@body.inspect}>"
37
+ else
38
+ str + "@response=#{@response.inspect}>"
39
+ end
40
+ end
41
+
42
+ def to_json
43
+ {self.class.name => self.message}
44
+ end
33
45
  end
34
- end
35
46
 
36
- def to_json
37
- {self.class.name => self.message}
47
+ # Raised when the API fails to respond with a timeout. The timeout
48
+ # can be set using Hashblue::API.timeout
49
+ class NotRespondingError < StandardError; end
50
+ # Raised when the API responded in a way that we couldn't handle.
51
+ class BadResponseError < Hashblue::API::Error; end
52
+ # Raised when the object requested cannot be found.
53
+ class NotFoundError < Hashblue::API::Error; end
54
+ # Raised when the object requested is not available given the
55
+ # credentials used; most often is is because a Contact or Message
56
+ # belongs to a different Subscriber
57
+ class AccessDeniedError < Hashblue::API::Error
58
+ status :unauthorized
59
+ end
60
+ # Raised when a subscriber has reached the limit of messages they
61
+ # can send.
62
+ class RateLimitError < Hashblue::API::Error
63
+ status :service_unavailable
64
+ def message
65
+ @response
66
+ end
67
+ end
68
+ # Raised when the request was malformed (e.g. bad parameters)
69
+ class BadRequestError < Hashblue::API::Error
70
+ status :bad_request
71
+ end
72
+ # Raised when the API is unable to fulfil a request
73
+ class ServiceUnavailableError < Hashblue::API::Error
74
+ status :service_unavailable
75
+ end
38
76
  end
39
77
  end
@@ -1,7 +1,7 @@
1
1
  require 'hashblue/api'
2
2
 
3
3
  # A Message represents an SMS either sent or received by this Subscriber.
4
- # It should not be instantiated directly; Messages can be loaded either
4
+ # It should not be instantiated directly; Messages can be loaded either
5
5
  # using Subscriber#messages, or Contact#messages.
6
6
  class Hashblue::API::Message < Hashblue::API::Model
7
7
  # Deletes a specific message. Note that you must be authenticated as the
@@ -11,6 +11,18 @@ class Hashblue::API::Message < Hashblue::API::Model
11
11
  Hashblue::API.delete("/subscribers/#{subscriber_id}/messages/#{id}.json")
12
12
  end
13
13
 
14
+ # Favourites a specific message. You must be authenticated as the Subscriber
15
+ # in order to favourite a message; otherwise an AccessDeniedError will be
16
+ # raised.
17
+ def self.favourite(subscriber_id, id)
18
+ Hashblue::API.post("/subscribers/#{subscriber_id}/favourites.json", :query => {:id => id})
19
+ end
20
+
21
+ # Un Favourite a specific message. Once authenticated this will mark a message as not a favourite
22
+ def self.unfavourite(subscriber_id, id)
23
+ Hashblue::API.delete("/subscribers/#{subscriber_id}/favourites/#{id}.json")
24
+ end
25
+
14
26
  #:nodoc:
15
27
  def initialize(attributes = {})
16
28
  super
@@ -18,11 +18,11 @@ class Hashblue::API::Model #:nodoc:all
18
18
  @partial_path = "#{@collection}/#{@element}".freeze
19
19
  end
20
20
  end
21
-
21
+
22
22
  def self.model_name
23
23
  @_model_name ||= Name.new(self.name.from(15))
24
24
  end
25
-
25
+
26
26
  def self.from_json(models)
27
27
  models.map do |attributes|
28
28
  new(attributes)
@@ -52,4 +52,8 @@ class Hashblue::API::Model #:nodoc:all
52
52
  def get(path, query_options={})
53
53
  Hashblue::API.get(path, :query => query_options)
54
54
  end
55
+
56
+ def post(path, query_options = {})
57
+ Hashblue::API.post(path, :query => query_options)
58
+ end
55
59
  end
@@ -7,6 +7,10 @@ module Hashblue::API::Request #:nodoc:all
7
7
  _request { super }
8
8
  end
9
9
 
10
+ def post(path, options={})
11
+ _request { super }
12
+ end
13
+
10
14
  def _request
11
15
  _with_timeout do
12
16
  response = Response.new(yield)
@@ -26,7 +30,7 @@ module Hashblue::API::Request #:nodoc:all
26
30
 
27
31
  class Response < DelegateClass(HTTParty::Response)
28
32
  def success?
29
- code == 200
33
+ code == 200 || code == 201
30
34
  end
31
35
 
32
36
  def json?
@@ -47,6 +47,15 @@ class Hashblue::API::Subscriber < Hashblue::API::Model
47
47
  Hashblue::API::Message.from_json(get("#{_path}/messages.json", options))
48
48
  end
49
49
 
50
+ # Returns an Array of Message objects for the subscriber that are marked as favourites
51
+ #
52
+ # All messages that are marked as favourites are included in the returned Array.
53
+ #
54
+ # See above for possible options this method takes.
55
+ def favourites(options={})
56
+ Hashblue::API::Message.from_json(get("#{_path}/favourites.json", options))
57
+ end
58
+
50
59
  # Returns an Array of Message objects for the Subscriber.
51
60
  #
52
61
  # All messages that have been deleted for all contacts are included in
@@ -57,6 +66,16 @@ class Hashblue::API::Subscriber < Hashblue::API::Model
57
66
  Hashblue::API::Message.from_json(get("#{_path}/messages/deleted.json", options))
58
67
  end
59
68
 
69
+ # Send a message from the Subscriber to the given number.
70
+ #
71
+ # The sent message will be returned if it was successfully sent. The contact_msisdn
72
+ # should be in the form "447#########".
73
+ def send_message(contact_msisdn, content)
74
+ result = post("#{_path}/messages.json", :message => {:content => content,
75
+ :contact_msisdn => contact_msisdn} )
76
+ Hashblue::API::Message.new(result)
77
+ end
78
+
60
79
  private
61
80
 
62
81
  def _path
@@ -24,31 +24,35 @@ require 'json'
24
24
  # The test helper will ensure that the data you are passing to +stub_hashblue_api+
25
25
  # matches the data that will be returned to your application by the API.
26
26
  module Hashblue::API::TestHelper
27
- API_KEYS = %w(contact_msisdn content timestamp id sent).map(&:to_sym)
27
+ API_KEYS = %w(contact_msisdn content timestamp id sent favourite).map(&:to_sym)
28
28
 
29
29
  def stub_hashblue_api(subscriber_id, options={})
30
- options.reverse_merge!(:contacts => [], :messages => [])
30
+ options.reverse_merge!(:contacts => [], :messages => [], :favourites => [])
31
31
 
32
- messages = options[:messages].map do |attributes|
33
- {:id => __next_firehose_id, :content => "Hello", :contact_msisdn => "123",
34
- :subscriber_id => subscriber_id, :sent => false,
35
- :timestamp => Time.zone.now.to_json}.merge(prepare_and_check_attributes(attributes))
32
+ options[:messages] = options[:messages].map do |attributes|
33
+ build_hashblue_api_message(subscriber_id, attributes)
36
34
  end
37
35
 
38
- messages.each do |message|
36
+ favourites = []
37
+ options[:messages].each do |message|
39
38
  Hashblue::API.stubs(:delete).with("/subscribers/#{subscriber_id}/messages/#{message[:id]}.json")
39
+ Hashblue::API.stubs(:post).with("/subscribers/#{subscriber_id}/favourites.json",
40
+ :query => {:id => message[:id]}).returns(message)
41
+ if message[:favourite] == true
42
+ Hashblue::API.stubs(:delete).with("/subscribers/#{subscriber_id}/favourites/#{message[:id]}.json")
43
+ favourites << message
44
+ end
40
45
  end
41
46
 
42
- if options[:q]
43
- search_results = messages.select { |message| message[:content][/#{options[:q]}/] }
44
- Hashblue::API.stubs(:get).with("/subscribers/#{subscriber_id}/messages.json",
45
- {:query => {:q => options[:q]}}).returns(search_results)
46
- Hashblue::API.stubs(:get).with("/subscribers/#{subscriber_id}/messages.json", {:query => {}}).returns(messages)
47
- else
48
- Hashblue::API.stubs(:get).with("/subscribers/#{subscriber_id}/messages.json", anything).returns(messages)
49
- end
47
+ Hashblue::API.stubs(:post).with("/subscribers/#{subscriber_id}/messages.json", anything).returns(
48
+ {:content => "sent message", :to=>"123", :sent => true}
49
+ )
50
+
51
+ Hashblue::API.stubs(:get).with("/subscribers/#{subscriber_id}/favourites.json", anything).returns(favourites)
50
52
 
51
- contacts = messages.group_by { |m| m[:contact_msisdn] }.map do |msisdn, contact_messages|
53
+ Hashblue::API.stubs(:get).with("/subscribers/#{subscriber_id}/messages.json", anything).returns(options[:messages])
54
+
55
+ contacts = options[:messages].group_by { |m| m[:contact_msisdn] }.map do |msisdn, contact_messages|
52
56
  explicit_contact = options[:contacts].find { |c| c[:msisdn] == msisdn }
53
57
  id = explicit_contact ? explicit_contact[:id] : __next_firehose_id
54
58
 
@@ -59,7 +63,7 @@ module Hashblue::API::TestHelper
59
63
  end
60
64
 
61
65
  contacts_with_no_messages = options[:contacts].reject do |contact|
62
- messages.find { |m| m[:contact_msisdn] == contact[:msisdn] }
66
+ options[:messages].find { |m| m[:contact_msisdn] == contact[:msisdn] }
63
67
  end.map do |contact|
64
68
  {:latest_message => nil, :id => __next_firehose_id}.merge(contact)
65
69
  end
@@ -69,8 +73,19 @@ module Hashblue::API::TestHelper
69
73
  Hashblue::API.stubs(:get).with("/subscribers/#{subscriber_id}/contacts.json", anything).returns(contacts)
70
74
  end
71
75
 
76
+ def stub_hashblue_api_message_search(subscriber_id, query, search_results)
77
+ Hashblue::API.stubs(:get).with("/subscribers/#{subscriber_id}/messages.json",
78
+ {:query => {:q => query}}).returns(search_results.map { |m| build_hashblue_api_message(subscriber_id, m) })
79
+ end
80
+
72
81
  private
73
82
 
83
+ def build_hashblue_api_message(subscriber_id, attributes = {})
84
+ {:id => __next_firehose_id, :content => "Hello", :contact_msisdn => "123",
85
+ :subscriber_id => subscriber_id, :sent => false, :favourite => false,
86
+ :timestamp => Time.zone.now.to_json}.merge(prepare_and_check_attributes(attributes))
87
+ end
88
+
74
89
  def prepare_and_check_attributes(message)
75
90
  unless (message.keys - API_KEYS).empty?
76
91
  raise "#{message.keys.inspect} doesn't match the current API (#{API_KEYS.inspect})"
@@ -40,9 +40,42 @@ class TestHelperTest < Test::Unit::TestCase
40
40
  end
41
41
  end
42
42
 
43
+ should "stub sending a message" do
44
+ stub_hashblue_api(@subscriber.id)
45
+
46
+ message = @subscriber.send_message("123", "Hey man post this shizzle")
47
+ assert_equal true, message.sent
48
+ end
49
+
50
+ should "stub favourite messages for a subscriber" do
51
+ messages = [{:content => "Blah", :contact_msisdn => "123", :timestamp => 1.day.ago, :favourite => true}]
52
+ stub_hashblue_api(@subscriber.id, :messages => messages)
53
+
54
+ messages = @subscriber.favourites
55
+ assert_equal 1, messages.length
56
+ assert_equal "Blah", messages.first.content
57
+ end
58
+
59
+ should "stub marking a message as a favourite" do
60
+ messages = [{:id=>"123"}]
61
+ stub_hashblue_api(@subscriber.id, :messages => messages)
62
+
63
+ assert_nothing_raised do
64
+ Hashblue::API::Message.favourite(@subscriber.id, "123")
65
+ end
66
+ end
67
+
68
+ should "stub marking a message as unfavourite when stubbing favourites" do
69
+ messages = [{:content => "Blah", :contact_msisdn => "123", :timestamp => 1.day.ago, :id=>"123", :favourite => true}]
70
+ stub_hashblue_api(@subscriber.id, :messages => messages)
71
+
72
+ assert_nothing_raised do
73
+ Hashblue::API::Message.unfavourite(@subscriber.id, "123")
74
+ end
75
+ end
76
+
43
77
  should "stub querying messages by keyword" do
44
- stub_hashblue_api(@subscriber.id, :messages => [{:id => "123", :content => "I'll meet you at the office"},
45
- {:id => "456", :content => "sure thing we'll do that"}], :q => "office")
78
+ stub_hashblue_api_message_search(@subscriber.id, "office", [{:id => "123", :content => "I'll meet you at the office"}])
46
79
  messages = @subscriber.messages(:q => "office")
47
80
  assert_equal 1, messages.length
48
81
  assert_equal "I'll meet you at the office", messages.first.content
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 0
8
- - 8
9
- version: 0.0.8
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - FreeRange
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-07 00:00:00 +01:00
17
+ date: 2010-05-24 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency