postbin 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.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2011-11-22)
2
+
3
+ * Initial gem release, v0.1.0
@@ -0,0 +1,33 @@
1
+ PostBin
2
+ =======
3
+
4
+ PostBin, a simple web service for testing WebHooks (HTTP POST requests).
5
+
6
+ Quick Start
7
+ -----------
8
+
9
+ $ gem install postbin
10
+ $ postbin
11
+ == PostBin online http://127.0.0.1:6969/
12
+ == CTRL+C to stop
13
+
14
+ When running postbin from the command line, requests are stored in a temporary
15
+ file database, they will be lost once the server terminates.
16
+
17
+ Rack Application
18
+ ----------------
19
+
20
+ You can run a more permeant install by running PostBin as a Rack application:
21
+
22
+ # example config.ru
23
+ require 'postbin'
24
+ # path of pstore file to use for storage.
25
+ pstore = File.expand_path(File.join(File.dirname(__FILE__), 'postbin.pstore'))
26
+ # start the server.
27
+ run PostBin::Server.new(pstore)
28
+
29
+ Command Line Options
30
+ --------------------
31
+
32
+ -a, --address HOST listen on HOST address (default: 127.0.0.1)
33
+ -p, --port PORT use PORT number (default: 6969)
@@ -0,0 +1,13 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'rake/testtask'
3
+
4
+ # by default run unit tests.
5
+ task :default => 'test:unit'
6
+
7
+ namespace :test do
8
+ # unit tests.
9
+ Rake::TestTask.new(:unit) do |task|
10
+ task.test_files = FileList['test/**/*_test.rb']
11
+ task.verbose = true
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # --- use local code if possible, otherwise use gem --------------------------
4
+ begin
5
+ require 'postbin'
6
+ rescue LoadError => err
7
+ require 'rubygems'
8
+ path = ::File.dirname(::File.expand_path(__FILE__))
9
+ $LOAD_PATH.unshift path + '/../lib' if File.directory?(path) && !$LOAD_PATH.include?(path)
10
+ require 'postbin'
11
+ end
12
+
13
+ # --- cli option parsing -----------------------------------------------------
14
+ require 'optparse'
15
+
16
+ # default options.
17
+ options = { :bind => '127.0.0.1', :port => 6969, :server => 'thin', :enviroment => :production }
18
+
19
+ # available options.
20
+ opts = OptionParser.new('', 24, ' ') do |opts|
21
+ opts.banner = 'Usage: postbin [options]'
22
+ opts.separator ''
23
+ opts.separator 'PostBin options:'
24
+ opts.on('-v', '--version', 'show version number') { puts 'PostBin ' + PostBin::Version; exit }
25
+ opts.on('-h', '--help', 'show this message') { puts opts; exit; }
26
+ opts.separator ''
27
+ opts.separator 'Rack options:'
28
+ opts.on('-s', '--server SERVER', 'server (webrick, mongrel, thin, etc.)') { |s| options[:server] = s }
29
+ opts.on('-a', '--address HOST', 'listen on HOST address (default: 127.0.0.1)') { |host| options[:bind] = host }
30
+ opts.on('-p', '--port PORT', 'use PORT number (default: 6969)') { |port| options[:port] = port }
31
+ end.parse!
32
+
33
+ # --- start sinatra server ---------------------------------------------------
34
+ puts "== Starting PostBin on http://#{options[:bind]}:#{options[:port]}"
35
+ PostBin::Server.run!(options)
@@ -0,0 +1,8 @@
1
+ require 'pstore'
2
+ require 'json'
3
+ require 'sinatra/base'
4
+
5
+ require_relative 'postbin/post'
6
+ require_relative 'postbin/server'
7
+ require_relative 'postbin/storage'
8
+ require_relative 'postbin/version'
@@ -0,0 +1,26 @@
1
+ module PostBin
2
+
3
+ # Represents header/body information of a HTTP POST request.
4
+ class Post
5
+ attr_reader :received_at, :headers, :body
6
+
7
+ def initialize(headers, body, received_at = nil)
8
+ @received_at = received_at || Time.now
9
+ @headers = headers
10
+ @body = body
11
+ end
12
+
13
+ # Returns true only if the two posts contain equal data.
14
+ def ==(other)
15
+ return false unless Post === other
16
+ return false unless received_at == other.received_at
17
+ return false unless headers == other.headers
18
+ body == other.body
19
+ end
20
+
21
+ def to_json(*a)
22
+ { received_at: received_at, headers: headers, body: body }.to_json(*a)
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,71 @@
1
+ module PostBin
2
+ class Server < Sinatra::Base
3
+
4
+ dir = File.dirname(File.expand_path(__FILE__))
5
+
6
+ set :views, "#{dir}/server/views"
7
+ set :public_folder, "#{dir}/server/public"
8
+ set :static, true
9
+
10
+ # by default we store in a temp file.
11
+ set :pstore_file, Tempfile.new(['postbin', 'pstore'])
12
+
13
+ # Redirect lost users.
14
+ get '/?' do; redirect '/postbin/overview'; end
15
+ get '/postbin/?' do; redirect '/postbin/overview'; end
16
+
17
+ # Display main overview.
18
+ get '/postbin/overview' do
19
+ storage = PostBin::Storage.new(settings.pstore_file)
20
+ # pull out all urls and there posts.
21
+ @url_hits = storage.hits
22
+
23
+ @url_posts = @url_hits.inject({}) do |result, data|
24
+ url, hits = *data
25
+ result[url] = storage.posts(url)
26
+ result
27
+ end
28
+
29
+ erb :overview
30
+ end
31
+
32
+ # Display urls and there hit count as JSON.
33
+ get '/postbin/hits' do
34
+ storage = PostBin::Storage.new(settings.pstore_file)
35
+
36
+ content_type :json
37
+ storage.hits.to_json
38
+ end
39
+
40
+ # Display posts for the given URL as JSON.
41
+ get %r{/postbin/posts(.*)} do
42
+ url = params[:captures].first
43
+ storage = PostBin::Storage.new(settings.pstore_file)
44
+
45
+ content_type :json
46
+ storage.posts(url).to_json
47
+ end
48
+
49
+ # Catch all for post requests.
50
+ post '*' do
51
+ storage = PostBin::Storage.new(settings.pstore_file)
52
+ post = PostBin::Post.new(client_request_headers, request.body.read)
53
+ storage.store(request.path, post)
54
+ status 201
55
+ end
56
+
57
+ private
58
+ # Returns an array of all client side HTTP request headers.
59
+ def client_request_headers
60
+ # POST /some/url HTTP/1.1
61
+ # Accept: application/json
62
+ # Content-Type: application/json
63
+ headers = request.env.select { |k,v| k.start_with? 'HTTP_' }
64
+ .collect { |pair| [ pair[0].sub(/^HTTP_/, ''), pair[1] ] }
65
+ .collect { |pair| pair.join(': ') }
66
+
67
+ headers
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <title>PostBin</title>
6
+ </head>
7
+ <body>
8
+ <div id="header">
9
+ <h1>PostBin</h1>
10
+ </div>
11
+
12
+ <div id="main">
13
+ <%= yield %>
14
+ </div>
15
+ <!--
16
+ <div id="footer">
17
+ <div class="block wat-cf">
18
+ <span class="links">http://github.com/lantins/postbin</span>
19
+ <span id="dev-info">v<%= PostBin::Version %></span>
20
+ </div>
21
+ </div>
22
+ -->
23
+ </body>
24
+ </html>
@@ -0,0 +1,45 @@
1
+ <!-- FIXME: replace with some async javascript magic and make pretty -->
2
+
3
+ <h3>URLs &amp; Hits</h3>
4
+
5
+ <table border="1">
6
+ <tr>
7
+ <th >Total Hits</th>
8
+ <th>URL</th>
9
+ </tr>
10
+ <% if @url_hits.empty? %>
11
+ <tr>
12
+ <td colspan="2"><em>No urls &amp; hits to display.</em></td>
13
+ </tr>
14
+ <% else %>
15
+ <% @url_hits.each do |url, hits| %>
16
+ <tr>
17
+ <td><%= hits %></td>
18
+ <td><%= url %></td>
19
+ </tr>
20
+ <% end %>
21
+ <% end %>
22
+ </table>
23
+
24
+ <hr>
25
+
26
+ <h3>Recent Post Requests</h3>
27
+
28
+ <% if @url_posts.empty? %>
29
+ <em>No post requests to display.</em>
30
+ <% else %>
31
+ <% @url_posts.each_pair do |url, posts| %>
32
+ <strong>URL:</strong> <%= url %><br /><br />
33
+ <div style="padding-left: 40px;">
34
+ <% posts.each do |post| %>
35
+ <strong>TIMESTAMP</strong>
36
+ <pre><%= post.received_at %></pre>
37
+ <strong>HEADERS</strong>
38
+ <pre><%= post.headers.join("\n") %></pre>
39
+ <strong>BODY</strong>
40
+ <pre><%= post.body %></pre>
41
+ <hr>
42
+ <% end %>
43
+ </div>
44
+ <% end %>
45
+ <% end %>
@@ -0,0 +1,47 @@
1
+ module PostBin
2
+
3
+ # Storage backend for PostBin, uses PStore under the hood.
4
+ class Storage
5
+
6
+ def initialize(pstore_file)
7
+ # setup file database for storage.
8
+ @db = PStore.new(pstore_file)
9
+ end
10
+
11
+ # Store a post in the database.
12
+ def store(url, post)
13
+ @db.transaction do
14
+ # init if no data exists.
15
+ @db['urls'] ||= {}
16
+ @db['urls'][url] ||= 0
17
+ @db[url] ||= []
18
+ # incr hit count.
19
+ @db['urls'][url] += 1
20
+ # store post.
21
+ @db[url] << post
22
+ end
23
+ end
24
+
25
+ # Returns hash, key being url and value being number of posts received.
26
+ def hits
27
+ @db.transaction(true) do
28
+ @db['urls'] || {}
29
+ end
30
+ end
31
+
32
+ # Returns array of urls that have been posted to.
33
+ def urls
34
+ @db.transaction(true) do
35
+ (@db['urls'] || {}).keys
36
+ end
37
+ end
38
+
39
+ # Returns array of posts for the given url.
40
+ def posts(url)
41
+ @db.transaction(true) do
42
+ @db[url] || []
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module PostBin
2
+ Version = '0.1.0'
3
+ end
@@ -0,0 +1,31 @@
1
+ require './lib/postbin/version'
2
+
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = 'postbin'
5
+ s.version = PostBin::Version
6
+ s.date = Time.now.strftime('%Y-%m-%d')
7
+ s.summary = 'PostBin, a simple web service for testing WebHooks (HTTP POST requests).'
8
+ s.homepage = 'https://github.com/lantins/postbin'
9
+ s.authors = ['Luke Antins']
10
+ s.email = ['luke@lividpenguin.com']
11
+ s.has_rdoc = false
12
+
13
+ s.files = %w(Rakefile README.md HISTORY.md Gemfile postbin.gemspec)
14
+ s.files += Dir.glob('{test/**/*,lib/**/*}')
15
+ s.require_paths = ['lib']
16
+ s.executables = ['postbin']
17
+ s.default_executable = 'postbin'
18
+
19
+ s.add_dependency('sinatra')
20
+ s.add_development_dependency('rake')
21
+ s.add_development_dependency('yard')
22
+ s.add_development_dependency('minitest')
23
+ s.add_development_dependency('simplecov')
24
+ s.add_development_dependency('rack-test')
25
+ s.add_development_dependency('bundler')
26
+
27
+ s.description = <<-EOL
28
+ A ruby command line/rack application for testing systems that send WebHooks
29
+ using a HTTP POST request. Requests can be viewed via a simple web interface.
30
+ EOL
31
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestPost < MiniTest::Unit::TestCase
4
+
5
+ def test_has_a_header_and_body
6
+ post = PostBin::Post.new('header data', 'body data')
7
+ assert_equal 'header data', post.headers, 'post headers not as we expect'
8
+ assert_equal 'body data', post.body, 'post body not as we expect'
9
+ end
10
+
11
+ def test_has_received_at_timestamp
12
+ post = PostBin::Post.new('headers', 'body')
13
+ assert_kind_of Time, post.received_at
14
+ end
15
+
16
+ def test_we_can_set_received_at
17
+ now = Time.now
18
+ post = PostBin::Post.new('headers', 'body', now)
19
+ assert_equal now, post.received_at
20
+ end
21
+
22
+ def test_double_equals_comparison_method
23
+ now = Time.now
24
+ post1 = PostBin::Post.new('headers', 'body', now)
25
+ post2 = PostBin::Post.new('headers', 'body', now)
26
+ assert_equal post1, post2, 'should be the same'
27
+ end
28
+
29
+ end
@@ -0,0 +1,62 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestServer < MiniTest::Unit::TestCase
4
+ def app
5
+ PostBin::Server.new
6
+ # use a new pstore file for each request.
7
+ PostBin::Server.set :pstore_file, Tempfile.new(['postbin', 'pstore'])
8
+ end
9
+
10
+ def test_roots_should_redirect_to_overview
11
+ get '/'
12
+ follow_redirect!
13
+ assert_equal '/postbin/overview', last_request.path
14
+ assert last_response.ok?
15
+
16
+ get '/postbin'
17
+ follow_redirect!
18
+ assert_equal '/postbin/overview', last_request.path
19
+ assert last_response.ok?
20
+ end
21
+
22
+ def test_overview_returns_200
23
+ get '/postbin/overview'
24
+ assert_equal 200, last_response.status, 'status code should be 200 OK'
25
+ end
26
+
27
+ def test_accepts_post_requests
28
+ post '/hello', 'foobar'
29
+ assert_equal 201, last_response.status, 'status code should be 201 Created'
30
+ end
31
+
32
+ def test_accepts_posts_to_long_urls
33
+ post '/foo/bar/is/very/long.php', 'hello world'
34
+ assert_equal 201, last_response.status, 'status code should be 201 Created'
35
+ end
36
+
37
+ def test_json_url_hits
38
+ get '/postbin/hits'
39
+ assert_equal 200, last_response.status, 'status code should be 200 OK'
40
+ assert_includes last_response.headers['Content-Type'], 'application/json', 'Content-Type header should be json'
41
+ end
42
+
43
+ def test_json_url_posts
44
+ get '/postbin/posts'
45
+ assert_equal 200, last_response.status, 'status code should be 200 OK'
46
+ assert_includes last_response.headers['Content-Type'], 'application/json', 'Content-Type header should be json'
47
+ end
48
+
49
+ def test_post_and_read_back
50
+ # post some data.
51
+ post '/cats', 'they miaow'
52
+ get '/postbin/hits'
53
+ assert_equal '{"/cats":1}', last_response.body
54
+
55
+ # read it back.
56
+ get '/postbin/posts/cats'
57
+ post = JSON.parse(last_response.body)[0]
58
+ assert_equal 'they miaow', post['body']
59
+ end
60
+
61
+
62
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestStorage < MiniTest::Unit::TestCase
4
+
5
+ def test_new_instance
6
+ assert PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
7
+ end
8
+
9
+ def test_all_urls_returns_empty_hash_by_default
10
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
11
+ assert_empty storage.urls, 'should return empty array by default'
12
+ end
13
+
14
+ def test_can_store_post
15
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
16
+ post = PostBin::Post.new('1', '2')
17
+ assert storage.store('/test/url', post)
18
+ end
19
+
20
+ def test_can_get_all_urls
21
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
22
+ post = PostBin::Post.new('1', '2')
23
+ storage.store('/foobar', post)
24
+
25
+ expected = ['/foobar']
26
+ assert_equal expected, storage.urls, 'all urls should be a populated array'
27
+ end
28
+
29
+ def test_keeps_count_of_posts_to_same_url
30
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
31
+ post = PostBin::Post.new('1', '2')
32
+ storage.store('/foobar', post)
33
+ storage.store('/foobar', post)
34
+ storage.store('/hello', post)
35
+
36
+ hits = storage.hits
37
+ assert_equal 2, hits['/foobar'], 'count should be 2'
38
+ assert_equal 1, hits['/hello'], 'count should be 1'
39
+ end
40
+
41
+ def test_can_retrieve_saved_post_data
42
+ storage = PostBin::Storage.new(Tempfile.new(['postbin', 'pstore']))
43
+ post = PostBin::Post.new('4', '2')
44
+ storage.store('/retrieve', post)
45
+
46
+ posts = storage.posts('/retrieve')
47
+ assert_equal post, posts[0], 'should match the post we stored'
48
+ end
49
+
50
+ end
@@ -0,0 +1,24 @@
1
+ #
2
+ # Test helper init script.
3
+ #
4
+ require 'simplecov'
5
+
6
+ # code coverage groups.
7
+ SimpleCov.start do
8
+ add_filter 'test/'
9
+ end
10
+
11
+ # load our dependencies using bundler.
12
+ require 'bundler/setup'
13
+ Bundler.setup
14
+ require 'minitest/autorun'
15
+ require 'minitest/pride'
16
+ require 'rack/test'
17
+
18
+ # load postbin lib.
19
+ require_relative './../lib/postbin'
20
+
21
+ # extend main TestCase
22
+ class MiniTest::Unit::TestCase
23
+ include Rack::Test::Methods
24
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestVersion < MiniTest::Unit::TestCase
4
+ def test_should_have_a_version
5
+ assert PostBin::Version, 'unable to access the version number'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: postbin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Luke Antins
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra
16
+ requirement: &2165957000 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2165957000
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &2165956540 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2165956540
36
+ - !ruby/object:Gem::Dependency
37
+ name: yard
38
+ requirement: &2165956060 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2165956060
47
+ - !ruby/object:Gem::Dependency
48
+ name: minitest
49
+ requirement: &2165955600 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2165955600
58
+ - !ruby/object:Gem::Dependency
59
+ name: simplecov
60
+ requirement: &2165955180 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2165955180
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: &2165954740 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2165954740
80
+ - !ruby/object:Gem::Dependency
81
+ name: bundler
82
+ requirement: &2165954320 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *2165954320
91
+ description: ! " A ruby command line/rack application for testing systems that send
92
+ WebHooks\n using a HTTP POST request. Requests can be viewed via a simple web interface.\n"
93
+ email:
94
+ - luke@lividpenguin.com
95
+ executables:
96
+ - postbin
97
+ extensions: []
98
+ extra_rdoc_files: []
99
+ files:
100
+ - Rakefile
101
+ - README.md
102
+ - HISTORY.md
103
+ - Gemfile
104
+ - postbin.gemspec
105
+ - test/post_test.rb
106
+ - test/server_test.rb
107
+ - test/storage_test.rb
108
+ - test/test_helper.rb
109
+ - test/version_test.rb
110
+ - lib/postbin/post.rb
111
+ - lib/postbin/server/views/layout.erb
112
+ - lib/postbin/server/views/overview.erb
113
+ - lib/postbin/server.rb
114
+ - lib/postbin/storage.rb
115
+ - lib/postbin/version.rb
116
+ - lib/postbin.rb
117
+ - bin/postbin
118
+ homepage: https://github.com/lantins/postbin
119
+ licenses: []
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 1.8.10
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: PostBin, a simple web service for testing WebHooks (HTTP POST requests).
142
+ test_files: []
143
+ has_rdoc: false