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