ruby-nikto 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +11 -0
- data/.github/workflows/ruby.yml +31 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.specopts +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +9 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +22 -0
- data/README.md +80 -0
- data/Rakefile +10 -0
- data/gemspec.yml +24 -0
- data/lib/nikto/command.rb +188 -0
- data/lib/nikto/version.rb +4 -0
- data/lib/nikto/xml/item.rb +55 -0
- data/lib/nikto/xml/scan_details.rb +175 -0
- data/lib/nikto/xml/statistics.rb +64 -0
- data/lib/nikto/xml.rb +230 -0
- data/lib/nikto.rb +2 -0
- data/ruby-nikto.gemspec +58 -0
- data/spec/command_spec.rb +97 -0
- data/spec/fixtures/nikto.xml +47 -0
- data/spec/nikto_spec.rb +8 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/xml/item_spec.rb +61 -0
- data/spec/xml/scan_details_spec.rb +181 -0
- data/spec/xml/statistics_spec.rb +57 -0
- data/spec/xml_spec.rb +190 -0
- metadata +121 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Nikto
|
4
|
+
class XML
|
5
|
+
#
|
6
|
+
# Represents a `statistics` XML element.
|
7
|
+
#
|
8
|
+
class Statistics
|
9
|
+
|
10
|
+
#
|
11
|
+
# Initializes the statistics object.
|
12
|
+
#
|
13
|
+
# @param [Nokogiri::XML::Node] node
|
14
|
+
# The XML node for the `statistics` XML element.
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
#
|
18
|
+
def initialize(node)
|
19
|
+
@node = node
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# The number of seconds elapsed.
|
24
|
+
#
|
25
|
+
# @return [Intger]
|
26
|
+
# The parsed value of the `elapsed` attribute.
|
27
|
+
#
|
28
|
+
def elapsed
|
29
|
+
@elapsed ||= @node['elapsed'].to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# The number of items found.
|
34
|
+
#
|
35
|
+
# @return [Intger]
|
36
|
+
# The parsed value of the `itemsfound` attribute.
|
37
|
+
#
|
38
|
+
def items_found
|
39
|
+
@items_found ||= @node['itemsfound'].to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# The number of items tested.
|
44
|
+
#
|
45
|
+
# @return [Intger]
|
46
|
+
# The parsed value of the `itemstested` attribute.
|
47
|
+
#
|
48
|
+
def items_tested
|
49
|
+
@items_tested ||= @node['itemstested'].to_i
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# The end-time of the scan.
|
54
|
+
#
|
55
|
+
# @return [Time]
|
56
|
+
# The parsed value of the `endtime` attribute.
|
57
|
+
#
|
58
|
+
def end_time
|
59
|
+
@end_time ||= Time.parse(@node['endtime'])
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/nikto/xml.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'nikto/xml/scan_details'
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module Nikto
|
6
|
+
#
|
7
|
+
# Represents an nikto XML file or XML data.
|
8
|
+
#
|
9
|
+
# ## Example
|
10
|
+
#
|
11
|
+
# require 'nikto/xml'
|
12
|
+
#
|
13
|
+
# Nikto::XML.open('nikto.xml') do |xml|
|
14
|
+
# xml.each_scan_details do |scan_details|
|
15
|
+
# puts "#{scan_details.site_name}"
|
16
|
+
#
|
17
|
+
# scan_details.each_item do |item|
|
18
|
+
# puts " #{item.uri}"
|
19
|
+
# puts
|
20
|
+
# puts " #{item.description}"
|
21
|
+
# puts
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
class XML
|
27
|
+
|
28
|
+
# The parsed XML document.
|
29
|
+
#
|
30
|
+
# @return [Nokogiri::XML]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
attr_reader :doc
|
34
|
+
|
35
|
+
# The path to the XML file.
|
36
|
+
#
|
37
|
+
# @return [String, nil]
|
38
|
+
attr_reader :path
|
39
|
+
|
40
|
+
#
|
41
|
+
# Creates a new XML object.
|
42
|
+
#
|
43
|
+
# @param [Nokogiri::XML] doc
|
44
|
+
# The parsed XML document.
|
45
|
+
#
|
46
|
+
# @param [String, nil] path
|
47
|
+
# The path to the XML file.
|
48
|
+
#
|
49
|
+
# @yield [xml]
|
50
|
+
# If a block is given, it will be passed the newly created XML
|
51
|
+
# parser.
|
52
|
+
#
|
53
|
+
# @yieldparam [XML] xml
|
54
|
+
# The newly created XML parser.
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
#
|
58
|
+
def initialize(doc, path: nil)
|
59
|
+
@doc = doc
|
60
|
+
@path = File.expand_path(path) if path
|
61
|
+
|
62
|
+
yield self if block_given?
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Parses the given XML String.
|
67
|
+
#
|
68
|
+
# @param [String] xml
|
69
|
+
# The XML String.
|
70
|
+
#
|
71
|
+
# @yield [xml]
|
72
|
+
# If a block is given, it will be passed the newly created XML
|
73
|
+
# parser.
|
74
|
+
#
|
75
|
+
# @yieldparam [XML] xml
|
76
|
+
# The newly created XML parser.
|
77
|
+
#
|
78
|
+
# @return [XML]
|
79
|
+
# The parsed XML.
|
80
|
+
#
|
81
|
+
# @api public
|
82
|
+
#
|
83
|
+
def self.parse(xml,&block)
|
84
|
+
new(Nokogiri::XML(xml),&block)
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Opens an parses an XML file.
|
89
|
+
#
|
90
|
+
# @param [String] path
|
91
|
+
# The path to the XML file.
|
92
|
+
#
|
93
|
+
# @yield [xml]
|
94
|
+
# If a block is given, it will be passed the newly created XML
|
95
|
+
# parser.
|
96
|
+
#
|
97
|
+
# @yieldparam [XML] xml
|
98
|
+
# The newly created XML parser.
|
99
|
+
#
|
100
|
+
# @return [XML]
|
101
|
+
# The parsed XML.
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
#
|
105
|
+
def self.open(path,&block)
|
106
|
+
path = File.expand_path(path)
|
107
|
+
doc = Nokogiri::XML(File.open(path))
|
108
|
+
|
109
|
+
new(doc, path: path, &block)
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# The `hoststest` value.
|
114
|
+
#
|
115
|
+
# @return [Integer]
|
116
|
+
# The parsed value of the `hoststest` attribute.
|
117
|
+
#
|
118
|
+
def hosts_test
|
119
|
+
@hosts_test ||= @doc.root['@hoststest'].to_i
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Additional command-line options passed to `nikto`.
|
124
|
+
#
|
125
|
+
# @return [String]
|
126
|
+
# The value of the `options` attribute.
|
127
|
+
#
|
128
|
+
def options
|
129
|
+
@doc.root['options']
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# When the scan started.
|
134
|
+
#
|
135
|
+
# @return [Time]
|
136
|
+
# The parsed value of the `scanstart` attribute.
|
137
|
+
#
|
138
|
+
def scan_start
|
139
|
+
@scan_start ||= Time.parse(@doc.root['scanstart'])
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# When the scan completed.
|
144
|
+
#
|
145
|
+
# @return [Time]
|
146
|
+
# The parsed value `scanned` attribute.
|
147
|
+
#
|
148
|
+
def scan_end
|
149
|
+
@scan_end ||= Time.parse(@doc.root['scanend'])
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# The duration of the scan.
|
154
|
+
#
|
155
|
+
# @return [String]
|
156
|
+
# The value of the `scanelapsed` attribute.
|
157
|
+
#
|
158
|
+
def scan_elapsed
|
159
|
+
@doc.root['scanelapsed']
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# The Nikto XML schema version.
|
164
|
+
#
|
165
|
+
# @return [String]
|
166
|
+
# The value of the `nxmlversion` attribute.
|
167
|
+
#
|
168
|
+
def nikto_xml_version
|
169
|
+
@doc.root['nxmlversion']
|
170
|
+
end
|
171
|
+
|
172
|
+
#
|
173
|
+
# Parses each `scandetails` child element.
|
174
|
+
#
|
175
|
+
# @yield [scan_details]
|
176
|
+
# If a block is given, it will be yielded each scan details object.
|
177
|
+
#
|
178
|
+
# @yieldparam [ScanDetails] scan_details
|
179
|
+
# A scan details object.
|
180
|
+
#
|
181
|
+
# @return [Enumerator]
|
182
|
+
# If no block is given, an Enumerator will be returned.
|
183
|
+
#
|
184
|
+
def each_scan_details
|
185
|
+
return enum_for(__method__) unless block_given?
|
186
|
+
|
187
|
+
@doc.xpath('/niktoscan/scandetails').each do |node|
|
188
|
+
yield ScanDetails.new(node)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# The scan details.
|
194
|
+
#
|
195
|
+
# @return [Array<ScanDetails>]
|
196
|
+
#
|
197
|
+
def scan_details
|
198
|
+
each_scan_details.to_a
|
199
|
+
end
|
200
|
+
|
201
|
+
alias each_target each_scan_details
|
202
|
+
|
203
|
+
alias targets scan_details
|
204
|
+
|
205
|
+
#
|
206
|
+
# The first scan details object.
|
207
|
+
#
|
208
|
+
# @return [ScanDetails, nil]
|
209
|
+
#
|
210
|
+
def target
|
211
|
+
each_target.first
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
# Converts the XML object to a String.
|
216
|
+
#
|
217
|
+
# @return [String]
|
218
|
+
# The path to the XML if {#path} is set, or the XML if the XML was parsed
|
219
|
+
# from a String.
|
220
|
+
#
|
221
|
+
def to_s
|
222
|
+
if @path
|
223
|
+
@path
|
224
|
+
else
|
225
|
+
@doc.to_s
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
end
|
data/lib/nikto.rb
ADDED
data/ruby-nikto.gemspec
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gemspec = YAML.load_file('gemspec.yml')
|
5
|
+
|
6
|
+
gem.name = gemspec.fetch('name')
|
7
|
+
gem.version = gemspec.fetch('version') do
|
8
|
+
lib_dir = File.join(File.dirname(__FILE__),'lib')
|
9
|
+
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
|
10
|
+
|
11
|
+
require File.join('nikto','version')
|
12
|
+
Nikto::VERSION
|
13
|
+
end
|
14
|
+
|
15
|
+
gem.summary = gemspec['summary']
|
16
|
+
gem.description = gemspec['description']
|
17
|
+
gem.licenses = Array(gemspec['license'])
|
18
|
+
gem.authors = Array(gemspec['authors'])
|
19
|
+
gem.email = gemspec['email']
|
20
|
+
gem.homepage = gemspec['homepage']
|
21
|
+
gem.metadata = gemspec['metadata'] if gemspec['metadata']
|
22
|
+
|
23
|
+
glob = lambda { |patterns| gem.files & Dir[*patterns] }
|
24
|
+
|
25
|
+
gem.files = if gemspec['files'] then glob[gemspec['files']]
|
26
|
+
else `git ls-files`.split($/)
|
27
|
+
end
|
28
|
+
|
29
|
+
gem.executables = gemspec.fetch('executables') do
|
30
|
+
glob['bin/*'].map { |path| File.basename(path) }
|
31
|
+
end
|
32
|
+
|
33
|
+
gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
|
34
|
+
gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
|
35
|
+
|
36
|
+
gem.require_paths = Array(gemspec.fetch('require_paths') {
|
37
|
+
%w[ext lib].select { |dir| File.directory?(dir) }
|
38
|
+
})
|
39
|
+
|
40
|
+
gem.requirements = gemspec['requirements']
|
41
|
+
gem.required_ruby_version = gemspec['required_ruby_version']
|
42
|
+
gem.required_rubygems_version = gemspec['required_rubygems_version']
|
43
|
+
gem.post_install_message = gemspec['post_install_message']
|
44
|
+
|
45
|
+
split = lambda { |string| string.split(/,\s*/) }
|
46
|
+
|
47
|
+
if gemspec['dependencies']
|
48
|
+
gemspec['dependencies'].each do |name,versions|
|
49
|
+
gem.add_dependency(name,split[versions])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if gemspec['development_dependencies']
|
54
|
+
gemspec['development_dependencies'].each do |name,versions|
|
55
|
+
gem.add_development_dependency(name,split[versions])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nikto/command'
|
3
|
+
|
4
|
+
describe Nikto::Command do
|
5
|
+
describe described_class::OptionString do
|
6
|
+
let(:map) do
|
7
|
+
{
|
8
|
+
:a => '1',
|
9
|
+
:b => '2',
|
10
|
+
:c => '3'
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
subject { described_class.new(map) }
|
15
|
+
|
16
|
+
describe "#initialize" do
|
17
|
+
it "must initialize #type to be a Map of the options" do
|
18
|
+
expect(subject.type).to be_kind_of(CommandMapper::Types::Map)
|
19
|
+
expect(subject.type.map).to eq(map)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "must set #separator to ''" do
|
23
|
+
expect(subject.separator).to eq('')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe described_class::PortList do
|
29
|
+
describe "#validate" do
|
30
|
+
context "when given a single port number" do
|
31
|
+
let(:value) { 443 }
|
32
|
+
|
33
|
+
it "must return true" do
|
34
|
+
expect(subject.validate(value)).to be(true)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "when given a Range of port numbers" do
|
39
|
+
let(:value) { (1..1024) }
|
40
|
+
|
41
|
+
it "must return true" do
|
42
|
+
expect(subject.validate(value)).to be(true)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when given an Array of port numbers" do
|
47
|
+
let(:value) { [80, 443] }
|
48
|
+
|
49
|
+
it "must return true" do
|
50
|
+
expect(subject.validate(value)).to be(true)
|
51
|
+
end
|
52
|
+
|
53
|
+
context "and the Array contains Ranges" do
|
54
|
+
let(:value) { [80, (1..42), 443] }
|
55
|
+
|
56
|
+
it "must return true" do
|
57
|
+
expect(subject.validate(value)).to be(true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#format" do
|
64
|
+
context "when given a single port number" do
|
65
|
+
let(:value) { 443 }
|
66
|
+
|
67
|
+
it "must return the formatted port number" do
|
68
|
+
expect(subject.format(value)).to eq(value.to_s)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when given a Range of port numbers" do
|
73
|
+
let(:value) { (1..1024) }
|
74
|
+
|
75
|
+
it "must return the formatted port number range (ex: 1-102)" do
|
76
|
+
expect(subject.format(value)).to eq("#{value.begin}-#{value.end}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when given an Array of port numbers" do
|
81
|
+
let(:value) { [80, 443] }
|
82
|
+
|
83
|
+
it "must return the formatted list of port numbers" do
|
84
|
+
expect(subject.format(value)).to eq(value.join(','))
|
85
|
+
end
|
86
|
+
|
87
|
+
context "and the Array contains Ranges" do
|
88
|
+
let(:value) { [80, (1..42), 443] }
|
89
|
+
|
90
|
+
it "must return the formatted list of port numbers and port ranges" do
|
91
|
+
expect(subject.format(value)).to eq("#{value[0]},#{value[1].begin}-#{value[1].end},#{value[2]}")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<?xml version="1.0" ?>
|
2
|
+
<!DOCTYPE niktoscan SYSTEM "/usr/share/doc/nikto/nikto.dtd">
|
3
|
+
<niktoscan hoststest="0" options="-host example.com -output nikto.xml" version="2.1.6" scanstart="Mon Nov 29 07:22:56 2021" scanend="Wed Dec 31 16:00:00 1969" scanelapsed=" seconds" nxmlversion="1.2">
|
4
|
+
|
5
|
+
<scandetails targetip="93.184.216.34" targethostname="example.com" targetport="80" targetbanner="ECS (sec/974D)" starttime="2021-11-29 07:22:57" sitename="http://example.com:80/" siteip="http://93.184.216.34:80/" hostheader="example.com" errors="0" checks="4587">
|
6
|
+
|
7
|
+
|
8
|
+
<item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
|
9
|
+
<description><![CDATA[#TEMPL_MSG#]]></description>
|
10
|
+
<uri><![CDATA[#TEMPL_URI#]]></uri>
|
11
|
+
<namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
|
12
|
+
<iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
|
13
|
+
</item>
|
14
|
+
|
15
|
+
<item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
|
16
|
+
<description><![CDATA[#TEMPL_MSG#]]></description>
|
17
|
+
<uri><![CDATA[#TEMPL_URI#]]></uri>
|
18
|
+
<namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
|
19
|
+
<iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
|
20
|
+
</item>
|
21
|
+
|
22
|
+
<item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
|
23
|
+
<description><![CDATA[#TEMPL_MSG#]]></description>
|
24
|
+
<uri><![CDATA[#TEMPL_URI#]]></uri>
|
25
|
+
<namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
|
26
|
+
<iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
|
27
|
+
</item>
|
28
|
+
|
29
|
+
<item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
|
30
|
+
<description><![CDATA[#TEMPL_MSG#]]></description>
|
31
|
+
<uri><![CDATA[#TEMPL_URI#]]></uri>
|
32
|
+
<namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
|
33
|
+
<iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
|
34
|
+
</item>
|
35
|
+
|
36
|
+
<item id="#ID#" osvdbid="#TEMPL_OSVDB#" osvdblink="#TEMPL_OSVDB_LINK#" method="#TEMPL_HTTP_METHOD#">
|
37
|
+
<description><![CDATA[#TEMPL_MSG#]]></description>
|
38
|
+
<uri><![CDATA[#TEMPL_URI#]]></uri>
|
39
|
+
<namelink><![CDATA[#TEMPL_ITEM_NAME_LINK#]]></namelink>
|
40
|
+
<iplink><![CDATA[#TEMPL_ITEM_IP_LINK#]]></iplink>
|
41
|
+
</item>
|
42
|
+
|
43
|
+
<statistics elapsed="178" itemsfound="5" itemstested="4587" endtime="2021-11-29 07:25:55" />
|
44
|
+
</scandetails>
|
45
|
+
|
46
|
+
|
47
|
+
</niktoscan>
|
data/spec/nikto_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nikto/xml/item'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
describe Nikto::XML::Item do
|
6
|
+
let(:fixtures_dir) { File.expand_path(File.join(__dir__,'..','fixtures')) }
|
7
|
+
let(:path) { File.join(fixtures_dir,'nikto.xml') }
|
8
|
+
let(:xml) { File.read(path) }
|
9
|
+
let(:doc) { Nokogiri::XML(File.open(path)) }
|
10
|
+
let(:node) { doc.at_xpath('/niktoscan/scandetails/item') }
|
11
|
+
|
12
|
+
subject { described_class.new(node) }
|
13
|
+
|
14
|
+
describe "#description" do
|
15
|
+
subject { super().description }
|
16
|
+
|
17
|
+
it "must return a String" do
|
18
|
+
expect(subject).to be_kind_of(String)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "must return the inner text of the 'description' child element" do
|
22
|
+
expect(subject).to eq(node.at_xpath('description').inner_text)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#uri" do
|
27
|
+
subject { super().uri }
|
28
|
+
|
29
|
+
it "must return a String" do
|
30
|
+
expect(subject).to be_kind_of(String)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "must return the inner text of the 'uri' child element" do
|
34
|
+
expect(subject).to eq(node.at_xpath('uri').inner_text)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#name_link" do
|
39
|
+
subject { super().name_link }
|
40
|
+
|
41
|
+
it "must return a String" do
|
42
|
+
expect(subject).to be_kind_of(String)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "must return the inner text of the 'namelink' child element" do
|
46
|
+
expect(subject).to eq(node.at_xpath('namelink').inner_text)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#ip_link" do
|
51
|
+
subject { super().ip_link }
|
52
|
+
|
53
|
+
it "must return a String" do
|
54
|
+
expect(subject).to be_kind_of(String)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "must return the inner text of the 'iplink' child element" do
|
58
|
+
expect(subject).to eq(node.at_xpath('iplink').inner_text)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|