webspicy 0.24.0 → 0.26.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/webspicy/specification/pre.rb +0 -2
- data/lib/webspicy/tester/asserter.rb +12 -0
- data/lib/webspicy/tester/assertions.rb +10 -0
- data/lib/webspicy/version.rb +1 -1
- data/lib/webspicy/web/specification/post/etag_caching_protocol.rb +38 -0
- data/lib/webspicy/web/specification/post/last_modified_caching_protocol.rb +41 -0
- data/lib/webspicy/web/specification/post/semantics_preserved_by_refactoring.rb +67 -0
- data/lib/webspicy/web/specification/post.rb +9 -0
- data/lib/webspicy/web/specification/pre/global_request_headers.rb +38 -0
- data/lib/webspicy/web/specification/pre/robust_to_invalid_input.rb +70 -0
- data/lib/webspicy/web/specification/pre.rb +9 -0
- data/lib/webspicy/web/specification.rb +2 -0
- data/lib/webspicy.rb +1 -0
- data/spec/unit/tester/test_assertions.rb +77 -70
- data/spec/unit/web/specification/pre/test_global_request_headers.rb +50 -0
- metadata +85 -36
- data/lib/webspicy/specification/pre/global_request_headers.rb +0 -35
- data/lib/webspicy/specification/pre/robust_to_invalid_input.rb +0 -68
- data/spec/unit/specification/pre/test_global_request_headers.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49f65b0370d22d9ff81cb309ec916a292c7c852e25d783b11b72de21d5466772
|
4
|
+
data.tar.gz: be4edc16dd8809028b243687b69da34591fec591c520ad72f9a5786a8f324130
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89f2301b02f83bfe3e48286854e6c51c3b55dfd588d2dc1c4f1f4f343a8cde91eccbf592d3c7516975143f743869aa26719b2d5389b318b95284c5f9239a3761
|
7
|
+
data.tar.gz: f63b1e52e4ae1edebe0b2e9f6aaa7360a4208f8b9fe7bc917ef19a6710d28b003bad13bcea31874935528cf428444e7548d6bc6fac42d46526fc11b84a327dba
|
@@ -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)
|
data/lib/webspicy/version.rb
CHANGED
@@ -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,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
|
@@ -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
@@ -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
|
-
|
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.
|
4
|
+
version: 0.26.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -28,9 +28,9 @@ dependencies:
|
|
28
28
|
name: sinatra
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '3.0'
|
34
34
|
- - "<"
|
35
35
|
- !ruby/object:Gem::Version
|
36
36
|
version: '4.0'
|
@@ -38,9 +38,9 @@ dependencies:
|
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
|
-
- - "
|
41
|
+
- - ">"
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
43
|
+
version: '3.0'
|
44
44
|
- - "<"
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: '4.0'
|
@@ -62,30 +62,42 @@ dependencies:
|
|
62
62
|
name: rspec_junit_formatter
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
|
-
- - "
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0.6'
|
68
|
+
- - "<"
|
66
69
|
- !ruby/object:Gem::Version
|
67
|
-
version: 0.
|
70
|
+
version: '0.7'
|
68
71
|
type: :development
|
69
72
|
prerelease: false
|
70
73
|
version_requirements: !ruby/object:Gem::Requirement
|
71
74
|
requirements:
|
72
|
-
- - "
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0.6'
|
78
|
+
- - "<"
|
73
79
|
- !ruby/object:Gem::Version
|
74
|
-
version: 0.
|
80
|
+
version: '0.7'
|
75
81
|
- !ruby/object:Gem::Dependency
|
76
82
|
name: rack-test
|
77
83
|
requirement: !ruby/object:Gem::Requirement
|
78
84
|
requirements:
|
79
|
-
- - "
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '2.0'
|
88
|
+
- - "<"
|
80
89
|
- !ruby/object:Gem::Version
|
81
|
-
version: 0
|
90
|
+
version: '3.0'
|
82
91
|
type: :runtime
|
83
92
|
prerelease: false
|
84
93
|
version_requirements: !ruby/object:Gem::Requirement
|
85
94
|
requirements:
|
86
|
-
- - "
|
95
|
+
- - ">="
|
87
96
|
- !ruby/object:Gem::Version
|
88
|
-
version: 0
|
97
|
+
version: '2.0'
|
98
|
+
- - "<"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '3.0'
|
89
101
|
- !ruby/object:Gem::Dependency
|
90
102
|
name: finitio
|
91
103
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,16 +122,22 @@ dependencies:
|
|
110
122
|
name: http
|
111
123
|
requirement: !ruby/object:Gem::Requirement
|
112
124
|
requirements:
|
113
|
-
- - "
|
125
|
+
- - ">="
|
114
126
|
- !ruby/object:Gem::Version
|
115
|
-
version:
|
127
|
+
version: '5.0'
|
128
|
+
- - "<"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '6.0'
|
116
131
|
type: :runtime
|
117
132
|
prerelease: false
|
118
133
|
version_requirements: !ruby/object:Gem::Requirement
|
119
134
|
requirements:
|
120
|
-
- - "
|
135
|
+
- - ">="
|
121
136
|
- !ruby/object:Gem::Version
|
122
|
-
version:
|
137
|
+
version: '5.0'
|
138
|
+
- - "<"
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '6.0'
|
123
141
|
- !ruby/object:Gem::Dependency
|
124
142
|
name: path
|
125
143
|
requirement: !ruby/object:Gem::Requirement
|
@@ -138,22 +156,22 @@ dependencies:
|
|
138
156
|
name: rack-robustness
|
139
157
|
requirement: !ruby/object:Gem::Requirement
|
140
158
|
requirements:
|
141
|
-
- - "~>"
|
142
|
-
- !ruby/object:Gem::Version
|
143
|
-
version: '1.1'
|
144
159
|
- - ">="
|
145
160
|
- !ruby/object:Gem::Version
|
146
|
-
version: 1.
|
161
|
+
version: '1.2'
|
162
|
+
- - "<"
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '2.0'
|
147
165
|
type: :runtime
|
148
166
|
prerelease: false
|
149
167
|
version_requirements: !ruby/object:Gem::Requirement
|
150
168
|
requirements:
|
151
|
-
- - "~>"
|
152
|
-
- !ruby/object:Gem::Version
|
153
|
-
version: '1.1'
|
154
169
|
- - ">="
|
155
170
|
- !ruby/object:Gem::Version
|
156
|
-
version: 1.
|
171
|
+
version: '1.2'
|
172
|
+
- - "<"
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '2.0'
|
157
175
|
- !ruby/object:Gem::Dependency
|
158
176
|
name: mustermann
|
159
177
|
requirement: !ruby/object:Gem::Requirement
|
@@ -200,16 +218,22 @@ dependencies:
|
|
200
218
|
name: openapi3_parser
|
201
219
|
requirement: !ruby/object:Gem::Requirement
|
202
220
|
requirements:
|
203
|
-
- - "
|
221
|
+
- - ">="
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '0.9'
|
224
|
+
- - "<"
|
204
225
|
- !ruby/object:Gem::Version
|
205
|
-
version: 0.
|
226
|
+
version: '0.10'
|
206
227
|
type: :runtime
|
207
228
|
prerelease: false
|
208
229
|
version_requirements: !ruby/object:Gem::Requirement
|
209
230
|
requirements:
|
210
|
-
- - "
|
231
|
+
- - ">="
|
232
|
+
- !ruby/object:Gem::Version
|
233
|
+
version: '0.9'
|
234
|
+
- - "<"
|
211
235
|
- !ruby/object:Gem::Version
|
212
|
-
version: 0.
|
236
|
+
version: '0.10'
|
213
237
|
- !ruby/object:Gem::Dependency
|
214
238
|
name: mustache
|
215
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -266,6 +290,26 @@ dependencies:
|
|
266
290
|
- - "~>"
|
267
291
|
- !ruby/object:Gem::Version
|
268
292
|
version: '3.7'
|
293
|
+
- !ruby/object:Gem::Dependency
|
294
|
+
name: predicate
|
295
|
+
requirement: !ruby/object:Gem::Requirement
|
296
|
+
requirements:
|
297
|
+
- - ">="
|
298
|
+
- !ruby/object:Gem::Version
|
299
|
+
version: '2.8'
|
300
|
+
- - "<"
|
301
|
+
- !ruby/object:Gem::Version
|
302
|
+
version: '3.0'
|
303
|
+
type: :runtime
|
304
|
+
prerelease: false
|
305
|
+
version_requirements: !ruby/object:Gem::Requirement
|
306
|
+
requirements:
|
307
|
+
- - ">="
|
308
|
+
- !ruby/object:Gem::Version
|
309
|
+
version: '2.8'
|
310
|
+
- - "<"
|
311
|
+
- !ruby/object:Gem::Version
|
312
|
+
version: '3.0'
|
269
313
|
description: Webspicy helps testing web services as software operation black boxes
|
270
314
|
email: blambeau@gmail.com
|
271
315
|
executables:
|
@@ -300,8 +344,6 @@ files:
|
|
300
344
|
- lib/webspicy/specification/post/missing_condition_impl.rb
|
301
345
|
- lib/webspicy/specification/post/unexpected_condition_impl.rb
|
302
346
|
- 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
347
|
- lib/webspicy/specification/service.rb
|
306
348
|
- lib/webspicy/specification/test_case.rb
|
307
349
|
- lib/webspicy/support.rb
|
@@ -361,6 +403,13 @@ files:
|
|
361
403
|
- lib/webspicy/web/openapi/generator.rb
|
362
404
|
- lib/webspicy/web/specification.rb
|
363
405
|
- lib/webspicy/web/specification/file_upload.rb
|
406
|
+
- lib/webspicy/web/specification/post.rb
|
407
|
+
- lib/webspicy/web/specification/post/etag_caching_protocol.rb
|
408
|
+
- lib/webspicy/web/specification/post/last_modified_caching_protocol.rb
|
409
|
+
- lib/webspicy/web/specification/post/semantics_preserved_by_refactoring.rb
|
410
|
+
- lib/webspicy/web/specification/pre.rb
|
411
|
+
- lib/webspicy/web/specification/pre/global_request_headers.rb
|
412
|
+
- lib/webspicy/web/specification/pre/robust_to_invalid_input.rb
|
364
413
|
- lib/webspicy/web/specification/service.rb
|
365
414
|
- lib/webspicy/web/specification/test_case.rb
|
366
415
|
- spec/spec_helper.rb
|
@@ -369,7 +418,6 @@ files:
|
|
369
418
|
- spec/unit/configuration/scope/test_each_specification.rb
|
370
419
|
- spec/unit/configuration/scope/test_expand_example.rb
|
371
420
|
- spec/unit/configuration/scope/test_to_real_url.rb
|
372
|
-
- spec/unit/specification/pre/test_global_request_headers.rb
|
373
421
|
- spec/unit/specification/service/test_dress_params.rb
|
374
422
|
- spec/unit/specification/test_case/test_mutate.rb
|
375
423
|
- spec/unit/specification/test_condition.rb
|
@@ -392,6 +440,7 @@ files:
|
|
392
440
|
- spec/unit/web/inferer/test_inferer.rb
|
393
441
|
- spec/unit/web/mocker/test_mocker.rb
|
394
442
|
- spec/unit/web/openapi/test_generator.rb
|
443
|
+
- spec/unit/web/specification/pre/test_global_request_headers.rb
|
395
444
|
- spec/unit/web/specification/test_instantiate_url.rb
|
396
445
|
- spec/unit/web/specification/test_url_placeholders.rb
|
397
446
|
- tasks/gem.rake
|
@@ -400,7 +449,7 @@ homepage: http://github.com/enspirit/webspicy
|
|
400
449
|
licenses:
|
401
450
|
- MIT
|
402
451
|
metadata: {}
|
403
|
-
post_install_message:
|
452
|
+
post_install_message:
|
404
453
|
rdoc_options: []
|
405
454
|
require_paths:
|
406
455
|
- lib
|
@@ -416,7 +465,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
416
465
|
version: '0'
|
417
466
|
requirements: []
|
418
467
|
rubygems_version: 3.3.26
|
419
|
-
signing_key:
|
468
|
+
signing_key:
|
420
469
|
specification_version: 4
|
421
470
|
summary: Webspicy helps testing web services as software operation black boxes!
|
422
471
|
test_files: []
|
@@ -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
|