site-inspector 3.1.1 → 3.2.0
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.
- checksums.yaml +5 -5
- data/.gitignore +1 -1
- data/.rubocop.yml +18 -10
- data/.rubocop_todo.yml +139 -0
- data/.ruby-version +1 -1
- data/Gemfile +4 -0
- data/Guardfile +2 -0
- data/Rakefile +2 -0
- data/bin/site-inspector +7 -6
- data/lib/cliver/dependency_ext.rb +6 -3
- data/lib/site-inspector.rb +18 -11
- data/lib/site-inspector/cache.rb +2 -0
- data/lib/site-inspector/checks/accessibility.rb +30 -22
- data/lib/site-inspector/checks/check.rb +4 -2
- data/lib/site-inspector/checks/content.rb +15 -4
- data/lib/site-inspector/checks/cookies.rb +5 -3
- data/lib/site-inspector/checks/dns.rb +13 -11
- data/lib/site-inspector/checks/headers.rb +8 -6
- data/lib/site-inspector/checks/hsts.rb +16 -12
- data/lib/site-inspector/checks/https.rb +3 -1
- data/lib/site-inspector/checks/sniffer.rb +10 -7
- data/lib/site-inspector/checks/wappalyzer.rb +62 -0
- data/lib/site-inspector/checks/whois.rb +36 -0
- data/lib/site-inspector/disk_cache.rb +2 -0
- data/lib/site-inspector/domain.rb +36 -30
- data/lib/site-inspector/endpoint.rb +22 -23
- data/lib/site-inspector/rails_cache.rb +2 -0
- data/lib/site-inspector/version.rb +3 -1
- data/package-lock.json +505 -0
- data/package.json +1 -1
- data/script/pa11y-version +1 -0
- data/site-inspector.gemspec +24 -17
- data/spec/checks/site_inspector_endpoint_accessibility_spec.rb +15 -13
- data/spec/checks/site_inspector_endpoint_check_spec.rb +9 -7
- data/spec/checks/site_inspector_endpoint_content_spec.rb +30 -21
- data/spec/checks/site_inspector_endpoint_cookies_spec.rb +17 -15
- data/spec/checks/site_inspector_endpoint_dns_spec.rb +42 -40
- data/spec/checks/site_inspector_endpoint_headers_spec.rb +12 -10
- data/spec/checks/site_inspector_endpoint_hsts_spec.rb +27 -25
- data/spec/checks/site_inspector_endpoint_https_spec.rb +12 -10
- data/spec/checks/site_inspector_endpoint_sniffer_spec.rb +33 -31
- data/spec/checks/site_inspector_endpoint_wappalyzer_spec.rb +34 -0
- data/spec/checks/site_inspector_endpoint_whois_spec.rb +26 -0
- data/spec/fixtures/wappalyzer.json +125 -0
- data/spec/site_inspector_cache_spec.rb +2 -0
- data/spec/site_inspector_disk_cache_spec.rb +8 -6
- data/spec/site_inspector_domain_spec.rb +34 -34
- data/spec/site_inspector_endpoint_spec.rb +44 -43
- data/spec/site_inspector_spec.rb +15 -13
- data/spec/spec_helper.rb +2 -0
- metadata +125 -55
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe SiteInspector::Endpoint::Headers do
|
@@ -5,7 +7,7 @@ describe SiteInspector::Endpoint::Headers do
|
|
5
7
|
stub_request(:head, 'http://example.com/')
|
6
8
|
.to_return(status: 200, headers: { foo: 'bar' })
|
7
9
|
endpoint = SiteInspector::Endpoint.new('http://example.com')
|
8
|
-
|
10
|
+
described_class.new(endpoint)
|
9
11
|
end
|
10
12
|
|
11
13
|
def stub_header(header, value)
|
@@ -13,7 +15,7 @@ describe SiteInspector::Endpoint::Headers do
|
|
13
15
|
end
|
14
16
|
|
15
17
|
it 'parses the headers' do
|
16
|
-
expect(subject.headers.count).to
|
18
|
+
expect(subject.headers.count).to be(1)
|
17
19
|
expect(subject.headers.keys).to include('foo')
|
18
20
|
end
|
19
21
|
|
@@ -34,30 +36,30 @@ describe SiteInspector::Endpoint::Headers do
|
|
34
36
|
|
35
37
|
it 'validates xss-protection' do
|
36
38
|
stub_header 'x-xss-protection', 'foo'
|
37
|
-
expect(subject.xss_protection?).to
|
39
|
+
expect(subject.xss_protection?).to be(false)
|
38
40
|
|
39
41
|
stub_header 'x-xss-protection', '1; mode=block'
|
40
|
-
expect(subject.xss_protection?).to
|
42
|
+
expect(subject.xss_protection?).to be(true)
|
41
43
|
end
|
42
44
|
|
43
45
|
it 'checks for clickjack proetection' do
|
44
|
-
expect(subject.click_jacking_protection?).to
|
46
|
+
expect(subject.click_jacking_protection?).to be(false)
|
45
47
|
stub_header 'x-frame-options', 'foo'
|
46
48
|
expect(subject.click_jacking_protection).to eql('foo')
|
47
|
-
expect(subject.click_jacking_protection?).to
|
49
|
+
expect(subject.click_jacking_protection?).to be(true)
|
48
50
|
end
|
49
51
|
|
50
52
|
it 'checks for CSP' do
|
51
|
-
expect(subject.content_security_policy?).to
|
53
|
+
expect(subject.content_security_policy?).to be(false)
|
52
54
|
stub_header 'content-security-policy', 'foo'
|
53
55
|
expect(subject.content_security_policy).to eql('foo')
|
54
|
-
expect(subject.content_security_policy?).to
|
56
|
+
expect(subject.content_security_policy?).to be(true)
|
55
57
|
end
|
56
58
|
|
57
59
|
it 'checks for strict-transport-security' do
|
58
|
-
expect(subject.strict_transport_security?).to
|
60
|
+
expect(subject.strict_transport_security?).to be(false)
|
59
61
|
stub_header 'strict-transport-security', 'foo'
|
60
62
|
expect(subject.strict_transport_security).to eql('foo')
|
61
|
-
expect(subject.strict_transport_security?).to
|
63
|
+
expect(subject.strict_transport_security?).to be(true)
|
62
64
|
end
|
63
65
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe SiteInspector::Endpoint::Hsts do
|
@@ -6,7 +8,7 @@ describe SiteInspector::Endpoint::Hsts do
|
|
6
8
|
stub_request(:head, 'http://example.com/')
|
7
9
|
.to_return(status: 200, headers: headers)
|
8
10
|
endpoint = SiteInspector::Endpoint.new('http://example.com')
|
9
|
-
|
11
|
+
described_class.new(endpoint)
|
10
12
|
end
|
11
13
|
|
12
14
|
def stub_header(value)
|
@@ -21,8 +23,8 @@ describe SiteInspector::Endpoint::Hsts do
|
|
21
23
|
expect(subject.send(:header)).to eql('max-age=31536000; includeSubDomains;')
|
22
24
|
end
|
23
25
|
|
24
|
-
it '
|
25
|
-
expect(subject.send(:directives).count).to
|
26
|
+
it 'parses the directives' do
|
27
|
+
expect(subject.send(:directives).count).to be(2)
|
26
28
|
expect(subject.send(:directives).first).to eql('max-age=31536000')
|
27
29
|
expect(subject.send(:directives).last).to eql('includeSubDomains')
|
28
30
|
end
|
@@ -33,58 +35,58 @@ describe SiteInspector::Endpoint::Hsts do
|
|
33
35
|
end
|
34
36
|
|
35
37
|
it 'knows if the header is valid' do
|
36
|
-
expect(subject.valid?).to
|
38
|
+
expect(subject.valid?).to be(true)
|
37
39
|
|
38
|
-
allow(subject).to receive(:pairs)
|
39
|
-
expect(subject.valid?).to
|
40
|
+
allow(subject).to receive(:pairs).and_return(['fo o' => 'bar'])
|
41
|
+
expect(subject.valid?).to be(false)
|
40
42
|
|
41
|
-
allow(subject).to receive(:pairs)
|
42
|
-
expect(subject.valid?).to
|
43
|
+
allow(subject).to receive(:pairs).and_return(["fo'o" => 'bar'])
|
44
|
+
expect(subject.valid?).to be(false)
|
43
45
|
end
|
44
46
|
|
45
47
|
it 'knows the max age' do
|
46
|
-
expect(subject.max_age).to
|
48
|
+
expect(subject.max_age).to be(31_536_000)
|
47
49
|
end
|
48
50
|
|
49
51
|
it 'knows if subdomains are included' do
|
50
|
-
expect(subject.include_subdomains?).to
|
51
|
-
allow(subject).to receive(:pairs)
|
52
|
-
expect(subject.include_subdomains?).to
|
52
|
+
expect(subject.include_subdomains?).to be(true)
|
53
|
+
allow(subject).to receive(:pairs).and_return(foo: 'bar')
|
54
|
+
expect(subject.include_subdomains?).to be(false)
|
53
55
|
end
|
54
56
|
|
55
57
|
it "knows if it's preloaded" do
|
56
|
-
expect(subject.preload?).to
|
57
|
-
allow(subject).to receive(:pairs)
|
58
|
-
expect(subject.preload?).to
|
58
|
+
expect(subject.preload?).to be(false)
|
59
|
+
allow(subject).to receive(:pairs).and_return(preload: nil)
|
60
|
+
expect(subject.preload?).to be(true)
|
59
61
|
end
|
60
62
|
|
61
63
|
it "knows if it's enabled" do
|
62
|
-
expect(subject.enabled?).to
|
64
|
+
expect(subject.enabled?).to be(true)
|
63
65
|
|
64
|
-
allow(subject).to receive(:pairs)
|
65
|
-
expect(subject.preload?).to
|
66
|
+
allow(subject).to receive(:pairs).and_return("max-age": 0)
|
67
|
+
expect(subject.preload?).to be(false)
|
66
68
|
|
67
|
-
allow(subject).to receive(:pairs)
|
68
|
-
expect(subject.preload?).to
|
69
|
+
allow(subject).to receive(:pairs).and_return(foo: 'bar')
|
70
|
+
expect(subject.preload?).to be(false)
|
69
71
|
end
|
70
72
|
|
71
73
|
it "knows if it's preload ready" do
|
72
|
-
expect(subject.preload_ready?).to
|
74
|
+
expect(subject.preload_ready?).to be(false)
|
73
75
|
|
74
76
|
pairs = { "max-age": 10_886_401, preload: nil, includesubdomains: nil }
|
75
77
|
allow(subject).to receive(:pairs) { pairs }
|
76
|
-
expect(subject.preload_ready?).to
|
78
|
+
expect(subject.preload_ready?).to be(true)
|
77
79
|
|
78
80
|
pairs = { "max-age": 10_886_401, includesubdomains: nil }
|
79
81
|
allow(subject).to receive(:pairs) { pairs }
|
80
|
-
expect(subject.preload_ready?).to
|
82
|
+
expect(subject.preload_ready?).to be(false)
|
81
83
|
|
82
84
|
pairs = { "max-age": 10_886_401, preload: nil, includesubdomains: nil }
|
83
85
|
allow(subject).to receive(:pairs) { pairs }
|
84
|
-
expect(subject.preload_ready?).to
|
86
|
+
expect(subject.preload_ready?).to be(true)
|
85
87
|
|
86
88
|
pairs = { "max-age": 5, preload: nil, includesubdomains: nil }
|
87
89
|
allow(subject).to receive(:pairs) { pairs }
|
88
|
-
expect(subject.preload_ready?).to
|
90
|
+
expect(subject.preload_ready?).to be(false)
|
89
91
|
end
|
90
92
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe SiteInspector::Endpoint::Https do
|
@@ -5,8 +7,8 @@ describe SiteInspector::Endpoint::Https do
|
|
5
7
|
stub_request(:head, 'https://example.com/')
|
6
8
|
.to_return(status: 200)
|
7
9
|
endpoint = SiteInspector::Endpoint.new('https://example.com')
|
8
|
-
allow(endpoint.response).to receive(:return_code)
|
9
|
-
|
10
|
+
allow(endpoint.response).to receive(:return_code).and_return(:ok)
|
11
|
+
described_class.new(endpoint)
|
10
12
|
end
|
11
13
|
|
12
14
|
it 'knows the scheme' do
|
@@ -14,34 +16,34 @@ describe SiteInspector::Endpoint::Https do
|
|
14
16
|
end
|
15
17
|
|
16
18
|
it 'knows if the scheme is https' do
|
17
|
-
expect(subject.scheme?).to
|
18
|
-
allow(subject).to receive(:scheme)
|
19
|
-
expect(subject.scheme?).to
|
19
|
+
expect(subject.scheme?).to be(true)
|
20
|
+
allow(subject).to receive(:scheme).and_return('http')
|
21
|
+
expect(subject.scheme?).to be(false)
|
20
22
|
end
|
21
23
|
|
22
24
|
it "knows if it's valid" do
|
23
|
-
expect(subject.valid?).to
|
25
|
+
expect(subject.valid?).to be(true)
|
24
26
|
end
|
25
27
|
|
26
28
|
it "knows when there's a bad chain" do
|
27
|
-
expect(subject.bad_chain?).to
|
29
|
+
expect(subject.bad_chain?).to be(false)
|
28
30
|
|
29
31
|
url = Addressable::URI.parse('https://example.com')
|
30
32
|
response = Typhoeus::Response.new(return_code: :ssl_cacert)
|
31
33
|
response.request = Typhoeus::Request.new(url)
|
32
34
|
|
33
35
|
allow(subject).to receive(:response) { response }
|
34
|
-
expect(subject.bad_chain?).to
|
36
|
+
expect(subject.bad_chain?).to be(true)
|
35
37
|
end
|
36
38
|
|
37
39
|
it "knows when there's a bad name" do
|
38
|
-
expect(subject.bad_name?).to
|
40
|
+
expect(subject.bad_name?).to be(false)
|
39
41
|
|
40
42
|
url = Addressable::URI.parse('https://example.com')
|
41
43
|
response = Typhoeus::Response.new(return_code: :peer_failed_verification)
|
42
44
|
response.request = Typhoeus::Request.new(url)
|
43
45
|
|
44
46
|
allow(subject).to receive(:response) { response }
|
45
|
-
expect(subject.bad_name?).to
|
47
|
+
expect(subject.bad_name?).to be(true)
|
46
48
|
end
|
47
49
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe SiteInspector::Endpoint::Sniffer do
|
@@ -8,16 +10,16 @@ describe SiteInspector::Endpoint::Sniffer do
|
|
8
10
|
def set_cookie(key, value)
|
9
11
|
cookies = [
|
10
12
|
CGI::Cookie.new(
|
11
|
-
'name'
|
12
|
-
'value'
|
13
|
+
'name' => 'foo',
|
14
|
+
'value' => 'bar',
|
13
15
|
'domain' => 'example.com',
|
14
|
-
'path'
|
16
|
+
'path' => '/'
|
15
17
|
),
|
16
18
|
CGI::Cookie.new(
|
17
|
-
'name'
|
18
|
-
'value'
|
19
|
+
'name' => key,
|
20
|
+
'value' => value,
|
19
21
|
'domain' => 'example.com',
|
20
|
-
'path'
|
22
|
+
'path' => '/'
|
21
23
|
)
|
22
24
|
].map(&:to_s)
|
23
25
|
|
@@ -30,7 +32,7 @@ describe SiteInspector::Endpoint::Sniffer do
|
|
30
32
|
|
31
33
|
context 'stubbed body' do
|
32
34
|
subject do
|
33
|
-
body = <<-
|
35
|
+
body = <<-BODY
|
34
36
|
<html>
|
35
37
|
<head>
|
36
38
|
<link rel='stylesheet' href='/wp-content/themes/foo/style.css type='text/css' media='all' />
|
@@ -48,7 +50,7 @@ describe SiteInspector::Endpoint::Sniffer do
|
|
48
50
|
</script>
|
49
51
|
</body>
|
50
52
|
</html>
|
51
|
-
|
53
|
+
BODY
|
52
54
|
|
53
55
|
stub_request(:get, 'http://example.com/')
|
54
56
|
.to_return(status: 200, body: body)
|
@@ -56,56 +58,56 @@ describe SiteInspector::Endpoint::Sniffer do
|
|
56
58
|
stub_request(:head, 'http://example.com/')
|
57
59
|
.to_return(status: 200)
|
58
60
|
endpoint = SiteInspector::Endpoint.new('http://example.com')
|
59
|
-
|
61
|
+
described_class.new(endpoint)
|
60
62
|
end
|
61
63
|
|
62
64
|
it 'sniffs' do
|
63
65
|
sniff = subject.send(:sniff, :cms)
|
64
|
-
expect(sniff).to
|
66
|
+
expect(sniff).to be(:wordpress)
|
65
67
|
end
|
66
68
|
|
67
69
|
it 'detects the CMS' do
|
68
|
-
expect(subject.framework).to
|
70
|
+
expect(subject.framework).to be(:wordpress)
|
69
71
|
end
|
70
72
|
|
71
73
|
it 'detects the analytics' do
|
72
|
-
expect(subject.analytics).to
|
74
|
+
expect(subject.analytics).to be(:google_analytics)
|
73
75
|
end
|
74
76
|
|
75
77
|
it 'detects javascript' do
|
76
|
-
expect(subject.javascript).to
|
78
|
+
expect(subject.javascript).to be(:jquery)
|
77
79
|
end
|
78
80
|
|
79
81
|
it 'detects advertising' do
|
80
|
-
expect(subject.advertising).to
|
82
|
+
expect(subject.advertising).to be(:adsense)
|
81
83
|
end
|
82
84
|
|
83
85
|
it 'knows wordpress is open source' do
|
84
|
-
expect(subject.open_source?).to
|
86
|
+
expect(subject.open_source?).to be(true)
|
85
87
|
end
|
86
88
|
end
|
87
89
|
|
88
90
|
context 'no body' do
|
89
91
|
subject do
|
90
92
|
endpoint = SiteInspector::Endpoint.new('http://example.com')
|
91
|
-
|
93
|
+
described_class.new(endpoint)
|
92
94
|
end
|
93
95
|
|
94
96
|
it "knows when something isn't open source" do
|
95
97
|
set_cookie('foo', 'bar')
|
96
|
-
expect(subject.open_source?).to
|
98
|
+
expect(subject.open_source?).to be(false)
|
97
99
|
end
|
98
100
|
|
99
101
|
it 'detects PHP' do
|
100
102
|
set_cookie('PHPSESSID', '1234')
|
101
|
-
expect(subject.framework).to
|
102
|
-
expect(subject.open_source?).to
|
103
|
+
expect(subject.framework).to be(:php)
|
104
|
+
expect(subject.open_source?).to be(true)
|
103
105
|
end
|
104
106
|
|
105
107
|
it 'detects Expression Engine' do
|
106
108
|
set_cookie('exp_csrf_token', '1234')
|
107
|
-
expect(subject.framework).to
|
108
|
-
expect(subject.open_source?).to
|
109
|
+
expect(subject.framework).to be(:expression_engine)
|
110
|
+
expect(subject.open_source?).to be(true)
|
109
111
|
end
|
110
112
|
|
111
113
|
it 'detects cowboy' do
|
@@ -115,23 +117,23 @@ describe SiteInspector::Endpoint::Sniffer do
|
|
115
117
|
stub_request(:head, 'http://example.com/')
|
116
118
|
.to_return(status: 200, headers: { 'server' => 'Cowboy' })
|
117
119
|
|
118
|
-
expect(subject.framework).to
|
119
|
-
expect(subject.open_source?).to
|
120
|
+
expect(subject.framework).to be(:cowboy)
|
121
|
+
expect(subject.open_source?).to be(true)
|
120
122
|
end
|
121
123
|
|
122
124
|
it 'detects ColdFusion' do
|
123
125
|
cookies = [
|
124
126
|
CGI::Cookie.new(
|
125
|
-
'name'
|
126
|
-
'value'
|
127
|
+
'name' => 'CFID',
|
128
|
+
'value' => '1234',
|
127
129
|
'domain' => 'example.com',
|
128
|
-
'path'
|
130
|
+
'path' => '/'
|
129
131
|
),
|
130
132
|
CGI::Cookie.new(
|
131
|
-
'name'
|
132
|
-
'value'
|
133
|
+
'name' => 'CFTOKEN',
|
134
|
+
'value' => '5678',
|
133
135
|
'domain' => 'example.com',
|
134
|
-
'path'
|
136
|
+
'path' => '/'
|
135
137
|
)
|
136
138
|
].map(&:to_s)
|
137
139
|
|
@@ -141,8 +143,8 @@ describe SiteInspector::Endpoint::Sniffer do
|
|
141
143
|
stub_request(:head, 'http://example.com/')
|
142
144
|
.to_return(status: 200, headers: { 'set-cookie' => cookies })
|
143
145
|
|
144
|
-
expect(subject.framework).to
|
145
|
-
expect(subject.open_source?).to
|
146
|
+
expect(subject.framework).to be(:coldfusion)
|
147
|
+
expect(subject.open_source?).to be(false)
|
146
148
|
end
|
147
149
|
end
|
148
150
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe SiteInspector::Endpoint::Wappalyzer do
|
6
|
+
subject { described_class.new(endpoint) }
|
7
|
+
|
8
|
+
let(:domain) { 'http://ben.balter.com.com' }
|
9
|
+
let(:endpoint) { SiteInspector::Endpoint.new(domain) }
|
10
|
+
let(:url) { "https://api.wappalyzer.com/lookup/v2/?urls=#{domain}/" }
|
11
|
+
|
12
|
+
before do
|
13
|
+
path = File.expand_path '../fixtures/wappalyzer.json', __dir__
|
14
|
+
body = File.read path
|
15
|
+
stub_request(:get, url).to_return(status: 200, body: body)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns the API response' do
|
19
|
+
expected = {
|
20
|
+
'Analytics' => ['Google Analytics'],
|
21
|
+
'CDN' => %w[Cloudflare Fastly],
|
22
|
+
'Caching' => ['Varnish'],
|
23
|
+
'Other' => %w[Disqus Jekyll],
|
24
|
+
'PaaS' => ['GitHub Pages'],
|
25
|
+
'Web frameworks' => ['Ruby on Rails']
|
26
|
+
}
|
27
|
+
expect(subject.to_h).to eql(expected)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'fails gracefully' do
|
31
|
+
stub_request(:get, url).to_return(status: 400, body: '')
|
32
|
+
expect(subject.to_h).to eql({})
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe SiteInspector::Endpoint::Whois do
|
6
|
+
subject do
|
7
|
+
stub_request(:head, site).to_return(status: 200)
|
8
|
+
endpoint = SiteInspector::Endpoint.new(site)
|
9
|
+
described_class.new(endpoint)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:site) { 'https://example.com' }
|
13
|
+
|
14
|
+
it 'returns the whois for the IP' do
|
15
|
+
expect(subject.ip).to match(/Derrick Sawyer/)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns the whois for the domain' do
|
19
|
+
expect(subject.domain).to match(/Domain Name: EXAMPLE\.COM/)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns the hash' do
|
23
|
+
expect(subject.to_h[:domain].keys.first).to eql('Domain Name')
|
24
|
+
expect(subject.to_h[:domain].values.first).to eql('EXAMPLE.COM')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"url":"https://ben.balter.com",
|
4
|
+
"technologies":[
|
5
|
+
{
|
6
|
+
"slug":"cloudflare",
|
7
|
+
"name":"Cloudflare",
|
8
|
+
"versions":[
|
9
|
+
|
10
|
+
],
|
11
|
+
"trafficRank":11,
|
12
|
+
"categories":[
|
13
|
+
{
|
14
|
+
"id":31,
|
15
|
+
"slug":"cdn",
|
16
|
+
"name":"CDN"
|
17
|
+
}
|
18
|
+
]
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"slug":"varnish",
|
22
|
+
"name":"Varnish",
|
23
|
+
"versions":[
|
24
|
+
|
25
|
+
],
|
26
|
+
"trafficRank":11,
|
27
|
+
"categories":[
|
28
|
+
{
|
29
|
+
"id":23,
|
30
|
+
"slug":"caching",
|
31
|
+
"name":"Caching"
|
32
|
+
}
|
33
|
+
]
|
34
|
+
},
|
35
|
+
{
|
36
|
+
"slug":"disqus",
|
37
|
+
"name":"Disqus",
|
38
|
+
"versions":[
|
39
|
+
|
40
|
+
],
|
41
|
+
"trafficRank":11,
|
42
|
+
"categories":[
|
43
|
+
|
44
|
+
]
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"slug":"google-analytics",
|
48
|
+
"name":"Google Analytics",
|
49
|
+
"versions":[
|
50
|
+
|
51
|
+
],
|
52
|
+
"trafficRank":11,
|
53
|
+
"categories":[
|
54
|
+
{
|
55
|
+
"id":10,
|
56
|
+
"slug":"analytics",
|
57
|
+
"name":"Analytics"
|
58
|
+
},
|
59
|
+
{
|
60
|
+
"id":61,
|
61
|
+
"slug":"saas",
|
62
|
+
"name":"SaaS"
|
63
|
+
}
|
64
|
+
]
|
65
|
+
},
|
66
|
+
{
|
67
|
+
"slug":"jekyll",
|
68
|
+
"name":"Jekyll",
|
69
|
+
"versions":[
|
70
|
+
"v3.9.0"
|
71
|
+
],
|
72
|
+
"trafficRank":11,
|
73
|
+
"categories":[
|
74
|
+
|
75
|
+
]
|
76
|
+
},
|
77
|
+
{
|
78
|
+
"slug":"ruby-on-rails",
|
79
|
+
"name":"Ruby on Rails",
|
80
|
+
"versions":[
|
81
|
+
|
82
|
+
],
|
83
|
+
"trafficRank":11,
|
84
|
+
"categories":[
|
85
|
+
{
|
86
|
+
"id":18,
|
87
|
+
"slug":"web-frameworks",
|
88
|
+
"name":"Web frameworks"
|
89
|
+
}
|
90
|
+
]
|
91
|
+
},
|
92
|
+
{
|
93
|
+
"slug":"fastly",
|
94
|
+
"name":"Fastly",
|
95
|
+
"versions":[
|
96
|
+
|
97
|
+
],
|
98
|
+
"trafficRank":11,
|
99
|
+
"categories":[
|
100
|
+
{
|
101
|
+
"id":31,
|
102
|
+
"slug":"cdn",
|
103
|
+
"name":"CDN"
|
104
|
+
}
|
105
|
+
]
|
106
|
+
},
|
107
|
+
{
|
108
|
+
"slug":"github-pages",
|
109
|
+
"name":"GitHub Pages",
|
110
|
+
"versions":[
|
111
|
+
|
112
|
+
],
|
113
|
+
"trafficRank":11,
|
114
|
+
"categories":[
|
115
|
+
{
|
116
|
+
"id":62,
|
117
|
+
"slug":"paas",
|
118
|
+
"name":"PaaS"
|
119
|
+
}
|
120
|
+
]
|
121
|
+
}
|
122
|
+
],
|
123
|
+
"crawl":true
|
124
|
+
}
|
125
|
+
]
|