contracted 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 +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README +32 -0
- data/Rakefile +18 -0
- data/contracted.gemspec +30 -0
- data/features/echo.feature +18 -0
- data/features/echo_body.feature +71 -0
- data/features/hello_world.feature +17 -0
- data/features/support/apps/echo.rb +6 -0
- data/features/support/apps/echo_body.rb +5 -0
- data/features/support/apps/hello_world.rb +5 -0
- data/features/support/env.rb +3 -0
- data/lib/contracted.rb +5 -0
- data/lib/contracted/application.rb +61 -0
- data/lib/contracted/cucumber.rb +27 -0
- data/lib/contracted/http_description.treetop +74 -0
- data/lib/contracted/json.rb +29 -0
- data/lib/contracted/json_description.treetop +128 -0
- data/lib/contracted/version.rb +3 -0
- data/spec/contracted/application_spec.rb +28 -0
- data/spec/contracted/http_description_parser_spec.rb +38 -0
- data/spec/contracted/json_description_parser_spec.rb +46 -0
- data/spec/spec_helper.rb +6 -0
- metadata +198 -0
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ree@contracted
|
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Contracted
|
2
|
+
==========================================================
|
3
|
+
Contracted provides a few tools to describe HTTP Responses
|
4
|
+
and HTTP Response bodies.
|
5
|
+
|
6
|
+
A few cucumber steps are implemented which faciliated this
|
7
|
+
description.
|
8
|
+
|
9
|
+
|
10
|
+
JSON Description Grammar
|
11
|
+
----------------------------------------------------------
|
12
|
+
The JSON Description Grammar is a superset of JSON which
|
13
|
+
adds some wildcards allowing you to describe the parts of
|
14
|
+
the response your interested in. Example:
|
15
|
+
|
16
|
+
{
|
17
|
+
"message": {
|
18
|
+
"text": ...,
|
19
|
+
...
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
This says that the 'message' object has an entry with key
|
24
|
+
'text' and any number of other entries.
|
25
|
+
|
26
|
+
The cucumber features provide more examples.
|
27
|
+
|
28
|
+
The grammar is based on the JSON parser from the following
|
29
|
+
blog post:
|
30
|
+
|
31
|
+
http://coderrr.wordpress.com/2008/02/13/treetop-is-cool/
|
32
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
require 'cucumber/rake/task'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
desc 'Runs specs'
|
8
|
+
RSpec::Core::RakeTask.new do |t|
|
9
|
+
end
|
10
|
+
|
11
|
+
Bundler::GemHelper.install_tasks
|
12
|
+
|
13
|
+
desc 'Runs features'
|
14
|
+
Cucumber::Rake::Task.new do |t|
|
15
|
+
t.cucumber_opts = %w{--format pretty}
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => [:spec, :cucumber]
|
data/contracted.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "contracted/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "contracted"
|
7
|
+
s.version = Contracted::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Andrew Kiellor"]
|
10
|
+
s.email = ["akiellor@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Contracted is a tool aimed at testing JSON API's.}
|
13
|
+
s.description = %q{
|
14
|
+
Contracted is a tool aimed at testing JSON API's.
|
15
|
+
}
|
16
|
+
|
17
|
+
s.rubyforge_project = "contracted"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
s.add_dependency('cucumber')
|
24
|
+
s.add_dependency('thin')
|
25
|
+
s.add_dependency('rest-client')
|
26
|
+
s.add_dependency('activesupport')
|
27
|
+
s.add_dependency('rspec')
|
28
|
+
s.add_dependency('treetop')
|
29
|
+
s.add_dependency('polyglot')
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: Echo app responds with message
|
2
|
+
|
3
|
+
Scenario: GET with message 'hello'
|
4
|
+
Given the "Echo" app is running
|
5
|
+
When an api client performs GET /?message=hello
|
6
|
+
Then the json response body should look like:
|
7
|
+
"""
|
8
|
+
{"message": "hello"}
|
9
|
+
"""
|
10
|
+
|
11
|
+
Scenario: GET with any message
|
12
|
+
Given the "Echo" app is running
|
13
|
+
When an api client performs GET /?message=blah
|
14
|
+
Then the json response body should look like:
|
15
|
+
"""
|
16
|
+
{"message": ...}
|
17
|
+
"""
|
18
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
Feature: EchoBody app responds with message
|
2
|
+
|
3
|
+
Scenario: POST with body '{"message": "hello"}'
|
4
|
+
Given the "EchoBody" app is running
|
5
|
+
When an api client performs POST / with json body:
|
6
|
+
"""
|
7
|
+
{'message': 'hello'}
|
8
|
+
"""
|
9
|
+
Then the json response body should look like:
|
10
|
+
"""
|
11
|
+
{"message": "hello"}
|
12
|
+
"""
|
13
|
+
|
14
|
+
Scenario: POST with body '{"message": "hello", "sender": "Tom"}'
|
15
|
+
Given the "EchoBody" app is running
|
16
|
+
When an api client performs POST / with json body:
|
17
|
+
"""
|
18
|
+
{
|
19
|
+
"message": "hello",
|
20
|
+
"sender": "Tom"
|
21
|
+
}
|
22
|
+
"""
|
23
|
+
Then the json response body should look like:
|
24
|
+
"""
|
25
|
+
{
|
26
|
+
"message": "hello",
|
27
|
+
"sender": ...
|
28
|
+
}
|
29
|
+
"""
|
30
|
+
|
31
|
+
Scenario: POST with nested object body
|
32
|
+
Given the "EchoBody" app is running
|
33
|
+
When an api client performs POST / with json body:
|
34
|
+
"""
|
35
|
+
{
|
36
|
+
"message": {
|
37
|
+
"sender": "Tom",
|
38
|
+
"text": "Hello"
|
39
|
+
}
|
40
|
+
}
|
41
|
+
"""
|
42
|
+
Then the json response body should look like:
|
43
|
+
"""
|
44
|
+
{
|
45
|
+
"message": {
|
46
|
+
"sender": "Tom",
|
47
|
+
"text": ...
|
48
|
+
}
|
49
|
+
}
|
50
|
+
"""
|
51
|
+
|
52
|
+
Scenario: POST with nested object body using wilcard match
|
53
|
+
Given the "EchoBody" app is running
|
54
|
+
When an api client performs POST / with json body:
|
55
|
+
"""
|
56
|
+
{
|
57
|
+
"message": {
|
58
|
+
"sender": "Tom",
|
59
|
+
"text": "Hello"
|
60
|
+
}
|
61
|
+
}
|
62
|
+
"""
|
63
|
+
Then the json response body should look like:
|
64
|
+
"""
|
65
|
+
{
|
66
|
+
"message": {
|
67
|
+
"sender": ...,
|
68
|
+
...
|
69
|
+
}
|
70
|
+
}
|
71
|
+
"""
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Feature: HelloWorld app says "Hello World"
|
2
|
+
|
3
|
+
Scenario: GET /
|
4
|
+
Given the "HelloWorld" app is running
|
5
|
+
When an api client performs GET /
|
6
|
+
Then the json response body should look like:
|
7
|
+
"""
|
8
|
+
{"message": "Hello World"}
|
9
|
+
"""
|
10
|
+
|
11
|
+
Scenario: GET / has message
|
12
|
+
Given the "HelloWorld" app is running
|
13
|
+
When an api client performs GET /
|
14
|
+
Then the json response body should look like:
|
15
|
+
"""
|
16
|
+
{"message": ...}
|
17
|
+
"""
|
data/lib/contracted.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'thin'
|
2
|
+
require 'rest_client'
|
3
|
+
Thin::Logging.silent = true
|
4
|
+
|
5
|
+
module Contracted
|
6
|
+
def self.mount app, port = '9898'
|
7
|
+
@app = Application.new app, port.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.app
|
11
|
+
@app
|
12
|
+
end
|
13
|
+
|
14
|
+
class Application
|
15
|
+
APPLICATION_START_TIMEOUT = 5
|
16
|
+
attr_reader :last, :app, :host, :port, :server
|
17
|
+
|
18
|
+
def initialize app, port
|
19
|
+
@app = app
|
20
|
+
@host = "0.0.0.0"
|
21
|
+
@port = port
|
22
|
+
@server = Thin::Server.new(host, port.to_s) { run app }
|
23
|
+
mount
|
24
|
+
end
|
25
|
+
|
26
|
+
def host_and_port
|
27
|
+
"#{host}:#{port}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def unmount
|
31
|
+
@server.stop
|
32
|
+
@server_thread.terminate
|
33
|
+
end
|
34
|
+
|
35
|
+
def mount
|
36
|
+
@server_thread = Thread.start do
|
37
|
+
server.start
|
38
|
+
end
|
39
|
+
Timeout.timeout(APPLICATION_START_TIMEOUT) do
|
40
|
+
loop_until { server.running? }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def get url, body, headers
|
45
|
+
@last = RestClient.get("#{host_and_port}#{url}", headers)
|
46
|
+
end
|
47
|
+
|
48
|
+
[:post, :put, :delete].each do |method|
|
49
|
+
define_method(method) do |url, body, headers|
|
50
|
+
@last = RestClient::Resource.new("#{host_and_port}#{url}", {}).send(method, body.to_s, headers)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def loop_until &block
|
57
|
+
loop { break if begin block.call rescue false end; sleep(1) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'contracted'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
After do
|
5
|
+
Contracted.app and Contracted.app.unmount
|
6
|
+
end
|
7
|
+
|
8
|
+
Given /^the "([^"]*)" app is running$/ do |app_name|
|
9
|
+
app = Kernel.const_get(app_name).new
|
10
|
+
Contracted.mount app, '9898'
|
11
|
+
end
|
12
|
+
|
13
|
+
When /^an api client performs (GET|POST|PUT|DELETE) ([^\s]+)$/ do |method, url|
|
14
|
+
Contracted.app.send(method.downcase.to_sym, url, "", {})
|
15
|
+
end
|
16
|
+
|
17
|
+
When /^an api client performs (POST|PUT) ([^\s]+) with json body:$/ do |method, url, body|
|
18
|
+
Contracted.app.send(method.downcase.to_sym, url, body, {:content_type => 'application/json'})
|
19
|
+
end
|
20
|
+
|
21
|
+
Then /^the json response should look like:$/ do |response_body_string|
|
22
|
+
Contracted::ResponseDescription.new(response_body_string).should be_description_of Contracted.app.last
|
23
|
+
end
|
24
|
+
|
25
|
+
Then /^the json response body should look like:$/ do |response_body_string|
|
26
|
+
JsonDescriptionParser.new.parse(response_body_string).value.should == ActiveSupport::JSON.decode(Contracted.app.last.body)
|
27
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
grammar HttpDescription
|
2
|
+
rule http
|
3
|
+
status_line headers new_line body
|
4
|
+
end
|
5
|
+
|
6
|
+
rule body
|
7
|
+
.*
|
8
|
+
end
|
9
|
+
|
10
|
+
rule headers
|
11
|
+
header* (wildcard new_line)? {
|
12
|
+
def values
|
13
|
+
elements.collect {|e| e.elements }.flatten.compact.select {|e| e.respond_to?(:value) }.map(&:value)
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
rule wildcard
|
19
|
+
'...' {
|
20
|
+
def value
|
21
|
+
text_value
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
rule header
|
27
|
+
header_key ':' white_space header_value new_line {
|
28
|
+
def value
|
29
|
+
{header_key.text_value => header_value.text_value}
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
rule header_key
|
35
|
+
[^:]+
|
36
|
+
end
|
37
|
+
|
38
|
+
rule header_value
|
39
|
+
[^\n]+
|
40
|
+
end
|
41
|
+
|
42
|
+
rule status_line
|
43
|
+
'HTTP/' version space status space reason new_line
|
44
|
+
end
|
45
|
+
|
46
|
+
rule version
|
47
|
+
[0-9.]+
|
48
|
+
end
|
49
|
+
|
50
|
+
rule white_space
|
51
|
+
[\s]+
|
52
|
+
end
|
53
|
+
|
54
|
+
rule space
|
55
|
+
' '
|
56
|
+
end
|
57
|
+
|
58
|
+
rule reason_word
|
59
|
+
[A-Z]+
|
60
|
+
end
|
61
|
+
|
62
|
+
rule reason
|
63
|
+
reason_word (space reason_word)*
|
64
|
+
end
|
65
|
+
|
66
|
+
rule status
|
67
|
+
[0-9] 3..3
|
68
|
+
end
|
69
|
+
|
70
|
+
rule new_line
|
71
|
+
[\n]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Contracted
|
2
|
+
module Json
|
3
|
+
class Wildcard
|
4
|
+
def self.instance
|
5
|
+
@@instance ||= Wildcard.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def == other
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Object
|
14
|
+
def initialize elements
|
15
|
+
@hash = elements.select {|e| e != Wildcard.instance }.inject({}) {|r, h| r.merge(h)}
|
16
|
+
@include = elements.include? Wildcard.instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def == other
|
20
|
+
if @include
|
21
|
+
other.merge(@hash) == other
|
22
|
+
else
|
23
|
+
@hash == other
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'contracted/json'
|
2
|
+
|
3
|
+
grammar JsonDescription
|
4
|
+
rule json
|
5
|
+
space json_value space {
|
6
|
+
def value
|
7
|
+
json_value.value
|
8
|
+
end
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
rule json_value
|
13
|
+
string / numeric / keyword / object / array / wildcard
|
14
|
+
end
|
15
|
+
|
16
|
+
rule string
|
17
|
+
'"' chars:char* '"' {
|
18
|
+
def value
|
19
|
+
chars.elements.map {|e| e.value }.join
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
rule wildcard
|
25
|
+
space '...' space {
|
26
|
+
def value
|
27
|
+
Contracted::Json::Wildcard.instance
|
28
|
+
end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
rule char
|
33
|
+
!'"' ('\\\\' ( ( [nbfrt"] / '\\\\' / '/' ) / 'u' hex hex hex hex ) / !'\\\\' .) {
|
34
|
+
def value
|
35
|
+
if text_value[0..0] == '\\\\'
|
36
|
+
case c = text_value[1..1]
|
37
|
+
when /[nbfrt]/
|
38
|
+
{'n' => "\n", 'b' => "\b", 'f' => "\f", 'r' => "\r", 't' => "\t"}[c]
|
39
|
+
when 'u'
|
40
|
+
[text_value[2,4].to_i(16)].pack("U")
|
41
|
+
else
|
42
|
+
c
|
43
|
+
end
|
44
|
+
else
|
45
|
+
text_value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
rule hex
|
52
|
+
[0-9a-fA-F]
|
53
|
+
end
|
54
|
+
|
55
|
+
rule numeric
|
56
|
+
exp / float / integer
|
57
|
+
end
|
58
|
+
|
59
|
+
rule exp
|
60
|
+
(float / integer) [eE] [+-]? unsigned_integer { def value; text_value.to_f; end }
|
61
|
+
end
|
62
|
+
|
63
|
+
rule float
|
64
|
+
integer '.' [0-9]+ { def value; text_value.to_f; end }
|
65
|
+
end
|
66
|
+
|
67
|
+
rule integer
|
68
|
+
'-'? unsigned_integer { def value; text_value.to_i; end }
|
69
|
+
end
|
70
|
+
|
71
|
+
rule unsigned_integer
|
72
|
+
('0' / [1-9] [0-9]*)
|
73
|
+
end
|
74
|
+
|
75
|
+
rule keyword
|
76
|
+
('true' / 'false' / 'null') {
|
77
|
+
def value
|
78
|
+
{ 'true' => true, 'false' => false, 'null' => nil }[text_value]
|
79
|
+
end
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
rule object
|
84
|
+
'{' space object_elements:object_element* space '}' {
|
85
|
+
def value
|
86
|
+
Contracted::Json::Object.new(object_elements.elements.map {|p| p.value })
|
87
|
+
end
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
rule object_element
|
92
|
+
e:(pair / wildcard) (',' &object_element / !object_element) {
|
93
|
+
def value
|
94
|
+
e.value
|
95
|
+
end
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
rule pair
|
100
|
+
space string space ':' space json_value space {
|
101
|
+
def value
|
102
|
+
{ string.value => json_value.value }
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
rule array
|
108
|
+
'[' space array_values:array_value* space ']' {
|
109
|
+
def value
|
110
|
+
array_values.elements.map {|e| e.value }
|
111
|
+
end
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
rule array_value
|
116
|
+
space json_value space (',' &array_value / !array_value) {
|
117
|
+
def value
|
118
|
+
json_value.value
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
rule space
|
124
|
+
[ \t\r\n]*
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'contracted/application'
|
3
|
+
|
4
|
+
describe "a rack app mounted with contracted.mount" do
|
5
|
+
before :all do @app = Contracted.mount(proc {|env| [200, {}, "body"]}, '9898') end
|
6
|
+
|
7
|
+
after :all do @app.unmount end
|
8
|
+
|
9
|
+
subject { @app }
|
10
|
+
|
11
|
+
its(:host_and_port) { should == '0.0.0.0:9898' }
|
12
|
+
|
13
|
+
it "should be able to perform GET operations" do
|
14
|
+
@app.get('/', '', {}).should == 'body'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "a rack app mounted with contracted.mount that returns a non 200 series status code" do
|
19
|
+
it "should timeout after waiting 5 seconds" do
|
20
|
+
Thin::Server.stub(:new).and_return(mock(:server, :running? => false))
|
21
|
+
|
22
|
+
Timeout.timeout(6) do
|
23
|
+
proc do
|
24
|
+
Contracted.mount(proc {|env| [500, {}, "body"]}, '9898')
|
25
|
+
end.should raise_error(Timeout::Error)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
require 'contracted/http_description'
|
4
|
+
|
5
|
+
describe HttpDescriptionParser do
|
6
|
+
let(:description) { HttpDescriptionParser.new.parse(description_string) }
|
7
|
+
|
8
|
+
context "no headers as part of description" do
|
9
|
+
let(:description_string) { "HTTP/1.1 200 OK\n\n{'message': 'hello'}" }
|
10
|
+
|
11
|
+
describe "description" do
|
12
|
+
subject { description }
|
13
|
+
|
14
|
+
its(:headers) { subject.values.should == [] }
|
15
|
+
its(:headers) { subject.text_value.should == '' }
|
16
|
+
its(:body) { subject.text_value.should == "{'message': 'hello'}" }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "status line" do
|
20
|
+
subject { description.status_line }
|
21
|
+
|
22
|
+
its(:status) { subject.text_value.should == "200" }
|
23
|
+
its(:version) { subject.text_value.should == "1.1" }
|
24
|
+
its(:reason) { subject.text_value.should == "OK" }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "wildcard header in description" do
|
29
|
+
let(:description_string) { "HTTP/1.1 200 OK\nContent-Type: application/json\n...\n\n{'message': 'hello'}" }
|
30
|
+
|
31
|
+
describe "description" do
|
32
|
+
subject { description }
|
33
|
+
|
34
|
+
its(:headers) { subject.values.should == [{'Content-Type' => 'application/json'}, '...'] }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'contracted'
|
3
|
+
|
4
|
+
describe JsonDescriptionParser do
|
5
|
+
let(:parser) { JsonDescriptionParser.new }
|
6
|
+
let(:description) { parser.parse(description_string) }
|
7
|
+
|
8
|
+
subject { description.value }
|
9
|
+
|
10
|
+
context "a standard json representation" do
|
11
|
+
let(:description_string) { '{"message": "hello"}' }
|
12
|
+
|
13
|
+
describe "the description" do
|
14
|
+
it { should == json('{"message": "hello"}') }
|
15
|
+
it { should_not == json('{"message": "goodbye"}') }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with a key wildcard" do
|
20
|
+
let(:description_string) { '{"message": "hello", ...}' }
|
21
|
+
|
22
|
+
describe "the description" do
|
23
|
+
it { should == json('{"message": "hello"}') }
|
24
|
+
it { should == json('{"message": "hello", "type": "greeting"}') }
|
25
|
+
it { should_not == json('{"message": "goodbye"}') }
|
26
|
+
it { should_not == json('{"type": "greeting"}') }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with a wildcard value" do
|
31
|
+
let(:description_string) { '{"message": ..., "type": "greeting" }' }
|
32
|
+
|
33
|
+
describe "the description" do
|
34
|
+
it { should == json('{"message": "hello", "type": "greeting" }') }
|
35
|
+
it { should == json('{"message": "goodbye", "type": "greeting" }') }
|
36
|
+
it { should_not == json('{"msg": "hello", "type": "greeting" }') }
|
37
|
+
it { should_not == json('{"message": "hello"}') }
|
38
|
+
it { should_not == json('{"type": "greeting"}') }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def json json_string
|
43
|
+
ActiveSupport::JSON.decode json_string
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: contracted
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrew Kiellor
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-31 00:00:00 +10:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: cucumber
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: thin
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rest-client
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: activesupport
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :runtime
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: rspec
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
type: :runtime
|
90
|
+
version_requirements: *id005
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: treetop
|
93
|
+
prerelease: false
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
type: :runtime
|
104
|
+
version_requirements: *id006
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: polyglot
|
107
|
+
prerelease: false
|
108
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
hash: 3
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
type: :runtime
|
118
|
+
version_requirements: *id007
|
119
|
+
description: "\n Contracted is a tool aimed at testing JSON API's.\n "
|
120
|
+
email:
|
121
|
+
- akiellor@gmail.com
|
122
|
+
executables: []
|
123
|
+
|
124
|
+
extensions: []
|
125
|
+
|
126
|
+
extra_rdoc_files: []
|
127
|
+
|
128
|
+
files:
|
129
|
+
- .gitignore
|
130
|
+
- .rvmrc
|
131
|
+
- Gemfile
|
132
|
+
- README
|
133
|
+
- Rakefile
|
134
|
+
- contracted.gemspec
|
135
|
+
- features/echo.feature
|
136
|
+
- features/echo_body.feature
|
137
|
+
- features/hello_world.feature
|
138
|
+
- features/support/apps/echo.rb
|
139
|
+
- features/support/apps/echo_body.rb
|
140
|
+
- features/support/apps/hello_world.rb
|
141
|
+
- features/support/env.rb
|
142
|
+
- lib/contracted.rb
|
143
|
+
- lib/contracted/application.rb
|
144
|
+
- lib/contracted/cucumber.rb
|
145
|
+
- lib/contracted/http_description.treetop
|
146
|
+
- lib/contracted/json.rb
|
147
|
+
- lib/contracted/json_description.treetop
|
148
|
+
- lib/contracted/version.rb
|
149
|
+
- spec/contracted/application_spec.rb
|
150
|
+
- spec/contracted/http_description_parser_spec.rb
|
151
|
+
- spec/contracted/json_description_parser_spec.rb
|
152
|
+
- spec/spec_helper.rb
|
153
|
+
has_rdoc: true
|
154
|
+
homepage: ""
|
155
|
+
licenses: []
|
156
|
+
|
157
|
+
post_install_message:
|
158
|
+
rdoc_options: []
|
159
|
+
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
none: false
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
hash: 3
|
168
|
+
segments:
|
169
|
+
- 0
|
170
|
+
version: "0"
|
171
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
+
none: false
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
hash: 3
|
177
|
+
segments:
|
178
|
+
- 0
|
179
|
+
version: "0"
|
180
|
+
requirements: []
|
181
|
+
|
182
|
+
rubyforge_project: contracted
|
183
|
+
rubygems_version: 1.4.2
|
184
|
+
signing_key:
|
185
|
+
specification_version: 3
|
186
|
+
summary: Contracted is a tool aimed at testing JSON API's.
|
187
|
+
test_files:
|
188
|
+
- features/echo.feature
|
189
|
+
- features/echo_body.feature
|
190
|
+
- features/hello_world.feature
|
191
|
+
- features/support/apps/echo.rb
|
192
|
+
- features/support/apps/echo_body.rb
|
193
|
+
- features/support/apps/hello_world.rb
|
194
|
+
- features/support/env.rb
|
195
|
+
- spec/contracted/application_spec.rb
|
196
|
+
- spec/contracted/http_description_parser_spec.rb
|
197
|
+
- spec/contracted/json_description_parser_spec.rb
|
198
|
+
- spec/spec_helper.rb
|