fitting 3.0.2 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +16 -0
- data/README.md +327 -108
- data/fitting.gemspec +2 -2
- data/images/b1.png +0 -0
- data/images/b2.png +0 -0
- data/images/w1.png +0 -0
- data/images/w2.png +0 -0
- data/lib/fitting/action.rb +105 -0
- data/lib/fitting/cover/json_schema.rb +2 -2
- data/lib/fitting/cover/json_schema_one_of.rb +4 -2
- data/lib/fitting/debug.rb +47 -0
- data/lib/fitting/doc/action.rb +141 -0
- data/lib/fitting/doc/code.rb +53 -0
- data/lib/fitting/doc/combination_enum.rb +110 -0
- data/lib/fitting/doc/combination_one_of.rb +61 -0
- data/lib/fitting/doc/combination_optional.rb +54 -0
- data/lib/fitting/doc/combination_step.rb +48 -0
- data/lib/fitting/doc/content_type.rb +152 -0
- data/lib/fitting/doc/json_schema.rb +112 -0
- data/lib/fitting/doc/step.rb +102 -0
- data/lib/fitting/doc.rb +107 -0
- data/lib/fitting/host.rb +37 -0
- data/lib/fitting/log.rb +102 -0
- data/lib/fitting/nocov.rb +68 -0
- data/lib/fitting/prefix.rb +62 -0
- data/lib/fitting/railtie.rb +0 -1
- data/lib/fitting/records/spherical/request.rb +7 -4
- data/lib/fitting/records/spherical/response.rb +22 -16
- data/lib/fitting/records/tested/request.rb +6 -1
- data/lib/fitting/records/tested/response.rb +6 -1
- data/lib/fitting/rep/html.rb +32 -0
- data/lib/fitting/rep.rb +24 -0
- data/lib/fitting/report/action.rb +9 -15
- data/lib/fitting/report/actions.rb +22 -33
- data/lib/fitting/report/combination.rb +10 -6
- data/lib/fitting/report/combinations.rb +9 -29
- data/lib/fitting/report/prefix.rb +7 -24
- data/lib/fitting/report/prefixes.rb +11 -25
- data/lib/fitting/report/response.rb +12 -22
- data/lib/fitting/report/responses.rb +23 -27
- data/lib/fitting/report/tests.rb +4 -8
- data/lib/fitting/skip/action.rb +44 -0
- data/lib/fitting/skip/api.rb +29 -0
- data/lib/fitting/skip.rb +21 -0
- data/lib/fitting/version.rb +1 -1
- data/lib/fitting.rb +12 -28
- data/lib/tasks/fitting.rake +23 -84
- data/lib/templates/htmlcss/bootstrap-nightshade.min.css +12 -0
- data/lib/templates/htmlcss/bootstrap.min.js +7 -0
- data/lib/templates/htmlcss/darkmode.min.js +6 -0
- data/lib/templates/htmlcss/fitting.html +196 -0
- data/lib/templates/htmlcss/jquery-3.6.0.min.js +2 -0
- metadata +40 -39
- data/lib/fitting/configuration.rb +0 -17
- data/lib/fitting/records/spherical/requests.rb +0 -25
- data/lib/fitting/storage/responses.rb +0 -21
- data/lib/fitting/tests.rb +0 -31
- data/lib/tasks/fitting_outgoing.rake +0 -91
- data/lib/templates/bomboniere/.gitignore +0 -21
- data/lib/templates/bomboniere/.tool-versions +0 -1
- data/lib/templates/bomboniere/README.md +0 -19
- data/lib/templates/bomboniere/dist/css/app.aa2bcd8a.css +0 -1
- data/lib/templates/bomboniere/dist/css/chunk-vendors.ec5f6c3f.css +0 -1
- data/lib/templates/bomboniere/dist/favicon.ico +0 -0
- data/lib/templates/bomboniere/dist/index.html +0 -1
- data/lib/templates/bomboniere/dist/js/app.e5f1a5ec.js +0 -2
- data/lib/templates/bomboniere/dist/js/app.e5f1a5ec.js.map +0 -1
- data/lib/templates/bomboniere/dist/js/chunk-vendors.0f99b670.js +0 -13
- data/lib/templates/bomboniere/dist/js/chunk-vendors.0f99b670.js.map +0 -1
- data/lib/templates/bomboniere/package-lock.json +0 -9292
- data/lib/templates/bomboniere/package.json +0 -27
- data/lib/templates/bomboniere/public/favicon.ico +0 -0
- data/lib/templates/bomboniere/public/index.html +0 -17
- data/lib/templates/bomboniere/src/App.vue +0 -102
- data/lib/templates/bomboniere/src/assets/logo.png +0 -0
- data/lib/templates/bomboniere/src/components/HelloWorld.vue +0 -204
- data/lib/templates/bomboniere/src/main.js +0 -10
- data/lib/templates/bomboniere/src/router/index.js +0 -31
- data/lib/templates/bomboniere/src/views/About.vue +0 -5
- data/lib/templates/bomboniere/src/views/Action.vue +0 -173
- data/lib/templates/bomboniere/src/views/Home.vue +0 -17
- data/lib/templates/bomboniere/vue.config.js +0 -3
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'fitting/doc/step'
|
2
|
+
require 'fitting/doc/json_schema'
|
3
|
+
|
4
|
+
module Fitting
|
5
|
+
class Doc
|
6
|
+
class ContentType < Step
|
7
|
+
class NotFound < RuntimeError; end
|
8
|
+
|
9
|
+
def initialize(content_type, subvalue)
|
10
|
+
@logs = []
|
11
|
+
@step_key = content_type
|
12
|
+
@next_steps = []
|
13
|
+
@step_cover_size = 0
|
14
|
+
return self if content_type != 'application/json'
|
15
|
+
|
16
|
+
definitions = subvalue.inject({}) do |sum, sv|
|
17
|
+
if sv['body']["definitions"] != nil
|
18
|
+
sum.merge!(sv['body'].delete('definitions'))
|
19
|
+
end
|
20
|
+
sum
|
21
|
+
end
|
22
|
+
|
23
|
+
if subvalue.size == 1
|
24
|
+
if definitions && definitions != {}
|
25
|
+
res = merge_definitions(subvalue[0], definitions)
|
26
|
+
if res && res['properties']['type'] == 'object'
|
27
|
+
@next_steps.push(JsonSchema.new(
|
28
|
+
{
|
29
|
+
"$schema" => "http://json-schema.org/draft-04/schema#",
|
30
|
+
}.merge(res['properties']), false
|
31
|
+
))
|
32
|
+
elsif res
|
33
|
+
@next_steps.push(JsonSchema.new(
|
34
|
+
{
|
35
|
+
"$schema" => "http://json-schema.org/draft-04/schema#",
|
36
|
+
"type" => "object"
|
37
|
+
}.merge(res), false
|
38
|
+
))
|
39
|
+
else
|
40
|
+
@next_steps.push(JsonSchema.new({}, false))
|
41
|
+
end
|
42
|
+
else
|
43
|
+
@next_steps.push(JsonSchema.new(subvalue[0]['body'], false))
|
44
|
+
end
|
45
|
+
else
|
46
|
+
if definitions && definitions != {}
|
47
|
+
@next_steps.push(JsonSchema.new(
|
48
|
+
{
|
49
|
+
"$schema" => "http://json-schema.org/draft-04/schema#",
|
50
|
+
"type" => "object",
|
51
|
+
"oneOf" => subvalue.inject([]) do |sum, sv|
|
52
|
+
res = merge_definitions(sv, definitions)
|
53
|
+
sum.push(res)
|
54
|
+
end
|
55
|
+
}, false
|
56
|
+
))
|
57
|
+
else
|
58
|
+
super_schema = false
|
59
|
+
subvalue.each do |sv|
|
60
|
+
super_schema = true if sv['body']["properties"] == nil
|
61
|
+
end
|
62
|
+
|
63
|
+
if super_schema
|
64
|
+
@next_steps.push(JsonSchema.new(
|
65
|
+
{
|
66
|
+
"$schema" => "http://json-schema.org/draft-04/schema#",
|
67
|
+
"type" => "object",
|
68
|
+
"oneOf" => subvalue.inject([]) do |sum, sv|
|
69
|
+
if sv['body']["properties"]
|
70
|
+
sum.push(check_body(sv['body']["properties"], sv))
|
71
|
+
end
|
72
|
+
sum
|
73
|
+
end
|
74
|
+
}, super_schema
|
75
|
+
))
|
76
|
+
else
|
77
|
+
@next_steps.push(JsonSchema.new(
|
78
|
+
{
|
79
|
+
"$schema" => "http://json-schema.org/draft-04/schema#",
|
80
|
+
"type" => "object",
|
81
|
+
"oneOf" => subvalue.inject([]) do |sum, sv|
|
82
|
+
sum.push(check_body(sv['body']["properties"], sv))
|
83
|
+
end
|
84
|
+
}, super_schema
|
85
|
+
))
|
86
|
+
super_schema
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def cover!(log)
|
93
|
+
if @step_key == log.content_type && log.content_type == 'application/json'
|
94
|
+
@step_cover_size += 1
|
95
|
+
@logs.push(log.body)
|
96
|
+
@next_steps.each { |json_schema| json_schema.cover!(log) }
|
97
|
+
elsif @step_key != 'application/json' && log.content_type != 'application/json'
|
98
|
+
@step_cover_size += 1
|
99
|
+
end
|
100
|
+
rescue Fitting::Doc::JsonSchema::NotFound => e
|
101
|
+
raise NotFound.new "content-type: #{@step_key}\n\n"\
|
102
|
+
"#{e.message}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def debug(debug)
|
106
|
+
return self if @step_key == debug.content_type && debug.content_type == 'application/json'
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def logs
|
111
|
+
@logs
|
112
|
+
end
|
113
|
+
|
114
|
+
def merge_definitions(sv, definitions)
|
115
|
+
if sv['body']["properties"]
|
116
|
+
res = sv['body']["properties"]
|
117
|
+
elsif sv['body']['type'] != 'array'
|
118
|
+
res = sv['body']
|
119
|
+
end
|
120
|
+
definitions.each_pair do |key, value|
|
121
|
+
while JSON.dump(res).include?("\"$ref\":\"#/definitions/#{key}\"") do
|
122
|
+
new_res_array = JSON.dump(res).split('{')
|
123
|
+
index = new_res_array.index { |js| js.include?("\"$ref\":\"#/definitions/#{key}\"") }
|
124
|
+
if index != nil
|
125
|
+
def_size = "\"$ref\":\"#/definitions/#{key}\"".size
|
126
|
+
new_res_array[index] = JSON.dump(value)[1..-2] + new_res_array[index][def_size..-1]
|
127
|
+
res = JSON.load(new_res_array.join("{"))
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
if res == nil
|
132
|
+
nil
|
133
|
+
else
|
134
|
+
check_body(res, sv)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def check_body(res, sv)
|
139
|
+
if sv['body']["required"] == nil
|
140
|
+
{
|
141
|
+
"properties" => res
|
142
|
+
}
|
143
|
+
else
|
144
|
+
{
|
145
|
+
"properties" => res,
|
146
|
+
"required" => sv['body']["required"]
|
147
|
+
}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'fitting/doc/step'
|
2
|
+
require 'fitting/doc/combination_one_of'
|
3
|
+
require 'fitting/cover/json_schema_one_of'
|
4
|
+
require 'fitting/doc/combination_enum'
|
5
|
+
require 'fitting/cover/json_schema_enum'
|
6
|
+
require 'fitting/cover/json_schema'
|
7
|
+
require 'fitting/doc/combination_optional'
|
8
|
+
require 'json'
|
9
|
+
require 'json-schema'
|
10
|
+
|
11
|
+
module Fitting
|
12
|
+
class Doc
|
13
|
+
class JsonSchema < Step
|
14
|
+
class NotFound < RuntimeError; end
|
15
|
+
|
16
|
+
def initialize(json_schema, super_schema)
|
17
|
+
@super_schema = super_schema
|
18
|
+
@logs = []
|
19
|
+
@step_cover_size = 0
|
20
|
+
@step_key = json_schema
|
21
|
+
@next_steps = []
|
22
|
+
@oneOf = false
|
23
|
+
Fitting::Cover::JSONSchemaOneOf.new(json_schema).combi.each do |combination|
|
24
|
+
@oneOf = true
|
25
|
+
@next_steps.push(CombinationOneOf.new(combination[0], combination[1][0], combination[1][1], json_schema))
|
26
|
+
end
|
27
|
+
combinations = Fitting::Cover::JSONSchemaEnum.new(json_schema).combi
|
28
|
+
if combinations.size > 1
|
29
|
+
combinations.each do |comb|
|
30
|
+
@next_steps.push(CombinationEnum.new(comb[0], comb[1][0], comb[1][1], json_schema))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if json_schema['type'] != 'array'
|
35
|
+
combinations = Fitting::Cover::JSONSchema.new(json_schema).combi
|
36
|
+
combinations.each do |comb|
|
37
|
+
@next_steps.push(CombinationOptional.new(comb[0], comb[1][0], comb[1][1], json_schema))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def cover!(log)
|
43
|
+
if @super_schema
|
44
|
+
@step_cover_size += 1
|
45
|
+
@logs.push(log.body)
|
46
|
+
@next_steps.each { |combination| combination.cover!(log) }
|
47
|
+
elsif JSON::Validator.fully_validate(@step_key, log.body) == []
|
48
|
+
@step_cover_size += 1
|
49
|
+
@logs.push(log.body)
|
50
|
+
@next_steps.each { |combination| combination.cover!(log) }
|
51
|
+
else
|
52
|
+
raise Fitting::Doc::JsonSchema::NotFound.new "json-schema: #{::JSON.pretty_generate(@step_key)}\n\n"\
|
53
|
+
"body: #{::JSON.pretty_generate(log.body)}\n\n"\
|
54
|
+
"error #{::JSON.pretty_generate(JSON::Validator.fully_validate(@step_key, log.body))}"
|
55
|
+
end
|
56
|
+
rescue JSON::Schema::SchemaError => e
|
57
|
+
raise Fitting::Doc::JsonSchema::NotFound.new "json-schema: #{::JSON.pretty_generate(@step_key)}\n\n"\
|
58
|
+
"body: #{::JSON.pretty_generate(log.body)}\n\n"\
|
59
|
+
"error #{e.message}"
|
60
|
+
rescue Fitting::Doc::CombinationOneOf::NotFound => e
|
61
|
+
raise Fitting::Doc::JsonSchema::NotFound.new "#{e.message}\n\nsource json-schema: #{::JSON.pretty_generate(@step_key)}\n\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
def logs
|
65
|
+
@logs
|
66
|
+
end
|
67
|
+
|
68
|
+
def mark_range(index, res)
|
69
|
+
start_index = index
|
70
|
+
end_index = start_index + 2
|
71
|
+
|
72
|
+
(start_index..end_index).each do |i|
|
73
|
+
res[i] = @step_cover_size
|
74
|
+
end
|
75
|
+
|
76
|
+
if @step_key["required"]
|
77
|
+
mark_required(end_index, res, @step_key)
|
78
|
+
end
|
79
|
+
end_index
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_hash
|
83
|
+
@step_key
|
84
|
+
end
|
85
|
+
|
86
|
+
def report(res, index)
|
87
|
+
@index_before = index
|
88
|
+
@res_before = [] + res
|
89
|
+
|
90
|
+
index = mark_range(index, res)
|
91
|
+
@index_medium = index
|
92
|
+
@res_medium = [] + res
|
93
|
+
|
94
|
+
if @next_steps != []
|
95
|
+
new_index = index
|
96
|
+
@next_steps.each do |next_step|
|
97
|
+
if @oneOf
|
98
|
+
res, new_index = next_step.report(res, new_index)
|
99
|
+
else
|
100
|
+
res, new_index = next_step.report(res, @index_before)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
index += index_offset
|
106
|
+
@index_after = index
|
107
|
+
@res_after = [] + res
|
108
|
+
[res, index]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Fitting
|
2
|
+
class Doc
|
3
|
+
class Step
|
4
|
+
attr_accessor :step_cover_size, :step_key, :next_steps, :index_before, :index_medium, :index_after, :res_before, :res_medium, :res_after
|
5
|
+
|
6
|
+
def to_hash
|
7
|
+
{
|
8
|
+
@step_key => @next_steps.inject({}) { |sum, value| sum.merge!(value) }
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def nocover!
|
13
|
+
@step_cover_size = nil
|
14
|
+
@next_steps.each do |next_step|
|
15
|
+
next_step.nocover!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def report(res, index)
|
20
|
+
@index_before = index
|
21
|
+
@res_before = [] + res
|
22
|
+
|
23
|
+
mark_range(index, res)
|
24
|
+
@index_medium = index
|
25
|
+
@res_medium = [] + res
|
26
|
+
|
27
|
+
if @next_steps != []
|
28
|
+
new_index = index + new_index_offset
|
29
|
+
@next_steps.each do |next_step|
|
30
|
+
if self.class == Fitting::Doc::CombinationOneOf
|
31
|
+
res, _new_index = next_step.report(res, new_index - 2)
|
32
|
+
else
|
33
|
+
res, new_index = next_step.report(res, new_index)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
index += index_offset
|
39
|
+
@index_after = index
|
40
|
+
@res_after = [] + res
|
41
|
+
[res, index]
|
42
|
+
end
|
43
|
+
|
44
|
+
def mark_range(index, res)
|
45
|
+
res[index] = @step_cover_size
|
46
|
+
if @json_schema && @json_schema["required"]
|
47
|
+
mark_required(index, res, @json_schema)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def new_index_offset
|
52
|
+
1
|
53
|
+
end
|
54
|
+
|
55
|
+
def index_offset
|
56
|
+
YAML.dump(@next_steps.inject({}) { |sum, value| sum.merge!(value) }).split("\n").size
|
57
|
+
end
|
58
|
+
|
59
|
+
def mark_required(index, res, schema)
|
60
|
+
start_index = index + YAML.dump(schema["properties"]).split("\n").size
|
61
|
+
end_index = start_index + YAML.dump(schema["required"]).split("\n").size - 1
|
62
|
+
(start_index..end_index).each do |i|
|
63
|
+
res[i] = @step_cover_size
|
64
|
+
end
|
65
|
+
|
66
|
+
return if schema["required"].nil?
|
67
|
+
|
68
|
+
schema["required"].each do |required|
|
69
|
+
required_index = YAML.dump(schema["properties"]).split("\n").index { |key| key == "#{required}:" }
|
70
|
+
break if required_index.nil?
|
71
|
+
res[index + required_index] = @step_cover_size
|
72
|
+
res[index + required_index + 1] = @step_cover_size
|
73
|
+
res[index + required_index + 2] = @step_cover_size if schema["properties"][required]["type"] == "string" && schema["properties"][required]["enum"]
|
74
|
+
if schema["properties"][required]["type"] == "object"
|
75
|
+
res[index + required_index + 2] = @step_cover_size
|
76
|
+
new_index = index + required_index + 2
|
77
|
+
mark_required(new_index, res, schema["properties"][required])
|
78
|
+
elsif schema["properties"][required]["type"] == "string" && schema["properties"][required]["enum"]
|
79
|
+
new_index = index + required_index + 2
|
80
|
+
mark_enum(new_index, res, schema["properties"][required])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def mark_enum(index, res, schema)
|
86
|
+
if schema["enum"].size == 1
|
87
|
+
res[index] = @step_cover_size
|
88
|
+
res[index + 1] = @step_cover_size
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def valid?
|
93
|
+
end
|
94
|
+
|
95
|
+
def range
|
96
|
+
end
|
97
|
+
|
98
|
+
def next
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/fitting/doc.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'fitting/doc/action'
|
2
|
+
require 'tomograph'
|
3
|
+
|
4
|
+
module Fitting
|
5
|
+
class Doc
|
6
|
+
class NotFound < RuntimeError; end
|
7
|
+
|
8
|
+
def self.all
|
9
|
+
apis = YAML.safe_load(File.read('.fitting.yml'))['APIs']
|
10
|
+
return [] unless apis
|
11
|
+
apis.map do |api|
|
12
|
+
if api['type'] == 'openapi2'
|
13
|
+
Tomograph::Tomogram.new(prefix: api['prefix'] || '', openapi2_json_path: api['path']).to_a.map do |action|
|
14
|
+
Fitting::Doc::Action.new(
|
15
|
+
api['host'],
|
16
|
+
api['prefix'] || '',
|
17
|
+
action.to_hash['method'],
|
18
|
+
action.to_hash['path'].path,
|
19
|
+
action.responses
|
20
|
+
)
|
21
|
+
end
|
22
|
+
elsif api['type'] == 'openapi3'
|
23
|
+
Tomograph::Tomogram.new(prefix: api['prefix'] || '', openapi3_yaml_path: api['path']).to_a.map do |action|
|
24
|
+
Fitting::Doc::Action.new(
|
25
|
+
api['host'],
|
26
|
+
api['prefix'] || '',
|
27
|
+
action.to_hash['method'],
|
28
|
+
action.to_hash['path'].path,
|
29
|
+
action.responses
|
30
|
+
)
|
31
|
+
end
|
32
|
+
elsif api['type'] == 'drafter'
|
33
|
+
Tomograph::Tomogram.new(prefix: api['prefix'] || '', drafter_yaml_path: api['path']).to_a.map do |action|
|
34
|
+
Fitting::Doc::Action.new(
|
35
|
+
api['host'],
|
36
|
+
api['prefix'] || '',
|
37
|
+
action.to_hash['method'],
|
38
|
+
action.to_hash['path'].path,
|
39
|
+
action.responses
|
40
|
+
)
|
41
|
+
end
|
42
|
+
elsif api['type'] == 'crafter'
|
43
|
+
Tomograph::Tomogram.new(prefix: api['prefix'] || '', crafter_yaml_path: api['path']).to_a.map do |action|
|
44
|
+
Fitting::Doc::Action.new(
|
45
|
+
api['host'],
|
46
|
+
api['prefix'] || '',
|
47
|
+
action.to_hash['method'],
|
48
|
+
action.to_hash['path'].path,
|
49
|
+
action.responses
|
50
|
+
)
|
51
|
+
end
|
52
|
+
elsif api['type'] == 'tomogram'
|
53
|
+
Tomograph::Tomogram.new(prefix: api['prefix'] || '', tomogram_json_path: api['path']).to_a.map do |action|
|
54
|
+
Fitting::Doc::Action.new(
|
55
|
+
api['host'],
|
56
|
+
api['prefix'] || '',
|
57
|
+
action.to_hash['method'],
|
58
|
+
action.to_hash['path'].path,
|
59
|
+
action.responses
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end.flatten
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.cover!(docs, log)
|
67
|
+
docs.each do |doc|
|
68
|
+
return if doc.cover!(log)
|
69
|
+
end
|
70
|
+
raise NotFound.new "log: #{log.method} #{log.host} #{log.url} #{log.status}"
|
71
|
+
rescue Fitting::Doc::Action::NotFound => e
|
72
|
+
raise NotFound.new "log error: #{e.message}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.debug(docs, debug)
|
76
|
+
docs.each do |doc|
|
77
|
+
res = doc.debug(debug)
|
78
|
+
return res if res
|
79
|
+
end
|
80
|
+
raise NotFound
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.report(actions)
|
84
|
+
all = 0
|
85
|
+
cov = 0
|
86
|
+
actions.each do |action|
|
87
|
+
action.to_hash.values.first.each do |cover_line|
|
88
|
+
if cover_line == nil
|
89
|
+
next
|
90
|
+
elsif cover_line == 0
|
91
|
+
all += 1
|
92
|
+
elsif cover_line > 0
|
93
|
+
all += 1
|
94
|
+
cov += 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
res = (cov.to_f / all.to_f * 100).round(2)
|
99
|
+
puts "Coverage: #{res}%"
|
100
|
+
if res == 100.00
|
101
|
+
exit 0
|
102
|
+
else
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/fitting/host.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Fitting
|
2
|
+
class Host
|
3
|
+
class Skip < RuntimeError; end
|
4
|
+
class NotFound < RuntimeError
|
5
|
+
attr_reader :log
|
6
|
+
def initialize(msg, log)
|
7
|
+
@log = log
|
8
|
+
super(msg)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(host, skip)
|
13
|
+
@host = host
|
14
|
+
@skip = skip
|
15
|
+
@cover = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find!(log)
|
19
|
+
yaml = YAML.safe_load(File.read('.fitting.yml'))
|
20
|
+
yaml['hosts']&.map do |host|
|
21
|
+
if log.host == host.first.first
|
22
|
+
return new(host.first.first, host.first.last)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
raise NotFound.new(log.host, log)
|
26
|
+
end
|
27
|
+
|
28
|
+
def cover!
|
29
|
+
@cover = true
|
30
|
+
raise Skip if @skip == 'skip'
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
@host
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/fitting/log.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
module Fitting
|
2
|
+
class Log
|
3
|
+
def initialize(log, type)
|
4
|
+
@log = log
|
5
|
+
@type = type
|
6
|
+
@error = nil
|
7
|
+
@skip = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.all
|
11
|
+
logs = []
|
12
|
+
Dir["log/fitting*.log"].each do |file_path|
|
13
|
+
testlog = File.read(file_path)
|
14
|
+
testlog.split("\n").select { |f| f.include?('incoming request ') }.each do |test|
|
15
|
+
logs.push(new(JSON.load(test.split('incoming request ')[1]), 'incoming'))
|
16
|
+
end
|
17
|
+
testlog.split("\n").select { |f| f.include?('outgoing request ') }.each do |test|
|
18
|
+
logs.push(new(JSON.load(test.split('outgoing request ')[1]), 'outgoing'))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
logs.sort { |a, b| b.path <=> a.path }
|
22
|
+
end
|
23
|
+
|
24
|
+
def url
|
25
|
+
"#{host}#{path}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def path
|
29
|
+
@log['path']
|
30
|
+
end
|
31
|
+
|
32
|
+
def method
|
33
|
+
@log['method']
|
34
|
+
end
|
35
|
+
|
36
|
+
def status
|
37
|
+
@log['response']['status'].to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def body
|
41
|
+
@log['response']['body']
|
42
|
+
end
|
43
|
+
|
44
|
+
def content_type
|
45
|
+
@log['response']['content_type']
|
46
|
+
end
|
47
|
+
|
48
|
+
def host
|
49
|
+
@log['host'] || 'www.example.com'
|
50
|
+
end
|
51
|
+
|
52
|
+
def type
|
53
|
+
@type
|
54
|
+
end
|
55
|
+
|
56
|
+
def access!
|
57
|
+
print "\e[32m.\e[0m"
|
58
|
+
end
|
59
|
+
|
60
|
+
def pending!
|
61
|
+
@skip = true
|
62
|
+
print "\e[33m*\e[0m"
|
63
|
+
end
|
64
|
+
|
65
|
+
def failure!(error)
|
66
|
+
@error = error
|
67
|
+
print "\e[31mF\e[0m"
|
68
|
+
end
|
69
|
+
|
70
|
+
def error
|
71
|
+
@error
|
72
|
+
end
|
73
|
+
|
74
|
+
def failure?
|
75
|
+
@error.present?
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.failure(logs)
|
79
|
+
logs.select do |log|
|
80
|
+
log.failure?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def pending?
|
85
|
+
@skip
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.pending(logs)
|
89
|
+
logs.select do |log|
|
90
|
+
log.pending?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.report(logs)
|
95
|
+
puts "\n\n"
|
96
|
+
Fitting::Log.failure(logs).each_with_index do |log, index|
|
97
|
+
puts "\e[31m #{index + 1}) #{log.error.class} #{log.error.message}\n\n\e[0m"
|
98
|
+
end
|
99
|
+
print "\e[31m#{logs.size} examples, #{Fitting::Log.failure(logs).size} failure, #{Fitting::Log.pending(logs).size} pending\e[0m\n"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Fitting
|
2
|
+
class NoCov
|
3
|
+
class NotFound < RuntimeError; end
|
4
|
+
|
5
|
+
def initialize(host, method, path, code, content_type, combination, combination_next)
|
6
|
+
@host = host
|
7
|
+
@method = method
|
8
|
+
@path = path
|
9
|
+
@code = code
|
10
|
+
@content_type = content_type
|
11
|
+
@combination = combination
|
12
|
+
@combination_next = combination_next
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all(yaml)
|
16
|
+
return [] unless yaml['NoCov']
|
17
|
+
yaml['NoCov'].map do |action|
|
18
|
+
new(action['host'], action['method'], action['path'], action['code'], action['content-type'], action['combination'], action['combination_next'])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def find(docs)
|
23
|
+
res = docs.find do |action|
|
24
|
+
action.host == @host && action.method == @method && action.path_match(@path)
|
25
|
+
end
|
26
|
+
|
27
|
+
if @code == nil
|
28
|
+
return res if res.present?
|
29
|
+
raise NotFound.new("host: #{@host}, method: #{@method}, path: #{@path}")
|
30
|
+
end
|
31
|
+
|
32
|
+
if res == nil
|
33
|
+
raise NotFound.new("host: #{@host}, method: #{@method}, path: #{@path}")
|
34
|
+
end
|
35
|
+
|
36
|
+
res_code = res.responses.find { |response| response.step_key == @code.to_s }
|
37
|
+
|
38
|
+
if @content_type == nil
|
39
|
+
return res_code if res_code.present?
|
40
|
+
raise NotFound.new("host: #{@host}, method: #{@method}, path: #{@path}, code: #{@code}")
|
41
|
+
end
|
42
|
+
|
43
|
+
res_content_type = res_code.next_steps.find { |content_type| content_type.step_key == @content_type.to_s }
|
44
|
+
|
45
|
+
if @combination == nil
|
46
|
+
return res_content_type if res_content_type.present?
|
47
|
+
raise NotFound.new("host: #{@host}, method: #{@method}, path: #{@path}, code: #{@code}, content-type: #{@content_type}")
|
48
|
+
end
|
49
|
+
|
50
|
+
res_json_schema = res_content_type.next_steps[0]
|
51
|
+
res_combination = res_json_schema.next_steps.find do |combination|
|
52
|
+
combination.step_key == @combination.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
if @combination_next == nil
|
56
|
+
return res_combination if res_combination
|
57
|
+
raise NotFound.new("host: #{@host}, method: #{@method}, path: #{@path}, code: #{@code}, content-type: #{@content_type}, combination: #{@combination}")
|
58
|
+
end
|
59
|
+
|
60
|
+
res_combination_next = res_combination.next_steps.find do |combination_next|
|
61
|
+
combination_next.step_key == @combination_next.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
return res_combination_next if res_combination_next
|
65
|
+
raise NotFound.new("host: #{@host}, method: #{@method}, path: #{@path}, code: #{@code}, content-type: #{@content_type}, combination: #{@combination}, combination_next: #{@combination_next}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|