anmo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|