require "kontena/cli/stacks/install_command"

describe Kontena::Cli::Stacks::InstallCommand do

  include ClientHelpers
  include RequirementsHelper
  include FixturesHelpers

  mock_current_master

  before(:each) do
    ENV['STACK'] = nil
    allow(client).to receive(:server_version).and_return(Kontena::Cli::VERSION)
  end

  describe '#execute' do
    let(:stack_expectation) do
      {
        'name' => 'stackname',
        'stack' => 'user/stackname',
        'version' => '0.1.1',
        'registry' => 'file://',
        'services' => array_including(hash_including('name', 'image')),
        'variables' => {},
        'volumes' => [],
        'dependencies' => nil,
        'source' => /stack:/,
        'parent' => nil,
        'expose' => nil,
        'metadata' => {}
      }
    end

    expect_to_require_current_master
    expect_to_require_current_master_token

    it 'sends stack to master' do
      expect(client).to receive(:post) do |path, data|
        expect(path).to eq 'grids/test-grid/stacks'
        expect(data).to match hash_including(stack_expectation)
        expect(data['services'].find { |s| s['name'] == 'wordpress' }['env']).to match array_including("WORDPRESS_DB_PASSWORD=stackname_secret")
        expect(data['services'].find { |s| s['name'] == 'mysql' }['env']).to match array_including("MYSQL_ROOT_PASSWORD=stackname_secret")
      end.and_return({})
      subject.run(['--no-deploy', fixture_path('kontena_v3.yml')])
    end

    it 'allows to override stack name' do
      expect(client).to receive(:post) do |path, data|
        expect(path).to eq 'grids/test-grid/stacks'
        expect(data).to match hash_including(stack_expectation.merge('name' => 'stack-a'))
        expect(data['services'].find { |s| s['name'] == 'wordpress' }['env']).to match array_including("WORDPRESS_DB_PASSWORD=stack-a_secret")
        expect(data['services'].find { |s| s['name'] == 'mysql' }['env']).to match array_including("MYSQL_ROOT_PASSWORD=stack-a_secret")
      end.and_return({})
      subject.run(['--no-deploy', '--name', 'stack-a', fixture_path('kontena_v3.yml')])
    end

    it 'accepts a stack name as filename' do
      allow(File).to receive(:exist?).with('user/stack:1.0.0').at_least(:once).and_return(false)
      expect(subject.loader_class).to receive(:for).with('user/stack:1.0.0').and_return(subject.loader_class.for(fixture_path('kontena_v3.yml')))
      allow(subject.loader_class).to receive(:for).and_call_original
      expect(client).to receive(:post).with(
        'grids/test-grid/stacks', hash_including(stack_expectation)
      )
      subject.run(['--no-deploy', 'user/stack:1.0.0'])
    end

    context '--[no-]deploy' do
      it 'runs deploy for the stack after install by default' do
        expect(client).to receive(:post).with(
          'grids/test-grid/stacks', hash_including(stack_expectation)
        )
        expect(Kontena).to receive(:run!).with(['stack', 'deploy', 'stackname']).and_return(true)
        subject.run([fixture_path('kontena_v3.yml')])
      end
    end

    context 'with a stack including dependencies' do
      it 'installs all the dependencies' do
        expect(Kontena).to receive(:run!).with([
          "stack", "install", "-n", "deptest-dep_1", "--parent-name", "deptest", "--no-deploy",
          '-v', 'dep_1.dep_var=1', fixture_path('stack-with-dependencies-dep-1.yml')
        ])
        expect(Kontena).to receive(:run!).with([
          "stack", "install", "-n", "deptest-dep_2", "--parent-name", "deptest", '--no-deploy',
          "-v", "dep_var=1", fixture_path('stack-with-dependencies-dep-2.yml')])
        expect(client).to receive(:post).with('grids/test-grid/stacks', hash_including('stack' => 'user/depstack1', 'name' => 'deptest'))
        subject.run(['-n', 'deptest', '--no-deploy', '-v', 'dep_1.dep_1.dep_var=1', fixture_path('stack-with-dependencies.yml')])
      end
    end

    context 'with a stack including metadata' do
      let(:stack_meta_expectation) do
        stack_expectation.merge(
          'metadata' => hash_including(
            'tags' => array_including('tag1', 'tag2'),
            'readme' => /Text goes/,
            'required_kontena_version' => ">= 0.5.0"
          )
        )
      end

      it 'includes the metadata in the json sent to master' do
        expect(client).to receive(:post).with('grids/test-grid/stacks', hash_including(stack_meta_expectation))
        subject.run(['--no-deploy', fixture_path('kontena_v3_with_metadata.yml')])
      end

      it 'requires force if master version does not match metadata required_kontena_version' do
        expect(client).to receive(:server_version).and_return('0.2.0')
        expect(subject).to receive(:confirm).and_call_original
        expect{subject.run(['--no-deploy', fixture_path('kontena_v3_with_metadata.yml')])}.to exit_with_error.and output(/version/).to_stdout
      end
    end
  end

  context 'variable value input' do
    let(:expectation) do
      {
        'services' => array_including(
          hash_including(
            'name' => 'mysql',
            'image' => 'mysqlimage:latest',
            'env' => array_including(
              'TEST_VAR=abc'
            )
          ),
          hash_including(
            'name' => 'wordpress',
            'image' => 'wordpress:def'
          )
        )
      }
    end

    describe '--values-from' do
      it 'sends stack to master, loading values from a file' do
        allow(File).to receive(:exist?).with('values.yml').and_return(true)
        expect(File).to receive(:read).with('values.yml').and_return(
          YAML.dump('TEST_ENV_VAR' => 'abc', 'MYSQL_IMAGE' => 'mysqlimage', 'tag' => 'def')
        )
        expect(client).to receive(:post) do |path, data|
          expect(path).to eq 'grids/test-grid/stacks'
          expect(data).to match hash_including(expectation)
        end.and_return({})
        subject.run(['--no-deploy', '--values-from', 'values.yml', fixture_path('stack-with-variables.yml')])
      end
    end

    describe '--values-from-stack' do
      it 'sends stack to master, loading values from another stack on master' do
        expect(client).to receive(:get).with('stacks/test-grid/otherdep').and_return(
          'variables' => { 'foo' => 'bar' },
          'children' => [
            { 'name' => 'otherdep-dep_1' } ,
            { 'name' => 'otherdep-dep_2' }
          ]
        )

        expect(client).to receive(:get).with('stacks/test-grid/otherdep-dep_1').and_return(
          'variables' => { 'dep1var' => 'test' },
          'children' => [
            { 'name' => 'otherdep-dep_1-dep_1' }
          ]
        )

        expect(client).to receive(:get).with('stacks/test-grid/otherdep-dep_1-dep_1').and_return(
          'variables' => { 'dep1dep1var' => 'test11' },
          'children' => []
        )

        expect(client).to receive(:get).with('stacks/test-grid/otherdep-dep_2').and_return(
          'variables' => { 'dep2var' => 'test2' },
          'children' => [ ]
        )

        expect(Kontena).to receive(:run!).with([
          "stack", "install", "-n", "deptest-dep_1", "--parent-name", "deptest", "--no-deploy",
          '-v', 'dep1var=test', '-v', 'dep_1.dep1dep1var=test11', fixture_path('stack-with-dependencies-dep-1.yml')
        ]).and_return(true)
        expect(Kontena).to receive(:run!).with([
          "stack", "install", "-n", "deptest-dep_2", "--parent-name", "deptest", "--no-deploy",
          '-v', 'dep_var=1', '-v', 'dep2var=test2', fixture_path('stack-with-dependencies-dep-2.yml')
        ]).and_return(true)

        allow(client).to receive(:post).and_return({})

        subject.run(['--values-from-stack', 'otherdep', '-n', 'deptest', '--no-deploy', fixture_path('stack-with-dependencies.yml')])
      end
    end

    describe '-v' do
      it 'sends stack to master, loading values from command line' do
        expect(client).to receive(:post) do |path, data|
          expect(path).to eq 'grids/test-grid/stacks'
          expect(data).to match hash_including(expectation)
        end.and_return({})
        YAML.dump('TEST_ENV_VAR' => 'abc', 'MYSQL_IMAGE' => 'mysqlimage', 'tag' => 'def')
        subject.run(['--no-deploy', '-v', 'TEST_ENV_VAR=abc', '-v', 'MYSQL_IMAGE=mysqlimage', '-v', 'tag=def', fixture_path('stack-with-variables.yml')])
      end
    end

    describe 'combination of value inputs' do
      it 'can combine multiple inputs' do
        allow(File).to receive(:exist?).with('values.yml').and_return(true)
        expect(File).to receive(:read).with('values.yml').and_return(
          YAML.dump('TEST_ENV_VAR' => 'abc')
        )
        allow(client).to receive(:get).with('stacks/test-grid/foostack').and_return(
          'variables' => { 'MYSQL_IMAGE' => 'mysqlimage' }
        )
        expect(client).to receive(:post) do |path, data|
          expect(path).to eq 'grids/test-grid/stacks'
          expect(data).to match hash_including(expectation)
        end.and_return({})
        subject.run(['--no-deploy', '--values-from-stack', 'foostack', '--values-from', 'values.yml', '-v', 'tag=def', fixture_path('stack-with-variables.yml')])
      end
    end
  end
end
