ronin-web 1.0.0.beta2 → 1.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +0 -4
- data/README.md +0 -4
- data/gemspec.yml +1 -1
- data/lib/ronin/web/cli/commands/reverse_proxy.rb +9 -2
- data/lib/ronin/web/cli/commands/server.rb +7 -1
- data/lib/ronin/web/cli/commands/spider.rb +6 -2
- data/lib/ronin/web/version.rb +1 -1
- data/man/ronin-web-spider.1 +1 -1
- data/man/ronin-web-spider.1.md +1 -1
- data/ronin-web.gemspec +2 -1
- metadata +5 -16
- data/spec/cli/ruby_shell_spec.rb +0 -14
- data/spec/html_spec.rb +0 -43
- data/spec/mechanize_spec.rb +0 -72
- data/spec/spec_helper.rb +0 -8
- data/spec/web_spec.rb +0 -97
- data/spec/xml_spec.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e36e87f767a665ff3dafb90022b8cb0b0d8d7d9ebc23255c873dcebde04d7b1
|
4
|
+
data.tar.gz: af5f942251ddefcfd8940d2de5cc688edbd6e55ba96715d8798b0e85efff33f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e0bc704b1177e0f958c00a0f1f33d606258c78186dc2ac38de85d7188d29e9911aab5d337cb39a7e90798865841b947abd8c6e928325df2a394e7a61b6576a2
|
7
|
+
data.tar.gz: c7dd821942055424f990ab2f5511252308d9b12bf3bdb2ccb07281d523d40066425989d1645a27f464321147973b1ff12410c6bcc79cece614d09efbaf730e57
|
data/.github/workflows/ruby.yml
CHANGED
@@ -21,10 +21,6 @@ jobs:
|
|
21
21
|
uses: ruby/setup-ruby@v1
|
22
22
|
with:
|
23
23
|
ruby-version: ${{ matrix.ruby }}
|
24
|
-
- name: Install libsqlite3
|
25
|
-
run: |
|
26
|
-
sudo apt update -y && \
|
27
|
-
sudo apt install -y --no-install-recommends --no-install-suggests libsqlite3-dev libxml2-dev libxslt1-dev
|
28
24
|
- name: Install dependencies
|
29
25
|
run: bundle install --jobs 4 --retry 3
|
30
26
|
- name: Run tests
|
data/README.md
CHANGED
@@ -147,8 +147,6 @@ puts doc.to_xml
|
|
147
147
|
|
148
148
|
* [Ruby] >= 3.0.0
|
149
149
|
* [nokogiri] ~> 1.4
|
150
|
-
* [libxml2]
|
151
|
-
* [libxslt1]
|
152
150
|
* [nokogiri-ext] ~> 0.1
|
153
151
|
* [nokogiri-diff] ~> 0.2
|
154
152
|
* [mechanize] ~> 2.0
|
@@ -201,8 +199,6 @@ along with ronin-web. If not, see <https://www.gnu.org/licenses/>.
|
|
201
199
|
[nokogiri]: https://nokogiri.org/
|
202
200
|
[nokogiri-ext]: https://github.com/postmodern/nokogiri-ext#readme
|
203
201
|
[nokogiri-diff]: https://github.com/postmodern/nokogiri-diff#readme
|
204
|
-
[libxml2]: http://xmlsoft.org/
|
205
|
-
[libxslt1]: http://xmlsoft.org/XSLT/
|
206
202
|
[mechanize]: https://github.com/sparklemotion/mechanize#readme
|
207
203
|
[open_namespace]: https://github.com/postmodern/open_namespace#readme
|
208
204
|
[ronin-support]: https://github.com/ronin-rb/ronin-support#readme
|
data/gemspec.yml
CHANGED
@@ -41,7 +41,7 @@ dependencies:
|
|
41
41
|
open_namespace: ~> 0.4
|
42
42
|
# Ronin dependencies:
|
43
43
|
ronin-support: ~> 1.0.0.beta2
|
44
|
-
ronin-web-server: ~> 0.1.0.
|
44
|
+
ronin-web-server: ~> 0.1.0.beta3
|
45
45
|
ronin-web-spider: ~> 0.1.0.beta2
|
46
46
|
ronin-web-user_agents: ~> 0.1.0.beta1
|
47
47
|
ronin-core: ~> 0.1.0.beta1
|
@@ -45,7 +45,7 @@ module Ronin
|
|
45
45
|
#
|
46
46
|
# @api private
|
47
47
|
#
|
48
|
-
class
|
48
|
+
class ReverseProxy < Command
|
49
49
|
|
50
50
|
include Core::CLI::Logging
|
51
51
|
|
@@ -142,7 +142,14 @@ module Ronin
|
|
142
142
|
end
|
143
143
|
|
144
144
|
log_info "Starting proxy server on #{options[:host]}:#{options[:port]} ..."
|
145
|
-
|
145
|
+
|
146
|
+
begin
|
147
|
+
proxy.run!(host: options[:host], port: options[:port])
|
148
|
+
rescue Errno::EADDRINUSE => error
|
149
|
+
log_error(error.message)
|
150
|
+
exit(1)
|
151
|
+
end
|
152
|
+
|
146
153
|
log_info "shutting down ..."
|
147
154
|
end
|
148
155
|
|
@@ -144,7 +144,13 @@ module Ronin
|
|
144
144
|
end
|
145
145
|
|
146
146
|
log_info "Starting web server listening on #{App.host}:#{App.port} ..."
|
147
|
-
|
147
|
+
begin
|
148
|
+
App.run!
|
149
|
+
rescue Errno::EADDRINUSE => error
|
150
|
+
log_error(error.message)
|
151
|
+
exit(1)
|
152
|
+
end
|
153
|
+
|
148
154
|
log_info "Shutting down ..."
|
149
155
|
end
|
150
156
|
|
@@ -49,7 +49,7 @@ module Ronin
|
|
49
49
|
# -P, --proxy PROXY Sets the proxy to use.
|
50
50
|
# -H, --header NAME: VALUE Sets a default header
|
51
51
|
# --host-header NAME=VALUE Sets a default header
|
52
|
-
# -u
|
52
|
+
# -u chrome-linux|chrome-macos|chrome-windows|chrome-iphone|chrome-ipad|chrome-android|firefox-linux|firefox-macos|firefox-windows|firefox-iphone|firefox-ipad|firefox-android|safari-macos|safari-iphone|safari-ipad|edge,
|
53
53
|
# --user-agent The User-Agent to use
|
54
54
|
# -U, --user-agent-string STRING The User-Agent string to use
|
55
55
|
# -R, --referer URL Sets the Referer URL
|
@@ -183,7 +183,11 @@ module Ronin
|
|
183
183
|
|
184
184
|
option :user_agent, short: '-u',
|
185
185
|
value: {
|
186
|
-
type:
|
186
|
+
type: Hash[
|
187
|
+
Support::Network::HTTP::UserAgents::ALIASES.keys.map { |key|
|
188
|
+
[key.to_s.tr('_','-'), key]
|
189
|
+
}
|
190
|
+
]
|
187
191
|
},
|
188
192
|
desc: 'The User-Agent to use' do |name|
|
189
193
|
@user_agent = name
|
data/lib/ronin/web/version.rb
CHANGED
data/man/ronin-web-spider.1
CHANGED
@@ -47,7 +47,7 @@ Sets a default header\.
|
|
47
47
|
Sets a default header\.
|
48
48
|
.LP
|
49
49
|
.HP
|
50
|
-
\fB-u\fR, \fB--user-agent\fR chrome
|
50
|
+
\fB-u\fR, \fB--user-agent\fR chrome\-linux\[or]chrome\-macos\[or]chrome\-windows\[or]chrome\-iphone\[or]chrome\-ipad\[or]chrome\-android\[or]firefox\-linux\[or]firefox\-macos\[or]firefox\-windows\[or]firefox\-iphone\[or]firefox\-ipad\[or]firefox\-android\[or]safari\-macos\[or]safari\-iphone\[or]safari\-ipad\[or]edge
|
51
51
|
The \fBUser-Agent\fR to use\.
|
52
52
|
.LP
|
53
53
|
.TP
|
data/man/ronin-web-spider.1.md
CHANGED
@@ -34,7 +34,7 @@ Spiders a website.
|
|
34
34
|
`--host-header` *NAME*=*VALUE*
|
35
35
|
Sets a default header.
|
36
36
|
|
37
|
-
`-u`, `--user-agent`
|
37
|
+
`-u`, `--user-agent` chrome-linux|chrome-macos|chrome-windows|chrome-iphone|chrome-ipad|chrome-android|firefox-linux|firefox-macos|firefox-windows|firefox-iphone|firefox-ipad|firefox-android|safari-macos|safari-iphone|safari-ipad|edge
|
38
38
|
The `User-Agent` to use.
|
39
39
|
|
40
40
|
`-U`, `--user-agent-string` *STRING*
|
data/ronin-web.gemspec
CHANGED
@@ -26,6 +26,8 @@ Gem::Specification.new do |gem|
|
|
26
26
|
gem.files = `git ls-files`.split($/)
|
27
27
|
gem.files = glob[gemspec['files']] if gemspec['files']
|
28
28
|
gem.files += Array(gemspec['generated_files'])
|
29
|
+
# exclude test files from the packages gem
|
30
|
+
gem.files -= glob[gemspec['test_files'] || 'spec/{**/}*']
|
29
31
|
|
30
32
|
gem.executables = gemspec.fetch('executables') do
|
31
33
|
glob['bin/*'].map { |path| File.basename(path) }
|
@@ -33,7 +35,6 @@ Gem::Specification.new do |gem|
|
|
33
35
|
gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
|
34
36
|
|
35
37
|
gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
|
36
|
-
gem.test_files = glob[gemspec['test_files'] || 'spec/{**/}*_spec.rb']
|
37
38
|
gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
|
38
39
|
|
39
40
|
gem.require_paths = Array(gemspec.fetch('require_paths') {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ronin-web
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Postmodern
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.1.0.
|
103
|
+
version: 0.1.0.beta3
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.1.0.
|
110
|
+
version: 0.1.0.beta3
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: ronin-web-spider
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -245,12 +245,6 @@ files:
|
|
245
245
|
- man/ronin-web.1
|
246
246
|
- man/ronin-web.1.md
|
247
247
|
- ronin-web.gemspec
|
248
|
-
- spec/cli/ruby_shell_spec.rb
|
249
|
-
- spec/html_spec.rb
|
250
|
-
- spec/mechanize_spec.rb
|
251
|
-
- spec/spec_helper.rb
|
252
|
-
- spec/web_spec.rb
|
253
|
-
- spec/xml_spec.rb
|
254
248
|
homepage: https://github.com/ronin-rb/ronin-web#readme
|
255
249
|
licenses:
|
256
250
|
- GPL-3.0
|
@@ -274,9 +268,4 @@ rubygems_version: 3.3.26
|
|
274
268
|
signing_key:
|
275
269
|
specification_version: 4
|
276
270
|
summary: A collection of common web security commands and libraries.
|
277
|
-
test_files:
|
278
|
-
- spec/cli/ruby_shell_spec.rb
|
279
|
-
- spec/html_spec.rb
|
280
|
-
- spec/mechanize_spec.rb
|
281
|
-
- spec/web_spec.rb
|
282
|
-
- spec/xml_spec.rb
|
271
|
+
test_files: []
|
data/spec/cli/ruby_shell_spec.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'ronin/web/cli/ruby_shell'
|
3
|
-
|
4
|
-
describe Ronin::Web::CLI::RubyShell do
|
5
|
-
describe "#initialize" do
|
6
|
-
it "must default #name to 'ronin-web'" do
|
7
|
-
expect(subject.name).to eq('ronin-web')
|
8
|
-
end
|
9
|
-
|
10
|
-
it "must default #context to Ronin::Web" do
|
11
|
-
expect(subject.context).to be(Ronin::Web)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
data/spec/html_spec.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'ronin/web/html'
|
3
|
-
|
4
|
-
describe Ronin::Web::HTML do
|
5
|
-
describe ".parse" do
|
6
|
-
let(:html) do
|
7
|
-
<<~HTML
|
8
|
-
<html>
|
9
|
-
<body>Hello</body>
|
10
|
-
</html>
|
11
|
-
HTML
|
12
|
-
end
|
13
|
-
|
14
|
-
it "must parse an HTML String and return a Nokogiri::HTML::Document" do
|
15
|
-
doc = subject.parse(html)
|
16
|
-
|
17
|
-
expect(doc).to be_kind_of(Nokogiri::HTML::Document)
|
18
|
-
expect(doc.at('body').inner_text).to eq("Hello")
|
19
|
-
end
|
20
|
-
|
21
|
-
context "when given a block" do
|
22
|
-
it "must yield the Nokogiri::HTML::Document object" do
|
23
|
-
expect { |b|
|
24
|
-
subject.parse(html,&b)
|
25
|
-
}.to yield_with_args(Nokogiri::HTML::Document)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
describe ".build" do
|
31
|
-
it "must build an HTML document" do
|
32
|
-
doc = subject.build do
|
33
|
-
html {
|
34
|
-
body {
|
35
|
-
div { text("hello") }
|
36
|
-
}
|
37
|
-
}
|
38
|
-
end
|
39
|
-
|
40
|
-
expect(doc.to_html).to include("<html><body><div>hello</div></body></html>")
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
data/spec/mechanize_spec.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'ronin/web/mechanize'
|
3
|
-
|
4
|
-
describe Ronin::Web::Mechanize do
|
5
|
-
describe "#initialize" do
|
6
|
-
context "when Ronin::Support::Network::HTTP.user_agent is set" do
|
7
|
-
let(:user_agent) { 'test' }
|
8
|
-
|
9
|
-
before { Ronin::Support::Network::HTTP.user_agent = user_agent }
|
10
|
-
|
11
|
-
it "should set #user_agent to Ronin::Support::Network::HTTP.user_agent" do
|
12
|
-
expect(subject.user_agent).to eq(user_agent)
|
13
|
-
end
|
14
|
-
|
15
|
-
after { Ronin::Support::Network::HTTP.user_agent = nil }
|
16
|
-
end
|
17
|
-
|
18
|
-
context "when the :user_agent option is given" do
|
19
|
-
context "and it's a String" do
|
20
|
-
let(:user_agent) { 'test2' }
|
21
|
-
|
22
|
-
subject { described_class.new(user_agent: user_agent) }
|
23
|
-
|
24
|
-
it "should set #user_agent to the custom User-Agent string" do
|
25
|
-
expect(subject.user_agent).to eq(user_agent)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
context "and it's a Symbol" do
|
30
|
-
let(:user_agent) { :chrome_linux }
|
31
|
-
let(:expected_user_agent) do
|
32
|
-
Ronin::Support::Network::HTTP::UserAgents[user_agent]
|
33
|
-
end
|
34
|
-
|
35
|
-
subject { described_class.new(user_agent: user_agent) }
|
36
|
-
|
37
|
-
it "should set #user_agent to the custom User-Agent alias" do
|
38
|
-
expect(subject.user_agent).to eq(expected_user_agent)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
let(:host) { '127.0.0.1' }
|
44
|
-
let(:port) { 8080 }
|
45
|
-
let(:proxy) { URI::HTTP.build(host: host, port: port) }
|
46
|
-
|
47
|
-
context "when Ronin::Support::Network::HTTP.proxy is set" do
|
48
|
-
before { Ronin::Support::Network::HTTP.proxy = proxy }
|
49
|
-
|
50
|
-
it "should set #proxy_addr and #proxy_port to Ronin::Support::Network::HTTP.proxy" do
|
51
|
-
expect(subject.proxy_addr).to eq(
|
52
|
-
Ronin::Support::Network::HTTP.proxy.host
|
53
|
-
)
|
54
|
-
|
55
|
-
expect(subject.proxy_port).to eq(
|
56
|
-
Ronin::Support::Network::HTTP.proxy.port
|
57
|
-
)
|
58
|
-
end
|
59
|
-
|
60
|
-
after { Ronin::Support::Network::HTTP.proxy = nil }
|
61
|
-
end
|
62
|
-
|
63
|
-
context "when the :proxy option is given" do
|
64
|
-
subject { described_class.new(proxy: proxy) }
|
65
|
-
|
66
|
-
it "should set #proxy_addr and #proxy_port to the custom proxy" do
|
67
|
-
expect(subject.proxy_addr).to eq(host)
|
68
|
-
expect(subject.proxy_port).to eq(port)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
data/spec/spec_helper.rb
DELETED
data/spec/web_spec.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'ronin/web'
|
3
|
-
|
4
|
-
describe Ronin::Web do
|
5
|
-
let(:url) { 'https://example.com/' }
|
6
|
-
|
7
|
-
it "should have a VERSION constant" do
|
8
|
-
expect(subject.const_defined?('VERSION')).to eq(true)
|
9
|
-
end
|
10
|
-
|
11
|
-
describe ".html" do
|
12
|
-
it "should be able to parse HTML" do
|
13
|
-
doc = subject.html(%{
|
14
|
-
<html>
|
15
|
-
<body>Hello</body>
|
16
|
-
</html>
|
17
|
-
})
|
18
|
-
|
19
|
-
expect(doc.at('body').inner_text).to eq("Hello")
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
describe ".build_html" do
|
24
|
-
it "should be able to build HTML documents" do
|
25
|
-
doc = subject.build_html do
|
26
|
-
html {
|
27
|
-
body {
|
28
|
-
div { text("hello") }
|
29
|
-
}
|
30
|
-
}
|
31
|
-
end
|
32
|
-
|
33
|
-
expect(doc.to_html).to include("<html><body><div>hello</div></body></html>")
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
describe ".xml" do
|
38
|
-
it "should be able to parse XML" do
|
39
|
-
doc = subject.xml(%{
|
40
|
-
<?xml version="1.0"?>
|
41
|
-
<root>
|
42
|
-
<stuff>Hello</stuff>
|
43
|
-
</root>
|
44
|
-
})
|
45
|
-
|
46
|
-
expect(doc.at('stuff').inner_text).to eq("Hello")
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe ".build_xml" do
|
51
|
-
it "should be able to build XML documents" do
|
52
|
-
doc = subject.build_xml do
|
53
|
-
root {
|
54
|
-
stuff(name: 'bla') { text("hello") }
|
55
|
-
}
|
56
|
-
end
|
57
|
-
|
58
|
-
expect(doc.to_xml).to include("<root>\n <stuff name=\"bla\">hello</stuff>\n</root>")
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
describe ".open", :network do
|
63
|
-
it "must open URLs as temporary files" do
|
64
|
-
file = subject.open(url)
|
65
|
-
|
66
|
-
expect(file).to be_kind_of(StringIO)
|
67
|
-
expect(file.read).to include("Example Domain")
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
describe ".agent" do
|
72
|
-
it "must return a #{described_class}::Mechanize object" do
|
73
|
-
expect(subject.agent).to be_kind_of(described_class::Mechanize)
|
74
|
-
end
|
75
|
-
|
76
|
-
it "must return the same object each time" do
|
77
|
-
expect(subject.agent).to be(subject.agent)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
describe ".get", :network do
|
82
|
-
it "should be able to get Mechanize pages" do
|
83
|
-
page = subject.get(url)
|
84
|
-
|
85
|
-
expect(page.class).to eq(Mechanize::Page)
|
86
|
-
expect(page.uri).to eq(URI(url))
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
describe ".get_body", :network do
|
91
|
-
it "should be able to get the bodies of Mechanize pages" do
|
92
|
-
body = subject.get_body(url)
|
93
|
-
|
94
|
-
expect(body).to include("Example Domain")
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
data/spec/xml_spec.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'ronin/web/xml'
|
3
|
-
|
4
|
-
describe Ronin::Web::XML do
|
5
|
-
describe ".parse" do
|
6
|
-
let(:xml) do
|
7
|
-
<<~XML
|
8
|
-
<?xml version="1.0"?>
|
9
|
-
<root>
|
10
|
-
<stuff>Hello</stuff>
|
11
|
-
</root>
|
12
|
-
XML
|
13
|
-
end
|
14
|
-
|
15
|
-
it "must parse an XML String and return a Nokogiri::XML::Document" do
|
16
|
-
doc = subject.parse(xml)
|
17
|
-
|
18
|
-
expect(doc).to be_kind_of(Nokogiri::XML::Document)
|
19
|
-
expect(doc.at('stuff').inner_text).to eq("Hello")
|
20
|
-
end
|
21
|
-
|
22
|
-
context "when given a block" do
|
23
|
-
it "must yield the Nokogiri::XML::Document object" do
|
24
|
-
expect { |b|
|
25
|
-
subject.parse(xml,&b)
|
26
|
-
}.to yield_with_args(Nokogiri::XML::Document)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe ".build" do
|
32
|
-
it "must build an XML document" do
|
33
|
-
doc = subject.build do
|
34
|
-
root {
|
35
|
-
stuff(name: 'bla') { text("hello") }
|
36
|
-
}
|
37
|
-
end
|
38
|
-
|
39
|
-
expect(doc.to_xml).to include("<root>\n <stuff name=\"bla\">hello</stuff>\n</root>")
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|