webspicy 0.15.8 → 0.17.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +76 -24
- data/bin/webspicy +30 -14
- data/examples/restful/Gemfile.lock +40 -24
- data/examples/restful/Rakefile +0 -1
- data/examples/restful/app.rb +4 -1
- data/examples/restful/webspicy/config.rb +9 -0
- data/examples/restful/webspicy/{todo/deleteTodo.yml → formaldef/todo/_one/delete.yml} +7 -1
- data/examples/restful/webspicy/formaldef/todo/_one/get.simpler.yml +46 -0
- data/examples/restful/webspicy/{todo/getTodo.yml → formaldef/todo/_one/get.yml} +0 -0
- data/examples/restful/webspicy/{todo/patchTodo.yml → formaldef/todo/_one/patch.yml} +3 -0
- data/examples/restful/webspicy/{todo/putTodo.yml → formaldef/todo/_one/put.yml} +0 -0
- data/examples/restful/webspicy/{todo/getTodos.yml → formaldef/todo/get.yml} +0 -0
- data/examples/restful/webspicy/{todo → formaldef/todo}/options.yml +1 -1
- data/examples/restful/webspicy/{todo/postCsv.yml → formaldef/todo/post.csv.yml} +0 -0
- data/examples/restful/webspicy/{todo/postFile.yml → formaldef/todo/post.file.yml} +1 -1
- data/examples/restful/webspicy/{todo/postTodos.yml → formaldef/todo/post.yml} +0 -0
- data/examples/restful/webspicy/{todo → formaldef/todo}/todos.csv +0 -0
- data/examples/restful/webspicy/rack.rb +1 -1
- data/examples/restful/webspicy/real.rb +1 -1
- data/examples/restful/webspicy/schema.fio +2 -2
- data/examples/restful/webspicy/support/must_be_authenticated.rb +2 -2
- data/examples/restful/webspicy/support/todo_not_removed.rb +21 -0
- data/examples/restful/webspicy/support/todo_removed.rb +20 -0
- data/examples/single_spec/spec.yml +59 -0
- data/examples/website/config.rb +2 -0
- data/examples/website/schema.fio +1 -0
- data/examples/website/specification/get-http.yml +30 -0
- data/examples/website/specification/get-https.yml +30 -0
- data/lib/finitio/webspicy/scalars.fio +25 -0
- data/lib/webspicy.rb +49 -17
- data/lib/webspicy/checker.rb +5 -20
- data/lib/webspicy/configuration.rb +79 -14
- data/lib/webspicy/configuration/scope.rb +154 -0
- data/lib/webspicy/configuration/single_url.rb +58 -0
- data/lib/webspicy/configuration/single_yml_file.rb +30 -0
- data/lib/webspicy/formaldoc.fio +25 -8
- data/lib/webspicy/mocker.rb +8 -8
- data/lib/webspicy/mocker/config.ru +5 -0
- data/lib/webspicy/openapi.rb +1 -0
- data/lib/webspicy/openapi/generator.rb +127 -0
- data/lib/webspicy/rspec/checker.rb +2 -0
- data/lib/webspicy/rspec/checker/rspec_checker.rb +24 -0
- data/lib/webspicy/rspec/support/rspec_runnable.rb +27 -0
- data/lib/webspicy/rspec/tester.rb +4 -0
- data/lib/webspicy/rspec/tester/rspec_asserter.rb +121 -0
- data/lib/webspicy/rspec/tester/rspec_matchers.rb +114 -0
- data/lib/webspicy/rspec/tester/rspec_tester.rb +63 -0
- data/lib/webspicy/{resource.rb → specification.rb} +31 -10
- data/lib/webspicy/specification/errcondition.rb +16 -0
- data/lib/webspicy/specification/file_upload.rb +37 -0
- data/lib/webspicy/specification/postcondition.rb +16 -0
- data/lib/webspicy/specification/precondition.rb +19 -0
- data/lib/webspicy/specification/precondition/global_request_headers.rb +35 -0
- data/lib/webspicy/specification/precondition/robust_to_invalid_input.rb +68 -0
- data/lib/webspicy/{resource → specification}/service.rb +38 -25
- data/lib/webspicy/specification/test_case.rb +133 -0
- data/lib/webspicy/support.rb +2 -0
- data/lib/webspicy/support/colorize.rb +28 -0
- data/lib/webspicy/support/data_object.rb +25 -0
- data/lib/webspicy/support/status_range.rb +6 -1
- data/lib/webspicy/tester.rb +8 -77
- data/lib/webspicy/tester/asserter.rb +11 -5
- data/lib/webspicy/tester/assertions.rb +13 -10
- data/lib/webspicy/tester/client.rb +63 -0
- data/lib/webspicy/tester/client/http_client.rb +154 -0
- data/lib/webspicy/tester/client/rack_test_client.rb +188 -0
- data/lib/webspicy/tester/client/support.rb +65 -0
- data/lib/webspicy/tester/failure.rb +6 -0
- data/lib/webspicy/tester/invocation.rb +70 -0
- data/lib/webspicy/version.rb +2 -2
- data/spec/{unit/spec_helper.rb → spec_helper.rb} +0 -0
- data/spec/unit/configuration/scope/test_each_service.rb +49 -0
- data/spec/unit/configuration/scope/test_each_specification.rb +68 -0
- data/spec/unit/configuration/scope/test_expand_example.rb +65 -0
- data/spec/unit/configuration/scope/test_to_real_url.rb +82 -0
- data/spec/unit/openapi/test_generator.rb +28 -0
- data/spec/unit/specification/precondition/test_global_request_headers.rb +42 -0
- data/spec/unit/{resource → specification}/service/test_dress_params.rb +2 -2
- data/spec/unit/specification/test_case/test_mutate.rb +24 -0
- data/spec/unit/{resource → specification}/test_instantiate_url.rb +5 -5
- data/spec/unit/{resource → specification}/test_url_placeholders.rb +4 -4
- data/spec/unit/test_configuration.rb +24 -7
- data/spec/unit/tester/client/test_around.rb +61 -0
- data/spec/unit/tester/test_asserter.rb +246 -0
- data/spec/unit/tester/test_assertions.rb +12 -10
- data/tasks/test.rake +3 -1
- metadata +106 -48
- data/LICENSE.md +0 -22
- data/lib/webspicy/client.rb +0 -61
- data/lib/webspicy/client/http_client.rb +0 -145
- data/lib/webspicy/client/rack_test_client.rb +0 -181
- data/lib/webspicy/client/support.rb +0 -48
- data/lib/webspicy/file_upload.rb +0 -35
- data/lib/webspicy/postcondition.rb +0 -14
- data/lib/webspicy/precondition.rb +0 -15
- data/lib/webspicy/resource/service/invocation.rb +0 -212
- data/lib/webspicy/resource/service/test_case.rb +0 -132
- data/lib/webspicy/scope.rb +0 -160
- data/spec/unit/client/test_around.rb +0 -59
- data/spec/unit/scope/test_each_resource.rb +0 -66
- data/spec/unit/scope/test_each_service.rb +0 -47
- data/spec/unit/scope/test_expand_example.rb +0 -63
- data/spec/unit/scope/test_to_real_url.rb +0 -80
@@ -0,0 +1,63 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Tester
|
3
|
+
class RSpecTester
|
4
|
+
include Webspicy::Support::RSpecRunnable
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def load_rspec_examples
|
9
|
+
tester = self
|
10
|
+
RSpec.describe "Webspicy test suite" do
|
11
|
+
before(:all) do
|
12
|
+
tester.config.listeners(:before_all).each do |l|
|
13
|
+
l.call(tester.config)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
after(:all) do
|
17
|
+
tester.config.listeners(:after_all).each do |l|
|
18
|
+
l.call(tester.config)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
tester.config.each_scope do |scope|
|
22
|
+
client = scope.get_client
|
23
|
+
scope.each_specification do |specification|
|
24
|
+
scope.each_service(specification) do |service|
|
25
|
+
scope.each_testcase(service) do |test_case|
|
26
|
+
str = "#{service} #{test_case}"
|
27
|
+
str = Webspicy::Support::Colorize.colorize_highlight(str, tester.config)
|
28
|
+
describe(str) do
|
29
|
+
|
30
|
+
around(:each) do |example|
|
31
|
+
client.around(test_case) do
|
32
|
+
client.before(test_case)
|
33
|
+
test_case.instrument(client)
|
34
|
+
client.instrument(test_case)
|
35
|
+
@response = client.call(test_case)
|
36
|
+
@invocation = Tester::Invocation.new(test_case, @response, client)
|
37
|
+
example.run
|
38
|
+
client.after(test_case, @invocation)
|
39
|
+
@invocation
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:invocation) do
|
44
|
+
@invocation
|
45
|
+
end
|
46
|
+
|
47
|
+
it "meets its specification" do
|
48
|
+
raise "Test not ran" unless invocation.done?
|
49
|
+
RSpecAsserter.call(self, invocation)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
end # class RSpecTester
|
62
|
+
end # class Tester
|
63
|
+
end # module Webspicy
|
@@ -1,17 +1,30 @@
|
|
1
1
|
module Webspicy
|
2
|
-
class
|
2
|
+
class Specification
|
3
|
+
include Support::DataObject
|
3
4
|
|
4
5
|
def initialize(raw, location = nil)
|
5
|
-
|
6
|
+
super(raw)
|
6
7
|
@location = location
|
7
8
|
bind_services
|
8
9
|
end
|
10
|
+
attr_accessor :config
|
9
11
|
attr_reader :location
|
10
12
|
|
11
13
|
def self.info(raw)
|
12
14
|
new(raw)
|
13
15
|
end
|
14
16
|
|
17
|
+
def self.singleservice(raw)
|
18
|
+
converted = {
|
19
|
+
name: raw[:name] || "Unamed specification",
|
20
|
+
url: raw[:url],
|
21
|
+
services: [
|
22
|
+
Webspicy.service(raw.reject{|k| k==:url or k==:name }, Webspicy.current_scope)
|
23
|
+
]
|
24
|
+
}
|
25
|
+
info(converted)
|
26
|
+
end
|
27
|
+
|
15
28
|
def located_at!(location)
|
16
29
|
@location = Path(location)
|
17
30
|
end
|
@@ -22,6 +35,10 @@ module Webspicy
|
|
22
35
|
file
|
23
36
|
end
|
24
37
|
|
38
|
+
def name
|
39
|
+
@raw[:name]
|
40
|
+
end
|
41
|
+
|
25
42
|
def url
|
26
43
|
@raw[:url]
|
27
44
|
end
|
@@ -31,7 +48,7 @@ module Webspicy
|
|
31
48
|
end
|
32
49
|
|
33
50
|
def services
|
34
|
-
@raw[:services]
|
51
|
+
@raw[:services] || []
|
35
52
|
end
|
36
53
|
|
37
54
|
def url_placeholders
|
@@ -47,8 +64,8 @@ module Webspicy
|
|
47
64
|
[ url, rest ]
|
48
65
|
end
|
49
66
|
|
50
|
-
def
|
51
|
-
|
67
|
+
def to_singleservice
|
68
|
+
raise NotImplementedError
|
52
69
|
end
|
53
70
|
|
54
71
|
private
|
@@ -68,11 +85,15 @@ module Webspicy
|
|
68
85
|
end
|
69
86
|
|
70
87
|
def bind_services
|
71
|
-
|
72
|
-
s.
|
88
|
+
services.each do |s|
|
89
|
+
s.specification = self
|
73
90
|
end
|
74
91
|
end
|
75
92
|
|
76
|
-
end
|
77
|
-
end
|
78
|
-
require_relative '
|
93
|
+
end # class Specification
|
94
|
+
end # module Webspicy
|
95
|
+
require_relative 'specification/service'
|
96
|
+
require_relative 'specification/precondition'
|
97
|
+
require_relative 'specification/postcondition'
|
98
|
+
require_relative 'specification/test_case'
|
99
|
+
require_relative 'specification/file_upload'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Errcondition
|
4
|
+
|
5
|
+
def self.match(service, descr)
|
6
|
+
end
|
7
|
+
|
8
|
+
def instrument(test_case, client)
|
9
|
+
end
|
10
|
+
|
11
|
+
def check(invocation)
|
12
|
+
end
|
13
|
+
|
14
|
+
end # module Errcondition
|
15
|
+
end # module Specification
|
16
|
+
end # module Webspicy
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
class FileUpload
|
4
|
+
|
5
|
+
def initialize(raw)
|
6
|
+
@path = raw[:path]
|
7
|
+
@content_type = raw[:content_type]
|
8
|
+
@param_name = raw[:param_name] || "file"
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :path, :content_type, :param_name
|
12
|
+
|
13
|
+
def self.info(raw)
|
14
|
+
new(raw)
|
15
|
+
end
|
16
|
+
|
17
|
+
def locate(specification)
|
18
|
+
FileUpload.new({
|
19
|
+
path: specification.locate(path),
|
20
|
+
content_type: content_type
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_info
|
25
|
+
{ path: path.to_s,
|
26
|
+
content_type: content_type,
|
27
|
+
param_name: param_name }
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"FileUpload(#{to_info})"
|
32
|
+
end
|
33
|
+
alias :inspect :to_s
|
34
|
+
|
35
|
+
end # class FileUpload
|
36
|
+
end # class Specification
|
37
|
+
end # module Webspicy
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Postcondition
|
4
|
+
|
5
|
+
def self.match(service, descr)
|
6
|
+
end
|
7
|
+
|
8
|
+
def instrument(test_case, client)
|
9
|
+
end
|
10
|
+
|
11
|
+
def check(invocation)
|
12
|
+
end
|
13
|
+
|
14
|
+
end # module Postcondition
|
15
|
+
end # module Specification
|
16
|
+
end # module Webspicy
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Precondition
|
4
|
+
|
5
|
+
def self.match(service, pre)
|
6
|
+
end
|
7
|
+
|
8
|
+
def instrument(test_case, client)
|
9
|
+
end
|
10
|
+
|
11
|
+
def counterexamples(service)
|
12
|
+
[]
|
13
|
+
end
|
14
|
+
|
15
|
+
end # module Precondition
|
16
|
+
end # class Specification
|
17
|
+
end # module Webspicy
|
18
|
+
require_relative 'precondition/global_request_headers'
|
19
|
+
require_relative 'precondition/robust_to_invalid_input'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Precondition
|
4
|
+
class GlobalRequestHeaders
|
5
|
+
include Precondition
|
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(test_case, client)
|
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 Precondition
|
34
|
+
end # class Specification
|
35
|
+
end # module Webspicy
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Precondition
|
4
|
+
class RobustToInvalidInput
|
5
|
+
include Precondition
|
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 Precondition
|
67
|
+
end # class Specification
|
68
|
+
end # module Webspicy
|
@@ -1,26 +1,33 @@
|
|
1
1
|
module Webspicy
|
2
|
-
class
|
2
|
+
class Specification
|
3
3
|
class Service
|
4
|
+
include Support::DataObject
|
4
5
|
|
5
6
|
def initialize(raw)
|
6
|
-
|
7
|
+
super(raw)
|
7
8
|
bind_examples
|
8
9
|
bind_counterexamples
|
9
|
-
@preconditions = compile_preconditions
|
10
|
-
@postconditions = compile_postconditions
|
11
10
|
end
|
12
|
-
attr_accessor :
|
11
|
+
attr_accessor :specification
|
13
12
|
|
14
13
|
def self.info(raw)
|
15
14
|
new(raw)
|
16
15
|
end
|
17
16
|
|
17
|
+
def config
|
18
|
+
specification.config
|
19
|
+
end
|
20
|
+
|
18
21
|
def method
|
19
22
|
@raw[:method]
|
20
23
|
end
|
21
24
|
|
25
|
+
def description
|
26
|
+
@raw[:description]
|
27
|
+
end
|
28
|
+
|
22
29
|
def preconditions
|
23
|
-
@preconditions
|
30
|
+
@preconditions ||= compile_preconditions
|
24
31
|
end
|
25
32
|
|
26
33
|
def has_preconditions?
|
@@ -28,23 +35,31 @@ module Webspicy
|
|
28
35
|
end
|
29
36
|
|
30
37
|
def postconditions
|
31
|
-
@postconditions
|
38
|
+
@postconditions ||= compile_postconditions
|
32
39
|
end
|
33
40
|
|
34
41
|
def has_postconditions?
|
35
42
|
!postconditions.empty?
|
36
43
|
end
|
37
44
|
|
45
|
+
def errconditions
|
46
|
+
@errconditions ||= compile_errconditions
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_errconditions?
|
50
|
+
!errconditions.empty?
|
51
|
+
end
|
52
|
+
|
38
53
|
def default_example
|
39
54
|
@raw[:default_example]
|
40
55
|
end
|
41
56
|
|
42
57
|
def examples
|
43
|
-
@raw[:examples]
|
58
|
+
@raw[:examples] || []
|
44
59
|
end
|
45
60
|
|
46
61
|
def counterexamples
|
47
|
-
@raw[:counterexamples]
|
62
|
+
@raw[:counterexamples] || []
|
48
63
|
end
|
49
64
|
|
50
65
|
def generated_counterexamples
|
@@ -72,31 +87,31 @@ module Webspicy
|
|
72
87
|
input_schema.dress(params)
|
73
88
|
end
|
74
89
|
|
75
|
-
def to_info
|
76
|
-
@raw
|
77
|
-
end
|
78
|
-
|
79
90
|
def to_s
|
80
|
-
"#{method} #{
|
91
|
+
"#{method} #{specification.url}"
|
81
92
|
end
|
82
93
|
|
83
94
|
private
|
84
95
|
|
85
|
-
def scope
|
86
|
-
Webspicy.current_scope
|
87
|
-
end
|
88
|
-
|
89
96
|
def compile_preconditions
|
90
97
|
@raw[:preconditions] = [@raw[:preconditions]] if @raw[:preconditions].is_a?(String)
|
91
|
-
compile_conditions(@raw[:preconditions] ||= [],
|
98
|
+
compile_conditions(@raw[:preconditions] ||= [], config.preconditions)
|
92
99
|
end
|
93
100
|
|
94
101
|
def compile_postconditions
|
95
102
|
@raw[:postconditions] = [@raw[:postconditions]] if @raw[:postconditions].is_a?(String)
|
96
|
-
compile_conditions(@raw[:postconditions] ||= [],
|
103
|
+
compile_conditions(@raw[:postconditions] ||= [], config.postconditions)
|
104
|
+
end
|
105
|
+
|
106
|
+
def compile_errconditions
|
107
|
+
@raw[:errconditions] = [@raw[:errconditions]] if @raw[:errconditions].is_a?(String)
|
108
|
+
compile_conditions(@raw[:errconditions] ||= [], config.errconditions)
|
97
109
|
end
|
98
110
|
|
99
111
|
def compile_conditions(descriptions, conditions)
|
112
|
+
# Because we want pre & post to be able to match in all cases
|
113
|
+
# we need at least one condition
|
114
|
+
descriptions = ["all"] if descriptions.empty?
|
100
115
|
descriptions
|
101
116
|
.map{|descr|
|
102
117
|
conditions.map{|c| c.match(self, descr) }.compact
|
@@ -105,19 +120,17 @@ module Webspicy
|
|
105
120
|
end
|
106
121
|
|
107
122
|
def bind_examples
|
108
|
-
|
123
|
+
examples.each do |ex|
|
109
124
|
ex.bind(self, false)
|
110
125
|
end
|
111
126
|
end
|
112
127
|
|
113
128
|
def bind_counterexamples
|
114
|
-
|
129
|
+
counterexamples.each do |ex|
|
115
130
|
ex.bind(self, true)
|
116
131
|
end
|
117
132
|
end
|
118
133
|
|
119
134
|
end # class Service
|
120
|
-
end # class
|
135
|
+
end # class Specification
|
121
136
|
end # module Webspicy
|
122
|
-
require_relative 'service/test_case'
|
123
|
-
require_relative 'service/invocation'
|