keikokuc 0.0.3 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
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