keikokuc 0.0.3 → 0.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/LICENSE CHANGED
@@ -1,22 +1,19 @@
1
- Copyright (c) 2012 Harold Giménez
1
+ Copyright (C) 2012 Harold Giménez
2
2
 
3
- MIT License
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
4
9
 
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
12
 
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Keikokuc
2
2
 
3
- TODO: Write a gem description
3
+ Wrapper to the keikoku API
4
+
5
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/hgmnz/keikokuc)
4
6
 
5
7
  ## Installation
6
8
 
@@ -16,14 +18,13 @@ Or install it yourself as:
16
18
 
17
19
  $ gem install keikokuc
18
20
 
19
- ## Usage
21
+ ## Docs
22
+
23
+ For usage examples please see the docs:
20
24
 
21
- TODO: Write usage instructions here
25
+ [http://rdoc.info/github/hgmnz/keikokuc/master/frames](http://rdoc.info/github/hgmnz/keikokuc/master/frames)
22
26
 
23
- ## Contributing
27
+ ## License
24
28
 
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
29
+ keikokuc is copyright (c) Harold Giménez and is released under the terms of the
30
+ MIT License found in the LICENSE file.
data/keikokuc.gemspec CHANGED
@@ -5,9 +5,9 @@ require 'factory_girl'
5
5
  Gem::Specification.new do |gem|
6
6
  gem.authors = ["Harold Giménez"]
7
7
  gem.email = ["harold.gimenez@gmail.com"]
8
- gem.description = %q{Keikoku API client}
9
- gem.summary = %q{Keikoku API client}
10
- gem.homepage = ""
8
+ gem.description = %q{Keikoku client}
9
+ gem.summary = %q{Keikoku is an experimental notification system on Heroku. This gem interfaces with the keikoku API to be used bynotification producers and consumers}
10
+ gem.homepage = "https://github.com/hgmnz/keikokuc"
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
13
13
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -8,9 +8,13 @@ module HandlesTimeout
8
8
  module ClassMethods
9
9
  def handle_timeout(method_name)
10
10
  alias_method "#{method_name}_without_timeout", method_name
11
- define_method method_name do |args|
11
+ define_method method_name do |*args|
12
12
  begin
13
- Timeout::timeout(5) { send("#{method_name}_without_timeout", args) }
13
+ if args.empty?
14
+ Timeout::timeout(5) { send("#{method_name}_without_timeout") }
15
+ else
16
+ Timeout::timeout(5) { send("#{method_name}_without_timeout", *args) }
17
+ end
14
18
  rescue Timeout::Error
15
19
  [nil, RequestTimeout]
16
20
  end
data/lib/keikokuc.rb CHANGED
@@ -2,6 +2,7 @@ require 'handles_timeout'
2
2
  require 'keikokuc/version'
3
3
  require 'keikokuc/client'
4
4
  require 'keikokuc/notification'
5
+ require 'keikokuc/notification_list'
5
6
 
6
7
  module Keikokuc
7
8
  end
@@ -11,10 +11,12 @@ class Keikokuc::Client
11
11
  InvalidNotification = Class.new
12
12
  Unauthorized = Class.new
13
13
 
14
- attr_accessor :producer_api_key
14
+ attr_accessor :producer_api_key, :user, :password
15
15
 
16
16
  def initialize(opts = {})
17
17
  @producer_api_key = opts[:producer_api_key]
18
+ @user = opts[:user]
19
+ @password = opts[:password]
18
20
  end
19
21
 
20
22
  # Internal: posts a new notification to keikoku
@@ -44,15 +46,69 @@ class Keikokuc::Client
44
46
  [parse_json(response), nil]
45
47
  rescue RestClient::UnprocessableEntity => e
46
48
  [parse_json(e.response), InvalidNotification]
47
- rescue RestClient::Unauthorized => e
49
+ rescue RestClient::Unauthorized
48
50
  [{}, Unauthorized]
49
51
  end
50
52
  end
51
53
  handle_timeout :post_notification
52
54
 
55
+ # Internal: gets all active notifications for a user
56
+ #
57
+ # Examples
58
+ #
59
+ # client = Keikokuc::Client.new(user: 'user@example.com', password: 'pass')
60
+ # response, error = client.get_notifications
61
+ #
62
+ # Returns
63
+ #
64
+ # two objects:
65
+ # The response as a hash
66
+ # The error if any (nil if no error)
67
+ #
68
+ # Possible errors include:
69
+ #
70
+ # * `Client::Timeout` if the request takes longer than 5 seconds
71
+ # * `Client::Unauthorized` if HTTP Basic auth fails
72
+ def get_notifications
73
+ begin
74
+ response = notifications_api.get
75
+ [parse_json(response), nil]
76
+ rescue RestClient::Unauthorized
77
+ [{}, Unauthorized]
78
+ end
79
+ end
80
+ handle_timeout :get_notifications
81
+
82
+ # Public: Marks a notification as read
83
+ #
84
+ # remote_id - the keikoku id for the notification to mark as read
85
+ #
86
+ # two objects:
87
+ # The response as a hash
88
+ # The error if any (nil if no error)
89
+ #
90
+ # Possible errors include:
91
+ #
92
+ # * `Client::Timeout` if the request takes longer than 5 seconds
93
+ # * `Client::Unauthorized` if HTTP Basic auth fails
94
+ def read_notification(remote_id)
95
+ begin
96
+ response = notifications_api["/#{remote_id}/read"].post ''
97
+ parsed_response = parse_json(response)
98
+ parsed_response[:read_at] = DateTime.parse(parsed_response[:read_at])
99
+ [parsed_response, nil]
100
+ rescue RestClient::Unauthorized
101
+ [{}, Unauthorized]
102
+ end
103
+ end
104
+ handle_timeout :read_notification
105
+
106
+
53
107
  private
54
108
  def notifications_api # :nodoc:
55
- @notifications_api ||= RestClient::Resource.new(api_url)
109
+ @notifications_api ||= RestClient::Resource.new(api_url,
110
+ user: user,
111
+ password: password)
56
112
  end
57
113
 
58
114
  def api_url # :nodoc:
@@ -67,7 +123,16 @@ private
67
123
  symbolize_keys(Yajl::Parser.parse(data)) if data
68
124
  end
69
125
 
70
- def symbolize_keys(hash) # :nodoc:
126
+ def symbolize_keys(object) # :nodoc:
127
+ case object
128
+ when Hash
129
+ symbolize_hash_keys(object)
130
+ when Array
131
+ object.map { |item| symbolize_hash_keys(item) }
132
+ end
133
+ end
134
+
135
+ def symbolize_hash_keys(hash)
71
136
  hash.inject({}) do |result, (k, v)|
72
137
  result[k.to_sym] = v
73
138
  result
@@ -8,7 +8,7 @@
8
8
  # severity: 'info',
9
9
  # target: 'sunny-skies-42'
10
10
  # producer_api_key: 'abcd')
11
- # if notificaiton.publish
11
+ # if notification.publish
12
12
  # # persist notification
13
13
  # else
14
14
  # # handle error
@@ -17,11 +17,11 @@
17
17
  class Keikokuc::Notification
18
18
  ATTRS = %w[message url severity
19
19
  target_name account_email
20
- producer_api_key remote_id errors].freeze
20
+ producer_api_key remote_id errors read_at].freeze
21
21
 
22
22
  attr_accessor *ATTRS
23
23
 
24
- # Public: class constructor
24
+ # Public: Initialize a notification
25
25
  #
26
26
  # opts - a hash of attributes to be set on constructed object
27
27
  #
@@ -36,6 +36,8 @@ class Keikokuc::Notification
36
36
  send("#{attribute}=", opts[attribute.to_sym])
37
37
  end
38
38
  end
39
+ @read = false
40
+ @client = opts[:client]
39
41
  end
40
42
 
41
43
  # Public: publishes this notification to keikoku
@@ -55,6 +57,27 @@ class Keikokuc::Notification
55
57
  error.nil?
56
58
  end
57
59
 
60
+ # Public: marks this notification as read on keikoku
61
+ #
62
+ # Marks the notification as read, after which it will
63
+ # no longer be displayed to any consumer for this user
64
+ #
65
+ # Returns a boolean set to true if marking as read succeeded
66
+ def read
67
+ response, error = client.read_notification(remote_id)
68
+ if error.nil?
69
+ self.read_at = response[:read_at]
70
+ end
71
+ error.nil?
72
+ end
73
+
74
+ # Public: whether this notification is marked as read by this user
75
+ #
76
+ # Returns true if the user has marked this notification as read
77
+ def read?
78
+ !!@read_at
79
+ end
80
+
58
81
  # Internal: coerces this notification to a hash
59
82
  #
60
83
  # Returns this notification's attributes as a hash
@@ -65,7 +88,6 @@ class Keikokuc::Notification
65
88
  end
66
89
  end
67
90
 
68
- private
69
91
  def client # :nodoc:
70
92
  @client ||= Keikokuc::Client.new(producer_api_key: producer_api_key)
71
93
  end
@@ -0,0 +1,74 @@
1
+ # Public: collection of keikoku notifications
2
+ #
3
+ # This class encapsulates Keikoku::Notification objects
4
+ # as a collection.
5
+ #
6
+ # It includes the Enumerable module, so `map`, `detect`,
7
+ # and friends can be used.
8
+ #
9
+ # Examples
10
+ #
11
+ # notifications = Keikokuc::NotificationList.new(user: 'user@example.com',
12
+ # api_key: 'abcd')
13
+ # if notifications.fetch
14
+ # notifications.each do |notification|
15
+ # puts notification.inspect
16
+ # end
17
+ # else
18
+ # # handle error
19
+ # end
20
+ class Keikokuc::NotificationList
21
+ include Enumerable
22
+
23
+ attr_accessor :user, :password
24
+
25
+ # Public: Initializes a NotificationList
26
+ #
27
+ # opts - options hash containing attribute values for the object
28
+ # being constructed accepting the following three keys:
29
+ # user - the heroku account's email (required)
30
+ # password - the heroku account's password (required)
31
+ # client - the client, used for DI in tests
32
+ def initialize(opts)
33
+ @user = opts.fetch(:user)
34
+ @password = opts.fetch(:password)
35
+ @client = opts[:client]
36
+ @notifications = []
37
+ end
38
+
39
+ # Public: fetches notifications for the provided user
40
+ #
41
+ # Sets notifications to a set of `Notification` objects
42
+ # accessible via methods in Enumerable
43
+ #
44
+ # Returns a boolean set to true if fetching succeeded
45
+ def fetch
46
+ result, error = client.get_notifications
47
+ if error.nil?
48
+ @notifications = result.map do |attributes|
49
+ Keikokuc::Notification.new(attributes)
50
+ end
51
+ end
52
+
53
+ error.nil?
54
+ end
55
+
56
+ # Public: the number of notifications
57
+ #
58
+ # Returns an Integer set to the number of notifications
59
+ def size
60
+ @notifications.size
61
+ end
62
+
63
+ # Public: yields each Notification
64
+ #
65
+ # Yields every notification in this collection
66
+ def each
67
+ @notifications.each
68
+ end
69
+
70
+ private
71
+ def client # :nodoc:
72
+ @client ||= Client.new(user: user, password: password)
73
+ end
74
+ end
@@ -1,3 +1,3 @@
1
1
  module Keikokuc
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1"
3
3
  end
data/spec/factories.rb CHANGED
@@ -6,5 +6,14 @@ FactoryGirl.define do
6
6
  target_name 'cloudy-skies-243'
7
7
  severity 'info'
8
8
  account_email 'harold@heroku.com'
9
+
10
+ initialize_with { new(attributes) }
11
+ end
12
+
13
+ factory :notification_list, class: Keikokuc::NotificationList do
14
+ user 'user@example.com'
15
+ password 'pass'
16
+
17
+ initialize_with { new(attributes) }
9
18
  end
10
19
  end
@@ -1,10 +1,13 @@
1
1
  require 'spec_helper'
2
2
  require 'sham_rack'
3
3
  module Keikokuc
4
- describe Client, '#post_notification' do
4
+ shared_context 'client specs' do
5
5
  let(:fake_keikoku) { FakeKeikoku.new }
6
-
7
6
  after { ShamRack.unmount_all }
7
+ end
8
+
9
+ describe Client, '#post_notification' do
10
+ include_context 'client specs'
8
11
 
9
12
  it 'publishes a new notification' do
10
13
  ShamRack.mount(fake_keikoku, "keikoku.herokuapp.com", 443)
@@ -12,8 +15,8 @@ module Keikokuc
12
15
  client = Client.new(producer_api_key: 'abc')
13
16
  result, error = client.post_notification(message: 'hello',
14
17
  severity: 'info')
15
- result[:id].should_not be_nil
16
- error.should be_nil
18
+ expect(result[:id]).not_to be_nil
19
+ expect(error).to be_nil
17
20
  end
18
21
 
19
22
  it 'handles invalid notifications' do
@@ -22,8 +25,8 @@ module Keikokuc
22
25
  end
23
26
 
24
27
  response, error = Client.new.post_notification({})
25
- error.should == Client::InvalidNotification
26
- response[:errors].should == 'srorre'
28
+ expect(error).to be Client::InvalidNotification
29
+ expect(response[:errors]).to eq('srorre')
27
30
  end
28
31
 
29
32
  it 'handles authentication failures' do
@@ -32,15 +35,88 @@ module Keikokuc
32
35
  client = Client.new(producer_api_key: 'bad one')
33
36
  result, error = client.post_notification(message: 'hello',
34
37
  severity: 'info')
35
- result[:id].should be_nil
36
- error.should == Client::Unauthorized
38
+ expect(result[:id]).to be_nil
39
+ expect(error).to eq Client::Unauthorized
37
40
  end
38
41
 
39
42
  it 'handles timeouts' do
40
43
  RestClient::Resource.any_instance.stub(:post).and_raise Timeout::Error
41
44
  response, error = Client.new.post_notification({})
42
- response.should be_nil
43
- error.should == Client::RequestTimeout
45
+ expect(response).to be_nil
46
+ expect(error).to eq(Client::RequestTimeout)
47
+ end
48
+ end
49
+
50
+ describe Client, '#get_notifications' do
51
+ include_context 'client specs'
52
+
53
+ it 'gets all notifications for a user' do
54
+ ShamRack.mount(fake_keikoku, "keikoku.herokuapp.com", 443)
55
+ fake_keikoku.register_publisher(api_key: 'abc')
56
+ fake_keikoku.register_user(email: 'harold@heroku.com', password: 'pass')
57
+ build(:notification, account_email: 'harold@heroku.com', message: 'find me!', producer_api_key: 'abc').publish
58
+ build(:notification, account_email: 'another@heroku.com', producer_api_key: 'abc').publish
59
+
60
+ client = Client.new(user: 'harold@heroku.com', password: 'pass')
61
+
62
+ notifications, error = client.get_notifications
63
+
64
+ expect(error).to be_nil
65
+ expect(notifications).to have(1).item
66
+
67
+ expect(notifications.first[:message]).to eq('find me!')
68
+ end
69
+
70
+ it 'handles timeouts' do
71
+ RestClient::Resource.any_instance.stub(:get).and_raise Timeout::Error
72
+ response, error = Client.new.get_notifications
73
+ expect(response).to be_nil
74
+ expect(error).to eq(Client::RequestTimeout)
75
+ end
76
+
77
+ it 'handles authentication failures' do
78
+ ShamRack.mount(fake_keikoku, "keikoku.herokuapp.com", 443)
79
+ fake_keikoku.register_user(email: 'harold@heroku.com', password: 'pass')
80
+ client = Client.new(user: 'harold@heroku.com', password: 'bad-pass')
81
+
82
+ response, error = client.get_notifications
83
+
84
+ expect(response).to be_empty
85
+ expect(error).to eq(Client::Unauthorized)
86
+ end
87
+ end
88
+
89
+ describe Client, '#read_notification' do
90
+ include_context 'client specs'
91
+ it 'marks the notification as read' do
92
+ ShamRack.mount(fake_keikoku, "keikoku.herokuapp.com", 443)
93
+ fake_keikoku.register_publisher(api_key: 'abc')
94
+ fake_keikoku.register_user(email: 'harold@heroku.com', password: 'pass')
95
+ client = Client.new(user: 'harold@heroku.com', password: 'pass')
96
+ notification = build(:notification, account_email: 'harold@heroku.com', producer_api_key: 'abc')
97
+ notification.publish or raise "Notification publish error"
98
+
99
+ response, error = client.read_notification(notification.remote_id)
100
+ expect(error).to be_nil
101
+
102
+ expect(response[:read_by]).to eq('harold@heroku.com')
103
+ expect(response[:read_at]).to be_within(1).of(DateTime.now)
104
+ end
105
+
106
+ it 'handles authentication errors' do
107
+ ShamRack.mount(fake_keikoku, "keikoku.herokuapp.com", 443)
108
+ fake_keikoku.register_user(email: 'harold@heroku.com', password: 'pass')
109
+ client = Client.new(user: 'harold@heroku.com', password: 'bad-pass')
110
+ response, error = client.read_notification(1)
111
+ expect(response).to be_empty
112
+ expect(error).to eq(Client::Unauthorized)
113
+ end
114
+
115
+ it 'handles timeouts' do
116
+ RestClient::Resource.any_instance.stub(:post).and_raise Timeout::Error
117
+ response, error = Client.new.read_notification(1)
118
+ expect(response).to be_nil
119
+ expect(error).to eq(Client::RequestTimeout)
44
120
  end
45
121
  end
46
122
  end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ module Keikokuc
4
+ describe NotificationList, '#fetch' do
5
+ it 'finds all notifications for the current user' do
6
+ fake_client = double
7
+ fake_client.should_receive(:get_notifications).
8
+ and_return([user_notifications, nil])
9
+ list = build(:notification_list, client: fake_client)
10
+
11
+ result = list.fetch
12
+ expect(result).to be_true
13
+
14
+ expect(list.size).to eq(2)
15
+ list.each do |notification|
16
+ expect(user_notifications.map do |h|
17
+ h[:message]
18
+ end).to include(notification.message)
19
+ expect(notification).to be_kind_of(Notification)
20
+ end
21
+ end
22
+
23
+ def user_notifications
24
+ [
25
+ {
26
+ resource: 'flying-monkey-123',
27
+ message: 'Database HEROKU_POSTGRESQL_BROWN is over row limits',
28
+ url: 'https://devcenter.heroku.com/how-to-fix-problem',
29
+ severity: 'info'
30
+ },
31
+ {
32
+ resource: 'rising-cloud-42',
33
+ message: 'High OOM rates',
34
+ url: 'https://devcenter.heroku.com/oom',
35
+ severity: 'fatal'
36
+ }
37
+ ]
38
+ end
39
+ end
40
+ end
@@ -4,44 +4,100 @@ module Keikokuc
4
4
  describe Notification, '#publish' do
5
5
  it 'publishes to keikoku and stores an id' do
6
6
  fake_client = double
7
- Client.stub(new: fake_client)
8
7
  fake_client.
9
8
  should_receive(:post_notification).with do |args|
10
- args[:message].should == 'hello'
11
- args[:account_email].should == 'harold@heroku.com'
9
+ expect(args[:message]).to eq('hello')
10
+ expect(args[:account_email]).to eq('harold@heroku.com')
12
11
  end.and_return([{ id: 1 }, nil])
13
12
 
14
13
  notification = build(:notification, message: 'hello',
15
- account_email: 'harold@heroku.com')
14
+ account_email: 'harold@heroku.com',
15
+ client: fake_client)
16
16
 
17
17
  result = notification.publish
18
- result.should be_true
18
+ expect(result).to be_true
19
19
 
20
- notification.remote_id.should == 1
20
+ expect(notification.remote_id).to eq(1)
21
21
  end
22
22
 
23
23
  it 'returns false when publishing fails and stores errors' do
24
24
  fake_client = double
25
- Client.stub(new: fake_client)
26
25
  fake_client.
27
26
  should_receive(:post_notification).with do |args|
28
- args[:message].should be_nil
27
+ expect(args[:message]).to be_nil
29
28
  end.
30
- and_return([{ errors: { attributes: { message: ['is not present'] }}}, Keikokuc::Client::InvalidNotification])
29
+ and_return([{ errors: { attributes: { message: ['is not present'] }}},
30
+ Keikokuc::Client::InvalidNotification])
31
31
 
32
- notification = build(:notification, message: nil)
32
+ notification = build(:notification, message: nil, client: fake_client)
33
33
 
34
34
  result = notification.publish
35
- result.should be_false
35
+ expect(result).to be_false
36
36
 
37
- notification.remote_id.should be_nil
38
- notification.errors[:attributes][:message].should == ['is not present']
37
+ expect(notification.remote_id).to be_nil
38
+ expect(notification.errors[:attributes][:message]).to eq(['is not present'])
39
39
  end
40
40
 
41
41
  it 'stores attributes as instance vars' do
42
42
  notification = Notification.new(message: 'foo')
43
- notification.message.should == 'foo'
43
+ expect(notification.message).to eq('foo')
44
44
  end
45
+ end
46
+
47
+ describe Notification, '#read' do
48
+ it 'marks as read to keikoku' do
49
+ fake_client = double
50
+
51
+ fake_client.should_receive(:read_notification).
52
+ with('1234').
53
+ and_return([{read_at: Time.now}, nil])
54
+
55
+ notification = Notification.new(remote_id: '1234', client: fake_client)
56
+
57
+ result = notification.read
58
+ expect(result).to be_true
59
+
60
+ expect(notification.read_at).to be_within(1).of(Time.now)
61
+ expect(notification).to be_read
62
+ end
63
+
64
+ it 'handles errors' do
65
+ fake_client = double
66
+ fake_client.stub(:read_notification).
67
+ with('1234').
68
+ and_return([{}, :some_error])
69
+
70
+ notification = Notification.new(remote_id: '1234', client: fake_client)
71
+
72
+ result = notification.read
73
+ expect(result).to be_false
45
74
 
75
+ expect(notification.read_at).to be_nil
76
+ expect(notification).not_to be_read
77
+ end
78
+ end
79
+
80
+ describe Notification, '#read?' do
81
+ it 'is true if the read_at is known' do
82
+ notification = build(:notification, read_at: nil)
83
+ expect(notification.read?).to be_false
84
+
85
+ notification.read_at = Time.now
86
+
87
+ expect(notification.read?).to be_true
88
+ end
89
+ end
90
+
91
+ describe Notification, '#client' do
92
+ it 'defaults to a properly constructer Keikokuc::Client' do
93
+ notification = build(:notification, producer_api_key: 'fake-api-key')
94
+ expect(notification.client).to be_kind_of(Keikokuc::Client)
95
+ expect(notification.client.producer_api_key).to eq('fake-api-key')
96
+ end
97
+
98
+ it 'can be injected' do
99
+ notification = Notification.new(client: :foo)
100
+ expect(notification.client).to eq(:foo)
101
+ end
46
102
  end
47
103
  end
data/spec/spec_helper.rb CHANGED
@@ -5,8 +5,8 @@
5
5
  #
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
7
  require 'keikokuc'
8
- require './spec/factories'
9
- require './spec/support/fake_keikoku'
8
+ require 'factories'
9
+ require 'support/fake_keikoku'
10
10
  RSpec.configure do |config|
11
11
  config.treat_symbols_as_metadata_keys_with_true_values = true
12
12
  config.run_all_when_everything_filtered = true
@@ -19,4 +19,8 @@ RSpec.configure do |config|
19
19
  config.order = 'random'
20
20
 
21
21
  config.include FactoryGirl::Syntax::Methods
22
+
23
+ config.expect_with :rspec do |c|
24
+ c.syntax = :expect
25
+ end
22
26
  end
@@ -2,16 +2,31 @@ class FakeKeikoku
2
2
  def initialize
3
3
  @publishers = []
4
4
  @notifications = []
5
+ @users = []
5
6
  end
6
7
 
7
8
  def register_publisher(opts)
8
9
  @publishers << opts
9
10
  end
10
11
 
12
+ def register_user(opts)
13
+ @users << opts
14
+ end
15
+
11
16
  def publisher_by_api_key(api_key)
12
17
  @publishers.detect { |p| p[:api_key] == api_key }
13
18
  end
14
19
 
20
+ def find_user(user, pass)
21
+ @users.detect { |u| u[:email] == user && u[:password] == pass }
22
+ end
23
+
24
+ def notifications_for_user(email)
25
+ @notifications.select do |notification|
26
+ notification.to_hash['account_email'] == email
27
+ end
28
+ end
29
+
15
30
  def call(env)
16
31
  with_rack_env(env) do
17
32
  if request_path == '/api/v1/notifications' && request_verb == 'POST'
@@ -22,6 +37,23 @@ class FakeKeikoku
22
37
  else
23
38
  [401, { }, ["Not authorized"]]
24
39
  end
40
+ elsif request_path == '/api/v1/notifications' && request_verb == 'GET'
41
+ if current_user = authenticate_consumer
42
+ notifications = notifications_for_user(current_user).map(&:to_hash)
43
+ [200, { }, [Yajl::Encoder.encode(notifications)]]
44
+ else
45
+ [401, { }, ["Not authorized"]]
46
+ end
47
+ elsif request_path =~ %r{/api/v1/notifications/([^/]+)/read} && request_verb == 'POST'
48
+ if current_user = authenticate_consumer
49
+ notification = notifications_for_user(current_user).detect do |notification|
50
+ notification.to_hash[:id].to_s == $1.to_s
51
+ end
52
+ notification.mark_read_by!(current_user)
53
+ [200, {}, [Yajl::Encoder.encode({read_by: current_user, read_at: Time.now})]]
54
+ else
55
+ [401, { }, ["Not authorized"]]
56
+ end
25
57
  end
26
58
  end
27
59
  end
@@ -57,6 +89,16 @@ private
57
89
  rack_env["HTTP_X_KEIKOKU_AUTH"]
58
90
  end
59
91
 
92
+ def authenticate_consumer
93
+ auth = Rack::Auth::Basic::Request.new(rack_env)
94
+ if auth.provided? && auth.basic? && creds = auth.credentials
95
+ # creds looks like [user, password]
96
+ if find_user(*creds)
97
+ creds.first
98
+ end
99
+ end
100
+ end
101
+
60
102
  def next_id
61
103
  @@sequence ||= 0
62
104
  @@sequence += 1
@@ -71,5 +113,13 @@ private
71
113
  end
72
114
  end
73
115
  end
116
+
117
+ def to_hash
118
+ @opts
119
+ end
120
+
121
+ def mark_read_by!(user)
122
+ (@read_by ||= []) << user
123
+ end
74
124
  end
75
125
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keikokuc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: '0.1'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-08 00:00:00.000000000 Z
12
+ date: 2012-10-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
16
- requirement: &70352245332780 !ruby/object:Gem::Requirement
16
+ requirement: &70298442791900 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70352245332780
24
+ version_requirements: *70298442791900
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yajl-ruby
27
- requirement: &70352245332360 !ruby/object:Gem::Requirement
27
+ requirement: &70298442791480 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70352245332360
35
+ version_requirements: *70298442791480
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70352245331940 !ruby/object:Gem::Requirement
38
+ requirement: &70298442791040 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70352245331940
46
+ version_requirements: *70298442791040
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: factory_girl
49
- requirement: &70352245331520 !ruby/object:Gem::Requirement
49
+ requirement: &70298442790620 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70352245331520
57
+ version_requirements: *70298442790620
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: sham_rack
60
- requirement: &70352245331100 !ruby/object:Gem::Requirement
60
+ requirement: &70298442790180 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,8 +65,8 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70352245331100
69
- description: Keikoku API client
68
+ version_requirements: *70298442790180
69
+ description: Keikoku client
70
70
  email:
71
71
  - harold.gimenez@gmail.com
72
72
  executables: []
@@ -84,13 +84,15 @@ files:
84
84
  - lib/keikokuc.rb
85
85
  - lib/keikokuc/client.rb
86
86
  - lib/keikokuc/notification.rb
87
+ - lib/keikokuc/notification_list.rb
87
88
  - lib/keikokuc/version.rb
88
89
  - spec/factories.rb
89
90
  - spec/keikoku/client_spec.rb
91
+ - spec/keikoku/notification_list_spec.rb
90
92
  - spec/keikoku/notification_spec.rb
91
93
  - spec/spec_helper.rb
92
94
  - spec/support/fake_keikoku.rb
93
- homepage: ''
95
+ homepage: https://github.com/hgmnz/keikokuc
94
96
  licenses: []
95
97
  post_install_message:
96
98
  rdoc_options: []
@@ -113,10 +115,12 @@ rubyforge_project:
113
115
  rubygems_version: 1.8.10
114
116
  signing_key:
115
117
  specification_version: 3
116
- summary: Keikoku API client
118
+ summary: Keikoku is an experimental notification system on Heroku. This gem interfaces
119
+ with the keikoku API to be used bynotification producers and consumers
117
120
  test_files:
118
121
  - spec/factories.rb
119
122
  - spec/keikoku/client_spec.rb
123
+ - spec/keikoku/notification_list_spec.rb
120
124
  - spec/keikoku/notification_spec.rb
121
125
  - spec/spec_helper.rb
122
126
  - spec/support/fake_keikoku.rb