# frozen_string_literal: true

describe Gitlab::QA::Support::ShellCommand do
  describe 'execute!' do
    context 'when masking secrets' do
      let(:wait_thread) { instance_double(Thread) }
      let(:errored_wait) { instance_double(Process::Status, exited?: true, exitstatus: 1) }
      let(:non_errored_wait) { instance_double(Process::Status, exited?: true, exitstatus: 0) }
      let(:stdin) { StringIO.new }
      let(:stdout) { [+'logged in as ***** with password *****'] }
      let(:logger) { Gitlab::QA::Runtime::Logger.logger }
      let(:command) { +'login -u user -p secret' }

      before do
        Rainbow.enabled = false
        allow(Open3).to receive(:popen2e).and_yield(stdin, stdout, wait_thread)
        allow(Gitlab::QA::Runtime::Logger).to receive(:logger).and_return(logger)
      end

      subject(:shell_command) { described_class.new(command, mask_secrets: %w[secret user]) }

      it 'masks secrets when logging the command itself' do
        expect(logger).to receive(:info).with('Shell command: `login -u ***** -p *****`')
        expect(wait_thread).to receive(:value).twice.and_return(non_errored_wait)

        shell_command.execute!
      end

      it 'masks command secrets on CommandError' do
        expect(wait_thread).to receive(:value).twice.and_return(errored_wait)

        expect { shell_command.execute! }
          .to raise_error(Gitlab::QA::Support::ShellCommand::StatusError, /Command `login -u \*{5} -p \*{5}` failed/)
      end

      it 'masks secrets when yielding output' do
        expect(wait_thread).to receive(:value).twice.and_return(non_errored_wait)

        shell_command.execute! do |output|
          expect(output).not_to be_nil
          expect(output).to eql('logged in as ***** with password *****')
        end
      end

      it 'masks secrets in debug logs' do
        expect(logger).to receive(:debug).with(/Shell command output:\nlogged in as \*{5} with password \*{5}/)
        expect(wait_thread).to receive(:value).twice.and_return(non_errored_wait)

        shell_command.execute!
      end

      it 'masks secrets in error logs' do
        expect(logger).to receive(:error).with(/Shell command output:\nlogged in as \*{5} with password \*{5}/)
        expect(wait_thread).to receive(:value).twice.and_return(errored_wait)

        expect { shell_command.execute! }.to raise_error(Gitlab::QA::Support::ShellCommand::StatusError)
      end

      context 'when there is no secret' do
        let(:command) { +'docker pull ruby:3' }

        it 'returns the original string' do
          expect(wait_thread).to receive(:value).twice.and_return(errored_wait)

          expect { shell_command.execute! }
            .to raise_error(Gitlab::QA::Support::ShellCommand::StatusError, /Command `docker pull ruby:3` failed/)
        end

        context 'with any other docker command other than attach' do
          let(:command) { 'docker exec' }

          it 'doesnt log' do
            expect(wait_thread).to receive(:value).twice.and_return(errored_wait)
            expect(logger).to receive(:error).with(/Shell command output:\nlogged in as \*{5} with password \*{5}/)

            expect { shell_command.execute! }
            .to raise_error(Gitlab::QA::Support::ShellCommand::StatusError, /Command `docker exec` failed/)
          end
        end

        context 'for sig docker command' do # rubocop:disable RSpec/ContextWording
          let(:command) { 'docker attach --sig-proxy=true' }

          it 'does log' do
            expect(wait_thread).to receive(:value).twice.and_return(errored_wait)
            expect(logger).not_to receive(:error).with(/Shell command output:\nlogged in as \*{5} with password \*{5}/)
            expect { shell_command.execute! }
            .to raise_error(Gitlab::QA::Support::ShellCommand::StatusError,
              /Command `docker attach --sig-proxy=true` failed/
            )
          end
        end
      end
    end
  end
end
