summaryrefslogtreecommitdiffstats
path: root/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
diff options
context:
space:
mode:
Diffstat (limited to 'test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1')
-rw-r--r--test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps13206
1 files changed, 3206 insertions, 0 deletions
diff --git a/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1 b/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
new file mode 100644
index 0000000..cfa73c6
--- /dev/null
+++ b/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
@@ -0,0 +1,3206 @@
+#!powershell
+
+#AnsibleRequires -CSharpUtil Ansible.Basic
+
+$module = [Ansible.Basic.AnsibleModule]::Create($args, @{})
+
+Function Assert-Equal {
+ param(
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)][AllowNull()]$Actual,
+ [Parameter(Mandatory = $true, Position = 0)][AllowNull()]$Expected
+ )
+
+ process {
+ $matched = $false
+ if ($Actual -is [System.Collections.ArrayList] -or $Actual -is [Array]) {
+ $Actual.Count | Assert-Equal -Expected $Expected.Count
+ for ($i = 0; $i -lt $Actual.Count; $i++) {
+ $actual_value = $Actual[$i]
+ $expected_value = $Expected[$i]
+ Assert-Equal -Actual $actual_value -Expected $expected_value
+ }
+ $matched = $true
+ }
+ else {
+ $matched = $Actual -ceq $Expected
+ }
+
+ if (-not $matched) {
+ if ($Actual -is [PSObject]) {
+ $Actual = $Actual.ToString()
+ }
+
+ $call_stack = (Get-PSCallStack)[1]
+ $module.Result.failed = $true
+ $module.Result.test = $test
+ $module.Result.actual = $Actual
+ $module.Result.expected = $Expected
+ $module.Result.line = $call_stack.ScriptLineNumber
+ $module.Result.method = $call_stack.Position.Text
+ $module.Result.msg = "AssertionError: actual != expected"
+
+ Exit-Module
+ }
+ }
+}
+
+Function Assert-DictionaryEqual {
+ param(
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)][AllowNull()]$Actual,
+ [Parameter(Mandatory = $true, Position = 0)][AllowNull()]$Expected
+ )
+
+ process {
+ $actual_keys = $Actual.Keys
+ $expected_keys = $Expected.Keys
+
+ $actual_keys.Count | Assert-Equal -Expected $expected_keys.Count
+ foreach ($actual_entry in $Actual.GetEnumerator()) {
+ $actual_key = $actual_entry.Key
+ ($actual_key -cin $expected_keys) | Assert-Equal -Expected $true
+ $actual_value = $actual_entry.Value
+ $expected_value = $Expected.$actual_key
+
+ if ($actual_value -is [System.Collections.IDictionary]) {
+ $actual_value | Assert-DictionaryEqual -Expected $expected_value
+ }
+ elseif ($actual_value -is [System.Collections.ArrayList] -or $actual_value -is [Array]) {
+ for ($i = 0; $i -lt $actual_value.Count; $i++) {
+ $actual_entry = $actual_value[$i]
+ $expected_entry = $expected_value[$i]
+ if ($actual_entry -is [System.Collections.IDictionary]) {
+ $actual_entry | Assert-DictionaryEqual -Expected $expected_entry
+ }
+ else {
+ Assert-Equal -Actual $actual_entry -Expected $expected_entry
+ }
+ }
+ }
+ else {
+ Assert-Equal -Actual $actual_value -Expected $expected_value
+ }
+ }
+ foreach ($expected_key in $expected_keys) {
+ ($expected_key -cin $actual_keys) | Assert-Equal -Expected $true
+ }
+ }
+}
+
+Function Exit-Module {
+ # Make sure Exit actually calls exit and not our overriden test behaviour
+ [Ansible.Basic.AnsibleModule]::Exit = { param([Int32]$rc) exit $rc }
+ Write-Output -InputObject (ConvertTo-Json -InputObject $module.Result -Compress -Depth 99)
+ $module.ExitJson()
+}
+
+$tmpdir = $module.Tmpdir
+
+# Override the Exit and WriteLine behaviour to throw an exception instead of exiting the module
+[Ansible.Basic.AnsibleModule]::Exit = {
+ param([Int32]$rc)
+ $exp = New-Object -TypeName System.Exception -ArgumentList "exit: $rc"
+ $exp | Add-Member -Type NoteProperty -Name Output -Value $_test_out
+ throw $exp
+}
+[Ansible.Basic.AnsibleModule]::WriteLine = {
+ param([String]$line)
+ Set-Variable -Name _test_out -Scope Global -Value $line
+}
+
+$tests = @{
+ "Empty spec and no options - args file" = {
+ $args_file = Join-Path -Path $tmpdir -ChildPath "args-$(Get-Random).json"
+ [System.IO.File]::WriteAllText($args_file, '{ "ANSIBLE_MODULE_ARGS": {} }')
+ $m = [Ansible.Basic.AnsibleModule]::Create(@($args_file), @{})
+
+ $m.CheckMode | Assert-Equal -Expected $false
+ $m.DebugMode | Assert-Equal -Expected $false
+ $m.DiffMode | Assert-Equal -Expected $false
+ $m.KeepRemoteFiles | Assert-Equal -Expected $false
+ $m.ModuleName | Assert-Equal -Expected "undefined win module"
+ $m.NoLog | Assert-Equal -Expected $false
+ $m.Verbosity | Assert-Equal -Expected 0
+ $m.AnsibleVersion | Assert-Equal -Expected $null
+ }
+
+ "Empty spec and no options - complex_args" = {
+ Set-Variable -Name complex_args -Scope Global -Value @{}
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+
+ $m.CheckMode | Assert-Equal -Expected $false
+ $m.DebugMode | Assert-Equal -Expected $false
+ $m.DiffMode | Assert-Equal -Expected $false
+ $m.KeepRemoteFiles | Assert-Equal -Expected $false
+ $m.ModuleName | Assert-Equal -Expected "undefined win module"
+ $m.NoLog | Assert-Equal -Expected $false
+ $m.Verbosity | Assert-Equal -Expected 0
+ $m.AnsibleVersion | Assert-Equal -Expected $null
+ }
+
+ "Internal param changes - args file" = {
+ $m_tmpdir = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
+ New-Item -Path $m_tmpdir -ItemType Directory > $null
+ $args_file = Join-Path -Path $tmpdir -ChildPath "args-$(Get-Random).json"
+ [System.IO.File]::WriteAllText($args_file, @"
+{
+ "ANSIBLE_MODULE_ARGS": {
+ "_ansible_check_mode": true,
+ "_ansible_debug": true,
+ "_ansible_diff": true,
+ "_ansible_keep_remote_files": true,
+ "_ansible_module_name": "ansible_basic_tests",
+ "_ansible_no_log": true,
+ "_ansible_remote_tmp": "%TEMP%",
+ "_ansible_selinux_special_fs": "ignored",
+ "_ansible_shell_executable": "ignored",
+ "_ansible_socket": "ignored",
+ "_ansible_syslog_facility": "ignored",
+ "_ansible_tmpdir": "$($m_tmpdir -replace "\\", "\\")",
+ "_ansible_verbosity": 3,
+ "_ansible_version": "2.8.0"
+ }
+}
+"@)
+ $m = [Ansible.Basic.AnsibleModule]::Create(@($args_file), @{supports_check_mode = $true })
+ $m.CheckMode | Assert-Equal -Expected $true
+ $m.DebugMode | Assert-Equal -Expected $true
+ $m.DiffMode | Assert-Equal -Expected $true
+ $m.KeepRemoteFiles | Assert-Equal -Expected $true
+ $m.ModuleName | Assert-Equal -Expected "ansible_basic_tests"
+ $m.NoLog | Assert-Equal -Expected $true
+ $m.Verbosity | Assert-Equal -Expected 3
+ $m.AnsibleVersion | Assert-Equal -Expected "2.8.0"
+ $m.Tmpdir | Assert-Equal -Expected $m_tmpdir
+ }
+
+ "Internal param changes - complex_args" = {
+ $m_tmpdir = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
+ New-Item -Path $m_tmpdir -ItemType Directory > $null
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_check_mode = $true
+ _ansible_debug = $true
+ _ansible_diff = $true
+ _ansible_keep_remote_files = $true
+ _ansible_module_name = "ansible_basic_tests"
+ _ansible_no_log = $true
+ _ansible_remote_tmp = "%TEMP%"
+ _ansible_selinux_special_fs = "ignored"
+ _ansible_shell_executable = "ignored"
+ _ansible_socket = "ignored"
+ _ansible_syslog_facility = "ignored"
+ _ansible_tmpdir = $m_tmpdir.ToString()
+ _ansible_verbosity = 3
+ _ansible_version = "2.8.0"
+ }
+ $spec = @{
+ supports_check_mode = $true
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ $m.CheckMode | Assert-Equal -Expected $true
+ $m.DebugMode | Assert-Equal -Expected $true
+ $m.DiffMode | Assert-Equal -Expected $true
+ $m.KeepRemoteFiles | Assert-Equal -Expected $true
+ $m.ModuleName | Assert-Equal -Expected "ansible_basic_tests"
+ $m.NoLog | Assert-Equal -Expected $true
+ $m.Verbosity | Assert-Equal -Expected 3
+ $m.AnsibleVersion | Assert-Equal -Expected "2.8.0"
+ $m.Tmpdir | Assert-Equal -Expected $m_tmpdir
+ }
+
+ "Parse complex module options" = {
+ $spec = @{
+ options = @{
+ option_default = @{}
+ missing_option_default = @{}
+ string_option = @{type = "str" }
+ required_option = @{required = $true }
+ missing_choices = @{choices = "a", "b" }
+ choices = @{choices = "a", "b" }
+ one_choice = @{choices = , "b" }
+ choice_with_default = @{choices = "a", "b"; default = "b" }
+ alias_direct = @{aliases = , "alias_direct1" }
+ alias_as_alias = @{aliases = "alias_as_alias1", "alias_as_alias2" }
+ bool_type = @{type = "bool" }
+ bool_from_str = @{type = "bool" }
+ dict_type = @{
+ type = "dict"
+ options = @{
+ int_type = @{type = "int" }
+ str_type = @{type = "str"; default = "str_sub_type" }
+ }
+ }
+ dict_type_missing = @{
+ type = "dict"
+ options = @{
+ int_type = @{type = "int" }
+ str_type = @{type = "str"; default = "str_sub_type" }
+ }
+ }
+ dict_type_defaults = @{
+ type = "dict"
+ apply_defaults = $true
+ options = @{
+ int_type = @{type = "int" }
+ str_type = @{type = "str"; default = "str_sub_type" }
+ }
+ }
+ dict_type_json = @{type = "dict" }
+ dict_type_str = @{type = "dict" }
+ float_type = @{type = "float" }
+ int_type = @{type = "int" }
+ json_type = @{type = "json" }
+ json_type_dict = @{type = "json" }
+ list_type = @{type = "list" }
+ list_type_str = @{type = "list" }
+ list_with_int = @{type = "list"; elements = "int" }
+ list_type_single = @{type = "list" }
+ list_with_dict = @{
+ type = "list"
+ elements = "dict"
+ options = @{
+ int_type = @{type = "int" }
+ str_type = @{type = "str"; default = "str_sub_type" }
+ }
+ }
+ path_type = @{type = "path" }
+ path_type_nt = @{type = "path" }
+ path_type_missing = @{type = "path" }
+ raw_type_str = @{type = "raw" }
+ raw_type_int = @{type = "raw" }
+ sid_type = @{type = "sid" }
+ sid_from_name = @{type = "sid" }
+ str_type = @{type = "str" }
+ delegate_type = @{type = [Func[[Object], [UInt64]]] { [System.UInt64]::Parse($args[0]) } }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_default = 1
+ string_option = 1
+ required_option = "required"
+ choices = "a"
+ one_choice = "b"
+ alias_direct = "a"
+ alias_as_alias2 = "a"
+ bool_type = $true
+ bool_from_str = "false"
+ dict_type = @{
+ int_type = "10"
+ }
+ dict_type_json = '{"a":"a","b":1,"c":["a","b"]}'
+ dict_type_str = 'a=a b="b 2" c=c'
+ float_type = "3.14159"
+ int_type = 0
+ json_type = '{"a":"a","b":1,"c":["a","b"]}'
+ json_type_dict = @{
+ a = "a"
+ b = 1
+ c = @("a", "b")
+ }
+ list_type = @("a", "b", 1, 2)
+ list_type_str = "a, b,1,2 "
+ list_with_int = @("1", 2)
+ list_type_single = "single"
+ list_with_dict = @(
+ @{
+ int_type = 2
+ str_type = "dict entry"
+ },
+ @{ int_type = 1 },
+ @{}
+ )
+ path_type = "%SystemRoot%\System32"
+ path_type_nt = "\\?\%SystemRoot%\System32"
+ path_type_missing = "T:\missing\path"
+ raw_type_str = "str"
+ raw_type_int = 1
+ sid_type = "S-1-5-18"
+ sid_from_name = "SYSTEM"
+ str_type = "str"
+ delegate_type = "1234"
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $m.Params.option_default | Assert-Equal -Expected "1"
+ $m.Params.option_default.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.missing_option_default | Assert-Equal -Expected $null
+ $m.Params.string_option | Assert-Equal -Expected "1"
+ $m.Params.string_option.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.required_option | Assert-Equal -Expected "required"
+ $m.Params.required_option.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.missing_choices | Assert-Equal -Expected $null
+ $m.Params.choices | Assert-Equal -Expected "a"
+ $m.Params.choices.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.one_choice | Assert-Equal -Expected "b"
+ $m.Params.one_choice.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.choice_with_default | Assert-Equal -Expected "b"
+ $m.Params.choice_with_default.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.alias_direct | Assert-Equal -Expected "a"
+ $m.Params.alias_direct.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.alias_as_alias | Assert-Equal -Expected "a"
+ $m.Params.alias_as_alias.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.bool_type | Assert-Equal -Expected $true
+ $m.Params.bool_type.GetType().ToString() | Assert-Equal -Expected "System.Boolean"
+ $m.Params.bool_from_str | Assert-Equal -Expected $false
+ $m.Params.bool_from_str.GetType().ToString() | Assert-Equal -Expected "System.Boolean"
+ $m.Params.dict_type | Assert-DictionaryEqual -Expected @{int_type = 10; str_type = "str_sub_type" }
+ $m.Params.dict_type.GetType().ToString() | Assert-Equal -Expected "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
+ $m.Params.dict_type.int_type.GetType().ToString() | Assert-Equal -Expected "System.Int32"
+ $m.Params.dict_type.str_type.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.dict_type_missing | Assert-Equal -Expected $null
+ $m.Params.dict_type_defaults | Assert-DictionaryEqual -Expected @{int_type = $null; str_type = "str_sub_type" }
+ $m.Params.dict_type_defaults.GetType().ToString() | Assert-Equal -Expected "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
+ $m.Params.dict_type_defaults.str_type.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.dict_type_json | Assert-DictionaryEqual -Expected @{
+ a = "a"
+ b = 1
+ c = @("a", "b")
+ }
+ $m.Params.dict_type_json.GetType().ToString() | Assert-Equal -Expected "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
+ $m.Params.dict_type_json.a.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.dict_type_json.b.GetType().ToString() | Assert-Equal -Expected "System.Int32"
+ $m.Params.dict_type_json.c.GetType().ToString() | Assert-Equal -Expected "System.Collections.ArrayList"
+ $m.Params.dict_type_str | Assert-DictionaryEqual -Expected @{a = "a"; b = "b 2"; c = "c" }
+ $m.Params.dict_type_str.GetType().ToString() | Assert-Equal -Expected "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
+ $m.Params.dict_type_str.a.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.dict_type_str.b.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.dict_type_str.c.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.float_type | Assert-Equal -Expected ([System.Single]3.14159)
+ $m.Params.float_type.GetType().ToString() | Assert-Equal -Expected "System.Single"
+ $m.Params.int_type | Assert-Equal -Expected 0
+ $m.Params.int_type.GetType().ToString() | Assert-Equal -Expected "System.Int32"
+ $m.Params.json_type | Assert-Equal -Expected '{"a":"a","b":1,"c":["a","b"]}'
+ $m.Params.json_type.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $jsonValue = ([Ansible.Basic.AnsibleModule]::FromJson('{"a":"a","b":1,"c":["a","b"]}'))
+ [Ansible.Basic.AnsibleModule]::FromJson($m.Params.json_type_dict) | Assert-DictionaryEqual -Expected $jsonValue
+ $m.Params.json_type_dict.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.list_type.GetType().ToString() | Assert-Equal -Expected "System.Collections.Generic.List``1[System.Object]"
+ $m.Params.list_type.Count | Assert-Equal -Expected 4
+ $m.Params.list_type[0] | Assert-Equal -Expected "a"
+ $m.Params.list_type[0].GetType().FullName | Assert-Equal -Expected "System.String"
+ $m.Params.list_type[1] | Assert-Equal -Expected "b"
+ $m.Params.list_type[1].GetType().FullName | Assert-Equal -Expected "System.String"
+ $m.Params.list_type[2] | Assert-Equal -Expected 1
+ $m.Params.list_type[2].GetType().FullName | Assert-Equal -Expected "System.Int32"
+ $m.Params.list_type[3] | Assert-Equal -Expected 2
+ $m.Params.list_type[3].GetType().FullName | Assert-Equal -Expected "System.Int32"
+ $m.Params.list_type_str.GetType().ToString() | Assert-Equal -Expected "System.Collections.Generic.List``1[System.Object]"
+ $m.Params.list_type_str.Count | Assert-Equal -Expected 4
+ $m.Params.list_type_str[0] | Assert-Equal -Expected "a"
+ $m.Params.list_type_str[0].GetType().FullName | Assert-Equal -Expected "System.String"
+ $m.Params.list_type_str[1] | Assert-Equal -Expected "b"
+ $m.Params.list_type_str[1].GetType().FullName | Assert-Equal -Expected "System.String"
+ $m.Params.list_type_str[2] | Assert-Equal -Expected "1"
+ $m.Params.list_type_str[2].GetType().FullName | Assert-Equal -Expected "System.String"
+ $m.Params.list_type_str[3] | Assert-Equal -Expected "2"
+ $m.Params.list_type_str[3].GetType().FullName | Assert-Equal -Expected "System.String"
+ $m.Params.list_with_int.GetType().ToString() | Assert-Equal -Expected "System.Collections.Generic.List``1[System.Object]"
+ $m.Params.list_with_int.Count | Assert-Equal -Expected 2
+ $m.Params.list_with_int[0] | Assert-Equal -Expected 1
+ $m.Params.list_with_int[0].GetType().FullName | Assert-Equal -Expected "System.Int32"
+ $m.Params.list_with_int[1] | Assert-Equal -Expected 2
+ $m.Params.list_with_int[1].GetType().FullName | Assert-Equal -Expected "System.Int32"
+ $m.Params.list_type_single.GetType().ToString() | Assert-Equal -Expected "System.Collections.Generic.List``1[System.Object]"
+ $m.Params.list_type_single.Count | Assert-Equal -Expected 1
+ $m.Params.list_type_single[0] | Assert-Equal -Expected "single"
+ $m.Params.list_type_single[0].GetType().FullName | Assert-Equal -Expected "System.String"
+ $m.Params.list_with_dict.GetType().FullName.StartsWith("System.Collections.Generic.List``1[[System.Object") | Assert-Equal -Expected $true
+ $m.Params.list_with_dict.Count | Assert-Equal -Expected 3
+ $m.Params.list_with_dict[0].GetType().FullName.StartsWith("System.Collections.Generic.Dictionary``2[[System.String") | Assert-Equal -Expected $true
+ $m.Params.list_with_dict[0] | Assert-DictionaryEqual -Expected @{int_type = 2; str_type = "dict entry" }
+ $m.Params.list_with_dict[0].int_type.GetType().FullName.ToString() | Assert-Equal -Expected "System.Int32"
+ $m.Params.list_with_dict[0].str_type.GetType().FullName.ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.list_with_dict[1].GetType().FullName.StartsWith("System.Collections.Generic.Dictionary``2[[System.String") | Assert-Equal -Expected $true
+ $m.Params.list_with_dict[1] | Assert-DictionaryEqual -Expected @{int_type = 1; str_type = "str_sub_type" }
+ $m.Params.list_with_dict[1].int_type.GetType().FullName.ToString() | Assert-Equal -Expected "System.Int32"
+ $m.Params.list_with_dict[1].str_type.GetType().FullName.ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.list_with_dict[2].GetType().FullName.StartsWith("System.Collections.Generic.Dictionary``2[[System.String") | Assert-Equal -Expected $true
+ $m.Params.list_with_dict[2] | Assert-DictionaryEqual -Expected @{int_type = $null; str_type = "str_sub_type" }
+ $m.Params.list_with_dict[2].str_type.GetType().FullName.ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.path_type | Assert-Equal -Expected "$($env:SystemRoot)\System32"
+ $m.Params.path_type.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.path_type_nt | Assert-Equal -Expected "\\?\%SystemRoot%\System32"
+ $m.Params.path_type_nt.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.path_type_missing | Assert-Equal -Expected "T:\missing\path"
+ $m.Params.path_type_missing.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.raw_type_str | Assert-Equal -Expected "str"
+ $m.Params.raw_type_str.GetType().FullName | Assert-Equal -Expected "System.String"
+ $m.Params.raw_type_int | Assert-Equal -Expected 1
+ $m.Params.raw_type_int.GetType().FullName | Assert-Equal -Expected "System.Int32"
+ $m.Params.sid_type | Assert-Equal -Expected (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList "S-1-5-18")
+ $m.Params.sid_type.GetType().ToString() | Assert-Equal -Expected "System.Security.Principal.SecurityIdentifier"
+ $m.Params.sid_from_name | Assert-Equal -Expected (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList "S-1-5-18")
+ $m.Params.sid_from_name.GetType().ToString() | Assert-Equal -Expected "System.Security.Principal.SecurityIdentifier"
+ $m.Params.str_type | Assert-Equal -Expected "str"
+ $m.Params.str_type.GetType().ToString() | Assert-Equal -Expected "System.String"
+ $m.Params.delegate_type | Assert-Equal -Expected 1234
+ $m.Params.delegate_type.GetType().ToString() | Assert-Equal -Expected "System.UInt64"
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_module_args = @{
+ option_default = "1"
+ missing_option_default = $null
+ string_option = "1"
+ required_option = "required"
+ missing_choices = $null
+ choices = "a"
+ one_choice = "b"
+ choice_with_default = "b"
+ alias_direct = "a"
+ alias_as_alias = "a"
+ alias_as_alias2 = "a"
+ bool_type = $true
+ bool_from_str = $false
+ dict_type = @{
+ int_type = 10
+ str_type = "str_sub_type"
+ }
+ dict_type_missing = $null
+ dict_type_defaults = @{
+ int_type = $null
+ str_type = "str_sub_type"
+ }
+ dict_type_json = @{
+ a = "a"
+ b = 1
+ c = @("a", "b")
+ }
+ dict_type_str = @{
+ a = "a"
+ b = "b 2"
+ c = "c"
+ }
+ float_type = 3.14159
+ int_type = 0
+ json_type = $m.Params.json_type.ToString()
+ json_type_dict = $m.Params.json_type_dict.ToString()
+ list_type = @("a", "b", 1, 2)
+ list_type_str = @("a", "b", "1", "2")
+ list_with_int = @(1, 2)
+ list_type_single = @("single")
+ list_with_dict = @(
+ @{
+ int_type = 2
+ str_type = "dict entry"
+ },
+ @{
+ int_type = 1
+ str_type = "str_sub_type"
+ },
+ @{
+ int_type = $null
+ str_type = "str_sub_type"
+ }
+ )
+ path_type = "$($env:SystemRoot)\System32"
+ path_type_nt = "\\?\%SystemRoot%\System32"
+ path_type_missing = "T:\missing\path"
+ raw_type_str = "str"
+ raw_type_int = 1
+ sid_type = "S-1-5-18"
+ sid_from_name = "S-1-5-18"
+ str_type = "str"
+ delegate_type = 1234
+ }
+ $actual.Keys.Count | Assert-Equal -Expected 2
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $expected_module_args }
+ }
+
+ "Parse module args with list elements and delegate type" = {
+ $spec = @{
+ options = @{
+ list_delegate_type = @{
+ type = "list"
+ elements = [Func[[Object], [UInt16]]] { [System.UInt16]::Parse($args[0]) }
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ list_delegate_type = @(
+ "1234",
+ 4321
+ )
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ $m.Params.list_delegate_type.GetType().Name | Assert-Equal -Expected 'List`1'
+ $m.Params.list_delegate_type[0].GetType().FullName | Assert-Equal -Expected "System.UInt16"
+ $m.Params.list_delegate_Type[1].GetType().FullName | Assert-Equal -Expected "System.UInt16"
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_module_args = @{
+ list_delegate_type = @(
+ 1234,
+ 4321
+ )
+ }
+ $actual.Keys.Count | Assert-Equal -Expected 2
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $expected_module_args }
+ }
+
+ "Parse module args with case insensitive input" = {
+ $spec = @{
+ options = @{
+ option1 = @{ type = "int"; required = $true }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_module_name = "win_test"
+ Option1 = "1"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ # Verifies the case of the params key is set to the module spec not actual input
+ $m.Params.Keys | Assert-Equal -Expected @("option1")
+ $m.Params.option1 | Assert-Equal -Expected 1
+
+ # Verifies the type conversion happens even on a case insensitive match
+ $m.Params.option1.GetType().FullName | Assert-Equal -Expected "System.Int32"
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_warnings = "Parameters for (win_test) was a case insensitive match: Option1. "
+ $expected_warnings += "Module options will become case sensitive in a future Ansible release. "
+ $expected_warnings += "Supported parameters include: option1"
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{
+ option1 = 1
+ }
+ }
+ # We have disabled the warning for now
+ #warnings = @($expected_warnings)
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "No log values" = {
+ $spec = @{
+ options = @{
+ username = @{type = "str" }
+ password = @{type = "str"; no_log = $true }
+ password2 = @{type = "int"; no_log = $true }
+ dict = @{type = "dict" }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_module_name = "test_no_log"
+ username = "user - pass - name"
+ password = "pass"
+ password2 = 1234
+ dict = @{
+ data = "Oops this is secret: pass"
+ dict = @{
+ pass = "plain"
+ hide = "pass"
+ sub_hide = "password"
+ int_hide = 123456
+ }
+ list = @(
+ "pass",
+ "password",
+ 1234567,
+ "pa ss",
+ @{
+ pass = "plain"
+ hide = "pass"
+ sub_hide = "password"
+ int_hide = 123456
+ }
+ )
+ custom = "pass"
+ }
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ $m.Result.data = $complex_args.dict
+
+ # verify params internally aren't masked
+ $m.Params.username | Assert-Equal -Expected "user - pass - name"
+ $m.Params.password | Assert-Equal -Expected "pass"
+ $m.Params.password2 | Assert-Equal -Expected 1234
+ $m.Params.dict.custom | Assert-Equal -Expected "pass"
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ # verify no_log params are masked in invocation
+ $expected = @{
+ invocation = @{
+ module_args = @{
+ password2 = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ dict = @{
+ dict = @{
+ pass = "plain"
+ hide = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ sub_hide = "********word"
+ int_hide = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ }
+ custom = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ list = @(
+ "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
+ "********word",
+ "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
+ "pa ss",
+ @{
+ pass = "plain"
+ hide = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ sub_hide = "********word"
+ int_hide = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ }
+ )
+ data = "Oops this is secret: ********"
+ }
+ username = "user - ******** - name"
+ password = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ }
+ }
+ changed = $false
+ data = $complex_args.dict
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+
+ $expected_event = @'
+test_no_log - Invoked with:
+ username: user - ******** - name
+ dict: dict: sub_hide: ****word
+ pass: plain
+ int_hide: ********56
+ hide: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ data: Oops this is secret: ********
+ custom: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ list:
+ - VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ - ********word
+ - ********567
+ - pa ss
+ - sub_hide: ********word
+ pass: plain
+ int_hide: ********56
+ hide: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ password2: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+ password: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
+'@
+ $actual_event = (Get-EventLog -LogName Application -Source Ansible -Newest 1).Message
+ $actual_event | Assert-DictionaryEqual -Expected $expected_event
+ }
+
+ "No log value with an empty string" = {
+ $spec = @{
+ options = @{
+ password1 = @{type = "str"; no_log = $true }
+ password2 = @{type = "str"; no_log = $true }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_module_name = "test_no_log"
+ password1 = ""
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ $m.Result.data = $complex_args.dict
+
+ # verify params internally aren't masked
+ $m.Params.password1 | Assert-Equal -Expected ""
+ $m.Params.password2 | Assert-Equal -Expected $null
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ invocation = @{
+ module_args = @{
+ password1 = ""
+ password2 = $null
+ }
+ }
+ changed = $false
+ data = $complex_args.dict
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Removed in version" = {
+ $spec = @{
+ options = @{
+ removed1 = @{removed_in_version = "2.1" }
+ removed2 = @{removed_in_version = "2.2" }
+ removed3 = @{removed_in_version = "2.3"; removed_from_collection = "ansible.builtin" }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ removed1 = "value"
+ removed3 = "value"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{
+ removed1 = "value"
+ removed2 = $null
+ removed3 = "value"
+ }
+ }
+ deprecations = @(
+ @{
+ msg = "Param 'removed3' is deprecated. See the module docs for more information"
+ version = "2.3"
+ collection_name = "ansible.builtin"
+ },
+ @{
+ msg = "Param 'removed1' is deprecated. See the module docs for more information"
+ version = "2.1"
+ collection_name = $null
+ }
+ )
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Removed at date" = {
+ $spec = @{
+ options = @{
+ removed1 = @{removed_at_date = [DateTime]"2020-03-10" }
+ removed2 = @{removed_at_date = [DateTime]"2020-03-11" }
+ removed3 = @{removed_at_date = [DateTime]"2020-06-07"; removed_from_collection = "ansible.builtin" }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ removed1 = "value"
+ removed3 = "value"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{
+ removed1 = "value"
+ removed2 = $null
+ removed3 = "value"
+ }
+ }
+ deprecations = @(
+ @{
+ msg = "Param 'removed3' is deprecated. See the module docs for more information"
+ date = "2020-06-07"
+ collection_name = "ansible.builtin"
+ },
+ @{
+ msg = "Param 'removed1' is deprecated. See the module docs for more information"
+ date = "2020-03-10"
+ collection_name = $null
+ }
+ )
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Deprecated aliases" = {
+ $spec = @{
+ options = @{
+ option1 = @{ type = "str"; aliases = "alias1"; deprecated_aliases = @(@{name = "alias1"; version = "2.10" }) }
+ option2 = @{ type = "str"; aliases = "alias2"; deprecated_aliases = @(@{name = "alias2"; version = "2.11" }) }
+ option3 = @{
+ type = "dict"
+ options = @{
+ option1 = @{ type = "str"; aliases = "alias1"; deprecated_aliases = @(@{name = "alias1"; version = "2.10" }) }
+ option2 = @{ type = "str"; aliases = "alias2"; deprecated_aliases = @(@{name = "alias2"; version = "2.11" }) }
+ option3 = @{
+ type = "str"
+ aliases = "alias3"
+ deprecated_aliases = @(
+ @{name = "alias3"; version = "2.12"; collection_name = "ansible.builtin" }
+ )
+ }
+ option4 = @{ type = "str"; aliases = "alias4"; deprecated_aliases = @(@{name = "alias4"; date = [DateTime]"2020-03-11" }) }
+ option5 = @{ type = "str"; aliases = "alias5"; deprecated_aliases = @(@{name = "alias5"; date = [DateTime]"2020-03-09" }) }
+ option6 = @{
+ type = "str"
+ aliases = "alias6"
+ deprecated_aliases = @(
+ @{name = "alias6"; date = [DateTime]"2020-06-01"; collection_name = "ansible.builtin" }
+ )
+ }
+ }
+ }
+ option4 = @{ type = "str"; aliases = "alias4"; deprecated_aliases = @(@{name = "alias4"; date = [DateTime]"2020-03-10" }) }
+ option5 = @{ type = "str"; aliases = "alias5"; deprecated_aliases = @(@{name = "alias5"; date = [DateTime]"2020-03-12" }) }
+ option6 = @{
+ type = "str"
+ aliases = "alias6"
+ deprecated_aliases = @(
+ @{name = "alias6"; version = "2.12"; collection_name = "ansible.builtin" }
+ )
+ }
+ option7 = @{
+ type = "str"
+ aliases = "alias7"
+ deprecated_aliases = @(
+ @{name = "alias7"; date = [DateTime]"2020-06-07"; collection_name = "ansible.builtin" }
+ )
+ }
+ }
+ }
+
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ alias1 = "alias1"
+ option2 = "option2"
+ option3 = @{
+ option1 = "option1"
+ alias2 = "alias2"
+ alias3 = "alias3"
+ option4 = "option4"
+ alias5 = "alias5"
+ alias6 = "alias6"
+ }
+ option4 = "option4"
+ alias5 = "alias5"
+ alias6 = "alias6"
+ alias7 = "alias7"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{
+ alias1 = "alias1"
+ option1 = "alias1"
+ option2 = "option2"
+ option3 = @{
+ option1 = "option1"
+ option2 = "alias2"
+ alias2 = "alias2"
+ option3 = "alias3"
+ alias3 = "alias3"
+ option4 = "option4"
+ option5 = "alias5"
+ alias5 = "alias5"
+ option6 = "alias6"
+ alias6 = "alias6"
+ }
+ option4 = "option4"
+ option5 = "alias5"
+ alias5 = "alias5"
+ option6 = "alias6"
+ alias6 = "alias6"
+ option7 = "alias7"
+ alias7 = "alias7"
+ }
+ }
+ deprecations = @(
+ @{
+ msg = "Alias 'alias7' is deprecated. See the module docs for more information"
+ date = "2020-06-07"
+ collection_name = "ansible.builtin"
+ },
+ @{
+ msg = "Alias 'alias1' is deprecated. See the module docs for more information"
+ version = "2.10"
+ collection_name = $null
+ },
+ @{
+ msg = "Alias 'alias5' is deprecated. See the module docs for more information"
+ date = "2020-03-12"
+ collection_name = $null
+ },
+ @{
+ msg = "Alias 'alias6' is deprecated. See the module docs for more information"
+ version = "2.12"
+ collection_name = "ansible.builtin"
+ },
+ @{
+ msg = "Alias 'alias2' is deprecated. See the module docs for more information - found in option3"
+ version = "2.11"
+ collection_name = $null
+ },
+ @{
+ msg = "Alias 'alias5' is deprecated. See the module docs for more information - found in option3"
+ date = "2020-03-09"
+ collection_name = $null
+ },
+ @{
+ msg = "Alias 'alias3' is deprecated. See the module docs for more information - found in option3"
+ version = "2.12"
+ collection_name = "ansible.builtin"
+ },
+ @{
+ msg = "Alias 'alias6' is deprecated. See the module docs for more information - found in option3"
+ date = "2020-06-01"
+ collection_name = "ansible.builtin"
+ }
+ )
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Required by - single value" = {
+ $spec = @{
+ options = @{
+ option1 = @{type = "str" }
+ option2 = @{type = "str" }
+ option3 = @{type = "str" }
+ }
+ required_by = @{
+ option1 = "option2"
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "option1"
+ option2 = "option2"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{
+ option1 = "option1"
+ option2 = "option2"
+ option3 = $null
+ }
+ }
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Required by - multiple values" = {
+ $spec = @{
+ options = @{
+ option1 = @{type = "str" }
+ option2 = @{type = "str" }
+ option3 = @{type = "str" }
+ }
+ required_by = @{
+ option1 = "option2", "option3"
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "option1"
+ option2 = "option2"
+ option3 = "option3"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{
+ option1 = "option1"
+ option2 = "option2"
+ option3 = "option3"
+ }
+ }
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Required by explicit null" = {
+ $spec = @{
+ options = @{
+ option1 = @{type = "str" }
+ option2 = @{type = "str" }
+ option3 = @{type = "str" }
+ }
+ required_by = @{
+ option1 = "option2"
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "option1"
+ option2 = $null
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{
+ option1 = "option1"
+ option2 = $null
+ option3 = $null
+ }
+ }
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Required by failed - single value" = {
+ $spec = @{
+ options = @{
+ option1 = @{type = "str" }
+ option2 = @{type = "str" }
+ option3 = @{type = "str" }
+ }
+ required_by = @{
+ option1 = "option2"
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "option1"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ failed = $true
+ invocation = @{
+ module_args = @{
+ option1 = "option1"
+ }
+ }
+ msg = "missing parameter(s) required by 'option1': option2"
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Required by failed - multiple values" = {
+ $spec = @{
+ options = @{
+ option1 = @{type = "str" }
+ option2 = @{type = "str" }
+ option3 = @{type = "str" }
+ }
+ required_by = @{
+ option1 = "option2", "option3"
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "option1"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ failed = $true
+ invocation = @{
+ module_args = @{
+ option1 = "option1"
+ }
+ }
+ msg = "missing parameter(s) required by 'option1': option2, option3"
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Debug without debug set" = {
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_debug = $false
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ $m.Debug("debug message")
+ $actual_event = (Get-EventLog -LogName Application -Source Ansible -Newest 1).Message
+ $actual_event | Assert-Equal -Expected "undefined win module - Invoked with:`r`n "
+ }
+
+ "Debug with debug set" = {
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_debug = $true
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ $m.Debug("debug message")
+ $actual_event = (Get-EventLog -LogName Application -Source Ansible -Newest 1).Message
+ $actual_event | Assert-Equal -Expected "undefined win module - [DEBUG] debug message"
+ }
+
+ "Deprecate and warn with version" = {
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ $m.Deprecate("message", "2.7")
+ $actual_deprecate_event_1 = Get-EventLog -LogName Application -Source Ansible -Newest 1
+ $m.Deprecate("message w collection", "2.8", "ansible.builtin")
+ $actual_deprecate_event_2 = Get-EventLog -LogName Application -Source Ansible -Newest 1
+ $m.Warn("warning")
+ $actual_warn_event = Get-EventLog -LogName Application -Source Ansible -Newest 1
+
+ $actual_deprecate_event_1.Message | Assert-Equal -Expected "undefined win module - [DEPRECATION WARNING] message 2.7"
+ $actual_deprecate_event_2.Message | Assert-Equal -Expected "undefined win module - [DEPRECATION WARNING] message w collection 2.8"
+ $actual_warn_event.EntryType | Assert-Equal -Expected "Warning"
+ $actual_warn_event.Message | Assert-Equal -Expected "undefined win module - [WARNING] warning"
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{}
+ }
+ warnings = @("warning")
+ deprecations = @(
+ @{msg = "message"; version = "2.7"; collection_name = $null },
+ @{msg = "message w collection"; version = "2.8"; collection_name = "ansible.builtin" }
+ )
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Deprecate and warn with date" = {
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ $m.Deprecate("message", [DateTime]"2020-01-01")
+ $actual_deprecate_event_1 = Get-EventLog -LogName Application -Source Ansible -Newest 1
+ $m.Deprecate("message w collection", [DateTime]"2020-01-02", "ansible.builtin")
+ $actual_deprecate_event_2 = Get-EventLog -LogName Application -Source Ansible -Newest 1
+ $m.Warn("warning")
+ $actual_warn_event = Get-EventLog -LogName Application -Source Ansible -Newest 1
+
+ $actual_deprecate_event_1.Message | Assert-Equal -Expected "undefined win module - [DEPRECATION WARNING] message 2020-01-01"
+ $actual_deprecate_event_2.Message | Assert-Equal -Expected "undefined win module - [DEPRECATION WARNING] message w collection 2020-01-02"
+ $actual_warn_event.EntryType | Assert-Equal -Expected "Warning"
+ $actual_warn_event.Message | Assert-Equal -Expected "undefined win module - [WARNING] warning"
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{}
+ }
+ warnings = @("warning")
+ deprecations = @(
+ @{msg = "message"; date = "2020-01-01"; collection_name = $null },
+ @{msg = "message w collection"; date = "2020-01-02"; collection_name = "ansible.builtin" }
+ )
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "FailJson with message" = {
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+
+ $failed = $false
+ try {
+ $m.FailJson("fail message")
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $failed
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{}
+ }
+ failed = $true
+ msg = "fail message"
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "FailJson with Exception" = {
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+
+ try {
+ [System.IO.Path]::GetFullPath($null)
+ }
+ catch {
+ $excp = $_.Exception
+ }
+
+ $failed = $false
+ try {
+ $m.FailJson("fail message", $excp)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $failed
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{}
+ }
+ failed = $true
+ msg = "fail message"
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "FailJson with ErrorRecord" = {
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+
+ try {
+ Get-Item -LiteralPath $null
+ }
+ catch {
+ $error_record = $_
+ }
+
+ $failed = $false
+ try {
+ $m.FailJson("fail message", $error_record)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $failed
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{}
+ }
+ failed = $true
+ msg = "fail message"
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "FailJson with Exception and verbosity 3" = {
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_verbosity = 3
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+
+ try {
+ [System.IO.Path]::GetFullPath($null)
+ }
+ catch {
+ $excp = $_.Exception
+ }
+
+ $failed = $false
+ try {
+ $m.FailJson("fail message", $excp)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $failed
+
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = @{} }
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected "fail message"
+ $expected = 'System.Management.Automation.MethodInvocationException: Exception calling "GetFullPath" with "1" argument(s)'
+ $actual.exception.Contains($expected) | Assert-Equal -Expected $true
+ }
+
+ "FailJson with ErrorRecord and verbosity 3" = {
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_verbosity = 3
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+
+ try {
+ Get-Item -LiteralPath $null
+ }
+ catch {
+ $error_record = $_
+ }
+
+ $failed = $false
+ try {
+ $m.FailJson("fail message", $error_record)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $failed
+
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = @{} }
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected "fail message"
+ $actual.exception.Contains("Cannot bind argument to parameter 'LiteralPath' because it is null") | Assert-Equal -Expected $true
+ $actual.exception.Contains("+ Get-Item -LiteralPath `$null") | Assert-Equal -Expected $true
+ $actual.exception.Contains("ScriptStackTrace:") | Assert-Equal -Expected $true
+ }
+
+ "Diff entry without diff set" = {
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ $m.Diff.before = @{a = "a" }
+ $m.Diff.after = @{b = "b" }
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $failed
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{}
+ }
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "Diff entry with diff set" = {
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_diff = $true
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ $m.Diff.before = @{a = "a" }
+ $m.Diff.after = @{b = "b" }
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $failed
+
+ $expected = @{
+ changed = $false
+ invocation = @{
+ module_args = @{}
+ }
+ diff = @{
+ before = @{a = "a" }
+ after = @{b = "b" }
+ }
+ }
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+
+ "ParseBool tests" = {
+ $mapping = New-Object -TypeName 'System.Collections.Generic.Dictionary`2[[Object], [Bool]]'
+ $mapping.Add("y", $true)
+ $mapping.Add("Y", $true)
+ $mapping.Add("yes", $true)
+ $mapping.Add("Yes", $true)
+ $mapping.Add("on", $true)
+ $mapping.Add("On", $true)
+ $mapping.Add("1", $true)
+ $mapping.Add(1, $true)
+ $mapping.Add("true", $true)
+ $mapping.Add("True", $true)
+ $mapping.Add("t", $true)
+ $mapping.Add("T", $true)
+ $mapping.Add("1.0", $true)
+ $mapping.Add(1.0, $true)
+ $mapping.Add($true, $true)
+ $mapping.Add("n", $false)
+ $mapping.Add("N", $false)
+ $mapping.Add("no", $false)
+ $mapping.Add("No", $false)
+ $mapping.Add("off", $false)
+ $mapping.Add("Off", $false)
+ $mapping.Add("0", $false)
+ $mapping.Add(0, $false)
+ $mapping.Add("false", $false)
+ $mapping.Add("False", $false)
+ $mapping.Add("f", $false)
+ $mapping.Add("F", $false)
+ $mapping.Add("0.0", $false)
+ $mapping.Add(0.0, $false)
+ $mapping.Add($false, $false)
+
+ foreach ($map in $mapping.GetEnumerator()) {
+ $expected = $map.Value
+ $actual = [Ansible.Basic.AnsibleModule]::ParseBool($map.Key)
+ $actual | Assert-Equal -Expected $expected
+ $actual.GetType().FullName | Assert-Equal -Expected "System.Boolean"
+ }
+
+ $fail_bools = @(
+ "falsey",
+ "abc",
+ 2,
+ "2",
+ -1
+ )
+ foreach ($fail_bool in $fail_bools) {
+ $failed = $false
+ try {
+ [Ansible.Basic.AnsibleModule]::ParseBool($fail_bool)
+ }
+ catch {
+ $failed = $true
+ $_.Exception.Message.Contains("The value '$fail_bool' is not a valid boolean") | Assert-Equal -Expected $true
+ }
+ $failed | Assert-Equal -Expected $true
+ }
+ }
+
+ "Unknown internal key" = {
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_invalid = "invalid"
+ }
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+
+ $expected = @{
+ invocation = @{
+ module_args = @{
+ _ansible_invalid = "invalid"
+ }
+ }
+ changed = $false
+ failed = $true
+ msg = "Unsupported parameters for (undefined win module) module: _ansible_invalid. Supported parameters include: "
+ }
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ $actual | Assert-DictionaryEqual -Expected $expected
+ }
+ $failed | Assert-Equal -Expected $true
+ }
+
+ "Module tmpdir with present remote tmp" = {
+ $current_user = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
+ $dir_security = New-Object -TypeName System.Security.AccessControl.DirectorySecurity
+ $dir_security.SetOwner($current_user)
+ $dir_security.SetAccessRuleProtection($true, $false)
+ $ace = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @(
+ $current_user, [System.Security.AccessControl.FileSystemRights]::FullControl,
+ [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
+ [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow
+ )
+ $dir_security.AddAccessRule($ace)
+ $expected_sd = $dir_security.GetSecurityDescriptorSddlForm("Access, Owner")
+
+ $remote_tmp = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
+ New-Item -Path $remote_tmp -ItemType Directory > $null
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_remote_tmp = $remote_tmp.ToString()
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equal -Expected $true
+
+ $actual_tmpdir = $m.Tmpdir
+ $parent_tmpdir = Split-Path -Path $actual_tmpdir -Parent
+ $tmpdir_name = Split-Path -Path $actual_tmpdir -Leaf
+
+ $parent_tmpdir | Assert-Equal -Expected $remote_tmp
+ $tmpdir_name.StartSwith("ansible-moduletmp-") | Assert-Equal -Expected $true
+ (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $true
+ (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equal -Expected $true
+ $children = [System.IO.Directory]::EnumerateDirectories($remote_tmp)
+ $children.Count | Assert-Equal -Expected 1
+ $actual_tmpdir_sd = (Get-Acl -Path $actual_tmpdir).GetSecurityDescriptorSddlForm("Access, Owner")
+ $actual_tmpdir_sd | Assert-Equal -Expected $expected_sd
+
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $false
+ (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equal -Expected $true
+ $output.warnings.Count | Assert-Equal -Expected 0
+ }
+
+ "Module tmpdir with missing remote_tmp" = {
+ $current_user = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
+ $dir_security = New-Object -TypeName System.Security.AccessControl.DirectorySecurity
+ $dir_security.SetOwner($current_user)
+ $dir_security.SetAccessRuleProtection($true, $false)
+ $ace = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @(
+ $current_user, [System.Security.AccessControl.FileSystemRights]::FullControl,
+ [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
+ [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow
+ )
+ $dir_security.AddAccessRule($ace)
+ $expected_sd = $dir_security.GetSecurityDescriptorSddlForm("Access, Owner")
+
+ $remote_tmp = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_remote_tmp = $remote_tmp.ToString()
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+ (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equal -Expected $false
+
+ $actual_tmpdir = $m.Tmpdir
+ $parent_tmpdir = Split-Path -Path $actual_tmpdir -Parent
+ $tmpdir_name = Split-Path -Path $actual_tmpdir -Leaf
+
+ $parent_tmpdir | Assert-Equal -Expected $remote_tmp
+ $tmpdir_name.StartSwith("ansible-moduletmp-") | Assert-Equal -Expected $true
+ (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $true
+ (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equal -Expected $true
+ $children = [System.IO.Directory]::EnumerateDirectories($remote_tmp)
+ $children.Count | Assert-Equal -Expected 1
+ $actual_remote_sd = (Get-Acl -Path $remote_tmp).GetSecurityDescriptorSddlForm("Access, Owner")
+ $actual_tmpdir_sd = (Get-Acl -Path $actual_tmpdir).GetSecurityDescriptorSddlForm("Access, Owner")
+ $actual_remote_sd | Assert-Equal -Expected $expected_sd
+ $actual_tmpdir_sd | Assert-Equal -Expected $expected_sd
+
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $false
+ (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equal -Expected $true
+ $output.warnings.Count | Assert-Equal -Expected 1
+ $nt_account = $current_user.Translate([System.Security.Principal.NTAccount])
+ $actual_warning = "Module remote_tmp $remote_tmp did not exist and was created with FullControl to $nt_account, "
+ $actual_warning += "this may cause issues when running as another user. To avoid this, "
+ $actual_warning += "create the remote_tmp dir with the correct permissions manually"
+ $actual_warning | Assert-Equal -Expected $output.warnings[0]
+ }
+
+ "Module tmp, keep remote files" = {
+ $remote_tmp = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
+ New-Item -Path $remote_tmp -ItemType Directory > $null
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_remote_tmp = $remote_tmp.ToString()
+ _ansible_keep_remote_files = $true
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+
+ $actual_tmpdir = $m.Tmpdir
+ $parent_tmpdir = Split-Path -Path $actual_tmpdir -Parent
+ $tmpdir_name = Split-Path -Path $actual_tmpdir -Leaf
+
+ $parent_tmpdir | Assert-Equal -Expected $remote_tmp
+ $tmpdir_name.StartSwith("ansible-moduletmp-") | Assert-Equal -Expected $true
+ (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $true
+ (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equal -Expected $true
+
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $true
+ (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equal -Expected $true
+ $output.warnings.Count | Assert-Equal -Expected 0
+ Remove-Item -LiteralPath $actual_tmpdir -Force -Recurse
+ }
+
+ "Invalid argument spec key" = {
+ $spec = @{
+ invalid = $true
+ }
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: argument spec entry contains an invalid key 'invalid', valid keys: apply_defaults, "
+ $expected_msg += "aliases, choices, default, deprecated_aliases, elements, mutually_exclusive, no_log, options, "
+ $expected_msg += "removed_in_version, removed_at_date, removed_from_collection, required, required_by, required_if, "
+ $expected_msg += "required_one_of, required_together, supports_check_mode, type"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Invalid argument spec key - nested" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ options = @{
+ sub_option_key = @{
+ invalid = $true
+ }
+ }
+ }
+ }
+ }
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: argument spec entry contains an invalid key 'invalid', valid keys: apply_defaults, "
+ $expected_msg += "aliases, choices, default, deprecated_aliases, elements, mutually_exclusive, no_log, options, "
+ $expected_msg += "removed_in_version, removed_at_date, removed_from_collection, required, required_by, required_if, "
+ $expected_msg += "required_one_of, required_together, supports_check_mode, type - found in option_key -> sub_option_key"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Invalid argument spec value type" = {
+ $spec = @{
+ apply_defaults = "abc"
+ }
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: argument spec for 'apply_defaults' did not match expected "
+ $expected_msg += "type System.Boolean: actual type System.String"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Invalid argument spec option type" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "invalid type"
+ }
+ }
+ }
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: type 'invalid type' is unsupported - found in option_key. "
+ $expected_msg += "Valid types are: bool, dict, float, int, json, list, path, raw, sid, str"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Invalid argument spec option element type" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "list"
+ elements = "invalid type"
+ }
+ }
+ }
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: elements 'invalid type' is unsupported - found in option_key. "
+ $expected_msg += "Valid types are: bool, dict, float, int, json, list, path, raw, sid, str"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Invalid deprecated aliases entry - no version and date" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "str"
+ aliases = , "alias_name"
+ deprecated_aliases = @(
+ @{name = "alias_name" }
+ )
+ }
+ }
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: One of version or date is required in a deprecated_aliases entry"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Invalid deprecated aliases entry - no name (nested)" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "dict"
+ options = @{
+ sub_option_key = @{
+ type = "str"
+ aliases = , "alias_name"
+ deprecated_aliases = @(
+ @{version = "2.10" }
+ )
+ }
+ }
+ }
+ }
+ }
+
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = @{
+ sub_option_key = "a"
+ }
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.ArgumentException] {
+ $failed = $true
+ $expected_msg = "name is required in a deprecated_aliases entry - found in option_key"
+ $_.Exception.Message | Assert-Equal -Expected $expected_msg
+ }
+ $failed | Assert-Equal -Expected $true
+ }
+
+ "Invalid deprecated aliases entry - both version and date" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "str"
+ aliases = , "alias_name"
+ deprecated_aliases = @(
+ @{
+ name = "alias_name"
+ date = [DateTime]"2020-03-10"
+ version = "2.11"
+ }
+ )
+ }
+ }
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: Only one of version or date is allowed in a deprecated_aliases entry"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Invalid deprecated aliases entry - wrong date type" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "str"
+ aliases = , "alias_name"
+ deprecated_aliases = @(
+ @{
+ name = "alias_name"
+ date = "2020-03-10"
+ }
+ )
+ }
+ }
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: A deprecated_aliases date must be a DateTime object"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Spec required and default set at the same time" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ required = $true
+ default = "default value"
+ }
+ }
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: required and default are mutually exclusive for option_key"
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ ("exception" -cin $actual.Keys) | Assert-Equal -Expected $true
+ }
+
+ "Unsupported options" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "str"
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "abc"
+ invalid_key = "def"
+ another_key = "ghi"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "Unsupported parameters for (undefined win module) module: another_key, invalid_key. "
+ $expected_msg += "Supported parameters include: option_key"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Check mode and module doesn't support check mode" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "str"
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_check_mode = $true
+ option_key = "abc"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "remote module (undefined win module) does not support check mode"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.skipped | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = @{option_key = "abc" } }
+ }
+
+ "Check mode with suboption without supports_check_mode" = {
+ $spec = @{
+ options = @{
+ sub_options = @{
+ # This tests the situation where a sub key doesn't set supports_check_mode, the logic in
+ # Ansible.Basic automatically sets that to $false and we want it to ignore it for a nested check
+ type = "dict"
+ options = @{
+ sub_option = @{ type = "str"; default = "value" }
+ }
+ }
+ }
+ supports_check_mode = $true
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ _ansible_check_mode = $true
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ $m.CheckMode | Assert-Equal -Expected $true
+ }
+
+ "Type conversion error" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "int"
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "a"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "argument for option_key is of type System.String and we were unable to convert to int: "
+ $expected_msg += "Input string was not in a correct format."
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Type conversion error - delegate" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "dict"
+ options = @{
+ sub_option_key = @{
+ type = [Func[[Object], [UInt64]]] { [System.UInt64]::Parse($args[0]) }
+ }
+ }
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = @{
+ sub_option_key = "a"
+ }
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "argument for sub_option_key is of type System.String and we were unable to convert to delegate: "
+ $expected_msg += "Exception calling `"Parse`" with `"1`" argument(s): `"Input string was not in a correct format.`" "
+ $expected_msg += "found in option_key"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Numeric choices" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ choices = 1, 2, 3
+ type = "int"
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "2"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $output.Keys.Count | Assert-Equal -Expected 2
+ $output.changed | Assert-Equal -Expected $false
+ $output.invocation | Assert-DictionaryEqual -Expected @{module_args = @{option_key = 2 } }
+ }
+
+ "Case insensitive choice" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ choices = "abc", "def"
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "ABC"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $expected_warning = "value of option_key was a case insensitive match of one of: abc, def. "
+ $expected_warning += "Checking of choices will be case sensitive in a future Ansible release. "
+ $expected_warning += "Case insensitive matches were: ABC"
+
+ $output.invocation | Assert-DictionaryEqual -Expected @{module_args = @{option_key = "ABC" } }
+ # We have disabled the warnings for now
+ #$output.warnings.Count | Assert-Equal -Expected 1
+ #$output.warnings[0] | Assert-Equal -Expected $expected_warning
+ }
+
+ "Case insensitive choice no_log" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ choices = "abc", "def"
+ no_log = $true
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "ABC"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $expected_warning = "value of option_key was a case insensitive match of one of: abc, def. "
+ $expected_warning += "Checking of choices will be case sensitive in a future Ansible release. "
+ $expected_warning += "Case insensitive matches were: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+
+ $output.invocation | Assert-DictionaryEqual -Expected @{module_args = @{option_key = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" } }
+ # We have disabled the warnings for now
+ #$output.warnings.Count | Assert-Equal -Expected 1
+ #$output.warnings[0] | Assert-Equal -Expected $expected_warning
+ }
+
+ "Case insentitive choice as list" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ choices = "abc", "def", "ghi", "JKL"
+ type = "list"
+ elements = "str"
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "AbC", "ghi", "jkl"
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $expected_warning = "value of option_key was a case insensitive match of one or more of: abc, def, ghi, JKL. "
+ $expected_warning += "Checking of choices will be case sensitive in a future Ansible release. "
+ $expected_warning += "Case insensitive matches were: AbC, jkl"
+
+ $output.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ # We have disabled the warnings for now
+ #$output.warnings.Count | Assert-Equal -Expected 1
+ #$output.warnings[0] | Assert-Equal -Expected $expected_warning
+ }
+
+ "Invalid choice" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ choices = "a", "b"
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "c"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "value of option_key must be one of: a, b. Got no match for: c"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Invalid choice with no_log" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ choices = "a", "b"
+ no_log = $true
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "abc"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "value of option_key must be one of: a, b. Got no match for: ********"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = @{option_key = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER" } }
+ }
+
+ "Invalid choice in list" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ choices = "a", "b"
+ type = "list"
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = "a", "c"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "value of option_key must be one or more of: a, b. Got no match for: c"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Mutually exclusive options" = {
+ $spec = @{
+ options = @{
+ option1 = @{}
+ option2 = @{}
+ }
+ mutually_exclusive = @(, @("option1", "option2"))
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "a"
+ option2 = "b"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "parameters are mutually exclusive: option1, option2"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Missing required argument" = {
+ $spec = @{
+ options = @{
+ option1 = @{}
+ option2 = @{required = $true }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "a"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "missing required arguments: option2"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Missing required argument subspec - no value defined" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "dict"
+ options = @{
+ sub_option_key = @{
+ required = $true
+ }
+ }
+ }
+ }
+ }
+
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.Keys.Count | Assert-Equal -Expected 2
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Missing required argument subspec" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "dict"
+ options = @{
+ sub_option_key = @{
+ required = $true
+ }
+ another_key = @{}
+ }
+ }
+ }
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = @{
+ another_key = "abc"
+ }
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "missing required arguments: sub_option_key found in option_key"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Required together not set" = {
+ $spec = @{
+ options = @{
+ option1 = @{}
+ option2 = @{}
+ }
+ required_together = @(, @("option1", "option2"))
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "abc"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "parameters are required together: option1, option2"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Required together not set - subspec" = {
+ $spec = @{
+ options = @{
+ option_key = @{
+ type = "dict"
+ options = @{
+ option1 = @{}
+ option2 = @{}
+ }
+ required_together = @(, @("option1", "option2"))
+ }
+ another_option = @{}
+ }
+ required_together = @(, @("option_key", "another_option"))
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option_key = @{
+ option1 = "abc"
+ }
+ another_option = "def"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "parameters are required together: option1, option2 found in option_key"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Required one of not set" = {
+ $spec = @{
+ options = @{
+ option1 = @{}
+ option2 = @{}
+ option3 = @{}
+ }
+ required_one_of = @(@("option1", "option2"), @("option2", "option3"))
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "abc"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "one of the following is required: option2, option3"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Required if invalid entries" = {
+ $spec = @{
+ options = @{
+ state = @{choices = "absent", "present"; default = "present" }
+ path = @{type = "path" }
+ }
+ required_if = @(, @("state", "absent"))
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "internal error: invalid required_if value count of 2, expecting 3 or 4 entries"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Required if no missing option" = {
+ $spec = @{
+ options = @{
+ state = @{choices = "absent", "present"; default = "present" }
+ name = @{}
+ path = @{type = "path" }
+ }
+ required_if = @(, @("state", "absent", @("name", "path")))
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ name = "abc"
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.Keys.Count | Assert-Equal -Expected 2
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Required if missing option" = {
+ $spec = @{
+ options = @{
+ state = @{choices = "absent", "present"; default = "present" }
+ name = @{}
+ path = @{type = "path" }
+ }
+ required_if = @(, @("state", "absent", @("name", "path")))
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ state = "absent"
+ name = "abc"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "state is absent but all of the following are missing: path"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Required if missing option and required one is set" = {
+ $spec = @{
+ options = @{
+ state = @{choices = "absent", "present"; default = "present" }
+ name = @{}
+ path = @{type = "path" }
+ }
+ required_if = @(, @("state", "absent", @("name", "path"), $true))
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ state = "absent"
+ }
+
+ $failed = $false
+ try {
+ $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $expected_msg = "state is absent but any of the following are missing: name, path"
+
+ $actual.Keys.Count | Assert-Equal -Expected 4
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected $expected_msg
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Required if missing option but one required set" = {
+ $spec = @{
+ options = @{
+ state = @{choices = "absent", "present"; default = "present" }
+ name = @{}
+ path = @{type = "path" }
+ }
+ required_if = @(, @("state", "absent", @("name", "path"), $true))
+ }
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ state = "absent"
+ name = "abc"
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.Keys.Count | Assert-Equal -Expected 2
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "PS Object in return result" = {
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
+
+ # JavaScriptSerializer struggles with PS Object like PSCustomObject due to circular references, this test makes
+ # sure we can handle these types of objects without bombing
+ $m.Result.output = [PSCustomObject]@{a = "a"; b = "b" }
+ $failed = $true
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.Keys.Count | Assert-Equal -Expected 3
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = @{} }
+ $actual.output | Assert-DictionaryEqual -Expected @{a = "a"; b = "b" }
+ }
+
+ "String json array to object" = {
+ $input_json = '["abc", "def"]'
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($input_json)
+ $actual -is [Array] | Assert-Equal -Expected $true
+ $actual.Length | Assert-Equal -Expected 2
+ $actual[0] | Assert-Equal -Expected "abc"
+ $actual[1] | Assert-Equal -Expected "def"
+ }
+
+ "String json array of dictionaries to object" = {
+ $input_json = '[{"abc":"def"}]'
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($input_json)
+ $actual -is [Array] | Assert-Equal -Expected $true
+ $actual.Length | Assert-Equal -Expected 1
+ $actual[0] | Assert-DictionaryEqual -Expected @{"abc" = "def" }
+ }
+
+ "Spec with fragments" = {
+ $spec = @{
+ options = @{
+ option1 = @{ type = "str" }
+ }
+ }
+ $fragment1 = @{
+ options = @{
+ option2 = @{ type = "str" }
+ }
+ }
+
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "option1"
+ option2 = "option2"
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment1))
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{module_args = $complex_args }
+ }
+
+ "Fragment spec that with a deprecated alias" = {
+ $spec = @{
+ options = @{
+ option1 = @{
+ aliases = @("alias1_spec")
+ type = "str"
+ deprecated_aliases = @(
+ @{name = "alias1_spec"; version = "2.0" }
+ )
+ }
+ option2 = @{
+ aliases = @("alias2_spec")
+ deprecated_aliases = @(
+ @{name = "alias2_spec"; version = "2.0"; collection_name = "ansible.builtin" }
+ )
+ }
+ }
+ }
+ $fragment1 = @{
+ options = @{
+ option1 = @{
+ aliases = @("alias1")
+ deprecated_aliases = @() # Makes sure it doesn't overwrite the spec, just adds to it.
+ }
+ option2 = @{
+ aliases = @("alias2")
+ deprecated_aliases = @(
+ @{name = "alias2"; version = "2.0"; collection_name = "foo.bar" }
+ )
+ type = "str"
+ }
+ }
+ }
+
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ alias1_spec = "option1"
+ alias2 = "option2"
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment1))
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.deprecations.Count | Assert-Equal -Expected 2
+ $actual.deprecations[0] | Assert-DictionaryEqual -Expected @{
+ msg = "Alias 'alias1_spec' is deprecated. See the module docs for more information"; version = "2.0"; collection_name = $null
+ }
+ $actual.deprecations[1] | Assert-DictionaryEqual -Expected @{
+ msg = "Alias 'alias2' is deprecated. See the module docs for more information"; version = "2.0"; collection_name = "foo.bar"
+ }
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{
+ module_args = @{
+ option1 = "option1"
+ alias1_spec = "option1"
+ option2 = "option2"
+ alias2 = "option2"
+ }
+ }
+ }
+
+ "Fragment spec with mutual args" = {
+ $spec = @{
+ options = @{
+ option1 = @{ type = "str" }
+ option2 = @{ type = "str" }
+ }
+ mutually_exclusive = @(
+ , @('option1', 'option2')
+ )
+ }
+ $fragment1 = @{
+ options = @{
+ fragment1_1 = @{ type = "str" }
+ fragment1_2 = @{ type = "str" }
+ }
+ mutually_exclusive = @(
+ , @('fragment1_1', 'fragment1_2')
+ )
+ }
+ $fragment2 = @{
+ options = @{
+ fragment2 = @{ type = "str" }
+ }
+ }
+
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "option1"
+ fragment1_1 = "fragment1_1"
+ fragment1_2 = "fragment1_2"
+ fragment2 = "fragment2"
+ }
+
+ $failed = $false
+ try {
+ [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment1, $fragment2))
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg | Assert-Equal -Expected "parameters are mutually exclusive: fragment1_1, fragment1_2"
+ $actual.invocation | Assert-DictionaryEqual -Expected @{ module_args = $complex_args }
+ }
+
+ "Fragment spec with no_log" = {
+ $spec = @{
+ options = @{
+ option1 = @{
+ aliases = @("alias")
+ }
+ }
+ }
+ $fragment1 = @{
+ options = @{
+ option1 = @{
+ no_log = $true # Makes sure that a value set in the fragment but not in the spec is respected.
+ type = "str"
+ }
+ }
+ }
+
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ alias = "option1"
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment1))
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.invocation | Assert-DictionaryEqual -Expected @{
+ module_args = @{
+ option1 = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ alias = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
+ }
+ }
+ }
+
+ "Catch invalid fragment spec format" = {
+ $spec = @{
+ options = @{
+ option1 = @{ type = "str" }
+ }
+ }
+ $fragment = @{
+ options = @{}
+ invalid = "will fail"
+ }
+
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ option1 = "option1"
+ }
+
+ $failed = $false
+ try {
+ [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment))
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 1"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.failed | Assert-Equal -Expected $true
+ $actual.msg.StartsWith("internal error: argument spec entry contains an invalid key 'invalid', valid keys: ") | Assert-Equal -Expected $true
+ }
+
+ "Spec with different list types" = {
+ $spec = @{
+ options = @{
+ # Single element of the same list type not in a list
+ option1 = @{
+ aliases = "alias1"
+ deprecated_aliases = @{name = "alias1"; version = "2.0"; collection_name = "foo.bar" }
+ }
+
+ # Arrays
+ option2 = @{
+ aliases = , "alias2"
+ deprecated_aliases = , @{name = "alias2"; version = "2.0"; collection_name = "foo.bar" }
+ }
+
+ # ArrayList
+ option3 = @{
+ aliases = [System.Collections.ArrayList]@("alias3")
+ deprecated_aliases = [System.Collections.ArrayList]@(@{name = "alias3"; version = "2.0"; collection_name = "foo.bar" })
+ }
+
+ # Generic.List[Object]
+ option4 = @{
+ aliases = [System.Collections.Generic.List[Object]]@("alias4")
+ deprecated_aliases = [System.Collections.Generic.List[Object]]@(@{name = "alias4"; version = "2.0"; collection_name = "foo.bar" })
+ }
+
+ # Generic.List[T]
+ option5 = @{
+ aliases = [System.Collections.Generic.List[String]]@("alias5")
+ deprecated_aliases = [System.Collections.Generic.List[Hashtable]]@()
+ }
+ }
+ }
+ $spec.options.option5.deprecated_aliases.Add(@{name = "alias5"; version = "2.0"; collection_name = "foo.bar" })
+
+ Set-Variable -Name complex_args -Scope Global -Value @{
+ alias1 = "option1"
+ alias2 = "option2"
+ alias3 = "option3"
+ alias4 = "option4"
+ alias5 = "option5"
+ }
+ $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
+
+ $failed = $false
+ try {
+ $m.ExitJson()
+ }
+ catch [System.Management.Automation.RuntimeException] {
+ $failed = $true
+ $_.Exception.Message | Assert-Equal -Expected "exit: 0"
+ $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
+ }
+ $failed | Assert-Equal -Expected $true
+
+ $actual.changed | Assert-Equal -Expected $false
+ $actual.deprecations.Count | Assert-Equal -Expected 5
+ foreach ($dep in $actual.deprecations) {
+ $dep.msg -like "Alias 'alias?' is deprecated. See the module docs for more information" | Assert-Equal -Expected $true
+ $dep.version | Assert-Equal -Expected '2.0'
+ $dep.collection_name | Assert-Equal -Expected 'foo.bar'
+ }
+ $actual.invocation | Assert-DictionaryEqual -Expected @{
+ module_args = @{
+ alias1 = "option1"
+ option1 = "option1"
+ alias2 = "option2"
+ option2 = "option2"
+ alias3 = "option3"
+ option3 = "option3"
+ alias4 = "option4"
+ option4 = "option4"
+ alias5 = "option5"
+ option5 = "option5"
+ }
+ }
+ }
+}
+
+try {
+ foreach ($test_impl in $tests.GetEnumerator()) {
+ # Reset the variables before each test
+ Set-Variable -Name complex_args -Value @{} -Scope Global
+
+ $test = $test_impl.Key
+ &$test_impl.Value
+ }
+ $module.Result.data = "success"
+}
+catch [System.Management.Automation.RuntimeException] {
+ $module.Result.failed = $true
+ $module.Result.test = $test
+ $module.Result.line = $_.InvocationInfo.ScriptLineNumber
+ $module.Result.method = $_.InvocationInfo.Line.Trim()
+
+ if ($_.Exception.Message.StartSwith("exit: ")) {
+ # The exception was caused by an unexpected Exit call, log that on the output
+ $module.Result.output = (ConvertFrom-Json -InputObject $_.Exception.InnerException.Output)
+ $module.Result.msg = "Uncaught AnsibleModule exit in tests, see output"
+ }
+ else {
+ # Unrelated exception
+ $module.Result.exception = $_.Exception.ToString()
+ $module.Result.msg = "Uncaught exception: $(($_ | Out-String).ToString())"
+ }
+}
+
+Exit-Module