# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

require File.expand_path("../../../../base", __FILE__)
require "vagrant/util/mime"

describe Vagrant::Action::Builtin::CloudInitSetup do
  let(:app) { lambda { |env| } }
  let(:vm) { double("vm", disk: disk, disks: disks) }
  let(:disk) { double("disk") }
  let(:disks) { double("disk") }
  let(:config) { double("config", vm: vm) }
  let(:provider) { double("provider") }
  let(:machine) { double("machine", config: config, provider: provider, name: "machine",
                         provider_name: "provider", data_dir: Pathname.new("/fake/dir"),
                         ui: ui, env: machine_env, id: "123-456-789") }
  let(:host) { double("host") }
  let(:machine_env) { double("machine_env", root_path: "root", host: host) }
  let(:env) { { ui: ui, machine: machine, env: machine_env} }

  let(:ui)  { Vagrant::UI::Silent.new }

  let(:cfg) { double("cfg", type: :user_data, content_type: "text/cloud-config",
                     content_disposition_filename: nil, path: "my/path",
                     inline: nil) }
  let(:cfg_inline) { double("cfg", type: :user_data, content_type: "text/cloud-config",
                            content_disposition_filename: nil, inline: "data: true",
                            path: nil) }
  let(:cfg_with_content_disposition_filename_inline) {
    double("cfg", type: :user_data, content_type: "text/x-shellscript",
           content_disposition_filename: "test.ps1",
           inline: "#ps1_sysnative\n", path: nil) }
  let(:cloud_init_configs) { [cfg, cfg_inline] }

  let(:text_cfgs) { [Vagrant::Util::Mime::Entity.new("data: true", "text/cloud-config"),
                     Vagrant::Util::Mime::Entity.new("data: false", "text/cloud-config") ] }

  let(:meta_data) { { "instance-id" => "i-123456789" } }


  let(:subject) { described_class.new(app, env) }

  describe "#call" do
    it "calls setup_user_data if cloud_init config present" do
      allow(vm).to receive(:cloud_init_configs).and_return(cloud_init_configs)

      expect(app).to receive(:call).with(env).ordered

      expect(subject).to receive(:setup_user_data).and_return(true)
      expect(subject).to receive(:write_cfg_iso).and_return(true)

      subject.call(env)
    end

    it "continues on if no cloud_init config present" do
      allow(vm).to receive(:cloud_init_configs).and_return([])

      expect(app).to receive(:call).with(env).ordered

      expect(subject).not_to receive(:setup_user_data)
      expect(subject).not_to receive(:write_cfg_iso)
      expect(subject).not_to receive(:attack_disk_config)

      subject.call(env)
    end
  end

  describe "#setup_user_data" do
    it "builds a MIME message and prepares a disc to be attached" do
      expect(subject).to receive(:read_text_cfg).twice

      expect(subject).to receive(:generate_cfg_msg)

      subject.setup_user_data(machine, env, cloud_init_configs)
    end
  end

  describe "#read_text_cfg" do
    let(:cfg_text) { "config: true" }

    it "takes a text cfg path and saves it as a MIME text message" do
      mime_text_part = double("mime_text_part")
      expect(mime_text_part).not_to receive(:disposition=)
      allow(File).to receive(:read).and_return(cfg_text)
      expect(Vagrant::Util::Mime::Entity).to receive(:new).with(cfg_text, "text/cloud-config").and_return(mime_text_part)
      subject.read_text_cfg(machine, cfg)
    end

    it "takes a text cfg inline string and saves it as a MIME text message" do
      mime_text_part = double("mime_text_part")
      expect(mime_text_part).not_to receive(:disposition=)
      expect(Vagrant::Util::Mime::Entity).to receive(:new).with("data: true", "text/cloud-config").and_return(mime_text_part)
      subject.read_text_cfg(machine, cfg_inline)
    end

    it "takes a text cfg inline string with content_disposition_filename and saves it as a MIME text message" do
      mime_text_part = double("mime_text_part")
      expect(mime_text_part).to receive(:disposition=).with("attachment; filename=\"test.ps1\"")
      expect(Vagrant::Util::Mime::Entity).to receive(:new).with("#ps1_sysnative\n", "text/x-shellscript").and_return(mime_text_part)
      subject.read_text_cfg(machine, cfg_with_content_disposition_filename_inline)
    end
  end

  describe "#generate_cfg_msg" do
    it "creates a miltipart mixed message of combined configs" do
      message = subject.generate_cfg_msg(machine, text_cfgs)
      expect(message).to be_a(Vagrant::Util::Mime::Multipart)
    end

    it "sets a MIME-Version header" do
      message = subject.generate_cfg_msg(machine, text_cfgs)
      expect(message.headers["MIME-Version"]).to eq("1.0")
    end
  end

  describe "#write_cfg_iso" do
    let(:iso_path) { Pathname.new("fake/iso/path") }
    let(:source_dir) { Pathname.new("fake/source/path") }
    let(:meta_data_file) { double("meta_data_file") }

    before do
      allow(meta_data_file).to receive(:write).and_return(true)
    end

    it "raises an error if the host capability is not supported" do
      message = subject.generate_cfg_msg(machine, text_cfgs)
      allow(host).to receive(:capability?).with(:create_iso).and_return(false)

      expect{subject.write_cfg_iso(machine, env, message, {})}.to raise_error(Vagrant::Errors::CreateIsoHostCapNotFound)
    end

    it "creates a temp dir with the cloud_init config and generates an iso" do
      message = subject.generate_cfg_msg(machine, text_cfgs)
      allow(host).to receive(:capability?).with(:create_iso).and_return(true)
      allow(Dir).to receive(:mktmpdir).and_return(source_dir)
      expect(File).to receive(:open).with("#{source_dir}/user-data", 'w').and_return(true)
      expect(File).to receive(:open).with("#{source_dir}/meta-data", 'w').and_yield(meta_data_file)
      expect(FileUtils).to receive(:remove_entry).with(source_dir).and_return(true)
      allow(host).to receive(:capability).with(:create_iso, source_dir, volume_id: "cidata").and_return(iso_path)
      expect(vm.disks).to receive(:each)
      expect(meta_data).to receive(:to_yaml)

      subject.write_cfg_iso(machine, env, message, meta_data)
    end
  end

  describe "#attach_disk_config" do
    let(:iso_path) { Pathname.new("fake/iso/path") }

    it "creates a new disk config based on the iso_path" do
      expect(vm.disks).to receive(:each)
      subject.attach_disk_config(machine, env, iso_path)
    end
  end
end
