Skip to content

Commit aecc501

Browse files
committed
run "functional" tests against a fake API server
1 parent 2485472 commit aecc501

4 files changed

Lines changed: 238 additions & 1 deletion

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#
2+
# Author:: Daniel DeLeo (<dan@opscode.com>)
3+
# Copyright:: Copyright (c) 2010 Opscode, Inc.
4+
# License:: Apache License, Version 2.0
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
20+
require File.expand_path(File.dirname(__FILE__) + '/../tiny_server')
21+
22+
describe TinyServer::API do
23+
before do
24+
@api = TinyServer::API.instance
25+
@api.clear
26+
end
27+
28+
it "is a Singleton" do
29+
lambda {TinyServer::API.new}.should raise_error
30+
end
31+
32+
it "clears the router" do
33+
@api.get('/blargh', 200, "blargh")
34+
@api.clear
35+
@api.api_data["GET"].should be_empty
36+
end
37+
38+
it "creates a route for a GET request" do
39+
@api.get('/foo/bar', 200, 'hello foobar')
40+
response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/foo/bar')
41+
response.should == [200, {'Content-Type' => 'application/json'}, 'hello foobar']
42+
end
43+
44+
it "creates a route for a request with a block" do
45+
block_called = false
46+
@api.get('/bar/baz', 200) { block_called = true; 'hello barbaz' }
47+
response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/bar/baz')
48+
response.should == [200, {'Content-Type' => 'application/json'}, 'hello barbaz']
49+
block_called.should be_true
50+
end
51+
52+
it "returns debugging info for 404s" do
53+
response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/no_such_thing')
54+
response[0].should == 404
55+
response[1].should == {'Content-Type' => 'application/json'}
56+
response[2].should be_a_kind_of(String)
57+
response_obj = JSON.parse(response[2])
58+
response_obj["message"].should == "no data matches the request for /no_such_thing"
59+
response_obj["available_data"].should == {"GET"=>[], "PUT"=>[], "POST"=>[], "DELETE"=>[]}
60+
response_obj["request"].should == {"REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"/no_such_thing"}
61+
end
62+
63+
end
64+
65+
describe TinyServer::Manager do
66+
it "runs the server" do
67+
@server = TinyServer::Manager.new
68+
@server.start
69+
70+
TinyServer::API.instance.get("/index", 200, "[\"hello\"]")
71+
72+
rest = Chef::REST.new('http://localhost:9000', false, false)
73+
rest.get_rest("index").should == ["hello"]
74+
75+
@server.stop
76+
end
77+
end

‎chef/spec/spec_helper.rb‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module Shef
2525
require 'rubygems'
2626

2727
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
28+
$:.unshift(File.dirname(__FILE__))
2829

2930
require 'chef'
3031
require 'chef/knife'

‎chef/spec/tiny_server.rb‎

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#
2+
# Author:: Daniel DeLeo (<dan@opscode.com>)
3+
# Copyright:: Copyright (c) 2010 Opscode, Inc.
4+
# License:: Apache License, Version 2.0
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
require 'rubygems'
20+
require 'rack'
21+
require 'thin'
22+
require 'singleton'
23+
require 'json'
24+
require 'open-uri'
25+
26+
module TinyServer
27+
28+
class Server < Rack::Server
29+
30+
attr_writer :app
31+
32+
def self.run(options=nil, &block)
33+
tiny_app = new(options)
34+
app_code = Rack::Builder.new(&block).to_app
35+
tiny_app.app = app_code
36+
tiny_app.start
37+
end
38+
end
39+
40+
class Manager
41+
42+
DEFAULT_OPTIONS = {:server => 'thin', :Port => 9000, :Host => 'localhost', :environment => :none}
43+
44+
def initialize(options=nil)
45+
@options = options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
46+
@creator = caller.first
47+
end
48+
49+
def start
50+
@server_thread = Thread.new do
51+
@server = Server.run(@options) do
52+
run API.instance
53+
end
54+
end
55+
block_until_started
56+
end
57+
58+
def url
59+
"http://localhost:#{@options[:Port]}"
60+
end
61+
62+
def block_until_started
63+
20.times do
64+
return true if started?
65+
end
66+
raise "TinyServer failed to boot :/"
67+
end
68+
69+
def started?
70+
open(url)
71+
true
72+
rescue OpenURI::HTTPError
73+
true
74+
rescue Errno::ECONNREFUSED => e
75+
sleep 0.1
76+
false
77+
end
78+
79+
def stop
80+
# yes, this is terrible.
81+
@server_thread.kill
82+
@server_thread.join
83+
@server_thread = nil
84+
end
85+
86+
end
87+
88+
class API
89+
include Singleton
90+
91+
GET = "GET"
92+
PUT = "PUT"
93+
POST = "POST"
94+
DELETE = "DELETE"
95+
96+
attr_reader :api_data
97+
98+
def initialize
99+
clear
100+
end
101+
102+
def clear
103+
@api_data = {GET => [], PUT => [], POST => [], DELETE => []}
104+
end
105+
106+
def get(path, response_code, data=nil, &block)
107+
@api_data[GET] << [path, Response.new(response_code,data, &block)]
108+
end
109+
110+
def put(path, response_code, data=nil, &block)
111+
@api_data[PUT] << [path, Response.new(response_code,data, &block)]
112+
end
113+
114+
def post(path, response_code, data=nil, &block)
115+
@api_data[POST] << [path, Response.new(response_code,data, &block)]
116+
end
117+
118+
def delete(path, response_code, data=nil, &block)
119+
@api_data[DELETE] << [path, Response.new(response_code,data, &block)]
120+
end
121+
122+
def call(env)
123+
if response = response_for_request(env)
124+
response.call
125+
else
126+
debug_info = {:message => "no data matches the request for #{env['REQUEST_URI']}",
127+
:available_data => @api_data, :request => env}
128+
[404, {'Content-Type' => 'application/json'}, debug_info.to_json]
129+
end
130+
end
131+
132+
def response_for_request(env)
133+
if route_tuple = @api_data[env["REQUEST_METHOD"]].find { |route, response| route === env["REQUEST_URI"] }
134+
route_tuple[1]
135+
end
136+
end
137+
end
138+
139+
class Response
140+
HEADERS = {'Content-Type' => 'application/json'}
141+
142+
def initialize(response_code=200,data=nil, &block)
143+
@response_code, @data = response_code, data
144+
@block = block_given? ? block : nil
145+
end
146+
147+
def call
148+
data = @data || @block.call
149+
[@response_code, HEADERS, data]
150+
end
151+
end
152+
153+
end

‎chef/tasks/rspec.rb‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@
3030
desc "Run all specs in spec directory"
3131
Spec::Rake::SpecTask.new(:spec) do |t|
3232
t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
33-
t.spec_files = FileList['spec/**/*_spec.rb']
33+
t.spec_files = FileList['spec/unit/**/*_spec.rb']
34+
end
35+
36+
desc "Run all functional specs (in functional/ directory)"
37+
Spec::Rake::SpecTask.new(:functional) do |t|
38+
t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
39+
t.spec_files = FileList['spec/functional/**/*_spec.rb']
3440
end
3541

3642
namespace :spec do

0 commit comments

Comments
 (0)