---
- name: sample config for traceback behavior
  set_fact:
    traceback_on_error: |
      {{ lookup('ansible._protomatter.config', 'DISPLAY_TRACEBACK') is contains 'error' }}

- name: fetch current target date/time for log filtering
  raw: '[datetime]::now | Out-String'
  register: test_starttime

- name: test normal module execution
  test_fail:
  register: normal

- name: assert test normal module execution
  assert:
    that:
    - not normal is failed

- name: test fail module execution
  test_fail:
    data: fail
  register: fail_module
  ignore_errors: yes

- name: assert test fail module execution
  assert:
    that:
    - fail_module is failed
    - fail_module.msg == "fail message"
    - fail_module.exception is defined

- name: test module with exception thrown
  test_fail:
    data: throw
  register: throw_module
  ignore_errors: yes

- name: assert test module with exception thrown
  assert:
    that:
    - throw_module is failed
    - 'throw_module.msg == "Unhandled exception while executing module: module is thrown"'
    - not traceback_on_error or '"throw [ArgumentException]\"module is thrown\"" in throw_module.exception'
    - not traceback_on_error or throw_module.exception is search "at <ScriptBlock>, ansible.modules.test_fail.ps1. line 39"

- name: test module with error msg
  test_fail:
    data: error
  register: error_module
  ignore_errors: yes
  vars:
    # Running with coverage means the module is run from a script and not as a psuedo script in a pipeline. This
    # results in a different error message being returned so we disable coverage collection for this task.
    _ansible_coverage_remote_output: ''

- name: assert test module with error msg
  assert:
    that:
    - error_module is failed
    - 'error_module.msg == "Unhandled exception while executing module: error"'
    - not traceback_on_error or "Write-Error -Message $data" in error_module.exception

- name: test module with cmdlet error
  test_fail:
    data: cmdlet_error
  register: cmdlet_error
  ignore_errors: yes

- name: assert test module with cmdlet error
  assert:
    that:
    - cmdlet_error is failed
    - 'cmdlet_error.msg == "Unhandled exception while executing module: Cannot find drive. A drive with the name ''fake'' does not exist."'
    - not traceback_on_error or "Get-Item -Path \"fake:\\path\"" in cmdlet_error.exception

- name: test module with .NET exception
  test_fail:
    data: dotnet_exception
  register: dotnet_exception
  ignore_errors: yes

- name: assert test module with .NET exception
  assert:
    that:
    - dotnet_exception is failed
    - 'dotnet_exception.msg == "Unhandled exception while executing module: Exception calling \"GetFullPath\" with \"1\" argument(s): \"The path is not of a legal form.\""'
    - not traceback_on_error or "[System.IO.Path]::GetFullPath($null)" in dotnet_exception.exception

- name: test module with function exception
  test_fail:
    data: function_throw
  register: function_exception
  ignore_errors: yes
  vars:
    _ansible_coverage_remote_output: ''

- name: assert test module with function exception
  assert:
    that:
    - function_exception is failed
    - 'function_exception.msg == "Unhandled exception while executing module: exception in function"'
    - 'not traceback_on_error or "throw \"exception in function\"" in function_exception.exception'
    - 'not traceback_on_error or "at Test-ThrowException, ansible.modules.test_fail.ps1: line" in function_exception.exception'

- name: test module with fail process but Exit-Json
  test_fail:
    data: proc_exit_fine
  register: proc_exit_fine

- name: assert test module with fail process but Exit-Json
  assert:
    that:
    - not proc_exit_fine is failed

- name: test module with fail process but Fail-Json
  test_fail:
    data: proc_exit_fail
  register: proc_exit_fail
  ignore_errors: yes

- name: assert test module with fail process but Fail-Json
  assert:
    that:
    - proc_exit_fail is failed
    - proc_exit_fail.msg == "proc_exit_fail"
    - proc_exit_fail.exception is defined

- name: test out invalid options
  test_invalid_requires:
  register: invalid_options

- name: assert test out invalid options
  assert:
    that:
    - invalid_options is successful
    - invalid_options.output == "output"

- name: test out invalid os version
  test_min_os_version:
  register: invalid_os_version
  ignore_errors: yes

- name: assert test out invalid os version
  assert:
    that:
    - invalid_os_version is failed
    - '"This module cannot run on this OS as it requires a minimum version of 20.0, actual was " in invalid_os_version.msg'

- name: test out invalid powershell version
  test_min_ps_version:
  register: invalid_ps_version
  ignore_errors: yes

- name: assert test out invalid powershell version
  assert:
    that:
    - invalid_ps_version is failed
    - '"This module cannot run as it requires a minimum PowerShell version of 20.0.0.0, actual was " in invalid_ps_version.msg'

- name: test out environment block for task
  win_shell: set
  args:
    executable: cmd.exe
  environment:
    String: string value
    Int: 1234
    Bool: True
    double_quote: 'double " quote'
    single_quote: "single ' quote"
    hyphen-var: abc@123
    '_-(){}[]<>*+-/\?"''!@#$%^&|;:i,.`~0': '_-(){}[]<>*+-/\?"''!@#$%^&|;:i,.`~0'
    '‘key': 'value‚'
  register: environment_block

- name: assert environment block for task
  assert:
    that:
    - '"String=string value" in environment_block.stdout_lines'
    - '"Int=1234" in environment_block.stdout_lines'
    - '"Bool=True" in environment_block.stdout_lines'
    - '"double_quote=double \" quote" in environment_block.stdout_lines'
    - '"single_quote=single '' quote" in environment_block.stdout_lines'
    - '"hyphen-var=abc@123" in environment_block.stdout_lines'
    # yaml escaping rules - (\\ == \), (\" == "), ('' == ')
    - '"_-(){}[]<>*+-/\\?\"''!@#$%^&|;:i,.`~0=_-(){}[]<>*+-/\\?\"''!@#$%^&|;:i,.`~0" in environment_block.stdout_lines'
    - '"‘key=value‚" in environment_block.stdout_lines'

- name: test out become requires without become_user set
  test_all_options:
  register: become_system

- name: assert become requires without become_user set
  assert:
    that:
    - become_system is successful
    - become_system.output == "S-1-5-18"

- set_fact:
    become_test_username: ansible_become_test
    gen_pw: "{{ 'password123!' + lookup('password', '/dev/null chars=ascii_letters,digits length=8') }}"

- name: execute tests and ensure that test user is deleted regardless of success/failure
  block:
  - name: create unprivileged user
    win_user:
      name: "{{ become_test_username }}"
      password: "{{ gen_pw }}"
      update_password: always
      groups: Users
    register: become_test_user_result

  - name: create tempdir for test user
    win_file:
      path: C:\Windows\TEMP\test-dir
      state: directory

  - name: deny delete permissions on new temp dir for test user
    win_acl:
      path: C:\Windows\TEMP\test-dir
      user: '{{ become_test_user_result.sid }}'
      type: '{{ item.type }}'
      rights: '{{ item.rights }}'
    loop:
    - type: allow
      rights: ListDirectory, CreateFiles, CreateDirectories, ReadAttributes, ReadExtendedAttributes, WriteData, WriteAttributes, WriteExtendedAttributes, Synchronize
    - type: deny
      rights: DeleteSubdirectoriesAndFiles, Delete

  - name: ensure current user is not the become user
    win_shell: whoami
    register: whoami_out

  - name: verify output
    assert:
      that:
      - not whoami_out.stdout_lines[0].endswith(become_test_username)

  - name: get become user profile dir so we can clean it up later
    vars: &become_vars
      ansible_become_user: "{{ become_test_username }}"
      ansible_become_password: "{{ gen_pw }}"
      ansible_become_method: runas
      ansible_become: yes
    win_shell: $env:USERPROFILE
    register: profile_dir_out

  - name: ensure profile dir contains test username (eg, if become fails silently, prevent deletion of real user profile)
    assert:
      that:
      - become_test_username in profile_dir_out.stdout_lines[0]

  - name: test out become requires when become_user set
    test_all_options:
    vars: *become_vars
    register: become_system

  - name: assert become requires when become_user set
    assert:
      that:
      - become_system is successful
      - become_system.output == become_test_user_result.sid

  - name: run module with tempdir with no delete access
    win_ping:
    register: temp_deletion_warning
    vars:
      <<: *become_vars
      ansible_remote_tmp: C:\Windows\TEMP\test-dir

  - name: assert warning about tmpdir deletion is present
    assert:
      that:
      - temp_deletion_warning.warnings | count == 1
      - >-
        temp_deletion_warning.warnings[0] is
        regex("(?i).*Failed to cleanup temporary directory 'C:\\\\Windows\\\\TEMP\\\\test-dir\\\\.*' used for compiling C# code\\. Files may still be present after the task is complete\\..*")

  always:
  - name: ensure test user is deleted
    win_user:
      name: "{{ become_test_username }}"
      state: absent

  - name: ensure test user profile is deleted
    # NB: have to work around powershell limitation of long filenames until win_file fixes it
    win_shell: rmdir /S /Q {{ profile_dir_out.stdout_lines[0] }}
    args:
      executable: cmd.exe
    when: become_test_username in profile_dir_out.stdout_lines[0] | default("")

  - name: remove test tempdir
    win_file:
      path: C:\Windows\TEMP\test-dir
      state: absent

- name: get PS events containing module args or envvars created since test start
  raw: |
    $dt=[datetime]"{{ test_starttime.stdout|trim }}"
    (Get-WinEvent -LogName Microsoft-Windows-Powershell/Operational |
    ? { $_.TimeCreated -ge $dt -and $_.Message -match "fail_module|hyphen-var" }).Count
  register: ps_log_count

- name: assert no PS events contain module args or envvars
  assert:
    that:
    - ps_log_count.stdout | int == 0

- name: test module that sets HadErrors with no error records
  test_rc_1:
  register: module_had_errors

- name: assert test module that sets HadErrors with no error records
  assert:
    that:
    - module_had_errors.rc == 0

- name: test module cannot access exec wrapper scope
  test_exec_wrapper_scope:
  register: exec_wrapper_scope

- name: assert test module cannot access exec wrapper scope
  assert:
    that:
    - exec_wrapper_scope.module_using_namespace == 'System.Management.Automation.Language.Parser'
    - exec_wrapper_scope.missing_using_namespace == True
    - exec_wrapper_scope.script_var == 'foo'
    - exec_wrapper_scope.util_res.module_using_namespace == 'System.Security.Cryptography.X509Certificates.X509Certificate2'
    - exec_wrapper_scope.util_res.missing_using_namespace == True
    - exec_wrapper_scope.util_res.script_var == 'bar'

- name: test module without any util references
  test_no_utils:
    foo: bar
  register: no_utils_res

- name: assert test module without any util references
  assert:
    that:
    - no_utils_res is not changed
    - "no_utils_res.complex_args == {'foo': 'bar'}"
