lazy-check 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|