indexmap 0.5.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +2 -2
- data/README.md +162 -10
- data/lib/indexmap/configuration.rb +27 -26
- data/lib/indexmap/creator.rb +62 -0
- data/lib/indexmap/index_now_configuration.rb +12 -5
- data/lib/indexmap/output.rb +72 -0
- data/lib/indexmap/parser.rb +26 -15
- data/lib/indexmap/pinger/base.rb +5 -1
- data/lib/indexmap/pinger/google.rb +17 -6
- data/lib/indexmap/pinger/index_now.rb +21 -24
- data/lib/indexmap/storage/active_storage.rb +105 -0
- data/lib/indexmap/storage/file.rb +11 -0
- data/lib/indexmap/storage/filesystem.rb +77 -0
- data/lib/indexmap/storage/memory.rb +61 -0
- data/lib/indexmap/task_runner.rb +13 -13
- data/lib/indexmap/validator.rb +42 -30
- data/lib/indexmap/version.rb +1 -1
- data/lib/indexmap/writer.rb +10 -9
- data/lib/indexmap.rb +10 -2
- data/lib/tasks/indexmap_tasks.rake +7 -9
- data/test/indexmap/configuration_test.rb +129 -26
- data/test/indexmap/parser_test.rb +44 -3
- data/test/indexmap/pinger/google_test.rb +125 -96
- data/test/indexmap/pinger/index_now_test.rb +148 -179
- data/test/indexmap/storage_test.rb +123 -0
- data/test/indexmap/task_runner_test.rb +97 -41
- data/test/indexmap/validator_test.rb +96 -92
- data/test/indexmap/writer_test.rb +63 -74
- metadata +8 -3
- data/lib/indexmap/path.rb +0 -43
- data/test/indexmap/path_test.rb +0 -28
|
@@ -6,243 +6,198 @@ class IndexmapPingerIndexNowTest < Minitest::Test
|
|
|
6
6
|
VALID_KEY = "1234567890abcdef1234567890abcdef"
|
|
7
7
|
|
|
8
8
|
def test_writes_key_file_from_configuration
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
configuration.public_path = Pathname(dir)
|
|
13
|
-
configuration.index_now.key = VALID_KEY
|
|
9
|
+
storage = Indexmap::Storage::Memory.new
|
|
10
|
+
configuration = configuration_with(storage: storage)
|
|
11
|
+
configuration.index_now.key = VALID_KEY
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
filename = Indexmap::Pinger::IndexNow.new(configuration: configuration).write_key_file
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
assert_equal "#{VALID_KEY}.txt", filename
|
|
16
|
+
assert_equal VALID_KEY, storage.read(filename)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_does_not_rewrite_existing_valid_key_file
|
|
20
|
+
storage = Indexmap::Storage::Memory.new
|
|
21
|
+
storage.write("#{VALID_KEY}.txt", VALID_KEY, content_type: "text/plain")
|
|
22
|
+
configuration = configuration_with(storage: storage)
|
|
23
|
+
configuration.index_now.key = VALID_KEY
|
|
24
|
+
|
|
25
|
+
result = Indexmap::Pinger::IndexNow.new(configuration: configuration).write_key_file
|
|
26
|
+
|
|
27
|
+
assert_equal "#{VALID_KEY}.txt", result
|
|
28
|
+
assert_equal VALID_KEY, storage.read(result)
|
|
20
29
|
end
|
|
21
30
|
|
|
22
31
|
def test_ensure_key_file_generates_a_key_when_configuration_is_missing
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
configuration.base_url = "https://www.example.com"
|
|
26
|
-
configuration.public_path = Pathname(dir)
|
|
32
|
+
storage = Indexmap::Storage::Memory.new
|
|
33
|
+
configuration = configuration_with(storage: storage)
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
filename = Indexmap::Pinger::IndexNow.new(configuration: configuration).ensure_key_file
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
37
|
+
assert_match(/\A[a-f0-9]{32}\.txt\z/, filename)
|
|
38
|
+
assert_equal filename.delete_suffix(".txt"), storage.read(filename)
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
def test_pings_using_existing_key_file_when_key_is_not_configured
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
key_path.write(VALID_KEY)
|
|
40
|
-
write_sitemap_files(
|
|
41
|
-
public_path,
|
|
42
|
-
marketing_lastmod: "2026-04-18T00:00:00Z",
|
|
43
|
-
insights_lastmod: "2026-04-10T00:00:00Z"
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
configuration = Indexmap::Configuration.new
|
|
47
|
-
configuration.base_url = "https://www.example.com"
|
|
48
|
-
configuration.public_path = public_path
|
|
42
|
+
storage = sitemap_storage
|
|
43
|
+
storage.write("#{VALID_KEY}.txt", VALID_KEY, content_type: "text/plain")
|
|
44
|
+
configuration = configuration_with(storage: storage)
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
indexnow_url = "https://api.indexnow.org/indexnow"
|
|
47
|
+
stub_request(:post, indexnow_url).to_return(status: 200, body: "", headers: {})
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
result = Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
end
|
|
60
|
-
assert_equal :submitted, result[:status]
|
|
61
|
-
assert_equal 2, result[:url_count]
|
|
62
|
-
assert_equal 1, result[:batch_count]
|
|
51
|
+
assert_requested(:post, indexnow_url, times: 1) do |request|
|
|
52
|
+
payload = JSON.parse(request.body)
|
|
53
|
+
assert_equal VALID_KEY, payload.fetch("key")
|
|
54
|
+
assert_equal "https://www.example.com/#{VALID_KEY}.txt", payload.fetch("keyLocation")
|
|
63
55
|
end
|
|
56
|
+
assert_equal :submitted, result[:status]
|
|
57
|
+
assert_equal 2, result[:url_count]
|
|
58
|
+
assert_equal 1, result[:batch_count]
|
|
64
59
|
end
|
|
65
60
|
|
|
66
61
|
def test_ignores_existing_key_file_with_trailing_newline
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
invalid_key_path.write("#{VALID_KEY}\n")
|
|
62
|
+
storage = Indexmap::Storage::Memory.new
|
|
63
|
+
storage.write("#{VALID_KEY}.txt", "#{VALID_KEY}\n", content_type: "text/plain")
|
|
64
|
+
configuration = configuration_with(storage: storage)
|
|
71
65
|
|
|
72
|
-
|
|
73
|
-
configuration.base_url = "https://www.example.com"
|
|
74
|
-
configuration.public_path = public_path
|
|
66
|
+
filename = Indexmap::Pinger::IndexNow.new(configuration: configuration).ensure_key_file
|
|
75
67
|
|
|
76
|
-
|
|
68
|
+
refute_equal "#{VALID_KEY}.txt", filename
|
|
69
|
+
assert_match(/\A[a-f0-9]{32}\.txt\z/, filename)
|
|
70
|
+
assert_equal filename.delete_suffix(".txt"), storage.read(filename)
|
|
71
|
+
end
|
|
77
72
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
def test_pings_all_sitemap_urls_when_no_cutoff_is_provided
|
|
74
|
+
configuration = configuration_with(storage: sitemap_storage)
|
|
75
|
+
configuration.index_now.key = VALID_KEY
|
|
76
|
+
|
|
77
|
+
indexnow_url = "https://api.indexnow.org/indexnow"
|
|
78
|
+
stub_request(:post, indexnow_url).to_return(status: 200, body: "", headers: {})
|
|
79
|
+
|
|
80
|
+
result = Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
81
|
+
|
|
82
|
+
assert_requested(:post, indexnow_url, times: 1) do |request|
|
|
83
|
+
payload = JSON.parse(request.body)
|
|
84
|
+
assert_equal [
|
|
85
|
+
"https://www.example.com/pages/features",
|
|
86
|
+
"https://www.example.com/insights/us/restaurants/overview"
|
|
87
|
+
].sort, payload.fetch("urlList").sort
|
|
88
|
+
assert_equal "https://www.example.com/#{VALID_KEY}.txt", payload.fetch("keyLocation")
|
|
81
89
|
end
|
|
90
|
+
assert_equal :submitted, result[:status]
|
|
91
|
+
assert_equal 2, result[:url_count]
|
|
92
|
+
assert_equal 1, result[:batch_count]
|
|
82
93
|
end
|
|
83
94
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
def test_pings_sitemap_urls_from_directory_storage_keys
|
|
96
|
+
storage = Indexmap::Storage::Memory.new(public_url: "https://www.example.com")
|
|
97
|
+
storage.write("sitemaps/sitemap.xml", <<~XML)
|
|
98
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
99
|
+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
100
|
+
<sitemap><loc>https://www.example.com/sitemaps/sitemap-marketing.xml</loc></sitemap>
|
|
101
|
+
</sitemapindex>
|
|
102
|
+
XML
|
|
103
|
+
storage.write("sitemaps/sitemap-marketing.xml", <<~XML)
|
|
104
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
105
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
106
|
+
<url><loc>https://www.example.com/pages/features</loc></url>
|
|
107
|
+
</urlset>
|
|
108
|
+
XML
|
|
109
|
+
configuration = configuration_with(storage: storage, index_filename: "sitemaps/sitemap.xml")
|
|
110
|
+
configuration.index_now.key = VALID_KEY
|
|
97
111
|
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
indexnow_url = "https://api.indexnow.org/indexnow"
|
|
113
|
+
stub_request(:post, indexnow_url).to_return(status: 200, body: "", headers: {})
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
result = Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
102
116
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"https://www.example.com/pages/features",
|
|
107
|
-
"https://www.example.com/insights/us/restaurants/overview"
|
|
108
|
-
].sort, payload.fetch("urlList").sort
|
|
109
|
-
assert_equal "https://www.example.com/#{VALID_KEY}.txt", payload.fetch("keyLocation")
|
|
110
|
-
end
|
|
111
|
-
assert_equal :submitted, result[:status]
|
|
112
|
-
assert_equal 2, result[:url_count]
|
|
113
|
-
assert_equal 1, result[:batch_count]
|
|
117
|
+
assert_requested(:post, indexnow_url, times: 1) do |request|
|
|
118
|
+
payload = JSON.parse(request.body)
|
|
119
|
+
assert_equal ["https://www.example.com/pages/features"], payload.fetch("urlList")
|
|
114
120
|
end
|
|
121
|
+
assert_equal :submitted, result[:status]
|
|
122
|
+
assert_equal 1, result[:url_count]
|
|
115
123
|
end
|
|
116
124
|
|
|
117
125
|
def test_pings_only_sitemap_urls_newer_than_since
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
write_sitemap_files(
|
|
121
|
-
public_path,
|
|
122
|
-
marketing_lastmod: "2026-04-18T00:00:00Z",
|
|
123
|
-
insights_lastmod: "2026-04-10T00:00:00Z"
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
configuration = Indexmap::Configuration.new
|
|
127
|
-
configuration.base_url = "https://www.example.com"
|
|
128
|
-
configuration.public_path = public_path
|
|
129
|
-
configuration.index_now.key = VALID_KEY
|
|
126
|
+
configuration = configuration_with(storage: sitemap_storage)
|
|
127
|
+
configuration.index_now.key = VALID_KEY
|
|
130
128
|
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
indexnow_url = "https://api.indexnow.org/indexnow"
|
|
130
|
+
stub_request(:post, indexnow_url).to_return(status: 200, body: "", headers: {})
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
with_env("SINCE" => "2026-04-15T00:00:00Z") do
|
|
133
|
+
result = Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
assert_equal :submitted, result[:status]
|
|
136
|
+
assert_equal 1, result[:url_count]
|
|
137
|
+
assert_equal 1, result[:batch_count]
|
|
138
|
+
end
|
|
141
139
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
end
|
|
140
|
+
assert_requested(:post, indexnow_url, times: 1) do |request|
|
|
141
|
+
payload = JSON.parse(request.body)
|
|
142
|
+
assert_equal ["https://www.example.com/pages/features"], payload.fetch("urlList")
|
|
146
143
|
end
|
|
147
144
|
end
|
|
148
145
|
|
|
149
146
|
def test_skips_indexnow_ping_when_key_is_missing
|
|
150
|
-
|
|
151
|
-
public_path = Pathname(dir)
|
|
152
|
-
write_sitemap_files(
|
|
153
|
-
public_path,
|
|
154
|
-
marketing_lastmod: "2026-04-18T00:00:00Z",
|
|
155
|
-
insights_lastmod: "2026-04-10T00:00:00Z"
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
configuration = Indexmap::Configuration.new
|
|
159
|
-
configuration.base_url = "https://www.example.com"
|
|
160
|
-
configuration.public_path = public_path
|
|
147
|
+
configuration = configuration_with(storage: sitemap_storage)
|
|
161
148
|
|
|
162
|
-
|
|
149
|
+
result = Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
163
150
|
|
|
164
|
-
|
|
165
|
-
end
|
|
151
|
+
assert_equal({status: :skipped, reason: :missing_key}, result)
|
|
166
152
|
end
|
|
167
153
|
|
|
168
154
|
def test_reports_indexnow_dry_run
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
write_sitemap_files(
|
|
172
|
-
public_path,
|
|
173
|
-
marketing_lastmod: "2026-04-18T00:00:00Z",
|
|
174
|
-
insights_lastmod: "2026-04-10T00:00:00Z"
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
configuration = Indexmap::Configuration.new
|
|
178
|
-
configuration.base_url = "https://www.example.com"
|
|
179
|
-
configuration.public_path = public_path
|
|
180
|
-
configuration.index_now.key = VALID_KEY
|
|
155
|
+
configuration = configuration_with(storage: sitemap_storage)
|
|
156
|
+
configuration.index_now.key = VALID_KEY
|
|
181
157
|
|
|
182
|
-
|
|
183
|
-
|
|
158
|
+
with_env("INDEXNOW_DRY_RUN" => "1") do
|
|
159
|
+
result = Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
184
160
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
end
|
|
161
|
+
assert_equal :dry_run, result[:status]
|
|
162
|
+
assert_equal 2, result[:url_count]
|
|
163
|
+
assert_equal 1, result[:batch_count]
|
|
189
164
|
end
|
|
190
165
|
end
|
|
191
166
|
|
|
192
167
|
def test_reports_failed_indexnow_submission
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
write_sitemap_files(
|
|
196
|
-
public_path,
|
|
197
|
-
marketing_lastmod: "2026-04-18T00:00:00Z",
|
|
198
|
-
insights_lastmod: "2026-04-10T00:00:00Z"
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
configuration = Indexmap::Configuration.new
|
|
202
|
-
configuration.base_url = "https://www.example.com"
|
|
203
|
-
configuration.public_path = public_path
|
|
204
|
-
configuration.index_now.key = VALID_KEY
|
|
168
|
+
configuration = configuration_with(storage: sitemap_storage)
|
|
169
|
+
configuration.index_now.key = VALID_KEY
|
|
205
170
|
|
|
206
|
-
|
|
207
|
-
|
|
171
|
+
indexnow_url = "https://api.indexnow.org/indexnow"
|
|
172
|
+
stub_request(:post, indexnow_url).to_return(status: 500, body: "boom", headers: {})
|
|
208
173
|
|
|
209
|
-
|
|
174
|
+
result = Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
210
175
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
end
|
|
176
|
+
assert_equal :failed, result[:status]
|
|
177
|
+
assert_equal 1, result[:failures].count
|
|
178
|
+
assert_equal 500, result[:failures].first[:status_code]
|
|
215
179
|
end
|
|
216
180
|
|
|
217
181
|
def test_rejects_invalid_configured_key
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
configuration.base_url = "https://www.example.com"
|
|
221
|
-
configuration.public_path = Pathname(dir)
|
|
222
|
-
configuration.index_now.key = "test-key"
|
|
223
|
-
|
|
224
|
-
error = assert_raises(Indexmap::ConfigurationError) do
|
|
225
|
-
Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
226
|
-
end
|
|
182
|
+
configuration = configuration_with
|
|
183
|
+
configuration.index_now.key = "test-key"
|
|
227
184
|
|
|
228
|
-
|
|
185
|
+
error = assert_raises(Indexmap::ConfigurationError) do
|
|
186
|
+
Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
|
|
229
187
|
end
|
|
188
|
+
|
|
189
|
+
assert_equal "IndexNow key must be a 32-character lowercase hexadecimal string", error.message
|
|
230
190
|
end
|
|
231
191
|
|
|
232
192
|
def test_reuses_existing_key_file_deterministically
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
configuration = Indexmap::Configuration.new
|
|
239
|
-
configuration.base_url = "https://www.example.com"
|
|
240
|
-
configuration.public_path = public_path
|
|
193
|
+
storage = Indexmap::Storage::Memory.new
|
|
194
|
+
storage.write("ffffffffffffffffffffffffffffffff.txt", "ffffffffffffffffffffffffffffffff")
|
|
195
|
+
storage.write("00000000000000000000000000000000.txt", "00000000000000000000000000000000")
|
|
196
|
+
configuration = configuration_with(storage: storage)
|
|
241
197
|
|
|
242
|
-
|
|
198
|
+
filename = Indexmap::Pinger::IndexNow.new(configuration: configuration).ensure_key_file
|
|
243
199
|
|
|
244
|
-
|
|
245
|
-
end
|
|
200
|
+
assert_equal "00000000000000000000000000000000.txt", filename
|
|
246
201
|
end
|
|
247
202
|
|
|
248
203
|
private
|
|
@@ -257,8 +212,22 @@ class IndexmapPingerIndexNowTest < Minitest::Test
|
|
|
257
212
|
end
|
|
258
213
|
end
|
|
259
214
|
|
|
260
|
-
def
|
|
261
|
-
|
|
215
|
+
def configuration_with(storage: Indexmap::Storage::Memory.new(public_url: "https://www.example.com"), index_filename: "sitemap.xml")
|
|
216
|
+
Indexmap::Configuration.new.tap do |configuration|
|
|
217
|
+
configuration.base_url = "https://www.example.com"
|
|
218
|
+
configuration.index_filename = index_filename
|
|
219
|
+
configuration.storage = storage
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def sitemap_storage(marketing_lastmod: "2026-04-18T00:00:00Z", insights_lastmod: "2026-04-10T00:00:00Z")
|
|
224
|
+
Indexmap::Storage::Memory.new(public_url: "https://www.example.com").tap do |storage|
|
|
225
|
+
write_sitemap_files(storage, marketing_lastmod: marketing_lastmod, insights_lastmod: insights_lastmod)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def write_sitemap_files(storage, marketing_lastmod:, insights_lastmod:)
|
|
230
|
+
storage.write("sitemap.xml", <<~XML)
|
|
262
231
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
263
232
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
264
233
|
<sitemap><loc>https://www.example.com/sitemap-marketing.xml</loc></sitemap>
|
|
@@ -266,7 +235,7 @@ class IndexmapPingerIndexNowTest < Minitest::Test
|
|
|
266
235
|
</sitemapindex>
|
|
267
236
|
XML
|
|
268
237
|
|
|
269
|
-
|
|
238
|
+
storage.write("sitemap-marketing.xml", <<~XML)
|
|
270
239
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
271
240
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
272
241
|
<url>
|
|
@@ -276,7 +245,7 @@ class IndexmapPingerIndexNowTest < Minitest::Test
|
|
|
276
245
|
</urlset>
|
|
277
246
|
XML
|
|
278
247
|
|
|
279
|
-
|
|
248
|
+
storage.write("sitemap-insights.xml", <<~XML)
|
|
280
249
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
281
250
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
282
251
|
<url>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class IndexmapStorageTest < Minitest::Test
|
|
6
|
+
class FakeAttachment
|
|
7
|
+
attr_reader :body, :content_type
|
|
8
|
+
|
|
9
|
+
def attach(io:, filename:, content_type:)
|
|
10
|
+
@filename = filename
|
|
11
|
+
@body = io.read
|
|
12
|
+
@content_type = content_type
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def attached?
|
|
16
|
+
!!@filename
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def download
|
|
20
|
+
body
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def purge
|
|
24
|
+
@filename = nil
|
|
25
|
+
@body = nil
|
|
26
|
+
@content_type = nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class FakeRecord
|
|
31
|
+
attr_reader :file
|
|
32
|
+
attr_accessor :filename
|
|
33
|
+
|
|
34
|
+
def initialize(filename:)
|
|
35
|
+
@filename = filename
|
|
36
|
+
@file = FakeAttachment.new
|
|
37
|
+
@persisted = false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def persisted?
|
|
41
|
+
@persisted
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def save!
|
|
45
|
+
@persisted = true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class FakeRelation
|
|
50
|
+
include Enumerable
|
|
51
|
+
|
|
52
|
+
def initialize(records)
|
|
53
|
+
@records = records
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def each(&block)
|
|
57
|
+
@records.each(&block)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class FakeModel
|
|
62
|
+
def initialize
|
|
63
|
+
@records = {}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def find_or_initialize_by(filename:)
|
|
67
|
+
@records[filename] ||= FakeRecord.new(filename: filename)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def find_by(filename:)
|
|
71
|
+
@records[filename]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def all
|
|
75
|
+
FakeRelation.new(@records.values)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_filesystem_storage_writes_reads_lists_and_builds_public_urls
|
|
80
|
+
Dir.mktmpdir do |dir|
|
|
81
|
+
storage = Indexmap::Storage::Filesystem.new(path: dir, public_url: "https://example.com")
|
|
82
|
+
|
|
83
|
+
storage.write("sitemap.xml", "<xml/>")
|
|
84
|
+
storage.write("sitemaps/sitemap-pages.xml", "<xml/>")
|
|
85
|
+
storage.write("robots.txt", "robots", content_type: "text/plain")
|
|
86
|
+
|
|
87
|
+
assert storage.exist?("sitemap.xml")
|
|
88
|
+
assert storage.exist?("sitemaps/sitemap-pages.xml")
|
|
89
|
+
assert_equal "<xml/>", storage.read("sitemap.xml")
|
|
90
|
+
assert_equal "<xml/>", storage.read("sitemaps/sitemap-pages.xml")
|
|
91
|
+
assert_equal ["sitemap.xml", "sitemaps/sitemap-pages.xml"], storage.list(prefix: "sitemap", suffix: ".xml")
|
|
92
|
+
assert_equal "https://example.com/sitemap.xml", storage.public_url("sitemap.xml")
|
|
93
|
+
assert_equal "https://example.com/sitemaps/sitemap-pages.xml", storage.public_url("sitemaps/sitemap-pages.xml")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_filesystem_storage_rejects_absolute_filenames
|
|
98
|
+
Dir.mktmpdir do |dir|
|
|
99
|
+
storage = Indexmap::Storage::Filesystem.new(path: dir)
|
|
100
|
+
|
|
101
|
+
assert_raises(ArgumentError) { storage.write("/tmp/sitemap.xml", "") }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_active_storage_adapter_does_not_require_active_storage_to_load
|
|
106
|
+
assert Indexmap::Storage::ActiveStorage
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_active_storage_adapter_uses_supplied_model_and_attachment
|
|
110
|
+
model = FakeModel.new
|
|
111
|
+
storage = Indexmap::Storage::ActiveStorage.new(
|
|
112
|
+
model: model,
|
|
113
|
+
public_url: "https://example.com"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
storage.write("sitemap.xml", "<xml/>")
|
|
117
|
+
|
|
118
|
+
assert storage.exist?("sitemap.xml")
|
|
119
|
+
assert_equal "<xml/>", storage.read("sitemap.xml")
|
|
120
|
+
assert_equal ["sitemap.xml"], storage.list(prefix: "sitemap", suffix: ".xml")
|
|
121
|
+
assert_equal "https://example.com/sitemap.xml", storage.public_url("sitemap.xml")
|
|
122
|
+
end
|
|
123
|
+
end
|