recog-intrigue 2.3.7

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.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  4. data/.github/ISSUE_TEMPLATE/fingerprint_request.md +27 -0
  5. data/.github/PULL_REQUEST_TEMPLATE +24 -0
  6. data/.gitignore +14 -0
  7. data/.rbenv-gemset +1 -0
  8. data/.rspec +3 -0
  9. data/.ruby-gemset +1 -0
  10. data/.ruby-version +1 -0
  11. data/.travis.yml +25 -0
  12. data/.yardopts +1 -0
  13. data/CONTRIBUTING.md +171 -0
  14. data/COPYING +23 -0
  15. data/Gemfile +10 -0
  16. data/LICENSE +7 -0
  17. data/README.md +85 -0
  18. data/Rakefile +22 -0
  19. data/bin/recog_export +81 -0
  20. data/bin/recog_match +55 -0
  21. data/bin/recog_standardize +118 -0
  22. data/bin/recog_verify +64 -0
  23. data/cpe-remap.yaml +134 -0
  24. data/features/data/failing_banners_fingerprints.xml +20 -0
  25. data/features/data/matching_banners_fingerprints.xml +23 -0
  26. data/features/data/multiple_banners_fingerprints.xml +32 -0
  27. data/features/data/no_tests.xml +3 -0
  28. data/features/data/sample_banner.txt +2 -0
  29. data/features/data/successful_tests.xml +18 -0
  30. data/features/data/tests_with_failures.xml +20 -0
  31. data/features/data/tests_with_warnings.xml +17 -0
  32. data/features/match.feature +36 -0
  33. data/features/support/aruba.rb +3 -0
  34. data/features/support/env.rb +6 -0
  35. data/features/verify.feature +48 -0
  36. data/identifiers/README.md +47 -0
  37. data/identifiers/os_architecture.txt +20 -0
  38. data/identifiers/os_device.txt +52 -0
  39. data/identifiers/os_family.txt +160 -0
  40. data/identifiers/os_product.txt +199 -0
  41. data/identifiers/service_family.txt +185 -0
  42. data/identifiers/service_product.txt +255 -0
  43. data/identifiers/software_class.txt +26 -0
  44. data/identifiers/software_family.txt +91 -0
  45. data/identifiers/software_product.txt +333 -0
  46. data/identifiers/vendor.txt +405 -0
  47. data/lib/recog.rb +4 -0
  48. data/lib/recog/db.rb +78 -0
  49. data/lib/recog/db_manager.rb +31 -0
  50. data/lib/recog/fingerprint.rb +280 -0
  51. data/lib/recog/fingerprint/regexp_factory.rb +56 -0
  52. data/lib/recog/fingerprint/test.rb +18 -0
  53. data/lib/recog/formatter.rb +51 -0
  54. data/lib/recog/match_reporter.rb +77 -0
  55. data/lib/recog/matcher.rb +94 -0
  56. data/lib/recog/matcher_factory.rb +14 -0
  57. data/lib/recog/nizer.rb +347 -0
  58. data/lib/recog/verifier.rb +39 -0
  59. data/lib/recog/verifier_factory.rb +13 -0
  60. data/lib/recog/verify_reporter.rb +86 -0
  61. data/lib/recog/version.rb +3 -0
  62. data/misc/convert_mysql_err +61 -0
  63. data/misc/order.xsl +17 -0
  64. data/recog-intrigue.gemspec +45 -0
  65. data/requirements.txt +2 -0
  66. data/spec/data/best_os_match_1.yml +17 -0
  67. data/spec/data/best_os_match_2.yml +17 -0
  68. data/spec/data/best_service_match_1.yml +17 -0
  69. data/spec/data/smb_native_os.txt +25 -0
  70. data/spec/data/test_fingerprints.xml +36 -0
  71. data/spec/data/verification_fingerprints.xml +86 -0
  72. data/spec/data/whitespaced_fingerprint.xml +5 -0
  73. data/spec/lib/fingerprint_self_test_spec.rb +174 -0
  74. data/spec/lib/recog/db_spec.rb +98 -0
  75. data/spec/lib/recog/fingerprint/regexp_factory_spec.rb +73 -0
  76. data/spec/lib/recog/fingerprint_spec.rb +112 -0
  77. data/spec/lib/recog/formatter_spec.rb +69 -0
  78. data/spec/lib/recog/match_reporter_spec.rb +91 -0
  79. data/spec/lib/recog/nizer_spec.rb +330 -0
  80. data/spec/lib/recog/verify_reporter_spec.rb +113 -0
  81. data/spec/spec_helper.rb +82 -0
  82. data/update_cpes.py +186 -0
  83. data/xml/apache_modules.xml +1911 -0
  84. data/xml/apache_os.xml +273 -0
  85. data/xml/architecture.xml +36 -0
  86. data/xml/dns_versionbind.xml +761 -0
  87. data/xml/fingerprints.xsd +128 -0
  88. data/xml/ftp_banners.xml +1553 -0
  89. data/xml/h323_callresp.xml +603 -0
  90. data/xml/hp_pjl_id.xml +358 -0
  91. data/xml/html_title.xml +1630 -0
  92. data/xml/http_cookies.xml +411 -0
  93. data/xml/http_servers.xml +3195 -0
  94. data/xml/http_wwwauth.xml +595 -0
  95. data/xml/imap_banners.xml +245 -0
  96. data/xml/ldap_searchresult.xml +711 -0
  97. data/xml/mdns_device-info_txt.xml +1796 -0
  98. data/xml/mdns_workstation_txt.xml +15 -0
  99. data/xml/mysql_banners.xml +1649 -0
  100. data/xml/mysql_error.xml +871 -0
  101. data/xml/nntp_banners.xml +82 -0
  102. data/xml/ntp_banners.xml +1223 -0
  103. data/xml/operating_system.xml +629 -0
  104. data/xml/pop_banners.xml +499 -0
  105. data/xml/rsh_resp.xml +76 -0
  106. data/xml/rtsp_servers.xml +76 -0
  107. data/xml/sip_banners.xml +359 -0
  108. data/xml/sip_user_agents.xml +221 -0
  109. data/xml/smb_native_lm.xml +62 -0
  110. data/xml/smb_native_os.xml +662 -0
  111. data/xml/smtp_banners.xml +1690 -0
  112. data/xml/smtp_debug.xml +39 -0
  113. data/xml/smtp_ehlo.xml +49 -0
  114. data/xml/smtp_expn.xml +82 -0
  115. data/xml/smtp_help.xml +157 -0
  116. data/xml/smtp_mailfrom.xml +20 -0
  117. data/xml/smtp_noop.xml +44 -0
  118. data/xml/smtp_quit.xml +29 -0
  119. data/xml/smtp_rcptto.xml +25 -0
  120. data/xml/smtp_rset.xml +26 -0
  121. data/xml/smtp_turn.xml +26 -0
  122. data/xml/smtp_vrfy.xml +89 -0
  123. data/xml/snmp_sysdescr.xml +6507 -0
  124. data/xml/snmp_sysobjid.xml +430 -0
  125. data/xml/ssh_banners.xml +1968 -0
  126. data/xml/telnet_banners.xml +1595 -0
  127. data/xml/x11_banners.xml +232 -0
  128. data/xml/x509_issuers.xml +134 -0
  129. data/xml/x509_subjects.xml +1268 -0
  130. metadata +304 -0
@@ -0,0 +1,5 @@
1
+ <fingerprint pattern=".*this doesn't matter.*">
2
+ <description> I love
3
+ whitespace!
4
+ </description>
5
+ </fingerprint>
@@ -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