hashblue-api 0.0.8 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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