# Copyright (c) Facebook, Inc. and its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'chefspec'
require_relative '../libraries/cpe_helpers'
require_relative '../../cpe_logger/libraries/cpe_log'

describe CPE::Helpers do
  context 'When on Linux' do
    before do
      allow(CPE::Helpers).to receive(:macos?).and_return(false)
      allow(CPE::Helpers).to receive(:linux?).and_return(true)
      allow(CPE::Helpers).to receive(:windows?).and_return(false)
      allow(CPE::Log).to receive(:log)
      allow(CPE::Helpers).to receive(:machine_owner).
        and_return('from_machine_owner')
      allow(::File).to receive(:exist?).and_call_original
      allow(::File).to receive(:exist?).with('/usr/bin/loginctl').
        and_return(true)
      allow(::File).to receive(:exist?).with('/bin/loginctl').
        and_return(false)
    end

    context 'When loginctl does not exist' do
      before do
        CPE::Helpers.instance_variable_set :@console_user, nil
        CPE::Helpers.instance_variable_set :@loginctl_users, nil
        allow(::File).to receive(:exist?).with('/usr/bin/loginctl').
          and_return(false)
      end
      it 'console_user returns machine owner' do
        expect(CPE::Helpers.console_user).to eq('from_machine_owner')
      end
    end

    context 'When loginctl returns no end-user' do
      context 'When shell_out fails' do
        before do
          CPE::Helpers.instance_variable_set :@console_user, nil
          CPE::Helpers.instance_variable_set :@loginctl_users, nil
          allow(CPE::Helpers).to receive(:shell_out).
            with('/usr/bin/loginctl --no-legend list-users').and_return(
              double(
                :error? => true,
              ),
            )
        end
        it 'loginctl_users should return []' do
          expect(CPE::Helpers.loginctl_users).to eq([])
        end

        it 'console_user should return machine owner' do
          expect(CPE::Helpers.console_user).to eq('from_machine_owner')
        end
      end

      context 'When shell_out returns only gdm' do
        before do
          CPE::Helpers.instance_variable_set :@console_user, nil
          CPE::Helpers.instance_variable_set :@loginctl_users, nil
          allow(CPE::Helpers).to receive(:shell_out).
            with('/usr/bin/loginctl --no-legend list-users').and_return(
              double(
                :error? => false,
                :stdout => " 42 gdm \n",
              ),
            )
        end

        it 'loginctl_users should return a list with gdm' do
          expect(CPE::Helpers.loginctl_users).to eq(
            [{ 'uid' => 42, 'user' => 'gdm' }],
          )
        end

        it 'console_user should return machine owner' do
          expect(CPE::Helpers.console_user).to eq('from_machine_owner')
        end
      end

      context 'When shell_out returns only an end user' do
        before do
          CPE::Helpers.instance_variable_set :@console_user, nil
          CPE::Helpers.instance_variable_set :@loginctl_users, nil
          allow(CPE::Helpers).to receive(:shell_out).
            with('/usr/bin/loginctl --no-legend list-users').and_return(
              double(
                :error? => false,
                :stdout => "1001 endusr\n",
              ),
            )
        end

        it 'loginctl_users should return a list with endusr' do
          expect(CPE::Helpers.loginctl_users).to eq(
            [{ 'uid' => 1001, 'user' => 'endusr' }],
          )
        end

        it 'console_user should return endusr' do
          expect(CPE::Helpers.console_user).to eq('endusr')
        end
      end

      context 'When shell_out returns gdm and an end user' do
        before do
          CPE::Helpers.instance_variable_set :@console_user, nil
          CPE::Helpers.instance_variable_set :@loginctl_users, nil
          allow(CPE::Helpers).to receive(:shell_out).
            with('/usr/bin/loginctl --no-legend list-users').and_return(
              double(
                :error? => false,
                :stdout =>
                  "      1001 endusr          \n" +
                  "        42 gdm             \n",
              ),
            )
        end

        it 'loginctl_users should return a list with gdm and endusr' do
          require 'set'
          expect(CPE::Helpers.loginctl_users.to_set).to eq(
            [{ 'uid' => 42, 'user' => 'gdm' },
             { 'uid' => 1001, 'user' => 'endusr' }].to_set,
          )
        end

        it 'console_user should return endusr' do
          expect(CPE::Helpers.console_user).to eq('endusr')
        end
      end
    end
  end

  context 'When on Windows' do
    context '#logged_on_user_query' do
      subject { CPE::Helpers }

      NO_USER = <<-'EOF'.freeze
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
        EOF
      ONE_USER = <<-'EOF'.freeze
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
>testdummy             console             1  Active      none   8/31/2020 12:22 PM
        EOF
      TWO_USERS = <<-'EOF'.freeze
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
testdummy              console             1  Active      none   8/31/2020 12:22 PM
testdummy2             console             1  Active      none   8/31/2020 18:00 PM
        EOF
      INACTIVE_USERS = <<-'EOF'.freeze
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
testdummy              console             1  Inactive    none   8/31/2020 12:22 PM
testdummy2             console             1  Active      none   8/31/2020 18:00 PM
        EOF

      MISSING_FIELD = <<-'EOF'.freeze
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
testdummy              console             1
        EOF
      NO_ACTIVE_USERS = <<-'EOF'.freeze
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
testdummy              console             1  Inactive    none   8/31/2020 12:22 PM
testdummy2             console             1  Inactive    none   8/31/2020 18:00 PM
        EOF

      OUTPUT_DIFFERENT_LANG = <<-'EOF'.freeze
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
>testdummy             console             1  Actif      aucun   8/31/2020 12:22 PM
        EOF

      before do
        allow(Chef::Log).to receive(:debug)
      end

      it 'should return nil if there is an exception' do
        allow(subject).to receive(:shell_out).and_raise(NoMethodError, 'aieee')
        expect(subject.logged_on_user_query).to be nil
      end

      it 'should return nil if the command output is empty' do
        result = double(:stdout => '')
        allow(subject).to receive(:shell_out).and_return(result)
        expect(subject.logged_on_user_query).to be nil
      end

      it 'should return nil if the output does not contain a user' do
        result = double(:stdout => NO_USER)
        allow(subject).to receive(:shell_out).and_return(result)
        expect(subject.logged_on_user_query).to be nil
      end

      it 'should return nil if the command times out' do
        allow(subject).to receive(:shell_out).and_raise(
          Mixlib::ShellOut::CommandTimeout,
        )
        allow(Chef::Log).to receive(:warn)
        expect(Chef::Log).to receive(:warn).
          with('command timeout while looking up user').
          once
        expect(subject.logged_on_user_query).to be nil
      end

      {
        'should return nil if a field is missing for whatever reason' =>
          { 'output' => MISSING_FIELD },
        'should return nil if no active users' =>
          { 'output' => NO_ACTIVE_USERS },
        'should return nil when output is in different language' =>
          { 'output' => OUTPUT_DIFFERENT_LANG },
      }.each do |test_name, test_data|
        it test_name do
          result = double(:stdout => test_data['output'])
          allow(subject).to receive(:shell_out).and_return(result)
          expect(subject.logged_on_user_query).to be nil
        end
      end

      {
        'should return testdummy' =>
          { 'output' => ONE_USER, 'expected' => 'testdummy' },
        'should return testdummy when two users are present' =>
          { 'output' => TWO_USERS, 'expected' => 'testdummy' },
        'should return testdummy2 when testdummy is inactive' =>
          { 'output' => INACTIVE_USERS, 'expected' => 'testdummy2' },
      }.each do |test_name, test_data|
        it test_name do
          result = double(:stdout => test_data['output'])
          allow(subject).to receive(:shell_out).and_return(result)
          expect(subject.logged_on_user_query).to eql test_data['expected']
        end
      end

      it 'last_logged_on_user_from_registry should return nil if
      the output does not contain LastLoggedOnUser' do
        allow(subject).to receive(:logged_on_user_registry).and_return({})
        expect(subject.last_logged_on_user_from_registry).to be nil
      end

      it 'last_logged_on_user_from_registry should return nil if there is
      an exception ' do
        allow(subject).to receive(:logged_on_user_registry).and_raise(
          NoMethodError, 'blah'
        )
        expect(subject.last_logged_on_user_from_registry).to be nil
      end

      logged_on_user_registry_output = {
        'LastLoggedOnSAMUser'=>'THEFACEBOOK\\testdummy',
        'LastLoggedOnUser'=>'THEFACEBOOK\\testdummy',
        'LastLoggedOnUserSID'=>'S-1-5-21-4286658467',
        'SelectedUserSID'=>'S-1-5-21-4286658467',
      }
      it 'last_logged_on_user_from_registry should parse a valid user ' do
        allow(subject).to receive(:logged_on_user_registry).and_return(
          logged_on_user_registry_output,
        )
        expect(subject.last_logged_on_user_from_registry).to eql 'testdummy'
      end
    end
  end
end
