hootenanny 0.0.1 → 0.1.0
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/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +5 -7
- data/Gemfile.lock +46 -28
- data/app/controllers/hootenanny/notifications_controller.rb +31 -0
- data/app/controllers/hootenanny/parameters.rb +16 -0
- data/app/controllers/hootenanny/subscriptions_controller.rb +24 -3
- data/app/models/hootenanny/publish_notification.rb +90 -0
- data/app/models/hootenanny/subscription.rb +72 -23
- data/config/routes.rb +2 -1
- data/db/migrate/20130607182642_add_started_at_and_lease_duration_to_subscriptions.rb +15 -0
- data/db/migrate/20130608225621_add_hmac_secret_to_subscription.rb +5 -0
- data/db/migrate/20130611235218_add_publish_notifications.rb +9 -0
- data/db/migrate/20130612153138_add_timestamps_to_publish_notification.rb +5 -0
- data/db/migrate/20130705200729_add_processed_flag_to_publish_notifications.rb +6 -0
- data/db/migrate/20130711061329_switch_publish_notification_process_state_from_boolean_to_string.rb +11 -0
- data/db/migrate/20130711061558_add_index_to_notifications_state.rb +5 -0
- data/hootenanny.gemspec +6 -2
- data/lib/hootenanny/configuration.rb +43 -0
- data/lib/hootenanny/correspondent.rb +44 -0
- data/lib/hootenanny/errors.rb +42 -1
- data/lib/hootenanny/feed.rb +75 -0
- data/lib/hootenanny/feed/atom_feed.rb +18 -0
- data/lib/hootenanny/feed/atom_feed_item.rb +8 -0
- data/lib/hootenanny/feed/digest_feed.rb +14 -0
- data/lib/hootenanny/feed/digest_feed_item.rb +11 -0
- data/lib/hootenanny/feed/feed_item.rb +30 -0
- data/lib/hootenanny/feed/file.rb +66 -0
- data/lib/hootenanny/feed/json_feed.rb +48 -0
- data/lib/hootenanny/feed/json_feed_item.rb +8 -0
- data/lib/hootenanny/feed/null_feed.rb +27 -0
- data/lib/hootenanny/feed/rss_feed.rb +52 -0
- data/lib/hootenanny/feed/rss_feed_item.rb +8 -0
- data/lib/hootenanny/feed_store.rb +30 -0
- data/lib/hootenanny/feed_store/file_feed_store.rb +55 -0
- data/lib/hootenanny/feed_store/web_feed_store.rb +42 -0
- data/lib/hootenanny/hub.rb +116 -22
- data/lib/hootenanny/publish_notification_expiration_policy.rb +17 -0
- data/lib/hootenanny/request.rb +40 -0
- data/lib/hootenanny/request/publish_notification.rb +94 -0
- data/lib/hootenanny/request/subscription.rb +153 -0
- data/lib/hootenanny/subscription_delivery.rb +47 -0
- data/lib/hootenanny/topic.rb +38 -0
- data/lib/hootenanny/topic_synchronizer.rb +71 -0
- data/lib/hootenanny/uri.rb +53 -0
- data/lib/hootenanny/verification.rb +108 -0
- data/lib/hootenanny/version.rb +1 -1
- data/spec/dummy/config/database.yml +12 -6
- data/spec/dummy/db/migrate/20130607183149_add_started_at_and_lease_duration_to_subscriptions.hootenanny.rb +16 -0
- data/spec/dummy/db/migrate/20130608231253_add_hmac_secret_to_subscription.hootenanny.rb +6 -0
- data/spec/dummy/db/migrate/20130611235546_add_publish_notifications.hootenanny.rb +10 -0
- data/spec/dummy/db/migrate/20130612153353_add_timestamps_to_publish_notification.hootenanny.rb +6 -0
- data/spec/dummy/db/migrate/20130705200832_add_processed_flag_to_publish_notifications.hootenanny.rb +7 -0
- data/spec/dummy/db/migrate/20130711061518_switch_publish_notification_process_state_from_boolean_to_string.hootenanny.rb +12 -0
- data/spec/dummy/db/migrate/20130711061629_add_index_to_notifications_state.hootenanny.rb +6 -0
- data/spec/factories/publish_notification.rb +13 -0
- data/spec/factories/requests/publish_notification.rb +11 -0
- data/spec/factories/requests/subscription.rb +46 -0
- data/spec/factories/subscription.rb +14 -2
- data/spec/factories/verification.rb +15 -0
- data/spec/features/publishers/can_notify_the_hub_of_content_updates_spec.rb +58 -0
- data/spec/features/subscribers/are_protected_from_unwarranted_subscriptions_spec.rb +59 -0
- data/spec/features/subscribers/can_receive_distributions_of_topic_content_spec.rb +175 -0
- data/spec/features/subscribers/can_subscribe_to_a_topic_spec.rb +76 -7
- data/spec/fixtures/feeds/atom/97d79220f68b4bf27.atom +0 -0
- data/spec/fixtures/feeds/atom/sample_feed.atom +51 -0
- data/spec/fixtures/feeds/digest/97d79220f68b4bf27.digest +1 -0
- data/spec/fixtures/feeds/digest/complete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +6 -0
- data/spec/fixtures/feeds/digest/incomplete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +5 -0
- data/spec/fixtures/feeds/digest/sample_feed.digest +6 -0
- data/spec/fixtures/feeds/json/97d79220f68b4bf27.json +1 -0
- data/spec/fixtures/feeds/json/feed_with_one_item.json +21 -0
- data/spec/fixtures/feeds/json/sample_feed.json +30 -0
- data/spec/fixtures/feeds/rss/5b187098da59f077f/97d79220f68b4bf27.rss +21 -0
- data/spec/fixtures/feeds/rss/97d79220f68b4bf27.rss +0 -0
- data/spec/fixtures/feeds/rss/feed_with_one_item.rss +15 -0
- data/spec/fixtures/feeds/rss/minimal_feed.rss +12 -0
- data/spec/fixtures/feeds/rss/sample_feed.rss +22 -0
- data/spec/fixtures/feeds/rss/sample_feed_2.rss +22 -0
- data/spec/lib/hootenanny/configuration_spec.rb +7 -0
- data/spec/lib/hootenanny/correspondent_spec.rb +94 -0
- data/spec/lib/hootenanny/errors_spec.rb +21 -0
- data/spec/lib/hootenanny/feed/atom_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/atom_feed_spec.rb +40 -0
- data/spec/lib/hootenanny/feed/digest_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/digest_feed_spec.rb +40 -0
- data/spec/lib/hootenanny/feed/feed_item_spec.rb +49 -0
- data/spec/lib/hootenanny/feed/file_spec.rb +66 -0
- data/spec/lib/hootenanny/feed/json_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/json_feed_spec.rb +128 -0
- data/spec/lib/hootenanny/feed/null_feed_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/rss_feed_item_spec.rb +9 -0
- data/spec/lib/hootenanny/feed/rss_feed_spec.rb +143 -0
- data/spec/lib/hootenanny/feed_spec.rb +159 -0
- data/spec/lib/hootenanny/feed_store/file_feed_store_spec.rb +58 -0
- data/spec/lib/hootenanny/feed_store/web_feed_store_spec.rb +47 -0
- data/spec/lib/hootenanny/feed_store_spec.rb +27 -0
- data/spec/lib/hootenanny/hub_spec.rb +73 -0
- data/spec/lib/hootenanny/publish_notification_expiration_policy_spec.rb +35 -0
- data/spec/lib/hootenanny/request/publish_notification_spec.rb +43 -0
- data/spec/lib/hootenanny/request/subscription_spec.rb +89 -0
- data/spec/lib/hootenanny/request_spec.rb +21 -0
- data/spec/lib/hootenanny/subscription_delivery_spec.rb +54 -0
- data/spec/lib/hootenanny/topic_spec.rb +15 -0
- data/spec/lib/hootenanny/topic_synchronizer_spec.rb +98 -0
- data/spec/lib/hootenanny/uri_spec.rb +32 -0
- data/spec/lib/hootenanny/verification_spec.rb +92 -0
- data/spec/models/hootenanny/publish_notification_spec.rb +55 -0
- data/spec/models/hootenanny/subscription_spec.rb +58 -19
- data/spec/support/verification.rb +63 -0
- metadata +231 -14
- data/spec/controllers/hootenanny/hub_spec.rb +0 -15
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'delegate'
|
3
|
+
require 'digest'
|
4
|
+
require 'hootenanny/errors'
|
5
|
+
|
6
|
+
module Hootenanny
|
7
|
+
class URI < SimpleDelegator
|
8
|
+
VALID_SCHEMES = ['http', 'https']
|
9
|
+
|
10
|
+
###
|
11
|
+
# Private: Parses a String into a URI in conformance to the the PubSubHubbub
|
12
|
+
# spec. Only HTTP and HTTPS URIs are allowed.
|
13
|
+
#
|
14
|
+
# Returns a Ruby URI
|
15
|
+
# Raises a Hootenanny::URI::InvalidSchemeError if something other than an HTTP
|
16
|
+
# or HTTPS URI is parsed
|
17
|
+
# Raises a Hootenanny::URI::InvalidError if the URI cannot be parsed
|
18
|
+
#
|
19
|
+
def self.parse(uriable)
|
20
|
+
uri = if uriable.is_a? self
|
21
|
+
uriable
|
22
|
+
else
|
23
|
+
::URI.parse(uriable)
|
24
|
+
end
|
25
|
+
|
26
|
+
uri.fragment = nil
|
27
|
+
|
28
|
+
if !VALID_SCHEMES.include?(uri.scheme)
|
29
|
+
raise Hootenanny::URI::InvalidSchemeError
|
30
|
+
end
|
31
|
+
|
32
|
+
self.new(uri)
|
33
|
+
rescue ::URI::InvalidURIError => e
|
34
|
+
raise Hootenanny::URI::InvalidError.wrap(e)
|
35
|
+
end
|
36
|
+
|
37
|
+
def hash
|
38
|
+
self.to_s.hash
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(comparee)
|
42
|
+
self.to_s == comparee.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def eql?(comparee)
|
46
|
+
self == comparee
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_digest
|
50
|
+
@digest ||= Digest::SHA256.hexdigest(self.to_s)[0..16]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'hootenanny/uri'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
###
|
6
|
+
# Private: Takes information (typically related to a Request) and verifies that
|
7
|
+
# the originating system confirms that the information is accurate.
|
8
|
+
#
|
9
|
+
# A Verification, like a Request, is designed to be publicly immutable. As far
|
10
|
+
# as the users of a Verification instance are concerned, the state of
|
11
|
+
# Verification never changes.
|
12
|
+
#
|
13
|
+
module Hootenanny
|
14
|
+
class Verification
|
15
|
+
|
16
|
+
attr_reader :base_url,
|
17
|
+
:topic,
|
18
|
+
:requester_token,
|
19
|
+
:mode
|
20
|
+
|
21
|
+
def initialize(attrs = {})
|
22
|
+
self.base_url = attrs.fetch(:url)
|
23
|
+
self.topic = attrs.fetch(:topic)
|
24
|
+
self.mode = attrs.fetch(:mode)
|
25
|
+
self.requester_token = attrs.fetch(:requester_token, nil)
|
26
|
+
end
|
27
|
+
|
28
|
+
###
|
29
|
+
# Private: Identifies whether the originating system has verified the
|
30
|
+
# verification.
|
31
|
+
#
|
32
|
+
# This involves not only checking to see whether the system itself confirms it
|
33
|
+
# but also by verifying that the response from the originating system contains
|
34
|
+
# the challenge code sent to it.
|
35
|
+
#
|
36
|
+
# Returns a TrueClass or FalseClass
|
37
|
+
#
|
38
|
+
def verified?
|
39
|
+
return false unless response.body == challenge
|
40
|
+
|
41
|
+
response.success?
|
42
|
+
end
|
43
|
+
|
44
|
+
###
|
45
|
+
# Private: Attempts to verify the URI which has been requested to receive
|
46
|
+
# topic feed updates.
|
47
|
+
#
|
48
|
+
# Returns a Faraday::Response
|
49
|
+
#
|
50
|
+
def response
|
51
|
+
@response ||= Faraday.get(uri.to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
###
|
55
|
+
# Private: Constructs a PubSubHubbub compliant verification URI
|
56
|
+
#
|
57
|
+
# * Parameters which exist in the base_url must not be overwritten by
|
58
|
+
# verification parameters even if they are of the same name. For example:
|
59
|
+
#
|
60
|
+
# http://example.com?hub.mode=foo&hub.mode=subscribe
|
61
|
+
#
|
62
|
+
# not
|
63
|
+
#
|
64
|
+
# http://example.com?hub.mode=subscribe
|
65
|
+
#
|
66
|
+
# Returns a URI
|
67
|
+
# Raises a Hootenanny::URI::InvalidError if the base_url is not a valid URI
|
68
|
+
#
|
69
|
+
def uri
|
70
|
+
@uri ||= -> do
|
71
|
+
uri = base_url
|
72
|
+
params = ::URI.encode_www_form(parameters)
|
73
|
+
|
74
|
+
uri.query = [uri.query, params].compact.join('&')
|
75
|
+
|
76
|
+
uri
|
77
|
+
end.call
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
attr_writer :requester_token,
|
83
|
+
:mode
|
84
|
+
|
85
|
+
def base_url=(other)
|
86
|
+
@base_url = Hootenanny::URI.parse(other)
|
87
|
+
end
|
88
|
+
|
89
|
+
def topic=(other)
|
90
|
+
@topic = Hootenanny::URI.parse(other)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def challenge
|
96
|
+
@challenge ||= SecureRandom.hex(16)
|
97
|
+
end
|
98
|
+
|
99
|
+
def parameters
|
100
|
+
@parameters ||= {
|
101
|
+
:'hub.mode' => mode,
|
102
|
+
:'hub.topic' => topic,
|
103
|
+
:'hub.challenge' => challenge,
|
104
|
+
:'hub.verify_token' => requester_token,
|
105
|
+
}.delete_if { |k, v| v.nil? || v == '' }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/hootenanny/version.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
development:
|
2
|
-
|
3
|
-
|
2
|
+
database: hootenanny_development
|
3
|
+
min_messages: warning
|
4
|
+
adapter: postgresql
|
5
|
+
encoding: unicode
|
4
6
|
pool: 5
|
5
|
-
|
7
|
+
username: postgres
|
8
|
+
password:
|
6
9
|
|
7
10
|
test:
|
8
|
-
|
9
|
-
|
11
|
+
database: hootenanny_test
|
12
|
+
min_messages: warning
|
13
|
+
adapter: postgresql
|
14
|
+
encoding: unicode
|
10
15
|
pool: 5
|
11
|
-
|
16
|
+
username: postgres
|
17
|
+
password:
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This migration comes from hootenanny (originally 20130607182642)
|
2
|
+
class AddStartedAtAndLeaseDurationToSubscriptions < ActiveRecord::Migration
|
3
|
+
def change
|
4
|
+
remove_index :hootenanny_subscriptions, :subscriber
|
5
|
+
remove_index :hootenanny_subscriptions, [:subscriber, :topic]
|
6
|
+
|
7
|
+
add_column :hootenanny_subscriptions, :started_at, :datetime
|
8
|
+
add_column :hootenanny_subscriptions, :lease_duration, :integer
|
9
|
+
|
10
|
+
change_column :hootenanny_subscriptions, :started_at, :datetime, :null => false
|
11
|
+
change_column :hootenanny_subscriptions, :lease_duration, :integer, :null => false
|
12
|
+
|
13
|
+
add_index :hootenanny_subscriptions, :subscriber, :unique => false
|
14
|
+
add_index :hootenanny_subscriptions, [:subscriber, :topic], :unique => true
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# This migration comes from hootenanny (originally 20130611235218)
|
2
|
+
class AddPublishNotifications < ActiveRecord::Migration
|
3
|
+
def change
|
4
|
+
create_table :hootenanny_publish_notifications do |t|
|
5
|
+
t.string :topic, :limit => 250, :null => false
|
6
|
+
end
|
7
|
+
|
8
|
+
add_index 'hootenanny_publish_notifications', 'topic', :name => 'index_hootenanny_publish_notifications_on_topic', :unique => true
|
9
|
+
end
|
10
|
+
end
|
data/spec/dummy/db/migrate/20130705200832_add_processed_flag_to_publish_notifications.hootenanny.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# This migration comes from hootenanny (originally 20130705200729)
|
2
|
+
class AddProcessedFlagToPublishNotifications < ActiveRecord::Migration
|
3
|
+
def change
|
4
|
+
add_column :hootenanny_publish_notifications, :processed, :boolean, :null => false, :default => false
|
5
|
+
add_index :hootenanny_publish_notifications, :processed
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# This migration comes from hootenanny (originally 20130711061329)
|
2
|
+
class SwitchPublishNotificationProcessStateFromBooleanToString < ActiveRecord::Migration
|
3
|
+
def up
|
4
|
+
add_column :hootenanny_publish_notifications, :state, :string, :limit => 15, :null => false, :default => 'unprocessed'
|
5
|
+
remove_column :hootenanny_publish_notifications, :processed
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
add_column :hootenanny_publish_notifications, :processed, :boolean, :default => false, :null => false
|
10
|
+
remove_column :hootenanny_publish_notifications, :state
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :publish_notification_request, :class => 'Hootenanny::Request::PublishNotification' do
|
3
|
+
topics 'http://example.org'
|
4
|
+
|
5
|
+
initialize_with do
|
6
|
+
Hootenanny::Request::PublishNotification.build(
|
7
|
+
topics: topics
|
8
|
+
)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module HootenannyFactories
|
2
|
+
class VerifiedVerification
|
3
|
+
def initialize(*args)
|
4
|
+
end
|
5
|
+
|
6
|
+
def verified?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class UnverifiedVerification
|
12
|
+
def initialize(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def verified?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
FactoryGirl.define do
|
22
|
+
factory :subscription_request, :class => 'Hootenanny::Request::Subscription' do
|
23
|
+
subscriber 'http://example.com'
|
24
|
+
topic 'http://example.org'
|
25
|
+
verification_class nil
|
26
|
+
requester_token '12345'
|
27
|
+
digest_secret 'secret'
|
28
|
+
|
29
|
+
initialize_with do
|
30
|
+
Hootenanny::Request::Subscription.build(
|
31
|
+
subscriber: subscriber,
|
32
|
+
topic: topic,
|
33
|
+
requester_token: requester_token,
|
34
|
+
secret: digest_secret,
|
35
|
+
verification_class: verification_class)
|
36
|
+
end
|
37
|
+
|
38
|
+
trait :verified do
|
39
|
+
verification_class HootenannyFactories::VerifiedVerification
|
40
|
+
end
|
41
|
+
|
42
|
+
trait :unverified do
|
43
|
+
verification_class HootenannyFactories::UnverifiedVerification
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,6 +1,18 @@
|
|
1
1
|
FactoryGirl.define do
|
2
2
|
factory :subscription, :class => 'Hootenanny::Subscription' do
|
3
|
-
subscriber '
|
4
|
-
topic '
|
3
|
+
subscriber 'http://subscriber.com'
|
4
|
+
topic 'http://topic.com'
|
5
|
+
started_at { Time.now }
|
6
|
+
lease_duration { 10 }
|
7
|
+
|
8
|
+
trait :active do
|
9
|
+
started_at { Time.now - 907_200 }
|
10
|
+
lease_duration { 1_814_400 }
|
11
|
+
end
|
12
|
+
|
13
|
+
trait :inactive do
|
14
|
+
started_at { Time.now - 3601 }
|
15
|
+
lease_duration { 3600 }
|
16
|
+
end
|
5
17
|
end
|
6
18
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :verification, :class => 'Hootenanny::Verification' do
|
3
|
+
url 'http://example.com'
|
4
|
+
topic 'http://example.org'
|
5
|
+
mode 'mymode'
|
6
|
+
requester_token nil
|
7
|
+
|
8
|
+
initialize_with do
|
9
|
+
new(url: url,
|
10
|
+
topic: topic,
|
11
|
+
mode: mode,
|
12
|
+
requester_token: requester_token,)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rspectacular/spec_helpers/rails_engine'
|
2
|
+
require 'hootenanny'
|
3
|
+
|
4
|
+
###
|
5
|
+
# In order for the hub not to have to degrade performance by constantly checking each publisher for content updates,
|
6
|
+
# As a Publisher
|
7
|
+
# I want to be able to send a notification to the hub describing content updates
|
8
|
+
#
|
9
|
+
feature 'Publishers send notification of content updates' do
|
10
|
+
scenario 'Publishers can send a content update notification to the hub if all notifications for that topic have been processed' do
|
11
|
+
# Given there are no notifications
|
12
|
+
expect( Hootenanny::PublishNotification ).to have(0).items
|
13
|
+
|
14
|
+
# When the hub is sent a content update notification for a single topic
|
15
|
+
post( '/hootenanny/publish_notification', :url => 'http://mytopic')
|
16
|
+
|
17
|
+
# Then the notification should be created
|
18
|
+
expect( Hootenanny::PublishNotification.for('http://mytopic') ).to have(1).item
|
19
|
+
|
20
|
+
# And the response should be 204 'No Content'
|
21
|
+
expect( last_response.status ).to eql 204
|
22
|
+
expect( last_response ).to be_empty
|
23
|
+
end
|
24
|
+
|
25
|
+
scenario 'Publishers can send a content update notification to the hub if a notification for the topic has not yet been processed' do
|
26
|
+
# Given there is a notification for a topic
|
27
|
+
create :publish_notification, :topic => 'http://mytopic'
|
28
|
+
|
29
|
+
# When the hub is sent a content update notification for that topic
|
30
|
+
post( '/hootenanny/publish_notification', :url => 'http://mytopic')
|
31
|
+
|
32
|
+
# Then no new notifications should be created
|
33
|
+
expect( Hootenanny::PublishNotification.for('http://mytopic') ).to have(1).item
|
34
|
+
|
35
|
+
# And the response should be 204 'No Content'
|
36
|
+
expect( last_response.status ).to eql 204
|
37
|
+
expect( last_response ).to be_empty
|
38
|
+
end
|
39
|
+
|
40
|
+
scenario 'Publishers receive error information if there is a problem notifying the hub' do
|
41
|
+
# Given there are no notifications
|
42
|
+
expect( Hootenanny::PublishNotification ).to have(0).items
|
43
|
+
|
44
|
+
# When a notification request is made with invalid information
|
45
|
+
post( '/hootenanny/publish_notification', url: 'http://!nv4l!d')
|
46
|
+
|
47
|
+
# Then the notifications should not be created
|
48
|
+
expect( Hootenanny::PublishNotification ).to have(0).items
|
49
|
+
|
50
|
+
# And the response should be 400 'Bad Request'
|
51
|
+
expect( last_response.status ).to eql 400
|
52
|
+
|
53
|
+
# And the response should include a message indicating the problem
|
54
|
+
expect( last_response.body ).to eql({'error' => {
|
55
|
+
'message' => 'the scheme http does not accept registry part: !nv4l!d (or bad hostname?)' }
|
56
|
+
}.to_json)
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rspectacular/spec_helpers/rails_engine'
|
2
|
+
require 'hootenanny'
|
3
|
+
|
4
|
+
###
|
5
|
+
# So that I do not face potentially catastrophic repercussions,
|
6
|
+
# As a potential Subscriber,
|
7
|
+
# I do not want unverified users to be able to subscribe/unsubscribe me from topics
|
8
|
+
#
|
9
|
+
feature 'Subscribers cannot subscribe to an unverified topic', :web_mock do
|
10
|
+
scenario 'Potential subscribers cannot subscribe to a new topic if it cannot be confirmed' do
|
11
|
+
# Given there are no subscriptions
|
12
|
+
expect( Hootenanny::Subscription ).to have(0).items
|
13
|
+
|
14
|
+
# When an unverifiable subscription is requested for a topic
|
15
|
+
unconfirmed_subscription_verification('http://mycallback', 'http://mytopic')
|
16
|
+
post( '/hootenanny/subscription', topic: 'http://mytopic',
|
17
|
+
callback: 'http://mycallback')
|
18
|
+
|
19
|
+
# Then the subscriber should be subscribed
|
20
|
+
expect( Hootenanny::Subscription.to('http://mytopic') ).to have(0).item
|
21
|
+
|
22
|
+
# And the response should be 400 'Bad Request'
|
23
|
+
expect( last_response.status ).to eql 400
|
24
|
+
expect( last_response.body ).to eql({'error' => {
|
25
|
+
'message' => 'Request could not be verified.' }
|
26
|
+
}.to_json)
|
27
|
+
end
|
28
|
+
|
29
|
+
scenario 'Potential subscribers see their verification token sent back to them so they can keep track of the request' do
|
30
|
+
# When an unverifiable subscription is requested for a topic
|
31
|
+
confirmed_verification('subscribe',
|
32
|
+
'http://mycallback',
|
33
|
+
'http://mytopic',
|
34
|
+
:requester_token => '12345')
|
35
|
+
|
36
|
+
post( '/hootenanny/subscription', topic: 'http://mytopic',
|
37
|
+
callback: 'http://mycallback',
|
38
|
+
requester_token: '12345')
|
39
|
+
end
|
40
|
+
|
41
|
+
scenario 'Potential subscribers cannot subscribe to a new topic if the confirmation is invalid' do
|
42
|
+
# Given there are no subscriptions
|
43
|
+
expect( Hootenanny::Subscription ).to have(0).items
|
44
|
+
|
45
|
+
# When an unverifiable subscription is requested for a topic
|
46
|
+
unverified_subscription_verification('http://mycallback', 'http://mytopic')
|
47
|
+
post( '/hootenanny/subscription', topic: 'http://mytopic',
|
48
|
+
callback: 'http://mycallback')
|
49
|
+
|
50
|
+
# Then the subscriber should be subscribed
|
51
|
+
expect( Hootenanny::Subscription.to('http://mytopic') ).to have(0).item
|
52
|
+
|
53
|
+
# And the response should be 400 'Bad Request'
|
54
|
+
expect( last_response.status ).to eql 400
|
55
|
+
expect( last_response.body ).to eql({'error' => {
|
56
|
+
'message' => 'Request could not be verified.' }
|
57
|
+
}.to_json)
|
58
|
+
end
|
59
|
+
end
|