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.
Files changed (112) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +5 -0
  3. data/Gemfile +5 -7
  4. data/Gemfile.lock +46 -28
  5. data/app/controllers/hootenanny/notifications_controller.rb +31 -0
  6. data/app/controllers/hootenanny/parameters.rb +16 -0
  7. data/app/controllers/hootenanny/subscriptions_controller.rb +24 -3
  8. data/app/models/hootenanny/publish_notification.rb +90 -0
  9. data/app/models/hootenanny/subscription.rb +72 -23
  10. data/config/routes.rb +2 -1
  11. data/db/migrate/20130607182642_add_started_at_and_lease_duration_to_subscriptions.rb +15 -0
  12. data/db/migrate/20130608225621_add_hmac_secret_to_subscription.rb +5 -0
  13. data/db/migrate/20130611235218_add_publish_notifications.rb +9 -0
  14. data/db/migrate/20130612153138_add_timestamps_to_publish_notification.rb +5 -0
  15. data/db/migrate/20130705200729_add_processed_flag_to_publish_notifications.rb +6 -0
  16. data/db/migrate/20130711061329_switch_publish_notification_process_state_from_boolean_to_string.rb +11 -0
  17. data/db/migrate/20130711061558_add_index_to_notifications_state.rb +5 -0
  18. data/hootenanny.gemspec +6 -2
  19. data/lib/hootenanny/configuration.rb +43 -0
  20. data/lib/hootenanny/correspondent.rb +44 -0
  21. data/lib/hootenanny/errors.rb +42 -1
  22. data/lib/hootenanny/feed.rb +75 -0
  23. data/lib/hootenanny/feed/atom_feed.rb +18 -0
  24. data/lib/hootenanny/feed/atom_feed_item.rb +8 -0
  25. data/lib/hootenanny/feed/digest_feed.rb +14 -0
  26. data/lib/hootenanny/feed/digest_feed_item.rb +11 -0
  27. data/lib/hootenanny/feed/feed_item.rb +30 -0
  28. data/lib/hootenanny/feed/file.rb +66 -0
  29. data/lib/hootenanny/feed/json_feed.rb +48 -0
  30. data/lib/hootenanny/feed/json_feed_item.rb +8 -0
  31. data/lib/hootenanny/feed/null_feed.rb +27 -0
  32. data/lib/hootenanny/feed/rss_feed.rb +52 -0
  33. data/lib/hootenanny/feed/rss_feed_item.rb +8 -0
  34. data/lib/hootenanny/feed_store.rb +30 -0
  35. data/lib/hootenanny/feed_store/file_feed_store.rb +55 -0
  36. data/lib/hootenanny/feed_store/web_feed_store.rb +42 -0
  37. data/lib/hootenanny/hub.rb +116 -22
  38. data/lib/hootenanny/publish_notification_expiration_policy.rb +17 -0
  39. data/lib/hootenanny/request.rb +40 -0
  40. data/lib/hootenanny/request/publish_notification.rb +94 -0
  41. data/lib/hootenanny/request/subscription.rb +153 -0
  42. data/lib/hootenanny/subscription_delivery.rb +47 -0
  43. data/lib/hootenanny/topic.rb +38 -0
  44. data/lib/hootenanny/topic_synchronizer.rb +71 -0
  45. data/lib/hootenanny/uri.rb +53 -0
  46. data/lib/hootenanny/verification.rb +108 -0
  47. data/lib/hootenanny/version.rb +1 -1
  48. data/spec/dummy/config/database.yml +12 -6
  49. data/spec/dummy/db/migrate/20130607183149_add_started_at_and_lease_duration_to_subscriptions.hootenanny.rb +16 -0
  50. data/spec/dummy/db/migrate/20130608231253_add_hmac_secret_to_subscription.hootenanny.rb +6 -0
  51. data/spec/dummy/db/migrate/20130611235546_add_publish_notifications.hootenanny.rb +10 -0
  52. data/spec/dummy/db/migrate/20130612153353_add_timestamps_to_publish_notification.hootenanny.rb +6 -0
  53. data/spec/dummy/db/migrate/20130705200832_add_processed_flag_to_publish_notifications.hootenanny.rb +7 -0
  54. data/spec/dummy/db/migrate/20130711061518_switch_publish_notification_process_state_from_boolean_to_string.hootenanny.rb +12 -0
  55. data/spec/dummy/db/migrate/20130711061629_add_index_to_notifications_state.hootenanny.rb +6 -0
  56. data/spec/factories/publish_notification.rb +13 -0
  57. data/spec/factories/requests/publish_notification.rb +11 -0
  58. data/spec/factories/requests/subscription.rb +46 -0
  59. data/spec/factories/subscription.rb +14 -2
  60. data/spec/factories/verification.rb +15 -0
  61. data/spec/features/publishers/can_notify_the_hub_of_content_updates_spec.rb +58 -0
  62. data/spec/features/subscribers/are_protected_from_unwarranted_subscriptions_spec.rb +59 -0
  63. data/spec/features/subscribers/can_receive_distributions_of_topic_content_spec.rb +175 -0
  64. data/spec/features/subscribers/can_subscribe_to_a_topic_spec.rb +76 -7
  65. data/spec/fixtures/feeds/atom/97d79220f68b4bf27.atom +0 -0
  66. data/spec/fixtures/feeds/atom/sample_feed.atom +51 -0
  67. data/spec/fixtures/feeds/digest/97d79220f68b4bf27.digest +1 -0
  68. data/spec/fixtures/feeds/digest/complete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +6 -0
  69. data/spec/fixtures/feeds/digest/incomplete_broadcasted_items/5b187098da59f077f/97d79220f68b4bf27.digest +5 -0
  70. data/spec/fixtures/feeds/digest/sample_feed.digest +6 -0
  71. data/spec/fixtures/feeds/json/97d79220f68b4bf27.json +1 -0
  72. data/spec/fixtures/feeds/json/feed_with_one_item.json +21 -0
  73. data/spec/fixtures/feeds/json/sample_feed.json +30 -0
  74. data/spec/fixtures/feeds/rss/5b187098da59f077f/97d79220f68b4bf27.rss +21 -0
  75. data/spec/fixtures/feeds/rss/97d79220f68b4bf27.rss +0 -0
  76. data/spec/fixtures/feeds/rss/feed_with_one_item.rss +15 -0
  77. data/spec/fixtures/feeds/rss/minimal_feed.rss +12 -0
  78. data/spec/fixtures/feeds/rss/sample_feed.rss +22 -0
  79. data/spec/fixtures/feeds/rss/sample_feed_2.rss +22 -0
  80. data/spec/lib/hootenanny/configuration_spec.rb +7 -0
  81. data/spec/lib/hootenanny/correspondent_spec.rb +94 -0
  82. data/spec/lib/hootenanny/errors_spec.rb +21 -0
  83. data/spec/lib/hootenanny/feed/atom_feed_item_spec.rb +9 -0
  84. data/spec/lib/hootenanny/feed/atom_feed_spec.rb +40 -0
  85. data/spec/lib/hootenanny/feed/digest_feed_item_spec.rb +9 -0
  86. data/spec/lib/hootenanny/feed/digest_feed_spec.rb +40 -0
  87. data/spec/lib/hootenanny/feed/feed_item_spec.rb +49 -0
  88. data/spec/lib/hootenanny/feed/file_spec.rb +66 -0
  89. data/spec/lib/hootenanny/feed/json_feed_item_spec.rb +9 -0
  90. data/spec/lib/hootenanny/feed/json_feed_spec.rb +128 -0
  91. data/spec/lib/hootenanny/feed/null_feed_spec.rb +9 -0
  92. data/spec/lib/hootenanny/feed/rss_feed_item_spec.rb +9 -0
  93. data/spec/lib/hootenanny/feed/rss_feed_spec.rb +143 -0
  94. data/spec/lib/hootenanny/feed_spec.rb +159 -0
  95. data/spec/lib/hootenanny/feed_store/file_feed_store_spec.rb +58 -0
  96. data/spec/lib/hootenanny/feed_store/web_feed_store_spec.rb +47 -0
  97. data/spec/lib/hootenanny/feed_store_spec.rb +27 -0
  98. data/spec/lib/hootenanny/hub_spec.rb +73 -0
  99. data/spec/lib/hootenanny/publish_notification_expiration_policy_spec.rb +35 -0
  100. data/spec/lib/hootenanny/request/publish_notification_spec.rb +43 -0
  101. data/spec/lib/hootenanny/request/subscription_spec.rb +89 -0
  102. data/spec/lib/hootenanny/request_spec.rb +21 -0
  103. data/spec/lib/hootenanny/subscription_delivery_spec.rb +54 -0
  104. data/spec/lib/hootenanny/topic_spec.rb +15 -0
  105. data/spec/lib/hootenanny/topic_synchronizer_spec.rb +98 -0
  106. data/spec/lib/hootenanny/uri_spec.rb +32 -0
  107. data/spec/lib/hootenanny/verification_spec.rb +92 -0
  108. data/spec/models/hootenanny/publish_notification_spec.rb +55 -0
  109. data/spec/models/hootenanny/subscription_spec.rb +58 -19
  110. data/spec/support/verification.rb +63 -0
  111. metadata +231 -14
  112. 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
@@ -1,3 +1,3 @@
1
1
  module Hootenanny
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,11 +1,17 @@
1
1
  development:
2
- adapter: sqlite3
3
- database: db/development.sqlite3
2
+ database: hootenanny_development
3
+ min_messages: warning
4
+ adapter: postgresql
5
+ encoding: unicode
4
6
  pool: 5
5
- timeout: 5000
7
+ username: postgres
8
+ password:
6
9
 
7
10
  test:
8
- adapter: sqlite3
9
- database: db/test.sqlite3
11
+ database: hootenanny_test
12
+ min_messages: warning
13
+ adapter: postgresql
14
+ encoding: unicode
10
15
  pool: 5
11
- timeout: 5000
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,6 @@
1
+ # This migration comes from hootenanny (originally 20130608225621)
2
+ class AddHmacSecretToSubscription < ActiveRecord::Migration
3
+ def change
4
+ add_column :hootenanny_subscriptions, :digest_secret, :string, :null => true, :limit => 200
5
+ end
6
+ 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
@@ -0,0 +1,6 @@
1
+ # This migration comes from hootenanny (originally 20130612153138)
2
+ class AddTimestampsToPublishNotification < ActiveRecord::Migration
3
+ def change
4
+ add_timestamps :hootenanny_publish_notifications
5
+ end
6
+ end
@@ -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,6 @@
1
+ # This migration comes from hootenanny (originally 20130711061558)
2
+ class AddIndexToNotificationsState < ActiveRecord::Migration
3
+ def change
4
+ add_index :hootenanny_publish_notifications, :state
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ FactoryGirl.define do
2
+ factory :publish_notification, :class => 'Hootenanny::PublishNotification' do
3
+ topic 'mytopic'
4
+
5
+ trait :unprocessed do
6
+ state 'unprocessed'
7
+ end
8
+
9
+ trait :processed do
10
+ state 'processed'
11
+ end
12
+ end
13
+ 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 'my_subscriber'
4
- topic 'my_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