contracts_api_test 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.
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