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 ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ree@contracted
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in api-testing.gemspec
4
+ gemspec
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]
@@ -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
+ """
@@ -0,0 +1,6 @@
1
+ class Echo
2
+ def call env
3
+ message = Rack::Request.new(env).params["message"]
4
+ [200, {'Content-Type' => 'application/json'}, "{'message': '#{message}'}"]
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class EchoBody
2
+ def call env
3
+ [200, {'Content-Type' => 'application/json'}, Rack::Request.new(env).body]
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class HelloWorld
2
+ def call env
3
+ [200, {'Content-Type' => 'application/json'}, '{"message": "Hello World"}']
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
+ require 'contracted'
3
+ require 'contracted/cucumber'
data/lib/contracted.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'polyglot'
2
+ require 'treetop'
3
+
4
+ require 'contracted/application'
5
+ require 'contracted/json_description'
@@ -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,3 @@
1
+ module Contracted
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
+
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../features/support')
3
+
4
+ RSpec.configure do |config|
5
+ config.mock_with :rspec
6
+ end
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