browser.rb 0.16.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 +7 -0
- data/CHANGELOG +516 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +116 -0
- data/Rakefile +182 -0
- data/browser.rb.gemspec +54 -0
- data/lib/Browser/Base.rb +50 -0
- data/lib/Browser/Bookmark.rb +41 -0
- data/lib/Browser/Bookmarks.rb +112 -0
- data/lib/Browser/Brave.rb +16 -0
- data/lib/Browser/Chrome.rb +16 -0
- data/lib/Browser/Chromium.rb +16 -0
- data/lib/Browser/ChromiumBased.rb +48 -0
- data/lib/Browser/Firefox.rb +16 -0
- data/lib/Browser/FirefoxBased.rb +86 -0
- data/lib/Browser/History.rb +41 -0
- data/lib/Browser/Rendering.rb +108 -0
- data/lib/Browser/Safari.rb +48 -0
- data/lib/Browser/TorBrowser.rb +26 -0
- data/lib/Browser/Unreadable.rb +11 -0
- data/lib/Browser/VERSION.rb +6 -0
- data/lib/browser-classes.rb +12 -0
- data/lib/browser.rb +29 -0
- data/test/Browser/Bookmark_test.rb +72 -0
- data/test/Browser/Brave_test.rb +143 -0
- data/test/Browser/Chrome_test.rb +143 -0
- data/test/Browser/Chromium_test.rb +143 -0
- data/test/Browser/Firefox_test.rb +205 -0
- data/test/Browser/Rendering_test.rb +68 -0
- data/test/Browser/Safari_test.rb +152 -0
- data/test/Browser/TorBrowser_test.rb +147 -0
- data/test/browser_test.rb +33 -0
- data/test/fixtures/Brave_bookmarks.json +47 -0
- data/test/fixtures/ChromiumBased_history.sqlite +0 -0
- data/test/fixtures/Chromium_bookmarks.json +47 -0
- data/test/fixtures/FirefoxBased_places.sqlite +0 -0
- data/test/fixtures/Safari_bookmarks.plist +0 -0
- data/test/fixtures/Safari_history.sqlite +0 -0
- data/test/gemspec_test.rb +42 -0
- data/test/loading_test.rb +35 -0
- metadata +194 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Chromium_test.rb
|
|
2
|
+
|
|
3
|
+
gem 'minitest'
|
|
4
|
+
gem 'minitest-spec-context'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
require 'minitest-spec-context'
|
|
8
|
+
require 'rspec/expectations/minitest_integration'
|
|
9
|
+
|
|
10
|
+
lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
|
11
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
12
|
+
|
|
13
|
+
require 'browser'
|
|
14
|
+
|
|
15
|
+
describe Browser::Chromium do
|
|
16
|
+
let(:bookmarks_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/Chromium_bookmarks.json")}
|
|
17
|
+
let(:history_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/ChromiumBased_history.sqlite")}
|
|
18
|
+
let(:expected_history_sql){'SELECT * FROM urls ORDER BY last_visit_time DESC;'}
|
|
19
|
+
|
|
20
|
+
describe Browser::Chromium::DEFAULT_PROFILE_NAME do
|
|
21
|
+
let(:expected_default_profile_name){'Default'}
|
|
22
|
+
|
|
23
|
+
it "contains the correct default profile name" do
|
|
24
|
+
expect(Browser::Chromium::DEFAULT_PROFILE_NAME).to eq(expected_default_profile_name)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "class methods" do
|
|
29
|
+
subject{Browser::Chromium}
|
|
30
|
+
|
|
31
|
+
describe '.bookmarks_location' do
|
|
32
|
+
let(:expected_bookmarks_location){File.expand_path('~/Library/Application Support/Chromium/Default/Bookmarks')}
|
|
33
|
+
|
|
34
|
+
it "contains the correct bookmarks location" do
|
|
35
|
+
expect(subject.bookmarks_location).to eq(expected_bookmarks_location)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '.history_location' do
|
|
40
|
+
let(:expected_history_location){File.expand_path('~/Library/Application Support/Chromium/Default/History')}
|
|
41
|
+
|
|
42
|
+
it "contains the correct history location" do
|
|
43
|
+
expect(subject.history_location).to eq(expected_history_location)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe ".bookmarks_sql" do
|
|
48
|
+
it "returns the bookmarks sql" do
|
|
49
|
+
expect(subject.send(:bookmarks_sql)).to be_nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe ".history_sql" do
|
|
54
|
+
it "returns the history sql" do
|
|
55
|
+
expect(subject.send(:history_sql)).to eq(expected_history_sql)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context "instance methods" do
|
|
61
|
+
subject{Browser::Chromium.new(bookmarks_location: bookmarks_fixtures_location, history_location: history_fixtures_location)}
|
|
62
|
+
|
|
63
|
+
let(:browser_name){'Chromium'}
|
|
64
|
+
let(:default_profile_name){'Default'}
|
|
65
|
+
let(:expected_history) do
|
|
66
|
+
[
|
|
67
|
+
{'id' => 1, 'url' => 'https://www.chromium.org/chromium-projects/', 'title' => 'Home', 'visit_count' => 3, 'typed_count' => 1, 'last_visit_time' => 13403232000000000, 'hidden' => 0},
|
|
68
|
+
{'id' => 2, 'url' => 'chrome://welcome/', 'title' => 'Welcome', 'visit_count' => 1, 'typed_count' => 0, 'last_visit_time' => 13403231000000000, 'hidden' => 0},
|
|
69
|
+
]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "#initialize" do
|
|
73
|
+
it "returns an instance of Browser" do
|
|
74
|
+
expect(subject).to be_a(Browser::Chromium)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "assigns @bookmarks_location" do
|
|
78
|
+
expect(subject.instance_variable_get(:@bookmarks_location)).to eq(bookmarks_fixtures_location)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "assigns @history_location" do
|
|
82
|
+
expect(subject.instance_variable_get(:@history_location)).to eq(history_fixtures_location)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "takes the default profile name when none is given" do
|
|
86
|
+
expect(subject.profile_name).to eq(default_profile_name)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe "#name" do
|
|
91
|
+
it "returns name" do
|
|
92
|
+
expect(subject.name).to eq(browser_name)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
let(:expected_bookmarks) do
|
|
97
|
+
[
|
|
98
|
+
{title: 'Home', url: 'https://www.chromium.org/chromium-projects/', folder: 'Bookmarks Bar'},
|
|
99
|
+
]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "#bookmarks" do
|
|
103
|
+
it "returns an instance of Browser::Bookmarks" do
|
|
104
|
+
expect(subject.bookmarks).to be_a(Browser::Bookmarks)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "returns the bookmarks" do
|
|
108
|
+
expect(subject.bookmarks.to_objects).to eq(expected_bookmarks)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
describe "#history" do
|
|
113
|
+
it "returns an instance of Browser::History" do
|
|
114
|
+
expect(subject.history).to be_a(Browser::History)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "returns the history" do
|
|
118
|
+
expect(subject.history.to_json).to eq(expected_history.to_json)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
describe "#readable?" do
|
|
123
|
+
it "is true when the data is there" do
|
|
124
|
+
expect(subject.bookmarks.readable?).to be(true)
|
|
125
|
+
expect(subject.history.readable?).to be(true)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
context "WHEN the data is not there" do
|
|
130
|
+
subject{Browser::Chromium.new(bookmarks_location: '/nonexistent/file', history_location: '/nonexistent/file')}
|
|
131
|
+
|
|
132
|
+
it "is not readable" do
|
|
133
|
+
expect(subject.bookmarks.readable?).to be(false)
|
|
134
|
+
expect(subject.history.readable?).to be(false)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "raises Browser::Unreadable" do
|
|
138
|
+
expect{subject.bookmarks.to_json}.to raise_error(Browser::Unreadable)
|
|
139
|
+
expect{subject.history.to_json}.to raise_error(Browser::Unreadable)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Firefox_test.rb
|
|
2
|
+
|
|
3
|
+
gem 'minitest'
|
|
4
|
+
gem 'minitest-spec-context'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
require 'minitest-spec-context'
|
|
8
|
+
require 'rspec/expectations/minitest_integration'
|
|
9
|
+
|
|
10
|
+
lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
|
11
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
12
|
+
|
|
13
|
+
require 'fileutils'
|
|
14
|
+
require 'tmpdir'
|
|
15
|
+
|
|
16
|
+
require 'browser'
|
|
17
|
+
|
|
18
|
+
describe Browser::Firefox do
|
|
19
|
+
let(:places_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/FirefoxBased_places.sqlite")}
|
|
20
|
+
let(:expected_bookmarks_sql){'SELECT moz_bookmarks.id, moz_bookmarks.parent, moz_bookmarks.position, moz_bookmarks.type, moz_bookmarks.title, moz_bookmarks.guid, moz_places.url FROM moz_bookmarks LEFT JOIN moz_places ON moz_bookmarks.fk = moz_places.id ORDER BY moz_bookmarks.parent, moz_bookmarks.position;'}
|
|
21
|
+
let(:expected_history_sql){'SELECT moz_historyvisits.id, moz_places.url, moz_places.title, moz_historyvisits.visit_date FROM moz_historyvisits LEFT JOIN moz_places ON moz_historyvisits.place_id = moz_places.id ORDER BY moz_historyvisits.visit_date DESC;'}
|
|
22
|
+
|
|
23
|
+
describe '.default_profile_name' do
|
|
24
|
+
let(:profiles_ini) do
|
|
25
|
+
<<~INI
|
|
26
|
+
[General]
|
|
27
|
+
StartWithLastProfile=0
|
|
28
|
+
Version=2
|
|
29
|
+
|
|
30
|
+
[Profile0]
|
|
31
|
+
Name=default-release
|
|
32
|
+
IsRelative=1
|
|
33
|
+
Path=Profiles/abcd1234.default-release
|
|
34
|
+
|
|
35
|
+
[InstallD7847D39F15872CF]
|
|
36
|
+
Default=Profiles/abcd1234.default-release
|
|
37
|
+
Locked=1
|
|
38
|
+
|
|
39
|
+
[Profile1]
|
|
40
|
+
Name=default
|
|
41
|
+
IsRelative=1
|
|
42
|
+
Path=Profiles/efgh5678.default
|
|
43
|
+
Default=1
|
|
44
|
+
INI
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "is the profile an [Install] section names, over the one flagged Default=1" do
|
|
48
|
+
expect(Browser::Firefox.default_profile_name(profiles_ini)).to eq('abcd1234.default-release')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "is the profile flagged Default=1 when no [Install] section names one" do
|
|
52
|
+
without_install = profiles_ini.sub(/\[Install.*?\n\n/m, '')
|
|
53
|
+
expect(Browser::Firefox.default_profile_name(without_install)).to eq('efgh5678.default')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "is the only profile when nothing names a default" do
|
|
57
|
+
sole = "[Profile0]\nName=default-release\nIsRelative=1\nPath=Profiles/abcd1234.default-release\n"
|
|
58
|
+
expect(Browser::Firefox.default_profile_name(sole)).to eq('abcd1234.default-release')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "raises Browser::Unreadable when two installations each name a default" do
|
|
62
|
+
two = profiles_ini + "\n[Install0123456789ABCDEF]\nDefault=Profiles/efgh5678.default\n"
|
|
63
|
+
expect{Browser::Firefox.default_profile_name(two)}.to raise_error(Browser::Unreadable, /names 2 profiles/)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "raises Browser::Unreadable when there are no profiles" do
|
|
67
|
+
expect{Browser::Firefox.default_profile_name("[General]\nVersion=2\n")}.to raise_error(Browser::Unreadable, /names 0 profiles/)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
let(:profile_name){'abcd1234.default-release'}
|
|
72
|
+
|
|
73
|
+
context "class methods" do
|
|
74
|
+
subject{Browser::Firefox}
|
|
75
|
+
|
|
76
|
+
describe '.bookmarks_location' do
|
|
77
|
+
let(:expected_bookmarks_location){File.expand_path('~/Library/Application Support/Firefox/Profiles/abcd1234.default-release/places.sqlite')}
|
|
78
|
+
|
|
79
|
+
it "contains the correct bookmarks location" do
|
|
80
|
+
expect(subject.bookmarks_location(profile_name: profile_name)).to eq(expected_bookmarks_location)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe '.history_location' do
|
|
85
|
+
let(:expected_history_location){File.expand_path('~/Library/Application Support/Firefox/Profiles/abcd1234.default-release/places.sqlite')}
|
|
86
|
+
|
|
87
|
+
it "contains the correct history location" do
|
|
88
|
+
expect(subject.history_location(profile_name: profile_name)).to eq(expected_history_location)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe ".bookmarks_sql" do
|
|
93
|
+
it "returns the bookmarks sql" do
|
|
94
|
+
expect(subject.send(:bookmarks_sql)).to eq(expected_bookmarks_sql)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe ".history_sql" do
|
|
99
|
+
it "returns the history sql" do
|
|
100
|
+
expect(subject.send(:history_sql)).to eq(expected_history_sql)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context "instance methods" do
|
|
106
|
+
subject{Browser::Firefox.new(bookmarks_location: places_fixtures_location, history_location: places_fixtures_location, profile_name: profile_name)}
|
|
107
|
+
|
|
108
|
+
let(:browser_name){'Firefox'}
|
|
109
|
+
let(:expected_history) do
|
|
110
|
+
[
|
|
111
|
+
{'id' => 1, 'url' => 'https://addons.mozilla.org/en-US/firefox/', 'title' => 'Extension Starter Pack', 'visit_date' => 1787449152741667},
|
|
112
|
+
]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "#initialize" do
|
|
116
|
+
it "returns an instance of Browser" do
|
|
117
|
+
expect(subject).to be_a(Browser::Firefox)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "assigns @bookmarks_location" do
|
|
121
|
+
expect(subject.instance_variable_get(:@bookmarks_location)).to eq(places_fixtures_location)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "assigns @history_location" do
|
|
125
|
+
expect(subject.instance_variable_get(:@history_location)).to eq(places_fixtures_location)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "assigns @profile_name" do
|
|
129
|
+
expect(subject.instance_variable_get(:@profile_name)).to eq(profile_name)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe "#name" do
|
|
134
|
+
it "returns name" do
|
|
135
|
+
expect(subject.name).to eq(browser_name)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
let(:expected_bookmarks) do
|
|
140
|
+
[
|
|
141
|
+
{title: 'Get Help', url: 'https://support.mozilla.org/products/firefox', folder: 'Bookmarks Menu/Mozilla Firefox'},
|
|
142
|
+
{title: 'Customize Firefox', url: 'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars', folder: 'Bookmarks Menu/Mozilla Firefox'},
|
|
143
|
+
{title: 'Get Involved', url: 'https://www.mozilla.org/contribute/', folder: 'Bookmarks Menu/Mozilla Firefox'},
|
|
144
|
+
{title: 'About Us', url: 'https://www.mozilla.org/about/', folder: 'Bookmarks Menu/Mozilla Firefox'},
|
|
145
|
+
{title: 'Extension Starter Pack', url: 'https://addons.mozilla.org/en-US/firefox/', folder: 'Bookmarks Toolbar'},
|
|
146
|
+
]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "#bookmarks" do
|
|
150
|
+
it "returns an instance of Browser::Bookmarks" do
|
|
151
|
+
expect(subject.bookmarks).to be_a(Browser::Bookmarks)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "returns the bookmarks" do
|
|
155
|
+
expect(subject.bookmarks.to_objects).to eq(expected_bookmarks)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
describe "#history" do
|
|
160
|
+
it "returns an instance of Browser::History" do
|
|
161
|
+
expect(subject.history).to be_a(Browser::History)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "returns the history" do
|
|
165
|
+
expect(subject.history.to_json).to eq(expected_history.to_json)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
describe "#readable?" do
|
|
170
|
+
it "is true when the data is there" do
|
|
171
|
+
expect(subject.bookmarks.readable?).to be(true)
|
|
172
|
+
expect(subject.history.readable?).to be(true)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "WHEN the data is not there" do
|
|
177
|
+
subject{Browser::Firefox.new(bookmarks_location: '/nonexistent/file', history_location: '/nonexistent/file')}
|
|
178
|
+
|
|
179
|
+
it "is not readable" do
|
|
180
|
+
expect(subject.bookmarks.readable?).to be(false)
|
|
181
|
+
expect(subject.history.readable?).to be(false)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "raises Browser::Unreadable" do
|
|
185
|
+
expect{subject.bookmarks.to_json}.to raise_error(Browser::Unreadable)
|
|
186
|
+
expect{subject.history.to_json}.to raise_error(Browser::Unreadable)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
context "WHEN the database is locked, as it is while the browser runs" do
|
|
191
|
+
it "is not readable" do
|
|
192
|
+
Dir.mktmpdir do |directory|
|
|
193
|
+
path = File.join(directory, 'places.sqlite')
|
|
194
|
+
FileUtils.cp(places_fixtures_location, path)
|
|
195
|
+
locker = SQLite3::Database.new(path)
|
|
196
|
+
locker.execute('BEGIN EXCLUSIVE')
|
|
197
|
+
browser = Browser::Firefox.new(bookmarks_location: path, history_location: path)
|
|
198
|
+
expect(browser.bookmarks.readable?).to be(false)
|
|
199
|
+
expect{browser.history.to_json}.to raise_error(Browser::Unreadable)
|
|
200
|
+
locker.close
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Rendering_test.rb
|
|
2
|
+
|
|
3
|
+
gem 'minitest'
|
|
4
|
+
gem 'minitest-spec-context'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
require 'minitest-spec-context'
|
|
8
|
+
require 'rspec/expectations/minitest_integration'
|
|
9
|
+
|
|
10
|
+
lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
|
11
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
12
|
+
|
|
13
|
+
require 'csv'
|
|
14
|
+
|
|
15
|
+
require 'browser'
|
|
16
|
+
|
|
17
|
+
describe Browser::Rendering do
|
|
18
|
+
let(:records){[{title: 'Home', url: 'https://example.com/', folder: 'Bar'}, {title: 'Docs', url: 'https://example.com/docs', folder: ''}]}
|
|
19
|
+
let(:column_names){%i{title url folder}}
|
|
20
|
+
|
|
21
|
+
describe Browser::Rendering::CSV do
|
|
22
|
+
it "renders the column names, then a row per record in their order" do
|
|
23
|
+
expect(Browser::Rendering::CSV.new(records, column_names).render).to eq(%Q{title,url,folder\nHome,https://example.com/,Bar\nDocs,https://example.com/docs,""\n})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "renders the column names alone when there are no records" do
|
|
27
|
+
expect(Browser::Rendering::CSV.new([], column_names).render).to eq(%Q{title,url,folder\n})
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "doubles a quote within a field, so the row still parses" do
|
|
31
|
+
records = [{title: %Q{He said "hi", then left}, url: 'https://example.com/', folder: ''}]
|
|
32
|
+
rendered = Browser::Rendering::CSV.new(records, column_names).render
|
|
33
|
+
expect(rendered).to eq(%Q{title,url,folder\n"He said ""hi"", then left",https://example.com/,""\n})
|
|
34
|
+
expect(::CSV.parse(rendered).last).to eq(['He said "hi", then left', 'https://example.com/', ''])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "carries a comma and a newline within a field, quoting it" do
|
|
38
|
+
records = [{title: "one, two\nthree", url: 'https://example.com/', folder: ''}]
|
|
39
|
+
rendered = Browser::Rendering::CSV.new(records, column_names).render
|
|
40
|
+
expect(::CSV.parse(rendered).last).to eq(["one, two\nthree", 'https://example.com/', ''])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "carries a title which is not ASCII" do
|
|
44
|
+
records = [{title: 'Café — 日本語', url: 'https://example.com/', folder: ''}]
|
|
45
|
+
rendered = Browser::Rendering::CSV.new(records, column_names).render
|
|
46
|
+
expect(::CSV.parse(rendered).last.first).to eq('Café — 日本語')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "takes the columns in the order given, whatever the records' own" do
|
|
50
|
+
expect(Browser::Rendering::CSV.new(records, %i{url title}).render).to eq(%Q{url,title\nhttps://example.com/,Home\nhttps://example.com/docs,Docs\n})
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe Browser::Rendering::JSON do
|
|
55
|
+
it "renders the records as a JSON array" do
|
|
56
|
+
expect(Browser::Rendering::JSON.new(records).render).to eq(records.to_json)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe Browser::Rendering::Plist do
|
|
61
|
+
it "renders the records as an XML property list which reads back as they were" do
|
|
62
|
+
plist = Browser::Rendering::Plist.new(records).render
|
|
63
|
+
read_back = CFPropertyList.native_types(CFPropertyList::List.new(data: plist).value)
|
|
64
|
+
expect(plist).to start_with('<?xml')
|
|
65
|
+
expect(read_back).to eq(records.collect{|record| record.transform_keys(&:to_s)})
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Safari_test.rb
|
|
2
|
+
|
|
3
|
+
gem 'minitest'
|
|
4
|
+
gem 'minitest-spec-context'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
require 'minitest-spec-context'
|
|
8
|
+
require 'rspec/expectations/minitest_integration'
|
|
9
|
+
|
|
10
|
+
lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
|
11
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
12
|
+
|
|
13
|
+
require 'browser'
|
|
14
|
+
|
|
15
|
+
describe Browser::Safari do
|
|
16
|
+
let(:bookmarks_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/Safari_bookmarks.plist")}
|
|
17
|
+
let(:history_fixtures_location){File.expand_path("#{__FILE__}/../../fixtures/Safari_history.sqlite")}
|
|
18
|
+
let(:expected_history_sql){'SELECT history_items.id, history_items.url, history_visits.visit_time FROM history_items LEFT JOIN history_visits WHERE history_items.id = history_visits.history_item ORDER BY visit_time DESC;'}
|
|
19
|
+
|
|
20
|
+
describe 'DEFAULT_PROFILE_NAME' do
|
|
21
|
+
it "has no default profile name" do
|
|
22
|
+
expect(Browser::Safari::DEFAULT_PROFILE_NAME).to be_nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "class methods" do
|
|
27
|
+
subject{Browser::Safari}
|
|
28
|
+
|
|
29
|
+
describe '.bookmarks_location' do
|
|
30
|
+
let(:expected_bookmarks_location){File.expand_path('~/Library/Safari/Bookmarks.plist')}
|
|
31
|
+
|
|
32
|
+
it "contains the correct bookmarks location" do
|
|
33
|
+
expect(subject.bookmarks_location).to eq(expected_bookmarks_location)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '.history_location' do
|
|
38
|
+
let(:expected_history_location){File.expand_path('~/Library/Safari/History.db')}
|
|
39
|
+
|
|
40
|
+
it "contains the correct history location" do
|
|
41
|
+
expect(subject.history_location).to eq(expected_history_location)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe ".bookmarks_sql" do
|
|
46
|
+
it "returns the bookmarks sql" do
|
|
47
|
+
expect(subject.send(:bookmarks_sql)).to be_nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe ".history_sql" do
|
|
52
|
+
it "returns the history sql" do
|
|
53
|
+
expect(subject.send(:history_sql)).to eq(expected_history_sql)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context "instance methods" do
|
|
59
|
+
subject{Browser::Safari.new(bookmarks_location: bookmarks_fixtures_location, history_location: history_fixtures_location)}
|
|
60
|
+
|
|
61
|
+
let(:browser_name){'Safari'}
|
|
62
|
+
let(:expected_history) do
|
|
63
|
+
[
|
|
64
|
+
{'id' => 1, 'url' => 'https://www.apple.com/', 'visit_time' => 780451200.0},
|
|
65
|
+
{'id' => 2, 'url' => 'https://support.apple.com/', 'visit_time' => 780450000.0},
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "#initialize" do
|
|
70
|
+
it "returns an instance of Browser" do
|
|
71
|
+
expect(subject).to be_a(Browser::Safari)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "assigns @bookmarks_location" do
|
|
75
|
+
expect(subject.instance_variable_get(:@bookmarks_location)).to eq(bookmarks_fixtures_location)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "assigns @history_location" do
|
|
79
|
+
expect(subject.instance_variable_get(:@history_location)).to eq(history_fixtures_location)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "assigns no @profile_name" do
|
|
83
|
+
expect(subject.instance_variable_get(:@profile_name)).to be_nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "#name" do
|
|
88
|
+
it "returns name" do
|
|
89
|
+
expect(subject.name).to eq(browser_name)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
let(:expected_bookmarks) do
|
|
94
|
+
[
|
|
95
|
+
{title: 'Apple', url: 'https://www.apple.com/', folder: 'BookmarksBar'},
|
|
96
|
+
{title: 'iCloud', url: 'https://www.icloud.com/', folder: 'BookmarksBar'},
|
|
97
|
+
{title: 'Yahoo', url: 'https://www.yahoo.com/', folder: 'BookmarksBar'},
|
|
98
|
+
{title: 'Bing', url: 'https://www.bing.com/', folder: 'BookmarksBar'},
|
|
99
|
+
{title: 'Google', url: 'https://www.google.com/?client=safari&channel=mac_bm', folder: 'BookmarksBar'},
|
|
100
|
+
{title: 'Wikipedia', url: 'https://www.wikipedia.org/', folder: 'BookmarksBar'},
|
|
101
|
+
{title: 'Facebook', url: 'https://www.facebook.com/', folder: 'BookmarksBar'},
|
|
102
|
+
{title: 'Twitter', url: 'https://twitter.com/', folder: 'BookmarksBar'},
|
|
103
|
+
{title: 'LinkedIn', url: 'https://www.linkedin.com/', folder: 'BookmarksBar'},
|
|
104
|
+
{title: 'The Weather Channel', url: 'https://www.weather.com/', folder: 'BookmarksBar'},
|
|
105
|
+
{title: 'Yelp', url: 'https://www.yelp.com/', folder: 'BookmarksBar'},
|
|
106
|
+
{title: 'TripAdvisor', url: 'https://www.tripadvisor.com/', folder: 'BookmarksBar'},
|
|
107
|
+
{title: 'Home \ Anthropic', url: 'https://www.anthropic.com/', folder: ''},
|
|
108
|
+
]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe "#bookmarks" do
|
|
112
|
+
it "returns an instance of Browser::Bookmarks" do
|
|
113
|
+
expect(subject.bookmarks).to be_a(Browser::Bookmarks)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "returns the bookmarks" do
|
|
117
|
+
expect(subject.bookmarks.to_objects).to eq(expected_bookmarks)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "#history" do
|
|
122
|
+
it "returns an instance of Browser::History" do
|
|
123
|
+
expect(subject.history).to be_a(Browser::History)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "returns the history" do
|
|
127
|
+
expect(subject.history.to_json).to eq(expected_history.to_json)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "#readable?" do
|
|
132
|
+
it "is true when the data is there" do
|
|
133
|
+
expect(subject.bookmarks.readable?).to be(true)
|
|
134
|
+
expect(subject.history.readable?).to be(true)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context "WHEN the data is not there" do
|
|
139
|
+
subject{Browser::Safari.new(bookmarks_location: '/nonexistent/file', history_location: '/nonexistent/file')}
|
|
140
|
+
|
|
141
|
+
it "is not readable" do
|
|
142
|
+
expect(subject.bookmarks.readable?).to be(false)
|
|
143
|
+
expect(subject.history.readable?).to be(false)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "raises Browser::Unreadable" do
|
|
147
|
+
expect{subject.bookmarks.to_json}.to raise_error(Browser::Unreadable)
|
|
148
|
+
expect{subject.history.to_json}.to raise_error(Browser::Unreadable)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|