api-validator 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.
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+ require 'faraday'
3
+
4
+ require 'support/validator_shared_examples'
5
+
6
+ describe ApiValidator::Json do
7
+ let(:env) { HashWrapper.new(:status => 200, :response_headers => {}, :body => '') }
8
+ let(:response) { Faraday::Response.new(env) }
9
+ let(:validator) { stub(:everything) }
10
+ let(:instance) { described_class.new(expected_properties) }
11
+ let(:expectation_key) { :response_body }
12
+
13
+ let(:res) { instance.validate(response) }
14
+
15
+ describe "#validate" do
16
+ let(:expected_properties) do
17
+ {
18
+ :water => {
19
+ :fire => "air",
20
+ :coords => {
21
+ :lat => "-19.65",
22
+ :lng => "86.86"
23
+ }
24
+ },
25
+ :air => "water fire",
26
+ :fire => /\Aair/,
27
+ :altitude => 500_000,
28
+ :snacks => [
29
+ {
30
+ :name => "Chunky Bar",
31
+ :quantity => 12
32
+ },
33
+ {
34
+ :name => "Celery Sticks",
35
+ :quantity => 30
36
+ },
37
+ {
38
+ :name => "Peanut Butter Cups",
39
+ :quantity => 1024
40
+ },
41
+ {
42
+ :name => "Date Squares",
43
+ :quantity => 256,
44
+ :ingredients => [["Dates", { :quantity => 4, :units => "cups" }], ["Almonds", { :quantity => 10, :units => "cups" }]]
45
+ }
46
+ ]
47
+ }
48
+ end
49
+
50
+ let(:expected_assertions) do
51
+ [
52
+ { :op => "test", :path => "/water/fire", :value => "air" },
53
+ { :op => "test", :path => "/water/coords/lat", :value => "-19.65" },
54
+ { :op => "test", :path => "/water/coords/lng", :value => "86.86" },
55
+ { :op => "test", :path => "/air", :value => "water fire" },
56
+ { :op => "test", :path => "/fire", :value => "/^air/", :type => "regexp" },
57
+ { :op => "test", :path => "/altitude", :value => 500_000 },
58
+ { :op => "test", :path => "/snacks/0/name", :value => "Chunky Bar" },
59
+ { :op => "test", :path => "/snacks/0/quantity", :value => 12 },
60
+ { :op => "test", :path => "/snacks/1/name", :value => "Celery Sticks" },
61
+ { :op => "test", :path => "/snacks/1/quantity", :value => 30 },
62
+ { :op => "test", :path => "/snacks/2/name", :value => "Peanut Butter Cups" },
63
+ { :op => "test", :path => "/snacks/2/quantity", :value => 1024 },
64
+ { :op => "test", :path => "/snacks/3/name", :value => "Date Squares" },
65
+ { :op => "test", :path => "/snacks/3/quantity", :value => 256 },
66
+ { :op => "test", :path => "/snacks/3/ingredients/0/0", :value => "Dates" },
67
+ { :op => "test", :path => "/snacks/3/ingredients/0/1/quantity", :value => 4 },
68
+ { :op => "test", :path => "/snacks/3/ingredients/0/1/units", :value => "cups" },
69
+ { :op => "test", :path => "/snacks/3/ingredients/1/0", :value => "Almonds" },
70
+ { :op => "test", :path => "/snacks/3/ingredients/1/1/quantity", :value => 10 },
71
+ { :op => "test", :path => "/snacks/3/ingredients/1/1/units", :value => "cups" },
72
+ ]
73
+ end
74
+
75
+ context "when expectation fails" do
76
+ it_behaves_like "a validator #validate method"
77
+
78
+ before do
79
+ env.body = {
80
+ "water" => {
81
+ "fire" => "very hot",
82
+ "depth" => 2_000_000_000,
83
+ "coords" => {
84
+ "lat" => "-19.65",
85
+ "type" => "latlng"
86
+ }
87
+ },
88
+ "fire" => "air water fire",
89
+ "altitude" => 500_000,
90
+ "type" => "random_data",
91
+ "snacks" => [
92
+ {
93
+ "name" => "Chunky Bar",
94
+ "quantity" => 12
95
+ },
96
+ {
97
+ "name" => "Celery Sticks",
98
+ "quantity" => 30
99
+ },
100
+ {
101
+ "name" => "Peanut Butter Cups",
102
+ "quantity" => 1024
103
+ },
104
+ {
105
+ "name" => "Date Squares",
106
+ "quantity" => 256,
107
+ "ingredients" => [["Dates", { "quantity" => 4, "units" => "cups" }], ["Almonds", { "quantity" => 10, "units" => "cups" }]]
108
+ }
109
+ ]
110
+ }
111
+ end
112
+
113
+ let(:expected_failed_assertions) do
114
+ [
115
+ { :op => "test", :path => "/water/fire", :value => "air" },
116
+ { :op => "test", :path => "/water/coords/lng", :value => "86.86" },
117
+ { :op => "test", :path => "/air", :value => "water fire" }
118
+ ]
119
+ end
120
+
121
+ let(:expected_diff) do
122
+ [
123
+ { :op => "replace", :path => "/water/fire", :value => "air", :current_value => "very hot" },
124
+ { :op => "add", :path => "/water/coords/lng", :value => "86.86" },
125
+ { :op => "add", :path => "/air", :value => "water fire" }
126
+ ]
127
+ end
128
+ end
129
+
130
+ context "when expectation passes" do
131
+ it_behaves_like "a validator #validate method"
132
+
133
+ before do
134
+ env.body = {
135
+ "water" => {
136
+ "fire" => "air",
137
+ "depth" => 2_000_000_000,
138
+ "coords" => {
139
+ "lat" => "-19.65",
140
+ "lng" => "86.86",
141
+ "type" => "latlng"
142
+ }
143
+ },
144
+ "air" => "water fire",
145
+ "fire" => "air water fire",
146
+ "altitude" => 500_000,
147
+ "type" => "random_data",
148
+ "snacks" => [
149
+ {
150
+ "name" => "Chunky Bar",
151
+ "quantity" => 12
152
+ },
153
+ {
154
+ "name" => "Celery Sticks",
155
+ "quantity" => 30
156
+ },
157
+ {
158
+ "name" => "Peanut Butter Cups",
159
+ "quantity" => 1024
160
+ },
161
+ {
162
+ "name" => "Date Squares",
163
+ "quantity" => 256,
164
+ "ingredients" => [["Dates", { "quantity" => 4, "units" => "cups" }], ["Almonds", { "quantity" => 10, "units" => "cups" }]]
165
+ }
166
+ ]
167
+ }
168
+ end
169
+
170
+ let(:expected_diff) { [] }
171
+ let(:expected_failed_assertions) { [] }
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApiValidator::ResponseExpectation::Results do
4
+ let(:request_url) { "http://lindeichmann.example.com/ollie?foo=bar" }
5
+ let(:request_headers) { { "Accept" => "text/plain" } }
6
+ let(:request_body) { "Ping" }
7
+ let(:response_headers) { { "Count" => "285", "Sugar" => "Crack from Mary Poppins" } }
8
+ let(:response_body) { "Pong" }
9
+ let(:env) {
10
+ HashWrapper.new(
11
+ :status => 200,
12
+ :request_headers => request_headers,
13
+ :request_body => request_body,
14
+ :method => 'get',
15
+ :url => URI(request_url),
16
+ :body => response_body,
17
+ :response_headers => response_headers
18
+ )
19
+ }
20
+ let(:results) {
21
+ [
22
+ {
23
+ :key => :response_headers,
24
+ :assertions => [
25
+ { :op => "test", :path => "/Count", :value => "/^\\d+$/", :type => "regexp" }
26
+ ],
27
+ :failed_assertions => [],
28
+ :diff => [],
29
+ :valid => true
30
+ },
31
+ {
32
+ :key => :response_headers,
33
+ :assertions => [
34
+ { :op => "test", :path => "/Sugar", :value => "Sweet" }
35
+ ],
36
+ :failed_assertions => [
37
+ { :op => "test", :path => "/Sugar", :value => "Sweet" }
38
+ ],
39
+ :diff => [
40
+ { :op => "replace", :path => "/Sugar", :value => "Sweet" },
41
+ { :op => "add", :path => "/Content-Type", :value => "text/plain" }
42
+ ],
43
+ :valid => false
44
+ },
45
+ {
46
+ :key => :response_status,
47
+ :assertions => [
48
+ { :op => "test", :path => "", :value => 200 }
49
+ ],
50
+ :failed_assertions => [],
51
+ :diff => [],
52
+ :valid => true
53
+ }
54
+ ]
55
+ }
56
+ let(:response) { Faraday::Response.new(env) }
57
+ let(:instance) { described_class.new(response, results) }
58
+
59
+ describe "#as_json" do
60
+ let(:expected_output) {
61
+ {
62
+ :expected => {
63
+ :response_headers => {
64
+ :assertions => [
65
+ { :op => "test", :path => "/Count", :value => "/^\\d+$/", :type => "regexp" },
66
+ { :op => "test", :path => "/Sugar", :value => "Sweet" }
67
+ ],
68
+ :failed_assertions => [
69
+ { :op => "test", :path => "/Sugar", :value => "Sweet" }
70
+ ],
71
+ :diff => [
72
+ { :op => "replace", :path => "/Sugar", :value => "Sweet" },
73
+ { :op => "add", :path => "/Content-Type", :value => "text/plain" }
74
+ ],
75
+ :valid => false
76
+ },
77
+ :response_status => {
78
+ :assertions => [
79
+ { :op => "test", :path => "", :value => 200 }
80
+ ],
81
+ :failed_assertions => [],
82
+ :diff => [],
83
+ :valid => true
84
+ }
85
+ },
86
+ :actual => {
87
+ :request_headers => request_headers,
88
+ :request_body => request_body,
89
+ :request_path => "/ollie",
90
+ :request_params => { "foo" => "bar" },
91
+ :request_url => request_url,
92
+ :request_method => 'GET',
93
+
94
+ :response_headers => response_headers,
95
+ :response_body => response_body,
96
+ :response_status => 200
97
+ }
98
+ }
99
+ }
100
+
101
+ it "merges expectation results with actual data" do
102
+ expect(instance.as_json).to eql(expected_output)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'bundler/setup'
5
+ require 'api-validator'
6
+ require 'support/hash_wrapper'
7
+ require 'mocha/api'
8
+
9
+ RSpec.configure do |config|
10
+ config.mock_with :mocha
11
+ config.expect_with :rspec do |c|
12
+ c.syntax = :expect
13
+ end
14
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApiValidator::Spec::Results do
4
+ let(:validator) { stub(:name => "GET /foo") }
5
+ let(:other_validator) { stub(:name => "with bar") }
6
+ let(:results) {
7
+ [
8
+ {
9
+ :response_headers => {
10
+ :assertions => [],
11
+ :failed_assertions => [],
12
+ :diff => [],
13
+ :valid => true
14
+ }
15
+ }
16
+ ]
17
+ }
18
+ let(:instance) { described_class.new(validator, results) }
19
+ let(:other_instance) { described_class.new(other_validator, results) }
20
+
21
+ describe "#as_json" do
22
+ let(:expected_output) {
23
+ {
24
+ "GET /foo" => {
25
+ :results => results
26
+ }
27
+ }
28
+ }
29
+ it "returns results nested under validator name" do
30
+ expect(instance.as_json).to eql(expected_output)
31
+ end
32
+ end
33
+
34
+ describe "#merge!" do
35
+ let(:expected_output) {
36
+ {
37
+ "GET /foo" => {
38
+ :results => results,
39
+ "with bar" => {
40
+ :results => results
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ it "merges given results as children and returns self" do
47
+ expect(instance.merge!(other_instance)).to eql(instance)
48
+ expect(instance.results).to eql(expected_output)
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,196 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/validation_declaration'
3
+ require 'support/shared_examples/shared_example_declaration'
4
+ require 'support/shared_examples/shared_example_lookup'
5
+
6
+ describe ApiValidator::Spec do
7
+ describe "class methods" do
8
+ let(:instance) { described_class }
9
+
10
+ describe ".describe" do
11
+ it_behaves_like "a validation declaration" do
12
+ let(:method_name) { :describe }
13
+ let(:parent) { nil }
14
+ end
15
+ end
16
+
17
+ describe ".context" do
18
+ it_behaves_like "a validation declaration" do
19
+ let(:method_name) { :describe }
20
+ let(:parent) { nil }
21
+ end
22
+ end
23
+
24
+ describe ".shared_example" do
25
+ it_behaves_like "a shared example declaration"
26
+ end
27
+ end
28
+
29
+ describe "instance methods" do
30
+ let(:instance) { described_class.new("foo bar") }
31
+
32
+ describe "#describe" do
33
+ it_behaves_like "a validation declaration" do
34
+ let(:method_name) { :describe }
35
+ let(:parent) { instance }
36
+ end
37
+ end
38
+
39
+ describe "#context" do
40
+ it_behaves_like "a validation declaration" do
41
+ let(:method_name) { :context }
42
+ let(:parent) { instance }
43
+ end
44
+ end
45
+
46
+ describe "#shared_example" do
47
+ it_behaves_like "a shared example declaration"
48
+ end
49
+
50
+ describe "#find_shared_example" do
51
+ let(:block) { lambda {} }
52
+ let(:name) { :foo }
53
+
54
+ context "when example in current instance" do
55
+ before do
56
+ instance.shared_examples[name] = block
57
+ end
58
+
59
+ it_behaves_like "shared example lookup"
60
+ end
61
+
62
+ context "when example in parent instance" do
63
+ before do
64
+ i = described_class.new("bar baz")
65
+ instance.instance_eval { @parent = i }
66
+ i.shared_examples[name] = block
67
+ end
68
+
69
+ it_behaves_like "shared example lookup"
70
+ end
71
+
72
+ context "when example in parent of parent instance" do
73
+ before do
74
+ i = described_class.new("bar bar")
75
+ instance.instance_eval { @parent = i }
76
+
77
+ i2 = described_class.new("baz biz")
78
+ i.instance_eval { @parent = i2 }
79
+
80
+ i2.shared_examples[name] = block
81
+ end
82
+
83
+ it_behaves_like "shared example lookup"
84
+ end
85
+
86
+ context "when example in class" do
87
+ before do
88
+ described_class.shared_examples[name] = block
89
+ end
90
+
91
+ it_behaves_like "shared example lookup"
92
+ end
93
+ end
94
+
95
+ describe "#behaves_as" do
96
+ let(:name) { :bar }
97
+
98
+ context "when shared example exists" do
99
+ it "calls block in scope of validator" do
100
+ ref = nil
101
+ example_block = proc { ref = self }
102
+
103
+ instance.stubs(:find_shared_example).with(name).returns(example_block)
104
+ instance.behaves_as(name)
105
+
106
+ expect(ref).to eql(instance)
107
+ end
108
+ end
109
+
110
+ context "when shared example does not exist" do
111
+ it "raises BehaviourNotFoundError" do
112
+ expect { instance.behaves_as(name) }.to raise_error(ApiValidator::Spec::BehaviourNotFoundError)
113
+ end
114
+ end
115
+ end
116
+
117
+ describe "#expect_response" do
118
+ it "creates a new response expectation" do
119
+ expect(instance.expect_response).to be_a(ApiValidator::ResponseExpectation)
120
+ end
121
+
122
+ it "appends expectation to list of expectations" do
123
+ expect { instance.expect_response }.to change(instance.expectations, :size).by(1)
124
+ end
125
+ end
126
+
127
+ describe "#run" do
128
+ it "calls before hooks in context of validator" do
129
+ ref = nil
130
+ before_hook = proc { ref = self }
131
+ instance.before_hooks << before_hook
132
+
133
+ instance.run
134
+ expect(ref).to eql(instance)
135
+ end
136
+
137
+ context "when before hook is an instance method" do
138
+ it "calls before hooks in context of validator" do
139
+ ref = nil
140
+ instance.class.class_eval do
141
+ define_method :something do
142
+ ref = self
143
+ end
144
+ end
145
+ instance.before_hooks << instance.method(:something)
146
+
147
+ instance.run
148
+ expect(ref).to eql(instance)
149
+ end
150
+ end
151
+
152
+ it "executes response expectations" do
153
+ response_expectation = stub
154
+ instance.expectations << response_expectation
155
+
156
+ response_expectation.expects(:run).returns([])
157
+ instance.run
158
+ end
159
+
160
+ it "runs child validations" do
161
+ child = stub
162
+ instance.validations << child
163
+
164
+ child.expects(:run).returns(stub(:results => {}))
165
+ instance.run
166
+ end
167
+
168
+ it "returns validator results object" do
169
+ child = described_class.new("biz baz")
170
+ instance.validations << child
171
+
172
+ child.stubs(:run).returns(stub(
173
+ :results => {
174
+ "biz baz" => {
175
+ :results => [
176
+ { :response_headers => { :valid => true } }
177
+ ]
178
+ }
179
+ }
180
+ ))
181
+
182
+ res = instance.run
183
+ expect(res.as_json).to eql(
184
+ instance.name => {
185
+ :results => [],
186
+ child.name => {
187
+ :results => [
188
+ { :response_headers => { :valid => true } }
189
+ ]
190
+ }
191
+ }
192
+ )
193
+ end
194
+ end
195
+ end
196
+ end