require "spec_helper" module Omnibus describe Packager::MSI do let(:project) do Project.new.tap do |project| project.name("project") project.homepage("https://example.com") project.install_dir(install_dir) project.build_version("1.2.3") project.build_iteration("2") project.maintainer("Chef Software ") end end subject { described_class.new(project) } let(:project_root) { File.join(tmp_path, "project/root") } let(:package_dir) { File.join(tmp_path, "package/dir") } let(:staging_dir) { File.join(tmp_path, "staging/dir") } let(:install_dir) { "C:/project" } before do Config.project_root(project_root) Config.package_dir(package_dir) allow(subject).to receive(:staging_dir).and_return(staging_dir) create_directory(staging_dir) end describe "DSL" do it "exposes :parameters" do expect(subject).to have_exposed_method(:parameters) end it "exposes :signing_identity" do expect(subject).to have_exposed_method(:signing_identity) end end describe "#id" do it "is :pkg" do expect(subject.id).to eq(:msi) end end describe "#upgrade_code" do it "is a DSL method" do expect(subject).to have_exposed_method(:upgrade_code) end it "is required" do expect do subject.upgrade_code end.to raise_error(MissingRequiredAttribute) end it "requires the value to be a String" do expect do subject.parameters(Object.new) end.to raise_error(InvalidValue) end it "returns the given value" do code = "ABCD-1234" subject.upgrade_code(code) expect(subject.upgrade_code).to be(code) end end describe "#parameters" do it "is a DSL method" do expect(subject).to have_exposed_method(:parameters) end it "is defaults to an empty hash" do expect(subject.parameters).to be_a(Hash) end it "requires the value to be a Hash" do expect do subject.parameters(Object.new) end.to raise_error(InvalidValue) end it "returns the given value" do params = { "Key" => "value" } subject.parameters(params) expect(subject.parameters).to be(params) end end describe "#localization" do it "is a DSL method" do expect(subject).to have_exposed_method(:localization) end it "defaults to String en-us" do expect(subject.localization).to be_a(String) expect(subject.localization).to eq("en-us") end it "requires the value to be a String" do expect do subject.localization(Object.new) end.to raise_error(InvalidValue) end it "returns the given value" do loc = "te-st" subject.localization(loc) expect(subject.localization).to eq(loc) end end describe "#package_name" do before do allow(Config).to receive(:windows_arch).and_return(:foo_arch) end it "includes the name, version, and build iteration" do expect(subject.package_name).to eq("project-1.2.3-2-foo_arch.msi") end it "returns the bundle name when building a bundle" do subject.bundle_msi(true) expect(subject.package_name).to eq("project-1.2.3-2-foo_arch.exe") end end describe "#resources_dir" do it "is nested inside the staging_dir" do expect(subject.resources_dir).to eq("#{staging_dir}/Resources") end end describe "#write_localization_file" do it "generates the file" do subject.write_localization_file expect("#{staging_dir}/localization-en-us.wxl").to be_a_file end it "has the correct content" do subject.write_localization_file contents = File.read("#{staging_dir}/localization-en-us.wxl") expect(contents).to include('Project') expect(contents).to include('"Chef Software <maintainers@chef.io>"') expect(contents).to include('Project') end end describe "#write_parameters_file" do before do subject.upgrade_code("ABCD-1234") end it "generates the file" do subject.write_parameters_file expect("#{staging_dir}/parameters.wxi").to be_a_file end it "has the correct content" do subject.write_parameters_file contents = File.read("#{staging_dir}/parameters.wxi") expect(contents).to include('') expect(contents).to include('') expect(contents).to include('') end end describe "#write_source_file" do it "generates the file" do subject.write_source_file expect("#{staging_dir}/source.wxs").to be_a_file end it "has the correct content" do project.install_dir("C:/foo/bar/blip") subject.write_source_file contents = File.read("#{staging_dir}/source.wxs") expect(contents).to include('') expect(contents).to include <<-EOH.gsub(/^ {6}/, "") EOH end it "has the correct wix_install_dir when the path is short" do subject.write_source_file contents = File.read("#{staging_dir}/source.wxs") expect(contents).to include('') expect(contents).to include('') end context "when fastmsi is not specified" do it "does not include a reference to the fast msi custom action" do subject.write_source_file contents = File.read("#{staging_dir}/source.wxs") expect(contents).not_to include("") end end describe "#windows_package_version" do context "when the project build_version semver" do it "returns the right value" do expect(subject.windows_package_version).to eq("1.2.3.2") end end context "when the project build_version is git" do before { project.build_version("1.2.3-alpha.1+20140501194641.git.94.561b564") } it "returns the right value" do expect(subject.windows_package_version).to eq("1.2.3.2") end end end describe "#msi_display_version" do context 'when the project build_version is "safe"' do it "returns the right value" do expect(subject.msi_display_version).to eq("1.2.3") end end context "when the project build_version is a git tag" do before { project.build_version("1.2.3-alpha.1+20140501194641.git.94.561b564") } it "returns the right value" do expect(subject.msi_display_version).to eq("1.2.3") end end end describe "#wix_candle_extensions" do it "defaults to an empty Array" do expect(subject.wix_candle_extensions).to be_an(Array) expect(subject.wix_candle_extensions).to be_empty end end describe "#wix_light_extensions" do it "defaults to an empty Array" do expect(subject.wix_light_extensions).to be_an(Array) expect(subject.wix_light_extensions).to be_empty end end describe "#wix_light_delay_validation" do it "is a DSL method" do expect(subject).to have_exposed_method(:wix_light_delay_validation) end it "requires the value to be a TrueClass or a FalseClass" do expect do subject.wix_light_delay_validation(Object.new) end.to raise_error(InvalidValue) end it "defaults to an empty String" do expect(subject.wix_light_delay_validation).to be_a(String) expect(subject.wix_light_delay_validation).to be_empty end it "returns the string `-sval` when true" do subject.wix_light_delay_validation(true) expect(subject.wix_light_delay_validation).to eq("-sval") end end describe "#wix_candle_extension" do it "is a DSL method" do expect(subject).to have_exposed_method(:wix_candle_extension) end it "requires the value to be an String" do expect do subject.wix_candle_extension(Object.new) end.to raise_error(InvalidValue) end it "returns the given value" do extensions = ["a"] subject.wix_candle_extension(extensions[0]) expect(subject.wix_candle_extensions).to match_array(extensions) end end describe "#wix_light_extension" do it "is a DSL method" do expect(subject).to have_exposed_method(:wix_light_extension) end it "requires the value to be an String" do expect do subject.wix_light_extension(Object.new) end.to raise_error(InvalidValue) end it "returns the given value" do extensions = ["a"] subject.wix_light_extension(extensions[0]) expect(subject.wix_light_extensions).to match_array(extensions) end end describe "#wix_extension_switches" do it "returns an empty string for an empty array" do expect(subject.wix_extension_switches([])).to eq("") end it "returns the correct value for one extension" do expect(subject.wix_extension_switches(["a"])).to eq("-ext 'a'") end it "returns the correct value for many extensions" do expect(subject.wix_extension_switches(%w{a b})).to eq("-ext 'a' -ext 'b'") end end describe "#bundle_msi" do it "is a DSL method" do expect(subject).to have_exposed_method(:bundle_msi) end it "requires the value to be a TrueClass or a FalseClass" do expect do subject.bundle_msi(Object.new) end.to raise_error(InvalidValue) end it "returns the given value" do subject.bundle_msi(true) expect(subject.bundle_msi).to be_truthy end end describe "#fast_msi" do it "is a DSL method" do expect(subject).to have_exposed_method(:fast_msi) end it "requires the value to be a TrueClass or a FalseClass" do expect do subject.fast_msi(Object.new) end.to raise_error(InvalidValue) end it "returns the given value" do subject.fast_msi(true) expect(subject.fast_msi).to be_truthy end end describe "#zip_command" do it "returns a String" do expect(subject.zip_command).to be_a(String) end it "sets zip file location to the staging directory" do expect(subject.zip_command).to include("#{subject.windows_safe_path(staging_dir)}\\#{project.name}.zip") end end describe "#candle_command" do it "returns a String" do expect(subject.candle_command).to be_a(String) end context "default behavior" do it "defines the ProjectSourceDir property" do expect(subject.candle_command).to include("-dProjectSourceDir=") end it "outputs a source.wxs file to the staging directory" do expect(subject.candle_command).to include("#{subject.windows_safe_path(staging_dir, "source.wxs")}") end end context "when is_bundle is true" do it "uses the WIX Bootstrapper/Burn extension" do expect(subject.candle_command(is_bundle: true)).to include("-ext WixBalExtension") end it "defines the OmnibusCacheDir property" do expect(subject.candle_command(is_bundle: true)).to include("-dOmnibusCacheDir=") end it "outputs a bundle.wxs file to the staging directory" do expect(subject.candle_command(is_bundle: true)).to include("#{subject.windows_safe_path(staging_dir, "bundle.wxs")}") end end end describe "#heat_command" do it "returns a String" do expect(subject.heat_command).to be_a(String) end context "when fast_msi is not set" do it "operates in directory mode" do expect(subject.heat_command).to include("dir \"#{subject.windows_safe_path(project.install_dir)}\"") end it "sets destination to the project location" do expect(subject.heat_command).to include("-dr PROJECTLOCATION") end end context "when fast_msi is set" do before do subject.fast_msi(true) end it "operates in file mode" do expect(subject.heat_command).to include("file \"#{project.name}.zip\"") end it "sets destination to the install location" do expect(subject.heat_command).to include("-dr INSTALLLOCATION") end end end describe "#light_command" do it "returns a String" do expect(subject.light_command("foo")).to be_a(String) end context "default behavior" do let (:command) { subject.light_command("foo") } it "uses the WIX UI extension" do expect(command).to include("-ext WixUIExtension") end it "includes the project-files and source wixobj files" do expect(command).to include("project-files.wixobj source.wixobj") end end context "when is_bundle is true" do let (:command) { subject.light_command("foo", is_bundle: true) } it "uses the WIX Bootstrapper/Burn extension" do expect(command).to include("-ext WixBalExtension") end it "includes the bundle wixobj file" do expect(command).to include("bundle.wixobj") end end end describe "#gem_path" do let(:install_dir) { File.join(tmp_path, "install_dir") } before do create_directory(install_dir) end after do remove_directory(install_dir) end it "is a DSL method" do expect(subject).to have_exposed_method(:gem_path) end it "requires the value to be a String" do expect do subject.gem_path(Object.new) end.to raise_error(InvalidValue) end it "globs for gems under the install directory" do expected_gem_path = "something/gems/athing-1.0.0" create_directory(File.join(install_dir, expected_gem_path)) expect(subject.gem_path("athing-*")).to eq(expected_gem_path) end it "returns the gem directory when no argument is given" do expected_gem_path = "foo/bar123/gems" create_directory(File.join(install_dir, expected_gem_path)) expect(subject.gem_path).to eq(expected_gem_path) end end context "when signing parameters are provided" do let(:msi) { "somemsi.msi" } context "when invalid parameters" do it "should raise an InvalidValue error when the certificate name is not a String" do expect { subject.signing_identity(Object.new) }.to raise_error(InvalidValue) end it "should raise an InvalidValue error when params is not a Hash" do expect { subject.signing_identity("foo", Object.new) }.to raise_error(InvalidValue) end it "should raise an InvalidValue error when params contains an invalid key" do expect { subject.signing_identity("foo", bar: "baz") }.to raise_error(InvalidValue) end end context "when valid parameters" do before do allow(subject).to receive(:shellout!) end describe "#keypair_alias" do it "uses the keypair alias if provided through the #keypair_alias dsl" do subject.signing_identity("foo", keypair_alias: "bar") expect(subject).to receive(:is_signed?).with(msi).and_return(true) subject.sign_package(msi) end it "raises an exception if the signing fails" do subject.signing_identity("foo", keypair_alias: "bar") expect(subject).to receive(:is_signed?).with(msi).and_return(false) expect { subject.sign_package(msi) }.to raise_error(FailedToSignWindowsPackage) end end end end end end