require File.expand_path("../../../../base", __FILE__)

require Vagrant.source_root.join("plugins/commands/cloud/client/client")

describe VagrantPlugins::CloudCommand::Client do
  include_context "unit"

  let(:env) { isolated_environment.create_vagrant_env }

  subject(:client) { described_class.new(env) }

  before(:all) do
    I18n.load_path << Vagrant.source_root.join("plugins/commands/cloud/locales/en.yml")
    I18n.reload!
  end

  before do
    stub_env("ATLAS_TOKEN" => nil)
    subject.clear_token
  end

  describe "#logged_in?" do
    let(:url) { "#{Vagrant.server_url}/api/v1/authenticate?access_token=#{token}" }
    let(:headers) { { "Content-Type" => "application/json" } }

    before { allow(subject).to receive(:token).and_return(token) }

    context "when there is no token" do
      let(:token) { nil }

      it "returns false" do
        expect(subject.logged_in?).to be(false)
      end
    end

    context "when there is a token" do
      let(:token) { "ABCD1234" }

      it "returns true if the endpoint returns a 200" do
        stub_request(:get, url)
          .with(headers: headers)
          .to_return(body: JSON.pretty_generate("token" => token))
        expect(subject.logged_in?).to be(true)
      end

      it "raises an error if the endpoint returns a non-200" do
        stub_request(:get, url)
          .with(headers: headers)
          .to_return(body: JSON.pretty_generate("bad" => true), status: 401)
        expect(subject.logged_in?).to be(false)
      end

      it "raises an exception if the server cannot be found" do
        stub_request(:get, url)
          .to_raise(SocketError)
        expect { subject.logged_in? }
          .to raise_error(VagrantPlugins::CloudCommand::Errors::ServerUnreachable)
      end
    end
  end

  describe "#login" do
    let(:request) {
      {
        user: {
          login: login,
          password: password,
        },
        token: {
          description: description,
        },
        two_factor: {
          code: nil
        }
      }
    }

    let(:login) { "foo" }
    let(:password) { "supersecretpassword" }
    let(:description) { "Token description" }

    let(:headers) {
      {
        "Accept" => "application/json",
        "Content-Type" => "application/json",
      }
    }
    let(:response) {
      {
        token: "mysecrettoken"
      }
    }

    it "returns the access token after successful login" do
      stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate").
        with(body: JSON.dump(request), headers: headers).
        to_return(status: 200, body: JSON.dump(response))

      client.username_or_email = login
      client.password = password

      expect(client.login(description: "Token description")).to eq("mysecrettoken")
    end

    context "when 2fa is required" do
      let(:response) {
        {
          two_factor: {
            default_delivery_method: default_delivery_method,
            delivery_methods: delivery_methods
          }
        }
      }
      let(:default_delivery_method) { "app" }
      let(:delivery_methods) { ["app"] }

      before do
        stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate").
          to_return(status: 406, body: JSON.dump(response))
      end

      it "raises a two-factor required error" do
        expect {
          client.login
        }.to raise_error(VagrantPlugins::CloudCommand::Errors::TwoFactorRequired)
      end

      context "when the default delivery method is not app" do
        let(:default_delivery_method) { "sms" }
        let(:delivery_methods) { ["app", "sms"] }

        it "requests a code and then raises a two-factor required error" do
          expect(client)
            .to receive(:request_code)
            .with(default_delivery_method)

          expect {
            client.login
          }.to raise_error(VagrantPlugins::CloudCommand::Errors::TwoFactorRequired)
        end
      end
    end

    context "on bad login" do
      before do
        stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate").
          to_return(status: 401, body: "")
      end

      it "raises an error" do
        expect {
          client.login
        }.to raise_error(VagrantPlugins::CloudCommand::Errors::Unauthorized)
      end
    end

    context "if it can't reach the server" do
      before do
        stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate").
          to_raise(SocketError)
      end

      it "raises an exception" do
        expect {
          subject.login
        }.to raise_error(VagrantPlugins::CloudCommand::Errors::ServerUnreachable)
      end
    end
  end

  describe "#request_code" do
    let(:request) {
      {
        user: {
          login: login,
          password: password,
        },
        two_factor: {
          delivery_method: delivery_method
        }
      }
    }

    let(:login) { "foo" }
    let(:password) { "supersecretpassword" }
    let(:delivery_method) { "sms" }

    let(:headers) {
      {
        "Accept" => "application/json",
        "Content-Type" => "application/json"
      }
    }

    let(:response) {
      {
        two_factor: {
          obfuscated_destination: "SMS number ending in 1234"
        }
      }
    }

    it "displays that the code was sent" do
      expect(env.ui)
        .to receive(:success)
        .with("2FA code sent to SMS number ending in 1234.")

      stub_request(:post, "#{Vagrant.server_url}/api/v1/two-factor/request-code").
        with(body: JSON.dump(request), headers: headers).
        to_return(status: 201, body: JSON.dump(response))

      client.username_or_email = login
      client.password = password

      client.request_code delivery_method
    end
  end

  describe "#token" do
    it "reads ATLAS_TOKEN" do
      stub_env("ATLAS_TOKEN" => "ABCD1234")
      expect(subject.token).to eq("ABCD1234")
    end

    it "reads the stored file" do
      subject.store_token("EFGH5678")
      expect(subject.token).to eq("EFGH5678")
    end

    it "prefers the environment variable" do
      stub_env("VAGRANT_CLOUD_TOKEN" => "ABCD1234")
      subject.store_token("EFGH5678")
      expect(subject.token).to eq("ABCD1234")
    end

    it "prints a warning if the envvar and stored file are both present" do
      stub_env("VAGRANT_CLOUD_TOKEN" => "ABCD1234")
      subject.store_token("EFGH5678")
      expect(env.ui).to receive(:warn).with(/detected both/)
      subject.token
    end

    it "returns nil if there's no token set" do
      expect(subject.token).to be(nil)
    end
  end

  describe "#store_token, #clear_token" do
    it "stores the token and can re-access it" do
      subject.store_token("foo")
      expect(subject.token).to eq("foo")
      expect(described_class.new(env).token).to eq("foo")
    end

    it "deletes the token" do
      subject.store_token("foo")
      subject.clear_token
      expect(subject.token).to be_nil
    end
  end
end
