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

- name: fetch host information
  get_info:
  register: test_host_info

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

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

- name: test exec_wrapper with async
  test_fail:
  async: 60
  poll: 5

- name: test out Windows specific exec wrapper tests
  include_tasks: windows.yml
  when: test_host_info.is_windows

- 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.legacy.test_fail.ps1. line 47"

- 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 pseudo 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 is contains("Unhandled exception while executing module: Exception calling \"GetFullPath\" with \"1\" argument(s): ")'
    - 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.legacy.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 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: define common environment variables for all tasks
  set_fact:
    common_env_ars:
      String: string value
      Int: 1234
      Bool: True
      double_quote: 'double " quote'
      single_quote: "single ' quote"

- name: define Windows specific environment variables
  set_fact:
    windows_env_ars:
      hyphen-var: abc@123
      '_-(){}[]<>*+-/\?"''!@#$%^&|;:i,.`~0': '_-(){}[]<>*+-/\?"''!@#$%^&|;:i,.`~0'
      '‘key': 'value‚'

# These env vars don't work with the shell inline commands so we only test
# them with Windows.
- name: test out environment block for task
  get_env:
  environment: '{{ common_env_ars | combine(windows_env_ars if test_host_info.is_windows else {}) }}'
  register: environment_block

- name: test out environment block for task with async
  get_env:
  environment: '{{ common_env_ars | combine(windows_env_ars if test_host_info.is_windows else {}) }}'
  register: environment_block_async
  async: 60
  poll: 5

- name: assert environment block for task
  assert:
    that:
    - '"String=string value" in environment_block.env_vars'
    - '"String=string value" in environment_block_async.env_vars'
    - '"Int=1234" in environment_block.env_vars'
    - '"Int=1234" in environment_block_async.env_vars'
    - '"Bool=True" in environment_block.env_vars'
    - '"Bool=True" in environment_block_async.env_vars'
    - '"double_quote=double \" quote" in environment_block.env_vars'
    - '"double_quote=double \" quote" in environment_block_async.env_vars'
    - '"single_quote=single '' quote" in environment_block.env_vars'
    - '"single_quote=single '' quote" in environment_block_async.env_vars'

- name: assert environment block for task on Windows
  assert:
    that:
    - '"hyphen-var=abc@123" in environment_block.env_vars'
    - '"hyphen-var=abc@123" in environment_block_async.env_vars'
    # yaml escaping rules - (\\ == \), (\" == "), ('' == ')
    - '"_-(){}[]<>*+-/\\?\"''!@#$%^&|;:i,.`~0=_-(){}[]<>*+-/\\?\"''!@#$%^&|;:i,.`~0" in environment_block.env_vars'
    - '"_-(){}[]<>*+-/\\?\"''!@#$%^&|;:i,.`~0=_-(){}[]<>*+-/\\?\"''!@#$%^&|;:i,.`~0" in environment_block_async.env_vars'
    - '"‘key=value‚" in environment_block.env_vars'
    - '"‘key=value‚" in environment_block_async.env_vars'
  when: test_host_info.is_windows

- 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'

- name: test module with invalid syntax
  test_invalid_syntax:
  register: invalid_syntax
  ignore_errors: true

- name: assert test module with invalid syntax
  assert:
    that:
    - invalid_syntax is failed
    - invalid_syntax.msg is contains("Failed to parse script 'ansible.legacy.test_invalid_syntax.ps1'")

- name: test module with invalid module_util syntax
  test_invalid_util_syntax:
  register: invalid_util_syntax
  ignore_errors: true

- name: assert test module with invalid module_util syntax
  assert:
    that:
    - invalid_util_syntax is failed
    - invalid_util_syntax.msg is contains("Failed to parse script 'Ansible.ModuleUtils.InvalidSyntax.psm1'")

- name: check if pipelining is supported on connection
  set_fact:
    connection_pipelining: |
      {{ lookup('config', 'pipelining', plugin_type='connection', plugin_name=ansible_connection, on_missing='skip') }}

# Needs to be define outside the block, will emit a warning
# if the block gets skipped as the name for each task fails templating
- name: determine if next tests are with or without pipelining
  set_fact:
    operation: '{{ connection_pipelining | ternary("without", "with") }}'

- when: connection_pipelining != []  # on_missing='skip' results in []
  block:
  - name: run pwsh module {{ operation }} pipelining
    test_fail:
    vars:
      ansible_pipelining: '{{ not connection_pipelining }}'

  - name: run async pwsh module {{ operation }} pipelining
    test_fail:
    async: 60
    poll: 5
    vars:
      ansible_pipelining: '{{ not connection_pipelining }}'
