├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE
├── README.markdown
├── Rakefile
├── bin
└── stubb
├── lib
├── stubb.rb
└── stubb
│ ├── combined_logger.rb
│ ├── counter.rb
│ ├── finder.rb
│ ├── match_finder.rb
│ ├── naive_finder.rb
│ ├── request.rb
│ ├── response.rb
│ ├── sequence_finder.rb
│ └── sequence_match_finder.rb
├── stubb.gemspec
├── stubb.png
└── test
├── fixtures
├── collection
│ ├── GET
│ ├── GET.json
│ ├── POST
│ ├── member.GET
│ ├── member.GET.json
│ ├── member.PUT.json
│ ├── member_template.GET
│ └── member_template.POST
├── looping_sequence
│ ├── GET.0
│ ├── GET.1
│ ├── GET.2
│ ├── member.GET.0
│ ├── member.GET.1
│ └── member.GET.2
├── matching
│ ├── _wildcard_collection_
│ │ ├── GET
│ │ ├── GET.json
│ │ ├── static.GET
│ │ ├── static.GET.json
│ │ ├── template.GET
│ │ └── template.POST
│ ├── collection
│ │ ├── _wildcard_member_.GET
│ │ └── _wildcard_member_.GET.json
│ └── sequences
│ │ └── _wildcard_
│ │ ├── looping
│ │ ├── GET.0
│ │ ├── GET.1
│ │ ├── GET.2
│ │ ├── member.GET.0
│ │ ├── member.GET.1
│ │ └── member.GET.2
│ │ └── stalling
│ │ ├── GET.1
│ │ ├── GET.2
│ │ ├── GET.3
│ │ ├── member.GET.1
│ │ ├── member.GET.2
│ │ ├── member.GET.3
│ │ ├── template.GET.1
│ │ └── template.POST.1
├── stalling_sequence
│ ├── GET.1
│ ├── GET.2
│ ├── GET.3
│ ├── member.GET.1
│ ├── member.GET.2
│ ├── member.GET.3
│ ├── template.GET.1
│ └── template.POST.1
└── users
│ └── :id
│ └── photos
│ └── :photo_id.GET.json
├── test_counter.rb
├── test_match_finder.rb
├── test_naive_finder.rb
├── test_request.rb
├── test_response.rb
├── test_sequence_finder.rb
└── test_sequence_match_finder.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 | .DS_Store
3 | responses/*
4 | resources/*
5 | info/*
6 | .rvmrc
7 | .rbenv-version
8 | *.gem
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 1.8.7
4 | - 1.9.3
5 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source :rubygems
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Johannes Emerich
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Stubb is a testing and development tool for frontend developers and anyone else depending on HTTP-requesting resources for their work. It allows **setting up a REST API stub by putting responses in files** organized in a directory tree. Which file is picked in response to a particular HTTP request is primarily determined by the request's **method**, **path** and **accept header**. Thus adding a response for a certain type of request is as easy as adding a file with a matching name. For example, the file
4 |
5 | whales/narwhal.GET.json
6 |
7 | in your base directory will be picked to deliver the response to the request
8 |
9 | GET /whales/narwhal.json HTTP/1.1
10 |
11 | or alternatively
12 |
13 | GET /whales/narwhal HTTP/1.1
14 | Accept: application/json
15 |
16 | .
17 |
18 | Additionally, **sequences of responses** to repeated identical requests can be defined through infix numerals in file names.
19 |
20 | Getting Started
21 | ---------------
22 |
23 | Simply install the Stubb gem by running
24 |
25 | gem install stubb
26 |
27 | and you are ready to run the stubb CLI:
28 |
29 | $ stubb
30 | Tasks:
31 | stubb help [TASK] # Describe available tasks or one specific task
32 | stubb server # Starts the server
33 | stubb version # Print the version of Stubb
34 |
35 | $ echo Ahoy > hello-world.GET
36 | $ ls
37 | hello-world.GET
38 | $ stubb server &
39 | $ curl http://localhost:4040/hello-world
40 | Ahoy
41 |
42 | By default the server runs on port 4040 and looks for response files in the working directory. Run `stubb help server` for information on configuration options.
43 |
44 | Directory Structure and Response Files
45 | --------------------------------------
46 |
47 | All requests are served from the *base directory*, that is the directory Stubb was started from. The directory tree in your base directory determines the path hierarchy of your stubbed REST API. Request paths are mapped to relative paths within the base directory to locate a response file.
48 |
49 | ### Response Files
50 |
51 | A *response file* is a file containing an API response. There are two kinds of response files, member response files and collection response files. They differ only in concept and naming.
52 |
53 | #### Member Response Files
54 |
55 | A *member response file* is a file containing an API response for a member resource, named after the scheme
56 |
57 | REQUEST_PATH_WITHOUT_EXTENSION.HTTP_METHOD[.SEQUENCE_NUMBER][.FILE_TYPE]
58 |
59 | , where `SEQUENCE_NUMBER` is optional and only needed when defining response sequences, and `FILE_TYPE` is also optional and only needed when a file type is implied by request path or accept header.
60 |
61 | Examples:
62 |
63 | whales/narwhal.GET
64 | whales/narwhal.GET.json
65 | whales/narwhal.GET.1
66 | whales/narwhal.GET.1.json
67 |
68 | #### Collection Response Files
69 |
70 | A *collection response file* is a file containing an API response for a collection resource, named after the scheme
71 |
72 | REQUEST_PATH_WITHOUT_EXTENSION/HTTP_METHOD[.SEQUENCE_NUMBER][.FILE_TYPE]
73 |
74 | , where `SEQUENCE_NUMBER` is optional and only needed when defining response sequences, and `FILE_TYPE` is also optional and only needed when a file type is implied by request path or accept header.
75 |
76 | Examples:
77 |
78 | whales/GET
79 | whales/GET.json
80 | whales/GET.1
81 | whales/GET.1.json
82 |
83 | ### Response Files as ERB Templates
84 |
85 | Any matching response file will be evaluated as an ERB template, with `GET` or `POST` parameters available in a `params` hash. This can come in handy when stubbing `POST` and `PUT` requests or serving JSONP.
86 |
87 | ### YAML Frontmatter
88 |
89 | Response files may contain YAML frontmatter before the response text, allowing to set custom values for response status and header:
90 |
91 | ---
92 | status: 201
93 | header:
94 | Cache-Control: no-cache
95 | ---
96 | {"name":"Stubb"}
97 |
98 | ### Missing Responses
99 |
100 | If no matching response file is found, Stubb replies with a status of `404`. You can customize error responses for types of requests by creating a matching response file that contains your custom response.
101 |
102 | Path Matching
103 | -------------
104 |
105 | Paths in the base directory may include wildcards to allow one response file to match for a whole range of request paths instead of just one. Both Directory names and file names may be wildcards. Wildcards are marked by starting and ending in an underscore (`_`). A wildcard segment matches any equally positioned segment of a request path.
106 |
107 | For example
108 |
109 | whales/_default_whale_.GET.json
110 |
111 | matches
112 |
113 | GET /whales/pygmy_sperm_whale.json HTTP/1.1
114 |
115 | as well as
116 |
117 | GET /whales/blackfish.json HTTP/1.1
118 |
119 | .
120 |
121 | If a literal match exists, it will be chosen over a wildcard match.
122 |
123 | Response Sequences
124 | ------------------
125 |
126 | A *response sequence* is a sequence of response files whose members are being used as responses to a sequence of requests of the same type. This allows for controlled stubbing of changes in the API.
127 |
128 | ### Stalling Sequences (1..._n_)
129 |
130 | A *stalling sequence* keeps responding with the last response file in the response sequence after _n_ requests of the same type. Stalling sequences are specified by adding response files with indices 1 through _n_.
131 |
132 | GET.1.format, GET.2.format, ..., GET._n-1_.format, GET._n_.format
133 |
134 | Example:
135 |
136 | whales/GET.1.json
137 | whales/GET.2.json
138 | whales/GET.3.json
139 |
140 | From the third request on, the response to
141 |
142 | GET /whales.json HTTP/1.1
143 |
144 | will be the one given in `whales/GET.3.json`.
145 |
146 | ### Looping Sequences (0..._n-1_)
147 |
148 | A *looping sequence* starts from the first response file in the response sequence after _n_ requests of the same type. Looping sequences are specified by adding response files with indices 0 through _n_-1.
149 |
150 | GET.0.format, GET.1.format, ..., GET._n-2_.format, GET._n-1_.format
151 |
152 | Example:
153 |
154 | whales/GET.0.json
155 | whales/GET.1.json
156 | whales/GET.2.json
157 |
158 | The response to the fourth request to
159 |
160 | GET /whales.json HTTP/1.1
161 |
162 | will again be the one given in `whales/GET.0.json`, and so forth.
163 |
164 | Dependencies
165 | ------------
166 |
167 | Stubb depends on
168 |
169 | - Rack for processing and serving requests, and
170 | - Thor for adding a CLI executable.
171 |
172 | Acknowledgements
173 | ----------------
174 |
175 | The logo for Stubb was kindly provided by Andres Colmenares of [Wawawiwa](https://www.facebook.com/pages/Wawawiwa-design/201009879921770).
176 |
177 | License
178 | -------
179 |
180 | Copyright (c) 2011 Johannes Emerich
181 |
182 | MIT-style licensing, for details see file `LICENSE`.
183 |
184 |
185 |
186 | [](https://travis-ci.org/knuton/stubb)
187 |
188 | _'Why,' thinks I, 'what's the row? It's not a real leg, only a false leg.'_
189 | --Stubb in _Moby Dick_
190 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler/setup'
3 | require 'rake/testtask'
4 |
5 | Bundler::GemHelper.install_tasks
6 |
7 | Rake::TestTask.new(:test) do |test|
8 | test.pattern = 'test/**/test_*.rb'
9 | test.verbose = true
10 | end
11 |
12 | task :default => [:test]
13 |
--------------------------------------------------------------------------------
/bin/stubb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4 |
5 | require 'thor'
6 | require 'stubb'
7 |
8 | module Stubb
9 | class CLI < Thor
10 | map '-v' => :version
11 |
12 | desc 'server', 'Starts the server'
13 | method_option :port, :type => :numeric, :default => 4040, :aliases => '-p', :desc => 'Specifies the port for the server to use'
14 | method_option :root, :type => :string, :default => '', :aliases => '-r', :desc => 'Specifies the root directory to serve from'
15 | method_option :verbose, :type => :boolean, :aliases => '-v', :desc => 'Print debug messages'
16 | def server
17 | Stubb.run :Port => options[:port], :root => options[:root], :verbose => options[:verbose]
18 | end
19 |
20 | desc 'version', 'Print the version of Stubb'
21 | def version
22 | puts Stubb::VERSION
23 | end
24 | end
25 | end
26 |
27 | Stubb::CLI.start
28 |
--------------------------------------------------------------------------------
/lib/stubb.rb:
--------------------------------------------------------------------------------
1 | require 'rack'
2 |
3 | module Stubb
4 |
5 | VERSION = '0.2.0'
6 |
7 | class ResponseNotFound < Exception
8 | end
9 |
10 | @config = {
11 | :matcher_pattern => '_*_'
12 | }
13 |
14 | def self.method_missing(m, *attrs)
15 | # Ease access to @config
16 | if @config[m.to_sym]
17 | @config[m.to_sym]
18 | elsif @config[m.to_s.chomp('=').to_sym]
19 | @config[m.to_s.chomp('=').to_sym] = attrs[0]
20 | else
21 | super
22 | end
23 | end
24 |
25 | def self.app(options = {})
26 | Rack::Builder.new {
27 | use CombinedLogger
28 | use Counter
29 |
30 | run Rack::Cascade.new [
31 | SequenceFinder.new(options),
32 | NaiveFinder.new(options),
33 | SequenceMatchFinder.new(options),
34 | MatchFinder.new(options)
35 | ]
36 | }.to_app
37 | end
38 |
39 | def self.run(options = {})
40 | Rack::Handler.default.run(
41 | app({:root => ''}.update(options)),
42 | {:Port => 4040}.update(options)
43 | )
44 | end
45 |
46 | end
47 |
48 | require 'stubb/request'
49 | require 'stubb/response'
50 | require 'stubb/counter'
51 | require 'stubb/combined_logger'
52 | require 'stubb/finder'
53 | require 'stubb/naive_finder'
54 | require 'stubb/sequence_finder'
55 | require 'stubb/match_finder'
56 | require 'stubb/sequence_match_finder'
57 |
--------------------------------------------------------------------------------
/lib/stubb/combined_logger.rb:
--------------------------------------------------------------------------------
1 | module Stubb
2 |
3 | class CombinedLogger < Rack::CommonLogger
4 | FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f "%s" "%s"\n}
5 |
6 | def log(env, status, header, began_at)
7 | now = Time.now
8 | length = extract_content_length(header)
9 |
10 | logger = @logger || env['rack.errors']
11 | logger.write FORMAT % [
12 | env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'] || '-',
13 | env['REMOTE_USER'] || '-',
14 | now.strftime('%d/%b/%Y %H:%M:%S'),
15 | env['REQUEST_METHOD'],
16 | env['PATH_INFO'],
17 | env['QUERY_STRING'].empty? ? '' : '?'+env['QUERY_STRING'],
18 | env['HTTP_VERSION'],
19 | status.to_s[0..3],
20 | length,
21 | now - began_at,
22 | header['X-Stubb-Response-File'] || 'None',
23 | "YAML Frontmatter: #{header['X-Stubb-Frontmatter'] || 'No'}"
24 | ]
25 |
26 | end
27 |
28 | end
29 |
30 | end
31 |
--------------------------------------------------------------------------------
/lib/stubb/counter.rb:
--------------------------------------------------------------------------------
1 | module Stubb
2 |
3 | class Counter
4 |
5 | def initialize(app)
6 | @app = app
7 | @request_history = {}
8 | trap(:INT) { |signal| reset_or_quit signal }
9 | end
10 |
11 | def call(env)
12 | env['stubb.request_sequence_index'] = count(env['REQUEST_METHOD'], env['PATH_INFO'], env['HTTP_ACCEPT'])
13 | @app.call(env)
14 | end
15 |
16 | private
17 | def count(method, path, accept)
18 | fingerprint = "#{method}-#{path}-#{accept}"
19 | @request_history[fingerprint] = (@request_history[fingerprint] || 0) + 1
20 | end
21 |
22 | def reset_or_quit(signal)
23 | if @request_history.empty?
24 | exit! signal
25 | else
26 | @request_history.clear
27 | puts "\n\nReset request history. Interrupt again to quit.\n\n"
28 | end
29 | end
30 |
31 | end
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/lib/stubb/finder.rb:
--------------------------------------------------------------------------------
1 | module Stubb
2 |
3 | class NotFound < Exception; end
4 |
5 | class Finder
6 |
7 | attr_accessor :request, :root
8 |
9 | def initialize(options = {})
10 | @root = File.expand_path options[:root] || ''
11 | @verbose = options[:verbose] || false
12 | end
13 |
14 | def call(env)
15 | @request = Request.new env
16 |
17 | respond
18 |
19 | rescue Errno::ENOENT, Errno::ELOOP
20 | [404, {"Content-Type" => "text/plain"}, ["Not found."]]
21 | rescue Exception => e
22 | debug e.message, e.backtrace.join("\n")
23 | [500, {'Content-Type' => 'text/plain'}, ['Internal server error.']]
24 | end
25 |
26 | private
27 | def respond
28 | response_file_path = projected_path
29 | response_body = File.open(response_file_path, 'r') {|f| f.read }
30 | Response.new(
31 | response_body,
32 | request.params,
33 | 200,
34 | {'Content-Type' => content_type, 'X-Stubb-Response-File' => response_file_path}
35 | ).finish
36 | rescue NotFound => e
37 | debug e.message
38 | [404, {}, [e.message]]
39 | end
40 |
41 | def request_options_as_file_ending
42 | "#{request.request_method}#{request.extension}"
43 | end
44 |
45 | def exists?(relative_path)
46 | File.exists? local_path_for(relative_path)
47 | end
48 |
49 | def local_path_for(relative_path)
50 | File.join root, relative_path
51 | end
52 |
53 | def glob(pattern)
54 | Dir.glob(pattern).sort
55 | end
56 |
57 | def content_type
58 | Rack::Mime.mime_type(request.extension) || "text/html"
59 | end
60 |
61 | def debug(*messages)
62 | log(*messages) if @verbose
63 | end
64 |
65 | def log(*messages)
66 | if request.env['rack.errors'] && request.env['rack.errors'].respond_to?('write')
67 | request.env['rack.errors'].write messages.join(" ") << "\n"
68 | else
69 | puts messages.join(" ")
70 | end
71 | end
72 |
73 | end
74 |
75 | end
76 |
--------------------------------------------------------------------------------
/lib/stubb/match_finder.rb:
--------------------------------------------------------------------------------
1 | module Stubb
2 |
3 | class NoMatch < NotFound; end
4 |
5 | class MatchFinder < Finder
6 | private
7 | def projected_path
8 | built_path = []
9 | last_is_dir = false
10 | request.path_parts.each_with_index do |level, index|
11 | if match = literal_directory(built_path, level)
12 | last_is_dir = true
13 | elsif match = literal_file(built_path, level)
14 | last_is_dir = false
15 | elsif match = matching_directory(built_path)
16 | last_is_dir = true
17 | elsif match = matching_file(built_path)
18 | last_is_dir = false
19 | else
20 | raise NoMatch.new("Not found.")
21 | end
22 |
23 | built_path << match
24 | end
25 |
26 | if last_is_dir
27 | File.join local_path_for(built_path), request_options_as_file_ending
28 | else
29 | local_path_for built_path
30 | end
31 | end
32 |
33 | def literal_directory(current_path, level)
34 | File.directory?(local_path_for(current_path + [level])) ? level: nil
35 | end
36 |
37 | def literal_file(current_path, level)
38 | parts = level.split('.')
39 | level = parts.size > 1 ? parts[0..-2].join('.') : parts.first
40 | filename = "#{level}.#{request_options_as_file_ending}"
41 | File.exists?(local_path_for(current_path + [filename])) ? filename : nil
42 | end
43 |
44 | def matching_directory(current_path)
45 | matches = glob local_path_for(current_path + [Stubb.matcher_pattern])
46 | for match in matches
47 | continue unless File.directory? match
48 | return File.split(match).last
49 | end
50 | nil
51 | end
52 |
53 | def matching_file(current_path)
54 | matches = glob local_path_for(current_path + ["#{Stubb.matcher_pattern}.#{request_options_as_file_ending}"])
55 |
56 | matches.empty? ? nil : File.split(matches.first).last
57 | end
58 |
59 | end
60 |
61 |
62 | end
63 |
--------------------------------------------------------------------------------
/lib/stubb/naive_finder.rb:
--------------------------------------------------------------------------------
1 | module Stubb
2 |
3 | class NaiveFinder < Finder
4 |
5 | private
6 | def projected_path
7 | relative_path = if File.directory? local_path_for(request.resource_path)
8 | File.join request.resource_path, request_options_as_file_ending
9 | else
10 | "#{request.resource_path}.#{request_options_as_file_ending}"
11 | end
12 |
13 | local_path_for relative_path
14 | end
15 |
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/lib/stubb/request.rb:
--------------------------------------------------------------------------------
1 | module Stubb
2 |
3 | class Request < Rack::Request
4 |
5 | def path_parts
6 | relative_path.empty? ? [''] : relative_path.split('/')
7 | end
8 |
9 | def path_dir_parts
10 | parts = path_parts
11 | parts.size > 1 ? parts[0..-2] : []
12 | end
13 |
14 | def file_name
15 | path_parts.last
16 | end
17 |
18 | def resource_name
19 | parts = file_name.split('.')
20 | parts.size > 1 ? parts[0..-2].join('.') : parts.first
21 | end
22 |
23 | def resource_path
24 | File.join((path_dir_parts << resource_name).compact)
25 | end
26 |
27 | def extension
28 | extension_by_path.empty? ? extension_by_header : extension_by_path
29 | end
30 |
31 | def extension_by_path
32 | File.extname(relative_path)
33 | end
34 |
35 | def extension_by_header
36 | Rack::Mime::MIME_TYPES.invert[accept]
37 | end
38 |
39 | # TODO parse, sort
40 | def accept
41 | @env['HTTP_ACCEPT'].to_s.split(',').first
42 | end
43 |
44 | def relative_path
45 | # Strip slashes at string end and start
46 | path_info.gsub /(\A\/|\/\Z)/, ''
47 | end
48 |
49 | def sequence_index
50 | @env['stubb.request_sequence_index'] || 1
51 | end
52 |
53 | end
54 |
55 | end
56 |
--------------------------------------------------------------------------------
/lib/stubb/response.rb:
--------------------------------------------------------------------------------
1 | require 'erb'
2 | require 'yaml'
3 |
4 | module Stubb
5 |
6 | class Response < Rack::Response
7 |
8 | attr_accessor :body, :params, :status, :header
9 |
10 | def initialize(body=[], params={}, status=200, header={})
11 | @body = body
12 | @params = params
13 | @status = status
14 | @header = header
15 |
16 | process_yaml
17 | render_template
18 |
19 | super self.body, self.status, self.header
20 | end
21 |
22 | private
23 | def process_yaml
24 | if self.body =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
25 | self.body = self.body[($1.size + $2.size)..-1]
26 | begin
27 | data = YAML.load($1)
28 |
29 | # Use specified HTTP status
30 | self.status = data['status'] if data['status']
31 | # Fill header information
32 | data['header'].each { |field, value| self.header[field] = value } if data['header'].kind_of? Hash
33 | self.header['X-Stubb-Frontmatter'] = 'Yes'
34 | rescue
35 | self.header['X-Stubb-Frontmatter'] = 'Error'
36 | end
37 | else
38 | self.header['X-Stubb-Frontmatter'] = 'No'
39 | end
40 | end
41 |
42 | def render_template
43 | erb = ERB.new @body
44 | @body = erb.result binding
45 | end
46 |
47 | end
48 |
49 | end
50 |
--------------------------------------------------------------------------------
/lib/stubb/sequence_finder.rb:
--------------------------------------------------------------------------------
1 | module Stubb
2 |
3 | class NoSuchSequence < NotFound; end
4 |
5 | class SequenceFinder < Finder
6 | private
7 | def projected_path
8 | sequence_members = glob local_path_for(sequenced_path_pattern)
9 | raise NoSuchSequence.new("Nothing found for sequence pattern `#{sequenced_path_pattern}`.") if sequence_members.empty?
10 |
11 | loop? ? pick_loop_member(sequence_members) : pick_stall_member(sequence_members)
12 | end
13 |
14 | def pick_loop_member(sequence_members)
15 | sequence_members[(request.sequence_index - 1) % sequence_members.size]
16 | end
17 |
18 | def pick_stall_member(sequence_members)
19 | request.sequence_index > sequence_members.size ? sequence_members.last : sequence_members[request.sequence_index - 1]
20 | end
21 |
22 | def sequenced_path(index)
23 | if File.directory? local_path_for(request.relative_path)
24 | File.join request.relative_path, request_options_as_file_ending(index)
25 | else
26 | "#{request.relative_path}.#{request_options_as_file_ending(index)}"
27 | end
28 | end
29 |
30 | def sequenced_path_pattern
31 | sequenced_path('[0-9]')
32 | end
33 |
34 | def request_options_as_file_ending(index)
35 | "#{request.request_method}.#{index}#{request.extension}"
36 | end
37 |
38 | def loop?
39 | exists? sequenced_path(0)
40 | end
41 |
42 | end
43 |
44 |
45 | end
46 |
--------------------------------------------------------------------------------
/lib/stubb/sequence_match_finder.rb:
--------------------------------------------------------------------------------
1 | module Stubb
2 |
3 | class SequenceMatchFinder < SequenceFinder
4 | private
5 | def sequenced_path(sequence_index)
6 | built_path = []
7 | last_is_dir = false
8 | request.path_parts.each_with_index do |level, index|
9 | if match = literal_directory(built_path, level)
10 | last_is_dir = true
11 | elsif match = literal_file(built_path, level, sequence_index)
12 | last_is_dir = false
13 | elsif match = matching_directory(built_path)
14 | last_is_dir = true
15 | elsif match = matching_file(built_path, sequence_index)
16 | last_is_dir = false
17 | else
18 | return 'NOT FOUND'
19 | end
20 |
21 | built_path << match
22 | end
23 |
24 | if last_is_dir
25 | File.join built_path, request_options_as_file_ending(sequence_index)
26 | else
27 | File.join built_path
28 | end
29 | end
30 |
31 | def literal_directory(current_path, level)
32 | File.directory?(local_path_for(current_path + [level])) ? level : nil
33 | end
34 |
35 | def literal_file(current_path, level, index)
36 | filename = "#{level}.#{request_options_as_file_ending(index)}"
37 | sequence_members = glob local_path_for(current_path + [filename])
38 | sequence_members.empty? ? nil : filename
39 | end
40 |
41 | def matching_directory(current_path)
42 | matches = glob local_path_for(current_path + [Stubb.matcher_pattern])
43 | for match in matches
44 | continue unless File.directory? match
45 | return File.split(match).last
46 | end
47 | nil
48 | end
49 |
50 | def matching_file(current_path, index)
51 | matches = glob local_path_for(current_path + ["#{Stubb.matcher_pattern}.#{request_options_as_file_ending(index)}"])
52 |
53 | matches.empty? ? nil : File.split(matches.first).last
54 | end
55 |
56 | end
57 |
58 | end
59 |
--------------------------------------------------------------------------------
/stubb.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.name = 'stubb'
3 | s.version = '0.2.0'
4 | s.date = '2014-10-03'
5 | s.summary = 'Specify REST API stubs using your file system'
6 | s.description = 'Specify REST API stubs using your file system'
7 | s.authors = ['Johannes Emerich']
8 | s.email = 'johannes@emerich.de'
9 | s.homepage = 'http://github.com/knuton/stubb'
10 | s.files = [
11 | 'lib/stubb.rb',
12 | 'lib/stubb/request.rb',
13 | 'lib/stubb/response.rb',
14 | 'lib/stubb/counter.rb',
15 | 'lib/stubb/combined_logger.rb',
16 | 'lib/stubb/finder.rb',
17 | 'lib/stubb/naive_finder.rb',
18 | 'lib/stubb/sequence_finder.rb',
19 | 'lib/stubb/match_finder.rb',
20 | 'lib/stubb/sequence_match_finder.rb',
21 | 'bin/stubb',
22 | 'LICENSE',
23 | 'README.markdown'
24 | ]
25 | s.executables = ['stubb']
26 |
27 | s.add_development_dependency 'rake'
28 |
29 | s.add_runtime_dependency 'rack', '>=1.2.0'
30 | s.add_runtime_dependency 'thor'
31 |
32 | end
33 |
--------------------------------------------------------------------------------
/stubb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/knuton/stubb/88c90d83ba5acf380e443e6f463d5c4b2000f8d6/stubb.png
--------------------------------------------------------------------------------
/test/fixtures/collection/GET:
--------------------------------------------------------------------------------
1 | GET collection
--------------------------------------------------------------------------------
/test/fixtures/collection/GET.json:
--------------------------------------------------------------------------------
1 | GET collection.json
--------------------------------------------------------------------------------
/test/fixtures/collection/POST:
--------------------------------------------------------------------------------
1 | POST collection
--------------------------------------------------------------------------------
/test/fixtures/collection/member.GET:
--------------------------------------------------------------------------------
1 | GET member
--------------------------------------------------------------------------------
/test/fixtures/collection/member.GET.json:
--------------------------------------------------------------------------------
1 | GET member.json
--------------------------------------------------------------------------------
/test/fixtures/collection/member.PUT.json:
--------------------------------------------------------------------------------
1 | PUT member
--------------------------------------------------------------------------------
/test/fixtures/collection/member_template.GET:
--------------------------------------------------------------------------------
1 | GET <%= params['name'] %>
--------------------------------------------------------------------------------
/test/fixtures/collection/member_template.POST:
--------------------------------------------------------------------------------
1 | POST <%= params['name'] %>
--------------------------------------------------------------------------------
/test/fixtures/looping_sequence/GET.0:
--------------------------------------------------------------------------------
1 | GET collection 0
--------------------------------------------------------------------------------
/test/fixtures/looping_sequence/GET.1:
--------------------------------------------------------------------------------
1 | GET collection 1
--------------------------------------------------------------------------------
/test/fixtures/looping_sequence/GET.2:
--------------------------------------------------------------------------------
1 | GET collection 2
--------------------------------------------------------------------------------
/test/fixtures/looping_sequence/member.GET.0:
--------------------------------------------------------------------------------
1 | GET member 0
--------------------------------------------------------------------------------
/test/fixtures/looping_sequence/member.GET.1:
--------------------------------------------------------------------------------
1 | GET member 1
--------------------------------------------------------------------------------
/test/fixtures/looping_sequence/member.GET.2:
--------------------------------------------------------------------------------
1 | GET member 2
--------------------------------------------------------------------------------
/test/fixtures/matching/_wildcard_collection_/GET:
--------------------------------------------------------------------------------
1 | GET matching collection
--------------------------------------------------------------------------------
/test/fixtures/matching/_wildcard_collection_/GET.json:
--------------------------------------------------------------------------------
1 | GET matching collection.json
--------------------------------------------------------------------------------
/test/fixtures/matching/_wildcard_collection_/static.GET:
--------------------------------------------------------------------------------
1 | GET static
--------------------------------------------------------------------------------
/test/fixtures/matching/_wildcard_collection_/static.GET.json:
--------------------------------------------------------------------------------
1 | GET static.json
--------------------------------------------------------------------------------
/test/fixtures/matching/_wildcard_collection_/template.GET:
--------------------------------------------------------------------------------
1 | GET matching <%= params['name'] %>
--------------------------------------------------------------------------------
/test/fixtures/matching/_wildcard_collection_/template.POST:
--------------------------------------------------------------------------------
1 | POST matching <%= params['name'] %>
--------------------------------------------------------------------------------
/test/fixtures/matching/collection/_wildcard_member_.GET:
--------------------------------------------------------------------------------
1 | GET matching member
--------------------------------------------------------------------------------
/test/fixtures/matching/collection/_wildcard_member_.GET.json:
--------------------------------------------------------------------------------
1 | GET matching member.json
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/looping/GET.0:
--------------------------------------------------------------------------------
1 | GET matching collection 0
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/looping/GET.1:
--------------------------------------------------------------------------------
1 | GET matching collection 1
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/looping/GET.2:
--------------------------------------------------------------------------------
1 | GET matching collection 2
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/looping/member.GET.0:
--------------------------------------------------------------------------------
1 | GET matching member 0
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/looping/member.GET.1:
--------------------------------------------------------------------------------
1 | GET matching member 1
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/looping/member.GET.2:
--------------------------------------------------------------------------------
1 | GET matching member 2
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/stalling/GET.1:
--------------------------------------------------------------------------------
1 | GET matching collection 1
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/stalling/GET.2:
--------------------------------------------------------------------------------
1 | GET matching collection 2
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/stalling/GET.3:
--------------------------------------------------------------------------------
1 | GET matching collection 3
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/stalling/member.GET.1:
--------------------------------------------------------------------------------
1 | GET matching member 1
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/stalling/member.GET.2:
--------------------------------------------------------------------------------
1 | GET matching member 2
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/stalling/member.GET.3:
--------------------------------------------------------------------------------
1 | GET matching member 3
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/stalling/template.GET.1:
--------------------------------------------------------------------------------
1 | GET matching <%= params['name'] %> 1
--------------------------------------------------------------------------------
/test/fixtures/matching/sequences/_wildcard_/stalling/template.POST.1:
--------------------------------------------------------------------------------
1 | POST matching <%= params['name'] %> 1
--------------------------------------------------------------------------------
/test/fixtures/stalling_sequence/GET.1:
--------------------------------------------------------------------------------
1 | GET collection 1
--------------------------------------------------------------------------------
/test/fixtures/stalling_sequence/GET.2:
--------------------------------------------------------------------------------
1 | GET collection 2
--------------------------------------------------------------------------------
/test/fixtures/stalling_sequence/GET.3:
--------------------------------------------------------------------------------
1 | GET collection 3
--------------------------------------------------------------------------------
/test/fixtures/stalling_sequence/member.GET.1:
--------------------------------------------------------------------------------
1 | GET member 1
--------------------------------------------------------------------------------
/test/fixtures/stalling_sequence/member.GET.2:
--------------------------------------------------------------------------------
1 | GET member 2
--------------------------------------------------------------------------------
/test/fixtures/stalling_sequence/member.GET.3:
--------------------------------------------------------------------------------
1 | GET member 3
--------------------------------------------------------------------------------
/test/fixtures/stalling_sequence/template.GET.1:
--------------------------------------------------------------------------------
1 | GET <%= params['name'] %> 1
--------------------------------------------------------------------------------
/test/fixtures/stalling_sequence/template.POST.1:
--------------------------------------------------------------------------------
1 | POST <%= params['name'] %> 1
--------------------------------------------------------------------------------
/test/fixtures/users/:id/photos/:photo_id.GET.json:
--------------------------------------------------------------------------------
1 | {'id':'nested_member'}
2 |
--------------------------------------------------------------------------------
/test/test_counter.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | require 'stubb'
4 |
5 | class TestCounter < Test::Unit::TestCase
6 |
7 | def setup
8 | @env = Rack::MockRequest.env_for '/request/path'
9 | @counter = Stubb::Counter.new lambda { |env| [200, {}, [env['stubb.request_sequence_index']]] }
10 | end
11 |
12 | def test_initial_request
13 | result = @counter.call(@env)
14 | assert_equal 1, @env['stubb.request_sequence_index']
15 | assert_equal result.last.last, @env['stubb.request_sequence_index']
16 | end
17 |
18 | def test_repeated_request
19 | @counter.call(@env)
20 | result = @counter.call(@env)
21 | assert_equal 2, result.last.last
22 | end
23 |
24 | def test_side_effect_on_different_path
25 | @counter.call(@env)
26 | result = @counter.call Rack::MockRequest.env_for('/request/other')
27 | assert_equal 1, result.last.last
28 | end
29 |
30 | def test_side_effect_on_different_method
31 | @counter.call(@env)
32 | result = @counter.call Rack::MockRequest.env_for('/request/path', 'REQUEST_METHOD' => 'POST')
33 | assert_equal 1, result.last.last
34 | end
35 |
36 | def test_side_effect_on_different_accept_header
37 | @counter.call(@env)
38 | result = @counter.call Rack::MockRequest.env_for('/request/path', 'HTTP_ACCEPT' => 'application/json')
39 | assert_equal 1, result.last.last
40 | end
41 |
42 | end
--------------------------------------------------------------------------------
/test/test_match_finder.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | require 'stubb'
4 |
5 | class TestMatchFinder < Test::Unit::TestCase
6 |
7 | def setup
8 | @finder = Stubb::MatchFinder.new :root => 'test/fixtures'
9 | end
10 |
11 | def test_trailing_matching_collection
12 | response = @finder.call Rack::MockRequest.env_for('/matching/dynamic', 'REQUEST_METHOD' => 'GET')
13 | assert_equal 200, response.first
14 | assert_equal ['GET matching collection'], response.last.body
15 | end
16 |
17 | def test_trailing_matching_collection_as_json_explicitly
18 | response = @finder.call Rack::MockRequest.env_for('/matching/dynamic.json', 'REQUEST_METHOD' => 'GET')
19 | assert_equal 200, response.first
20 | assert_equal ['GET matching collection.json'], response.last.body
21 | assert_equal 'application/json', response[1]['Content-Type']
22 | end
23 |
24 | def test_trailing_matching_collection_as_json_implicitly
25 | response = @finder.call Rack::MockRequest.env_for('/matching/dynamic', 'REQUEST_METHOD' => 'GET', 'HTTP_ACCEPT' => 'application/json')
26 | assert_equal 200, response.first
27 | assert_equal ['GET matching collection.json'], response.last.body
28 | assert_equal 'application/json', response[1]['Content-Type']
29 | end
30 |
31 | def test_embedded_matching_collection
32 | response = @finder.call Rack::MockRequest.env_for('/matching/dynamic/static', 'REQUEST_METHOD' => 'GET')
33 | assert_equal 200, response.first
34 | assert_equal ['GET static'], response.last.body
35 | end
36 |
37 | def test_embedded_matching_collection_as_json_explicitly
38 | response = @finder.call Rack::MockRequest.env_for('/matching/dynamic/static.json', 'REQUEST_METHOD' => 'GET')
39 | assert_equal 200, response.first
40 | assert_equal ['GET static.json'], response.last.body
41 | assert_equal 'application/json', response[1]['Content-Type']
42 | end
43 |
44 | def test_trailing_matching_collection_as_json_implicitly
45 | response = @finder.call Rack::MockRequest.env_for('/matching/dynamic/static', 'REQUEST_METHOD' => 'GET', 'HTTP_ACCEPT' => 'application/json')
46 | assert_equal 200, response.first
47 | assert_equal ['GET static.json'], response.last.body
48 | assert_equal 'application/json', response[1]['Content-Type']
49 | end
50 |
51 | def test_trailing_matching_member
52 | response = @finder.call Rack::MockRequest.env_for('/matching/collection/dynamic', 'REQUEST_METHOD' => 'GET')
53 | assert_equal 200, response.first
54 | assert_equal ['GET matching member'], response.last.body
55 | end
56 |
57 | def test_trailing_matching_member_as_json_explicitly
58 | response = @finder.call Rack::MockRequest.env_for('/matching/collection/dynamic.json', 'REQUEST_METHOD' => 'GET')
59 | assert_equal 200, response.first
60 | assert_equal ['GET matching member.json'], response.last.body
61 | assert_equal 'application/json', response[1]['Content-Type']
62 | end
63 |
64 | def test_trailing_matching_member_as_json_implicitly
65 | response = @finder.call Rack::MockRequest.env_for('/matching/collection/dynamic.json', 'REQUEST_METHOD' => 'GET', 'HTTP_ACCEPT' => 'application/json')
66 | assert_equal 200, response.first
67 | assert_equal ['GET matching member.json'], response.last.body
68 | assert_equal 'application/json', response[1]['Content-Type']
69 | end
70 |
71 | def test_get_matching_member_template
72 | response = @finder.call Rack::MockRequest.env_for('/matching/dynamic/template?name=Karl', 'REQUEST_METHOD' => 'GET')
73 | assert_equal 200, response.first
74 | assert_equal ['GET matching Karl'], response.last.body
75 | end
76 |
77 | def test_post_matching_member_template
78 | response = @finder.call Rack::MockRequest.env_for('/matching/dynamic/template', 'REQUEST_METHOD' => 'POST', :input => 'name=Karl')
79 | assert_equal 200, response.first
80 | assert_equal ['POST matching Karl'], response.last.body
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/test/test_naive_finder.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | require 'stubb'
4 |
5 | class TestNaiveFinder < Test::Unit::TestCase
6 |
7 | def setup
8 | @finder = Stubb::NaiveFinder.new :root => 'test/fixtures'
9 | end
10 |
11 | def test_get_collection
12 | response = @finder.call Rack::MockRequest.env_for('/collection', 'REQUEST_METHOD' => 'GET')
13 | assert_equal 200, response.first
14 | assert_equal ['GET collection'], response.last.body
15 | end
16 |
17 | def test_get_collection_as_root
18 | @finder = Stubb::NaiveFinder.new :root => 'test/fixtures/collection'
19 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET')
20 | assert_equal 200, response.first
21 | assert_equal ['GET collection'], response.last.body
22 | end
23 |
24 | def test_get_collection_as_json_explicitly
25 | response = @finder.call Rack::MockRequest.env_for('/collection.json', 'REQUEST_METHOD' => 'GET')
26 | assert_equal 200, response.first
27 | assert_equal ['GET collection.json'], response.last.body
28 | assert_equal 'application/json', response[1]['Content-Type']
29 | end
30 |
31 | def test_get_collection_as_json_implicitly
32 | response = @finder.call Rack::MockRequest.env_for('/collection', 'REQUEST_METHOD' => 'GET', 'HTTP_ACCEPT' => 'application/json, text/html')
33 | assert_equal 200, response.first
34 | assert_equal ['GET collection.json'], response.last.body
35 | assert_equal 'application/json', response[1]['Content-Type']
36 | end
37 |
38 | def test_post_collection
39 | response = @finder.call Rack::MockRequest.env_for('/collection', 'REQUEST_METHOD' => 'POST')
40 | assert_equal 200, response.first
41 | assert_equal ['POST collection'], response.last.body
42 | end
43 |
44 | def test_get_member
45 | response = @finder.call Rack::MockRequest.env_for('/collection/member', 'REQUEST_METHOD' => 'GET')
46 | assert_equal 200, response.first
47 | assert_equal ['GET member'], response.last.body
48 | end
49 |
50 | def test_get_member_as_json_explicitly
51 | response = @finder.call Rack::MockRequest.env_for('/collection/member.json', 'REQUEST_METHOD' => 'GET')
52 | assert_equal 200, response.first
53 | assert_equal ['GET member.json'], response.last.body
54 | assert_equal 'application/json', response[1]['Content-Type']
55 | end
56 |
57 | def test_get_member_as_json_implicitly
58 | response = @finder.call Rack::MockRequest.env_for('/collection/member', 'REQUEST_METHOD' => 'GET', 'HTTP_ACCEPT' => 'application/json')
59 | assert_equal 200, response.first
60 | assert_equal ['GET member.json'], response.last.body
61 | assert_equal 'application/json', response[1]['Content-Type']
62 | end
63 |
64 | def test_get_member_template
65 | response = @finder.call Rack::MockRequest.env_for('/collection/member_template?name=Karl', 'REQUEST_METHOD' => 'GET')
66 | assert_equal 200, response.first
67 | assert_equal ['GET Karl'], response.last.body
68 | end
69 |
70 | def test_post_member_template
71 | response = @finder.call Rack::MockRequest.env_for('/collection/member_template', 'REQUEST_METHOD' => 'POST', :input => 'name=Karl')
72 | assert_equal 200, response.first
73 | assert_equal ['POST Karl'], response.last.body
74 | end
75 |
76 | end
77 |
78 |
--------------------------------------------------------------------------------
/test/test_request.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | require 'stubb'
4 |
5 | class TestRequest < Test::Unit::TestCase
6 |
7 | def setup
8 | @request = Stubb::Request.new Rack::MockRequest.env_for('/test/the/functionality.html.erb')
9 | end
10 |
11 | def test_path_parts
12 | assert_equal ['test', 'the', 'functionality.html.erb'], @request.path_parts
13 | end
14 |
15 | def test_path_dir_parts
16 | assert_equal ['test', 'the'], @request.path_dir_parts
17 | end
18 |
19 | def test_file_name
20 | assert_equal 'functionality.html.erb', @request.file_name
21 | end
22 |
23 | def test_resource_name
24 | assert_equal 'functionality.html', @request.resource_name
25 | end
26 |
27 | def test_extension
28 | assert_equal '.erb', @request.extension
29 | end
30 |
31 | def test_relative_path
32 | assert_equal 'test/the/functionality.html.erb', @request.relative_path
33 | end
34 |
35 | end
36 |
37 |
--------------------------------------------------------------------------------
/test/test_response.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | require 'stubb'
4 |
5 | class TestResponse < Test::Unit::TestCase
6 |
7 | def test_yaml_frontmatter
8 | response = Stubb::Response.new(
9 | "---\nstatus: 201\nheader:\n Foo: Baz\n---\nBody",
10 | {},
11 | 200,
12 | {'Foo' => 'Bar'}
13 | ).finish
14 | assert_equal ['Body'], response.last.body
15 | assert_equal 'Baz', response[1]['Foo']
16 | end
17 |
18 | def test_templating
19 | response = Stubb::Response.new(
20 | "<%= params['foo'] %>",
21 | {'foo' => 'Bar'},
22 | 200,
23 | {}
24 | ).finish
25 | assert_equal ['Bar'], response.last.body
26 | end
27 |
28 | end
29 |
30 |
--------------------------------------------------------------------------------
/test/test_sequence_finder.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | require 'stubb'
4 |
5 | class TestSequenceFinder < Test::Unit::TestCase
6 |
7 | def setup
8 | @finder = Stubb::SequenceFinder.new :root => 'test/fixtures'
9 | end
10 |
11 | def test_get_stalling_collection
12 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
13 | assert_equal ['GET collection 1'], response.last.body
14 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
15 | assert_equal ['GET collection 2'], response.last.body
16 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
17 | assert_equal ['GET collection 3'], response.last.body
18 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
19 | assert_equal ['GET collection 3'], response.last.body
20 | end
21 |
22 | def test_get_stalling_collection_as_root
23 | @finder = Stubb::SequenceFinder.new :root => 'test/fixtures/stalling_sequence'
24 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
25 | assert_equal ['GET collection 1'], response.last.body
26 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
27 | assert_equal ['GET collection 2'], response.last.body
28 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
29 | assert_equal ['GET collection 3'], response.last.body
30 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
31 | assert_equal ['GET collection 3'], response.last.body
32 | end
33 |
34 | def test_get_stalling_member
35 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
36 | assert_equal ['GET member 1'], response.last.body
37 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
38 | assert_equal ['GET member 2'], response.last.body
39 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
40 | assert_equal ['GET member 3'], response.last.body
41 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
42 | assert_equal ['GET member 3'], response.last.body
43 | end
44 |
45 | def test_get_looping_collection
46 | response = @finder.call Rack::MockRequest.env_for('/looping_sequence', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
47 | assert_equal ['GET collection 0'], response.last.body
48 | response = @finder.call Rack::MockRequest.env_for('/looping_sequence', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
49 | assert_equal ['GET collection 1'], response.last.body
50 | response = @finder.call Rack::MockRequest.env_for('/looping_sequence', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
51 | assert_equal ['GET collection 2'], response.last.body
52 | response = @finder.call Rack::MockRequest.env_for('/looping_sequence', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
53 | assert_equal ['GET collection 0'], response.last.body
54 | end
55 |
56 | def test_get_looping_collection_as_root
57 | @finder = Stubb::SequenceFinder.new :root => 'test/fixtures/looping_sequence'
58 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
59 | assert_equal ['GET collection 0'], response.last.body
60 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
61 | assert_equal ['GET collection 1'], response.last.body
62 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
63 | assert_equal ['GET collection 2'], response.last.body
64 | response = @finder.call Rack::MockRequest.env_for('/', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
65 | assert_equal ['GET collection 0'], response.last.body
66 | end
67 |
68 | def test_get_looping_member
69 | response = @finder.call Rack::MockRequest.env_for('/looping_sequence/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
70 | assert_equal ['GET member 0'], response.last.body
71 | response = @finder.call Rack::MockRequest.env_for('/looping_sequence/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
72 | assert_equal ['GET member 1'], response.last.body
73 | response = @finder.call Rack::MockRequest.env_for('/looping_sequence/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
74 | assert_equal ['GET member 2'], response.last.body
75 | response = @finder.call Rack::MockRequest.env_for('/looping_sequence/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
76 | assert_equal ['GET member 0'], response.last.body
77 | end
78 |
79 | def test_get_template_sequence
80 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence/template?name=Karl', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
81 | assert_equal 200, response.first
82 | assert_equal ['GET Karl 1'], response.last.body
83 | end
84 |
85 | def test_post_template_sequence
86 | response = @finder.call Rack::MockRequest.env_for('/stalling_sequence/template', 'REQUEST_METHOD' => 'POST', 'stubb.request_sequence_index' => 1, :input => 'name=Karl')
87 | assert_equal 200, response.first
88 | assert_equal ['POST Karl 1'], response.last.body
89 | end
90 |
91 | end
92 |
93 |
--------------------------------------------------------------------------------
/test/test_sequence_match_finder.rb:
--------------------------------------------------------------------------------
1 | require 'test/unit'
2 |
3 | require 'stubb'
4 |
5 | class TestSequenceMatchFinder < Test::Unit::TestCase
6 |
7 | def setup
8 | @finder = Stubb::SequenceMatchFinder.new :root => 'test/fixtures'
9 | end
10 |
11 | def test_get_matched_stalling_collection
12 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
13 | assert_equal ['GET matching collection 1'], response.last.body
14 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
15 | assert_equal ['GET matching collection 2'], response.last.body
16 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
17 | assert_equal ['GET matching collection 3'], response.last.body
18 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
19 | assert_equal ['GET matching collection 3'], response.last.body
20 | end
21 |
22 | def test_get_matched_stalling_member
23 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
24 | assert_equal ['GET matching member 1'], response.last.body
25 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
26 | assert_equal ['GET matching member 2'], response.last.body
27 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
28 | assert_equal ['GET matching member 3'], response.last.body
29 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
30 | assert_equal ['GET matching member 3'], response.last.body
31 | end
32 |
33 | def test_get_matched_looping_collection
34 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/looping', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
35 | assert_equal ['GET matching collection 0'], response.last.body
36 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/looping', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
37 | assert_equal ['GET matching collection 1'], response.last.body
38 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/looping', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
39 | assert_equal ['GET matching collection 2'], response.last.body
40 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/looping', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
41 | assert_equal ['GET matching collection 0'], response.last.body
42 | end
43 |
44 | def test_get_matched_looping_member
45 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/looping/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
46 | assert_equal ['GET matching member 0'], response.last.body
47 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/looping/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 2)
48 | assert_equal ['GET matching member 1'], response.last.body
49 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/looping/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 3)
50 | assert_equal ['GET matching member 2'], response.last.body
51 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/looping/member', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 4)
52 | assert_equal ['GET matching member 0'], response.last.body
53 | end
54 |
55 | def test_get_matched_template_sequence
56 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling/template?name=Karl', 'REQUEST_METHOD' => 'GET', 'stubb.request_sequence_index' => 1)
57 | assert_equal 200, response.first
58 | assert_equal ['GET matching Karl 1'], response.last.body
59 | end
60 |
61 | def test_post_matched_template_sequence
62 | response = @finder.call Rack::MockRequest.env_for('/matching/sequences/dynamic/stalling/template', 'REQUEST_METHOD' => 'POST', 'stubb.request_sequence_index' => 1, :input => 'name=Karl')
63 | assert_equal 200, response.first
64 | assert_equal ['POST matching Karl 1'], response.last.body
65 | end
66 |
67 | end
68 |
69 |
--------------------------------------------------------------------------------