indexmap 0.8.2 → 0.10.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -0
  3. data/README.md +281 -94
  4. data/UPGRADE.md +361 -0
  5. data/docs/architecture.md +55 -0
  6. data/docs/deployment.md +223 -0
  7. data/docs/domain-terms.md +27 -0
  8. data/docs/dynamic-sitemaps.md +302 -0
  9. data/docs/search-engine-ping.md +368 -0
  10. data/docs/storage.md +94 -0
  11. data/lib/generators/indexmap/install_generator.rb +1 -1
  12. data/lib/generators/indexmap/templates/initializer.rb.tt +18 -18
  13. data/lib/indexmap/artifact.rb +9 -0
  14. data/lib/indexmap/configuration.rb +12 -1
  15. data/lib/indexmap/creator.rb +45 -42
  16. data/lib/indexmap/index_now_configuration.rb +3 -1
  17. data/lib/indexmap/output.rb +19 -5
  18. data/lib/indexmap/parser.rb +112 -53
  19. data/lib/indexmap/pinger/base.rb +6 -5
  20. data/lib/indexmap/pinger/google.rb +12 -8
  21. data/lib/indexmap/pinger/index_now.rb +97 -52
  22. data/lib/indexmap/publication.rb +89 -0
  23. data/lib/indexmap/rails.rb +6 -0
  24. data/lib/indexmap/railtie.rb +7 -0
  25. data/lib/indexmap/robots.rb +67 -0
  26. data/lib/indexmap/robots_controller.rb +13 -0
  27. data/lib/indexmap/sitemaps_controller.rb +57 -0
  28. data/lib/indexmap/storage/active_storage.rb +132 -1
  29. data/lib/indexmap/storage/filesystem.rb +182 -11
  30. data/lib/indexmap/storage/memory.rb +1 -1
  31. data/lib/indexmap/task_runner.rb +21 -28
  32. data/lib/indexmap/validator/duplicates.rb +83 -0
  33. data/lib/indexmap/validator.rb +171 -59
  34. data/lib/indexmap/version.rb +1 -1
  35. data/lib/indexmap/writer.rb +89 -34
  36. data/lib/indexmap.rb +14 -1
  37. data/lib/tasks/indexmap_tasks.rake +21 -2
  38. data/test/indexmap/correctness_test.rb +153 -0
  39. data/test/indexmap/install_generator_test.rb +36 -4
  40. data/test/indexmap/parser_test.rb +77 -0
  41. data/test/indexmap/pinger/google_test.rb +22 -0
  42. data/test/indexmap/pinger/index_now_test.rb +168 -0
  43. data/test/indexmap/publication_test.rb +280 -0
  44. data/test/indexmap/rails_test.rb +22 -0
  45. data/test/indexmap/robots_test.rb +73 -0
  46. data/test/indexmap/sitemaps_controller_test.rb +50 -0
  47. data/test/indexmap/storage_test.rb +27 -0
  48. data/test/indexmap/task_runner_test.rb +24 -0
  49. data/test/indexmap/validator_test.rb +14 -0
  50. data/test/indexmap/writer_test.rb +42 -0
  51. data/test/integration/queue_checks.rb +244 -0
  52. metadata +33 -51
  53. data/lib/indexmap/storage/file.rb +0 -11
  54. data/test/release_task_test.rb +0 -86
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require "indexmap"
5
+ require "timeout"
6
+ require "rbconfig"
7
+
8
+ class QueueChecks < Minitest::Test
9
+ def setup
10
+ Indexmap::HostConfiguration.apply
11
+ PublicPage.delete_all
12
+ 5.times { |id| PublicPage.create!(slug: "page-#{id}", published: true) }
13
+ end
14
+
15
+ def test_good_job_executes_database_projection_and_surfaces_validation_failure
16
+ IntegrationRefreshJob.queue_adapter = :good_job
17
+ job = IntegrationRefreshJob.perform_later
18
+ GoodJob.perform_inline
19
+ assert GoodJob::Job.find(job.provider_job_id).finished_at
20
+ assert_equal 5, Indexmap::Parser.new.entries.size
21
+ previous = Indexmap.configuration.storage.read("sitemap.xml")
22
+ PublicPage.create!(slug: "page-0", published: true)
23
+ IntegrationRefreshJob.perform_later
24
+ assert_raises(Indexmap::ValidationError) { GoodJob.perform_inline }
25
+ assert_equal previous, Indexmap.configuration.storage.read("sitemap.xml")
26
+ end
27
+
28
+ def test_solid_queue_worker_executes_and_records_failed_generation
29
+ IntegrationRefreshJob.queue_adapter = :solid_queue
30
+ reader, writer = IO.pipe
31
+ pid = Process.spawn({"INDEXMAP_ADAPTER" => "solid_queue"}, RbConfig.ruby, "bin/jobs",
32
+ chdir: Rails.root, out: writer, err: writer, pgroup: true)
33
+ writer.close
34
+ job = IntegrationRefreshJob.perform_later
35
+ wait_for(reader) { SolidQueue::Job.find_by(active_job_id: job.job_id)&.finished_at }
36
+ assert_equal 5, Indexmap::Parser.new.entries.size
37
+ previous = Indexmap.configuration.storage.read("sitemap.xml")
38
+ PublicPage.create!(slug: "page-0", published: true)
39
+ failed = IntegrationRefreshJob.perform_later
40
+ wait_for(reader) { SolidQueue::FailedExecution.joins(:job).where(solid_queue_jobs: {active_job_id: failed.job_id}).exists? }
41
+ assert_equal previous, Indexmap.configuration.storage.read("sitemap.xml")
42
+ ensure
43
+ if pid
44
+ Process.kill("TERM", -pid)
45
+ Timeout.timeout(15) { Process.wait(pid) }
46
+ end
47
+ reader&.close
48
+ writer&.close unless writer&.closed?
49
+ end
50
+
51
+ def test_active_storage_atomic_publication_and_retention
52
+ config = Indexmap.configuration
53
+ config.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
54
+ first = Indexmap.create
55
+ previous = config.storage.read("sitemap.xml")
56
+ SitemapArtifact.find_by!(filename: "sitemap.xml").file.blob.update!(created_at: 4.days.ago)
57
+ revision = config.storage.revision("sitemap.xml")
58
+ config.storage.define_singleton_method(:open) do |filename, &block|
59
+ raise "Root body must not be downloaded during activation" if filename == "sitemap.xml"
60
+
61
+ super(filename, &block)
62
+ end
63
+ second = Indexmap.create
64
+ config.storage.singleton_class.remove_method(:open)
65
+ refute_equal first, second
66
+ assert_empty config.storage.prune(root: "sitemap.xml", before: 2.days.ago)
67
+ assert first.all? { |filename| config.storage.exist?(filename) }
68
+ assert_raises(Indexmap::SupersededPublication) do
69
+ Dir.mktmpdir do |directory|
70
+ staged = Indexmap::Storage::Filesystem.new(path: directory)
71
+ staged.write("sitemap.xml", previous)
72
+ config.storage.publish(staged, files: ["sitemap.xml"], root: "sitemap.xml", expected: revision)
73
+ end
74
+ end
75
+ assert Indexmap::Validator.new(configuration: config).validate!
76
+ config.storage.prune(root: "sitemap.xml", before: Time.now.utc + 1)
77
+ assert (first - ["sitemap.xml"]).none? { |filename| config.storage.exist?(filename) }
78
+ assert second.all? { |filename| config.storage.exist?(filename) }
79
+ end
80
+
81
+ def test_active_storage_rejects_stale_builds_without_uploading
82
+ config = active_storage_configuration("stale.xml")
83
+ Indexmap.create(configuration: config)
84
+ expected = config.storage.revision(config.index_filename)
85
+ Indexmap.create(configuration: config)
86
+ blobs = ActiveStorage::Blob.order(:id).pluck(:id)
87
+ assert_raises(Indexmap::SupersededPublication) do
88
+ config.storage.publish(nil, files: [], root: config.index_filename, expected: expected)
89
+ end
90
+ assert_equal blobs, ActiveStorage::Blob.order(:id).pluck(:id)
91
+ end
92
+
93
+ def test_active_storage_cleans_partial_uploads_synchronously
94
+ config = active_storage_configuration("failed-upload.xml")
95
+ Indexmap.create(configuration: config)
96
+ previous = config.storage.read(config.index_filename)
97
+ blobs = ActiveStorage::Blob.order(:id).pluck(:id)
98
+ records = SitemapArtifact.order(:id).pluck(:id)
99
+ service = ActiveStorage::Blob.service
100
+ uploaded = []
101
+ service.define_singleton_method(:upload) do |key, io, **options|
102
+ super(key, io, **options)
103
+ uploaded << key
104
+ raise IOError, "upload interrupted after object write" if uploaded.size == 2
105
+ end
106
+ assert_raises(IOError) { Indexmap.create(configuration: config) }
107
+ assert_equal previous, config.storage.read(config.index_filename)
108
+ assert_equal blobs, ActiveStorage::Blob.order(:id).pluck(:id)
109
+ assert_equal records, SitemapArtifact.order(:id).pluck(:id)
110
+ assert uploaded.none? { |key| service.exist?(key) }
111
+ assert Indexmap::Validator.new(configuration: config).validate!
112
+ ensure
113
+ service&.singleton_class&.remove_method(:upload)
114
+ end
115
+
116
+ def test_active_storage_cleans_a_losing_build_without_pruning
117
+ config = active_storage_configuration("race.xml")
118
+ storage = config.storage
119
+ baseline = ActiveStorage::Blob.pluck(:id)
120
+ lost_files = []
121
+ lost_keys = []
122
+ winner = nil
123
+ storage.define_singleton_method(:publish) do |source, **options|
124
+ lost_files.concat(options.fetch(:files).reject { |name| name == options.fetch(:root) })
125
+ source.define_singleton_method(:open) do |filename, &block|
126
+ if filename == options.fetch(:root)
127
+ lost_keys.concat(ActiveStorage::Blob.where.not(id: baseline).pluck(:key))
128
+ competing = Indexmap::Configuration.new
129
+ competing.base_url = config.base_url
130
+ competing.index_filename = config.index_filename
131
+ competing.atomic = true
132
+ competing.sections = config.sections
133
+ competing.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
134
+ winner = Indexmap.create(configuration: competing)
135
+ end
136
+ super(filename, &block)
137
+ end
138
+ super(source, **options)
139
+ end
140
+ assert_raises(Indexmap::SupersededPublication) { Indexmap.create(configuration: config) }
141
+ assert_equal winner.size, ActiveStorage::Blob.where.not(id: baseline).count
142
+ assert lost_files.none? { |filename| storage.exist?(filename) }
143
+ assert lost_keys.none? { |key| ActiveStorage::Blob.service.exist?(key) }
144
+ assert winner.all? { |filename| storage.exist?(filename) }
145
+ assert Indexmap::Validator.new(configuration: config).validate!
146
+ end
147
+
148
+ def test_active_storage_does_not_activate_children_deleted_by_pruning
149
+ config = active_storage_configuration("prune-race.xml")
150
+ Indexmap.create(configuration: config)
151
+ previous = config.storage.read(config.index_filename)
152
+ blobs = ActiveStorage::Blob.pluck(:id).sort
153
+ config.storage.define_singleton_method(:publish) do |source, **options|
154
+ storage = self
155
+ source.define_singleton_method(:open) do |filename, &block|
156
+ storage.prune(root: options.fetch(:root), before: Time.now.utc + 1) if filename == options.fetch(:root)
157
+ super(filename, &block)
158
+ end
159
+ super(source, **options)
160
+ end
161
+ assert_raises(Indexmap::SupersededPublication) { Indexmap.create(configuration: config) }
162
+ assert_equal previous, config.storage.read(config.index_filename)
163
+ assert_equal blobs, ActiveStorage::Blob.pluck(:id).sort
164
+ assert Indexmap::Validator.new(configuration: config).validate!
165
+ end
166
+
167
+ def test_active_storage_preserves_a_commit_when_a_host_callback_raises
168
+ config = active_storage_configuration("callback.xml")
169
+ callback = proc do |record|
170
+ if record.filename == "callback.xml" && record.file.attached?
171
+ raise IOError, "host callback failed after commit"
172
+ end
173
+ end
174
+ SitemapArtifact.set_callback(:commit, :after, callback)
175
+ assert_raises(IOError) { Indexmap.create(configuration: config) }
176
+ assert config.storage.exist?(config.index_filename)
177
+ assert Indexmap::Validator.new(configuration: config).validate!
178
+ ensure
179
+ SitemapArtifact.skip_callback(:commit, :after, callback) if callback
180
+ end
181
+
182
+ def test_active_storage_reports_cleanup_failures_and_attempts_remaining_objects
183
+ config = active_storage_configuration("cleanup-error.xml")
184
+ Indexmap.create(configuration: config)
185
+ previous = config.storage.read(config.index_filename)
186
+ service = ActiveStorage::Blob.service
187
+ uploaded = []
188
+ attempted = []
189
+ service.define_singleton_method(:upload) do |key, io, **options|
190
+ super(key, io, **options)
191
+ uploaded << key
192
+ raise IOError, "original upload failure" if uploaded.size == 2
193
+ end
194
+ service.define_singleton_method(:delete) do |key|
195
+ attempted << key
196
+ raise IOError, "cleanup unavailable" if key == uploaded.first
197
+
198
+ super(key)
199
+ end
200
+ error = assert_raises(Indexmap::PublicationCleanupError) { Indexmap.create(configuration: config) }
201
+ assert_equal "original upload failure", error.cause.message
202
+ assert_equal [uploaded.first], error.failures.map { |failure| failure.fetch(:artifact) }
203
+ assert_equal uploaded.sort, attempted.sort
204
+ assert_equal previous, config.storage.read(config.index_filename)
205
+ assert Indexmap::Validator.new(configuration: config).validate!
206
+ ensure
207
+ if service
208
+ service.singleton_class.remove_method(:upload)
209
+ service.singleton_class.remove_method(:delete)
210
+ uploaded&.each { |key| service.delete(key) }
211
+ end
212
+ end
213
+
214
+ def test_active_storage_publishes_atomic_single_file_outputs
215
+ config = active_storage_configuration("single.xml")
216
+ config.format = :single_file
217
+ config.entries = [{loc: "https://example.com/page"}]
218
+ assert_equal ["single.xml"], Indexmap.create(configuration: config)
219
+ assert_equal ["single.xml"], Indexmap.create(configuration: config)
220
+ assert Indexmap::Validator.new(configuration: config).validate!
221
+ end
222
+
223
+ private
224
+
225
+ def active_storage_configuration(root)
226
+ Indexmap.configuration.tap do |config|
227
+ config.index_filename = root
228
+ config.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
229
+ end
230
+ end
231
+
232
+ def wait_for(reader)
233
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30
234
+ log = +""
235
+ until yield
236
+ raise "Worker did not reach expected state: #{log[-2000, 2000]}" if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
237
+
238
+ if IO.select([reader], nil, nil, 0.1)
239
+ chunk = reader.read_nonblock(4096, exception: false)
240
+ log << chunk if chunk.is_a?(String)
241
+ end
242
+ end
243
+ end
244
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: indexmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Fidalgo
@@ -16,14 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '7.1'
19
+ version: '8.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '7.1'
29
+ version: '8.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: faraday
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +86,6 @@ dependencies:
80
86
  - - ">="
81
87
  - !ruby/object:Gem::Version
82
88
  version: '1.16'
83
- - !ruby/object:Gem::Dependency
84
- name: railties
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '7.1'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '7.1'
97
89
  - !ruby/object:Gem::Dependency
98
90
  name: minitest
99
91
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +100,6 @@ dependencies:
108
100
  - - "~>"
109
101
  - !ruby/object:Gem::Version
110
102
  version: '5.0'
111
- - !ruby/object:Gem::Dependency
112
- name: rake
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '13.0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '13.0'
125
103
  - !ruby/object:Gem::Dependency
126
104
  name: simplecov
127
105
  requirement: !ruby/object:Gem::Requirement
@@ -136,20 +114,6 @@ dependencies:
136
114
  - - "~>"
137
115
  - !ruby/object:Gem::Version
138
116
  version: '0.22'
139
- - !ruby/object:Gem::Dependency
140
- name: standard
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '1.0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '1.0'
153
117
  - !ruby/object:Gem::Dependency
154
118
  name: webmock
155
119
  requirement: !ruby/object:Gem::Requirement
@@ -175,9 +139,17 @@ files:
175
139
  - CHANGELOG.md
176
140
  - LICENSE.txt
177
141
  - README.md
142
+ - UPGRADE.md
143
+ - docs/architecture.md
144
+ - docs/deployment.md
145
+ - docs/domain-terms.md
146
+ - docs/dynamic-sitemaps.md
147
+ - docs/search-engine-ping.md
148
+ - docs/storage.md
178
149
  - lib/generators/indexmap/install_generator.rb
179
150
  - lib/generators/indexmap/templates/initializer.rb.tt
180
151
  - lib/indexmap.rb
152
+ - lib/indexmap/artifact.rb
181
153
  - lib/indexmap/configuration.rb
182
154
  - lib/indexmap/creator.rb
183
155
  - lib/indexmap/entry.rb
@@ -188,29 +160,39 @@ files:
188
160
  - lib/indexmap/pinger/base.rb
189
161
  - lib/indexmap/pinger/google.rb
190
162
  - lib/indexmap/pinger/index_now.rb
163
+ - lib/indexmap/publication.rb
164
+ - lib/indexmap/rails.rb
191
165
  - lib/indexmap/railtie.rb
166
+ - lib/indexmap/robots.rb
167
+ - lib/indexmap/robots_controller.rb
192
168
  - lib/indexmap/section.rb
169
+ - lib/indexmap/sitemaps_controller.rb
193
170
  - lib/indexmap/storage/active_storage.rb
194
- - lib/indexmap/storage/file.rb
195
171
  - lib/indexmap/storage/filesystem.rb
196
172
  - lib/indexmap/storage/memory.rb
197
173
  - lib/indexmap/task_runner.rb
198
174
  - lib/indexmap/tasks.rb
199
175
  - lib/indexmap/validator.rb
176
+ - lib/indexmap/validator/duplicates.rb
200
177
  - lib/indexmap/version.rb
201
178
  - lib/indexmap/writer.rb
202
179
  - lib/tasks/indexmap_tasks.rake
203
180
  - test/indexmap/configuration_test.rb
181
+ - test/indexmap/correctness_test.rb
204
182
  - test/indexmap/install_generator_test.rb
205
183
  - test/indexmap/lazy_loading_test.rb
206
184
  - test/indexmap/parser_test.rb
207
185
  - test/indexmap/pinger/google_test.rb
208
186
  - test/indexmap/pinger/index_now_test.rb
187
+ - test/indexmap/publication_test.rb
188
+ - test/indexmap/rails_test.rb
189
+ - test/indexmap/robots_test.rb
190
+ - test/indexmap/sitemaps_controller_test.rb
209
191
  - test/indexmap/storage_test.rb
210
192
  - test/indexmap/task_runner_test.rb
211
193
  - test/indexmap/validator_test.rb
212
194
  - test/indexmap/writer_test.rb
213
- - test/release_task_test.rb
195
+ - test/integration/queue_checks.rb
214
196
  - test/test_helper.rb
215
197
  homepage: https://www.ethos-link.com/opensource/indexmap
216
198
  licenses:
@@ -219,8 +201,8 @@ metadata:
219
201
  homepage_uri: https://www.ethos-link.com/opensource/indexmap
220
202
  source_code_uri: https://github.com/ethos-link/indexmap
221
203
  bug_tracker_uri: https://github.com/ethos-link/indexmap/issues
222
- changelog_uri: https://github.com/ethos-link/indexmap/blob/main/CHANGELOG.md
223
- documentation_uri: https://github.com/ethos-link/indexmap/blob/main/README.md
204
+ changelog_uri: https://github.com/ethos-link/indexmap/blob/v0.10.0/CHANGELOG.md
205
+ documentation_uri: https://github.com/ethos-link/indexmap/blob/v0.10.0/README.md
224
206
  funding_uri: https://www.reviato.com/
225
207
  github_repo: ssh://github.com/ethos-link/indexmap
226
208
  allowed_push_host: https://rubygems.org
@@ -232,14 +214,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
232
214
  requirements:
233
215
  - - ">="
234
216
  - !ruby/object:Gem::Version
235
- version: 3.2.0
217
+ version: '4.0'
236
218
  required_rubygems_version: !ruby/object:Gem::Requirement
237
219
  requirements:
238
220
  - - ">="
239
221
  - !ruby/object:Gem::Version
240
222
  version: '0'
241
223
  requirements: []
242
- rubygems_version: 4.0.16
224
+ rubygems_version: 4.0.20
243
225
  specification_version: 4
244
226
  summary: Generate sitemap indexes and child sitemaps with plain Ruby
245
227
  test_files: []
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Indexmap
4
- module Storage
5
- File = Struct.new(:filename, :body, :content_type) do
6
- def basename
7
- ::File.basename(filename)
8
- end
9
- end
10
- end
11
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_helper"
4
- require "rake"
5
-
6
- unless respond_to?(:release_version, true)
7
- load File.expand_path("../Rakefile", __dir__)
8
- end
9
-
10
- class ReleaseTaskTest < Minitest::Test
11
- def test_release_version_increments_patch_from_current_version
12
- major, minor, patch = Indexmap::VERSION.split(".").map(&:to_i)
13
-
14
- assert_equal "#{major}.#{minor}.#{patch + 1}", release_version("patch")
15
- end
16
-
17
- def test_release_version_accepts_explicit_semantic_version
18
- assert_equal "0.3.0", release_version("0.3.0")
19
- end
20
-
21
- def test_validate_release_version_rejects_current_version
22
- error = assert_raises(ArgumentError) do
23
- validate_release_version!("0.2.7", "0.2.7")
24
- end
25
-
26
- assert_equal(
27
- "Release version 0.2.7 must be newer than current version 0.2.7.",
28
- error.message
29
- )
30
- end
31
-
32
- def test_validate_release_version_rejects_existing_local_tag
33
- @local_release_tag_exists = true
34
- @remote_release_tag_exists = false
35
-
36
- error = assert_raises(ArgumentError) do
37
- validate_release_version!("0.2.8", "0.2.7")
38
- end
39
-
40
- assert_equal "Release tag v0.2.8 already exists locally.", error.message
41
- end
42
-
43
- def test_validate_release_version_rejects_existing_remote_tag
44
- @local_release_tag_exists = false
45
- @remote_release_tag_exists = true
46
-
47
- error = assert_raises(ArgumentError) do
48
- validate_release_version!("0.2.8", "0.2.7")
49
- end
50
-
51
- assert_equal "Release tag v0.2.8 already exists on origin.", error.message
52
- end
53
-
54
- def test_remote_release_tag_command_asks_git_to_fail_when_no_tag_matches
55
- assert_equal(
56
- "git ls-remote --exit-code --tags origin refs/tags/v0.2.8",
57
- remote_release_tag_command("v0.2.8")
58
- )
59
- end
60
-
61
- def test_changelog_command_prepends_the_next_release
62
- assert_equal(
63
- [
64
- "git-cliff",
65
- "-c",
66
- "cliff.toml",
67
- "--unreleased",
68
- "--tag",
69
- "v0.2.8",
70
- "--prepend",
71
- "CHANGELOG.md"
72
- ],
73
- changelog_command("0.2.8")
74
- )
75
- end
76
-
77
- private
78
-
79
- def local_release_tag_exists?(_tag)
80
- @local_release_tag_exists || false
81
- end
82
-
83
- def remote_release_tag_exists?(_tag)
84
- @remote_release_tag_exists || false
85
- end
86
- end