recog-intrigue 2.3.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- data/.github/ISSUE_TEMPLATE/fingerprint_request.md +27 -0
- data/.github/PULL_REQUEST_TEMPLATE +24 -0
- data/.gitignore +14 -0
- data/.rbenv-gemset +1 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +25 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +171 -0
- data/COPYING +23 -0
- data/Gemfile +10 -0
- data/LICENSE +7 -0
- data/README.md +85 -0
- data/Rakefile +22 -0
- data/bin/recog_export +81 -0
- data/bin/recog_match +55 -0
- data/bin/recog_standardize +118 -0
- data/bin/recog_verify +64 -0
- data/cpe-remap.yaml +134 -0
- data/features/data/failing_banners_fingerprints.xml +20 -0
- data/features/data/matching_banners_fingerprints.xml +23 -0
- data/features/data/multiple_banners_fingerprints.xml +32 -0
- data/features/data/no_tests.xml +3 -0
- data/features/data/sample_banner.txt +2 -0
- data/features/data/successful_tests.xml +18 -0
- data/features/data/tests_with_failures.xml +20 -0
- data/features/data/tests_with_warnings.xml +17 -0
- data/features/match.feature +36 -0
- data/features/support/aruba.rb +3 -0
- data/features/support/env.rb +6 -0
- data/features/verify.feature +48 -0
- data/identifiers/README.md +47 -0
- data/identifiers/os_architecture.txt +20 -0
- data/identifiers/os_device.txt +52 -0
- data/identifiers/os_family.txt +160 -0
- data/identifiers/os_product.txt +199 -0
- data/identifiers/service_family.txt +185 -0
- data/identifiers/service_product.txt +255 -0
- data/identifiers/software_class.txt +26 -0
- data/identifiers/software_family.txt +91 -0
- data/identifiers/software_product.txt +333 -0
- data/identifiers/vendor.txt +405 -0
- data/lib/recog.rb +4 -0
- data/lib/recog/db.rb +78 -0
- data/lib/recog/db_manager.rb +31 -0
- data/lib/recog/fingerprint.rb +280 -0
- data/lib/recog/fingerprint/regexp_factory.rb +56 -0
- data/lib/recog/fingerprint/test.rb +18 -0
- data/lib/recog/formatter.rb +51 -0
- data/lib/recog/match_reporter.rb +77 -0
- data/lib/recog/matcher.rb +94 -0
- data/lib/recog/matcher_factory.rb +14 -0
- data/lib/recog/nizer.rb +347 -0
- data/lib/recog/verifier.rb +39 -0
- data/lib/recog/verifier_factory.rb +13 -0
- data/lib/recog/verify_reporter.rb +86 -0
- data/lib/recog/version.rb +3 -0
- data/misc/convert_mysql_err +61 -0
- data/misc/order.xsl +17 -0
- data/recog-intrigue.gemspec +45 -0
- data/requirements.txt +2 -0
- data/spec/data/best_os_match_1.yml +17 -0
- data/spec/data/best_os_match_2.yml +17 -0
- data/spec/data/best_service_match_1.yml +17 -0
- data/spec/data/smb_native_os.txt +25 -0
- data/spec/data/test_fingerprints.xml +36 -0
- data/spec/data/verification_fingerprints.xml +86 -0
- data/spec/data/whitespaced_fingerprint.xml +5 -0
- data/spec/lib/fingerprint_self_test_spec.rb +174 -0
- data/spec/lib/recog/db_spec.rb +98 -0
- data/spec/lib/recog/fingerprint/regexp_factory_spec.rb +73 -0
- data/spec/lib/recog/fingerprint_spec.rb +112 -0
- data/spec/lib/recog/formatter_spec.rb +69 -0
- data/spec/lib/recog/match_reporter_spec.rb +91 -0
- data/spec/lib/recog/nizer_spec.rb +330 -0
- data/spec/lib/recog/verify_reporter_spec.rb +113 -0
- data/spec/spec_helper.rb +82 -0
- data/update_cpes.py +186 -0
- data/xml/apache_modules.xml +1911 -0
- data/xml/apache_os.xml +273 -0
- data/xml/architecture.xml +36 -0
- data/xml/dns_versionbind.xml +761 -0
- data/xml/fingerprints.xsd +128 -0
- data/xml/ftp_banners.xml +1553 -0
- data/xml/h323_callresp.xml +603 -0
- data/xml/hp_pjl_id.xml +358 -0
- data/xml/html_title.xml +1630 -0
- data/xml/http_cookies.xml +411 -0
- data/xml/http_servers.xml +3195 -0
- data/xml/http_wwwauth.xml +595 -0
- data/xml/imap_banners.xml +245 -0
- data/xml/ldap_searchresult.xml +711 -0
- data/xml/mdns_device-info_txt.xml +1796 -0
- data/xml/mdns_workstation_txt.xml +15 -0
- data/xml/mysql_banners.xml +1649 -0
- data/xml/mysql_error.xml +871 -0
- data/xml/nntp_banners.xml +82 -0
- data/xml/ntp_banners.xml +1223 -0
- data/xml/operating_system.xml +629 -0
- data/xml/pop_banners.xml +499 -0
- data/xml/rsh_resp.xml +76 -0
- data/xml/rtsp_servers.xml +76 -0
- data/xml/sip_banners.xml +359 -0
- data/xml/sip_user_agents.xml +221 -0
- data/xml/smb_native_lm.xml +62 -0
- data/xml/smb_native_os.xml +662 -0
- data/xml/smtp_banners.xml +1690 -0
- data/xml/smtp_debug.xml +39 -0
- data/xml/smtp_ehlo.xml +49 -0
- data/xml/smtp_expn.xml +82 -0
- data/xml/smtp_help.xml +157 -0
- data/xml/smtp_mailfrom.xml +20 -0
- data/xml/smtp_noop.xml +44 -0
- data/xml/smtp_quit.xml +29 -0
- data/xml/smtp_rcptto.xml +25 -0
- data/xml/smtp_rset.xml +26 -0
- data/xml/smtp_turn.xml +26 -0
- data/xml/smtp_vrfy.xml +89 -0
- data/xml/snmp_sysdescr.xml +6507 -0
- data/xml/snmp_sysobjid.xml +430 -0
- data/xml/ssh_banners.xml +1968 -0
- data/xml/telnet_banners.xml +1595 -0
- data/xml/x11_banners.xml +232 -0
- data/xml/x509_issuers.xml +134 -0
- data/xml/x509_subjects.xml +1268 -0
- metadata +304 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'recog/db'
|
2
|
+
require 'regexp_parser'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
describe Recog::DB do
|
6
|
+
let(:schema) { Nokogiri::XML::Schema(open(File.expand_path(File.join(%w(xml fingerprints.xsd))))) }
|
7
|
+
Dir[File.expand_path File.join('xml', '*.xml')].each do |xml_file_name|
|
8
|
+
|
9
|
+
describe "##{File.basename(xml_file_name)}" do
|
10
|
+
|
11
|
+
it "is valid XML" do
|
12
|
+
doc = Nokogiri::XML(open(xml_file_name))
|
13
|
+
errors = schema.validate(doc)
|
14
|
+
expect(errors).to be_empty, "#{xml_file_name} is invalid recog XML -- #{errors.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
db = Recog::DB.new(xml_file_name)
|
18
|
+
|
19
|
+
it "has a match key" do
|
20
|
+
expect(db.match_key).not_to be_nil
|
21
|
+
expect(db.match_key).not_to be_empty
|
22
|
+
end
|
23
|
+
|
24
|
+
it "has valid 'preference' value" do
|
25
|
+
# Reserve values below 0.10 and above 0.90 for users
|
26
|
+
# See xml/fingerprints.xsd
|
27
|
+
expect(db.preference.class).to be ::Float
|
28
|
+
expect(db.preference).to be_between(0.10, 0.90)
|
29
|
+
end
|
30
|
+
|
31
|
+
fp_descriptions = []
|
32
|
+
db.fingerprints.each_index do |i|
|
33
|
+
fp = db.fingerprints[i]
|
34
|
+
|
35
|
+
it "doesn't have a duplicate description" do
|
36
|
+
if fp_descriptions.include?(fp.name)
|
37
|
+
fail "'#{fp.name}'s description is not unique"
|
38
|
+
else
|
39
|
+
fp_descriptions << fp.name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#{fp.name}" do
|
44
|
+
param_names = []
|
45
|
+
it "has consistent os.device and hw.device" do
|
46
|
+
if fp.params['os.device'] && fp.params['hw.device'] && (fp.params['os.device'] != fp.params['hw.device'])
|
47
|
+
fail "#{fp.name} has both hw.device and os.device but with differing values"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
fp.params.each do |param_name, pos_value|
|
51
|
+
pos, value = pos_value
|
52
|
+
it "has valid looking fingerprint parameter names" do
|
53
|
+
unless param_name =~ /^(?:cookie|[^\.]+\..*)$/
|
54
|
+
fail "'#{param_name}' is invalid"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "doesn't have param values for capture params" do
|
59
|
+
if pos > 0 && !value.to_s.empty?
|
60
|
+
fail "'#{fp.name}'s #{param_name} is a non-zero pos but specifies a value of '#{value}'"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "has parameter values other than General, Server or Unknown, which are not helpful" do
|
65
|
+
if pos == 0 && value =~ /^(?i:general|server|unknown)$/
|
66
|
+
fail "'#{param_name}' has general/server/unknown value '#{value}'"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "doesn't omit values for non-capture params" do
|
71
|
+
if pos == 0 && value.to_s.empty?
|
72
|
+
fail "'#{fp.name}'s #{param_name} is not a capture (pos=0) but doesn't specify a value"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "doesn't have duplicate params" do
|
77
|
+
if param_names.include?(param_name)
|
78
|
+
fail "'#{fp.name}'s has duplicate #{param_name}"
|
79
|
+
else
|
80
|
+
param_names << param_name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "uses interpolation correctly" do
|
85
|
+
if pos == 0 && /\{(?<interpolated>[^\s{}]+)\}/ =~ value
|
86
|
+
unless fp.params.key?(interpolated)
|
87
|
+
fail "'#{fp.name}' uses interpolated value '#{interpolated}' that does not exist"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "#{fp.regex}" do
|
95
|
+
|
96
|
+
it "has a valid looking name" do
|
97
|
+
expect(fp.name).not_to be_nil
|
98
|
+
expect(fp.name).not_to be_empty
|
99
|
+
end
|
100
|
+
|
101
|
+
it "has a regex" do
|
102
|
+
expect(fp.regex).not_to be_nil
|
103
|
+
expect(fp.regex.class).to be ::Regexp
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'uses capturing regular expressions properly' do
|
107
|
+
# the list of index-based captures that the fingerprint is expecting
|
108
|
+
expected_capture_positions = fp.params.values.map(&:first).map(&:to_i).select { |position| position > 0 }
|
109
|
+
if fp.params.empty? && expected_capture_positions.size > 0
|
110
|
+
fail "Non-asserting fingerprint with regex #{fp.regex} captures #{expected_capture_positions.size} time(s); 0 are needed"
|
111
|
+
else
|
112
|
+
# parse the regex and count the number of captures
|
113
|
+
actual_capture_positions = []
|
114
|
+
capture_number = 1
|
115
|
+
Regexp::Scanner.scan(fp.regex).each do |token_parts|
|
116
|
+
if token_parts.first == :group && ![:close, :passive, :options, :options_switch].include?(token_parts[1])
|
117
|
+
actual_capture_positions << capture_number
|
118
|
+
capture_number += 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
# compare the captures actually performed to those being used and ensure that they contain
|
122
|
+
# the same elements regardless of order, preventing, over-, under- and other forms of mis-capturing.
|
123
|
+
actual_capture_positions = actual_capture_positions.sort.uniq
|
124
|
+
expected_capture_positions = expected_capture_positions.sort.uniq
|
125
|
+
expect(actual_capture_positions).to eq(expected_capture_positions),
|
126
|
+
"Regex has #{actual_capture_positions.size} capture groups, but the fingerprint expected #{expected_capture_positions.size} extractions."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Not yet enforced
|
131
|
+
# it "has test cases" do
|
132
|
+
# expect(fp.tests.length).not_to equal(0)
|
133
|
+
# end
|
134
|
+
|
135
|
+
it "Has a reasonable number (<= 20) of test cases" do
|
136
|
+
expect(fp.tests.length).to be <= 20
|
137
|
+
end
|
138
|
+
|
139
|
+
fp_examples = []
|
140
|
+
fp.tests.each do |example|
|
141
|
+
it "doesn't have a duplicate examples" do
|
142
|
+
if fp_examples.include?(example.content)
|
143
|
+
fail "'#{fp.name}' has duplicate example '#{example.content}'"
|
144
|
+
else
|
145
|
+
fp_examples << example.content
|
146
|
+
end
|
147
|
+
end
|
148
|
+
it "Example '#{example.content}' matches this regex" do
|
149
|
+
match = fp.match(example.content)
|
150
|
+
expect(match).to_not be_nil, 'Regex did not match'
|
151
|
+
# test any extractions specified in the example
|
152
|
+
example.attributes.each_pair do |k,v|
|
153
|
+
next if k == '_encoding'
|
154
|
+
expect(match[k]).to eq(v), "Regex didn't extract expected value for fingerprint attribute #{k} -- got #{match[k]} instead of #{v}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
it "Example '#{example.content}' matches this regex first" do
|
159
|
+
db.fingerprints.slice(0, i).each_index do |previous_i|
|
160
|
+
prev_fp = db.fingerprints[previous_i]
|
161
|
+
prev_fp.tests.each do |prev_example|
|
162
|
+
match = prev_fp.match(example.content)
|
163
|
+
expect(match).to be_nil, "Matched regex ##{previous_i} (#{db.fingerprints[previous_i].regex}) rather than ##{i} (#{db.fingerprints[i].regex})"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'recog/db'
|
2
|
+
|
3
|
+
describe Recog::DB do
|
4
|
+
let(:xml_file) { File.expand_path File.join('spec', 'data', 'test_fingerprints.xml') }
|
5
|
+
subject { Recog::DB.new(xml_file) }
|
6
|
+
|
7
|
+
describe "#fingerprints" do
|
8
|
+
subject(:fingerprints) { described_class.new(xml_file).fingerprints }
|
9
|
+
|
10
|
+
it { is_expected.to be_a(Enumerable) }
|
11
|
+
|
12
|
+
context "with only a pattern" do
|
13
|
+
subject(:entry) { described_class.new(xml_file).fingerprints[0] }
|
14
|
+
|
15
|
+
it "has a blank name with no description" do
|
16
|
+
expect(entry.name).to be_empty
|
17
|
+
end
|
18
|
+
|
19
|
+
it "has a pattern" do
|
20
|
+
expect(entry.regex.source).to eq(".*\\(iSeries\\).*")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "has no params" do
|
24
|
+
expect(entry.params).to be_empty
|
25
|
+
end
|
26
|
+
|
27
|
+
it "has no tests" do
|
28
|
+
expect(entry.tests).to be_empty
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with params" do
|
33
|
+
subject(:entry) { described_class.new(xml_file).fingerprints[1] }
|
34
|
+
|
35
|
+
it "has a name" do
|
36
|
+
expect(entry.name).to eq('PalmOS')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "has a pattern" do
|
40
|
+
expect(entry.regex.source).to eq(".*\\(PalmOS\\).*")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "has params" do
|
44
|
+
expect(entry.params).to eq({"os.vendor"=>[1, "Palm"], "os.device"=>[2, "General"]})
|
45
|
+
end
|
46
|
+
|
47
|
+
it "has no tests" do
|
48
|
+
expect(entry.tests).to be_empty
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with pattern flags" do
|
53
|
+
subject(:entry) { described_class.new(xml_file).fingerprints[2] }
|
54
|
+
|
55
|
+
it "has a name and only uses the first value" do
|
56
|
+
expect(entry.name).to eq('HP Designjet printer')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'creates a Regexp with expected flags' do
|
60
|
+
expect(entry.regex).to be_a(Regexp)
|
61
|
+
expect(entry.regex.options).to eq(Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::IGNORECASE)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "has a pattern" do
|
65
|
+
expect(entry.regex).to be_a(Regexp)
|
66
|
+
expect(entry.regex.source).to eq("(designjet \\S+)")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "has params" do
|
70
|
+
expect(entry.params).to eq({"service.vendor"=>[0, "HP"]})
|
71
|
+
end
|
72
|
+
|
73
|
+
it "has no tests" do
|
74
|
+
expect(entry.tests).to be_empty
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "with test" do
|
79
|
+
subject(:entry) { described_class.new(xml_file).fingerprints[3] }
|
80
|
+
|
81
|
+
it "has a name" do
|
82
|
+
expect(entry.name).to eq('HP JetDirect Printer')
|
83
|
+
end
|
84
|
+
|
85
|
+
it "has a pattern" do
|
86
|
+
expect(entry.regex.source).to eq("laserjet (.*)(?: series)?")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "has params" do
|
90
|
+
expect(entry.params).to eq({"service.vendor"=>[0, "HP"]})
|
91
|
+
end
|
92
|
+
|
93
|
+
it "has no tests" do
|
94
|
+
expect(entry.tests.map(&:content)).to match_array(["HP LaserJet 4100 Series", "HP LaserJet 2200"])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
require 'recog/fingerprint/regexp_factory'
|
3
|
+
|
4
|
+
describe Recog::Fingerprint::RegexpFactory do
|
5
|
+
|
6
|
+
describe 'FLAG_MAP' do
|
7
|
+
subject { described_class::FLAG_MAP }
|
8
|
+
|
9
|
+
it "should have the right number of flags" do
|
10
|
+
expect(subject.size).to be 5
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '.build' do
|
15
|
+
subject { described_class.build(pattern, options) }
|
16
|
+
|
17
|
+
let(:pattern) { 'Apache/(\d+)' }
|
18
|
+
let(:options) { [ 'REG_ICASE' ] }
|
19
|
+
|
20
|
+
it { is_expected.to be_a(Regexp) }
|
21
|
+
it { is_expected.to match('Apache/2') }
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.build_options' do
|
26
|
+
subject { described_class.build_options(flags) }
|
27
|
+
|
28
|
+
let(:flags) { [ ] }
|
29
|
+
it { is_expected.to be_a(Integer) }
|
30
|
+
|
31
|
+
context 'without any explicit flags' do
|
32
|
+
let(:flags) { [ ] }
|
33
|
+
specify "sets default flags" do
|
34
|
+
expect(subject).to be Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with REG_ICASE' do
|
39
|
+
let(:flags) { [ 'REG_ICASE' ] }
|
40
|
+
specify "sets IGNORECASE" do
|
41
|
+
expect(subject).to be (Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::IGNORECASE)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with REG_DOT_NEWLINE' do
|
46
|
+
let(:flags) { [ 'REG_DOT_NEWLINE' ] }
|
47
|
+
specify "sets MULTILINE" do
|
48
|
+
expect(subject).to be (Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::MULTILINE)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with REG_LINE_ANY_CRLF' do
|
53
|
+
let(:flags) { [ 'REG_LINE_ANY_CRLF' ] }
|
54
|
+
specify "sets MULTILINE" do
|
55
|
+
expect(subject).to be (Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::MULTILINE)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'with multiple flags' do
|
60
|
+
let(:flags) { [ 'REG_LINE_ANY_CRLF', 'REG_ICASE' ] }
|
61
|
+
specify "sets correct flags" do
|
62
|
+
expect(subject).to be (Recog::Fingerprint::RegexpFactory::DEFAULT_FLAGS | Regexp::MULTILINE | Regexp::IGNORECASE)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with invalid flags' do
|
67
|
+
let(:flags) { %w(SYN ACK FIN) } # oh, wrong flags!
|
68
|
+
specify 'raises and lists supported/unsupported flags' do
|
69
|
+
expect { subject }.to raise_error(/SYN,ACK,FIN. Must be one of: .+/)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'recog/fingerprint'
|
3
|
+
|
4
|
+
describe Recog::Fingerprint do
|
5
|
+
context "whitespace" do
|
6
|
+
let(:xml) do
|
7
|
+
path = File.expand_path(File.join('spec', 'data', 'whitespaced_fingerprint.xml'))
|
8
|
+
doc = Nokogiri::XML(IO.read(path))
|
9
|
+
doc.xpath("//fingerprint").first
|
10
|
+
end
|
11
|
+
subject { Recog::Fingerprint.new(xml) }
|
12
|
+
|
13
|
+
describe "#name" do
|
14
|
+
it "properly squashes whitespace" do
|
15
|
+
expect(subject.name).to eq('I love whitespace!')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#verification" do
|
21
|
+
let(:xml_file) { File.expand_path(File.join('spec', 'data', 'verification_fingerprints.xml')) }
|
22
|
+
let(:doc) { Nokogiri::XML(IO.read(xml_file)) }
|
23
|
+
|
24
|
+
context "0 params" do
|
25
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[0]) }
|
26
|
+
|
27
|
+
it "does not yield if a fingerprint has 0 parameters" do
|
28
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "0 capture groups" do
|
33
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[1]) }
|
34
|
+
|
35
|
+
it "does not yield if a fingerprint has parameters, but 0 are defined by a capture group " do
|
36
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "0 examples" do
|
41
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[2]) }
|
42
|
+
|
43
|
+
it "does not yield if a fingerprint has 0 examples" do
|
44
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "1 capture group, 1 example" do
|
49
|
+
|
50
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[3]) }
|
51
|
+
|
52
|
+
it "does not yield when one capture group parameter is tested for in one example" do
|
53
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "2 capture groups, 1 example" do
|
58
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[4]) }
|
59
|
+
|
60
|
+
it "does not yield when two capture group parameters are tested for in one example" do
|
61
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "2 capture groups, 2 examples, 1 in each" do
|
66
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[5]) }
|
67
|
+
|
68
|
+
it "does not yield when two capture group parameters are tested for in two examples, one parameter in each" do
|
69
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.not_to yield_control
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "1 missing capture group, 1 example" do
|
74
|
+
|
75
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[6]) }
|
76
|
+
|
77
|
+
it "identifies when a parameter defined by a capture group is not included in one example" do
|
78
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.to yield_successive_args([:warn, String])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "2 missing capture groups, 1 example" do
|
83
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[7]) }
|
84
|
+
|
85
|
+
it "identifies when two parameters defined by a capture groups are not included in one example" do
|
86
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.to yield_successive_args([:warn, String], [:warn, String])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "1 missing capture group, 2 examples" do
|
91
|
+
|
92
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[8]) }
|
93
|
+
|
94
|
+
it "identifies when a parameter defined by a capture group is not included in one example" do
|
95
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.to yield_successive_args([:warn, String])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "2 missing capture groups, 2 examples" do
|
100
|
+
let(:entry) { described_class.new(doc.xpath("//fingerprints/fingerprint")[9]) }
|
101
|
+
|
102
|
+
it "identifies when two parameters defined by a capture groups are not included in one example" do
|
103
|
+
expect { |unused| entry.verify_tests_have_capture_groups(&unused) }.to yield_successive_args([:warn, String], [:warn, String])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
skip "value interpolation" do
|
110
|
+
# TODO
|
111
|
+
end
|
112
|
+
end
|