anmo 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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Guardfile +3 -0
- data/Rakefile +7 -0
- data/anmo.gemspec +31 -0
- data/features/api.feature +115 -0
- data/features/step_definitions/api_steps.rb +51 -0
- data/features/support/env.rb +13 -0
- data/lib/anmo.rb +21 -0
- data/lib/anmo/application.rb +65 -0
- data/lib/anmo/version.rb +3 -0
- data/test/anmo/application_test.rb +86 -0
- metadata +171 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/Rakefile
ADDED
data/anmo.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "anmo/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "anmo"
|
7
|
+
s.version = Anmo::VERSION
|
8
|
+
s.authors = ["Andrew Vos"]
|
9
|
+
s.email = ["andrew.vos@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{}
|
12
|
+
s.description = %q{}
|
13
|
+
|
14
|
+
s.rubyforge_project = "anmo"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_runtime_dependency "rack"
|
22
|
+
s.add_runtime_dependency "thin"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "minitest"
|
25
|
+
s.add_development_dependency "rack-test"
|
26
|
+
s.add_development_dependency "cucumber"
|
27
|
+
s.add_development_dependency "rspec"
|
28
|
+
s.add_development_dependency "guard"
|
29
|
+
s.add_development_dependency "httparty"
|
30
|
+
s.add_development_dependency "guard-shell"
|
31
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
Feature: API
|
2
|
+
In order to test applications against stable APIs
|
3
|
+
As a developer
|
4
|
+
I want to be able to mock http endpoints
|
5
|
+
|
6
|
+
Scenario: Launch a server
|
7
|
+
When I execute the following code in a new thread
|
8
|
+
"""
|
9
|
+
Anmo.launch_server
|
10
|
+
"""
|
11
|
+
Then I see an anmo server on port 8787
|
12
|
+
|
13
|
+
Scenario: Launch a server with a port
|
14
|
+
When I execute the following code in a new thread
|
15
|
+
"""
|
16
|
+
Anmo.launch_server 9393
|
17
|
+
"""
|
18
|
+
Then I see an anmo server on port 9393
|
19
|
+
|
20
|
+
Scenario: Create a request
|
21
|
+
Given an anmo server
|
22
|
+
When I execute the code
|
23
|
+
"""
|
24
|
+
Anmo.server = "http://localhost:8787"
|
25
|
+
Anmo.create_request({
|
26
|
+
:path => "/lollll",
|
27
|
+
:body => "can't think of anything",
|
28
|
+
:required_headers => {"meh" => "bleh", "blah" => "blah"}
|
29
|
+
})
|
30
|
+
"""
|
31
|
+
And I request the uri "http://localhost:8787/lollll" with the headers
|
32
|
+
"""
|
33
|
+
{
|
34
|
+
"meh": "bleh",
|
35
|
+
"blah": "blah"
|
36
|
+
}
|
37
|
+
"""
|
38
|
+
Then I see the response body "can't think of anything"
|
39
|
+
|
40
|
+
Scenario: Save object
|
41
|
+
Given an anmo server
|
42
|
+
When I execute the code
|
43
|
+
"""
|
44
|
+
Anmo.server = "http://localhost:8787"
|
45
|
+
Anmo.create_request({
|
46
|
+
:path => "/some/object",
|
47
|
+
:body => "some data"
|
48
|
+
})
|
49
|
+
"""
|
50
|
+
And I request the uri "http://localhost:8787/some/object"
|
51
|
+
Then I see the response body "some data"
|
52
|
+
|
53
|
+
Scenario: Save object with http status code
|
54
|
+
Given an anmo server
|
55
|
+
When I execute the code
|
56
|
+
"""
|
57
|
+
Anmo.server = "http://localhost:8787"
|
58
|
+
Anmo.create_request({
|
59
|
+
:path => "/some/object",
|
60
|
+
:status => 123
|
61
|
+
})
|
62
|
+
"""
|
63
|
+
And I request the uri "http://localhost:8787/some/object"
|
64
|
+
Then I see the response code 123
|
65
|
+
|
66
|
+
Scenario: Request without required headers 404s
|
67
|
+
Given an anmo server
|
68
|
+
When I execute the code
|
69
|
+
"""
|
70
|
+
Anmo.server = "http://localhost:8787"
|
71
|
+
Anmo.create_request({
|
72
|
+
:path => "/some/object",
|
73
|
+
:required_headers => {:meh => "bleh", :durp => "derp"}
|
74
|
+
})
|
75
|
+
"""
|
76
|
+
And I request the uri "http://localhost:8787/some/object"
|
77
|
+
Then I see the response code 404
|
78
|
+
|
79
|
+
Scenario: Request with required headers
|
80
|
+
Given an anmo server
|
81
|
+
When I execute the code
|
82
|
+
"""
|
83
|
+
Anmo.server = "http://localhost:8787"
|
84
|
+
Anmo.create_request({
|
85
|
+
:path => "/some/object",
|
86
|
+
:body => "helloow",
|
87
|
+
:required_headers => {:meh => "bleh", :durp => "derp"}
|
88
|
+
})
|
89
|
+
"""
|
90
|
+
And I request the uri "http://localhost:8787/some/object" with the headers
|
91
|
+
"""
|
92
|
+
{
|
93
|
+
"meh": "bleh",
|
94
|
+
"durp": "derp"
|
95
|
+
}
|
96
|
+
"""
|
97
|
+
Then I see the response body "helloow"
|
98
|
+
And I see the response code 200
|
99
|
+
|
100
|
+
Scenario: Delete all saved objects
|
101
|
+
Given an anmo server
|
102
|
+
When I execute the code
|
103
|
+
"""
|
104
|
+
Anmo.server = "http://localhost:8787"
|
105
|
+
Anmo.create_request({
|
106
|
+
:path => "/some/object",
|
107
|
+
:body => "some data"
|
108
|
+
})
|
109
|
+
"""
|
110
|
+
And I execute the code
|
111
|
+
"""
|
112
|
+
Anmo.delete_all
|
113
|
+
"""
|
114
|
+
And I request the uri "http://localhost:8787/some/object"
|
115
|
+
Then I see the response code 404
|
@@ -0,0 +1,51 @@
|
|
1
|
+
When /^I execute the following code in a new thread$/ do |code|
|
2
|
+
Thread.new do
|
3
|
+
eval code
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
When /^I execute the code$/ do |code|
|
8
|
+
eval code
|
9
|
+
end
|
10
|
+
|
11
|
+
Then /^I see an anmo server on port (\d+)$/ do |port|
|
12
|
+
response = nil
|
13
|
+
|
14
|
+
timeout 5 do
|
15
|
+
while response.nil?
|
16
|
+
response = HTTParty.get("http://localhost:#{port}") rescue nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
response.body.should include "Not Found"
|
21
|
+
end
|
22
|
+
|
23
|
+
Given /^an anmo server$/ do
|
24
|
+
Thread.new do
|
25
|
+
Anmo.launch_server
|
26
|
+
end
|
27
|
+
timeout 5 do
|
28
|
+
response = nil
|
29
|
+
while response.nil?
|
30
|
+
response = HTTParty.get("http://localhost:8787") rescue nil
|
31
|
+
end
|
32
|
+
sleep 0.1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
When /^I request the uri "([^"]*)"$/ do |uri|
|
37
|
+
@response = HTTParty.get(uri)
|
38
|
+
end
|
39
|
+
|
40
|
+
When /^I request the uri "([^"]*)" with the headers$/ do |uri, headers|
|
41
|
+
headers = JSON.parse(headers)
|
42
|
+
@response = HTTParty.get(uri, :headers => headers)
|
43
|
+
end
|
44
|
+
|
45
|
+
Then /^I see the response body "([^"]*)"$/ do |body|
|
46
|
+
@response.body.should == body
|
47
|
+
end
|
48
|
+
|
49
|
+
Then /^I see the response code (\d+)$/ do |code|
|
50
|
+
@response.code.should == code.to_i
|
51
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "rack/test"
|
3
|
+
require "httparty"
|
4
|
+
|
5
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "../../lib")))
|
6
|
+
require "anmo"
|
7
|
+
require "anmo/application"
|
8
|
+
|
9
|
+
World(Rack::Test::Methods)
|
10
|
+
|
11
|
+
def app
|
12
|
+
Anmo::Application.new
|
13
|
+
end
|
data/lib/anmo.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "anmo/version"
|
2
|
+
require "anmo/application"
|
3
|
+
require "thin"
|
4
|
+
|
5
|
+
module Anmo
|
6
|
+
class << self
|
7
|
+
attr_accessor :server
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.launch_server port = 8787
|
11
|
+
Thin::Server.start("0.0.0.0", port, Anmo::Application.new)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create_request options
|
15
|
+
HTTParty.put("#{server}/__CREATE__", :body => options.to_json)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.delete_all
|
19
|
+
HTTParty.put("#{server}/__DELETE_ALL__")
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
3
|
+
module Anmo
|
4
|
+
class Application
|
5
|
+
def call env
|
6
|
+
@stored_requests ||= []
|
7
|
+
request = Rack::Request.new(env)
|
8
|
+
|
9
|
+
if request.path_info == "/__CREATE__"
|
10
|
+
process_create_request request
|
11
|
+
elsif request.path_info == "/__DELETE_ALL__"
|
12
|
+
process_delete_all_request request
|
13
|
+
else
|
14
|
+
process_normal_request request
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def process_create_request request
|
21
|
+
request_info = JSON.parse(read_request_body(request))
|
22
|
+
@stored_requests.unshift(request_info)
|
23
|
+
[201, {}, ""]
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_delete_all_request request
|
27
|
+
@stored_requests = []
|
28
|
+
[200, {}, ""]
|
29
|
+
end
|
30
|
+
|
31
|
+
def process_normal_request request
|
32
|
+
if found_request = find_stored_request(request)
|
33
|
+
[Integer(found_request["status"]||200), {"Content-Type" => "text/html"}, [found_request["body"]]]
|
34
|
+
else
|
35
|
+
[404, {"Content-Type" => "text/html"}, "Not Found"]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_stored_request actual_request
|
40
|
+
found_request = @stored_requests.find {|r| r["path"] == actual_request.path_info}
|
41
|
+
if found_request
|
42
|
+
required_headers = found_request["required_headers"] || []
|
43
|
+
required_headers.each do |name, value|
|
44
|
+
if actual_request.env[convert_header_name_to_rack_style_name(name)] != value
|
45
|
+
found_request = nil
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
found_request
|
51
|
+
end
|
52
|
+
|
53
|
+
def convert_header_name_to_rack_style_name name
|
54
|
+
name = "HTTP_#{name}"
|
55
|
+
name.gsub!("-", "_")
|
56
|
+
name.upcase!
|
57
|
+
name
|
58
|
+
end
|
59
|
+
|
60
|
+
def read_request_body request
|
61
|
+
request.body.rewind
|
62
|
+
request.body.read
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/anmo/version.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require "minitest/pride"
|
2
|
+
require "minitest/autorun"
|
3
|
+
require "minitest/unit"
|
4
|
+
require "rack/test"
|
5
|
+
require "json"
|
6
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "../../lib/anmo/application"))
|
7
|
+
|
8
|
+
module Anmo
|
9
|
+
class ApplicationTest < MiniTest::Unit::TestCase
|
10
|
+
include Rack::Test::Methods
|
11
|
+
|
12
|
+
def app
|
13
|
+
Application.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def save_object path, body, status, required_headers
|
17
|
+
options = {
|
18
|
+
:path => path,
|
19
|
+
:body => body,
|
20
|
+
:status => status,
|
21
|
+
:required_headers => required_headers
|
22
|
+
}
|
23
|
+
put "__CREATE__", options.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_404s_if_object_does_not_exist
|
27
|
+
get "/bla/bla/bla.json"
|
28
|
+
assert_equal "Not Found", last_response.body
|
29
|
+
assert last_response.not_found?
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_stores_mock_data
|
33
|
+
save_object "/this/is/the/path.object", "please save this", nil, nil
|
34
|
+
assert_equal 201, last_response.status
|
35
|
+
|
36
|
+
get "/this/is/the/path.object"
|
37
|
+
assert_equal "please save this", last_response.body
|
38
|
+
assert last_response.ok?
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_stores_status_code
|
42
|
+
save_object "/monkeys", nil, 123, nil
|
43
|
+
get "/monkeys"
|
44
|
+
assert_equal 123, last_response.status
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_allows_deleting_all_objects
|
48
|
+
save_object "/this/is/the/path.object", "please save this", nil, nil
|
49
|
+
|
50
|
+
get "/this/is/the/path.object"
|
51
|
+
first_response = last_response
|
52
|
+
|
53
|
+
put "__DELETE_ALL__"
|
54
|
+
assert_equal 200, last_response.status
|
55
|
+
|
56
|
+
get "/this/is/the/path.object"
|
57
|
+
second_response = last_response
|
58
|
+
|
59
|
+
assert_equal "please save this", first_response.body
|
60
|
+
assert_equal "Not Found", second_response.body
|
61
|
+
assert_equal 404, second_response.status
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_404s_if_request_does_not_have_required_headers
|
65
|
+
save_object "/oh/hai", nil, nil, {"ruby" => "hipsters", "meh" => "bleh"}
|
66
|
+
get "/oh/hai"
|
67
|
+
assert_equal 404, last_response.status
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_returns_value_if_request_has_required_headers
|
71
|
+
save_object "/oh/hai", "the content", nil, {"lol-ruby" => "hipsters", "meh" => "bleh"}
|
72
|
+
header "lol-ruby", "hipsters"
|
73
|
+
header "meh", "bleh"
|
74
|
+
get "/oh/hai"
|
75
|
+
assert_equal 200, last_response.status
|
76
|
+
assert_equal "the content", last_response.body
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_returns_the_last_added_object_first
|
80
|
+
save_object "/oh/hai", "the first content", nil, nil
|
81
|
+
save_object "/oh/hai", "the second content", nil, nil
|
82
|
+
get "/oh/hai"
|
83
|
+
assert_equal "the second content", last_response.body
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: anmo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Vos
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-26 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: &70320251647360 !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: *70320251647360
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: thin
|
27
|
+
requirement: &70320251646940 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70320251646940
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70320251646520 !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: *70320251646520
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: minitest
|
49
|
+
requirement: &70320251646100 !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: *70320251646100
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rack-test
|
60
|
+
requirement: &70320251645680 !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: *70320251645680
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: cucumber
|
71
|
+
requirement: &70320251645260 !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: *70320251645260
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: rspec
|
82
|
+
requirement: &70320251644840 !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: *70320251644840
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: guard
|
93
|
+
requirement: &70320251644420 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70320251644420
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: httparty
|
104
|
+
requirement: &70320251644000 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *70320251644000
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: guard-shell
|
115
|
+
requirement: &70320251643580 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: *70320251643580
|
124
|
+
description: ''
|
125
|
+
email:
|
126
|
+
- andrew.vos@gmail.com
|
127
|
+
executables: []
|
128
|
+
extensions: []
|
129
|
+
extra_rdoc_files: []
|
130
|
+
files:
|
131
|
+
- .gitignore
|
132
|
+
- Gemfile
|
133
|
+
- Guardfile
|
134
|
+
- Rakefile
|
135
|
+
- anmo.gemspec
|
136
|
+
- features/api.feature
|
137
|
+
- features/step_definitions/api_steps.rb
|
138
|
+
- features/support/env.rb
|
139
|
+
- lib/anmo.rb
|
140
|
+
- lib/anmo/application.rb
|
141
|
+
- lib/anmo/version.rb
|
142
|
+
- test/anmo/application_test.rb
|
143
|
+
homepage: ''
|
144
|
+
licenses: []
|
145
|
+
post_install_message:
|
146
|
+
rdoc_options: []
|
147
|
+
require_paths:
|
148
|
+
- lib
|
149
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ! '>='
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ! '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
requirements: []
|
162
|
+
rubyforge_project: anmo
|
163
|
+
rubygems_version: 1.8.10
|
164
|
+
signing_key:
|
165
|
+
specification_version: 3
|
166
|
+
summary: ''
|
167
|
+
test_files:
|
168
|
+
- features/api.feature
|
169
|
+
- features/step_definitions/api_steps.rb
|
170
|
+
- features/support/env.rb
|
171
|
+
- test/anmo/application_test.rb
|