lazy-check 1.0.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 +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +48 -0
- data/Notes-developper.md +39 -0
- data/README.md +148 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lazy-check.gemspec +34 -0
- data/lib/lazy/check/Nokogiri_extension.rb +147 -0
- data/lib/lazy/check/checked_tag.rb +337 -0
- data/lib/lazy/check/checked_url.rb +81 -0
- data/lib/lazy/check/checker.rb +82 -0
- data/lib/lazy/check/checker_case.rb +111 -0
- data/lib/lazy/check/checker_code.rb +108 -0
- data/lib/lazy/check/checker_test.rb +137 -0
- data/lib/lazy/check/checker_url.rb +117 -0
- data/lib/lazy/check/constants.rb +17 -0
- data/lib/lazy/check/locales/en/errors.yaml +13 -0
- data/lib/lazy/check/locales/fr/errors.yaml +142 -0
- data/lib/lazy/check/locales/fr/messages.yaml +18 -0
- data/lib/lazy/check/reporter.rb +84 -0
- data/lib/lazy/check/version.rb +5 -0
- data/lib/lazy/check.rb +18 -0
- data/recipe.yaml +1 -0
- metadata +130 -0
@@ -0,0 +1,337 @@
|
|
1
|
+
module Lazy
|
2
|
+
class Checker
|
3
|
+
class CheckedTag
|
4
|
+
|
5
|
+
class CheckCaseError < StandardError; end
|
6
|
+
|
7
|
+
attr_reader :data
|
8
|
+
|
9
|
+
# Instanciation
|
10
|
+
#
|
11
|
+
#
|
12
|
+
def initialize(data)
|
13
|
+
@data = data
|
14
|
+
parse_tag
|
15
|
+
check_data
|
16
|
+
@error = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return les erreurs rencontrées
|
20
|
+
def errors
|
21
|
+
err = []
|
22
|
+
# err << "Erreur avec #{data.inspect}" # Pour obtenir précisément les données
|
23
|
+
err << @error.strip unless @error.nil?
|
24
|
+
err << @errors.join("\n") unless @errors.empty?
|
25
|
+
err << @sub_errors.join("\n") unless @sub_errors.empty?
|
26
|
+
return err.join("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
MESSAGES[4999] % {tag: tag, f_data: formated_data}
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String] les données formatées pour un message de
|
34
|
+
# succès (et peut-être même à l'avenir un message de failure)
|
35
|
+
def formated_data
|
36
|
+
ds = []
|
37
|
+
ds << "#{MESSAGES[10]}: #{count}" if count
|
38
|
+
ds << "#{MESSAGES[12]}: #{min_length}" if min_length
|
39
|
+
ds << "#{MESSAGES[13]}: #{max_length}" if max_length
|
40
|
+
ds << "#{MESSAGES[20]}" if direct_child_only?
|
41
|
+
ds << "#{MESSAGES[15]}" if must_not_be_empty?
|
42
|
+
ds << "#{MESSAGES[14]}" if must_be_empty?
|
43
|
+
ds << "#{MESSAGES[16]} #{contains}" if must_have_content?
|
44
|
+
ds << "#{MESSAGES[17]} #{text.inspect}" if must_have_text?
|
45
|
+
ds << "#{MESSAGES[21]} #{attributes.inspect}" if must_have_attributes?
|
46
|
+
#
|
47
|
+
# On les répartit sur plusieurs lignes
|
48
|
+
#
|
49
|
+
max_line = 70
|
50
|
+
lines = []
|
51
|
+
line = []
|
52
|
+
while seg = ds.shift
|
53
|
+
curline = line.join(', ')
|
54
|
+
if curline.length + seg.length > max_line
|
55
|
+
lines << curline
|
56
|
+
line = []
|
57
|
+
end
|
58
|
+
line << seg
|
59
|
+
end
|
60
|
+
lines << line.join(', ') unless line.empty?
|
61
|
+
lines.join("\n")
|
62
|
+
end
|
63
|
+
|
64
|
+
# --- TESTS METHODS ---
|
65
|
+
|
66
|
+
# =main=
|
67
|
+
#
|
68
|
+
# @return true si le checked-tag se trouve dans le XML::Element
|
69
|
+
# Nokogiri +noko+
|
70
|
+
#
|
71
|
+
# @param noko [Nokogiri::XML::Element] L'élément qui doit contenir le checked-tag courant
|
72
|
+
#
|
73
|
+
def is_in?(noko)
|
74
|
+
|
75
|
+
# puts "is_in? avec noko : #{noko.inspect}".bleu
|
76
|
+
#
|
77
|
+
# Array dans lequel seront placés tous les candidats, jusqu'aux
|
78
|
+
# derniers
|
79
|
+
#
|
80
|
+
founds = []
|
81
|
+
|
82
|
+
#
|
83
|
+
# Pour mettre les erreurs
|
84
|
+
#
|
85
|
+
|
86
|
+
@errors = []
|
87
|
+
|
88
|
+
#
|
89
|
+
# Traitement différent en fonction du fait qu'il s'agisse d'un
|
90
|
+
# document Nokogiri ou d'un XML::Element Nokogiri
|
91
|
+
#
|
92
|
+
if noko.document?
|
93
|
+
#
|
94
|
+
# <= L'élément envoyé est un document Nokogiri
|
95
|
+
# Nokogiri::HTML4/5::Document
|
96
|
+
#
|
97
|
+
if direct_child_only?
|
98
|
+
noko.children.first.children.each do |child|
|
99
|
+
founds << child if tagname_id_class_ok?(child)
|
100
|
+
end
|
101
|
+
else
|
102
|
+
founds = noko.css("//#{data[:tag]}")
|
103
|
+
end
|
104
|
+
elsif direct_child_only?
|
105
|
+
#
|
106
|
+
# Si on ne doit pas traverser toutes les générations d'éléments
|
107
|
+
#
|
108
|
+
noko.children.each do |child|
|
109
|
+
founds << child if tagname_id_class_ok?(child)
|
110
|
+
end
|
111
|
+
else
|
112
|
+
#
|
113
|
+
# Si on peut traverser toutes les générations d'éléments
|
114
|
+
#
|
115
|
+
#
|
116
|
+
# <= L'élément envoyé est un node (un XML::Element)
|
117
|
+
# => On doit chercher dans les enfants
|
118
|
+
#
|
119
|
+
noko.traverse_children do |child|
|
120
|
+
# puts "child: #{child.inspect}".bleu
|
121
|
+
founds << child if tagname_id_class_ok?(child)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Pour mettre toutes les sous-erreurs rencontrées
|
127
|
+
#
|
128
|
+
@sub_errors = []
|
129
|
+
|
130
|
+
# On boucle sur les éléments trouvés.
|
131
|
+
#
|
132
|
+
# Noter que +founds+ peut être vide, mais peu importe, on
|
133
|
+
# passera simplement la boucle. C'est pour ne pas répéter le
|
134
|
+
# code en [1]
|
135
|
+
founds = founds.select do |found|
|
136
|
+
begin
|
137
|
+
check_emptiness_of(found)
|
138
|
+
check_containess_of(found)
|
139
|
+
check_attributes_of(found)
|
140
|
+
check_lengths_of(found)
|
141
|
+
# S'il n'a pas raisé jusque-là, c'est qu'il est bon
|
142
|
+
true
|
143
|
+
rescue CheckCaseError => e
|
144
|
+
@sub_errors << e.message
|
145
|
+
false
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# [1] Si aucun élément n'a passé le test
|
150
|
+
if founds.empty?
|
151
|
+
if count === 0
|
152
|
+
return true
|
153
|
+
else
|
154
|
+
_raise(4999, count || 'un nombre indéfini')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
founds_count = founds.count
|
159
|
+
# puts "Il en reste : #{founds_count}".bleu
|
160
|
+
|
161
|
+
#
|
162
|
+
# Propriété :count
|
163
|
+
#
|
164
|
+
if must_have_count? && ( founds_count != count )
|
165
|
+
_raise(5000, count, founds_count, :count)
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
#
|
170
|
+
# Sinon, c'est un plein succès
|
171
|
+
#
|
172
|
+
|
173
|
+
|
174
|
+
rescue CheckCaseError => e
|
175
|
+
@error = e.message
|
176
|
+
return false
|
177
|
+
else
|
178
|
+
return true
|
179
|
+
end
|
180
|
+
|
181
|
+
def tagname_id_class_ok?(child)
|
182
|
+
name_ok = child.node_name == tag_name
|
183
|
+
id_ok = id.nil? ? true : child.id == id
|
184
|
+
css_ok = css.nil? ? true : child_has_css?(child.classes, css)
|
185
|
+
return name_ok && id_ok && css_ok
|
186
|
+
end
|
187
|
+
|
188
|
+
# --
|
189
|
+
# -- Check précis d'un node (Nokogiri::XML::Element) trouvé
|
190
|
+
# -- correspond à la recherche
|
191
|
+
# --
|
192
|
+
# @note
|
193
|
+
# Il existe deux sorte d'emptiness :
|
194
|
+
# 1. l'absence de texte (mais le node peut contenir d'autres nodes)
|
195
|
+
# 2. la vrai emptiness quand le node ne contient vraiment rien
|
196
|
+
def check_emptiness_of(found)
|
197
|
+
# puts "must_be_empty? #{must_be_empty?.inspect}".jaune
|
198
|
+
# puts "found.empty? #{found.empty?.inspect}".bleu
|
199
|
+
if must_be_empty? && not(found.empty?)
|
200
|
+
_raise(5001, nil, found.content)
|
201
|
+
elsif must_not_be_empty? && found.empty?
|
202
|
+
_raise(5002)
|
203
|
+
elsif must_have_no_text? && (found.has_text?)
|
204
|
+
_raise(5003, nil, found.text)
|
205
|
+
elsif must_have_text? && (found.has_no_text?)
|
206
|
+
_raise(5004)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def check_containess_of(found)
|
211
|
+
if must_have_text? && not(found.match?(text))
|
212
|
+
_raise(5011, text.inspect)
|
213
|
+
end
|
214
|
+
if must_have_content? && not(found.contains?(contains))
|
215
|
+
_raise(5010, data[:contains].inspect, found.errors.pretty_join)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# --
|
220
|
+
# -- Vérifie que +found+ contienne bien les attributs
|
221
|
+
# -- attendu par le check
|
222
|
+
# --
|
223
|
+
def check_attributes_of(found)
|
224
|
+
if must_have_attributes?
|
225
|
+
missing_attrs = found.attributes?(attributes)
|
226
|
+
if missing_attrs.empty?
|
227
|
+
return true # ne sert pas vraiment
|
228
|
+
else
|
229
|
+
# Il y a des attributs manquants
|
230
|
+
_raise(5031, missing_attrs.inspect)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# --
|
236
|
+
# -- Vérifie la longueur du contenu si :max_length ou
|
237
|
+
# -- :min_length ont été définis
|
238
|
+
# --
|
239
|
+
def check_lengths_of(found)
|
240
|
+
if must_have_lengths?
|
241
|
+
len = found.length.freeze
|
242
|
+
if min_length && len < min_length
|
243
|
+
_raise(5032, min_length, len)
|
244
|
+
end
|
245
|
+
if max_length && len > max_length
|
246
|
+
_raise(5033, max_length, len)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# @return true si les +child_css+ contiennent toutes les +css+
|
252
|
+
#
|
253
|
+
def child_has_css?(child_css, csss)
|
254
|
+
csss.each do |css|
|
255
|
+
return false if not(child_css.include?(css))
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
# -- Predicate Methods --
|
261
|
+
|
262
|
+
def direct_child_only?
|
263
|
+
:TRUE == @directchild ||= true_or_false((data[:direct_child_only]||data[:direct_child]) === true)
|
264
|
+
end
|
265
|
+
def must_have_text?
|
266
|
+
:TRUE == @mhtxt ||= true_or_false(notext === false || not(text.nil?))
|
267
|
+
end
|
268
|
+
def must_have_no_text?
|
269
|
+
:TRUE == @mhnotxt ||= true_or_false(notext === true)
|
270
|
+
end
|
271
|
+
def must_be_empty?
|
272
|
+
:TRUE == @mbempty ||= true_or_false(empty === true)
|
273
|
+
end
|
274
|
+
def must_not_be_empty?
|
275
|
+
:TRUE == @mnbempty ||= true_or_false(empty === false)
|
276
|
+
end
|
277
|
+
def must_have_content?
|
278
|
+
:TRUE == @mhcontent ||= true_or_false(not(contains.nil?))
|
279
|
+
end
|
280
|
+
def must_have_count?
|
281
|
+
:TRUE == @mhcount ||= true_or_false(not(count.nil?))
|
282
|
+
end
|
283
|
+
def must_have_attributes?
|
284
|
+
:TRUE == @mhattrs ||= true_or_false(not(attributes.nil?))
|
285
|
+
end
|
286
|
+
def must_have_lengths?
|
287
|
+
:TRUE == @mhlens ||= true_or_false(not(min_length.nil? && max_length.nil?))
|
288
|
+
end
|
289
|
+
|
290
|
+
# -- Data Methods --
|
291
|
+
|
292
|
+
def name ; data[:name] end
|
293
|
+
def tag_name ; @tag_name end
|
294
|
+
def id ; @id end
|
295
|
+
def css ; @css end
|
296
|
+
def tag ; data[:tag] end
|
297
|
+
def text ; data[:text] end
|
298
|
+
def count ; data[:count] end
|
299
|
+
def empty ; data[:empty] end
|
300
|
+
def notext ; data[:notext] end
|
301
|
+
def contains ; data[:contains] end
|
302
|
+
def max_length ; data[:max_length] end
|
303
|
+
def min_length ; data[:min_length] end
|
304
|
+
def attributes ; data[:attrs] end
|
305
|
+
|
306
|
+
private
|
307
|
+
|
308
|
+
def _raise(errno, expected = nil, actual = nil, property = nil)
|
309
|
+
derror = {tag: tag, e: expected, a:actual}
|
310
|
+
raise CheckCaseError.new(ERRORS[errno].strip % derror)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Méthode qui décompose la donnée :tag pour en tirer le nom
|
314
|
+
# de la balise (@tag_name), l'identifiant (@tag_id) et les
|
315
|
+
# classes css (@tag_classes)
|
316
|
+
def parse_tag
|
317
|
+
tag = data[:tag].downcase
|
318
|
+
found = tag.match(REG_TAG)
|
319
|
+
@tag_name = found[:tag_name]
|
320
|
+
@id = found[:tag_id].to_s.strip
|
321
|
+
@id = nil if @id.to_s.empty?
|
322
|
+
@css = found[:tag_classes].to_s.split('.')
|
323
|
+
@css = nil if @css.empty?
|
324
|
+
end
|
325
|
+
REG_TAG = /^(?<tag_name>[a-z_\-]+)(\#(?<tag_id>[a-z0-9_\-]+))?(\.(?<tag_classes>[a-z0-9_\-\.]+))?$/.freeze
|
326
|
+
|
327
|
+
def check_data
|
328
|
+
# id || css || raise(ArgumentError.new(ERRORS[1003] % {a: tag}))
|
329
|
+
if count
|
330
|
+
count.is_a?(Integer) || raise(ArgumentError.new(ERRORS[1004] % {a: count.inspect, c: count.class.name}))
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
end #/class CheckedTag
|
336
|
+
end #/class Checker
|
337
|
+
end #/module Lazy
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#
|
2
|
+
# Class Lazy::Checker::CheckedUrl
|
3
|
+
#
|
4
|
+
# Quand c'est la redirection ou la réponse qu'il faut checker. Donc
|
5
|
+
# quand il n'y a pas de :checks dans la définition du test.
|
6
|
+
#
|
7
|
+
module Lazy
|
8
|
+
class Checker
|
9
|
+
class CheckedUrl
|
10
|
+
|
11
|
+
attr_reader :data
|
12
|
+
|
13
|
+
# [String] L'erreur éventuelle à écrire
|
14
|
+
attr_reader :error
|
15
|
+
|
16
|
+
# Instanciation
|
17
|
+
#
|
18
|
+
#
|
19
|
+
def initialize(data)
|
20
|
+
@data = data
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return les erreurs rencontrées
|
24
|
+
def errors
|
25
|
+
@errors.join("\n") + "Sous-erreurs : #{@sub_errors.join("\n")}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# --- TESTS METHODS ---
|
29
|
+
|
30
|
+
def check(**options)
|
31
|
+
urler.readit
|
32
|
+
if test_redirection?
|
33
|
+
if urler.redirection? && urler.redirect_to == data[:redirect_to]
|
34
|
+
reporter.add_success(self)
|
35
|
+
elsif urler.redirect_to.nil?
|
36
|
+
@error = ERRORS[5500] % {e:data[:redirect_to]}
|
37
|
+
reporter.add_failure(self)
|
38
|
+
else
|
39
|
+
@error = ERRORS[5501] % {a:urler.redirect_to, e:data[:redirect_to]}
|
40
|
+
reporter.add_failure(self)
|
41
|
+
end
|
42
|
+
elsif test_response?
|
43
|
+
# STDOUT.write("\nurler.rvalue = #{urler.rvalue.inspect}".jaune)
|
44
|
+
if urler.rvalue == data[:response]
|
45
|
+
reporter.add_success(self)
|
46
|
+
else
|
47
|
+
case urler.rvalue
|
48
|
+
when 404
|
49
|
+
@error = ERRORS[5503] % {e: urler.url}
|
50
|
+
else
|
51
|
+
@error = ERRORS[5502] % {a:urler.rvalue, e:data[:response]}
|
52
|
+
end
|
53
|
+
reporter.add_failure(self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# -- Predicate Methods --
|
60
|
+
|
61
|
+
def test_response?
|
62
|
+
data.key?(:response) && not(data[:response].nil?)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_redirection?
|
66
|
+
data.key?(:redirect_to) && not(data[:redirect_to].nil?)
|
67
|
+
end
|
68
|
+
|
69
|
+
# -- Data Methods --
|
70
|
+
|
71
|
+
def urler ; data[:urler] end
|
72
|
+
def name ; data[:name] end
|
73
|
+
alias :message :name
|
74
|
+
def reporter ; data[:reporter] end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
|
79
|
+
end #/class CheckedUrl
|
80
|
+
end #/class Checker
|
81
|
+
end #/module Lazy
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Lazy
|
2
|
+
class Checker
|
3
|
+
|
4
|
+
class RecipeError < StandardError; end
|
5
|
+
|
6
|
+
attr_reader :recipe_path
|
7
|
+
|
8
|
+
attr_reader :reporter
|
9
|
+
|
10
|
+
def initialize(recipe_path = nil)
|
11
|
+
recipe_path ||= File.expand_path('.', 'recipe.yaml')
|
12
|
+
File.exist?(recipe_path) || raise(ERRORS[200] % {path: recipe_path})
|
13
|
+
@recipe_path = recipe_path
|
14
|
+
recipe_valid?
|
15
|
+
end
|
16
|
+
|
17
|
+
def check(**options)
|
18
|
+
@options = options || {}
|
19
|
+
proceed_check(**options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# = main =
|
23
|
+
#
|
24
|
+
# La méthode (silencieuse) qui produit le check
|
25
|
+
# ("silencieuse" parce qu'elle ne produit que des raises)
|
26
|
+
def proceed_check(**options)
|
27
|
+
@reporter = Reporter.new(self)
|
28
|
+
@reporter.start
|
29
|
+
recipe[:tests].collect do |dtest|
|
30
|
+
Test.new(self, dtest)
|
31
|
+
end.each do |test|
|
32
|
+
test.check(**options)
|
33
|
+
end
|
34
|
+
@reporter.end
|
35
|
+
if options[:return_result]
|
36
|
+
return @reporter
|
37
|
+
else
|
38
|
+
@reporter.display unless no_output?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# -- Predicate Methods --
|
43
|
+
|
44
|
+
def base?
|
45
|
+
not(base.nil?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def recipe_valid?
|
49
|
+
not(recipe.nil?) || raise(RecipeError, ERRORS[202])
|
50
|
+
recipe.is_a?(Hash) || raise(RecipeError, ERRORS[203] % {c: recipe.class.name})
|
51
|
+
unless recipe.key?(:name) && name.is_a?(String) && not(name.empty?)
|
52
|
+
raise(RecipeError, ERRORS[206])
|
53
|
+
end
|
54
|
+
recipe.key?(:tests) || raise(RecipeError, ERRORS[204] % {ks: recipe.keys.pretty_inspect})
|
55
|
+
recipe[:tests].is_a?(Array) || raise(RecipeError, ERRORS[205] % {c: recipe[:tests].class.name})
|
56
|
+
end
|
57
|
+
|
58
|
+
def no_output?
|
59
|
+
@options[:output] === false
|
60
|
+
end
|
61
|
+
|
62
|
+
# --- Données ---
|
63
|
+
|
64
|
+
def name
|
65
|
+
@name ||= recipe[:name]
|
66
|
+
end
|
67
|
+
|
68
|
+
# [String] La base pour l'url
|
69
|
+
def base
|
70
|
+
@base ||= recipe[:base]
|
71
|
+
end
|
72
|
+
|
73
|
+
# [Hash] Les données de la recette, ou simplement "la recette"
|
74
|
+
#
|
75
|
+
def recipe
|
76
|
+
@recipe ||= YAML.safe_load(File.read(recipe_path), **YAML_OPTIONS)
|
77
|
+
end
|
78
|
+
alias :data :recipe
|
79
|
+
|
80
|
+
|
81
|
+
end #/class Checker
|
82
|
+
end #/module Lazy
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#
|
2
|
+
# Un cas de test
|
3
|
+
# --------------
|
4
|
+
#
|
5
|
+
# C'est un cas absolument unique, par exemple on cherche :
|
6
|
+
#
|
7
|
+
# Un div d'identifiant #mondiv qui n'est pas vide
|
8
|
+
# ou
|
9
|
+
# Un div d'identifiant #mondiv qui contient le texte "Coucou"
|
10
|
+
# ou
|
11
|
+
# Un div d'identifiant #mondiv qui contient une balise <li>
|
12
|
+
# *MAIS*
|
13
|
+
# pas les trois en même temps, même s'ils sont définis dans le
|
14
|
+
# même 'check'
|
15
|
+
#
|
16
|
+
module Lazy
|
17
|
+
class Checker
|
18
|
+
class CheckCase
|
19
|
+
|
20
|
+
attr_reader :urler
|
21
|
+
attr_reader :data
|
22
|
+
attr_reader :reporter
|
23
|
+
|
24
|
+
# Instanciation d'un cas de test
|
25
|
+
#
|
26
|
+
# @param urler [Hash] Checker::Url parsé à utiliser
|
27
|
+
#
|
28
|
+
# @param data [Hash] Les données du check, tels que définis dans
|
29
|
+
# la recette du test. Sera transformé en une
|
30
|
+
# CheckedTag
|
31
|
+
#
|
32
|
+
# :tag [String] [Requis] La balise, l'identifiant et les classes. Par exemple "div#mondiv.maclasse.autreclasse"
|
33
|
+
# :count [Integer] Nombre d'éléments à trouver
|
34
|
+
# :empty [Boolean] true si doit être vide, false si ne doit pas être vide
|
35
|
+
# :direct_child [Boolean] true si doit être un enfant direct (mais sert plutôt pour les sous-éléments à checker)
|
36
|
+
# :attrs [Hash] Attributs à trouver
|
37
|
+
# :contains [Array|String] Ce que doit contenir le noeud
|
38
|
+
#
|
39
|
+
def initialize(urler, data, reporter = nil)
|
40
|
+
urler.is_a?(Lazy::Checker::Url) || raise(ArgumentError.new(ERRORS[1000] % {a:urler,c:urler.class.name}))
|
41
|
+
@data = data
|
42
|
+
check_data
|
43
|
+
@urler = urler
|
44
|
+
@reporter = reporter
|
45
|
+
end
|
46
|
+
|
47
|
+
# La nouvelle façon de checker
|
48
|
+
def check
|
49
|
+
ctag = CheckedTag.new(data)
|
50
|
+
if ctag.is_in?(noko)
|
51
|
+
# reporter.add_success(self) if reporter
|
52
|
+
reporter.add_success(ctag) if reporter
|
53
|
+
return true
|
54
|
+
else
|
55
|
+
# reporter.add_failure(self) if reporter
|
56
|
+
reporter.add_failure(ctag) if reporter
|
57
|
+
return false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# -- Data / Predicate de check --
|
62
|
+
|
63
|
+
# -- Données / Predicate utiles pour checker --
|
64
|
+
# (noter que toutes ces valeurs ne sont estimées que si elles
|
65
|
+
# sont utilisées)
|
66
|
+
|
67
|
+
# @return true si la balise contient quelque chose
|
68
|
+
def has_content?
|
69
|
+
not(content.empty?)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return true si la balise ne contient rien
|
73
|
+
def not_has_content?
|
74
|
+
content.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return le contenu de la balise, sous forme de string
|
78
|
+
#
|
79
|
+
def content
|
80
|
+
tag.content
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# -- Données du Case --
|
85
|
+
|
86
|
+
def tag
|
87
|
+
data[:tag]
|
88
|
+
end
|
89
|
+
|
90
|
+
def noko
|
91
|
+
@noko ||= urler.nokogiri
|
92
|
+
end
|
93
|
+
|
94
|
+
attr_reader :debug
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
#
|
99
|
+
# Vérification de la donnée de check transmise à l'instanciation
|
100
|
+
#
|
101
|
+
# @note
|
102
|
+
# C'est elle qui va servir à l'instanciation du CheckedTag
|
103
|
+
#
|
104
|
+
def check_data
|
105
|
+
data.is_a?(Hash) || raise(ArgumentError.new(ERRORS[1001] % {a: data, c:data.class.name}))
|
106
|
+
data.key?(:tag) || raise(ArgumentError.new(ERRORS[1002] % {ks: data.keys.collect{|k|":#{k}"}.join(', ')}))
|
107
|
+
end
|
108
|
+
|
109
|
+
end #/class CheckCase
|
110
|
+
end #/class Checker
|
111
|
+
end #/module Lazy
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#
|
2
|
+
# Lazy::Checker::Code
|
3
|
+
# -------------------
|
4
|
+
# Classe pour utiliser Lazy::Check directement avec du code, sans
|
5
|
+
# passer par une URL ou une recette
|
6
|
+
#
|
7
|
+
# @usage
|
8
|
+
#
|
9
|
+
# Lazy::Checker.check(<code html>, {<rechercher>})
|
10
|
+
#
|
11
|
+
module Lazy
|
12
|
+
class Checker
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# = main =
|
16
|
+
#
|
17
|
+
# Pour une utilisation du gem avec :
|
18
|
+
#
|
19
|
+
# Lazy::Checker.check(<code XML>, {<données check>})
|
20
|
+
#
|
21
|
+
# … qui produit un rapport.
|
22
|
+
#
|
23
|
+
# Les données check doivent être conformes, c'est-à-dire contenir
|
24
|
+
# au moins la propriété :tag qui définit une balise à trouver
|
25
|
+
# dans le code XML.
|
26
|
+
#
|
27
|
+
# @param options [Hash] Cf. Lazy::Checker::Code#check_against
|
28
|
+
#
|
29
|
+
def check(xml_code, data_check, **options)
|
30
|
+
@xml_code = check_xml_code(xml_code)
|
31
|
+
@data_check = check_data_check(data_check)
|
32
|
+
checker = Lazy::Checker::Code.new(@xml_code)
|
33
|
+
checker.check_against(@data_check, **options)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Méthode qui vérifie la conformité de la donnée
|
40
|
+
def check_xml_code(xml)
|
41
|
+
xml || raise(ArgumentError, ERRORS[6003])
|
42
|
+
xml.is_a?(String) || raise(ArgumentError, ERRORS[6001] % {c: xml.class.name, a: xml.inspect})
|
43
|
+
xml = is_rooted?(xml) || raise(ArgumentError, ERRORS[6002] % {a: xml.inspect})
|
44
|
+
return xml
|
45
|
+
rescue ArgumentError => e
|
46
|
+
raise ArgumentError.new(ERRORS[6000] % {se: e.message})
|
47
|
+
end
|
48
|
+
|
49
|
+
def is_rooted?(xml_ini)
|
50
|
+
xml = xml_ini.dup
|
51
|
+
xml = xml.gsub(/\r?\n/,'')
|
52
|
+
xml = xml.gsub(/^<\?xml version.+\?>/,'')
|
53
|
+
xml = xml.strip
|
54
|
+
if xml.match?(REG_ROOT)
|
55
|
+
return xml
|
56
|
+
else
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
REG_ROOT = /^<([a-zA-Z0-9:\-_]+)( .+?)?>.*<\/\1>$/.freeze
|
62
|
+
|
63
|
+
# Méthode qui s'assure que les données de check (choses à
|
64
|
+
# vérifier) est valide
|
65
|
+
def check_data_check(dch)
|
66
|
+
dch || raise(ArgumentError, ERRORS[6003])
|
67
|
+
dch.is_a?(Hash) || raise(ArgumentError, ERRORS[6011] % {c: dch.class.name, a: dch.inspect})
|
68
|
+
dch.key?(:tag) || raise(ArgumentError, ERRORS[1002] % {ks: dch.keys.pretty_join})
|
69
|
+
return dch
|
70
|
+
rescue ArgumentError => e
|
71
|
+
raise ArgumentError.new(ERRORS[6010] % {se: e.message})
|
72
|
+
end
|
73
|
+
end #/<< self
|
74
|
+
|
75
|
+
class Code
|
76
|
+
|
77
|
+
attr_reader :urler
|
78
|
+
|
79
|
+
# Le rapport courant (pour pouvoir être modifié en cours
|
80
|
+
# de test)
|
81
|
+
attr_reader :report
|
82
|
+
|
83
|
+
def initialize(xml_code)
|
84
|
+
@xml_code = xml_code
|
85
|
+
@urler = Checker::Url.new(xml_code)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Méthode qui procède au check
|
89
|
+
#
|
90
|
+
# @param options [Hash]
|
91
|
+
# :return_result Si true, on retourne les données au lieu de les afficher
|
92
|
+
#
|
93
|
+
def check_against(data_check, **options)
|
94
|
+
@report = Reporter.new(self)
|
95
|
+
@report.start
|
96
|
+
check_case = Checker::CheckCase.new(urler, data_check, @report)
|
97
|
+
check_case.check
|
98
|
+
@report.end
|
99
|
+
if options[:return_result]
|
100
|
+
return report
|
101
|
+
else
|
102
|
+
report.display
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end #/class Code
|
107
|
+
end #/class Checker
|
108
|
+
end #/module Lazy
|