webspicy 0.24.0 → 0.25.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e101e5e0888e85dfb80ab43b4577c9f01ce793c04b0e974dcf12f6225743881a
4
- data.tar.gz: a42aa4e7212a25bac508d4cdf2ed8707d2c9f9cb01df8a9ef2dd6646e6b15206
3
+ metadata.gz: b98da2f8756ded7b4d4a71d2eb227e2adfe25b4fd2922980e9de05a8ecd9ad65
4
+ data.tar.gz: 4a3c22b30c47e27fe8bc61ad804e5eac6cff87435741f91bd533a59aba7a69ba
5
5
  SHA512:
6
- metadata.gz: 230a0309efbaef05b78adf7f4ad7f8c5c1caf189be13fa5fba18ffa700e4b1da91659a267905f784e435279848405e639544e8072c9f8b748a577b13bde11aee
7
- data.tar.gz: a465e061e7f8aaed5942ad097e243e4a0c18c19e51b88f5805c3e18310d4dcdf70677e8178239368a91f6753c7f33b3530f49383655080a1248d9dbe5f6f1c47
6
+ metadata.gz: 557e4a8249210b4c64f84c6d9b41f56487d3e4a6ef2ec62a7e80783432de06202a9ca66978136d74880193a409db0587ce2e0aecb1df2f2f9d9296de6747117b
7
+ data.tar.gz: 71f1c9ee7fed6e38ee4eee06d03f9f8da39d64c4cb64999a5d1135a59d4a03080a77dcc8c7f327eb9d4f6faa355bf4c3b13f2ab83bdb4e6f422e260e5fe77f8d
@@ -15,5 +15,3 @@ module Webspicy
15
15
  end # module Pre
16
16
  end # class Specification
17
17
  end # module Webspicy
18
- require_relative 'pre/global_request_headers'
19
- require_relative 'pre/robust_to_invalid_input'
@@ -108,6 +108,18 @@ module Webspicy
108
108
  end
109
109
  end
110
110
 
111
+ def eq(path, expected = NO_ARG)
112
+ path, expected = '', path if expected == NO_ARG
113
+ target = @assertions.extract_path(@target, path)
114
+ Predicate.eq(target, expected).assert!
115
+ end
116
+
117
+ def eql(path, expected = NO_ARG)
118
+ path, expected = '', path if expected == NO_ARG
119
+ target = @assertions.extract_path(@target, path)
120
+ Predicate.eq(target, expected).assert!
121
+ end
122
+
111
123
  private
112
124
 
113
125
  def DateTime(str)
@@ -90,6 +90,16 @@ module Webspicy
90
90
  !match(target, path, rx)
91
91
  end
92
92
 
93
+ def eq(target, path, expected)
94
+ target = extract_path(target, path)
95
+ target == expected
96
+ end
97
+
98
+ def eql(target, path, expected)
99
+ target = extract_path(target, path)
100
+ value_equal(target, expected)
101
+ end
102
+
93
103
  public
94
104
 
95
105
  def extract_path(target, path = NO_ARG)
@@ -1,7 +1,7 @@
1
1
  module Webspicy
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 24
4
+ MINOR = 25
5
5
  TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
@@ -0,0 +1,38 @@
1
+ module Webspicy
2
+ module Web
3
+ class Specification
4
+ module Post
5
+ class ETagCachingProtocol
6
+ include Webspicy::Specification::Post
7
+
8
+ MATCH = /It supports the ETag\/If-None-Match caching protocol/
9
+
10
+ def self.match(service, descr)
11
+ return nil unless descr =~ MATCH
12
+ ETagCachingProtocol.new
13
+ end
14
+
15
+ def check!
16
+ res = invocation.response
17
+ etag = res.headers['ETag']
18
+ fail!("No ETag response header found") unless etag
19
+
20
+ url, _ = test_case.specification.instantiate_url(test_case.params)
21
+ url = scope.to_real_url(url, test_case){|u,_| u }
22
+
23
+ response = client.api.get(url, {}, test_case.headers.merge({
24
+ 'If-None-Match' => etag
25
+ }))
26
+ fail!("304 expected") unless response.status == 304
27
+
28
+ response = client.api.get(url, {}, test_case.headers.merge({
29
+ 'If-None-Match' => "W/somethingelse"
30
+ }))
31
+ fail!("2xx expected") if response.status == 304
32
+ end
33
+
34
+ end # class ETagCachingProtocol
35
+ end # module Post
36
+ end # module Webspicy
37
+ end # module Web
38
+ end # class Specification
@@ -0,0 +1,41 @@
1
+ module Webspicy
2
+ module Web
3
+ class Specification
4
+ module Post
5
+ class LastModifiedCachingProtocol
6
+ include Webspicy::Specification::Post
7
+
8
+ MATCH = /It supports the Last-Modified\/If-Modified-Since caching protocol/
9
+
10
+ def self.match(service, descr)
11
+ return nil unless descr =~ MATCH
12
+ LastModifiedCachingProtocol.new
13
+ end
14
+
15
+ def check!
16
+ res = invocation.response
17
+ last_modified = res.headers['Last-Modified']
18
+ fail!("No last-modified response header found") unless last_modified
19
+
20
+ # check it fits the HTTP-date format or fail
21
+ Time.httpdate(last_modified) rescue fail!("Not valid Last-Modified response header")
22
+
23
+ url, _ = test_case.specification.instantiate_url(test_case.params)
24
+ url = scope.to_real_url(url, test_case){|u,_| u }
25
+
26
+ response = client.api.get(url, {}, test_case.headers.merge({
27
+ 'If-Modified-Since' => last_modified
28
+ }))
29
+ fail!("304 expected") unless response.status == 304
30
+
31
+ response = client.api.get(url, {}, test_case.headers.merge({
32
+ 'If-Modified-Since' => "Thu, 08 Jun 1970 19:06:27 GMT"
33
+ }))
34
+ fail!("2xx expected") if response.status == 304
35
+ end
36
+
37
+ end # class LastModifiedCachingProtocol
38
+ end # module Post
39
+ end # module Webspicy
40
+ end # module Web
41
+ end # class Specification
@@ -0,0 +1,67 @@
1
+ module Webspicy
2
+ module Web
3
+ class Specification
4
+ module Post
5
+ class SemanticsPreservedByRefactoring
6
+ include ::Webspicy::Specification::Post
7
+
8
+ MATCH = /The data output semantics is preserved by the refactoring/
9
+
10
+ def self.match(service, descr)
11
+ return nil unless descr =~ MATCH
12
+ SemanticsPreservedByRefactoring.new
13
+ end
14
+
15
+ def instrument
16
+ end
17
+
18
+ def check!
19
+ test_id = {
20
+ description: test_case.description,
21
+ seeds: test_case.seeds,
22
+ url: test_case.service.specification.url,
23
+ method: test_case.service.method,
24
+ params: test_case.params,
25
+ headers: test_case.headers.reject{|k| k == 'Authorization' },
26
+ metadata: test_case.metadata,
27
+ }
28
+ sha1 = Digest::SHA1.hexdigest(test_id.to_json)
29
+
30
+ record_file_path = config.folder/".morpheus/#{sha1}.key.json"
31
+ record_file_path.parent.mkdir_p
32
+ record_file_path.write(JSON.pretty_generate(test_id))
33
+
34
+ response = invocation.response
35
+ test_data = {
36
+ status: response.status,
37
+ headers: response.headers,
38
+ body: JSON.parse(response.body),
39
+ }
40
+
41
+ case ENV['MORPHEUS'].upcase
42
+ when 'RECORD'
43
+ expected_file_path = config.folder/".morpheus/#{sha1}.expected.json"
44
+ expected_file_path.write(JSON.pretty_generate(test_data))
45
+ when 'CHECK'
46
+ expected_file_path = config.folder/".morpheus/#{sha1}.expected.json"
47
+ expected = expected_file_path.load
48
+
49
+ actual_file_path = config.folder/".morpheus/#{sha1}.actual.json"
50
+ actual_file_path.write(JSON.pretty_generate(test_data))
51
+ actual = actual_file_path.load
52
+
53
+ fail!("Semantics has changed.") unless values_equal?(actual, expected)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def values_equal?(a, b)
60
+ Tester::Asserter.new(a).eql('', b)
61
+ end
62
+
63
+ end # SemanticsPreservedByRefactoring
64
+ end # module Post
65
+ end # module Webspicy
66
+ end # module Web
67
+ end # class Specification
@@ -0,0 +1,9 @@
1
+ module Webspicy
2
+ module Web
3
+ class Specification
4
+ module Post
5
+ include Webspicy::Specification::Post
6
+ end # module Post
7
+ end # class Specification
8
+ end # module Web
9
+ end # module Webspicy
@@ -0,0 +1,38 @@
1
+ module Webspicy
2
+ module Web
3
+ class Specification
4
+ module Pre
5
+ class GlobalRequestHeaders
6
+ include Pre
7
+
8
+ DEFAULT_OPTIONS = {}
9
+
10
+ def initialize(headers, options = {}, &bl)
11
+ @headers = headers
12
+ @options = DEFAULT_OPTIONS.merge(options)
13
+ @matcher = bl
14
+ end
15
+ attr_reader :headers, :matcher
16
+
17
+ def match(service, pre)
18
+ if matcher
19
+ return self if matcher.call(service)
20
+ nil
21
+ else
22
+ self
23
+ end
24
+ end
25
+
26
+ def instrument
27
+ extra = headers.reject{|k|
28
+ test_case.headers.has_key?(k)
29
+ }
30
+ puts "Instrumenting #{test_case.object_id}"
31
+ test_case.headers.merge!(extra)
32
+ end
33
+
34
+ end # class GlobalRequestHeaders
35
+ end # module Pre
36
+ end # class Specification
37
+ end # module Web
38
+ end # module Webspicy
@@ -0,0 +1,70 @@
1
+ module Webspicy
2
+ module Web
3
+ class Specification
4
+ module Pre
5
+ class RobustToInvalidInput
6
+ include Pre
7
+
8
+ def self.match(service, pre)
9
+ self.new
10
+ end
11
+
12
+ def match(service, pre)
13
+ self
14
+ end
15
+
16
+ def counterexamples(service)
17
+ spec = service.specification
18
+ first = service.examples.first
19
+ cexamples = []
20
+ cexamples += url_randomness_counterexamples(service, first) if first
21
+ cexamples += empty_input_counterexamples(service, first) if first
22
+ cexamples
23
+ end
24
+
25
+ protected
26
+
27
+ def url_randomness_counterexamples(service, first)
28
+ service.specification.url_placeholders.map{|p|
29
+ first.mutate({
30
+ :description => "it is robust to URL randomness on param `#{p}` (RobustToInvalidInput)",
31
+ :dress_params => false,
32
+ :params => first.params.merge(p => (SecureRandom.random_number * 100000000).to_i),
33
+ :expected => {
34
+ status: Support::StatusRange.str("4xx")
35
+ },
36
+ :assert => []
37
+ })
38
+ }
39
+ end
40
+
41
+ def empty_input_counterexamples(service, first)
42
+ placeholders = service.specification.url_placeholders
43
+ empty_input = first.params.reject{|k| !placeholders.include?(k) }
44
+ if invalid_input?(service, empty_input)
45
+ [first.mutate({
46
+ :description => "it is robust to an invalid empty input (RobustToInvalidInput)",
47
+ :dress_params => false,
48
+ :params => empty_input,
49
+ :expected => {
50
+ status: Support::StatusRange.str("4xx")
51
+ },
52
+ :assert => []
53
+ })]
54
+ else
55
+ []
56
+ end
57
+ end
58
+
59
+ def invalid_input?(service, empty_input)
60
+ service.input_schema.dress(empty_input)
61
+ false
62
+ rescue Finitio::Error
63
+ true
64
+ end
65
+
66
+ end # class RobustToInvalidInput
67
+ end # module Pre
68
+ end # class Specification
69
+ end # module Web
70
+ end # module Webspicy
@@ -0,0 +1,9 @@
1
+ module Webspicy
2
+ module Web
3
+ class Specification
4
+ module Pre
5
+ include Webspicy::Specification::Pre
6
+ end # module Pre
7
+ end # class Specification
8
+ end # module Web
9
+ end # module Webspicy
@@ -63,6 +63,8 @@ module Webspicy
63
63
  end # class Specification
64
64
  end # module Web
65
65
  end # module Webspicy
66
+ require_relative 'specification/pre'
67
+ require_relative 'specification/post'
66
68
  require_relative 'specification/service'
67
69
  require_relative 'specification/test_case'
68
70
  require_relative 'specification/file_upload'
data/lib/webspicy.rb CHANGED
@@ -10,6 +10,7 @@ require 'mustermann'
10
10
  require 'paint'
11
11
  require 'securerandom'
12
12
  require 'forwardable'
13
+ require 'predicate'
13
14
  module Webspicy
14
15
 
15
16
  ###
@@ -3,92 +3,99 @@ require 'spec_helper'
3
3
  module Webspicy
4
4
  class Tester
5
5
  describe Assertions do
6
- include Assertions
7
6
 
8
- public :extract_path
7
+ class A
8
+ include Assertions
9
+
10
+ public :extract_path
11
+ end
12
+
13
+ let(:a) do
14
+ A.new
15
+ end
9
16
 
10
17
  it 'has an extract_path helper' do
11
18
  target = { foo: "Hello", bar: { foo: "Hello" }, baz: [{ foo: "world" }] }
12
- expect(extract_path(target)).to be(target)
13
- expect(extract_path(target, nil)).to be(target)
14
- expect(extract_path(target, '')).to be(target)
15
- expect(extract_path(target, 'foo')).to eql("Hello")
16
- expect(extract_path(target, 'bar/foo')).to eql("Hello")
17
- expect(extract_path(target, 'baz/0')).to eql({ foo: "world" })
18
- expect(extract_path(target, 'baz/0/foo')).to eql("world")
19
+ expect(a.extract_path(target)).to be(target)
20
+ expect(a.extract_path(target, nil)).to be(target)
21
+ expect(a.extract_path(target, '')).to be(target)
22
+ expect(a.extract_path(target, 'foo')).to eql("Hello")
23
+ expect(a.extract_path(target, 'bar/foo')).to eql("Hello")
24
+ expect(a.extract_path(target, 'baz/0')).to eql({ foo: "world" })
25
+ expect(a.extract_path(target, 'baz/0/foo')).to eql("world")
19
26
  end
20
27
 
21
28
  it 'has an includes() assertion' do
22
- expect(includes [], 1).to be(false)
23
- expect(includes [5, 1], 1).to be(true)
29
+ expect(a.includes [], 1).to be(false)
30
+ expect(a.includes [5, 1], 1).to be(true)
24
31
  end
25
32
 
26
33
  it 'has a notIncludes() assertion' do
27
- expect(notIncludes [], 1).to be(true)
28
- expect(notIncludes [5, 1], 1).to be(false)
34
+ expect(a.notIncludes [], 1).to be(true)
35
+ expect(a.notIncludes [5, 1], 1).to be(false)
29
36
  end
30
37
 
31
38
  it 'has an exists() assertion' do
32
- expect(exists nil).to be(false)
33
- expect(exists []).to be(true)
34
- expect(exists [1]).to be(true)
35
- expect(exists({ foo: [] }, 'foo')).to be(true)
36
- expect(exists({ foo: {} }, 'foo')).to be(true)
37
- expect(exists({ foo: {} }, 'foo/bar')).to be(false)
39
+ expect(a.exists nil).to be(false)
40
+ expect(a.exists []).to be(true)
41
+ expect(a.exists [1]).to be(true)
42
+ expect(a.exists({ foo: [] }, 'foo')).to be(true)
43
+ expect(a.exists({ foo: {} }, 'foo')).to be(true)
44
+ expect(a.exists({ foo: {} }, 'foo/bar')).to be(false)
38
45
  end
39
46
 
40
47
  it 'has a notExists() assertion' do
41
- expect(notExists nil).to be(true)
42
- expect(notExists []).to be(false)
43
- expect(notExists [1]).to be(false)
44
- expect(notExists({ foo: [] }, 'foo')).to be(false)
45
- expect(notExists({ foo: {} }, 'foo')).to be(false)
46
- expect(notExists({ foo: {} }, 'foo/bar')).to be(true)
48
+ expect(a.notExists nil).to be(true)
49
+ expect(a.notExists []).to be(false)
50
+ expect(a.notExists [1]).to be(false)
51
+ expect(a.notExists({ foo: [] }, 'foo')).to be(false)
52
+ expect(a.notExists({ foo: {} }, 'foo')).to be(false)
53
+ expect(a.notExists({ foo: {} }, 'foo/bar')).to be(true)
47
54
  end
48
55
 
49
56
  it 'has an empty() assertion' do
50
- expect(empty []).to be(true)
51
- expect(empty [1]).to be(false)
52
- expect(empty({ foo: [] }, 'foo')).to be(true)
53
- expect(empty({ foo: [1] }, 'foo')).to be(false)
57
+ expect(a.empty []).to be(true)
58
+ expect(a.empty [1]).to be(false)
59
+ expect(a.empty({ foo: [] }, 'foo')).to be(true)
60
+ expect(a.empty({ foo: [1] }, 'foo')).to be(false)
54
61
  end
55
62
 
56
63
  it 'has a notEmpty() assertion' do
57
- expect(notEmpty []).to be(false)
58
- expect(notEmpty [1]).to be(true)
59
- expect(notEmpty({ foo: [] }, 'foo')).to be(false)
60
- expect(notEmpty({ foo: [1] }, 'foo')).to be(true)
64
+ expect(a.notEmpty []).to be(false)
65
+ expect(a.notEmpty [1]).to be(true)
66
+ expect(a.notEmpty({ foo: [] }, 'foo')).to be(false)
67
+ expect(a.notEmpty({ foo: [1] }, 'foo')).to be(true)
61
68
  end
62
69
 
63
70
  it 'has a size() assertion' do
64
- expect(size [], 0).to be(true)
65
- expect(size [], 1).to be(false)
66
- expect(size [12], 1).to be(true)
67
- expect(size({ foo: [] }, 'foo', 0)).to be(true)
68
- expect(size({ foo: [] }, 'foo', 1)).to be(false)
69
- expect(size({ foo: ['bar'] }, 'foo', 1)).to be(true)
71
+ expect(a.size [], 0).to be(true)
72
+ expect(a.size [], 1).to be(false)
73
+ expect(a.size [12], 1).to be(true)
74
+ expect(a.size({ foo: [] }, 'foo', 0)).to be(true)
75
+ expect(a.size({ foo: [] }, 'foo', 1)).to be(false)
76
+ expect(a.size({ foo: ['bar'] }, 'foo', 1)).to be(true)
70
77
  end
71
78
 
72
79
  it 'has an idIn assertion' do
73
- expect(idIn [{id: 1}, {id: 2}], [1, 2]).to be(true)
74
- expect(idIn [{id: 1}, {id: 2}], [2, 1]).to be(true)
75
- expect(idIn [{id: 1}, {id: 2}], [1, 3]).to be(false)
76
- expect(idIn [{id: 1}, {id: 2}], [1]).to be(false)
77
- expect(idIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [1, 2])).to be(true)
78
-
79
- expect(idIn({id: 1}, [1])).to be(true)
80
- expect(idIn({id: 1}, [2])).to be(false)
80
+ expect(a.idIn [{id: 1}, {id: 2}], [1, 2]).to be(true)
81
+ expect(a.idIn [{id: 1}, {id: 2}], [2, 1]).to be(true)
82
+ expect(a.idIn [{id: 1}, {id: 2}], [1, 3]).to be(false)
83
+ expect(a.idIn [{id: 1}, {id: 2}], [1]).to be(false)
84
+ expect(a.idIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [1, 2])).to be(true)
85
+
86
+ expect(a.idIn({id: 1}, [1])).to be(true)
87
+ expect(a.idIn({id: 1}, [2])).to be(false)
81
88
  end
82
89
 
83
90
  it 'has an idNotIn assertion' do
84
- expect(idNotIn [{id: 1}, {id: 2}], [3]).to be(true)
85
- expect(idNotIn [{id: 1}, {id: 2}], [3, 4]).to be(true)
86
- expect(idNotIn [{id: 1}, {id: 2}], [1]).to be(false)
87
- expect(idNotIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [1])).to be(false)
88
- expect(idNotIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [3])).to be(true)
89
-
90
- expect(idNotIn({id: 1}, [3])).to be(true)
91
- expect(idNotIn({id: 1}, [1])).to be(false)
91
+ expect(a.idNotIn [{id: 1}, {id: 2}], [3]).to be(true)
92
+ expect(a.idNotIn [{id: 1}, {id: 2}], [3, 4]).to be(true)
93
+ expect(a.idNotIn [{id: 1}, {id: 2}], [1]).to be(false)
94
+ expect(a.idNotIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [1])).to be(false)
95
+ expect(a.idNotIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [3])).to be(true)
96
+
97
+ expect(a.idNotIn({id: 1}, [3])).to be(true)
98
+ expect(a.idNotIn({id: 1}, [1])).to be(false)
92
99
  end
93
100
 
94
101
  it 'has an idFD assertion' do
@@ -96,35 +103,35 @@ module Webspicy
96
103
  { id: 1, bar: "bar" },
97
104
  { id: 2, bar: "baz" }
98
105
  ] }
99
- element = element_with_id(target, 'foo', 1)
100
- expect(idFD(element, bar: "bar")).to be(true)
101
- expect(idFD(element, bar: "baz")).to be(false)
102
- expect(idFD(element, baz: "boz")).to be(false)
106
+ element = a.element_with_id(target, 'foo', 1)
107
+ expect(a.idFD(element, bar: "bar")).to be(true)
108
+ expect(a.idFD(element, bar: "baz")).to be(false)
109
+ expect(a.idFD(element, baz: "boz")).to be(false)
103
110
 
104
111
  target = { foo: { id: 1, bar: "bar" } }
105
- element = element_with_id(target, 'foo', 1)
106
- expect(idFD(element, bar: "bar")).to be(true)
107
- expect(idFD(element, bar: "baz")).to be(false)
108
- expect(idFD(element, baz: "boz")).to be(false)
112
+ element = a.element_with_id(target, 'foo', 1)
113
+ expect(a.idFD(element, bar: "bar")).to be(true)
114
+ expect(a.idFD(element, bar: "baz")).to be(false)
115
+ expect(a.idFD(element, baz: "boz")).to be(false)
109
116
  end
110
117
 
111
118
  it 'has a pathFD assertion' do
112
119
  target = { foo: { bar: "baz"} }
113
- expect(pathFD(target, 'foo', bar: "baz")).to be(true)
114
- expect(pathFD(target, 'foo', bar: "boz")).to be(false)
115
- expect(pathFD(target, 'foo', boz: "biz")).to be(false)
120
+ expect(a.pathFD(target, 'foo', bar: "baz")).to be(true)
121
+ expect(a.pathFD(target, 'foo', bar: "boz")).to be(false)
122
+ expect(a.pathFD(target, 'foo', boz: "biz")).to be(false)
116
123
  end
117
124
 
118
125
  it 'has a match assertion' do
119
126
  target = "hello world"
120
- expect(match(target, '', /world/)).to be(true)
121
- expect(match(target, '', /foobar/)).to be(false)
127
+ expect(a.match(target, '', /world/)).to be(true)
128
+ expect(a.match(target, '', /foobar/)).to be(false)
122
129
  end
123
130
 
124
131
  it 'has a notMatch assertion' do
125
132
  target = "hello world"
126
- expect(notMatch(target, '', /world/)).to be(false)
127
- expect(notMatch(target, '', /foobar/)).to be(true)
133
+ expect(a.notMatch(target, '', /world/)).to be(false)
134
+ expect(a.notMatch(target, '', /foobar/)).to be(true)
128
135
  end
129
136
 
130
137
  end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'webspicy/web/specification/pre/global_request_headers'
3
+
4
+ module Webspicy
5
+ module Web
6
+ class Specification
7
+ module Pre
8
+ describe GlobalRequestHeaders do
9
+ let(:gbr){
10
+ GlobalRequestHeaders.new('Accept' => 'application/json')
11
+ }
12
+
13
+ def instrument(tc)
14
+ t = OpenStruct.new(test_case: tc)
15
+ gbr.bind(t).instrument
16
+ end
17
+
18
+ describe "instrument" do
19
+ it 'injects the headers' do
20
+ tc = Web::Specification::TestCase.new({})
21
+ instrument(tc)
22
+ expect(tc.headers['Accept']).to eql("application/json")
23
+ end
24
+
25
+ it 'keeps original headers unchanged' do
26
+ tc = Web::Specification::TestCase.new({
27
+ headers: {
28
+ 'Content-Type' => 'text/plain'
29
+ }
30
+ })
31
+ instrument(tc)
32
+ expect(tc.headers['Content-Type']).to eql("text/plain")
33
+ expect(tc.headers['Accept']).to eql("application/json")
34
+ end
35
+
36
+ it 'has low precedence' do
37
+ tc = Web::Specification::TestCase.new({
38
+ headers: {
39
+ 'Accept' => 'text/plain'
40
+ }
41
+ })
42
+ instrument(tc)
43
+ expect(tc.headers['Accept']).to eql("text/plain")
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webspicy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-26 00:00:00.000000000 Z
11
+ date: 2023-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -138,22 +138,22 @@ dependencies:
138
138
  name: rack-robustness
139
139
  requirement: !ruby/object:Gem::Requirement
140
140
  requirements:
141
- - - "~>"
142
- - !ruby/object:Gem::Version
143
- version: '1.1'
144
141
  - - ">="
145
142
  - !ruby/object:Gem::Version
146
- version: 1.1.0
143
+ version: '1.2'
144
+ - - "<"
145
+ - !ruby/object:Gem::Version
146
+ version: '2.0'
147
147
  type: :runtime
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
- - - "~>"
152
- - !ruby/object:Gem::Version
153
- version: '1.1'
154
151
  - - ">="
155
152
  - !ruby/object:Gem::Version
156
- version: 1.1.0
153
+ version: '1.2'
154
+ - - "<"
155
+ - !ruby/object:Gem::Version
156
+ version: '2.0'
157
157
  - !ruby/object:Gem::Dependency
158
158
  name: mustermann
159
159
  requirement: !ruby/object:Gem::Requirement
@@ -266,6 +266,26 @@ dependencies:
266
266
  - - "~>"
267
267
  - !ruby/object:Gem::Version
268
268
  version: '3.7'
269
+ - !ruby/object:Gem::Dependency
270
+ name: predicate
271
+ requirement: !ruby/object:Gem::Requirement
272
+ requirements:
273
+ - - ">="
274
+ - !ruby/object:Gem::Version
275
+ version: '2.8'
276
+ - - "<"
277
+ - !ruby/object:Gem::Version
278
+ version: '3.0'
279
+ type: :runtime
280
+ prerelease: false
281
+ version_requirements: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - ">="
284
+ - !ruby/object:Gem::Version
285
+ version: '2.8'
286
+ - - "<"
287
+ - !ruby/object:Gem::Version
288
+ version: '3.0'
269
289
  description: Webspicy helps testing web services as software operation black boxes
270
290
  email: blambeau@gmail.com
271
291
  executables:
@@ -300,8 +320,6 @@ files:
300
320
  - lib/webspicy/specification/post/missing_condition_impl.rb
301
321
  - lib/webspicy/specification/post/unexpected_condition_impl.rb
302
322
  - lib/webspicy/specification/pre.rb
303
- - lib/webspicy/specification/pre/global_request_headers.rb
304
- - lib/webspicy/specification/pre/robust_to_invalid_input.rb
305
323
  - lib/webspicy/specification/service.rb
306
324
  - lib/webspicy/specification/test_case.rb
307
325
  - lib/webspicy/support.rb
@@ -361,6 +379,13 @@ files:
361
379
  - lib/webspicy/web/openapi/generator.rb
362
380
  - lib/webspicy/web/specification.rb
363
381
  - lib/webspicy/web/specification/file_upload.rb
382
+ - lib/webspicy/web/specification/post.rb
383
+ - lib/webspicy/web/specification/post/etag_caching_protocol.rb
384
+ - lib/webspicy/web/specification/post/last_modified_caching_protocol.rb
385
+ - lib/webspicy/web/specification/post/semantics_preserved_by_refactoring.rb
386
+ - lib/webspicy/web/specification/pre.rb
387
+ - lib/webspicy/web/specification/pre/global_request_headers.rb
388
+ - lib/webspicy/web/specification/pre/robust_to_invalid_input.rb
364
389
  - lib/webspicy/web/specification/service.rb
365
390
  - lib/webspicy/web/specification/test_case.rb
366
391
  - spec/spec_helper.rb
@@ -369,7 +394,6 @@ files:
369
394
  - spec/unit/configuration/scope/test_each_specification.rb
370
395
  - spec/unit/configuration/scope/test_expand_example.rb
371
396
  - spec/unit/configuration/scope/test_to_real_url.rb
372
- - spec/unit/specification/pre/test_global_request_headers.rb
373
397
  - spec/unit/specification/service/test_dress_params.rb
374
398
  - spec/unit/specification/test_case/test_mutate.rb
375
399
  - spec/unit/specification/test_condition.rb
@@ -392,6 +416,7 @@ files:
392
416
  - spec/unit/web/inferer/test_inferer.rb
393
417
  - spec/unit/web/mocker/test_mocker.rb
394
418
  - spec/unit/web/openapi/test_generator.rb
419
+ - spec/unit/web/specification/pre/test_global_request_headers.rb
395
420
  - spec/unit/web/specification/test_instantiate_url.rb
396
421
  - spec/unit/web/specification/test_url_placeholders.rb
397
422
  - tasks/gem.rake
@@ -1,35 +0,0 @@
1
- module Webspicy
2
- class Specification
3
- module Pre
4
- class GlobalRequestHeaders
5
- include Pre
6
-
7
- DEFAULT_OPTIONS = {}
8
-
9
- def initialize(headers, options = {}, &bl)
10
- @headers = headers
11
- @options = DEFAULT_OPTIONS.merge(options)
12
- @matcher = bl
13
- end
14
- attr_reader :headers, :matcher
15
-
16
- def match(service, pre)
17
- if matcher
18
- return self if matcher.call(service)
19
- nil
20
- else
21
- self
22
- end
23
- end
24
-
25
- def instrument
26
- extra = headers.reject{|k|
27
- test_case.headers.has_key?(k)
28
- }
29
- test_case.headers.merge!(extra)
30
- end
31
-
32
- end # class GlobalRequestHeaders
33
- end # module Pre
34
- end # class Specification
35
- end # module Webspicy
@@ -1,68 +0,0 @@
1
- module Webspicy
2
- class Specification
3
- module Pre
4
- class RobustToInvalidInput
5
- include Pre
6
-
7
- def self.match(service, pre)
8
- self.new
9
- end
10
-
11
- def match(service, pre)
12
- self
13
- end
14
-
15
- def counterexamples(service)
16
- spec = service.specification
17
- first = service.examples.first
18
- cexamples = []
19
- cexamples += url_randomness_counterexamples(service, first) if first
20
- cexamples += empty_input_counterexamples(service, first) if first
21
- cexamples
22
- end
23
-
24
- protected
25
-
26
- def url_randomness_counterexamples(service, first)
27
- service.specification.url_placeholders.map{|p|
28
- first.mutate({
29
- :description => "it is robust to URL randomness on param `#{p}` (RobustToInvalidInput)",
30
- :dress_params => false,
31
- :params => first.params.merge(p => (SecureRandom.random_number * 100000000).to_i),
32
- :expected => {
33
- status: Support::StatusRange.str("4xx")
34
- },
35
- :assert => []
36
- })
37
- }
38
- end
39
-
40
- def empty_input_counterexamples(service, first)
41
- placeholders = service.specification.url_placeholders
42
- empty_input = first.params.reject{|k| !placeholders.include?(k) }
43
- if invalid_input?(service, empty_input)
44
- [first.mutate({
45
- :description => "it is robust to an invalid empty input (RobustToInvalidInput)",
46
- :dress_params => false,
47
- :params => empty_input,
48
- :expected => {
49
- status: Support::StatusRange.str("4xx")
50
- },
51
- :assert => []
52
- })]
53
- else
54
- []
55
- end
56
- end
57
-
58
- def invalid_input?(service, empty_input)
59
- service.input_schema.dress(empty_input)
60
- false
61
- rescue Finitio::Error
62
- true
63
- end
64
-
65
- end # class RobustToInvalidInput
66
- end # module Pre
67
- end # class Specification
68
- end # module Webspicy
@@ -1,47 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Webspicy
4
- class Specification
5
- module Pre
6
- describe GlobalRequestHeaders do
7
- let(:gbr){
8
- GlobalRequestHeaders.new('Accept' => 'application/json')
9
- }
10
-
11
- def instrument(tc)
12
- t = OpenStruct.new(test_case: tc)
13
- gbr.bind(t).instrument
14
- end
15
-
16
- describe "instrument" do
17
- it 'injects the headers' do
18
- tc = Web::Specification::TestCase.new({})
19
- instrument(tc)
20
- expect(tc.headers['Accept']).to eql("application/json")
21
- end
22
-
23
- it 'keeps original headers unchanged' do
24
- tc = Web::Specification::TestCase.new({
25
- headers: {
26
- 'Content-Type' => 'text/plain'
27
- }
28
- })
29
- instrument(tc)
30
- expect(tc.headers['Content-Type']).to eql("text/plain")
31
- expect(tc.headers['Accept']).to eql("application/json")
32
- end
33
-
34
- it 'has low precedence' do
35
- tc = Web::Specification::TestCase.new({
36
- headers: {
37
- 'Accept' => 'text/plain'
38
- }
39
- })
40
- instrument(tc)
41
- expect(tc.headers['Accept']).to eql("text/plain")
42
- end
43
- end
44
- end
45
- end
46
- end
47
- end