indexmap 0.2.1 → 0.3.1

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.
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+ require "time"
6
+
7
+ module Indexmap
8
+ module Pinger
9
+ class IndexNow < Base
10
+ def initialize(configuration: Indexmap.configuration, connection: nil)
11
+ super(configuration: configuration)
12
+ @connection = connection
13
+ end
14
+
15
+ def ping
16
+ api_key = read_api_key
17
+ unless api_key
18
+ logger.debug("IndexNow API key is not configured.")
19
+ return
20
+ end
21
+
22
+ entries = entries_to_ping
23
+ if entries.empty?
24
+ logger.debug("IndexNow: no URLs matched the current filter.")
25
+ return
26
+ end
27
+
28
+ entries.each_slice(max_urls_per_request) do |batch|
29
+ urls = batch.map(&:loc)
30
+
31
+ if dry_run?
32
+ logger.debug { "IndexNow dry-run: would ping #{urls.count} URLs." }
33
+ next
34
+ end
35
+
36
+ submit_batch(api_key: api_key, urls: urls)
37
+ end
38
+ end
39
+
40
+ def write_key_file
41
+ key = index_now_configuration.key.to_s.strip
42
+ return if key.empty?
43
+
44
+ path = index_now_configuration.key_path(public_path: configuration.public_path)
45
+ FileUtils.mkdir_p(path.dirname)
46
+ File.write(path, "#{key}\n")
47
+ path
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :connection
53
+
54
+ def index_now_configuration
55
+ configuration.index_now
56
+ end
57
+
58
+ def sitemap_files
59
+ files = super
60
+ return files if files.one?
61
+
62
+ child_files = files.reject { |file| File.basename(file) == configuration.index_filename }
63
+ child_files.empty? ? files : child_files
64
+ end
65
+
66
+ def entries_to_ping
67
+ cutoff = since_cutoff
68
+ unless cutoff
69
+ logger.debug("IndexNow: no cutoff provided, submitting all sitemap URLs.")
70
+ return current_entries.values
71
+ end
72
+
73
+ logger.debug { "IndexNow: submitting sitemap URLs with lastmod >= #{cutoff.iso8601}." }
74
+
75
+ current_entries.values.select do |entry|
76
+ lastmod_after_cutoff?(entry, cutoff) || entry.lastmod.to_s.strip.empty?
77
+ end
78
+ end
79
+
80
+ def current_entries
81
+ sitemap_files.each_with_object({}) do |sitemap_file, entries|
82
+ Parser.new(path: sitemap_file).entries.each do |entry|
83
+ next if entry.loc.to_s.strip.empty?
84
+
85
+ entries[entry.loc] = entry
86
+ end
87
+ end
88
+ end
89
+
90
+ def since_cutoff
91
+ raw_value = ENV["SINCE"].to_s.strip
92
+ return recent_cutoff if raw_value.empty?
93
+
94
+ Time.iso8601(raw_value).utc
95
+ rescue ArgumentError
96
+ raise ArgumentError, "Invalid SINCE value: #{raw_value.inspect}. Use ISO 8601, e.g. 2026-04-18T10:30:00Z."
97
+ end
98
+
99
+ def recent_cutoff
100
+ hours = ENV["INDEXNOW_RECENT_HOURS"].to_s.strip
101
+ return if hours.empty?
102
+
103
+ hours_ago = Integer(hours, exception: false)
104
+ unless hours_ago&.positive?
105
+ raise ArgumentError, "Invalid INDEXNOW_RECENT_HOURS value: #{hours.inspect}. Use a positive integer."
106
+ end
107
+
108
+ Time.now.utc - (hours_ago * 3600)
109
+ end
110
+
111
+ def lastmod_after_cutoff?(entry, cutoff)
112
+ lastmod = entry_lastmod(entry)
113
+ return false unless lastmod
114
+
115
+ lastmod >= cutoff
116
+ end
117
+
118
+ def entry_lastmod(entry)
119
+ return if entry.lastmod.to_s.strip.empty?
120
+
121
+ Time.iso8601(entry.lastmod.to_s).utc
122
+ rescue ArgumentError
123
+ logger.debug { "IndexNow: skipping invalid sitemap lastmod #{entry.lastmod.inspect} for #{entry.loc}" }
124
+ nil
125
+ end
126
+
127
+ def max_urls_per_request
128
+ ENV.fetch("INDEXNOW_MAX_URLS_PER_REQUEST", index_now_configuration.max_urls_per_request).to_i
129
+ end
130
+
131
+ def submit_batch(api_key:, urls:)
132
+ payload = {host: hostname, key: api_key, urlList: urls}
133
+ response = index_now_connection.post("/indexnow") do |request|
134
+ request.headers["Content-Type"] = "application/json"
135
+ request.body = payload.to_json
136
+ end
137
+
138
+ if response.success?
139
+ logger.debug { "Successfully pinged IndexNow with #{urls.count} URLs." }
140
+ true
141
+ else
142
+ logger.debug { "Failed to ping IndexNow. Status: #{response.status}, Body: #{response.body}" }
143
+ false
144
+ end
145
+ end
146
+
147
+ def index_now_connection
148
+ @index_now_connection ||= connection || Faraday.new(url: index_now_configuration.endpoint) do |faraday|
149
+ faraday.request :json
150
+ end
151
+ end
152
+
153
+ def dry_run?
154
+ ENV["INDEXNOW_DRY_RUN"] == "1" || index_now_configuration.dry_run?
155
+ end
156
+
157
+ def read_api_key
158
+ configured_key = index_now_configuration.key.to_s.strip
159
+ return configured_key unless configured_key.empty?
160
+
161
+ key_file = configuration.public_path.glob("*.txt").find do |file|
162
+ filename = file.basename(".txt").to_s
163
+ next unless filename.match?(/\A[a-zA-Z0-9-]{8,128}\z/)
164
+
165
+ File.read(file).strip == filename
166
+ end
167
+ return nil unless key_file
168
+
169
+ File.read(key_file).strip
170
+ end
171
+ end
172
+ end
173
+ end
@@ -11,6 +11,7 @@ module Indexmap
11
11
  def create
12
12
  remove_existing_sitemap_files
13
13
  configuration.writer.write
14
+ write_index_now_key
14
15
  end
15
16
 
16
17
  def format
@@ -28,6 +29,14 @@ module Indexmap
28
29
  end
29
30
  end
30
31
 
32
+ def validate
33
+ Validator.new(configuration: configuration).validate!
34
+ end
35
+
36
+ def write_index_now_key
37
+ Pinger::IndexNow.new(configuration: configuration).write_key_file
38
+ end
39
+
31
40
  private
32
41
 
33
42
  attr_reader :configuration
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Indexmap
4
+ class Validator
5
+ def initialize(configuration: Indexmap.configuration, path: nil)
6
+ @configuration = configuration
7
+ @path = path
8
+ end
9
+
10
+ def validate!
11
+ sitemap_path = path || Indexmap::Path.existing_public_path(
12
+ public_path: configuration.public_path,
13
+ index_filename: configuration.index_filename
14
+ )
15
+ raise ValidationError, "Missing sitemap file: #{sitemap_path}" unless File.exist?(sitemap_path)
16
+
17
+ entries = Parser.new(path: sitemap_path).entries
18
+ validate_duplicates!(entries)
19
+ validate_parameterized_urls!(entries)
20
+ true
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :configuration, :path
26
+
27
+ def validate_duplicates!(entries)
28
+ duplicates = entries.map(&:loc).group_by(&:itself).select { |_url, values| values.size > 1 }.keys
29
+ return if duplicates.empty?
30
+
31
+ raise ValidationError, "Duplicate sitemap URLs detected: #{duplicates.first(5).join(", ")}"
32
+ end
33
+
34
+ def validate_parameterized_urls!(entries)
35
+ param_urls = entries.map(&:loc).select { |url| url&.include?("?") }
36
+ return if param_urls.empty?
37
+
38
+ raise ValidationError, "Parameterized sitemap URLs detected: #{param_urls.first(5).join(", ")}"
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Indexmap
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/indexmap.rb CHANGED
@@ -7,16 +7,25 @@ require "pathname"
7
7
  require "time"
8
8
 
9
9
  require_relative "indexmap/version"
10
+ require_relative "indexmap/google_configuration"
11
+ require_relative "indexmap/index_now_configuration"
10
12
  require_relative "indexmap/configuration"
11
13
  require_relative "indexmap/entry"
14
+ require_relative "indexmap/path"
15
+ require_relative "indexmap/parser"
16
+ require_relative "indexmap/pinger/base"
17
+ require_relative "indexmap/pinger/google"
18
+ require_relative "indexmap/pinger/index_now"
12
19
  require_relative "indexmap/section"
13
20
  require_relative "indexmap/task_runner"
21
+ require_relative "indexmap/validator"
14
22
  require_relative "indexmap/writer"
15
23
 
16
24
  module Indexmap
17
25
  class Error < StandardError; end
18
26
 
19
27
  class ConfigurationError < Error; end
28
+ class ValidationError < Error; end
20
29
 
21
30
  class << self
22
31
  def configuration
@@ -4,10 +4,42 @@ namespace :sitemap do
4
4
  runner = Indexmap::TaskRunner.new
5
5
  runner.create
6
6
  runner.format
7
+ runner.validate
7
8
  end
8
9
 
9
10
  desc "Format sitemap files for better readability"
10
11
  task format: :environment do
11
12
  Indexmap::TaskRunner.new.format
12
13
  end
14
+
15
+ desc "Validate sitemap shape and URL hygiene"
16
+ task validate: :environment do
17
+ Indexmap::TaskRunner.new.validate
18
+ end
19
+
20
+ desc "Ping all configured search engines"
21
+ task ping: :environment do
22
+ Rake::Task["sitemap:index_now:ping"].invoke
23
+ Rake::Task["sitemap:google:ping"].invoke
24
+ end
25
+
26
+ namespace :google do
27
+ desc "Ping Google Search Console"
28
+ task ping: :environment do
29
+ Indexmap::Pinger::Google.new.ping
30
+ end
31
+ end
32
+
33
+ namespace :index_now do
34
+ desc "Ping IndexNow. ENV: SINCE=2026-04-18T10:30:00Z or INDEXNOW_RECENT_HOURS=24"
35
+ task ping: :environment do
36
+ Indexmap::Pinger::IndexNow.new.ping
37
+ end
38
+
39
+ desc "Write the IndexNow key file into public/"
40
+ task write_key: :environment do
41
+ path = Indexmap::TaskRunner.new.write_index_now_key
42
+ puts "Wrote #{path}" if path
43
+ end
44
+ end
13
45
  end
@@ -66,4 +66,18 @@ class IndexmapConfigurationTest < Minitest::Test
66
66
 
67
67
  assert_equal "Indexmap format must be one of: index, single_file", error.message
68
68
  end
69
+
70
+ def test_exposes_nested_google_and_index_now_configuration
71
+ Indexmap.configure do |config|
72
+ config.google.credentials = -> { "{\"type\":\"service_account\"}" }
73
+ config.google.property = -> { "sc-domain:example.com" }
74
+ config.index_now.key = -> { "example-key" }
75
+ config.index_now.max_urls_per_request = -> { 250 }
76
+ end
77
+
78
+ assert_equal "{\"type\":\"service_account\"}", Indexmap.configuration.google.credentials
79
+ assert_equal "sc-domain:example.com", Indexmap.configuration.google.property
80
+ assert_equal "example-key", Indexmap.configuration.index_now.key
81
+ assert_equal 250, Indexmap.configuration.index_now.max_urls_per_request
82
+ end
69
83
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class IndexmapParserTest < Minitest::Test
6
+ def test_parses_remote_sitemap_urlset
7
+ stub_request(:get, "https://www.example.com/sitemap.xml")
8
+ .to_return(
9
+ status: 200,
10
+ body: <<~XML
11
+ <?xml version="1.0" encoding="UTF-8"?>
12
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
13
+ <url><loc>https://www.example.com/</loc></url>
14
+ <url><loc>https://www.example.com/pages/features</loc></url>
15
+ </urlset>
16
+ XML
17
+ )
18
+
19
+ parser = Indexmap::Parser.new(path: "https://www.example.com/sitemap.xml")
20
+
21
+ assert_equal ["/", "/pages/features"], parser.paths
22
+ end
23
+
24
+ def test_parses_remote_sitemap_index_with_child_sitemap
25
+ stub_request(:get, "https://www.example.com/sitemap.xml")
26
+ .to_return(
27
+ status: 200,
28
+ body: <<~XML
29
+ <?xml version="1.0" encoding="UTF-8"?>
30
+ <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
31
+ <sitemap><loc>/sitemaps/content.xml</loc></sitemap>
32
+ </sitemapindex>
33
+ XML
34
+ )
35
+
36
+ stub_request(:get, "https://www.example.com/sitemaps/content.xml")
37
+ .to_return(
38
+ status: 200,
39
+ body: <<~XML
40
+ <?xml version="1.0" encoding="UTF-8"?>
41
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
42
+ <url><loc>https://www.example.com/tools/google-reviews-calculator</loc></url>
43
+ </urlset>
44
+ XML
45
+ )
46
+
47
+ parser = Indexmap::Parser.new(path: "https://www.example.com/sitemap.xml")
48
+
49
+ assert_equal ["/tools/google-reviews-calculator"], parser.paths
50
+ assert_equal ["https://www.reviato.com/tools/google-reviews-calculator"], parser.urls(base_url: "https://www.reviato.com")
51
+ end
52
+
53
+ def test_can_rebase_remote_child_sitemap_urls_to_the_fetched_sitemap_origin
54
+ stub_request(:get, "http://localhost:3001/sitemap.xml")
55
+ .to_return(
56
+ status: 200,
57
+ body: <<~XML
58
+ <?xml version="1.0" encoding="UTF-8"?>
59
+ <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
60
+ <sitemap><loc>https://www.reviato.com/sitemap-marketing.xml</loc></sitemap>
61
+ </sitemapindex>
62
+ XML
63
+ )
64
+
65
+ stub_request(:get, "http://localhost:3001/sitemap-marketing.xml")
66
+ .to_return(
67
+ status: 200,
68
+ body: <<~XML
69
+ <?xml version="1.0" encoding="UTF-8"?>
70
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
71
+ <url><loc>https://www.reviato.com/pages/pricing</loc></url>
72
+ </urlset>
73
+ XML
74
+ )
75
+
76
+ parser = Indexmap::Parser.new(path: "http://localhost:3001/sitemap.xml", rebase_remote_children: true)
77
+
78
+ assert_equal ["/pages/pricing"], parser.paths
79
+ assert_equal ["http://localhost:3001/pages/pricing"], parser.urls(base_url: "http://localhost:3001")
80
+ end
81
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class IndexmapPathTest < Minitest::Test
6
+ def test_existing_public_path_prefers_sitemap_index_when_present
7
+ Dir.mktmpdir do |dir|
8
+ public_path = Pathname(dir)
9
+ public_path.join("sitemap_index.xml").write("<urlset/>")
10
+ public_path.join("sitemap.xml").write("<sitemapindex/>")
11
+
12
+ assert_equal public_path.join("sitemap.xml"), Indexmap::Path.existing_public_path(public_path: public_path)
13
+ end
14
+ end
15
+
16
+ def test_existing_public_path_falls_back_to_legacy_sitemap_path
17
+ Dir.mktmpdir do |dir|
18
+ public_path = Pathname(dir)
19
+ public_path.join("sitemap_index.xml").write("<sitemapindex/>")
20
+
21
+ assert_equal public_path.join("sitemap_index.xml"), Indexmap::Path.existing_public_path(public_path: public_path)
22
+ end
23
+ end
24
+
25
+ def test_canonical_url_targets_sitemap_index
26
+ assert_equal "https://www.example.com/sitemap.xml", Indexmap::Path.canonical_url("https://www.example.com")
27
+ end
28
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class IndexmapPingerGoogleTest < Minitest::Test
6
+ SiteEntry = Struct.new(:site_url)
7
+ SiteList = Struct.new(:site_entry)
8
+
9
+ class FakeWebmastersService
10
+ attr_accessor :authorization
11
+ attr_reader :submitted
12
+
13
+ def initialize(site_urls:)
14
+ @site_urls = site_urls
15
+ end
16
+
17
+ def list_sites
18
+ SiteList.new(@site_urls.map { |site_url| SiteEntry.new(site_url) })
19
+ end
20
+
21
+ def submit_sitemap(property, sitemap_url)
22
+ @submitted = [property, sitemap_url]
23
+ end
24
+ end
25
+
26
+ def test_pings_google_for_each_sitemap_file
27
+ Dir.mktmpdir do |dir|
28
+ public_path = Pathname(dir)
29
+ public_path.join("sitemap.xml").write("<sitemapindex/>")
30
+
31
+ configuration = Indexmap::Configuration.new
32
+ configuration.base_url = "https://www.example.com"
33
+ configuration.public_path = public_path
34
+ configuration.google.credentials = "{\"type\":\"service_account\"}"
35
+
36
+ service = FakeWebmastersService.new(site_urls: ["sc-domain:example.com"])
37
+ builder_calls = []
38
+ credentials_builder = lambda do |credentials:, scope:|
39
+ builder_calls << [credentials, scope]
40
+ :fake_authorizer
41
+ end
42
+
43
+ Indexmap::Pinger::Google.new(
44
+ configuration: configuration,
45
+ service: service,
46
+ credentials_builder: credentials_builder
47
+ ).ping
48
+
49
+ assert_equal [["{\"type\":\"service_account\"}", "https://www.googleapis.com/auth/webmasters"]], builder_calls
50
+ assert_equal :fake_authorizer, service.authorization
51
+ assert_equal ["sc-domain:example.com", "https://www.example.com/sitemap.xml"], service.submitted
52
+ end
53
+ end
54
+
55
+ def test_skips_google_ping_when_credentials_are_missing
56
+ Dir.mktmpdir do |dir|
57
+ public_path = Pathname(dir)
58
+ public_path.join("sitemap.xml").write("<sitemapindex/>")
59
+
60
+ configuration = Indexmap::Configuration.new
61
+ configuration.base_url = "https://www.example.com"
62
+ configuration.public_path = public_path
63
+
64
+ service = FakeWebmastersService.new(site_urls: ["sc-domain:example.com"])
65
+
66
+ Indexmap::Pinger::Google.new(configuration: configuration, service: service).ping
67
+
68
+ assert_nil service.submitted
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class IndexmapPingerIndexNowTest < Minitest::Test
6
+ def test_writes_key_file_from_configuration
7
+ Dir.mktmpdir do |dir|
8
+ configuration = Indexmap::Configuration.new
9
+ configuration.base_url = "https://www.example.com"
10
+ configuration.public_path = Pathname(dir)
11
+ configuration.index_now.key = "test-key"
12
+
13
+ path = Indexmap::Pinger::IndexNow.new(configuration: configuration).write_key_file
14
+
15
+ assert_equal Pathname(dir).join("test-key.txt"), path
16
+ assert_equal "test-key\n", path.read
17
+ end
18
+ end
19
+
20
+ def test_pings_all_sitemap_urls_when_no_cutoff_is_provided
21
+ Dir.mktmpdir do |dir|
22
+ public_path = Pathname(dir)
23
+ write_sitemap_files(
24
+ public_path,
25
+ marketing_lastmod: "2026-04-18T00:00:00Z",
26
+ insights_lastmod: "2026-04-10T00:00:00Z"
27
+ )
28
+
29
+ configuration = Indexmap::Configuration.new
30
+ configuration.base_url = "https://www.example.com"
31
+ configuration.public_path = public_path
32
+ configuration.index_now.key = "test-key"
33
+
34
+ indexnow_url = "https://api.indexnow.org/indexnow"
35
+ stub_request(:post, indexnow_url).to_return(status: 200, body: "", headers: {})
36
+
37
+ Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
38
+
39
+ assert_requested(:post, indexnow_url, times: 1) do |request|
40
+ payload = JSON.parse(request.body)
41
+ assert_equal [
42
+ "https://www.example.com/pages/features",
43
+ "https://www.example.com/insights/us/restaurants/overview"
44
+ ].sort, payload.fetch("urlList").sort
45
+ end
46
+ end
47
+ end
48
+
49
+ def test_pings_only_sitemap_urls_newer_than_since
50
+ Dir.mktmpdir do |dir|
51
+ public_path = Pathname(dir)
52
+ write_sitemap_files(
53
+ public_path,
54
+ marketing_lastmod: "2026-04-18T00:00:00Z",
55
+ insights_lastmod: "2026-04-10T00:00:00Z"
56
+ )
57
+
58
+ configuration = Indexmap::Configuration.new
59
+ configuration.base_url = "https://www.example.com"
60
+ configuration.public_path = public_path
61
+ configuration.index_now.key = "test-key"
62
+
63
+ indexnow_url = "https://api.indexnow.org/indexnow"
64
+ stub_request(:post, indexnow_url).to_return(status: 200, body: "", headers: {})
65
+
66
+ with_env("SINCE" => "2026-04-15T00:00:00Z") do
67
+ Indexmap::Pinger::IndexNow.new(configuration: configuration).ping
68
+ end
69
+
70
+ assert_requested(:post, indexnow_url, times: 1) do |request|
71
+ payload = JSON.parse(request.body)
72
+ assert_equal ["https://www.example.com/pages/features"], payload.fetch("urlList")
73
+ end
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def with_env(overrides)
80
+ previous_values = overrides.to_h { |key, _value| [key, ENV[key]] }
81
+ overrides.each { |key, value| ENV[key] = value }
82
+ yield
83
+ ensure
84
+ previous_values.each do |key, value|
85
+ value.nil? ? ENV.delete(key) : ENV[key] = value
86
+ end
87
+ end
88
+
89
+ def write_sitemap_files(public_path, marketing_lastmod:, insights_lastmod:)
90
+ public_path.join("sitemap.xml").write(<<~XML)
91
+ <?xml version="1.0" encoding="UTF-8"?>
92
+ <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
93
+ <sitemap><loc>https://www.example.com/sitemap-marketing.xml</loc></sitemap>
94
+ <sitemap><loc>https://www.example.com/sitemap-insights.xml</loc></sitemap>
95
+ </sitemapindex>
96
+ XML
97
+
98
+ public_path.join("sitemap-marketing.xml").write(<<~XML)
99
+ <?xml version="1.0" encoding="UTF-8"?>
100
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
101
+ <url>
102
+ <loc>https://www.example.com/pages/features</loc>
103
+ <lastmod>#{marketing_lastmod}</lastmod>
104
+ </url>
105
+ </urlset>
106
+ XML
107
+
108
+ public_path.join("sitemap-insights.xml").write(<<~XML)
109
+ <?xml version="1.0" encoding="UTF-8"?>
110
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
111
+ <url>
112
+ <loc>https://www.example.com/insights/us/restaurants/overview</loc>
113
+ <lastmod>#{insights_lastmod}</lastmod>
114
+ </url>
115
+ </urlset>
116
+ XML
117
+ end
118
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ class IndexmapTaskRunnerTest < Minitest::Test
6
+ def test_create_removes_existing_sitemap_files_writes_new_sitemap_and_key_file
7
+ Dir.mktmpdir do |dir|
8
+ public_path = Pathname(dir)
9
+ public_path.join("sitemap.xml").write("old")
10
+ public_path.join("sitemap-pages.xml.gz").write("old")
11
+
12
+ configuration = Indexmap::Configuration.new
13
+ configuration.base_url = "https://example.com"
14
+ configuration.public_path = public_path
15
+ configuration.sections = [
16
+ Indexmap::Section.new(
17
+ filename: "sitemap-pages.xml",
18
+ entries: [Indexmap::Entry.new(loc: "https://example.com/about")]
19
+ )
20
+ ]
21
+ configuration.index_now.key = "test-key"
22
+
23
+ Indexmap::TaskRunner.new(configuration: configuration).create
24
+
25
+ assert_equal false, public_path.join("sitemap-pages.xml.gz").exist?
26
+ assert_includes public_path.join("sitemap.xml").read, "<sitemapindex"
27
+ assert_equal "test-key\n", public_path.join("test-key.txt").read
28
+ end
29
+ end
30
+
31
+ def test_write_index_now_key_returns_nil_when_key_is_not_configured
32
+ Dir.mktmpdir do |dir|
33
+ configuration = Indexmap::Configuration.new
34
+ configuration.base_url = "https://example.com"
35
+ configuration.public_path = Pathname(dir)
36
+ configuration.sections = [
37
+ Indexmap::Section.new(
38
+ filename: "sitemap-pages.xml",
39
+ entries: [Indexmap::Entry.new(loc: "https://example.com/about")]
40
+ )
41
+ ]
42
+
43
+ result = Indexmap::TaskRunner.new(configuration: configuration).write_index_now_key
44
+
45
+ assert_nil result
46
+ end
47
+ end
48
+ end