mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 22:29:27 +04:00
- Tests reached into private APIs with `send` and poked internal state via `instance_variable_get`/`instance_variable_set`, hiding which interfaces specs really exercise and letting visibility rot. - Make every statically-poked method public and call it directly, keeping `public_send` only for dynamically-named public methods. - Read and write state through public `attr_*` accessors instead of instance variable reflection. - Add `Homebrew/NoSendInTests` and `Homebrew/NoInstanceVariableAccessInTests` cops scoped to `Library/Homebrew/test` so neither pattern creeps back in. - Keep rare justified disables, e.g. `Module#remove_const` is private core Ruby and raw memoisation state has no accessor.
1457 lines
42 KiB
Ruby
1457 lines
42 KiB
Ruby
# typed: false
|
|
# frozen_string_literal: true
|
|
|
|
require "cask/audit"
|
|
|
|
RSpec.describe Cask::Audit, :cask do
|
|
let(:cask) { instance_double(Cask::Cask) }
|
|
let(:new_cask) { nil }
|
|
let(:online) { nil }
|
|
let(:only) { [] }
|
|
let(:except) { [] }
|
|
let(:strict) { nil }
|
|
let(:signing) { nil }
|
|
let(:audit) do
|
|
described_class.new(cask, online:,
|
|
strict:,
|
|
new_cask:,
|
|
signing:,
|
|
only:,
|
|
except:)
|
|
end
|
|
|
|
def include_msg?(problems, msg)
|
|
if msg.is_a?(Regexp)
|
|
Array(problems).any? { |problem| msg.match?(problem[:message]) }
|
|
else
|
|
Array(problems).any? { |problem| problem[:message] == msg }
|
|
end
|
|
end
|
|
|
|
def passed?(audit)
|
|
!audit.errors?
|
|
end
|
|
|
|
def outcome(audit)
|
|
if passed?(audit)
|
|
"passed"
|
|
else
|
|
"errored with #{audit.errors.map { |e| e.fetch(:message).inspect }.join(",")}"
|
|
end
|
|
end
|
|
|
|
matcher :pass do
|
|
match do |audit|
|
|
passed?(audit)
|
|
end
|
|
|
|
failure_message do |audit|
|
|
"expected to pass, but #{outcome(audit)}"
|
|
end
|
|
end
|
|
|
|
matcher :error_with do |message|
|
|
match do |audit|
|
|
include_msg?(audit.errors, message)
|
|
end
|
|
|
|
failure_message do |audit|
|
|
"expected to error with message #{message.inspect} but #{outcome(audit)}"
|
|
end
|
|
end
|
|
|
|
describe "#new" do
|
|
context "when `new_cask` is specified" do
|
|
let(:new_cask) { true }
|
|
|
|
specify do
|
|
expect(audit).to be_online
|
|
expect(audit).to be_strict
|
|
end
|
|
end
|
|
|
|
context "when `online` is specified" do
|
|
let(:online) { true }
|
|
|
|
it "implies `download`" do
|
|
expect(audit.download).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "when `signing` is specified" do
|
|
let(:signing) { true }
|
|
|
|
it "implies `download`" do
|
|
expect(audit.download).to be_truthy
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#result" do
|
|
subject { audit.result }
|
|
|
|
context "when there are no errors and `--strict` is not passed so we should not show anything" do
|
|
before do
|
|
audit.add_error("eh", strict_only: true)
|
|
end
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
|
|
context "when there are errors" do
|
|
before do
|
|
audit.add_error "bad"
|
|
end
|
|
|
|
it { is_expected.to include("failed") }
|
|
end
|
|
|
|
context "when there are errors and warnings" do
|
|
before do
|
|
audit.add_error "bad"
|
|
audit.add_error("eh", strict_only: true)
|
|
end
|
|
|
|
it { is_expected.to include("failed") }
|
|
end
|
|
|
|
context "when there are errors and warnings and `--strict` is passed" do
|
|
let(:strict) { true }
|
|
|
|
before do
|
|
audit.add_error "very bad"
|
|
audit.add_error("a little bit bad", strict_only: true)
|
|
end
|
|
|
|
it { is_expected.to include("failed") }
|
|
end
|
|
|
|
context "when there are warnings and `--strict` is not passed" do
|
|
before do
|
|
audit.add_error("a little bit bad", strict_only: true)
|
|
end
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
|
|
context "when there are warnings and `--strict` is passed" do
|
|
let(:strict) { true }
|
|
|
|
before do
|
|
audit.add_error("a little bit bad", strict_only: true)
|
|
end
|
|
|
|
it { is_expected.to include("failed") }
|
|
end
|
|
end
|
|
|
|
describe "#run!" do
|
|
subject(:run) { audit.run! }
|
|
|
|
def tmp_cask(name, text)
|
|
path = Pathname.new "#{dir}/#{name}.rb"
|
|
path.open("w") do |f|
|
|
f.write text
|
|
end
|
|
|
|
Cask::CaskLoader.load(path)
|
|
end
|
|
|
|
let(:dir) { mktmpdir }
|
|
let(:cask) { Cask::CaskLoader.load(cask_token) }
|
|
|
|
describe "required stanzas" do
|
|
let(:only) { ["required_stanzas"] }
|
|
|
|
%w[version sha256 url name homepage].each do |stanza|
|
|
context "when missing #{stanza}" do
|
|
let(:cask_token) { "missing-#{stanza}" }
|
|
|
|
it { is_expected.to error_with(/#{stanza} stanza is required/) }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "token validation" do
|
|
let(:strict) { true }
|
|
let(:only) { ["token"] }
|
|
let(:cask) do
|
|
tmp_cask cask_token.to_s, <<~RUBY
|
|
cask '#{cask_token}' do
|
|
version '1.0'
|
|
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
|
|
url "https://brew.sh/"
|
|
name 'Audit'
|
|
homepage 'https://brew.sh/'
|
|
app 'Audit.app'
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
context "when cask token is not lowercase" do
|
|
let(:cask_token) { "Upper-Case" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain uppercase/)
|
|
end
|
|
end
|
|
|
|
context "when cask token is not ascii" do
|
|
let(:cask_token) { "ascii⌘" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain non-ASCII characters/)
|
|
end
|
|
end
|
|
|
|
context "when cask token is @-versioned with number" do
|
|
let(:cask_token) { "app@10" }
|
|
|
|
it "does not fail" do
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
|
|
context "when cask token is @-versioned with word" do
|
|
let(:cask_token) { "app@beta" }
|
|
|
|
it "does not fail" do
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
|
|
context "when cask token has multiple @" do
|
|
let(:cask_token) { "app@stuff@beta" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain multiple @ symbols/)
|
|
end
|
|
end
|
|
|
|
context "when cask token has a hyphen followed by @" do
|
|
let(:cask_token) { "app-@beta" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain a hyphen followed by an @/)
|
|
end
|
|
end
|
|
|
|
context "when cask token has @ followed by a hyphen" do
|
|
let(:cask_token) { "app@-beta" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain an @ followed by a hyphen/)
|
|
end
|
|
end
|
|
|
|
context "when cask token has whitespace" do
|
|
let(:cask_token) { "app stuff" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain whitespace/)
|
|
end
|
|
end
|
|
|
|
context "when cask token has double hyphens" do
|
|
let(:cask_token) { "app--stuff" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain double hyphens/)
|
|
end
|
|
end
|
|
|
|
context "when cask token has leading hyphens" do
|
|
let(:cask_token) { "-app" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain a leading hyphen/)
|
|
end
|
|
end
|
|
|
|
context "when cask token has trailing hyphens" do
|
|
let(:cask_token) { "app-" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/not contain a trailing hyphen/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "token bad words" do
|
|
let(:new_cask) { true }
|
|
let(:only) { ["token_bad_words", "reverse_migration"] }
|
|
let(:online) { false }
|
|
let(:cask) do
|
|
tmp_cask cask_token.to_s, <<~RUBY
|
|
cask "#{cask_token}" do
|
|
version "1.0"
|
|
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
|
|
url "https://brew.sh/v\#{version}.zip"
|
|
name "Audit"
|
|
desc "Cask for testing tokens"
|
|
homepage "https://brew.sh/"
|
|
app "Audit.app"
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
context "when cask token contains .app" do
|
|
let(:cask_token) { "token.app" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/token contains .app/)
|
|
end
|
|
end
|
|
|
|
context "when cask token contains launcher" do
|
|
let(:cask_token) { "token-launcher" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/token mentions launcher/)
|
|
end
|
|
end
|
|
|
|
context "when cask token contains desktop" do
|
|
let(:cask_token) { "token-desktop" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/token mentions desktop/)
|
|
end
|
|
end
|
|
|
|
context "when cask token contains platform" do
|
|
let(:cask_token) { "token-osx" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/token mentions platform/)
|
|
end
|
|
end
|
|
|
|
context "when cask token contains architecture" do
|
|
let(:cask_token) { "token-x86" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/token mentions architecture/)
|
|
end
|
|
end
|
|
|
|
context "when cask token contains framework" do
|
|
let(:cask_token) { "token-java" }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/cask token mentions framework/)
|
|
end
|
|
end
|
|
|
|
context "when cask token is framework" do
|
|
let(:cask_token) { "java" }
|
|
|
|
it "does not fail" do
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
|
|
context "when cask token is in tap_migrations.json and" do
|
|
let(:cask_token) { "token-migrated" }
|
|
let(:tap) { CoreCaskTap.instance }
|
|
|
|
before do
|
|
allow(tap).to receive(:tap_migrations).and_return({ cask_token => "homebrew/core" })
|
|
allow(cask).to receive(:tap).and_return(tap)
|
|
end
|
|
|
|
context "when `new_cask` is true" do
|
|
let(:new_cask) { true }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with("#{cask_token} is listed in tap_migrations.json")
|
|
end
|
|
end
|
|
|
|
context "when `new_cask` is false" do
|
|
let(:new_cask) { false }
|
|
|
|
it "does not fail" do
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "locale validation" do
|
|
let(:only) { ["languages"] }
|
|
let(:cask) do
|
|
tmp_cask "locale-cask-test", <<~RUBY
|
|
cask 'locale-cask-test' do
|
|
version '1.0'
|
|
url "https://brew.sh/"
|
|
name 'Audit'
|
|
homepage 'https://brew.sh/'
|
|
app 'Audit.app'
|
|
|
|
language 'en', default: true do
|
|
sha256 '96574251b885c12b48a3495e843e434f9174e02bb83121b578e17d9dbebf1ffb'
|
|
'zh-CN'
|
|
end
|
|
|
|
language 'zh-CN' do
|
|
sha256 '96574251b885c12b48a3495e843e434f9174e02bb83121b578e17d9dbebf1ffb'
|
|
'zh-CN'
|
|
end
|
|
|
|
language 'ZH-CN' do
|
|
sha256 '96574251b885c12b48a3495e843e434f9174e02bb83121b578e17d9dbebf1ffb'
|
|
'zh-CN'
|
|
end
|
|
|
|
language 'zh-' do
|
|
sha256 '96574251b885c12b48a3495e843e434f9174e02bb83121b578e17d9dbebf1ffb'
|
|
'zh-CN'
|
|
end
|
|
|
|
language 'zh-cn' do
|
|
sha256 '96574251b885c12b48a3495e843e434f9174e02bb83121b578e17d9dbebf1ffb'
|
|
'zh-CN'
|
|
end
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
context "when cask locale is invalid" do
|
|
it "error with invalid locale" do
|
|
expect(run).to error_with(/Locale 'ZH-CN' is invalid\./)
|
|
expect(run).to error_with(/Locale 'zh-' is invalid\./)
|
|
expect(run).to error_with(/Locale 'zh-cn' is invalid\./)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "pkg allow_untrusted checks" do
|
|
let(:only) { ["untrusted_pkg"] }
|
|
let(:message) { "allow_untrusted is not permitted in the official homebrew/cask tap" }
|
|
|
|
context "when the Cask has no pkg stanza" do
|
|
let(:cask_token) { "basic-cask" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask does not have allow_untrusted" do
|
|
let(:cask_token) { "with-uninstall-pkgutil" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has allow_untrusted" do
|
|
let(:cask_token) { "with-allow-untrusted" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "signing checks" do
|
|
let(:only) { ["signing"] }
|
|
let(:tap) { CoreCaskTap.instance }
|
|
let(:download_double) { instance_double(Cask::Download) }
|
|
|
|
before do
|
|
allow(audit).to receive_messages(download: download_double, signing?: true)
|
|
end
|
|
|
|
context "when cask is not using a signed artifact" do
|
|
let(:cask) do
|
|
tmp_cask "signing-cask-test", <<~RUBY
|
|
cask 'signing-cask-test' do
|
|
version '1.0'
|
|
url "https://brew.sh/index.html"
|
|
artifact "example.pdf", target: "/Library/Application Support/example"
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
it "does not fail" do
|
|
expect(download_double).not_to receive(:fetch)
|
|
expect(UnpackStrategy).not_to receive(:detect)
|
|
expect(run).not_to error_with(/Audit\.app/)
|
|
end
|
|
end
|
|
|
|
context "when cask is using a signed artifact" do
|
|
let(:cask) do
|
|
tmp_cask "signing-cask-test", <<~RUBY
|
|
cask 'signing-cask-test' do
|
|
version '1.0'
|
|
url "https://brew.sh/"
|
|
pkg 'Audit.app'
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
it "does not fail since no extract" do
|
|
allow(download_double).to receive(:fetch).and_return(Pathname.new("/tmp/test.zip"))
|
|
allow(UnpackStrategy).to receive(:detect).and_return(nil)
|
|
expect(run).not_to error_with(/Audit\.app/)
|
|
end
|
|
end
|
|
|
|
context "when quarantine support is not available" do
|
|
let(:cask) do
|
|
tmp_cask "signing-cask-test", <<~RUBY
|
|
cask 'signing-cask-test' do
|
|
version '1.0'
|
|
url "https://brew.sh/"
|
|
app 'Audit.app'
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
before do
|
|
allow(cask).to receive(:tap).and_return(tap)
|
|
|
|
allow(Cask::Quarantine).to receive(:available?).and_return(false)
|
|
end
|
|
|
|
it "skips signing audit with warning" do
|
|
allow(cask).to receive(:tap).and_return(tap)
|
|
|
|
expect(audit).to receive(:odebug).with("Quarantine support is not available, skipping signing audit")
|
|
expect(run).not_to error_with(/Signature verification failed/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "artifact extraction" do
|
|
let(:online) { true }
|
|
let(:cask) do
|
|
Cask::Cask.new("artifact-extraction") do
|
|
version "1.0"
|
|
sha256 :no_check
|
|
url "https://brew.sh/artifact-extraction.tar.gz"
|
|
binary "artifact-extraction"
|
|
end
|
|
end
|
|
|
|
it "skips quarantine detection when quarantine support is unavailable" do
|
|
downloaded_path = Pathname("/tmp/artifact-extraction.tar.gz")
|
|
container = instance_double(UnpackStrategy, dependencies: [], extract_nestedly: nil)
|
|
allow(audit.download).to receive(:fetch).and_return(downloaded_path)
|
|
allow(UnpackStrategy).to receive(:detect).and_return(container)
|
|
allow(ObjectSpace).to receive(:define_finalizer)
|
|
allow(Cask::Installer).to receive(:new)
|
|
.and_return(instance_double(Cask::Installer, process_rename_operations: nil))
|
|
allow(Cask::Quarantine).to receive(:available?).and_return(false)
|
|
expect(Cask::Quarantine).not_to receive(:detect)
|
|
|
|
audit.extract_artifacts
|
|
end
|
|
end
|
|
|
|
describe "livecheck version validation", :no_api do
|
|
let(:only) { ["livecheck_version"] }
|
|
let(:online) { true }
|
|
let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ }
|
|
|
|
context "when `@livecheck_result` is already set" do
|
|
let(:cask_token) { "basic-cask" }
|
|
|
|
it "returns existing `@livecheck_result` value" do
|
|
audit.livecheck_result = :auto_detected
|
|
expect(run).not_to error_with(message)
|
|
audit.livecheck_result = nil
|
|
end
|
|
end
|
|
|
|
context "when `cask.version` is not set" do
|
|
let(:cask_token) { "missing-version" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block using skip" do
|
|
let(:cask_token) { "livecheck-skip" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block referencing a Cask using skip" do
|
|
let(:cask_token) { "livecheck-skip-reference" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask is deprecated" do
|
|
let(:cask_token) { "livecheck-deprecated" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block referencing a deprecated Cask" do
|
|
let(:cask_token) { "livecheck-deprecated-reference" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask is disabled" do
|
|
let(:cask_token) { "livecheck-disabled" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block referencing a disabled Cask" do
|
|
let(:cask_token) { "livecheck-disabled-reference" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when version is :latest" do
|
|
let(:cask_token) { "livecheck-version-latest" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block referencing a Cask where version is :latest" do
|
|
let(:cask_token) { "livecheck-version-latest-reference" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when url is unversioned" do
|
|
let(:cask_token) { "livecheck-url-unversioned" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block referencing a Cask with an unversioned url" do
|
|
let(:cask_token) { "livecheck-url-unversioned-reference" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when `latest_version` returns `nil`" do
|
|
let(:cask_token) { "basic-cask" }
|
|
|
|
it do
|
|
allow(Homebrew::Livecheck).to receive(:latest_version).and_return(nil)
|
|
expect(run).to error_with(message)
|
|
end
|
|
end
|
|
|
|
context "when the Cask is not throttled" do
|
|
let(:cask_token) { "basic-cask" }
|
|
|
|
it do
|
|
allow(Homebrew::Livecheck).to receive(:latest_version).and_return({
|
|
latest: Version.new("1.2.3"),
|
|
})
|
|
expect(run).not_to error_with(message)
|
|
end
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block using `throttle`" do
|
|
let(:cask_token) { "livecheck-throttle" }
|
|
|
|
it do
|
|
allow(Homebrew::Livecheck).to receive(:latest_version).and_return({
|
|
latest: Version.new("1.2.6"),
|
|
latest_throttled: Version.new("1.2.5"),
|
|
})
|
|
expect(run).not_to error_with(message)
|
|
end
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block using `throttle days:`" do
|
|
let(:cask_token) { "livecheck-throttle-days" }
|
|
let(:cask) do
|
|
tmp_cask cask_token.to_s, <<~RUBY
|
|
cask "#{cask_token}" do
|
|
version "1.2.5"
|
|
sha256 "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
|
|
url "https://brew.sh/#{cask_token}-1.2.5.dmg"
|
|
name "Throttle Days"
|
|
homepage "https://brew.sh/#{cask_token}"
|
|
|
|
livecheck do
|
|
url :homepage
|
|
throttle days: 7
|
|
end
|
|
|
|
app "#{cask_token}.app"
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
it do
|
|
allow(Homebrew::Livecheck).to receive(:latest_version).and_return({
|
|
latest: Version.new("1.2.6"),
|
|
latest_throttled: Version.new("1.2.5"),
|
|
})
|
|
expect(run).not_to error_with(message)
|
|
end
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block referencing a Cask that uses `throttle`" do
|
|
let(:cask_token) { "livecheck-throttle-reference" }
|
|
|
|
it do
|
|
allow(Homebrew::Livecheck).to receive(:latest_version).and_return({
|
|
latest: Version.new("1.2.6"),
|
|
latest_throttled: Version.new("1.2.5"),
|
|
})
|
|
expect(run).not_to error_with(message)
|
|
end
|
|
end
|
|
|
|
context "when the Cask has a `livecheck` block referencing a Cask that uses `throttle days:`" do
|
|
let(:cask_token) { "livecheck-throttle-days-reference" }
|
|
let(:referenced_cask) do
|
|
tmp_cask "livecheck-throttle-days-source", <<~RUBY
|
|
cask "livecheck-throttle-days-source" do
|
|
version "1.2.5"
|
|
sha256 "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
|
|
url "https://brew.sh/livecheck-throttle-days-source-1.2.5.dmg"
|
|
name "Throttle Days Source"
|
|
homepage "https://brew.sh/livecheck-throttle-days-source"
|
|
|
|
livecheck do
|
|
url :homepage
|
|
throttle days: 7
|
|
end
|
|
|
|
app "livecheck-throttle-days-source.app"
|
|
end
|
|
RUBY
|
|
end
|
|
let(:cask) do
|
|
tmp_cask cask_token.to_s, <<~RUBY
|
|
cask "#{cask_token}" do
|
|
version "1.2.5"
|
|
sha256 "8c62a2b791cf5f0da6066a0a4b6e85f62949cd60975da062df44adf887f4370b"
|
|
|
|
url "https://brew.sh/#{cask_token}-1.2.5.dmg"
|
|
name "Throttle Days Reference"
|
|
homepage "https://brew.sh/#{cask_token}"
|
|
|
|
livecheck do
|
|
cask "livecheck-throttle-days-source"
|
|
end
|
|
|
|
app "#{cask_token}.app"
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
it do
|
|
allow(Homebrew::Livecheck).to receive_messages(
|
|
resolve_livecheck_reference: [referenced_cask, nil],
|
|
latest_version: {
|
|
latest: Version.new("1.2.6"),
|
|
latest_throttled: Version.new("1.2.5"),
|
|
},
|
|
)
|
|
expect(run).not_to error_with(message)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "when the Cask stanza requires uninstall" do
|
|
let(:only) { ["stanza_requires_uninstall"] }
|
|
let(:message) { "installer and pkg stanzas require an uninstall stanza" }
|
|
|
|
context "when the Cask does not require an uninstall" do
|
|
let(:cask_token) { "basic-cask" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the pkg Cask has an uninstall" do
|
|
let(:cask_token) { "with-uninstall-pkgutil" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the installer Cask has an uninstall" do
|
|
let(:cask_token) { "installer-with-uninstall" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the installer Cask does not have an uninstall" do
|
|
let(:cask_token) { "with-installer-manual" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
|
|
context "when the pkg Cask does not have an uninstall" do
|
|
let(:cask_token) { "pkg-without-uninstall" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "preflight stanza checks" do
|
|
let(:message) { "only a single preflight stanza is allowed" }
|
|
|
|
context "when the Cask has no preflight stanza" do
|
|
let(:cask_token) { "with-zap-rmdir" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has only one preflight stanza" do
|
|
let(:cask_token) { "with-preflight" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has multiple preflight stanzas" do
|
|
let(:cask_token) { "with-preflight-multi" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "postflight stanza checks" do
|
|
let(:message) { "only a single postflight stanza is allowed" }
|
|
|
|
context "when the Cask has no postflight stanza" do
|
|
let(:cask_token) { "with-zap-rmdir" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has only one postflight stanza" do
|
|
let(:cask_token) { "with-postflight" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has multiple postflight stanzas" do
|
|
let(:cask_token) { "with-postflight-multi" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "uninstall_preflight stanza checks" do
|
|
let(:message) { "only a single uninstall_preflight stanza is allowed" }
|
|
|
|
context "when the Cask has no uninstall_preflight stanza" do
|
|
let(:cask_token) { "with-zap-rmdir" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has only one uninstall_preflight stanza" do
|
|
let(:cask_token) { "with-uninstall-preflight" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has multiple uninstall_preflight stanzas" do
|
|
let(:cask_token) { "with-uninstall-preflight-multi" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "uninstall_postflight stanza checks" do
|
|
let(:message) { "only a single uninstall_postflight stanza is allowed" }
|
|
|
|
context "when the Cask has no uninstall_postflight stanza" do
|
|
let(:cask_token) { "with-zap-rmdir" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has only one uninstall_postflight stanza" do
|
|
let(:cask_token) { "with-uninstall-postflight" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has multiple uninstall_postflight stanzas" do
|
|
let(:cask_token) { "with-uninstall-postflight-multi" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "zap stanza checks" do
|
|
let(:message) { "only a single zap stanza is allowed" }
|
|
|
|
context "when the Cask has no zap stanza" do
|
|
let(:cask_token) { "with-uninstall-rmdir" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has only one zap stanza" do
|
|
let(:cask_token) { "with-zap-rmdir" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask has multiple zap stanzas" do
|
|
let(:cask_token) { "with-zap-multi" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "version checks" do
|
|
let(:message) { "you should use version :latest instead of version 'latest'" }
|
|
|
|
context "when version is 'latest'" do
|
|
let(:only) { ["no_string_version_latest"] }
|
|
let(:cask_token) { "version-latest-string" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
|
|
context "when version is :latest" do
|
|
let(:only) { ["sha256_no_check_if_latest"] }
|
|
let(:cask_token) { "version-latest-with-checksum" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when version contains a colon" do
|
|
let(:only) { ["version_special_characters"] }
|
|
let(:cask_token) { "version-colon" }
|
|
let(:message) { "version should not contain colons or slashes" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "sha256 checks" do
|
|
context "when version is :latest and sha256 is not :no_check" do
|
|
let(:only) { ["sha256_no_check_if_latest"] }
|
|
let(:cask_token) { "version-latest-with-checksum" }
|
|
|
|
it { is_expected.to error_with("you should use sha256 :no_check when version is :latest") }
|
|
end
|
|
|
|
context "when sha256 is not a legal SHA-256 digest" do
|
|
let(:only) { ["sha256_actually_256"] }
|
|
let(:cask_token) { "invalid-sha256" }
|
|
|
|
it { is_expected.to error_with("sha256 string must be of 64 hexadecimal characters") }
|
|
end
|
|
|
|
context "when sha256 is sha256 for empty string" do
|
|
let(:only) { ["sha256_invalid"] }
|
|
let(:cask_token) { "sha256-for-empty-string" }
|
|
|
|
it { is_expected.to error_with(/cannot use the sha256 for an empty string/) }
|
|
end
|
|
end
|
|
|
|
describe "hosting with livecheck checks" do
|
|
let(:only) { ["hosting_with_livecheck"] }
|
|
let(:message) { /please add a livecheck/ }
|
|
|
|
context "when the download does not use hosting with a livecheck" do
|
|
let(:cask_token) { "basic-cask" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the download is hosted on SourceForge and has a livecheck" do
|
|
let(:cask_token) { "sourceforge-with-livecheck" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the download is hosted on SourceForge and does not have a livecheck" do
|
|
let(:cask_token) { "sourceforge-correct-url-format" }
|
|
let(:online) { true }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
|
|
context "when the download is hosted on DevMate and has a livecheck" do
|
|
let(:cask_token) { "devmate-with-livecheck" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the download is hosted on DevMate and does not have a livecheck" do
|
|
let(:cask_token) { "devmate-without-livecheck" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
|
|
context "when the download is hosted on HockeyApp and has a livecheck" do
|
|
let(:cask_token) { "hockeyapp-with-livecheck" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the download is hosted on HockeyApp and does not have a livecheck" do
|
|
let(:cask_token) { "hockeyapp-without-livecheck" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "latest with livecheck checks" do
|
|
let(:only) { ["latest_with_livecheck"] }
|
|
let(:message) { "Casks with a `livecheck` should not use `version :latest`." }
|
|
|
|
context "when the Cask is :latest and does not have a livecheck" do
|
|
let(:cask_token) { "version-latest" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when the Cask is versioned and has a livecheck with skip information" do
|
|
let(:cask_token) { "latest-with-livecheck-skip" }
|
|
|
|
it { is_expected.to pass }
|
|
end
|
|
|
|
context "when the Cask is versioned and has a livecheck" do
|
|
let(:cask_token) { "latest-with-livecheck" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "conflicts with" do
|
|
let(:only) { ["conflicts_with"] }
|
|
let(:tap) { CoreCaskTap.instance }
|
|
|
|
context "when the Cask has no conflicts" do
|
|
let(:cask_token) { "basic-cask" }
|
|
|
|
it { is_expected.to pass }
|
|
end
|
|
|
|
context "when all conflicting casks exist" do
|
|
let(:cask) do
|
|
tmp_cask "test-conflicts-cask", <<~RUBY
|
|
cask 'test-conflicts-cask' do
|
|
version '1.0'
|
|
url "https://brew.sh/index.html"
|
|
artifact "example.pdf", target: "/Library/Application Support/example"
|
|
|
|
conflicts_with cask: ["foo", "bar"]
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
before do
|
|
allow(audit).to receive(:core_cask_tokens).and_return(%w[foo bar baz qux])
|
|
allow(cask).to receive(:tap).and_return(tap)
|
|
end
|
|
|
|
it { is_expected.to pass }
|
|
end
|
|
|
|
context "when conflicting casks are missing" do
|
|
let(:cask) do
|
|
tmp_cask "test-conflicts-cask", <<~RUBY
|
|
cask 'test-conflicts-cask' do
|
|
version '1.0'
|
|
url "https://brew.sh/index.html"
|
|
artifact "example.pdf", target: "/Library/Application Support/example"
|
|
|
|
conflicts_with cask: ["foo", "foo@1", "bar", "baz"]
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
before do
|
|
allow(audit).to receive(:core_cask_tokens).and_return(["foo", "baz"])
|
|
allow(cask).to receive(:tap).and_return(tap)
|
|
end
|
|
|
|
it { is_expected.to error_with(/cask conflicts with non-existing cask/) }
|
|
end
|
|
end
|
|
|
|
describe "denylist checks" do
|
|
let(:only) { ["denylist"] }
|
|
|
|
context "when the Cask is not on the denylist" do
|
|
let(:cask_token) { "adobe-air" }
|
|
|
|
it { is_expected.to pass }
|
|
end
|
|
|
|
context "when the Cask is on the denylist and" do
|
|
context "when it's in the official Homebrew tap" do
|
|
let(:cask_token) { "adobe-illustrator" }
|
|
|
|
it { is_expected.to error_with(/#{cask_token} is not allowed: \w+/) }
|
|
end
|
|
|
|
context "when it isn't in the official Homebrew tap" do
|
|
let(:cask_token) { "pharo" }
|
|
|
|
it do
|
|
allow(Homebrew::Trust).to receive(:trusted?).and_return(true)
|
|
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "latest with auto_updates checks" do
|
|
let(:only) { ["latest_with_auto_updates"] }
|
|
let(:message) { "Casks with `version :latest` should not use `auto_updates`." }
|
|
|
|
context "when the Cask is :latest and does not have auto_updates" do
|
|
let(:cask_token) { "version-latest" }
|
|
|
|
it { is_expected.to pass }
|
|
end
|
|
|
|
context "when the Cask is versioned and does not have auto_updates" do
|
|
let(:cask_token) { "basic-cask" }
|
|
|
|
it { is_expected.to pass }
|
|
end
|
|
|
|
context "when the Cask is versioned and has auto_updates" do
|
|
let(:cask_token) { "auto-updates" }
|
|
|
|
it { is_expected.to pass }
|
|
end
|
|
|
|
context "when the Cask is :latest and has auto_updates" do
|
|
let(:cask_token) { "latest-with-auto-updates" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "Rosetta checks" do
|
|
let(:online) { true }
|
|
let(:only) { ["rosetta"] }
|
|
let(:cask) do
|
|
Cask::Cask.new("rosetta-audit") do
|
|
version "1.0"
|
|
sha256 :no_check
|
|
url "https://brew.sh/rosetta-audit.zip"
|
|
name "Rosetta Audit"
|
|
homepage "https://brew.sh/"
|
|
depends_on macos: :big_sur
|
|
|
|
binary "rosetta-audit"
|
|
|
|
caveats do
|
|
requires_rosetta
|
|
end
|
|
end
|
|
end
|
|
|
|
around do |example|
|
|
Homebrew::SimulateSystem.with(os: :sequoia, arch: :arm) do
|
|
example.run
|
|
end
|
|
end
|
|
|
|
before do
|
|
allow(Hardware::CPU).to receive(:rosetta_installed?).and_return(true)
|
|
allow(audit).to receive(:extract_artifacts).and_yield(cask.artifacts, cask.staged_path)
|
|
allow(audit).to receive(:system_command)
|
|
.and_return(instance_double(SystemCommand::Result, success?: true, merged_output: "x86_64"))
|
|
end
|
|
|
|
it "recognizes a suppressed requires_rosetta caveat" do
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
|
|
describe "minimum OS checks" do
|
|
let(:online) { true }
|
|
let(:only) { ["min_os"] }
|
|
let(:cask) do
|
|
Cask::Cask.new("arch-min-os") do
|
|
version "1.0"
|
|
sha256 :no_check
|
|
url "https://brew.sh/arch-min-os.zip"
|
|
name "Arch Min OS"
|
|
homepage "https://brew.sh/"
|
|
|
|
on_arm do
|
|
depends_on macos: :sequoia
|
|
end
|
|
|
|
depends_on :macos
|
|
|
|
app "Arch Min OS.app"
|
|
end
|
|
end
|
|
|
|
before do
|
|
allow(audit).to receive_messages(cask_bundle_min_os: MacOSVersion.from_symbol(:sequoia),
|
|
cask_sparkle_min_os: nil)
|
|
end
|
|
|
|
context "when running on arm" do
|
|
around do |example|
|
|
Homebrew::SimulateSystem.with(arch: :arm) do
|
|
example.run
|
|
end
|
|
end
|
|
|
|
it { is_expected.to pass }
|
|
end
|
|
|
|
context "when running on intel" do
|
|
around do |example|
|
|
Homebrew::SimulateSystem.with(arch: :intel) do
|
|
example.run
|
|
end
|
|
end
|
|
|
|
it { is_expected.to error_with(/cask declared no minimum macOS version/) }
|
|
end
|
|
|
|
context "when the app requires a newer macOS but the cask declares no macOS dependency" do
|
|
let(:cask) do
|
|
tmp_cask "no-min-os", <<~RUBY
|
|
cask 'no-min-os' do
|
|
version '1.0'
|
|
sha256 :no_check
|
|
url 'https://brew.sh/no-min-os.zip'
|
|
name 'No Min OS'
|
|
homepage 'https://brew.sh/'
|
|
|
|
on_macos do
|
|
depends_on arch: :arm64
|
|
|
|
app 'No Min OS.app'
|
|
end
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
it { is_expected.to error_with(/cask declared no minimum macOS version/) }
|
|
end
|
|
|
|
it "normalizes 10.16.0 minimum macOS to Big Sur" do
|
|
expect(audit.normalize_min_os("10.16.0")).to eq(MacOSVersion.from_symbol(:big_sur))
|
|
end
|
|
end
|
|
|
|
describe "preferred download URL formats" do
|
|
let(:only) { ["download_url_format"] }
|
|
let(:message) { /URL format incorrect/ }
|
|
|
|
context "with incorrect SourceForge URL format" do
|
|
let(:cask_token) { "sourceforge-incorrect-url-format" }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
|
|
context "with correct SourceForge URL format" do
|
|
let(:cask_token) { "sourceforge-correct-url-format" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "with correct SourceForge URL format for version :latest" do
|
|
let(:cask_token) { "sourceforge-version-latest-correct-url-format" }
|
|
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "disable OSDN download url" do
|
|
let(:only) { ["download_url_is_osdn"] }
|
|
let(:message) { /OSDN download urls are disabled./ }
|
|
let(:cask_token) { "osdn-urls" }
|
|
|
|
context "when --strict is not passed" do
|
|
it { is_expected.not_to error_with(message) }
|
|
end
|
|
|
|
context "when --strict is passed" do
|
|
let(:strict) { true }
|
|
|
|
it { is_expected.to error_with(message) }
|
|
end
|
|
end
|
|
|
|
describe "generic artifact checks" do
|
|
let(:only) { ["generic_artifacts"] }
|
|
|
|
context "with relative target" do
|
|
let(:cask_token) { "generic-artifact-relative-target" }
|
|
|
|
it { is_expected.to error_with(/target must be.*absolute/) }
|
|
end
|
|
|
|
context "with user-relative target" do
|
|
let(:cask_token) { "generic-artifact-user-relative-target" }
|
|
|
|
it { is_expected.not_to error_with(/target must be.*absolute/) }
|
|
end
|
|
|
|
context "with absolute target" do
|
|
let(:cask_token) { "generic-artifact-absolute-target" }
|
|
|
|
it { is_expected.not_to error_with(/target must be.*absolute/) }
|
|
end
|
|
end
|
|
|
|
describe "token conflicts" do
|
|
let(:cask_token) { "with-binary" }
|
|
|
|
context "when cask token conflicts with a core formula" do
|
|
let(:formula_names) { %w[with-binary other-formula] }
|
|
|
|
it "warns about conflicts" do
|
|
expect(audit).to receive(:core_formula_names).and_return(formula_names)
|
|
expect(run).to error_with(/cask token conflicts/)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "audit of downloads" do
|
|
let(:only) { ["download"] }
|
|
let(:cask_token) { "basic-cask" }
|
|
let(:cask) { Cask::CaskLoader.load(cask_token) }
|
|
let(:download_double) { instance_double(Cask::Download) }
|
|
let(:message) { "Download Failed" }
|
|
|
|
before do
|
|
allow(audit).to receive(:download).and_return(download_double)
|
|
allow(UnpackStrategy).to receive(:detect).and_return(nil)
|
|
end
|
|
|
|
context "when the download succeeds" do
|
|
it "passes" do
|
|
expect(download_double).to receive(:fetch).and_return(Pathname.new("/tmp/test.zip"))
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
|
|
context "when the download fails" do
|
|
it "fails" do
|
|
expect(download_double).to receive(:fetch).and_raise(StandardError.new(message))
|
|
expect(run).to error_with(/#{message}/)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when an exception is raised" do
|
|
let(:cask) { instance_double(Cask::Cask) }
|
|
let(:only) { ["description"] }
|
|
|
|
it "fails the audit" do
|
|
expect(cask).to receive(:tap).and_raise(StandardError.new)
|
|
expect(run).to error_with(/exception while auditing/)
|
|
end
|
|
end
|
|
|
|
describe "checking description" do
|
|
let(:only) { ["description"] }
|
|
let(:cask_token) { "without-description" }
|
|
let(:cask) do
|
|
tmp_cask cask_token.to_s, <<~RUBY
|
|
cask '#{cask_token}' do
|
|
version '1.0'
|
|
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
|
|
url "https://brew.sh/"
|
|
name 'Audit'
|
|
homepage 'https://brew.sh/'
|
|
app 'Audit.app'
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
context "when `new_cask` is true" do
|
|
let(:new_cask) { true }
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/should have a description/)
|
|
end
|
|
end
|
|
|
|
context "when `new_cask` is false" do
|
|
let(:new_cask) { false }
|
|
|
|
it "does not warn" do
|
|
expect(run).not_to error_with(/should have a description/)
|
|
end
|
|
end
|
|
|
|
context "with description" do
|
|
let(:cask_token) { "with-description" }
|
|
let(:cask) do
|
|
tmp_cask cask_token.to_s, <<~RUBY
|
|
cask "#{cask_token}" do
|
|
version "1.0"
|
|
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
|
|
url "https://brew.sh/\#{version}.zip"
|
|
name "Audit"
|
|
desc "Cask Auditor"
|
|
homepage "https://brew.sh/"
|
|
app "Audit.app"
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
it "passes" do
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "checking deprecate/disable" do
|
|
let(:only) { ["deprecate_disable"] }
|
|
let(:cask_token) { "deprecated-cask" }
|
|
|
|
context "when deprecate/disable is used with a valid reason" do
|
|
let(:cask) do
|
|
tmp_cask cask_token.to_s, <<~RUBY
|
|
cask '#{cask_token}' do
|
|
version "1.0"
|
|
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
|
|
url "https://brew.sh/foo.zip"
|
|
name "Audit"
|
|
desc "Cask Auditor"
|
|
homepage "https://brew.sh/"
|
|
app "Audit.app"
|
|
deprecate! date: "2021-01-01", because: :foobar
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
it "fails" do
|
|
expect(run).to error_with(/foobar is not a valid deprecate! or disable! reason/)
|
|
end
|
|
end
|
|
|
|
context "when deprecate/disable is used with an invalid reason" do
|
|
let(:cask) do
|
|
tmp_cask cask_token.to_s, <<~RUBY
|
|
cask '#{cask_token}' do
|
|
version "1.0"
|
|
sha256 "8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a"
|
|
url "https://brew.sh/foo.zip"
|
|
name "Audit"
|
|
desc "Cask Auditor"
|
|
homepage "https://brew.sh/"
|
|
app "Audit.app"
|
|
disable! date: "2021-01-01", because: :discontinued
|
|
end
|
|
RUBY
|
|
end
|
|
|
|
it "passes" do
|
|
expect(run).to pass
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|