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 +16 -19
- data/README.md +10 -9
- data/keikokuc.gemspec +3 -3
- data/lib/handles_timeout.rb +6 -2
- data/lib/keikokuc.rb +1 -0
- data/lib/keikokuc/client.rb +69 -4
- data/lib/keikokuc/notification.rb +26 -4
- data/lib/keikokuc/notification_list.rb +74 -0
- data/lib/keikokuc/version.rb +1 -1
- data/spec/factories.rb +9 -0
- data/spec/keikoku/client_spec.rb +86 -10
- data/spec/keikoku/notification_list_spec.rb +40 -0
- data/spec/keikoku/notification_spec.rb +70 -14
- data/spec/spec_helper.rb +6 -2
- data/spec/support/fake_keikoku.rb +50 -0
- metadata +19 -15
data/LICENSE
CHANGED
@@ -1,22 +1,19 @@
|
|
1
|
-
Copyright (
|
1
|
+
Copyright (C) 2012 Harold Giménez
|
2
2
|
|
3
|
-
|
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
|
-
|
6
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
3
|
+
Wrapper to the keikoku API
|
4
|
+
|
5
|
+
[](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
|
-
##
|
21
|
+
## Docs
|
22
|
+
|
23
|
+
For usage examples please see the docs:
|
20
24
|
|
21
|
-
|
25
|
+
[http://rdoc.info/github/hgmnz/keikokuc/master/frames](http://rdoc.info/github/hgmnz/keikokuc/master/frames)
|
22
26
|
|
23
|
-
##
|
27
|
+
## License
|
24
28
|
|
25
|
-
|
26
|
-
|
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
|
9
|
-
gem.summary = %q{Keikoku API
|
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) }
|
data/lib/handles_timeout.rb
CHANGED
@@ -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
|
11
|
+
define_method method_name do |*args|
|
12
12
|
begin
|
13
|
-
|
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
data/lib/keikokuc/client.rb
CHANGED
@@ -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
|
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(
|
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
|
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:
|
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
|
data/lib/keikokuc/version.rb
CHANGED
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
|
data/spec/keikoku/client_spec.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'sham_rack'
|
3
3
|
module Keikokuc
|
4
|
-
|
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].
|
16
|
-
error.
|
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.
|
26
|
-
response[:errors].
|
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].
|
36
|
-
error.
|
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.
|
43
|
-
error.
|
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].
|
11
|
-
args[:account_email].
|
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.
|
18
|
+
expect(result).to be_true
|
19
19
|
|
20
|
-
notification.remote_id.
|
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].
|
27
|
+
expect(args[:message]).to be_nil
|
29
28
|
end.
|
30
|
-
and_return([{ errors: { attributes: { message: ['is not present'] }}},
|
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.
|
35
|
+
expect(result).to be_false
|
36
36
|
|
37
|
-
notification.remote_id.
|
38
|
-
notification.errors[:attributes][:message].
|
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.
|
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 '
|
9
|
-
require '
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *70298442791900
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: yajl-ruby
|
27
|
-
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: *
|
35
|
+
version_requirements: *70298442791480
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
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: *
|
46
|
+
version_requirements: *70298442791040
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: factory_girl
|
49
|
-
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: *
|
57
|
+
version_requirements: *70298442790620
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: sham_rack
|
60
|
-
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: *
|
69
|
-
description: Keikoku
|
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
|
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
|