fake_sns 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.simplecov +4 -0
- data/.travis.yml +5 -0
- data/Gemfile +2 -0
- data/README.md +167 -0
- data/Rakefile +24 -0
- data/bin/fake_sns +79 -0
- data/config.ru +3 -0
- data/fake_sns.gemspec +34 -0
- data/lib/fake_sns/action.rb +24 -0
- data/lib/fake_sns/actions/create_topic.rb +41 -0
- data/lib/fake_sns/actions/delete_topic.rb +13 -0
- data/lib/fake_sns/actions/get_topic_attributes.rb +32 -0
- data/lib/fake_sns/actions/list_subscriptions.rb +24 -0
- data/lib/fake_sns/actions/list_subscriptions_by_topic.rb +23 -0
- data/lib/fake_sns/actions/list_topics.rb +13 -0
- data/lib/fake_sns/actions/publish.rb +37 -0
- data/lib/fake_sns/actions/set_topic_attributes.rb +19 -0
- data/lib/fake_sns/actions/subscribe.rb +43 -0
- data/lib/fake_sns/database.rb +76 -0
- data/lib/fake_sns/deliver_message.rb +100 -0
- data/lib/fake_sns/error.rb +34 -0
- data/lib/fake_sns/error_response.rb +46 -0
- data/lib/fake_sns/message.rb +28 -0
- data/lib/fake_sns/message_collection.rb +40 -0
- data/lib/fake_sns/response.rb +16 -0
- data/lib/fake_sns/server.rb +76 -0
- data/lib/fake_sns/show_output.rb +20 -0
- data/lib/fake_sns/storage.rb +75 -0
- data/lib/fake_sns/subscription.rb +17 -0
- data/lib/fake_sns/subscription_collection.rb +34 -0
- data/lib/fake_sns/test_integration.rb +110 -0
- data/lib/fake_sns/topic.rb +13 -0
- data/lib/fake_sns/topic_collection.rb +41 -0
- data/lib/fake_sns/version.rb +3 -0
- data/lib/fake_sns/views/create_topic.xml.erb +8 -0
- data/lib/fake_sns/views/delete_topic.xml.erb +5 -0
- data/lib/fake_sns/views/error.xml.erb +8 -0
- data/lib/fake_sns/views/get_topic_attributes.xml.erb +15 -0
- data/lib/fake_sns/views/list_subscriptions.xml.erb +18 -0
- data/lib/fake_sns/views/list_subscriptions_by_topic.xml.erb +18 -0
- data/lib/fake_sns/views/list_topics.xml.erb +14 -0
- data/lib/fake_sns/views/publish.xml.erb +8 -0
- data/lib/fake_sns/views/set_topic_attributes.xml.erb +5 -0
- data/lib/fake_sns/views/subscribe.xml.erb +8 -0
- data/lib/fake_sns.rb +51 -0
- data/spec/fake_sns/drain_spec.rb +91 -0
- data/spec/fake_sns/publish_spec.rb +28 -0
- data/spec/fake_sns/replace_spec.rb +14 -0
- data/spec/fake_sns/subscribing_spec.rb +42 -0
- data/spec/fake_sns/topics_spec.rb +44 -0
- data/spec/spec_helper.rb +54 -0
- metadata +271 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module FakeSNS
|
2
|
+
class TopicCollection
|
3
|
+
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(store)
|
7
|
+
@store = store
|
8
|
+
@store["topics"] ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def collection
|
12
|
+
@store["topics"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
@store["topics"] = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def each(*args, &block)
|
20
|
+
p @store.to_yaml
|
21
|
+
collection.map { |item| Topic.new(item) }.each(*args, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(arn, &default)
|
25
|
+
default ||= -> { raise InvalidParameterValue, "Unknown topic #{arn}" }
|
26
|
+
found = collection.find do |topic|
|
27
|
+
topic["arn"] == arn
|
28
|
+
end
|
29
|
+
found || default.call
|
30
|
+
end
|
31
|
+
|
32
|
+
def create(attributes)
|
33
|
+
collection << attributes
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete(arn)
|
37
|
+
collection.delete(fetch(arn))
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<GetTopicAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
2
|
+
<GetTopicAttributesResult>
|
3
|
+
<Attributes>
|
4
|
+
<% each_attribute do |key, value| %>
|
5
|
+
<entry>
|
6
|
+
<key><%= key %></key>
|
7
|
+
<value><%= value %></value>
|
8
|
+
</entry>
|
9
|
+
<% end %>
|
10
|
+
</Attributes>
|
11
|
+
</GetTopicAttributesResult>
|
12
|
+
<ResponseMetadata>
|
13
|
+
<RequestId><%= request_id %></RequestId>
|
14
|
+
</ResponseMetadata>
|
15
|
+
</GetTopicAttributesResponse>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<ListSubscriptionsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
2
|
+
<ListSubscriptionsResult>
|
3
|
+
<Subscriptions>
|
4
|
+
<% each_subscription do |subscription| %>
|
5
|
+
<member>
|
6
|
+
<TopicArn><%= subscription.topic_arn %></TopicArn>
|
7
|
+
<Protocol><%= subscription.protocol %></Protocol>
|
8
|
+
<SubscriptionArn><%= subscription.arn %></SubscriptionArn>
|
9
|
+
<Owner><%= subscription.owner %></Owner>
|
10
|
+
<Endpoint><%= subscription.endpoint %></Endpoint>
|
11
|
+
</member>
|
12
|
+
<% end %>
|
13
|
+
</Subscriptions>
|
14
|
+
</ListSubscriptionsResult>
|
15
|
+
<ResponseMetadata>
|
16
|
+
<RequestId><%= request_id %></RequestId>
|
17
|
+
</ResponseMetadata>
|
18
|
+
</ListSubscriptionsResponse>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<ListSubscriptionsByTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
2
|
+
<ListSubscriptionsByTopicResult>
|
3
|
+
<Subscriptions>
|
4
|
+
<% each_subscription do |subscription| %>
|
5
|
+
<member>
|
6
|
+
<TopicArn><%= subscription.topic_arn %></TopicArn>
|
7
|
+
<Protocol><%= subscription.protocol %></Protocol>
|
8
|
+
<SubscriptionArn><%= subscription.arn %></SubscriptionArn>
|
9
|
+
<Owner><%= subscription.owner %></Owner>
|
10
|
+
<Endpoint><%= subscription.endpoint %></Endpoint>
|
11
|
+
</member>
|
12
|
+
<% end %>
|
13
|
+
</Subscriptions>
|
14
|
+
</ListSubscriptionsByTopicResult>
|
15
|
+
<ResponseMetadata>
|
16
|
+
<RequestId><%= request_id %></RequestId>
|
17
|
+
</ResponseMetadata>
|
18
|
+
</ListSubscriptionsByTopicResponse>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<ListTopicsResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
2
|
+
<ListTopicsResult>
|
3
|
+
<Topics>
|
4
|
+
<% each_topic do |topic| %>
|
5
|
+
<member>
|
6
|
+
<TopicArn><%= topic.arn %></TopicArn>
|
7
|
+
</member>
|
8
|
+
<% end %>
|
9
|
+
</Topics>
|
10
|
+
</ListTopicsResult>
|
11
|
+
<ResponseMetadata>
|
12
|
+
<RequestId><%= request_id %></RequestId>
|
13
|
+
</ResponseMetadata>
|
14
|
+
</ListTopicsResponse>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<SubscribeResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
|
2
|
+
<SubscribeResult>
|
3
|
+
<SubscriptionArn><%= subscription_arn %></SubscriptionArn>
|
4
|
+
</SubscribeResult>
|
5
|
+
<ResponseMetadata>
|
6
|
+
<RequestId><%= request_id %></RequestId>
|
7
|
+
</ResponseMetadata>
|
8
|
+
</SubscribeResponse>
|
data/lib/fake_sns.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
Encoding.default_external = Encoding::UTF_8
|
2
|
+
Encoding.default_internal = Encoding::UTF_8
|
3
|
+
|
4
|
+
require "virtus"
|
5
|
+
require "verbose_hash_fetch"
|
6
|
+
|
7
|
+
require "fake_sns/error"
|
8
|
+
require "fake_sns/error_response"
|
9
|
+
|
10
|
+
require "fake_sns/database"
|
11
|
+
require "fake_sns/storage"
|
12
|
+
|
13
|
+
require "fake_sns/topic_collection"
|
14
|
+
require "fake_sns/topic"
|
15
|
+
require "fake_sns/subscription_collection"
|
16
|
+
require "fake_sns/subscription"
|
17
|
+
|
18
|
+
require "fake_sns/message_collection"
|
19
|
+
require "fake_sns/message"
|
20
|
+
require "fake_sns/deliver_message"
|
21
|
+
|
22
|
+
require "fake_sns/response"
|
23
|
+
require "fake_sns/action"
|
24
|
+
require "fake_sns/server"
|
25
|
+
|
26
|
+
# load all the actions
|
27
|
+
action_files = File.expand_path("../fake_sns/actions/*.rb", __FILE__)
|
28
|
+
Dir.glob(action_files).each do |file|
|
29
|
+
require file
|
30
|
+
end
|
31
|
+
|
32
|
+
module FakeSNS
|
33
|
+
|
34
|
+
def self.server(options)
|
35
|
+
app = Server
|
36
|
+
if log = options[:log]
|
37
|
+
$stdout.reopen(log, "w:utf-8")
|
38
|
+
$stderr.reopen(log, "w:utf-8")
|
39
|
+
app.enable :logging
|
40
|
+
end
|
41
|
+
if options[:verbose]
|
42
|
+
require "fake_sns/show_output"
|
43
|
+
app.use FakeSNS::ShowOutput
|
44
|
+
end
|
45
|
+
options.each do |key, value|
|
46
|
+
app.set key, value
|
47
|
+
end
|
48
|
+
app
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "sinatra/base"
|
3
|
+
require 'json_expressions/rspec'
|
4
|
+
|
5
|
+
describe "Drain messages", :sqs do
|
6
|
+
|
7
|
+
it "works for SQS" do
|
8
|
+
topic = sns.topics.create("my-topic")
|
9
|
+
queue = sqs.queues.create("my-queue")
|
10
|
+
topic.subscribe(queue)
|
11
|
+
|
12
|
+
topic.publish("X")
|
13
|
+
|
14
|
+
$fake_sns.drain
|
15
|
+
expect(queue.visible_messages).to eq 1
|
16
|
+
end
|
17
|
+
|
18
|
+
it "works for SQS with a single message" do
|
19
|
+
topic = sns.topics.create("my-topic")
|
20
|
+
queue = sqs.queues.create("my-queue")
|
21
|
+
topic.subscribe(queue)
|
22
|
+
|
23
|
+
message_id = topic.publish("X")
|
24
|
+
topic.publish("Y")
|
25
|
+
|
26
|
+
$fake_sns.drain(message_id)
|
27
|
+
expect(queue.visible_messages).to eq 1
|
28
|
+
end
|
29
|
+
|
30
|
+
it "works for HTTP" do
|
31
|
+
requests = []
|
32
|
+
_headers = []
|
33
|
+
target_app = Class.new(Sinatra::Base) do
|
34
|
+
get("/") { 200 } # check if server started
|
35
|
+
post("/endpoint") do
|
36
|
+
requests << request.body.read
|
37
|
+
_headers << request.env
|
38
|
+
200
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
app_runner = Thread.new do
|
43
|
+
target_app.set :port, 5051
|
44
|
+
target_app.run!
|
45
|
+
end
|
46
|
+
|
47
|
+
topic = sns.topics.create("my-topic")
|
48
|
+
subscription = topic.subscribe("http://localhost:5051/endpoint")
|
49
|
+
|
50
|
+
message_id = topic.publish("X")
|
51
|
+
|
52
|
+
wait_for { Faraday.new("http://localhost:5051").get("/").success? rescue false }
|
53
|
+
|
54
|
+
$fake_sns.drain
|
55
|
+
|
56
|
+
app_runner.kill
|
57
|
+
|
58
|
+
expect(requests.size).to eq 1
|
59
|
+
expect(requests.first).to match_json_expression(
|
60
|
+
"Type" => "Notification",
|
61
|
+
"Message" => "X",
|
62
|
+
"MessageId" => message_id,
|
63
|
+
"Signature" => "Fake",
|
64
|
+
"SignatureVersion" => "1",
|
65
|
+
"SigningCertURL" => "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
|
66
|
+
"Subject" => nil,
|
67
|
+
"Timestamp" => anything,
|
68
|
+
"TopicArn" => topic.arn,
|
69
|
+
"Type" => "Notification",
|
70
|
+
"UnsubscribeURL" => "",
|
71
|
+
)
|
72
|
+
|
73
|
+
expect(_headers.size).to eq 1
|
74
|
+
expect(_headers.first).to match_json_expression({
|
75
|
+
"HTTP_X_AMZ_SNS_MESSAGE_TYPE" => "Notification",
|
76
|
+
"HTTP_X_AMZ_SNS_MESSAGE_ID" => message_id,
|
77
|
+
"HTTP_X_AMZ_SNS_TOPIC_ARN" => topic.arn,
|
78
|
+
"HTTP_X_AMZ_SNS_SUBSCRIPTION_ARN" => subscription.arn,
|
79
|
+
}.ignore_extra_keys!)
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def wait_for(&condition)
|
84
|
+
Timeout.timeout 1 do
|
85
|
+
until condition.call
|
86
|
+
sleep 0.01
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Publishing" do
|
4
|
+
|
5
|
+
let(:existing_topic) { sns.topics.create("my-topic") }
|
6
|
+
|
7
|
+
it "remembers published messages" do
|
8
|
+
message_id = existing_topic.publish("hallo")
|
9
|
+
messages = $fake_sns.data.fetch("messages")
|
10
|
+
expect(messages.size).to eq 1
|
11
|
+
message = messages.first
|
12
|
+
expect(message.fetch(:id)).to eq message_id
|
13
|
+
end
|
14
|
+
|
15
|
+
it "needs an existing topic" do
|
16
|
+
topic = sns.topics["arn:aws:sns:us-east-1:5068edfd0f7ee3ea9ccc1e73cbb17569:not-exist"]
|
17
|
+
expect {
|
18
|
+
topic.publish("hallo")
|
19
|
+
}.to raise_error AWS::SNS::Errors::InvalidParameterValue
|
20
|
+
end
|
21
|
+
|
22
|
+
it "doesn't allow messages that are too big" do
|
23
|
+
expect {
|
24
|
+
existing_topic.publish("A" * 262145)
|
25
|
+
}.to raise_error AWS::SNS::Errors::InvalidParameterValue
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Replacing the data" do
|
4
|
+
|
5
|
+
it "works by submitting new YAML" do
|
6
|
+
topic = sns.topics.create("my-topic")
|
7
|
+
previous_data = $fake_sns.data.to_yaml
|
8
|
+
apply_change = lambda { |data| data.gsub("my-topic", "your-topic") }
|
9
|
+
new_data = apply_change.call(previous_data)
|
10
|
+
$fake_sns.connection.put("/", new_data)
|
11
|
+
expect(sns.topics.map(&:arn)).to eq [apply_change.call(topic.arn)]
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Subscribing", :sqs do
|
4
|
+
|
5
|
+
it "lists subscriptions globally" do
|
6
|
+
topic = sns.topics.create("my-topic")
|
7
|
+
subscription = topic.subscribe("http://example.com")
|
8
|
+
expect(sns.subscriptions.map(&:topic_arn)).to eq [topic.arn]
|
9
|
+
expect(sns.subscriptions.map(&:arn)).to eq [subscription.arn]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "filters by topic" do
|
13
|
+
topic = sns.topics.create("my-topic")
|
14
|
+
other_topic = sns.topics.create("my-topic-2")
|
15
|
+
subscription = topic.subscribe("http://example.com")
|
16
|
+
expect(topic.subscriptions.map(&:topic_arn)).to eq [topic.arn]
|
17
|
+
expect(topic.subscriptions.map(&:arn)).to eq [subscription.arn]
|
18
|
+
expect(other_topic.subscriptions.map(&:arn)).to eq []
|
19
|
+
end
|
20
|
+
|
21
|
+
it "needs an existing topic" do
|
22
|
+
topic = sns.topics["arn:aws:sns:us-east-1:5068edfd0f7ee3ea9ccc1e73cbb17569:not-exist"]
|
23
|
+
expect {
|
24
|
+
topic.subscribe("http://example.com")
|
25
|
+
}.to raise_error AWS::SNS::Errors::InvalidParameterValue
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can subscribe to a SQS queue" do
|
29
|
+
queue = AWS::SQS.new.queues.create("my-queue")
|
30
|
+
topic = sns.topics.create("my-topic")
|
31
|
+
topic.subscribe(queue)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "won't subscribe twice to the same endpoint" do
|
35
|
+
queue = AWS::SQS.new.queues.create("my-queue")
|
36
|
+
topic = sns.topics.create("my-topic")
|
37
|
+
topic.subscribe(queue)
|
38
|
+
topic.subscribe(queue)
|
39
|
+
expect(sns.subscriptions.to_a.size).to eq 1
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Topics" do
|
4
|
+
|
5
|
+
it "rejects invalid characters in topic names" do
|
6
|
+
expect {
|
7
|
+
sns.topics.create("dot.dot")
|
8
|
+
}.to raise_error(AWS::SNS::Errors::InvalidParameterValue)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "creates a new topic" do
|
12
|
+
topic = sns.topics.create("my-topic")
|
13
|
+
expect(topic.arn).to match(/arn:aws:sns:us-east-1:(\w+):my-topic/)
|
14
|
+
|
15
|
+
new_topic = sns.topics.create("other-topic")
|
16
|
+
expect(new_topic.arn).not_to eq topic.arn
|
17
|
+
|
18
|
+
existing_topic = sns.topics.create("my-topic")
|
19
|
+
expect(existing_topic.arn).to eq topic.arn
|
20
|
+
end
|
21
|
+
|
22
|
+
it "lists topics" do
|
23
|
+
topic1 = sns.topics.create("my-topic-1")
|
24
|
+
topic2 = sns.topics.create("my-topic-2")
|
25
|
+
|
26
|
+
expect(sns.topics.map(&:arn)).to match_array [topic1.arn, topic2.arn]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "deletes topics" do
|
30
|
+
topic = sns.topics.create("my-topic")
|
31
|
+
expect(sns.topics.map(&:arn)).to eq [topic.arn]
|
32
|
+
sns.topics[topic.arn].delete
|
33
|
+
expect(sns.topics.map(&:arn)).to eq []
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can set and read attributes" do
|
37
|
+
topic = sns.topics.create("my-topic")
|
38
|
+
expect(topic.display_name).to eq "my-topic"
|
39
|
+
topic.display_name = "the display name"
|
40
|
+
expect(topic.display_name).to eq "the display name"
|
41
|
+
expect(topic.name).to eq "my-topic"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
ENV["RACK_ENV"] ||= 'test'
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
require "verbose_hash_fetch"
|
7
|
+
require "aws-sdk"
|
8
|
+
require "pry"
|
9
|
+
require "fake_sns/test_integration"
|
10
|
+
require "fake_sqs/test_integration"
|
11
|
+
|
12
|
+
|
13
|
+
AWS.config(
|
14
|
+
use_ssl: false,
|
15
|
+
sqs_endpoint: "localhost",
|
16
|
+
sqs_port: 4568,
|
17
|
+
sns_endpoint: "localhost",
|
18
|
+
sns_port: 9293,
|
19
|
+
access_key_id: "fake access key",
|
20
|
+
secret_access_key: "fake secret key",
|
21
|
+
)
|
22
|
+
|
23
|
+
db = ENV["SNS_DATABASE"]
|
24
|
+
db = ":memory:" if ENV["SNS_DATABASE"].to_s == ""
|
25
|
+
|
26
|
+
puts "Running tests with database stored in #{db}"
|
27
|
+
|
28
|
+
$fake_sns = FakeSNS::TestIntegration.new(database: db)
|
29
|
+
$fake_sqs = FakeSQS::TestIntegration.new(database: ":memory:")
|
30
|
+
|
31
|
+
module SpecHelper
|
32
|
+
def sns
|
33
|
+
AWS::SNS.new
|
34
|
+
end
|
35
|
+
def sqs
|
36
|
+
AWS::SQS.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
RSpec.configure do |config|
|
41
|
+
|
42
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
43
|
+
config.expect_with :rspec do |rspec|
|
44
|
+
rspec.syntax = :expect
|
45
|
+
end
|
46
|
+
|
47
|
+
config.before(:each) { $fake_sns.start }
|
48
|
+
config.after(:suite) { $fake_sns.stop }
|
49
|
+
config.include SpecHelper
|
50
|
+
|
51
|
+
config.before(:each, :sqs) { $fake_sqs.start }
|
52
|
+
config.after(:suite) { $fake_sqs.stop }
|
53
|
+
|
54
|
+
end
|