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 +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 +38 -13
- 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
|
@@ -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
|