- hosts: testhost
  gather_facts: no
  tasks:
  - name: skip this test for non-root users
    block:
      - debug:
          msg: Skipping sudo test for non-root user.
      - meta: end_play
    when: lookup('pipe', 'whoami') != 'root'

  - name: attempt root-to-root sudo become (no-op, should succeed)
    vars:
      ansible_become: yes
      ansible_become_user: root
      ansible_become_method: sudo
    raw: whoami
    register: root_noop_sudo

  - assert:
      that:
        - root_noop_sudo.stdout is contains "root"

  - name: create an unprivileged become user pair
    include_role:
      name: setup_become_user_pair
      public: true

  - name: deploy sudo shim
    copy:
      src: sudoshim.sh
      dest: /tmp/sudoshim.sh
      mode: u+x

  - set_fact:
      default_sudo_flags: "{{ lookup('config', 'become_flags', plugin_type='become', plugin_name='sudo') }}"

  - name: apply shared become vars to all tasks that use the sudo test shim
    vars:
      ansible_become: yes
      ansible_become_method: sudo
      ansible_become_user: '{{ target_user_name }}'
      ansible_become_password: '{{ target_user_password }}'
      ansible_become_exe: /tmp/sudoshim.sh
      ansible_local_become_strip_preamble: true
      intermediate: "{{ ((' --intermediate-user ' + intermediate_user_name) if intermediate_user_name is defined else '') }}"
    block:
      - name: verify stdout/stderr noise is present with preamble strip disabled
        raw: echo $(whoami) ran
        vars:
          ansible_become_flags: "{{ default_sudo_flags ~ intermediate ~ ' --inject-stdout-noise --inject-stderr-noise' }}"
          ansible_become_password: "{{ intermediate_user_password }}"
          ansible_local_become_strip_preamble: false
          ansible_pipelining: true
        register: preamble_visible

      - name: verify stdout/stderr noise is stripped with preamble strip enabled
        raw: echo $(whoami) ran
        vars:
          ansible_become_flags: "{{ default_sudo_flags ~ intermediate ~ ' --inject-stdout-noise --inject-stderr-noise' }}"
          ansible_become_password: "{{ intermediate_user_password }}"
          ansible_pipelining: true
        register: preamble_stripped

      - assert:
          that:
            - preamble_visible.stdout is contains(target_user_name ~ " ran")
            - preamble_stripped.stdout is contains(target_user_name ~ " ran")
            - preamble_visible.stdout is contains "stdout noise"
            - preamble_stripped.stdout is not contains "stdout noise"
            - preamble_visible.stderr is contains "stderr noise"
            - preamble_stripped.stderr is not contains "stderr noise"

      - name: verify sudo succeeds with a password (no PTY/pipelined)
        raw: echo $(whoami) ran
        vars:
          ansible_become_flags: "{{ default_sudo_flags ~ intermediate }}"
          ansible_become_password: "{{ intermediate_user_password }}"
          ansible_local_become_strip_preamble: false  # allow prompt sniffing from the output
          ansible_pipelining: true
        register: success_pipelined

      - assert:
          that:
            - success_pipelined.stdout is contains(target_user_name ~ " ran")
            - success_pipelined.stderr is search 'sudo via ansible.*password\:'

      - name: verify sudo works with a PTY allocated (pipelining disabled)
        raw: echo $(whoami) ran without pipelining
        vars:
          ansible_become_flags: "{{ default_sudo_flags ~ intermediate }}"
          ansible_become_password: "{{ intermediate_user_password }}"
          ansible_local_become_strip_preamble: false
          ansible_pipelining: no  # a PTY is allocated by the local plugin only when pipelining is disabled
        register: pty_non_pipelined

      - assert:
          that:
            - pty_non_pipelined.stdout is contains(test_user_name ~ " ran without pipelining")

      - name: verify early-closed stderr still sees success
        # this test triggers early EOF (which unregisters the associated selector) on most OSs, but not on Ubuntu 24.04
        vars:
          ansible_become_flags: --close-stderr --sleep-before-sudo --pretend-to-be-sudo
          ansible_local_become_success_timeout: 5
        raw: echo ran_ok
        register: stderr_closed

      - assert:
          that:
            - stderr_closed.stderr is contains "some injected stderr, EOF now"
            - stderr_closed.stdout is contains "ran_ok"

      - name: verify timeout handling by setting a sudo prompt that won't trigger password send
        vars:
          ansible_become_flags: "{{ default_sudo_flags ~ intermediate }} --bogus-prompt"
          ansible_local_become_success_timeout: 2
        raw: exit 99
        ignore_unreachable: true
        register: prompt_timeout

      - assert:
          that:
            - prompt_timeout is unreachable
            - prompt_timeout.msg is contains "Timed out waiting for become success or become password prompt"
            - prompt_timeout.msg is contains "BOGUSPROMPT"
            - prompt_timeout.rc is not defined

      - name: verify sub 1s timeout is always increased
        vars:
          ansible_become_flags: "{{ default_sudo_flags ~ ' --sleep-before-sudo' }}"
          ansible_local_become_success_timeout: 0  # a 0s timeout would always cause select to be skipped in the current impl, but added a 2s sleep in the shim in case that changes
        raw: whoami
        register: timeout_increased

      - assert:
          that:
            - timeout_increased.stdout is contains target_user_name

      - name: verify handling of premature exit/stream closure
        vars:
          ansible_become_exe: /bogus
        raw: exit 99
        ignore_errors: true
        register: early_close

      - assert:
          that:
            - early_close is failed
            - early_close.msg is contains "Premature end of stream"

      - name: verify lack of required password fails as expected
        raw: exit 99
        vars:
          ansible_become_flags: "{{ default_sudo_flags ~ intermediate }}"
          ansible_become_password: ~
        ignore_errors: true
        register: missing_required_password

      - assert:
          that:
            - missing_required_password is failed
            - missing_required_password.msg is contains "password is required"

      - name: verify duplicate password prompts are handled (due to incorrect password)
        raw: echo hi mom
        vars:
          ansible_become_flags: "{{ default_sudo_flags ~ intermediate }}"
          ansible_become_password: not_the_correct_password
        ignore_errors: yes
        register: incorrect_password

      - assert:
          that:
            - incorrect_password is failed
            - incorrect_password.msg is contains "Duplicate become password prompt encountered"

      - name: no error, but no become success message
        vars:
          ansible_become_flags: --pretend-to-be-broken-sudo  # handle password prompt, but return no output
        raw: exit 99  # should never actually run anyway
        ignore_errors: true
        register: no_become_success

      - assert:
          that:
            - no_become_success is failed
            - no_become_success.msg is contains "Premature end of stream waiting for become success"

      - name: test broken passwordless sudo
        raw: echo hi mom
        vars:
          ansible_become_flags: --pretend-to-be-broken-passwordless-sudo
        ignore_errors: yes
        register: broken_passwordless_sudo

      - assert:
          that:
            - broken_passwordless_sudo is failed
            - broken_passwordless_sudo.msg is contains "not a module response"
