| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require "livecheck/strategy" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-18 15:11:11 -08:00
										 |  |  | RSpec.describe Homebrew::Livecheck::Strategy do | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |   subject(:strategy) { described_class } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  |   let(:url) { "https://brew.sh/" } | 
					
						
							|  |  |  |   let(:redirection_url) { "https://brew.sh/redirection" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let(:post_hash) do | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       empty:   "", | 
					
						
							|  |  |  |       boolean: "true", | 
					
						
							|  |  |  |       number:  "1", | 
					
						
							|  |  |  |       string:  "a + b = c", | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  |   let(:form_string) { "empty=&boolean=true&number=1&string=a+%2B+b+%3D+c" } | 
					
						
							|  |  |  |   let(:json_string) { '{"empty":"","boolean":"true","number":"1","string":"a + b = c"}' } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let(:response_hash) do | 
					
						
							|  |  |  |     response_hash = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     response_hash[:ok] = { | 
					
						
							|  |  |  |       status_code: "200", | 
					
						
							|  |  |  |       status_text: "OK", | 
					
						
							|  |  |  |       headers:     { | 
					
						
							|  |  |  |         "cache-control"  => "max-age=604800", | 
					
						
							|  |  |  |         "content-type"   => "text/html; charset=UTF-8", | 
					
						
							|  |  |  |         "date"           => "Wed, 1 Jan 2020 01:23:45 GMT", | 
					
						
							|  |  |  |         "expires"        => "Wed, 31 Jan 2020 01:23:45 GMT", | 
					
						
							|  |  |  |         "last-modified"  => "Thu, 1 Jan 2019 01:23:45 GMT", | 
					
						
							|  |  |  |         "content-length" => "123", | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     response_hash[:redirection] = { | 
					
						
							|  |  |  |       status_code: "301", | 
					
						
							|  |  |  |       status_text: "Moved Permanently", | 
					
						
							|  |  |  |       headers:     { | 
					
						
							|  |  |  |         "cache-control"  => "max-age=604800", | 
					
						
							|  |  |  |         "content-type"   => "text/html; charset=UTF-8", | 
					
						
							|  |  |  |         "date"           => "Wed, 1 Jan 2020 01:23:45 GMT", | 
					
						
							|  |  |  |         "expires"        => "Wed, 31 Jan 2020 01:23:45 GMT", | 
					
						
							|  |  |  |         "last-modified"  => "Thu, 1 Jan 2019 01:23:45 GMT", | 
					
						
							|  |  |  |         "content-length" => "123", | 
					
						
							|  |  |  |         "location"       => redirection_url, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     response_hash | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let(:body) do | 
					
						
							|  |  |  |     <<~HTML | 
					
						
							|  |  |  |       <!DOCTYPE html> | 
					
						
							|  |  |  |       <html> | 
					
						
							|  |  |  |         <head> | 
					
						
							|  |  |  |           <meta charset="utf-8"> | 
					
						
							|  |  |  |           <title>Thank you!</title>
 | 
					
						
							|  |  |  |         </head>
 | 
					
						
							|  |  |  |         <body> | 
					
						
							|  |  |  |           <h1>Download</h1>
 | 
					
						
							|  |  |  |           <p>This download link could have been made publicly available in a reasonable fashion but we appreciate that you jumped through the hoops that we carefully set up!: <a href="https://brew.sh/example-1.2.3.tar.gz">Example v1.2.3</a></p> | 
					
						
							|  |  |  |           <p>The current legacy version is: <a href="https://brew.sh/example-0.1.2.tar.gz">Example v0.1.2</a></p> | 
					
						
							|  |  |  |         </body>
 | 
					
						
							|  |  |  |       </html>
 | 
					
						
							|  |  |  |     HTML | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   let(:response_text) do | 
					
						
							|  |  |  |     response_text = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     response_text[:ok] = <<~EOS | 
					
						
							|  |  |  |       HTTP/1.1 #{response_hash[:ok][:status_code]} #{response_hash[:ok][:status_text]}\r | 
					
						
							|  |  |  |       Cache-Control: #{response_hash[:ok][:headers]["cache-control"]}\r | 
					
						
							|  |  |  |       Content-Type: #{response_hash[:ok][:headers]["content-type"]}\r | 
					
						
							|  |  |  |       Date: #{response_hash[:ok][:headers]["date"]}\r | 
					
						
							|  |  |  |       Expires: #{response_hash[:ok][:headers]["expires"]}\r | 
					
						
							|  |  |  |       Last-Modified: #{response_hash[:ok][:headers]["last-modified"]}\r | 
					
						
							|  |  |  |       Content-Length: #{response_hash[:ok][:headers]["content-length"]}\r | 
					
						
							|  |  |  |       \r | 
					
						
							|  |  |  |       #{body.rstrip} | 
					
						
							|  |  |  |     EOS | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     response_text[:redirection_to_ok] = response_text[:ok].sub( | 
					
						
							|  |  |  |       "HTTP/1.1 #{response_hash[:ok][:status_code]} #{response_hash[:ok][:status_text]}\r", | 
					
						
							|  |  |  |       "HTTP/1.1 #{response_hash[:redirection][:status_code]} #{response_hash[:redirection][:status_text]}\r\n" \ | 
					
						
							|  |  |  |       "Location: #{response_hash[:redirection][:headers]["location"]}\r", | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     response_text | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |   describe "::from_symbol" do | 
					
						
							|  |  |  |     it "returns the Strategy module represented by the Symbol argument" do | 
					
						
							|  |  |  |       expect(strategy.from_symbol(:page_match)).to eq(Homebrew::Livecheck::Strategy::PageMatch) | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2025-02-04 15:48:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     it "returns `nil` if the argument is `nil`" do | 
					
						
							|  |  |  |       expect(strategy.from_symbol(nil)).to be_nil | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe "::from_url" do | 
					
						
							| 
									
										
										
										
											2025-02-04 15:48:59 -05:00
										 |  |  |     let(:sourceforge_url) { "https://sourceforge.net/projects/test" } | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 15:48:59 -05:00
										 |  |  |     context "when a regex or `strategy` block is not provided" do | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       it "returns an array of usable strategies which doesn't include PageMatch" do | 
					
						
							| 
									
										
										
										
											2025-02-04 15:48:59 -05:00
										 |  |  |         expect(strategy.from_url(sourceforge_url)).to eq([Homebrew::Livecheck::Strategy::Sourceforge]) | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 15:48:59 -05:00
										 |  |  |     context "when a regex or `strategy` block is provided" do | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       it "returns an array of usable strategies including PageMatch, sorted in descending order by priority" do | 
					
						
							| 
									
										
										
										
											2025-02-04 15:48:59 -05:00
										 |  |  |         expect(strategy.from_url(sourceforge_url, regex_provided: true)) | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |           .to eq( | 
					
						
							|  |  |  |             [Homebrew::Livecheck::Strategy::Sourceforge, Homebrew::Livecheck::Strategy::PageMatch], | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2025-02-04 15:48:59 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     context "when a `strategy` block is required and one is provided" do | 
					
						
							|  |  |  |       it "returns an array of usable strategies including the specified strategy" do | 
					
						
							|  |  |  |         # The strategies array is naturally in alphabetic order when all | 
					
						
							|  |  |  |         # applicable strategies have the same priority | 
					
						
							|  |  |  |         expect(strategy.from_url(url, livecheck_strategy: :json, block_provided: true)) | 
					
						
							|  |  |  |           .to eq([Homebrew::Livecheck::Strategy::Json, Homebrew::Livecheck::Strategy::PageMatch]) | 
					
						
							|  |  |  |         expect(strategy.from_url(url, livecheck_strategy: :xml, block_provided: true)) | 
					
						
							|  |  |  |           .to eq([Homebrew::Livecheck::Strategy::PageMatch, Homebrew::Livecheck::Strategy::Xml]) | 
					
						
							|  |  |  |         expect(strategy.from_url(url, livecheck_strategy: :yaml, block_provided: true)) | 
					
						
							|  |  |  |           .to eq([Homebrew::Livecheck::Strategy::PageMatch, Homebrew::Livecheck::Strategy::Yaml]) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     context "when a `strategy` block is required and one is not provided" do | 
					
						
							|  |  |  |       it "returns an array of usable strategies not including the specified strategy" do | 
					
						
							|  |  |  |         expect(strategy.from_url(url, livecheck_strategy: :json, block_provided: false)).to eq([]) | 
					
						
							|  |  |  |         expect(strategy.from_url(url, livecheck_strategy: :xml, block_provided: false)).to eq([]) | 
					
						
							|  |  |  |         expect(strategy.from_url(url, livecheck_strategy: :yaml, block_provided: false)).to eq([]) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2021-08-10 11:09:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  |   describe "::post_args" do | 
					
						
							| 
									
										
										
										
											2025-03-07 20:31:00 -05:00
										 |  |  |     let(:form_string_content_length) { "Content-Length: #{form_string.length}" } | 
					
						
							|  |  |  |     let(:json_string_content_length) { "Content-Length: #{json_string.length}" } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  |     it "returns an array including `--data` and an encoded form data string" do | 
					
						
							| 
									
										
										
										
											2025-03-07 20:31:00 -05:00
										 |  |  |       expect(strategy.post_args(post_form: post_hash)) | 
					
						
							|  |  |  |         .to eq(["--data", form_string, "--header", form_string_content_length]) | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |       # If both `post_form` and `post_json` are present, only `post_form` will | 
					
						
							|  |  |  |       # be used. | 
					
						
							| 
									
										
										
										
											2025-03-07 20:31:00 -05:00
										 |  |  |       expect(strategy.post_args(post_form: post_hash, post_json: post_hash)) | 
					
						
							|  |  |  |         .to eq(["--data", form_string, "--header", form_string_content_length]) | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns an array including `--json` and a JSON string" do | 
					
						
							| 
									
										
										
										
											2025-03-07 20:31:00 -05:00
										 |  |  |       expect(strategy.post_args(post_json: post_hash)) | 
					
						
							|  |  |  |         .to eq(["--json", json_string, "--header", json_string_content_length]) | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns an empty array if `post_form` value is blank" do | 
					
						
							|  |  |  |       expect(strategy.post_args(post_form: {})).to eq([]) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns an empty array if `post_json` value is blank" do | 
					
						
							|  |  |  |       expect(strategy.post_args(post_json: {})).to eq([]) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns an empty array if hash argument doesn't have a `post_form` or `post_json` value" do | 
					
						
							|  |  |  |       expect(strategy.post_args).to eq([]) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe "::page_headers" do | 
					
						
							|  |  |  |     let(:responses) { [response_hash[:ok]] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns headers from fetched content" do | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_headers).and_return({ responses:, body: }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(strategy.page_headers(url)).to eq([responses.first[:headers]]) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "handles `post_form` `url` options" do | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_headers).and_return({ responses:, body: }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												livecheck: Add Options class
This adds a `Livecheck::Options` class, which is intended to house
various configuration options that are set in `livecheck` blocks,
conditionally set by livecheck at runtime, etc. The general idea is
that when we add features involving configurations options (e.g., for
livecheck, strategies, curl, etc.), we can make changes to `Options`
without needing to modify parameters for strategy `find_versions`
methods, `Strategy` methods like `page_headers` and `page_content`,
etc. This is something that I've been trying to improve over the years
and `Options` should help to reduce maintenance overhead in this area
while also strengthening type signatures.
`Options` replaces the existing `homebrew_curl` option (which related
strategies pass to `Strategy` methods and on to `curl_args`) and the
new `url_options` (which contains `post_form` or `post_json` values
that are used to make `POST` requests). I recently added `url_options`
as a temporary way of enabling `POST` support without `Options` but
this restores the original `Options`-based implementation.
Along the way, I added a `homebrew_curl` parameter to the `url` DSL
method, allowing us to set an explicit value in `livecheck` blocks.
This is something that we've needed in some cases but I also intend
to replace implicit/inferred `homebrew_curl` usage with explicit
values in `livecheck` blocks once this is available for use. My
intention is to eventually remove the implicit behavior and only rely
on explicit values. That will align with how `homebrew_curl` options
work for other URLs and makes the behavior clear just from looking at
the `livecheck` block.
Lastly, this removes the `unused` rest parameter from `find_versions`
methods. I originally added `unused` as a way of handling parameters
that some `find_versions` methods have but others don't (e.g., `cask`
in `ExtractPlist`), as this allowed us to pass various arguments to
`find_versions` methods without worrying about whether a particular
parameter is available. This isn't an ideal solution and I originally
wanted to handle this situation by only passing expected arguments to
`find_versions` methods but there was a technical issue standing in
the way. I recently found an answer to the issue, so this also
replaces the existing `ExtractPlist` special case with generic logic
that checks the parameters for a strategy's `find_versions` method
and only passes expected arguments.
Replacing the aforementioned `find_versions` parameters with `Options`
ensures that the remaining parameters are fairly consistent across
strategies and any differences are handled by the aforementioned
logic. Outside of `ExtractPlist`, the only other difference is that
some `find_versions` methods have a `provided_content` parameter but
that's currently only used by tests (though it's intended for caching
support in the future). I will be renaming that parameter to `content`
in an upcoming PR and expanding it to the other strategies, which
should make them all consistent outside of `ExtractPlist`.
											
										 
											2025-02-11 18:04:38 -05:00
										 |  |  |       expect( | 
					
						
							|  |  |  |         strategy.page_headers( | 
					
						
							|  |  |  |           url, | 
					
						
							|  |  |  |           options: Homebrew::Livecheck::Options.new(post_form: post_hash), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ).to eq([responses.first[:headers]]) | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns an empty array if `curl_headers` only raises an `ErrorDuringExecution` error" do | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_headers).and_raise(ErrorDuringExecution.new([], status: 1)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(strategy.page_headers(url)).to eq([]) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe "::page_content" do | 
					
						
							|  |  |  |     let(:curl_version) { Version.new("8.7.1") } | 
					
						
							|  |  |  |     let(:success_status) { instance_double(Process::Status, success?: true, exitstatus: 0) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns hash including fetched content" do | 
					
						
							|  |  |  |       allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(strategy.page_content(url)).to eq({ content: body }) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "handles `post_form` `url` option" do | 
					
						
							|  |  |  |       allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												livecheck: Add Options class
This adds a `Livecheck::Options` class, which is intended to house
various configuration options that are set in `livecheck` blocks,
conditionally set by livecheck at runtime, etc. The general idea is
that when we add features involving configurations options (e.g., for
livecheck, strategies, curl, etc.), we can make changes to `Options`
without needing to modify parameters for strategy `find_versions`
methods, `Strategy` methods like `page_headers` and `page_content`,
etc. This is something that I've been trying to improve over the years
and `Options` should help to reduce maintenance overhead in this area
while also strengthening type signatures.
`Options` replaces the existing `homebrew_curl` option (which related
strategies pass to `Strategy` methods and on to `curl_args`) and the
new `url_options` (which contains `post_form` or `post_json` values
that are used to make `POST` requests). I recently added `url_options`
as a temporary way of enabling `POST` support without `Options` but
this restores the original `Options`-based implementation.
Along the way, I added a `homebrew_curl` parameter to the `url` DSL
method, allowing us to set an explicit value in `livecheck` blocks.
This is something that we've needed in some cases but I also intend
to replace implicit/inferred `homebrew_curl` usage with explicit
values in `livecheck` blocks once this is available for use. My
intention is to eventually remove the implicit behavior and only rely
on explicit values. That will align with how `homebrew_curl` options
work for other URLs and makes the behavior clear just from looking at
the `livecheck` block.
Lastly, this removes the `unused` rest parameter from `find_versions`
methods. I originally added `unused` as a way of handling parameters
that some `find_versions` methods have but others don't (e.g., `cask`
in `ExtractPlist`), as this allowed us to pass various arguments to
`find_versions` methods without worrying about whether a particular
parameter is available. This isn't an ideal solution and I originally
wanted to handle this situation by only passing expected arguments to
`find_versions` methods but there was a technical issue standing in
the way. I recently found an answer to the issue, so this also
replaces the existing `ExtractPlist` special case with generic logic
that checks the parameters for a strategy's `find_versions` method
and only passes expected arguments.
Replacing the aforementioned `find_versions` parameters with `Options`
ensures that the remaining parameters are fairly consistent across
strategies and any differences are handled by the aforementioned
logic. Outside of `ExtractPlist`, the only other difference is that
some `find_versions` methods have a `provided_content` parameter but
that's currently only used by tests (though it's intended for caching
support in the future). I will be renaming that parameter to `content`
in an upcoming PR and expanding it to the other strategies, which
should make them all consistent outside of `ExtractPlist`.
											
										 
											2025-02-11 18:04:38 -05:00
										 |  |  |       expect( | 
					
						
							|  |  |  |         strategy.page_content( | 
					
						
							|  |  |  |           url, | 
					
						
							|  |  |  |           options: Homebrew::Livecheck::Options.new(post_form: post_hash), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ).to eq({ content: body }) | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "handles `post_json` `url` option" do | 
					
						
							|  |  |  |       allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_output).and_return([response_text[:ok], nil, success_status]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
											  
											
												livecheck: Add Options class
This adds a `Livecheck::Options` class, which is intended to house
various configuration options that are set in `livecheck` blocks,
conditionally set by livecheck at runtime, etc. The general idea is
that when we add features involving configurations options (e.g., for
livecheck, strategies, curl, etc.), we can make changes to `Options`
without needing to modify parameters for strategy `find_versions`
methods, `Strategy` methods like `page_headers` and `page_content`,
etc. This is something that I've been trying to improve over the years
and `Options` should help to reduce maintenance overhead in this area
while also strengthening type signatures.
`Options` replaces the existing `homebrew_curl` option (which related
strategies pass to `Strategy` methods and on to `curl_args`) and the
new `url_options` (which contains `post_form` or `post_json` values
that are used to make `POST` requests). I recently added `url_options`
as a temporary way of enabling `POST` support without `Options` but
this restores the original `Options`-based implementation.
Along the way, I added a `homebrew_curl` parameter to the `url` DSL
method, allowing us to set an explicit value in `livecheck` blocks.
This is something that we've needed in some cases but I also intend
to replace implicit/inferred `homebrew_curl` usage with explicit
values in `livecheck` blocks once this is available for use. My
intention is to eventually remove the implicit behavior and only rely
on explicit values. That will align with how `homebrew_curl` options
work for other URLs and makes the behavior clear just from looking at
the `livecheck` block.
Lastly, this removes the `unused` rest parameter from `find_versions`
methods. I originally added `unused` as a way of handling parameters
that some `find_versions` methods have but others don't (e.g., `cask`
in `ExtractPlist`), as this allowed us to pass various arguments to
`find_versions` methods without worrying about whether a particular
parameter is available. This isn't an ideal solution and I originally
wanted to handle this situation by only passing expected arguments to
`find_versions` methods but there was a technical issue standing in
the way. I recently found an answer to the issue, so this also
replaces the existing `ExtractPlist` special case with generic logic
that checks the parameters for a strategy's `find_versions` method
and only passes expected arguments.
Replacing the aforementioned `find_versions` parameters with `Options`
ensures that the remaining parameters are fairly consistent across
strategies and any differences are handled by the aforementioned
logic. Outside of `ExtractPlist`, the only other difference is that
some `find_versions` methods have a `provided_content` parameter but
that's currently only used by tests (though it's intended for caching
support in the future). I will be renaming that parameter to `content`
in an upcoming PR and expanding it to the other strategies, which
should make them all consistent outside of `ExtractPlist`.
											
										 
											2025-02-11 18:04:38 -05:00
										 |  |  |       expect( | 
					
						
							|  |  |  |         strategy.page_content( | 
					
						
							|  |  |  |           url, | 
					
						
							|  |  |  |           options: Homebrew::Livecheck::Options.new(post_json: post_hash), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ).to eq({ content: body }) | 
					
						
							| 
									
										
										
										
											2025-02-04 10:30:16 -05:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns error `messages` from `stderr` in the return hash on failure when `stderr` is not `nil`" do | 
					
						
							|  |  |  |       error_message = "curl: (6) Could not resolve host: brew.sh" | 
					
						
							|  |  |  |       allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_output).and_return([ | 
					
						
							|  |  |  |         nil, | 
					
						
							|  |  |  |         error_message, | 
					
						
							|  |  |  |         instance_double(Process::Status, success?: false, exitstatus: 6), | 
					
						
							|  |  |  |       ]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(strategy.page_content(url)).to eq({ messages: [error_message] }) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns default error `messages` in the return hash on failure when `stderr` is `nil`" do | 
					
						
							|  |  |  |       allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_output).and_return([ | 
					
						
							|  |  |  |         nil, | 
					
						
							|  |  |  |         nil, | 
					
						
							|  |  |  |         instance_double(Process::Status, success?: false, exitstatus: 1), | 
					
						
							|  |  |  |       ]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(strategy.page_content(url)).to eq({ messages: ["cURL failed without a detectable error"] }) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns hash including `final_url` if it differs from initial `url`" do | 
					
						
							|  |  |  |       allow_any_instance_of(Utils::Curl).to receive(:curl_version).and_return(curl_version) | 
					
						
							|  |  |  |       allow(strategy).to receive(:curl_output).and_return([response_text[:redirection_to_ok], nil, success_status]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(strategy.page_content(url)).to eq({ content: body, final_url: redirection_url }) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 11:09:55 -04:00
										 |  |  |   describe "::handle_block_return" do | 
					
						
							|  |  |  |     it "returns an array of version strings when given a valid value" do | 
					
						
							|  |  |  |       expect(strategy.handle_block_return("1.2.3")).to eq(["1.2.3"]) | 
					
						
							|  |  |  |       expect(strategy.handle_block_return(["1.2.3", "1.2.4"])).to eq(["1.2.3", "1.2.4"]) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "returns an empty array when given a nil value" do | 
					
						
							|  |  |  |       expect(strategy.handle_block_return(nil)).to eq([]) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it "errors when given an invalid value" do | 
					
						
							|  |  |  |       expect { strategy.handle_block_return(123) } | 
					
						
							|  |  |  |         .to raise_error(TypeError, strategy::INVALID_BLOCK_RETURN_VALUE_MSG) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | end |