# typed: false # frozen_string_literal: true require "livecheck/strategy" describe Homebrew::Livecheck::Strategy::HeaderMatch do subject(:header_match) { described_class } let(:http_url) { "https://brew.sh/blog/" } let(:non_http_url) { "ftp://brew.sh/" } let(:versions) { versions = { content_disposition: ["1.2.3"], location: ["1.2.4"], } versions[:content_disposition_and_location] = versions[:content_disposition] + versions[:location] versions } let(:headers) { headers = { content_disposition: { "date" => "Fri, 01 Jan 2021 01:23:45 GMT", "content-type" => "application/x-gzip", "content-length" => "120", "content-disposition" => "attachment; filename=brew-#{versions[:content_disposition].first}.tar.gz", }, location: { "date" => "Fri, 01 Jan 2021 01:23:45 GMT", "content-type" => "text/html; charset=utf-8", "location" => "https://github.com/Homebrew/brew/releases/tag/#{versions[:location].first}", "content-length" => "117", }, } headers[:content_disposition_and_location] = headers[:content_disposition].merge(headers[:location]) headers } let(:regexes) { { archive: /filename=brew[._-]v?(\d+(?:\.\d+)+)\.t/i, latest: %r{.*?/tag/v?(\d+(?:\.\d+)+)$}i, loose: /v?(\d+(?:\.\d+)+)/i, } } describe "::match?" do it "returns true for an HTTP URL" do expect(header_match.match?(http_url)).to be true end it "returns false for a non-HTTP URL" do expect(header_match.match?(non_http_url)).to be false end end describe "::versions_from_headers" do it "returns an empty array if headers hash is empty" do expect(header_match.versions_from_headers({})).to eq([]) end it "returns an array of version strings when given headers" do expect(header_match.versions_from_headers(headers[:content_disposition])).to eq(versions[:content_disposition]) expect(header_match.versions_from_headers(headers[:location])).to eq(versions[:location]) expect(header_match.versions_from_headers(headers[:content_disposition_and_location])) .to eq(versions[:content_disposition_and_location]) expect(header_match.versions_from_headers(headers[:content_disposition], regexes[:archive])) .to eq(versions[:content_disposition]) expect(header_match.versions_from_headers(headers[:location], regexes[:latest])).to eq(versions[:location]) expect(header_match.versions_from_headers(headers[:content_disposition_and_location], regexes[:latest])) .to eq(versions[:location]) end it "returns an array of version strings when given headers and a block" do # Returning a string from block, no regex expect( header_match.versions_from_headers(headers[:location]) do |headers| v = Version.parse(headers["location"], detected_from_url: true) v.null? ? nil : v.to_s end, ).to eq(versions[:location]) # Returning a string from block, explicit regex expect( header_match.versions_from_headers(headers[:location], regexes[:latest]) do |headers, regex| headers["location"] ? headers["location"][regex, 1] : nil end, ).to eq(versions[:location]) # Returning an array of strings from block # NOTE: Strategies runs `#compact` on an array from a block, so nil # values are filtered out without needing to use `#compact` in the block. expect( header_match.versions_from_headers( headers[:content_disposition_and_location], regexes[:loose], ) do |headers, regex| headers.transform_values { |header| header[regex, 1] }.values end, ).to eq(versions[:content_disposition_and_location]) end it "allows a nil return from a block" do expect(header_match.versions_from_headers(headers[:location]) { next }).to eq([]) end it "errors on an invalid return type from a block" do expect { header_match.versions_from_headers(headers) { 123 } } .to raise_error(TypeError, Homebrew::Livecheck::Strategy::INVALID_BLOCK_RETURN_VALUE_MSG) end end end