You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ansible/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1

3398 lines
134 KiB
PowerShell

#!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 overridden 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_target_log_info": "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
}
"Module tmpdir with symlinks" = {
$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(@(), @{})
$actual_tmpdir = $m.Tmpdir
$dir1 = Join-Path $actual_tmpdir Dir1
$dir2 = Join-Path $actual_tmpdir Dir2
$dir1, $dir2 | New-Item -Path { $_ } -ItemType Directory > $null
$file1 = Join-Path $dir1 test.txt
$file2 = Join-Path $dir2 test.txt
$file3 = Join-Path $actual_tmpdir test.txt
Set-Content -LiteralPath $file1 ''
Set-Content -LiteralPath $file2 ''
Set-Content -LiteralPath $file3 ''
$outside_target = Join-Path -Path $tmpdir -ChildPath "moduleoutsidedir-$(Get-Random)"
$outside_file = Join-Path -Path $outside_target -ChildPath "file"
New-Item -Path $outside_target -ItemType Directory > $null
Set-Content -LiteralPath $outside_file ''
cmd.exe /c mklink /d "$dir1\missing-dir-link" "$actual_tmpdir\fake"
cmd.exe /c mklink /d "$dir1\good-dir-link" "$dir2"
cmd.exe /c mklink /d "$dir1\recursive-target-link" "$dir1"
cmd.exe /c mklink "$dir1\missing-file-link" "$actual_tmpdir\fake"
cmd.exe /c mklink "$dir1\good-file-link" "$dir2\test.txt"
cmd.exe /c mklink /d "$actual_tmpdir\outside-dir" $outside_target
cmd.exe /c mklink "$actual_tmpdir\outside-file" $outside_file
try {
$m.ExitJson()
}
catch [System.Management.Automation.RuntimeException] {
$output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
}
$output.warnings.Count | Assert-Equal -Expected 0
(Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $false
(Test-Path -LiteralPath $outside_target -PathType Container) | Assert-Equal -Expected $true
(Test-Path -LiteralPath $outside_file -PathType Leaf) | Assert-Equal -Expected $true
Remove-Item -LiteralPath $remote_tmp -Force -Recurse
}
"Module tmpdir with undeletable file" = {
$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(@(), @{})
$actual_tmpdir = $m.Tmpdir
$dir1 = Join-Path $actual_tmpdir Dir1
$dir2 = Join-Path $actual_tmpdir Dir2
$dir1, $dir2 | New-Item -Path { $_ } -ItemType Directory > $null
$file1 = Join-Path $dir1 test.txt
$file2 = Join-Path $dir2 test.txt
$file3 = Join-Path $actual_tmpdir test.txt
Set-Content -LiteralPath $file1 ''
Set-Content -LiteralPath $file2 ''
Set-Content -LiteralPath $file3 ''
$fs = [System.IO.File]::Open($file1, "Open", "Read", "None")
try {
$m.ExitJson()
}
catch [System.Management.Automation.RuntimeException] {
$output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
}
$expected_msg = "Failure cleaning temp path '$actual_tmpdir': IOException Directory contains files still open by other processes"
$output.warnings.Count | Assert-Equal -Expected 1
$output.warnings[0] | Assert-Equal -Expected $expected_msg
(Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $true
(Test-Path -LiteralPath $dir1 -PathType Container) | Assert-Equal -Expected $true
# Test-Path tries to open the file in a way that fails if it's marked as deleted
(Get-ChildItem -LiteralPath $dir1 -File).Count | Assert-Equal -Expected 1
(Test-Path -LiteralPath $dir2 -PathType Container) | Assert-Equal -Expected $false
(Test-Path -LiteralPath $file3 -PathType Leaf) | Assert-Equal -Expected $false
# Releasing the file handle releases the lock on the file but as the
# cleanup couldn't access the file to mark as delete on close it is
# still going to be present.
$fs.Dispose()
(Test-Path -LiteralPath $dir1 -PathType Container) | Assert-Equal -Expected $true
(Test-Path -LiteralPath $file1 -PathType Leaf) | Assert-Equal -Expected $true
Remove-Item -LiteralPath $remote_tmp -Force -Recurse
}
"Module tmpdir delete with locked handle" = {
$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(@(), @{})
$actual_tmpdir = $m.Tmpdir
$dir1 = Join-Path $actual_tmpdir Dir1
$dir2 = Join-Path $actual_tmpdir Dir2
$dir1, $dir2 | New-Item -Path { $_ } -ItemType Directory > $null
$file1 = Join-Path $dir1 test.txt
$file2 = Join-Path $dir2 test.txt
$file3 = Join-Path $actual_tmpdir test.txt
Set-Content -LiteralPath $file1 ''
Set-Content -LiteralPath $file2 ''
Set-Content -LiteralPath $file3 ''
[System.IO.File]::SetAttributes($file1, "ReadOnly")
[System.IO.File]::SetAttributes($file2, "ReadOnly")
[System.IO.File]::SetAttributes($file3, "ReadOnly")
$fs = [System.IO.File]::Open($file1, "Open", "Read", "Delete")
try {
$m.ExitJson()
}
catch [System.Management.Automation.RuntimeException] {
$output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
}
if ([System.Environment]::OSVersion.Version -lt [Version]'10.0') {
# Older hosts can only do delete on close. This means Dir1 and its
# file will still be present but Dir2 should be deleted.
$expected_msg = "Failure cleaning temp path '$actual_tmpdir': IOException Directory contains files still open by other processes"
$output.warnings.Count | Assert-Equal -Expected 1
$output.warnings[0] | Assert-Equal -Expected $expected_msg
(Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $true
(Test-Path -LiteralPath $dir1 -PathType Container) | Assert-Equal -Expected $true
# Test-Path tries to open the file in a way that fails if it's marked as deleted
(Get-ChildItem -LiteralPath $dir1 -File).Count | Assert-Equal -Expected 1
(Test-Path -LiteralPath $dir2 -PathType Container) | Assert-Equal -Expected $false
(Test-Path -LiteralPath $file3 -PathType Leaf) | Assert-Equal -Expected $false
# Releasing the file handle releases the lock on the file deleting
# it. Unfortunately the parent dir will still be present
$fs.Dispose()
(Test-Path -LiteralPath $dir1 -PathType Container) | Assert-Equal -Expected $true
(Test-Path -LiteralPath $file1 -PathType Leaf) | Assert-Equal -Expected $false
}
else {
# Server 2016+ can use the POSIX APIs which will delete it straight away
(Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equal -Expected $false
$output.warnings.Count | Assert-Equal -Expected 0
$fs.Dispose()
}
Remove-Item -LiteralPath $remote_tmp -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 }
}
"Unsupported options with ignore" = {
$spec = @{
options = @{
option_key = @{
type = "str"
}
}
}
Set-Variable -Name complex_args -Scope Global -Value @{
option_key = "abc"
invalid_key = "def"
another_key = "ghi"
_ansible_ignore_unknown_opts = $true
}
$m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
$m.Params | Assert-DictionaryEqual -Expected @{ option_key = "abc"; invalid_key = "def"; another_key = "ghi" }
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 = "abc"; invalid_key = "def"; another_key = "ghi" } }
}
"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