contracts_api_test 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +20 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/Guardfile +8 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +117 -0
  7. data/Rakefile +7 -0
  8. data/TODO.md +19 -0
  9. data/contracts.gemspec +29 -0
  10. data/lib/contracts.rb +46 -0
  11. data/lib/contracts/contract.rb +18 -0
  12. data/lib/contracts/extensions.rb +18 -0
  13. data/lib/contracts/instantiated_contract.rb +63 -0
  14. data/lib/contracts/rake_task.rb +75 -0
  15. data/lib/contracts/request.rb +62 -0
  16. data/lib/contracts/response.rb +27 -0
  17. data/lib/contracts/response_adapter.rb +24 -0
  18. data/lib/contracts/version.rb +3 -0
  19. data/lib/json-generator.rb +1 -0
  20. data/lib/json/generator.rb +18 -0
  21. data/lib/json/generator/array_attribute.rb +11 -0
  22. data/lib/json/generator/attribute_factory.rb +18 -0
  23. data/lib/json/generator/basic_attribute.rb +17 -0
  24. data/lib/json/generator/boolean_attribute.rb +7 -0
  25. data/lib/json/generator/dereferencer.rb +22 -0
  26. data/lib/json/generator/empty_attribute.rb +7 -0
  27. data/lib/json/generator/integer_attribute.rb +7 -0
  28. data/lib/json/generator/object_attribute.rb +18 -0
  29. data/lib/json/generator/string_attribute.rb +7 -0
  30. data/spec/contracts/contract_spec.rb +50 -0
  31. data/spec/contracts/contracts_spec.rb +77 -0
  32. data/spec/contracts/extensions_spec.rb +34 -0
  33. data/spec/contracts/instantiated_contract_spec.rb +224 -0
  34. data/spec/contracts/request_spec.rb +73 -0
  35. data/spec/contracts/response_adapter_spec.rb +27 -0
  36. data/spec/contracts/response_spec.rb +114 -0
  37. data/spec/data/contract.json +25 -0
  38. data/spec/json/generator/array_attribute_spec.rb +42 -0
  39. data/spec/json/generator/attribute_factory_spec.rb +72 -0
  40. data/spec/json/generator/basic_attribute_spec.rb +41 -0
  41. data/spec/json/generator/boolean_attribute_spec.rb +17 -0
  42. data/spec/json/generator/dereferencer_spec.rb +72 -0
  43. data/spec/json/generator/empty_attribute_spec.rb +17 -0
  44. data/spec/json/generator/integer_attribute_spec.rb +17 -0
  45. data/spec/json/generator/object_attribute_spec.rb +100 -0
  46. data/spec/json/generator/string_attribute_spec.rb +17 -0
  47. data/spec/json/generator_spec.rb +20 -0
  48. data/spec/spec_helper.rb +1 -0
  49. metadata +259 -0
@@ -0,0 +1,73 @@
1
+ module Contracts
2
+ describe Request do
3
+ let(:host) { 'http://localhost' }
4
+ let(:method) { 'GET' }
5
+ let(:path) { '/hello_world' }
6
+ let(:headers) { {'accept' => 'application/json'} }
7
+ let(:params) { {'foo' => 'bar'} }
8
+
9
+ let(:request) do
10
+ described_class.new(host, {
11
+ 'method' => method,
12
+ 'path' => path,
13
+ 'headers' => headers,
14
+ 'params' => params
15
+ })
16
+ end
17
+ subject { request }
18
+
19
+ its(:method) { should == :get }
20
+ its(:path) { should == path }
21
+ its(:headers) { should == headers }
22
+ its(:params) { should == params }
23
+
24
+ describe '#execute' do
25
+ let(:connection) { double('connection') }
26
+ let(:response) { double('response') }
27
+ let(:adapted_response) { double('adapted response') }
28
+
29
+ context 'for a GET request' do
30
+ it 'should make a GET request and return the response' do
31
+ HTTParty.should_receive(:get).
32
+ with(host + path, {:query => params, :headers => headers}).
33
+ and_return(response)
34
+ ResponseAdapter.should_receive(:new).with(response).and_return(adapted_response)
35
+ request.execute.should == adapted_response
36
+ end
37
+ end
38
+
39
+ context 'for a POST request' do
40
+ let(:method) { 'POST' }
41
+
42
+ it 'should make a POST request and return the response' do
43
+ HTTParty.should_receive(:post).
44
+ with(host + path, {:body => params.to_json, :headers => headers}).
45
+ and_return(response)
46
+ ResponseAdapter.should_receive(:new).with(response).and_return(adapted_response)
47
+ request.execute.should == adapted_response
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "#absolute_uri" do
53
+ it "should be equal to the host followed by the path" do
54
+ request.absolute_uri.should == "http://localhost/hello_world"
55
+ end
56
+ end
57
+
58
+ describe "#full_uri" do
59
+ context "when the query (params) exists" do
60
+ it "should be equal to the host followed by the path and the query" do
61
+ request.full_uri.should == "http://localhost/hello_world?foo=bar"
62
+ end
63
+ end
64
+
65
+ context "when the query (params) does not exists" do
66
+ let(:params) { {} }
67
+ it "should be equal to the host followed by the path" do
68
+ request.full_uri.should == "http://localhost/hello_world"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,27 @@
1
+ module Contracts
2
+ describe ResponseAdapter do
3
+ let(:response) do
4
+ double({
5
+ :code => 200,
6
+ :headers => {'foo' => ['bar', 'baz'], 'hello' => ['world']},
7
+ :body => double('body')
8
+ })
9
+ end
10
+
11
+ before do
12
+ @response_adapter = described_class.new(response)
13
+ end
14
+
15
+ it 'should have a status' do
16
+ @response_adapter.status.should == response.code
17
+ end
18
+
19
+ it 'should have a body' do
20
+ @response_adapter.body.should == response.body
21
+ end
22
+
23
+ it 'should normalize headers values according to RFC2616' do
24
+ @response_adapter.headers.should == {'foo' => 'bar,baz', 'hello' => 'world'}
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,114 @@
1
+ module Contracts
2
+ describe Response do
3
+ let(:definition) do
4
+ {
5
+ 'status' => 200,
6
+ 'headers' => {'Content-Type' => 'application/json'},
7
+ 'body' => double('definition body')
8
+ }
9
+ end
10
+
11
+ describe '#instantiate' do
12
+ let(:generated_body) { double('generated body') }
13
+
14
+ it 'should instantiate a response with a body that matches the given definition' do
15
+ JSON::Generator.should_receive(:generate).
16
+ with(definition['body']).
17
+ and_return(generated_body)
18
+
19
+ response = described_class.new(definition).instantiate
20
+ response.status.should == definition['status']
21
+ response.headers.should == definition['headers']
22
+ response.body.should == generated_body
23
+ end
24
+ end
25
+
26
+ describe '#validate' do
27
+ let(:status) { 200 }
28
+ let(:headers) { {'Content-Type' => 'application/json', 'Age' => '60'} }
29
+ let(:fake_response) do
30
+ double({
31
+ :status => status,
32
+ :headers => headers,
33
+ :body => 'body'
34
+ })
35
+ end
36
+
37
+ context 'when status, headers and body match' do
38
+ it 'should not return any errors' do
39
+ JSON::Validator.should_receive(:fully_validate).
40
+ with(definition['body'], fake_response.body).
41
+ and_return([])
42
+
43
+ response = described_class.new(definition)
44
+ response.validate(fake_response).should == []
45
+ end
46
+ end
47
+ context 'when status, headers and body match' do
48
+ it 'should not return any errors' do
49
+ JSON::Validator.should_receive(:fully_validate).
50
+ with(definition['body'], fake_response.body).
51
+ and_return([])
52
+
53
+ response = described_class.new(definition)
54
+ response.validate(fake_response).should == []
55
+ end
56
+ end
57
+
58
+ context 'when status does not match' do
59
+ let(:status) { 500 }
60
+
61
+ it 'should return a status error' do
62
+ JSON::Validator.stub!(:fully_validate).and_return([])
63
+
64
+ response = described_class.new(definition)
65
+ response.validate(fake_response).should == ["Invalid status: expected #{definition['status']} but got #{status}"]
66
+ end
67
+ end
68
+
69
+ context 'when headers do not match' do
70
+ let(:headers) { {'Content-Type' => 'text/html'} }
71
+
72
+ it 'should return a header error' do
73
+ JSON::Validator.stub!(:fully_validate).and_return([])
74
+
75
+ response = described_class.new(definition)
76
+ response.validate(fake_response).should == ["Invalid headers: expected #{definition['headers'].inspect} to be a subset of #{headers.inspect}"]
77
+ end
78
+ end
79
+
80
+ context 'when headers are a subset of expected headers' do
81
+ let(:headers) { {'Content-Type' => 'application/json'} }
82
+
83
+ it 'should not return any errors' do
84
+ JSON::Validator.stub!(:fully_validate).and_return([])
85
+
86
+ response = described_class.new(definition)
87
+ response.validate(fake_response).should == []
88
+ end
89
+ end
90
+
91
+ context 'when headers values match but keys have different case' do
92
+ let(:headers) { {'content-type' => 'application/json'} }
93
+
94
+ it 'should not return any errors' do
95
+ JSON::Validator.stub!(:fully_validate).and_return([])
96
+
97
+ response = described_class.new(definition)
98
+ response.validate(fake_response).should == []
99
+ end
100
+ end
101
+
102
+ context 'when body does not match' do
103
+ let(:errors) { [double('error1'), double('error2')] }
104
+
105
+ it 'should return a list of errors' do
106
+ JSON::Validator.stub!(:fully_validate).and_return(errors)
107
+
108
+ response = described_class.new(definition)
109
+ response.validate(fake_response).should == errors
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,25 @@
1
+ {
2
+ "request": {
3
+ "method": "GET",
4
+ "path": "/hello_world",
5
+ "headers": {
6
+ "Accept": "application/json"
7
+ }
8
+ },
9
+
10
+ "response": {
11
+ "status": 200,
12
+ "headers": {
13
+ "Content-Type": "application/json"
14
+ },
15
+ "body": {
16
+ "description": "A simple response",
17
+ "type": "object",
18
+ "properties": {
19
+ "message": {
20
+ "type": "string"
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,42 @@
1
+ module JSON
2
+ module Generator
3
+ describe ArrayAttribute do
4
+ it 'should be a BasicAttribute' do
5
+ described_class.new(nil).should be_a_kind_of(BasicAttribute)
6
+ end
7
+
8
+ describe '#generate' do
9
+ context 'with minItems' do
10
+ it 'should generate an array with two objects of the expected type' do
11
+ stubbed_item = stub('item', :generate => 'foo')
12
+ AttributeFactory.should_receive(:create).twice.
13
+ with('type' => 'dummy_type').
14
+ and_return(stubbed_item)
15
+
16
+ properties = {
17
+ 'type' => 'array',
18
+ 'minItems' => 2,
19
+ 'items' => {
20
+ 'type' => 'dummy_type'
21
+ }
22
+ }
23
+ described_class.new(properties).generate.should ==
24
+ [stubbed_item.generate, stubbed_item.generate]
25
+ end
26
+ end
27
+
28
+ context 'without minItems' do
29
+ it 'should generate an empty array' do
30
+ properties = {
31
+ 'type' => 'array',
32
+ 'items' => {
33
+ 'type' => 'dummy_type'
34
+ }
35
+ }
36
+ described_class.new(properties).generate.should == []
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,72 @@
1
+ module JSON
2
+ module Generator
3
+ describe AttributeFactory do
4
+ describe '.create' do
5
+ let(:attribute) { stub('attribute') }
6
+
7
+ context 'when type is a string' do
8
+ let(:properties) { {'type' => 'string'} }
9
+
10
+ it 'should create a StringAttribute' do
11
+ StringAttribute.should_receive(:new).with(properties).and_return(attribute)
12
+ described_class.create(properties).should == attribute
13
+ end
14
+ end
15
+
16
+ context 'when type is an object' do
17
+ let(:properties) { {'type' => 'object'} }
18
+
19
+ it 'should create an ObjectAttribute' do
20
+ ObjectAttribute.should_receive(:new).with(properties).and_return(attribute)
21
+ described_class.create(properties).should == attribute
22
+ end
23
+ end
24
+
25
+ context 'when type is an array' do
26
+ let(:properties) { {'type' => 'array'} }
27
+
28
+ it 'should create an ArrayAttribute' do
29
+ ArrayAttribute.should_receive(:new).with(properties).and_return(attribute)
30
+ described_class.create(properties).should == attribute
31
+ end
32
+ end
33
+
34
+ context 'when type is an integer' do
35
+ let(:properties) { {'type' => 'integer'} }
36
+
37
+ it 'should create an IntegerAttribute' do
38
+ IntegerAttribute.should_receive(:new).with(properties).and_return(attribute)
39
+ described_class.create(properties).should == attribute
40
+ end
41
+ end
42
+
43
+ context 'when type is a boolean' do
44
+ let(:properties) { {'type' => 'boolean'} }
45
+
46
+ it 'should create a BooleanAttribute' do
47
+ BooleanAttribute.should_receive(:new).with(properties).and_return(attribute)
48
+ described_class.create(properties).should == attribute
49
+ end
50
+ end
51
+
52
+ context 'when we have an array of types' do
53
+ let(:properties) { {'type' => ['string', 'boolean']} }
54
+
55
+ it 'should create a the default attribute for the first type' do
56
+ StringAttribute.should_receive(:new).with(properties).and_return(attribute)
57
+ described_class.create(properties).should == attribute
58
+ end
59
+ end
60
+
61
+ context 'when there is no type' do
62
+ let(:properties) { {} }
63
+
64
+ it 'should create an EmptyAttribute' do
65
+ EmptyAttribute.should_receive(:new).with(properties).and_return(attribute)
66
+ described_class.create(properties).should == attribute
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,41 @@
1
+ module JSON
2
+ module Generator
3
+ describe BasicAttribute do
4
+ describe '#generate' do
5
+ context 'with default value' do
6
+ let(:properties) { {'default' => stub('default')} }
7
+
8
+ it 'should return the default value' do
9
+ described_class.new(properties).generate.should == properties['default']
10
+ end
11
+ end
12
+ end
13
+
14
+ describe '#required?' do
15
+ context 'when required property is true' do
16
+ let(:properties) { {'required' => true} }
17
+
18
+ it 'should be required' do
19
+ described_class.new(properties).should be_required
20
+ end
21
+ end
22
+
23
+ context 'when required property is false' do
24
+ let(:properties) { {'required' => false} }
25
+
26
+ it 'should not be required' do
27
+ described_class.new(properties).should_not be_required
28
+ end
29
+ end
30
+
31
+ context 'when required property is not defined' do
32
+ let(:properties) { {} }
33
+
34
+ it 'should not be required' do
35
+ described_class.new(properties).should_not be_required
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ module JSON
2
+ module Generator
3
+ describe BooleanAttribute do
4
+ it 'should be a BasicAttribute' do
5
+ described_class.new(nil).should be_kind_of(BasicAttribute)
6
+ end
7
+
8
+ describe '#generate' do
9
+ context 'without a default value' do
10
+ it 'should return the default value' do
11
+ described_class.new({'type' => 'boolean'}).generate.should == false
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,72 @@
1
+ module JSON
2
+ module Generator
3
+ describe Dereferencer do
4
+ describe '.dereference' do
5
+ let(:schema) do
6
+ {
7
+ 'type' => 'object',
8
+ 'properties' => {
9
+ 'referencer' => referencer
10
+ },
11
+ 'definitions' => definitions
12
+ }
13
+ end
14
+
15
+ let(:definitions) { {'referenced' => referenced} }
16
+
17
+ let(:referencer) do
18
+ {
19
+ 'type' => 'object',
20
+ '$ref' => '#/definitions/referenced'
21
+ }
22
+ end
23
+
24
+ let(:referenced) do
25
+ {
26
+ 'properties' => {
27
+ 'message' => { 'type' => 'string' }
28
+ }
29
+ }
30
+ end
31
+
32
+ it 'should replace references with concrete definitions' do
33
+ described_class.dereference(schema).should == {
34
+ 'type' => 'object',
35
+ 'properties' => {
36
+ 'referencer' => {
37
+ 'type' => 'object',
38
+ 'properties' => {
39
+ 'message' => { 'type' => 'string' }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ end
45
+
46
+ context 'when the schema does not have any reference', 'but has definitions' do
47
+ let(:schema) { {'type' => 'object', 'properties' => {}, 'definitions' => {}} }
48
+
49
+ it 'should return the original schema without the definitions' do
50
+ described_class.dereference(schema).should == {'type' => 'object', 'properties' => {}}
51
+ end
52
+ end
53
+
54
+ context 'when the root element does not have properties' do
55
+ let(:schema) { {'type' => 'string'} }
56
+
57
+ it 'should return the original schema' do
58
+ described_class.dereference(schema).should == schema
59
+ end
60
+ end
61
+
62
+ context 'when a referenced definition does not exist' do
63
+ let(:definitions) { {} }
64
+
65
+ it 'should raise a name error' do
66
+ expect { described_class.dereference(schema) }.to raise_error NameError
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end