fake_sns 0.0.1

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 (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