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