fake_sns 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +4 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +2 -0
  7. data/README.md +167 -0
  8. data/Rakefile +24 -0
  9. data/bin/fake_sns +79 -0
  10. data/config.ru +3 -0
  11. data/fake_sns.gemspec +34 -0
  12. data/lib/fake_sns/action.rb +24 -0
  13. data/lib/fake_sns/actions/create_topic.rb +41 -0
  14. data/lib/fake_sns/actions/delete_topic.rb +13 -0
  15. data/lib/fake_sns/actions/get_topic_attributes.rb +32 -0
  16. data/lib/fake_sns/actions/list_subscriptions.rb +24 -0
  17. data/lib/fake_sns/actions/list_subscriptions_by_topic.rb +23 -0
  18. data/lib/fake_sns/actions/list_topics.rb +13 -0
  19. data/lib/fake_sns/actions/publish.rb +37 -0
  20. data/lib/fake_sns/actions/set_topic_attributes.rb +19 -0
  21. data/lib/fake_sns/actions/subscribe.rb +43 -0
  22. data/lib/fake_sns/database.rb +76 -0
  23. data/lib/fake_sns/deliver_message.rb +100 -0
  24. data/lib/fake_sns/error.rb +34 -0
  25. data/lib/fake_sns/error_response.rb +46 -0
  26. data/lib/fake_sns/message.rb +28 -0
  27. data/lib/fake_sns/message_collection.rb +40 -0
  28. data/lib/fake_sns/response.rb +16 -0
  29. data/lib/fake_sns/server.rb +76 -0
  30. data/lib/fake_sns/show_output.rb +20 -0
  31. data/lib/fake_sns/storage.rb +75 -0
  32. data/lib/fake_sns/subscription.rb +17 -0
  33. data/lib/fake_sns/subscription_collection.rb +34 -0
  34. data/lib/fake_sns/test_integration.rb +110 -0
  35. data/lib/fake_sns/topic.rb +13 -0
  36. data/lib/fake_sns/topic_collection.rb +41 -0
  37. data/lib/fake_sns/version.rb +3 -0
  38. data/lib/fake_sns/views/create_topic.xml.erb +8 -0
  39. data/lib/fake_sns/views/delete_topic.xml.erb +5 -0
  40. data/lib/fake_sns/views/error.xml.erb +8 -0
  41. data/lib/fake_sns/views/get_topic_attributes.xml.erb +15 -0
  42. data/lib/fake_sns/views/list_subscriptions.xml.erb +18 -0
  43. data/lib/fake_sns/views/list_subscriptions_by_topic.xml.erb +18 -0
  44. data/lib/fake_sns/views/list_topics.xml.erb +14 -0
  45. data/lib/fake_sns/views/publish.xml.erb +8 -0
  46. data/lib/fake_sns/views/set_topic_attributes.xml.erb +5 -0
  47. data/lib/fake_sns/views/subscribe.xml.erb +8 -0
  48. data/lib/fake_sns.rb +51 -0
  49. data/spec/fake_sns/drain_spec.rb +91 -0
  50. data/spec/fake_sns/publish_spec.rb +28 -0
  51. data/spec/fake_sns/replace_spec.rb +14 -0
  52. data/spec/fake_sns/subscribing_spec.rb +42 -0
  53. data/spec/fake_sns/topics_spec.rb +44 -0
  54. data/spec/spec_helper.rb +54 -0
  55. metadata +271 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: eea380f4afe8a28a3fe8ef442ee68bc36ed44fc7
4
+ data.tar.gz: 27394ec000f0bb22a985f37e38058c9c32057b33
5
+ SHA512:
6
+ metadata.gz: 4a4c3f9cb1e0e99c47fc939ba45bf15ce944ab3bdc9ac70f3086daccfd1e0ba5c8b1e7a3ceed9a360653d3ae614ac5bf3f83bf8e519f373f3ec5e5c688ce0271
7
+ data.tar.gz: 65744967afec469a3224a608dd141d5c2549f482e28c1d42f2980388cccfe3e1d37d3ee6714d4ae497c2f337c534780a0bc4621d42cbf182c4cb7eaa7c9a827c
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ log/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.simplecov ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ SimpleCov.start do
3
+ add_filter "/spec/"
4
+ end
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ cache: bundler
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # Fake SNS
2
+
3
+ A small web app for local SNS development.
4
+
5
+ It contains a small store to inspect, and some methods to inspect and change the
6
+ contents, so you can create scenarios.
7
+
8
+ ### Noteworthy differences:
9
+
10
+ * No checking of access keys.
11
+ * Returns all topics, not just 100, no support for `NextToken` parameter.
12
+
13
+ ### Implemented:
14
+
15
+ * CreateTopic
16
+ * ListTopics
17
+ * DeleteTopic
18
+ * GetTopicAttributes
19
+ * SetTopicAttributes
20
+ * ListSubscriptions
21
+ * ListSubscriptionsByTopic
22
+
23
+ ### Under Construction
24
+
25
+ * Subscribe
26
+ * Publish
27
+
28
+ ### Actions to be implemented:
29
+
30
+ * AddPermission
31
+ * ConfirmSubscription
32
+ * CreatePlatformApplication
33
+ * CreatePlatformEndpoint
34
+ * DeleteEndpoint
35
+ * DeletePlatformApplication
36
+ * GetEndpointAttributes
37
+ * GetPlatformApplicationAttributes
38
+ * GetSubscriptionAttributes
39
+ * ListEndpointsByPlatformApplication
40
+ * ListPlatformApplications
41
+ * RemovePermission
42
+ * SetEndpointAttributes
43
+ * SetPlatformApplicationAttributes
44
+ * SetSubscriptionAttributes
45
+ * Unsubscribe
46
+
47
+ ## Usage
48
+
49
+ There are 2 ways of running FakeSNS, as a gem, or as plain Rack app. The first
50
+ is easy, the latter is more flexible.
51
+
52
+ As a gem:
53
+
54
+ ```
55
+ $ gem install fake_sns
56
+ $ fake_sns -p 9292
57
+ ```
58
+
59
+ To configure AWS-SDK to send messages here:
60
+
61
+ ``` ruby
62
+ AWS.config(
63
+ use_ssl: false,
64
+ sns_endpoint: "0.0.0.0",
65
+ sns_port: 9292,
66
+ )
67
+ ```
68
+
69
+ ### Command line options
70
+
71
+ Get help by running `fake_sns --help`. These options are basically the same as
72
+ Sinatra's options. Here are the SNS specific options:
73
+
74
+ * Store the database somewhere else: `--database FILENAME` or
75
+ specify an in memory database that will be lost: `--database :memory:`
76
+
77
+ ### Extra endpoints
78
+
79
+ To get a YAML representation of all the data known to FakeSNS, do a GET request
80
+ to the root path:
81
+
82
+ ```
83
+ curl -X GET http://localhost:9292/
84
+ ```
85
+
86
+ To change the database, submit the contents you got from the previous step,
87
+ augment it and submit it as the body of a PUT request:
88
+
89
+ ```
90
+ curl -X GET http://localhost:9292/ -o my-data.yml
91
+ vim my-data.yml
92
+ curl -X PUT --data @my-data.yml http://localhost:9292/
93
+ ```
94
+
95
+ To reset the entire database, send a DELETE request:
96
+
97
+ ```
98
+ curl -X DELETE http://localhost:9292/
99
+ ```
100
+
101
+ To send ALL the messages stored in the queue, you can send a post request:
102
+
103
+ ```
104
+ curl -X POST http://localhost:9292/drain
105
+ ```
106
+
107
+ You can also just send a single message:
108
+
109
+ ```
110
+ curl -X POST http://localhost:9292/drain/:message_id
111
+ ```
112
+
113
+ Currently, only HTTP/HTTPS and SQS endpoints are working. You'll need
114
+ to pass AWS config (in JSON format) for the SQS integration to work. See
115
+ [FakeSNS] [fake_sns] for more information.
116
+
117
+ ```
118
+ curl \
119
+ -X POST \
120
+ --data '{"aws_config": {"use_ssl": false, "sqs_endpoint": "localhost", "sqs_port": 4789, "secret_access_key": "xxx", "access_key_id": "yyy"}}' \
121
+ http://localhost:9292/drain
122
+ ```
123
+
124
+ ### Test Integration
125
+
126
+ When making integration tests for your app, you can easily include Fake SNS.
127
+
128
+ Here are the methods you need to run FakeSNS programmatically.
129
+
130
+ ``` ruby
131
+ require "fake_sns/test_integration"
132
+
133
+ # globally, before the test suite starts:
134
+ AWS.config(
135
+ use_ssl: false,
136
+ sns_endpoint: "localhost",
137
+ sns_port: 4568,
138
+ access_key_id: "fake access key",
139
+ secret_access_key: "fake secret key",
140
+ )
141
+ fake_sns = FakeSNS::TestIntegration.new
142
+
143
+ # before each test that requires SNS:
144
+ fake_sns.start
145
+
146
+ # at the end of the suite:
147
+ at_exit {
148
+ fake_sns.stop
149
+ }
150
+
151
+ # for debugging, get everything FakeSNS knows:
152
+ puts fake_sns.data.inspect
153
+
154
+ # if you have SQS configured in the AWS config, you can also do:
155
+ fake_sns.drain
156
+ ```
157
+
158
+ See `spec/spec_helper.rb` in this project for an example on how to load it in
159
+ your test suite.
160
+
161
+ ## More information
162
+
163
+ * [API Reference](http://docs.aws.amazon.com/sns/latest/api/API_Operations.html)
164
+ * [AWS-SDK docs](http://rubydoc.info/gems/aws-sdk/frames)
165
+ * [Fake SQS] [fake_sqs]
166
+
167
+ [fake_sqs]: https://github.com/iain/fake_sqs
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "tempfile"
4
+ require "rspec/core/rake_task"
5
+
6
+ namespace :spec do
7
+
8
+ desc "Run specs with in-memory database"
9
+ RSpec::Core::RakeTask.new(:memory) do |t|
10
+ ENV["SNS_DATABASE"] = ":memory:"
11
+ end
12
+
13
+ desc "Run specs with file database"
14
+ RSpec::Core::RakeTask.new(:file) do |t|
15
+ file = Tempfile.new(["rspec-sns", ".yml"], encoding: "utf-8")
16
+ ENV["SNS_DATABASE"] = file.path
17
+ end
18
+
19
+ end
20
+
21
+ desc "Run spec suite with both in-memory and file"
22
+ task :spec => ["spec:memory", "spec:file"]
23
+
24
+ task :default => :spec
data/bin/fake_sns ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path("../../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'fake_sns'
7
+ require 'optparse'
8
+
9
+ options = {
10
+ :port => 9292,
11
+ :host => "0.0.0.0",
12
+ :verbose => false,
13
+ :daemonize => false,
14
+ :database => nil,
15
+ }
16
+
17
+ parser = OptionParser.new do |o|
18
+
19
+ o.on "--database FILENAME", "Place to store the database (defaults to ~/.fake_sns.yml)" do |filename|
20
+ options[:database] = filename
21
+ end
22
+
23
+ o.on "-p", "--port PORT", Integer, "Port to use (default: #{options[:port]})" do |port|
24
+ options[:port] = port
25
+ end
26
+
27
+ o.on "-o", "--bind HOST", "Host to bind to (default: 0.0.0.0)" do |host|
28
+ options[:host] = host
29
+ end
30
+
31
+ o.on "-s", "--server SERVER", ['thin', 'mongrel', 'webrick'], "Server to use: thin, mongrel or webrick (by default Sinatra chooses the best available)" do |server|
32
+ options[:server] = server
33
+ end
34
+
35
+ o.on "-P", "--pid PIDFILE", "Where to write the pid" do |pid|
36
+ options[:pid] = pid
37
+ end
38
+
39
+ o.on "-d", "--[no-]daemonize", "Detaches the process" do |daemonize|
40
+ options[:daemonize] = daemonize
41
+ end
42
+
43
+ o.on "-v", "--[no]-verbose", "Shows input parameters and output XML" do |verbose|
44
+ options[:verbose] = verbose
45
+ end
46
+
47
+ o.on_tail "--version", "Shows the version" do
48
+ puts "fake_sns version #{FakeSNS::VERSION}"
49
+ exit
50
+ end
51
+
52
+ o.on_tail "-h", "--help", "Shows this help page" do
53
+ puts o
54
+ exit
55
+ end
56
+
57
+ end
58
+
59
+ parser.parse!
60
+
61
+ if options[:daemonize]
62
+ Process.daemon(true, true)
63
+ end
64
+
65
+ if (pid = options[:pid])
66
+ if File.exist?(pid)
67
+ existing_pid = File.open(pid, 'r').read.chomp.to_i
68
+ running = Process.getpgid(existing_pid) rescue false
69
+ if running
70
+ warn "Error, Process #{existing_pid} already running"
71
+ exit 1
72
+ else
73
+ warn "Cleaning up stale pid at #{pid}"
74
+ end
75
+ end
76
+ File.open(pid, 'w') { |f| f.write(Process.pid) }
77
+ end
78
+
79
+ FakeSNS.server(options).run!
data/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
+ require "fake_sns/server"
3
+ run FakeSNS::Server
data/fake_sns.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fake_sns/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "fake_sns"
8
+ spec.version = FakeSNS::VERSION
9
+ spec.authors = ["iain"]
10
+ spec.email = ["iain@iain.nl"]
11
+ spec.description = %q{Small Fake version of SNS}
12
+ spec.summary = %q{Small Fake version of SNS}
13
+ spec.homepage = "https://github.com/yourkarma/fake_sns"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "sinatra", "~> 1.4"
21
+ spec.add_dependency "virtus", "~> 1.0"
22
+ spec.add_dependency "verbose_hash_fetch"
23
+ spec.add_dependency "faraday", "~> 0.8"
24
+ spec.add_dependency "aws-sdk", "~> 1.30"
25
+
26
+ spec.add_development_dependency "aws-sdk"
27
+ spec.add_development_dependency "bundler"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "rspec"
30
+ spec.add_development_dependency "pry"
31
+ spec.add_development_dependency "fake_sqs", "~> 0.0.9"
32
+ spec.add_development_dependency "json_expressions"
33
+
34
+ end
@@ -0,0 +1,24 @@
1
+ module FakeSNS
2
+ class Action
3
+
4
+ attr_reader :db, :params
5
+
6
+ def self.param(fields, &block)
7
+ fields.each do |field, key|
8
+ define_method field do
9
+ params.fetch(key, &block)
10
+ end
11
+ end
12
+ end
13
+
14
+ def initialize(db, params)
15
+ @db = db
16
+ @params = params
17
+ end
18
+
19
+ def call
20
+ # override me, if needed
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class CreateTopic < Action
4
+
5
+ param name: "Name"
6
+
7
+ def valid_name?
8
+ name =~ /\A[\w\-]+\z/
9
+ end
10
+
11
+ def call
12
+ raise InvalidParameterValue, "Topic Name: #{name.inspect}" unless valid_name?
13
+ @topic = (existing_topic || new_topic)
14
+ end
15
+
16
+ def arn
17
+ topic["arn"]
18
+ end
19
+
20
+ attr_reader :topic
21
+
22
+ private
23
+
24
+ def new_topic
25
+ arn = generate_arn
26
+ topic_attributes = { "arn" => arn, "name" => name }
27
+ db.topics.create(topic_attributes)
28
+ topic_attributes
29
+ end
30
+
31
+ def generate_arn
32
+ "arn:aws:sns:us-east-1:#{SecureRandom.hex}:#{name}"
33
+ end
34
+
35
+ def existing_topic
36
+ db.topics.find { |t| t["name"] == name }
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,13 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class DeleteTopic < Action
4
+
5
+ param arn: "TopicArn"
6
+
7
+ def call
8
+ db.topics.delete(arn)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class GetTopicAttributes < Action
4
+
5
+ param arn: "TopicArn"
6
+
7
+ # TopicArn
8
+ # Owner
9
+ # Policy
10
+ # DisplayName
11
+ # SubscriptionsPending
12
+ # SubscriptionsConfirmed
13
+ # SubscriptionsDeleted
14
+ # DeliveryPolicy
15
+ # EffectiveDeliveryPolicy
16
+
17
+ attr_reader :topic
18
+
19
+ def call
20
+ @topic = db.topics.fetch(arn) { raise NotFound, arn }
21
+ end
22
+
23
+ def each_attribute
24
+ yield "TopicArn", arn
25
+ %w(DisplayName Policy DeliveryPolicy).each do |key|
26
+ yield key, topic[key] if topic.has_key?(key)
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class ListSubscriptions < Action
4
+
5
+ param next_token: "NextToken"
6
+
7
+ def call
8
+ end
9
+
10
+ def each_subscription
11
+ subscriptions.each do |subscription|
12
+ yield subscription
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def subscriptions
19
+ db.subscriptions
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class ListSubscriptionsByTopic < ListSubscriptions
4
+
5
+ param topic_arn: "TopicArn"
6
+ param next_token: "NextToken"
7
+
8
+ def call
9
+ super
10
+ @topic = db.topics.fetch(topic_arn) do
11
+ raise InvalidParameterValue, "Unknown topic: #{topic_arn}"
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def subscriptions
18
+ super.select { |s| s["topic_arn"] == topic_arn }
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class ListTopics < Action
4
+
5
+ def each_topic
6
+ db.topics.each do |topic|
7
+ yield topic
8
+ end
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class Publish < Action
4
+
5
+ param message: "Message"
6
+ param message_structure: "MessageStructure"
7
+ param subject: "Subject" do nil end
8
+ param target_arn: "TargetArn" do nil end
9
+ param topic_arn: "TopicArn" do nil end
10
+
11
+ def call
12
+ if (bytes = message.bytesize) > 262144
13
+ raise InvalidParameterValue, "Too much bytes: #{bytes} > 262144."
14
+ end
15
+ @topic = db.topics.fetch(topic_arn) do
16
+ raise InvalidParameterValue, "Unknown topic: #{topic_arn}"
17
+ end
18
+ @message_id = SecureRandom.uuid
19
+
20
+ db.messages.create(
21
+ id: message_id,
22
+ subject: subject,
23
+ message: message,
24
+ topic_arn: topic_arn,
25
+ structure: message_structure,
26
+ target_arn: target_arn,
27
+ received_at: Time.now,
28
+ )
29
+ end
30
+
31
+ def message_id
32
+ @message_id || raise(InternalFailure, "no message id yet, this should not happen")
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class SetTopicAttributes < Action
4
+
5
+ VALID_PARAMETER_NAMES = %w(Policy DisplayName DeliveryPolicy)
6
+
7
+ param key: "AttributeName"
8
+ param value: "AttributeValue"
9
+ param arn: "TopicArn"
10
+
11
+ def call
12
+ raise InvalidParameterValue, "AttributeName: #{key.inspect}" unless VALID_PARAMETER_NAMES.include?(key)
13
+ topic = db.topics.fetch(arn) { raise NotFound, arn }
14
+ topic[key] = value
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ module FakeSNS
2
+ module Actions
3
+ class Subscribe < Action
4
+
5
+ param endpoint: "Endpoint"
6
+ param protocol: "Protocol"
7
+ param topic_arn: "TopicArn"
8
+
9
+ attr_reader :topic
10
+
11
+ def call
12
+ @topic = db.topics.fetch(topic_arn) do
13
+ raise InvalidParameterValue, "Unknown topic: #{topic_arn}"
14
+ end
15
+ @subscription = (existing_subscription || new_subscription)
16
+ end
17
+
18
+ def subscription_arn
19
+ @subscription["arn"]
20
+ end
21
+
22
+ private
23
+
24
+ def existing_subscription
25
+ db.subscriptions.to_a.find { |s|
26
+ s.topic_arn == topic_arn && s.endpoint == endpoint
27
+ }
28
+ end
29
+
30
+ def new_subscription
31
+ attributes = {
32
+ "arn" => "#{topic_arn}:#{SecureRandom.uuid}",
33
+ "protocol" => protocol,
34
+ "endpoint" => endpoint,
35
+ "topic_arn" => topic_arn,
36
+ }
37
+ db.subscriptions.create(attributes)
38
+ attributes
39
+ end
40
+
41
+ end
42
+ end
43
+ end