webspicy 0.23.0 → 0.25.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 +42 -17
- 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: b98da2f8756ded7b4d4a71d2eb227e2adfe25b4fd2922980e9de05a8ecd9ad65
|
4
|
+
data.tar.gz: 4a3c22b30c47e27fe8bc61ad804e5eac6cff87435741f91bd533a59aba7a69ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 557e4a8249210b4c64f84c6d9b41f56487d3e4a6ef2ec62a7e80783432de06202a9ca66978136d74880193a409db0587ce2e0aecb1df2f2f9d9296de6747117b
|
7
|
+
data.tar.gz: 71f1c9ee7fed6e38ee4eee06d03f9f8da39d64c4cb64999a5d1135a59d4a03080a77dcc8c7f327eb9d4f6faa355bf4c3b13f2ab83bdb4e6f422e260e5fe77f8d
|
@@ -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.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-
|
11
|
+
date: 2023-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -92,20 +92,20 @@ dependencies:
|
|
92
92
|
requirements:
|
93
93
|
- - ">="
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: 0.
|
95
|
+
version: 0.12.0
|
96
96
|
- - "<"
|
97
97
|
- !ruby/object:Gem::Version
|
98
|
-
version: 0.
|
98
|
+
version: 0.13.0
|
99
99
|
type: :runtime
|
100
100
|
prerelease: false
|
101
101
|
version_requirements: !ruby/object:Gem::Requirement
|
102
102
|
requirements:
|
103
103
|
- - ">="
|
104
104
|
- !ruby/object:Gem::Version
|
105
|
-
version: 0.
|
105
|
+
version: 0.12.0
|
106
106
|
- - "<"
|
107
107
|
- !ruby/object:Gem::Version
|
108
|
-
version: 0.
|
108
|
+
version: 0.13.0
|
109
109
|
- !ruby/object:Gem::Dependency
|
110
110
|
name: http
|
111
111
|
requirement: !ruby/object:Gem::Requirement
|
@@ -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.
|
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.
|
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
|