1 #!powershell
2
3 #AnsibleRequires -CSharpUtil Ansible.Basic
4
5 $module = [Ansible.Basic.AnsibleModule]::Create($args, @{})
6
Assert-Equals()7 Function Assert-Equals {
8 param(
9 [Parameter(Mandatory=$true, ValueFromPipeline=$true)][AllowNull()]$Actual,
10 [Parameter(Mandatory=$true, Position=0)][AllowNull()]$Expected
11 )
12
13 $matched = $false
14 if ($Actual -is [System.Collections.ArrayList] -or $Actual -is [Array]) {
15 $Actual.Count | Assert-Equals -Expected $Expected.Count
16 for ($i = 0; $i -lt $Actual.Count; $i++) {
17 $actual_value = $Actual[$i]
18 $expected_value = $Expected[$i]
19 Assert-Equals -Actual $actual_value -Expected $expected_value
20 }
21 $matched = $true
22 } else {
23 $matched = $Actual -ceq $Expected
24 }
25
26 if (-not $matched) {
27 if ($Actual -is [PSObject]) {
28 $Actual = $Actual.ToString()
29 }
30
31 $call_stack = (Get-PSCallStack)[1]
32 $module.Result.failed = $true
33 $module.Result.test = $test
34 $module.Result.actual = $Actual
35 $module.Result.expected = $Expected
36 $module.Result.line = $call_stack.ScriptLineNumber
37 $module.Result.method = $call_stack.Position.Text
38 $module.Result.msg = "AssertionError: actual != expected"
39
40 Exit-Module
41 }
42 }
43
Assert-DictionaryEqualsnull44 Function Assert-DictionaryEquals {
45 param(
46 [Parameter(Mandatory=$true, ValueFromPipeline=$true)][AllowNull()]$Actual,
47 [Parameter(Mandatory=$true, Position=0)][AllowNull()]$Expected
48 )
49 $actual_keys = $Actual.Keys
50 $expected_keys = $Expected.Keys
51
52 $actual_keys.Count | Assert-Equals -Expected $expected_keys.Count
53 foreach ($actual_entry in $Actual.GetEnumerator()) {
54 $actual_key = $actual_entry.Key
55 ($actual_key -cin $expected_keys) | Assert-Equals -Expected $true
56 $actual_value = $actual_entry.Value
57 $expected_value = $Expected.$actual_key
58
59 if ($actual_value -is [System.Collections.IDictionary]) {
60 $actual_value | Assert-DictionaryEquals -Expected $expected_value
61 } elseif ($actual_value -is [System.Collections.ArrayList] -or $actual_value -is [Array]) {
62 for ($i = 0; $i -lt $actual_value.Count; $i++) {
63 $actual_entry = $actual_value[$i]
64 $expected_entry = $expected_value[$i]
65 if ($actual_entry -is [System.Collections.IDictionary]) {
66 $actual_entry | Assert-DictionaryEquals -Expected $expected_entry
67 } else {
68 Assert-Equals -Actual $actual_entry -Expected $expected_entry
69 }
70 }
71 } else {
72 Assert-Equals -Actual $actual_value -Expected $expected_value
73 }
74 }
75 foreach ($expected_key in $expected_keys) {
76 ($expected_key -cin $actual_keys) | Assert-Equals -Expected $true
77 }
78 }
79
Exit-Modulenull80 Function Exit-Module {
81 # Make sure Exit actually calls exit and not our overriden test behaviour
82 [Ansible.Basic.AnsibleModule]::Exit = { param([Int32]$rc) exit $rc }
83 Write-Output -InputObject (ConvertTo-Json -InputObject $module.Result -Compress -Depth 99)
84 $module.ExitJson()
85 }
86
87 $tmpdir = $module.Tmpdir
88
89 # Override the Exit and WriteLine behaviour to throw an exception instead of exiting the module
90 [Ansible.Basic.AnsibleModule]::Exit = {
91 param([Int32]$rc)
92 $exp = New-Object -TypeName System.Exception -ArgumentList "exit: $rc"
93 $exp | Add-Member -Type NoteProperty -Name Output -Value $_test_out
94 throw $exp
95 }
96 [Ansible.Basic.AnsibleModule]::WriteLine = {
97 param([String]$line)
98 Set-Variable -Name _test_out -Scope Global -Value $line
99 }
100
101 $tests = @{
102 "Empty spec and no options - args file" = {
103 $args_file = Join-Path -Path $tmpdir -ChildPath "args-$(Get-Random).json"
104 [System.IO.File]::WriteAllText($args_file, '{ "ANSIBLE_MODULE_ARGS": {} }')
105 $m = [Ansible.Basic.AnsibleModule]::Create(@($args_file), @{})
106
107 $m.CheckMode | Assert-Equals -Expected $false
108 $m.DebugMode | Assert-Equals -Expected $false
109 $m.DiffMode | Assert-Equals -Expected $false
110 $m.KeepRemoteFiles | Assert-Equals -Expected $false
111 $m.ModuleName | Assert-Equals -Expected "undefined win module"
112 $m.NoLog | Assert-Equals -Expected $false
113 $m.Verbosity | Assert-Equals -Expected 0
114 $m.AnsibleVersion | Assert-Equals -Expected $null
115 }
116
117 "Empty spec and no options - complex_args" = {
118 Set-Variable -Name complex_args -Scope Global -Value @{}
119 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
120
121 $m.CheckMode | Assert-Equals -Expected $false
122 $m.DebugMode | Assert-Equals -Expected $false
123 $m.DiffMode | Assert-Equals -Expected $false
124 $m.KeepRemoteFiles | Assert-Equals -Expected $false
125 $m.ModuleName | Assert-Equals -Expected "undefined win module"
126 $m.NoLog | Assert-Equals -Expected $false
127 $m.Verbosity | Assert-Equals -Expected 0
128 $m.AnsibleVersion | Assert-Equals -Expected $null
129 }
130
131 "Internal param changes - args file" = {
132 $m_tmpdir = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
133 New-Item -Path $m_tmpdir -ItemType Directory > $null
134 $args_file = Join-Path -Path $tmpdir -ChildPath "args-$(Get-Random).json"
135 [System.IO.File]::WriteAllText($args_file, @"
136 {
137 "ANSIBLE_MODULE_ARGS": {
138 "_ansible_check_mode": true,
139 "_ansible_debug": true,
140 "_ansible_diff": true,
141 "_ansible_keep_remote_files": true,
142 "_ansible_module_name": "ansible_basic_tests",
143 "_ansible_no_log": true,
144 "_ansible_remote_tmp": "%TEMP%",
145 "_ansible_selinux_special_fs": "ignored",
146 "_ansible_shell_executable": "ignored",
147 "_ansible_socket": "ignored",
148 "_ansible_syslog_facility": "ignored",
149 "_ansible_tmpdir": "$($m_tmpdir -replace "\\", "\\")",
150 "_ansible_verbosity": 3,
151 "_ansible_version": "2.8.0"
152 }
153 }
154 "@)
155 $m = [Ansible.Basic.AnsibleModule]::Create(@($args_file), @{supports_check_mode=$true})
156 $m.CheckMode | Assert-Equals -Expected $true
157 $m.DebugMode | Assert-Equals -Expected $true
158 $m.DiffMode | Assert-Equals -Expected $true
159 $m.KeepRemoteFiles | Assert-Equals -Expected $true
160 $m.ModuleName | Assert-Equals -Expected "ansible_basic_tests"
161 $m.NoLog | Assert-Equals -Expected $true
162 $m.Verbosity | Assert-Equals -Expected 3
163 $m.AnsibleVersion | Assert-Equals -Expected "2.8.0"
164 $m.Tmpdir | Assert-Equals -Expected $m_tmpdir
165 }
166
167 "Internal param changes - complex_args" = {
168 $m_tmpdir = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
169 New-Item -Path $m_tmpdir -ItemType Directory > $null
170 Set-Variable -Name complex_args -Scope Global -Value @{
171 _ansible_check_mode = $true
172 _ansible_debug = $true
173 _ansible_diff = $true
174 _ansible_keep_remote_files = $true
175 _ansible_module_name = "ansible_basic_tests"
176 _ansible_no_log = $true
177 _ansible_remote_tmp = "%TEMP%"
178 _ansible_selinux_special_fs = "ignored"
179 _ansible_shell_executable = "ignored"
180 _ansible_socket = "ignored"
181 _ansible_syslog_facility = "ignored"
182 _ansible_tmpdir = $m_tmpdir.ToString()
183 _ansible_verbosity = 3
184 _ansible_version = "2.8.0"
185 }
186 $spec = @{
187 supports_check_mode = $true
188 }
189 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
190 $m.CheckMode | Assert-Equals -Expected $true
191 $m.DebugMode | Assert-Equals -Expected $true
192 $m.DiffMode | Assert-Equals -Expected $true
193 $m.KeepRemoteFiles | Assert-Equals -Expected $true
194 $m.ModuleName | Assert-Equals -Expected "ansible_basic_tests"
195 $m.NoLog | Assert-Equals -Expected $true
196 $m.Verbosity | Assert-Equals -Expected 3
197 $m.AnsibleVersion | Assert-Equals -Expected "2.8.0"
198 $m.Tmpdir | Assert-Equals -Expected $m_tmpdir
199 }
200
201 "Parse complex module options" = {
202 $spec = @{
203 options = @{
204 option_default = @{}
205 missing_option_default = @{}
206 string_option = @{type = "str"}
207 required_option = @{required = $true}
208 missing_choices = @{choices = "a", "b"}
209 choices = @{choices = "a", "b"}
210 one_choice = @{choices = ,"b"}
211 choice_with_default = @{choices = "a", "b"; default = "b"}
212 alias_direct = @{aliases = ,"alias_direct1"}
213 alias_as_alias = @{aliases = "alias_as_alias1", "alias_as_alias2"}
214 bool_type = @{type = "bool"}
215 bool_from_str = @{type = "bool"}
216 dict_type = @{
217 type = "dict"
218 options = @{
219 int_type = @{type = "int"}
220 str_type = @{type = "str"; default = "str_sub_type"}
221 }
222 }
223 dict_type_missing = @{
224 type = "dict"
225 options = @{
226 int_type = @{type = "int"}
227 str_type = @{type = "str"; default = "str_sub_type"}
228 }
229 }
230 dict_type_defaults = @{
231 type = "dict"
232 apply_defaults = $true
233 options = @{
234 int_type = @{type = "int"}
235 str_type = @{type = "str"; default = "str_sub_type"}
236 }
237 }
238 dict_type_json = @{type = "dict"}
239 dict_type_str = @{type = "dict"}
240 float_type = @{type = "float"}
241 int_type = @{type = "int"}
242 json_type = @{type = "json"}
243 json_type_dict = @{type = "json"}
244 list_type = @{type = "list"}
245 list_type_str = @{type = "list"}
246 list_with_int = @{type = "list"; elements = "int"}
247 list_type_single = @{type = "list"}
248 list_with_dict = @{
249 type = "list"
250 elements = "dict"
251 options = @{
252 int_type = @{type = "int"}
253 str_type = @{type = "str"; default = "str_sub_type"}
254 }
255 }
256 path_type = @{type = "path"}
257 path_type_nt = @{type = "path"}
258 path_type_missing = @{type = "path"}
259 raw_type_str = @{type = "raw"}
260 raw_type_int = @{type = "raw"}
261 sid_type = @{type = "sid"}
262 sid_from_name = @{type = "sid"}
263 str_type = @{type = "str"}
264 delegate_type = @{type = [Func[[Object], [UInt64]]]{ [System.UInt64]::Parse($args[0]) }}
265 }
266 }
267 Set-Variable -Name complex_args -Scope Global -Value @{
268 option_default = 1
269 string_option = 1
270 required_option = "required"
271 choices = "a"
272 one_choice = "b"
273 alias_direct = "a"
274 alias_as_alias2 = "a"
275 bool_type = $true
276 bool_from_str = "false"
277 dict_type = @{
278 int_type = "10"
279 }
280 dict_type_json = '{"a":"a","b":1,"c":["a","b"]}'
281 dict_type_str = 'a=a b="b 2" c=c'
282 float_type = "3.14159"
283 int_type = 0
284 json_type = '{"a":"a","b":1,"c":["a","b"]}'
285 json_type_dict = @{
286 a = "a"
287 b = 1
288 c = @("a", "b")
289 }
290 list_type = @("a", "b", 1, 2)
291 list_type_str = "a, b,1,2 "
292 list_with_int = @("1", 2)
293 list_type_single = "single"
294 list_with_dict = @(
295 @{
296 int_type = 2
297 str_type = "dict entry"
298 },
299 @{ int_type = 1 },
300 @{}
301 )
302 path_type = "%SystemRoot%\System32"
303 path_type_nt = "\\?\%SystemRoot%\System32"
304 path_type_missing = "T:\missing\path"
305 raw_type_str = "str"
306 raw_type_int = 1
307 sid_type = "S-1-5-18"
308 sid_from_name = "SYSTEM"
309 str_type = "str"
310 delegate_type = "1234"
311 }
312 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
313
314 $m.Params.option_default | Assert-Equals -Expected "1"
315 $m.Params.option_default.GetType().ToString() | Assert-Equals -Expected "System.String"
316 $m.Params.missing_option_default | Assert-Equals -Expected $null
317 $m.Params.string_option | Assert-Equals -Expected "1"
318 $m.Params.string_option.GetType().ToString() | Assert-Equals -Expected "System.String"
319 $m.Params.required_option | Assert-Equals -Expected "required"
320 $m.Params.required_option.GetType().ToString() | Assert-Equals -Expected "System.String"
321 $m.Params.missing_choices | Assert-Equals -Expected $null
322 $m.Params.choices | Assert-Equals -Expected "a"
323 $m.Params.choices.GetType().ToString() | Assert-Equals -Expected "System.String"
324 $m.Params.one_choice | Assert-Equals -Expected "b"
325 $m.Params.one_choice.GetType().ToString() | Assert-Equals -Expected "System.String"
326 $m.Params.choice_with_default | Assert-Equals -Expected "b"
327 $m.Params.choice_with_default.GetType().ToString() | Assert-Equals -Expected "System.String"
328 $m.Params.alias_direct | Assert-Equals -Expected "a"
329 $m.Params.alias_direct.GetType().ToString() | Assert-Equals -Expected "System.String"
330 $m.Params.alias_as_alias | Assert-Equals -Expected "a"
331 $m.Params.alias_as_alias.GetType().ToString() | Assert-Equals -Expected "System.String"
332 $m.Params.bool_type | Assert-Equals -Expected $true
333 $m.Params.bool_type.GetType().ToString() | Assert-Equals -Expected "System.Boolean"
334 $m.Params.bool_from_str | Assert-Equals -Expected $false
335 $m.Params.bool_from_str.GetType().ToString() | Assert-Equals -Expected "System.Boolean"
336 $m.Params.dict_type | Assert-DictionaryEquals -Expected @{int_type = 10; str_type = "str_sub_type"}
337 $m.Params.dict_type.GetType().ToString() | Assert-Equals -Expected "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
338 $m.Params.dict_type.int_type.GetType().ToString() | Assert-Equals -Expected "System.Int32"
339 $m.Params.dict_type.str_type.GetType().ToString() | Assert-Equals -Expected "System.String"
340 $m.Params.dict_type_missing | Assert-Equals -Expected $null
341 $m.Params.dict_type_defaults | Assert-DictionaryEquals -Expected @{int_type = $null; str_type = "str_sub_type"}
342 $m.Params.dict_type_defaults.GetType().ToString() | Assert-Equals -Expected "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
343 $m.Params.dict_type_defaults.str_type.GetType().ToString() | Assert-Equals -Expected "System.String"
344 $m.Params.dict_type_json | Assert-DictionaryEquals -Expected @{
345 a = "a"
346 b = 1
347 c = @("a", "b")
348 }
349 $m.Params.dict_type_json.GetType().ToString() | Assert-Equals -Expected "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
350 $m.Params.dict_type_json.a.GetType().ToString() | Assert-Equals -Expected "System.String"
351 $m.Params.dict_type_json.b.GetType().ToString() | Assert-Equals -Expected "System.Int32"
352 $m.Params.dict_type_json.c.GetType().ToString() | Assert-Equals -Expected "System.Collections.ArrayList"
353 $m.Params.dict_type_str | Assert-DictionaryEquals -Expected @{a = "a"; b = "b 2"; c = "c"}
354 $m.Params.dict_type_str.GetType().ToString() | Assert-Equals -Expected "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
355 $m.Params.dict_type_str.a.GetType().ToString() | Assert-Equals -Expected "System.String"
356 $m.Params.dict_type_str.b.GetType().ToString() | Assert-Equals -Expected "System.String"
357 $m.Params.dict_type_str.c.GetType().ToString() | Assert-Equals -Expected "System.String"
358 $m.Params.float_type | Assert-Equals -Expected ([System.Single]3.14159)
359 $m.Params.float_type.GetType().ToString() | Assert-Equals -Expected "System.Single"
360 $m.Params.int_type | Assert-Equals -Expected 0
361 $m.Params.int_type.GetType().ToString() | Assert-Equals -Expected "System.Int32"
362 $m.Params.json_type | Assert-Equals -Expected '{"a":"a","b":1,"c":["a","b"]}'
363 $m.Params.json_type.GetType().ToString() | Assert-Equals -Expected "System.String"
364 [Ansible.Basic.AnsibleModule]::FromJson($m.Params.json_type_dict) | Assert-DictionaryEquals -Expected ([Ansible.Basic.AnsibleModule]::FromJson('{"a":"a","b":1,"c":["a","b"]}'))
365 $m.Params.json_type_dict.GetType().ToString() | Assert-Equals -Expected "System.String"
366 $m.Params.list_type.GetType().ToString() | Assert-Equals -Expected "System.Collections.Generic.List``1[System.Object]"
367 $m.Params.list_type.Count | Assert-Equals -Expected 4
368 $m.Params.list_type[0] | Assert-Equals -Expected "a"
369 $m.Params.list_type[0].GetType().FullName | Assert-Equals -Expected "System.String"
370 $m.Params.list_type[1] | Assert-Equals -Expected "b"
371 $m.Params.list_type[1].GetType().FullName | Assert-Equals -Expected "System.String"
372 $m.Params.list_type[2] | Assert-Equals -Expected 1
373 $m.Params.list_type[2].GetType().FullName | Assert-Equals -Expected "System.Int32"
374 $m.Params.list_type[3] | Assert-Equals -Expected 2
375 $m.Params.list_type[3].GetType().FullName | Assert-Equals -Expected "System.Int32"
376 $m.Params.list_type_str.GetType().ToString() | Assert-Equals -Expected "System.Collections.Generic.List``1[System.Object]"
377 $m.Params.list_type_str.Count | Assert-Equals -Expected 4
378 $m.Params.list_type_str[0] | Assert-Equals -Expected "a"
379 $m.Params.list_type_str[0].GetType().FullName | Assert-Equals -Expected "System.String"
380 $m.Params.list_type_str[1] | Assert-Equals -Expected "b"
381 $m.Params.list_type_str[1].GetType().FullName | Assert-Equals -Expected "System.String"
382 $m.Params.list_type_str[2] | Assert-Equals -Expected "1"
383 $m.Params.list_type_str[2].GetType().FullName | Assert-Equals -Expected "System.String"
384 $m.Params.list_type_str[3] | Assert-Equals -Expected "2"
385 $m.Params.list_type_str[3].GetType().FullName | Assert-Equals -Expected "System.String"
386 $m.Params.list_with_int.GetType().ToString() | Assert-Equals -Expected "System.Collections.Generic.List``1[System.Object]"
387 $m.Params.list_with_int.Count | Assert-Equals -Expected 2
388 $m.Params.list_with_int[0] | Assert-Equals -Expected 1
389 $m.Params.list_with_int[0].GetType().FullName | Assert-Equals -Expected "System.Int32"
390 $m.Params.list_with_int[1] | Assert-Equals -Expected 2
391 $m.Params.list_with_int[1].GetType().FullName | Assert-Equals -Expected "System.Int32"
392 $m.Params.list_type_single.GetType().ToString() | Assert-Equals -Expected "System.Collections.Generic.List``1[System.Object]"
393 $m.Params.list_type_single.Count | Assert-Equals -Expected 1
394 $m.Params.list_type_single[0] | Assert-Equals -Expected "single"
395 $m.Params.list_type_single[0].GetType().FullName | Assert-Equals -Expected "System.String"
396 $m.Params.list_with_dict.GetType().FullName.StartsWith("System.Collections.Generic.List``1[[System.Object") | Assert-Equals -Expected $true
397 $m.Params.list_with_dict.Count | Assert-Equals -Expected 3
398 $m.Params.list_with_dict[0].GetType().FullName.StartsWith("System.Collections.Generic.Dictionary``2[[System.String") | Assert-Equals -Expected $true
399 $m.Params.list_with_dict[0] | Assert-DictionaryEquals -Expected @{int_type = 2; str_type = "dict entry"}
400 $m.Params.list_with_dict[0].int_type.GetType().FullName.ToString() | Assert-Equals -Expected "System.Int32"
401 $m.Params.list_with_dict[0].str_type.GetType().FullName.ToString() | Assert-Equals -Expected "System.String"
402 $m.Params.list_with_dict[1].GetType().FullName.StartsWith("System.Collections.Generic.Dictionary``2[[System.String") | Assert-Equals -Expected $true
403 $m.Params.list_with_dict[1] | Assert-DictionaryEquals -Expected @{int_type = 1; str_type = "str_sub_type"}
404 $m.Params.list_with_dict[1].int_type.GetType().FullName.ToString() | Assert-Equals -Expected "System.Int32"
405 $m.Params.list_with_dict[1].str_type.GetType().FullName.ToString() | Assert-Equals -Expected "System.String"
406 $m.Params.list_with_dict[2].GetType().FullName.StartsWith("System.Collections.Generic.Dictionary``2[[System.String") | Assert-Equals -Expected $true
407 $m.Params.list_with_dict[2] | Assert-DictionaryEquals -Expected @{int_type = $null; str_type = "str_sub_type"}
408 $m.Params.list_with_dict[2].str_type.GetType().FullName.ToString() | Assert-Equals -Expected "System.String"
409 $m.Params.path_type | Assert-Equals -Expected "$($env:SystemRoot)\System32"
410 $m.Params.path_type.GetType().ToString() | Assert-Equals -Expected "System.String"
411 $m.Params.path_type_nt | Assert-Equals -Expected "\\?\%SystemRoot%\System32"
412 $m.Params.path_type_nt.GetType().ToString() | Assert-Equals -Expected "System.String"
413 $m.Params.path_type_missing | Assert-Equals -Expected "T:\missing\path"
414 $m.Params.path_type_missing.GetType().ToString() | Assert-Equals -Expected "System.String"
415 $m.Params.raw_type_str | Assert-Equals -Expected "str"
416 $m.Params.raw_type_str.GetType().FullName | Assert-Equals -Expected "System.String"
417 $m.Params.raw_type_int | Assert-Equals -Expected 1
418 $m.Params.raw_type_int.GetType().FullName | Assert-Equals -Expected "System.Int32"
419 $m.Params.sid_type | Assert-Equals -Expected (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList "S-1-5-18")
420 $m.Params.sid_type.GetType().ToString() | Assert-Equals -Expected "System.Security.Principal.SecurityIdentifier"
421 $m.Params.sid_from_name | Assert-Equals -Expected (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList "S-1-5-18")
422 $m.Params.sid_from_name.GetType().ToString() | Assert-Equals -Expected "System.Security.Principal.SecurityIdentifier"
423 $m.Params.str_type | Assert-Equals -Expected "str"
424 $m.Params.str_type.GetType().ToString() | Assert-Equals -Expected "System.String"
425 $m.Params.delegate_type | Assert-Equals -Expected 1234
426 $m.Params.delegate_type.GetType().ToString() | Assert-Equals -Expected "System.UInt64"
427
428 $failed = $false
429 try {
430 $m.ExitJson()
431 } catch [System.Management.Automation.RuntimeException] {
432 $failed = $true
433 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
434 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
435 }
436 $failed | Assert-Equals -Expected $true
437
438 $expected_module_args = @{
439 option_default = "1"
440 missing_option_default = $null
441 string_option = "1"
442 required_option = "required"
443 missing_choices = $null
444 choices = "a"
445 one_choice = "b"
446 choice_with_default = "b"
447 alias_direct = "a"
448 alias_as_alias = "a"
449 alias_as_alias2 = "a"
450 bool_type = $true
451 bool_from_str = $false
452 dict_type = @{
453 int_type = 10
454 str_type = "str_sub_type"
455 }
456 dict_type_missing = $null
457 dict_type_defaults = @{
458 int_type = $null
459 str_type = "str_sub_type"
460 }
461 dict_type_json = @{
462 a = "a"
463 b = 1
464 c = @("a", "b")
465 }
466 dict_type_str = @{
467 a = "a"
468 b = "b 2"
469 c = "c"
470 }
471 float_type = 3.14159
472 int_type = 0
473 json_type = $m.Params.json_type.ToString()
474 json_type_dict = $m.Params.json_type_dict.ToString()
475 list_type = @("a", "b", 1, 2)
476 list_type_str = @("a", "b", "1", "2")
477 list_with_int = @(1, 2)
478 list_type_single = @("single")
479 list_with_dict = @(
480 @{
481 int_type = 2
482 str_type = "dict entry"
483 },
484 @{
485 int_type = 1
486 str_type = "str_sub_type"
487 },
488 @{
489 int_type = $null
490 str_type = "str_sub_type"
491 }
492 )
493 path_type = "$($env:SystemRoot)\System32"
494 path_type_nt = "\\?\%SystemRoot%\System32"
495 path_type_missing = "T:\missing\path"
496 raw_type_str = "str"
497 raw_type_int = 1
498 sid_type = "S-1-5-18"
499 sid_from_name = "S-1-5-18"
500 str_type = "str"
501 delegate_type = 1234
502 }
503 $actual.Keys.Count | Assert-Equals -Expected 2
504 $actual.changed | Assert-Equals -Expected $false
505 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $expected_module_args}
506 }
507
508 "Parse module args with list elements and delegate type" = {
509 $spec = @{
510 options = @{
511 list_delegate_type = @{
512 type = "list"
513 elements = [Func[[Object], [UInt16]]]{ [System.UInt16]::Parse($args[0]) }
514 }
515 }
516 }
517 Set-Variable -Name complex_args -Scope Global -Value @{
518 list_delegate_type = @(
519 "1234",
520 4321
521 )
522 }
523 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
524 $m.Params.list_delegate_type.GetType().Name | Assert-Equals -Expected 'List`1'
525 $m.Params.list_delegate_type[0].GetType().FullName | Assert-Equals -Expected "System.UInt16"
526 $m.Params.list_delegate_Type[1].GetType().FullName | Assert-Equals -Expected "System.UInt16"
527
528 $failed = $false
529 try {
530 $m.ExitJson()
531 } catch [System.Management.Automation.RuntimeException] {
532 $failed = $true
533 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
534 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
535 }
536 $failed | Assert-Equals -Expected $true
537
538 $expected_module_args = @{
539 list_delegate_type = @(
540 1234,
541 4321
542 )
543 }
544 $actual.Keys.Count | Assert-Equals -Expected 2
545 $actual.changed | Assert-Equals -Expected $false
546 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $expected_module_args}
547 }
548
549 "Parse module args with case insensitive input" = {
550 $spec = @{
551 options = @{
552 option1 = @{ type = "int"; required = $true }
553 }
554 }
555 Set-Variable -Name complex_args -Scope Global -Value @{
556 _ansible_module_name = "win_test"
557 Option1 = "1"
558 }
559
560 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
561 # Verifies the case of the params key is set to the module spec not actual input
562 $m.Params.Keys | Assert-Equals -Expected @("option1")
563 $m.Params.option1 | Assert-Equals -Expected 1
564
565 # Verifies the type conversion happens even on a case insensitive match
566 $m.Params.option1.GetType().FullName | Assert-Equals -Expected "System.Int32"
567
568 $failed = $false
569 try {
570 $m.ExitJson()
571 } catch [System.Management.Automation.RuntimeException] {
572 $failed = $true
573 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
574 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
575 }
576 $failed | Assert-Equals -Expected $true
577
578 $expected_warnings = "Parameters for (win_test) was a case insensitive match: Option1. "
579 $expected_warnings += "Module options will become case sensitive in a future Ansible release. "
580 $expected_warnings += "Supported parameters include: option1"
581
582 $expected = @{
583 changed = $false
584 invocation = @{
585 module_args = @{
586 option1 = 1
587 }
588 }
589 # We have disabled the warning for now
590 #warnings = @($expected_warnings)
591 }
592 $actual | Assert-DictionaryEquals -Expected $expected
593 }
594
595 "No log values" = {
596 $spec = @{
597 options = @{
598 username = @{type = "str"}
599 password = @{type = "str"; no_log = $true}
600 password2 = @{type = "int"; no_log = $true}
601 dict = @{type = "dict"}
602 }
603 }
604 Set-Variable -Name complex_args -Scope Global -Value @{
605 _ansible_module_name = "test_no_log"
606 username = "user - pass - name"
607 password = "pass"
608 password2 = 1234
609 dict = @{
610 data = "Oops this is secret: pass"
611 dict = @{
612 pass = "plain"
613 hide = "pass"
614 sub_hide = "password"
615 int_hide = 123456
616 }
617 list = @(
618 "pass",
619 "password",
620 1234567,
621 "pa ss",
622 @{
623 pass = "plain"
624 hide = "pass"
625 sub_hide = "password"
626 int_hide = 123456
627 }
628 )
629 custom = "pass"
630 }
631 }
632
633 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
634 $m.Result.data = $complex_args.dict
635
636 # verify params internally aren't masked
637 $m.Params.username | Assert-Equals -Expected "user - pass - name"
638 $m.Params.password | Assert-Equals -Expected "pass"
639 $m.Params.password2 | Assert-Equals -Expected 1234
640 $m.Params.dict.custom | Assert-Equals -Expected "pass"
641
642 $failed = $false
643 try {
644 $m.ExitJson()
645 } catch [System.Management.Automation.RuntimeException] {
646 $failed = $true
647 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
648 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
649 }
650 $failed | Assert-Equals -Expected $true
651
652 # verify no_log params are masked in invocation
653 $expected = @{
654 invocation = @{
655 module_args = @{
656 password2 = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
657 dict = @{
658 dict = @{
659 pass = "plain"
660 hide = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
661 sub_hide = "********word"
662 int_hide = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
663 }
664 custom = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
665 list = @(
666 "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
667 "********word",
668 "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
669 "pa ss",
670 @{
671 pass = "plain"
672 hide = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
673 sub_hide = "********word"
674 int_hide = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
675 }
676 )
677 data = "Oops this is secret: ********"
678 }
679 username = "user - ******** - name"
680 password = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
681 }
682 }
683 changed = $false
684 data = $complex_args.dict
685 }
686 $actual | Assert-DictionaryEquals -Expected $expected
687
688 $expected_event = @'
689 test_no_log - Invoked with:
690 username: user - ******** - name
691 dict: dict: sub_hide: ****word
692 pass: plain
693 int_hide: ********56
694 hide: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
695 data: Oops this is secret: ********
696 custom: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
697 list:
698 - VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
699 - ********word
700 - ********567
701 - pa ss
702 - sub_hide: ********word
703 pass: plain
704 int_hide: ********56
705 hide: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
706 password2: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
707 password: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
708 '@
709 $actual_event = (Get-EventLog -LogName Application -Source Ansible -Newest 1).Message
710 $actual_event | Assert-DictionaryEquals -Expected $expected_event
711 }
712
713 "No log value with an empty string" = {
714 $spec = @{
715 options = @{
716 password1 = @{type = "str"; no_log = $true}
717 password2 = @{type = "str"; no_log = $true}
718 }
719 }
720 Set-Variable -Name complex_args -Scope Global -Value @{
721 _ansible_module_name = "test_no_log"
722 password1 = ""
723 }
724
725 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
726 $m.Result.data = $complex_args.dict
727
728 # verify params internally aren't masked
729 $m.Params.password1 | Assert-Equals -Expected ""
730 $m.Params.password2 | Assert-Equals -Expected $null
731
732 $failed = $false
733 try {
734 $m.ExitJson()
735 } catch [System.Management.Automation.RuntimeException] {
736 $failed = $true
737 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
738 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
739 }
740 $failed | Assert-Equals -Expected $true
741
742 $expected = @{
743 invocation = @{
744 module_args = @{
745 password1 = ""
746 password2 = $null
747 }
748 }
749 changed = $false
750 data = $complex_args.dict
751 }
752 $actual | Assert-DictionaryEquals -Expected $expected
753 }
754
755 "Removed in version" = {
756 $spec = @{
757 options = @{
758 removed1 = @{removed_in_version = "2.1"}
759 removed2 = @{removed_in_version = "2.2"}
760 removed3 = @{removed_in_version = "2.3"; removed_from_collection = "ansible.builtin"}
761 }
762 }
763 Set-Variable -Name complex_args -Scope Global -Value @{
764 removed1 = "value"
765 removed3 = "value"
766 }
767
768 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
769
770 $failed = $false
771 try {
772 $m.ExitJson()
773 } catch [System.Management.Automation.RuntimeException] {
774 $failed = $true
775 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
776 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
777 }
778 $failed | Assert-Equals -Expected $true
779
780 $expected = @{
781 changed = $false
782 invocation = @{
783 module_args = @{
784 removed1 = "value"
785 removed2 = $null
786 removed3 = "value"
787 }
788 }
789 deprecations = @(
790 @{
791 msg = "Param 'removed3' is deprecated. See the module docs for more information"
792 version = "2.3"
793 collection_name = "ansible.builtin"
794 },
795 @{
796 msg = "Param 'removed1' is deprecated. See the module docs for more information"
797 version = "2.1"
798 collection_name = $null
799 }
800 )
801 }
802 $actual | Assert-DictionaryEquals -Expected $expected
803 }
804
805 "Removed at date" = {
806 $spec = @{
807 options = @{
808 removed1 = @{removed_at_date = [DateTime]"2020-03-10"}
809 removed2 = @{removed_at_date = [DateTime]"2020-03-11"}
810 removed3 = @{removed_at_date = [DateTime]"2020-06-07"; removed_from_collection = "ansible.builtin"}
811 }
812 }
813 Set-Variable -Name complex_args -Scope Global -Value @{
814 removed1 = "value"
815 removed3 = "value"
816 }
817
818 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
819
820 $failed = $false
821 try {
822 $m.ExitJson()
823 } catch [System.Management.Automation.RuntimeException] {
824 $failed = $true
825 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
826 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
827 }
828 $failed | Assert-Equals -Expected $true
829
830 $expected = @{
831 changed = $false
832 invocation = @{
833 module_args = @{
834 removed1 = "value"
835 removed2 = $null
836 removed3 = "value"
837 }
838 }
839 deprecations = @(
840 @{
841 msg = "Param 'removed3' is deprecated. See the module docs for more information"
842 date = "2020-06-07"
843 collection_name = "ansible.builtin"
844 },
845 @{
846 msg = "Param 'removed1' is deprecated. See the module docs for more information"
847 date = "2020-03-10"
848 collection_name = $null
849 }
850 )
851 }
852 $actual | Assert-DictionaryEquals -Expected $expected
853 }
854
855 "Deprecated aliases" = {
856 $spec = @{
857 options = @{
858 option1 = @{ type = "str"; aliases = "alias1"; deprecated_aliases = @(@{name = "alias1"; version = "2.10"}) }
859 option2 = @{ type = "str"; aliases = "alias2"; deprecated_aliases = @(@{name = "alias2"; version = "2.11"}) }
860 option3 = @{
861 type = "dict"
862 options = @{
863 option1 = @{ type = "str"; aliases = "alias1"; deprecated_aliases = @(@{name = "alias1"; version = "2.10"}) }
864 option2 = @{ type = "str"; aliases = "alias2"; deprecated_aliases = @(@{name = "alias2"; version = "2.11"}) }
865 option3 = @{ type = "str"; aliases = "alias3"; deprecated_aliases = @(@{name = "alias3"; version = "2.12"; collection_name = "ansible.builtin"}) }
866 option4 = @{ type = "str"; aliases = "alias4"; deprecated_aliases = @(@{name = "alias4"; date = [DateTime]"2020-03-11"}) }
867 option5 = @{ type = "str"; aliases = "alias5"; deprecated_aliases = @(@{name = "alias5"; date = [DateTime]"2020-03-09"}) }
868 option6 = @{ type = "str"; aliases = "alias6"; deprecated_aliases = @(@{name = "alias6"; date = [DateTime]"2020-06-01"; collection_name = "ansible.builtin"}) }
869 }
870 }
871 option4 = @{ type = "str"; aliases = "alias4"; deprecated_aliases = @(@{name = "alias4"; date = [DateTime]"2020-03-10"}) }
872 option5 = @{ type = "str"; aliases = "alias5"; deprecated_aliases = @(@{name = "alias5"; date = [DateTime]"2020-03-12"}) }
873 option6 = @{ type = "str"; aliases = "alias6"; deprecated_aliases = @(@{name = "alias6"; version = "2.12"; collection_name = "ansible.builtin"}) }
874 option7 = @{ type = "str"; aliases = "alias7"; deprecated_aliases = @(@{name = "alias7"; date = [DateTime]"2020-06-07"; collection_name = "ansible.builtin"}) }
875 }
876 }
877
878 Set-Variable -Name complex_args -Scope Global -Value @{
879 alias1 = "alias1"
880 option2 = "option2"
881 option3 = @{
882 option1 = "option1"
883 alias2 = "alias2"
884 alias3 = "alias3"
885 option4 = "option4"
886 alias5 = "alias5"
887 alias6 = "alias6"
888 }
889 option4 = "option4"
890 alias5 = "alias5"
891 alias6 = "alias6"
892 alias7 = "alias7"
893 }
894
895 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
896 $failed = $false
897 try {
898 $m.ExitJson()
899 } catch [System.Management.Automation.RuntimeException] {
900 $failed = $true
901 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
902 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
903 }
904 $failed | Assert-Equals -Expected $true
905
906 $expected = @{
907 changed = $false
908 invocation = @{
909 module_args = @{
910 alias1 = "alias1"
911 option1 = "alias1"
912 option2 = "option2"
913 option3 = @{
914 option1 = "option1"
915 option2 = "alias2"
916 alias2 = "alias2"
917 option3 = "alias3"
918 alias3 = "alias3"
919 option4 = "option4"
920 option5 = "alias5"
921 alias5 = "alias5"
922 option6 = "alias6"
923 alias6 = "alias6"
924 }
925 option4 = "option4"
926 option5 = "alias5"
927 alias5 = "alias5"
928 option6 = "alias6"
929 alias6 = "alias6"
930 option7 = "alias7"
931 alias7 = "alias7"
932 }
933 }
934 deprecations = @(
935 @{
936 msg = "Alias 'alias7' is deprecated. See the module docs for more information"
937 date = "2020-06-07"
938 collection_name = "ansible.builtin"
939 },
940 @{
941 msg = "Alias 'alias1' is deprecated. See the module docs for more information"
942 version = "2.10"
943 collection_name = $null
944 },
945 @{
946 msg = "Alias 'alias5' is deprecated. See the module docs for more information"
947 date = "2020-03-12"
948 collection_name = $null
949 },
950 @{
951 msg = "Alias 'alias6' is deprecated. See the module docs for more information"
952 version = "2.12"
953 collection_name = "ansible.builtin"
954 },
955 @{
956 msg = "Alias 'alias2' is deprecated. See the module docs for more information - found in option3"
957 version = "2.11"
958 collection_name = $null
959 },
960 @{
961 msg = "Alias 'alias5' is deprecated. See the module docs for more information - found in option3"
962 date = "2020-03-09"
963 collection_name = $null
964 },
965 @{
966 msg = "Alias 'alias3' is deprecated. See the module docs for more information - found in option3"
967 version = "2.12"
968 collection_name = "ansible.builtin"
969 },
970 @{
971 msg = "Alias 'alias6' is deprecated. See the module docs for more information - found in option3"
972 date = "2020-06-01"
973 collection_name = "ansible.builtin"
974 }
975 )
976 }
977 $actual | Assert-DictionaryEquals -Expected $expected
978 }
979
980 "Required by - single value" = {
981 $spec = @{
982 options = @{
983 option1 = @{type = "str"}
984 option2 = @{type = "str"}
985 option3 = @{type = "str"}
986 }
987 required_by = @{
988 option1 = "option2"
989 }
990 }
991 Set-Variable -Name complex_args -Scope Global -Value @{
992 option1 = "option1"
993 option2 = "option2"
994 }
995
996 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
997
998 $failed = $false
999 try {
1000 $m.ExitJson()
1001 } catch [System.Management.Automation.RuntimeException] {
1002 $failed = $true
1003 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
1004 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1005 }
1006 $failed | Assert-Equals -Expected $true
1007
1008 $expected = @{
1009 changed = $false
1010 invocation = @{
1011 module_args = @{
1012 option1 = "option1"
1013 option2 = "option2"
1014 option3 = $null
1015 }
1016 }
1017 }
1018 $actual | Assert-DictionaryEquals -Expected $expected
1019 }
1020
1021 "Required by - multiple values" = {
1022 $spec = @{
1023 options = @{
1024 option1 = @{type = "str"}
1025 option2 = @{type = "str"}
1026 option3 = @{type = "str"}
1027 }
1028 required_by = @{
1029 option1 = "option2", "option3"
1030 }
1031 }
1032 Set-Variable -Name complex_args -Scope Global -Value @{
1033 option1 = "option1"
1034 option2 = "option2"
1035 option3 = "option3"
1036 }
1037
1038 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1039
1040 $failed = $false
1041 try {
1042 $m.ExitJson()
1043 } catch [System.Management.Automation.RuntimeException] {
1044 $failed = $true
1045 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
1046 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1047 }
1048 $failed | Assert-Equals -Expected $true
1049
1050 $expected = @{
1051 changed = $false
1052 invocation = @{
1053 module_args = @{
1054 option1 = "option1"
1055 option2 = "option2"
1056 option3 = "option3"
1057 }
1058 }
1059 }
1060 $actual | Assert-DictionaryEquals -Expected $expected
1061 }
1062
1063 "Required by explicit null" = {
1064 $spec = @{
1065 options = @{
1066 option1 = @{type = "str"}
1067 option2 = @{type = "str"}
1068 option3 = @{type = "str"}
1069 }
1070 required_by = @{
1071 option1 = "option2"
1072 }
1073 }
1074 Set-Variable -Name complex_args -Scope Global -Value @{
1075 option1 = "option1"
1076 option2 = $null
1077 }
1078
1079 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1080
1081 $failed = $false
1082 try {
1083 $m.ExitJson()
1084 } catch [System.Management.Automation.RuntimeException] {
1085 $failed = $true
1086 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
1087 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1088 }
1089 $failed | Assert-Equals -Expected $true
1090
1091 $expected = @{
1092 changed = $false
1093 invocation = @{
1094 module_args = @{
1095 option1 = "option1"
1096 option2 = $null
1097 option3 = $null
1098 }
1099 }
1100 }
1101 $actual | Assert-DictionaryEquals -Expected $expected
1102 }
1103
1104 "Required by failed - single value" = {
1105 $spec = @{
1106 options = @{
1107 option1 = @{type = "str"}
1108 option2 = @{type = "str"}
1109 option3 = @{type = "str"}
1110 }
1111 required_by = @{
1112 option1 = "option2"
1113 }
1114 }
1115 Set-Variable -Name complex_args -Scope Global -Value @{
1116 option1 = "option1"
1117 }
1118
1119 $failed = $false
1120 try {
1121 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1122 } catch [System.Management.Automation.RuntimeException] {
1123 $failed = $true
1124 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1125 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1126 }
1127 $failed | Assert-Equals -Expected $true
1128
1129 $expected = @{
1130 changed = $false
1131 failed = $true
1132 invocation = @{
1133 module_args = @{
1134 option1 = "option1"
1135 }
1136 }
1137 msg = "missing parameter(s) required by 'option1': option2"
1138 }
1139 $actual | Assert-DictionaryEquals -Expected $expected
1140 }
1141
1142 "Required by failed - multiple values" = {
1143 $spec = @{
1144 options = @{
1145 option1 = @{type = "str"}
1146 option2 = @{type = "str"}
1147 option3 = @{type = "str"}
1148 }
1149 required_by = @{
1150 option1 = "option2", "option3"
1151 }
1152 }
1153 Set-Variable -Name complex_args -Scope Global -Value @{
1154 option1 = "option1"
1155 }
1156
1157 $failed = $false
1158 try {
1159 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1160 } catch [System.Management.Automation.RuntimeException] {
1161 $failed = $true
1162 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1163 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1164 }
1165 $failed | Assert-Equals -Expected $true
1166
1167 $expected = @{
1168 changed = $false
1169 failed = $true
1170 invocation = @{
1171 module_args = @{
1172 option1 = "option1"
1173 }
1174 }
1175 msg = "missing parameter(s) required by 'option1': option2, option3"
1176 }
1177 $actual | Assert-DictionaryEquals -Expected $expected
1178 }
1179
1180 "Debug without debug set" = {
1181 Set-Variable -Name complex_args -Scope Global -Value @{
1182 _ansible_debug = $false
1183 }
1184 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1185 $m.Debug("debug message")
1186 $actual_event = (Get-EventLog -LogName Application -Source Ansible -Newest 1).Message
1187 $actual_event | Assert-Equals -Expected "undefined win module - Invoked with:`r`n "
1188 }
1189
1190 "Debug with debug set" = {
1191 Set-Variable -Name complex_args -Scope Global -Value @{
1192 _ansible_debug = $true
1193 }
1194 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1195 $m.Debug("debug message")
1196 $actual_event = (Get-EventLog -LogName Application -Source Ansible -Newest 1).Message
1197 $actual_event | Assert-Equals -Expected "undefined win module - [DEBUG] debug message"
1198 }
1199
1200 "Deprecate and warn with version" = {
1201 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1202 $m.Deprecate("message", "2.7")
1203 $actual_deprecate_event_1 = Get-EventLog -LogName Application -Source Ansible -Newest 1
1204 $m.Deprecate("message w collection", "2.8", "ansible.builtin")
1205 $actual_deprecate_event_2 = Get-EventLog -LogName Application -Source Ansible -Newest 1
1206 $m.Warn("warning")
1207 $actual_warn_event = Get-EventLog -LogName Application -Source Ansible -Newest 1
1208
1209 $actual_deprecate_event_1.Message | Assert-Equals -Expected "undefined win module - [DEPRECATION WARNING] message 2.7"
1210 $actual_deprecate_event_2.Message | Assert-Equals -Expected "undefined win module - [DEPRECATION WARNING] message w collection 2.8"
1211 $actual_warn_event.EntryType | Assert-Equals -Expected "Warning"
1212 $actual_warn_event.Message | Assert-Equals -Expected "undefined win module - [WARNING] warning"
1213
1214 $failed = $false
1215 try {
1216 $m.ExitJson()
1217 } catch [System.Management.Automation.RuntimeException] {
1218 $failed = $true
1219 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
1220 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1221 }
1222 $failed | Assert-Equals -Expected $true
1223
1224 $expected = @{
1225 changed = $false
1226 invocation = @{
1227 module_args = @{}
1228 }
1229 warnings = @("warning")
1230 deprecations = @(
1231 @{msg = "message"; version = "2.7"; collection_name = $null},
1232 @{msg = "message w collection"; version = "2.8"; collection_name = "ansible.builtin"}
1233 )
1234 }
1235 $actual | Assert-DictionaryEquals -Expected $expected
1236 }
1237
1238 "Deprecate and warn with date" = {
1239 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1240 $m.Deprecate("message", [DateTime]"2020-01-01")
1241 $actual_deprecate_event_1 = Get-EventLog -LogName Application -Source Ansible -Newest 1
1242 $m.Deprecate("message w collection", [DateTime]"2020-01-02", "ansible.builtin")
1243 $actual_deprecate_event_2 = Get-EventLog -LogName Application -Source Ansible -Newest 1
1244 $m.Warn("warning")
1245 $actual_warn_event = Get-EventLog -LogName Application -Source Ansible -Newest 1
1246
1247 $actual_deprecate_event_1.Message | Assert-Equals -Expected "undefined win module - [DEPRECATION WARNING] message 2020-01-01"
1248 $actual_deprecate_event_2.Message | Assert-Equals -Expected "undefined win module - [DEPRECATION WARNING] message w collection 2020-01-02"
1249 $actual_warn_event.EntryType | Assert-Equals -Expected "Warning"
1250 $actual_warn_event.Message | Assert-Equals -Expected "undefined win module - [WARNING] warning"
1251
1252 $failed = $false
1253 try {
1254 $m.ExitJson()
1255 } catch [System.Management.Automation.RuntimeException] {
1256 $failed = $true
1257 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
1258 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1259 }
1260 $failed | Assert-Equals -Expected $true
1261
1262 $expected = @{
1263 changed = $false
1264 invocation = @{
1265 module_args = @{}
1266 }
1267 warnings = @("warning")
1268 deprecations = @(
1269 @{msg = "message"; date = "2020-01-01"; collection_name = $null},
1270 @{msg = "message w collection"; date = "2020-01-02"; collection_name = "ansible.builtin"}
1271 )
1272 }
1273 $actual | Assert-DictionaryEquals -Expected $expected
1274 }
1275
1276 "FailJson with message" = {
1277 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1278
1279 $failed = $false
1280 try {
1281 $m.FailJson("fail message")
1282 } catch [System.Management.Automation.RuntimeException] {
1283 $failed = $true
1284 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1285 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1286 }
1287 $failed | Assert-Equals -Expected $failed
1288
1289 $expected = @{
1290 changed = $false
1291 invocation = @{
1292 module_args = @{}
1293 }
1294 failed = $true
1295 msg = "fail message"
1296 }
1297 $actual | Assert-DictionaryEquals -Expected $expected
1298 }
1299
1300 "FailJson with Exception" = {
1301 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1302
1303 try {
1304 [System.IO.Path]::GetFullPath($null)
1305 } catch {
1306 $excp = $_.Exception
1307 }
1308
1309 $failed = $false
1310 try {
1311 $m.FailJson("fail message", $excp)
1312 } catch [System.Management.Automation.RuntimeException] {
1313 $failed = $true
1314 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1315 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1316 }
1317 $failed | Assert-Equals -Expected $failed
1318
1319 $expected = @{
1320 changed = $false
1321 invocation = @{
1322 module_args = @{}
1323 }
1324 failed = $true
1325 msg = "fail message"
1326 }
1327 $actual | Assert-DictionaryEquals -Expected $expected
1328 }
1329
1330 "FailJson with ErrorRecord" = {
1331 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1332
1333 try {
1334 Get-Item -LiteralPath $null
1335 } catch {
1336 $error_record = $_
1337 }
1338
1339 $failed = $false
1340 try {
1341 $m.FailJson("fail message", $error_record)
1342 } catch [System.Management.Automation.RuntimeException] {
1343 $failed = $true
1344 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1345 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1346 }
1347 $failed | Assert-Equals -Expected $failed
1348
1349 $expected = @{
1350 changed = $false
1351 invocation = @{
1352 module_args = @{}
1353 }
1354 failed = $true
1355 msg = "fail message"
1356 }
1357 $actual | Assert-DictionaryEquals -Expected $expected
1358 }
1359
1360 "FailJson with Exception and verbosity 3" = {
1361 Set-Variable -Name complex_args -Scope Global -Value @{
1362 _ansible_verbosity = 3
1363 }
1364 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1365
1366 try {
1367 [System.IO.Path]::GetFullPath($null)
1368 } catch {
1369 $excp = $_.Exception
1370 }
1371
1372 $failed = $false
1373 try {
1374 $m.FailJson("fail message", $excp)
1375 } catch [System.Management.Automation.RuntimeException] {
1376 $failed = $true
1377 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1378 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1379 }
1380 $failed | Assert-Equals -Expected $failed
1381
1382 $actual.changed | Assert-Equals -Expected $false
1383 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = @{}}
1384 $actual.failed | Assert-Equals -Expected $true
1385 $actual.msg | Assert-Equals -Expected "fail message"
1386 $actual.exception.Contains('System.Management.Automation.MethodInvocationException: Exception calling "GetFullPath" with "1" argument(s)') | Assert-Equals -Expected $true
1387 }
1388
1389 "FailJson with ErrorRecord and verbosity 3" = {
1390 Set-Variable -Name complex_args -Scope Global -Value @{
1391 _ansible_verbosity = 3
1392 }
1393 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1394
1395 try {
1396 Get-Item -LiteralPath $null
1397 } catch {
1398 $error_record = $_
1399 }
1400
1401 $failed = $false
1402 try {
1403 $m.FailJson("fail message", $error_record)
1404 } catch [System.Management.Automation.RuntimeException] {
1405 $failed = $true
1406 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1407 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1408 }
1409 $failed | Assert-Equals -Expected $failed
1410
1411 $actual.changed | Assert-Equals -Expected $false
1412 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = @{}}
1413 $actual.failed | Assert-Equals -Expected $true
1414 $actual.msg | Assert-Equals -Expected "fail message"
1415 $actual.exception.Contains("Cannot bind argument to parameter 'LiteralPath' because it is null") | Assert-Equals -Expected $true
1416 $actual.exception.Contains("+ Get-Item -LiteralPath `$null") | Assert-Equals -Expected $true
1417 $actual.exception.Contains("ScriptStackTrace:") | Assert-Equals -Expected $true
1418 }
1419
1420 "Diff entry without diff set" = {
1421 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1422 $m.Diff.before = @{a = "a"}
1423 $m.Diff.after = @{b = "b"}
1424
1425 $failed = $false
1426 try {
1427 $m.ExitJson()
1428 } catch [System.Management.Automation.RuntimeException] {
1429 $failed = $true
1430 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
1431 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1432 }
1433 $failed | Assert-Equals -Expected $failed
1434
1435 $expected = @{
1436 changed = $false
1437 invocation = @{
1438 module_args = @{}
1439 }
1440 }
1441 $actual | Assert-DictionaryEquals -Expected $expected
1442 }
1443
1444 "Diff entry with diff set" = {
1445 Set-Variable -Name complex_args -Scope Global -Value @{
1446 _ansible_diff = $true
1447 }
1448 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1449 $m.Diff.before = @{a = "a"}
1450 $m.Diff.after = @{b = "b"}
1451
1452 $failed = $false
1453 try {
1454 $m.ExitJson()
1455 } catch [System.Management.Automation.RuntimeException] {
1456 $failed = $true
1457 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
1458 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1459 }
1460 $failed | Assert-Equals -Expected $failed
1461
1462 $expected = @{
1463 changed = $false
1464 invocation = @{
1465 module_args = @{}
1466 }
1467 diff = @{
1468 before = @{a = "a"}
1469 after = @{b = "b"}
1470 }
1471 }
1472 $actual | Assert-DictionaryEquals -Expected $expected
1473 }
1474
1475 "ParseBool tests" = {
1476 $mapping = New-Object -TypeName 'System.Collections.Generic.Dictionary`2[[Object], [Bool]]'
1477 $mapping.Add("y", $true)
1478 $mapping.Add("Y", $true)
1479 $mapping.Add("yes", $true)
1480 $mapping.Add("Yes", $true)
1481 $mapping.Add("on", $true)
1482 $mapping.Add("On", $true)
1483 $mapping.Add("1", $true)
1484 $mapping.Add(1, $true)
1485 $mapping.Add("true", $true)
1486 $mapping.Add("True", $true)
1487 $mapping.Add("t", $true)
1488 $mapping.Add("T", $true)
1489 $mapping.Add("1.0", $true)
1490 $mapping.Add(1.0, $true)
1491 $mapping.Add($true, $true)
1492 $mapping.Add("n", $false)
1493 $mapping.Add("N", $false)
1494 $mapping.Add("no", $false)
1495 $mapping.Add("No", $false)
1496 $mapping.Add("off", $false)
1497 $mapping.Add("Off", $false)
1498 $mapping.Add("0", $false)
1499 $mapping.Add(0, $false)
1500 $mapping.Add("false", $false)
1501 $mapping.Add("False", $false)
1502 $mapping.Add("f", $false)
1503 $mapping.Add("F", $false)
1504 $mapping.Add("0.0", $false)
1505 $mapping.Add(0.0, $false)
1506 $mapping.Add($false, $false)
1507
1508 foreach ($map in $mapping.GetEnumerator()) {
1509 $expected = $map.Value
1510 $actual = [Ansible.Basic.AnsibleModule]::ParseBool($map.Key)
1511 $actual | Assert-Equals -Expected $expected
1512 $actual.GetType().FullName | Assert-Equals -Expected "System.Boolean"
1513 }
1514
1515 $fail_bools = @(
1516 "falsey",
1517 "abc",
1518 2,
1519 "2",
1520 -1
1521 )
1522 foreach ($fail_bool in $fail_bools) {
1523 $failed = $false
1524 try {
1525 [Ansible.Basic.AnsibleModule]::ParseBool($fail_bool)
1526 } catch {
1527 $failed = $true
1528 $_.Exception.Message.Contains("The value '$fail_bool' is not a valid boolean") | Assert-Equals -Expected $true
1529 }
1530 $failed | Assert-Equals -Expected $true
1531 }
1532 }
1533
1534 "Unknown internal key" = {
1535 Set-Variable -Name complex_args -Scope Global -Value @{
1536 _ansible_invalid = "invalid"
1537 }
1538 $failed = $false
1539 try {
1540 $null = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1541 } catch [System.Management.Automation.RuntimeException] {
1542 $failed = $true
1543 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1544
1545 $expected = @{
1546 invocation = @{
1547 module_args = @{
1548 _ansible_invalid = "invalid"
1549 }
1550 }
1551 changed = $false
1552 failed = $true
1553 msg = "Unsupported parameters for (undefined win module) module: _ansible_invalid. Supported parameters include: "
1554 }
1555 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1556 $actual | Assert-DictionaryEquals -Expected $expected
1557 }
1558 $failed | Assert-Equals -Expected $true
1559 }
1560
1561 "Module tmpdir with present remote tmp" = {
1562 $current_user = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
1563 $dir_security = New-Object -TypeName System.Security.AccessControl.DirectorySecurity
1564 $dir_security.SetOwner($current_user)
1565 $dir_security.SetAccessRuleProtection($true, $false)
1566 $ace = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @(
1567 $current_user, [System.Security.AccessControl.FileSystemRights]::FullControl,
1568 [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
1569 [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow
1570 )
1571 $dir_security.AddAccessRule($ace)
1572 $expected_sd = $dir_security.GetSecurityDescriptorSddlForm("Access, Owner")
1573
1574 $remote_tmp = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
1575 New-Item -Path $remote_tmp -ItemType Directory > $null
1576 Set-Variable -Name complex_args -Scope Global -Value @{
1577 _ansible_remote_tmp = $remote_tmp.ToString()
1578 }
1579 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1580 (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equals -Expected $true
1581
1582 $actual_tmpdir = $m.Tmpdir
1583 $parent_tmpdir = Split-Path -Path $actual_tmpdir -Parent
1584 $tmpdir_name = Split-Path -Path $actual_tmpdir -Leaf
1585
1586 $parent_tmpdir | Assert-Equals -Expected $remote_tmp
1587 $tmpdir_name.StartSwith("ansible-moduletmp-") | Assert-Equals -Expected $true
1588 (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equals -Expected $true
1589 (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equals -Expected $true
1590 $children = [System.IO.Directory]::EnumerateDirectories($remote_tmp)
1591 $children.Count | Assert-Equals -Expected 1
1592 $actual_tmpdir_sd = (Get-Acl -Path $actual_tmpdir).GetSecurityDescriptorSddlForm("Access, Owner")
1593 $actual_tmpdir_sd | Assert-Equals -Expected $expected_sd
1594
1595 try {
1596 $m.ExitJson()
1597 } catch [System.Management.Automation.RuntimeException] {
1598 $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1599 }
1600 (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equals -Expected $false
1601 (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equals -Expected $true
1602 $output.warnings.Count | Assert-Equals -Expected 0
1603 }
1604
1605 "Module tmpdir with missing remote_tmp" = {
1606 $current_user = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
1607 $dir_security = New-Object -TypeName System.Security.AccessControl.DirectorySecurity
1608 $dir_security.SetOwner($current_user)
1609 $dir_security.SetAccessRuleProtection($true, $false)
1610 $ace = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList @(
1611 $current_user, [System.Security.AccessControl.FileSystemRights]::FullControl,
1612 [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
1613 [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow
1614 )
1615 $dir_security.AddAccessRule($ace)
1616 $expected_sd = $dir_security.GetSecurityDescriptorSddlForm("Access, Owner")
1617
1618 $remote_tmp = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
1619 Set-Variable -Name complex_args -Scope Global -Value @{
1620 _ansible_remote_tmp = $remote_tmp.ToString()
1621 }
1622 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1623 (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equals -Expected $false
1624
1625 $actual_tmpdir = $m.Tmpdir
1626 $parent_tmpdir = Split-Path -Path $actual_tmpdir -Parent
1627 $tmpdir_name = Split-Path -Path $actual_tmpdir -Leaf
1628
1629 $parent_tmpdir | Assert-Equals -Expected $remote_tmp
1630 $tmpdir_name.StartSwith("ansible-moduletmp-") | Assert-Equals -Expected $true
1631 (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equals -Expected $true
1632 (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equals -Expected $true
1633 $children = [System.IO.Directory]::EnumerateDirectories($remote_tmp)
1634 $children.Count | Assert-Equals -Expected 1
1635 $actual_remote_sd = (Get-Acl -Path $remote_tmp).GetSecurityDescriptorSddlForm("Access, Owner")
1636 $actual_tmpdir_sd = (Get-Acl -Path $actual_tmpdir).GetSecurityDescriptorSddlForm("Access, Owner")
1637 $actual_remote_sd | Assert-Equals -Expected $expected_sd
1638 $actual_tmpdir_sd | Assert-Equals -Expected $expected_sd
1639
1640 try {
1641 $m.ExitJson()
1642 } catch [System.Management.Automation.RuntimeException] {
1643 $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1644 }
1645 (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equals -Expected $false
1646 (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equals -Expected $true
1647 $output.warnings.Count | Assert-Equals -Expected 1
1648 $nt_account = $current_user.Translate([System.Security.Principal.NTAccount])
1649 $actual_warning = "Module remote_tmp $remote_tmp did not exist and was created with FullControl to $nt_account, "
1650 $actual_warning += "this may cause issues when running as another user. To avoid this, "
1651 $actual_warning += "create the remote_tmp dir with the correct permissions manually"
1652 $actual_warning | Assert-Equals -Expected $output.warnings[0]
1653 }
1654
1655 "Module tmp, keep remote files" = {
1656 $remote_tmp = Join-Path -Path $tmpdir -ChildPath "moduletmpdir-$(Get-Random)"
1657 New-Item -Path $remote_tmp -ItemType Directory > $null
1658 Set-Variable -Name complex_args -Scope Global -Value @{
1659 _ansible_remote_tmp = $remote_tmp.ToString()
1660 _ansible_keep_remote_files = $true
1661 }
1662 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
1663
1664 $actual_tmpdir = $m.Tmpdir
1665 $parent_tmpdir = Split-Path -Path $actual_tmpdir -Parent
1666 $tmpdir_name = Split-Path -Path $actual_tmpdir -Leaf
1667
1668 $parent_tmpdir | Assert-Equals -Expected $remote_tmp
1669 $tmpdir_name.StartSwith("ansible-moduletmp-") | Assert-Equals -Expected $true
1670 (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equals -Expected $true
1671 (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equals -Expected $true
1672
1673 try {
1674 $m.ExitJson()
1675 } catch [System.Management.Automation.RuntimeException] {
1676 $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1677 }
1678 (Test-Path -LiteralPath $actual_tmpdir -PathType Container) | Assert-Equals -Expected $true
1679 (Test-Path -LiteralPath $remote_tmp -PathType Container) | Assert-Equals -Expected $true
1680 $output.warnings.Count | Assert-Equals -Expected 0
1681 Remove-Item -LiteralPath $actual_tmpdir -Force -Recurse
1682 }
1683
1684 "Invalid argument spec key" = {
1685 $spec = @{
1686 invalid = $true
1687 }
1688 $failed = $false
1689 try {
1690 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1691 } catch [System.Management.Automation.RuntimeException] {
1692 $failed = $true
1693 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1694 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1695 }
1696 $failed | Assert-Equals -Expected $true
1697
1698 $expected_msg = "internal error: argument spec entry contains an invalid key 'invalid', valid keys: apply_defaults, "
1699 $expected_msg += "aliases, choices, default, deprecated_aliases, elements, mutually_exclusive, no_log, options, "
1700 $expected_msg += "removed_in_version, removed_at_date, removed_from_collection, required, required_by, required_if, "
1701 $expected_msg += "required_one_of, required_together, supports_check_mode, type"
1702
1703 $actual.Keys.Count | Assert-Equals -Expected 3
1704 $actual.failed | Assert-Equals -Expected $true
1705 $actual.msg | Assert-Equals -Expected $expected_msg
1706 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1707 }
1708
1709 "Invalid argument spec key - nested" = {
1710 $spec = @{
1711 options = @{
1712 option_key = @{
1713 options = @{
1714 sub_option_key = @{
1715 invalid = $true
1716 }
1717 }
1718 }
1719 }
1720 }
1721 $failed = $false
1722 try {
1723 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1724 } catch [System.Management.Automation.RuntimeException] {
1725 $failed = $true
1726 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1727 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1728 }
1729 $failed | Assert-Equals -Expected $true
1730
1731 $expected_msg = "internal error: argument spec entry contains an invalid key 'invalid', valid keys: apply_defaults, "
1732 $expected_msg += "aliases, choices, default, deprecated_aliases, elements, mutually_exclusive, no_log, options, "
1733 $expected_msg += "removed_in_version, removed_at_date, removed_from_collection, required, required_by, required_if, "
1734 $expected_msg += "required_one_of, required_together, supports_check_mode, type - found in option_key -> sub_option_key"
1735
1736 $actual.Keys.Count | Assert-Equals -Expected 3
1737 $actual.failed | Assert-Equals -Expected $true
1738 $actual.msg | Assert-Equals -Expected $expected_msg
1739 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1740 }
1741
1742 "Invalid argument spec value type" = {
1743 $spec = @{
1744 apply_defaults = "abc"
1745 }
1746 $failed = $false
1747 try {
1748 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1749 } catch [System.Management.Automation.RuntimeException] {
1750 $failed = $true
1751 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1752 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1753 }
1754 $failed | Assert-Equals -Expected $true
1755
1756 $expected_msg = "internal error: argument spec for 'apply_defaults' did not match expected "
1757 $expected_msg += "type System.Boolean: actual type System.String"
1758
1759 $actual.Keys.Count | Assert-Equals -Expected 3
1760 $actual.failed | Assert-Equals -Expected $true
1761 $actual.msg | Assert-Equals -Expected $expected_msg
1762 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1763 }
1764
1765 "Invalid argument spec option type" = {
1766 $spec = @{
1767 options = @{
1768 option_key = @{
1769 type = "invalid type"
1770 }
1771 }
1772 }
1773 $failed = $false
1774 try {
1775 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1776 } catch [System.Management.Automation.RuntimeException] {
1777 $failed = $true
1778 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1779 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1780 }
1781 $failed | Assert-Equals -Expected $true
1782
1783 $expected_msg = "internal error: type 'invalid type' is unsupported - found in option_key. "
1784 $expected_msg += "Valid types are: bool, dict, float, int, json, list, path, raw, sid, str"
1785
1786 $actual.Keys.Count | Assert-Equals -Expected 3
1787 $actual.failed | Assert-Equals -Expected $true
1788 $actual.msg | Assert-Equals -Expected $expected_msg
1789 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1790 }
1791
1792 "Invalid argument spec option element type" = {
1793 $spec = @{
1794 options = @{
1795 option_key = @{
1796 type = "list"
1797 elements = "invalid type"
1798 }
1799 }
1800 }
1801 $failed = $false
1802 try {
1803 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1804 } catch [System.Management.Automation.RuntimeException] {
1805 $failed = $true
1806 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1807 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1808 }
1809 $failed | Assert-Equals -Expected $true
1810
1811 $expected_msg = "internal error: elements 'invalid type' is unsupported - found in option_key. "
1812 $expected_msg += "Valid types are: bool, dict, float, int, json, list, path, raw, sid, str"
1813
1814 $actual.Keys.Count | Assert-Equals -Expected 3
1815 $actual.failed | Assert-Equals -Expected $true
1816 $actual.msg | Assert-Equals -Expected $expected_msg
1817 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1818 }
1819
1820 "Invalid deprecated aliases entry - no version and date" = {
1821 $spec = @{
1822 options = @{
1823 option_key = @{
1824 type = "str"
1825 aliases = ,"alias_name"
1826 deprecated_aliases = @(
1827 @{name = "alias_name"}
1828 )
1829 }
1830 }
1831 }
1832
1833 $failed = $false
1834 try {
1835 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1836 } catch [System.Management.Automation.RuntimeException] {
1837 $failed = $true
1838 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1839 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1840 }
1841 $failed | Assert-Equals -Expected $true
1842
1843 $expected_msg = "internal error: One of version or date is required in a deprecated_aliases entry"
1844
1845 $actual.Keys.Count | Assert-Equals -Expected 3
1846 $actual.failed | Assert-Equals -Expected $true
1847 $actual.msg | Assert-Equals -Expected $expected_msg
1848 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1849 }
1850
1851 "Invalid deprecated aliases entry - no name (nested)" = {
1852 $spec = @{
1853 options = @{
1854 option_key = @{
1855 type = "dict"
1856 options = @{
1857 sub_option_key = @{
1858 type = "str"
1859 aliases = ,"alias_name"
1860 deprecated_aliases = @(
1861 @{version = "2.10"}
1862 )
1863 }
1864 }
1865 }
1866 }
1867 }
1868
1869 Set-Variable -Name complex_args -Scope Global -Value @{
1870 option_key = @{
1871 sub_option_key = "a"
1872 }
1873 }
1874
1875 $failed = $false
1876 try {
1877 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1878 } catch [System.ArgumentException] {
1879 $failed = $true
1880 $expected_msg = "name is required in a deprecated_aliases entry - found in option_key"
1881 $_.Exception.Message | Assert-Equals -Expected $expected_msg
1882 }
1883 $failed | Assert-Equals -Expected $true
1884 }
1885
1886 "Invalid deprecated aliases entry - both version and date" = {
1887 $spec = @{
1888 options = @{
1889 option_key = @{
1890 type = "str"
1891 aliases = ,"alias_name"
1892 deprecated_aliases = @(
1893 @{
1894 name = "alias_name"
1895 date = [DateTime]"2020-03-10"
1896 version = "2.11"
1897 }
1898 )
1899 }
1900 }
1901 }
1902
1903 $failed = $false
1904 try {
1905 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1906 } catch [System.Management.Automation.RuntimeException] {
1907 $failed = $true
1908 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1909 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1910 }
1911 $failed | Assert-Equals -Expected $true
1912
1913 $expected_msg = "internal error: Only one of version or date is allowed in a deprecated_aliases entry"
1914
1915 $actual.Keys.Count | Assert-Equals -Expected 3
1916 $actual.failed | Assert-Equals -Expected $true
1917 $actual.msg | Assert-Equals -Expected $expected_msg
1918 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1919 }
1920
1921 "Invalid deprecated aliases entry - wrong date type" = {
1922 $spec = @{
1923 options = @{
1924 option_key = @{
1925 type = "str"
1926 aliases = ,"alias_name"
1927 deprecated_aliases = @(
1928 @{
1929 name = "alias_name"
1930 date = "2020-03-10"
1931 }
1932 )
1933 }
1934 }
1935 }
1936
1937 $failed = $false
1938 try {
1939 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1940 } catch [System.Management.Automation.RuntimeException] {
1941 $failed = $true
1942 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1943 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1944 }
1945 $failed | Assert-Equals -Expected $true
1946
1947 $expected_msg = "internal error: A deprecated_aliases date must be a DateTime object"
1948
1949 $actual.Keys.Count | Assert-Equals -Expected 3
1950 $actual.failed | Assert-Equals -Expected $true
1951 $actual.msg | Assert-Equals -Expected $expected_msg
1952 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1953 }
1954
1955 "Spec required and default set at the same time" = {
1956 $spec = @{
1957 options = @{
1958 option_key = @{
1959 required = $true
1960 default = "default value"
1961 }
1962 }
1963 }
1964
1965 $failed = $false
1966 try {
1967 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
1968 } catch [System.Management.Automation.RuntimeException] {
1969 $failed = $true
1970 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
1971 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
1972 }
1973 $failed | Assert-Equals -Expected $true
1974
1975 $expected_msg = "internal error: required and default are mutually exclusive for option_key"
1976
1977 $actual.Keys.Count | Assert-Equals -Expected 3
1978 $actual.failed | Assert-Equals -Expected $true
1979 $actual.msg | Assert-Equals -Expected $expected_msg
1980 ("exception" -cin $actual.Keys) | Assert-Equals -Expected $true
1981 }
1982
1983 "Unsupported options" = {
1984 $spec = @{
1985 options = @{
1986 option_key = @{
1987 type = "str"
1988 }
1989 }
1990 }
1991 Set-Variable -Name complex_args -Scope Global -Value @{
1992 option_key = "abc"
1993 invalid_key = "def"
1994 another_key = "ghi"
1995 }
1996
1997 $failed = $false
1998 try {
1999 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2000 } catch [System.Management.Automation.RuntimeException] {
2001 $failed = $true
2002 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2003 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2004 }
2005 $failed | Assert-Equals -Expected $true
2006
2007 $expected_msg = "Unsupported parameters for (undefined win module) module: another_key, invalid_key. "
2008 $expected_msg += "Supported parameters include: option_key"
2009
2010 $actual.Keys.Count | Assert-Equals -Expected 4
2011 $actual.changed | Assert-Equals -Expected $false
2012 $actual.failed | Assert-Equals -Expected $true
2013 $actual.msg | Assert-Equals -Expected $expected_msg
2014 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2015 }
2016
2017 "Check mode and module doesn't support check mode" = {
2018 $spec = @{
2019 options = @{
2020 option_key = @{
2021 type = "str"
2022 }
2023 }
2024 }
2025 Set-Variable -Name complex_args -Scope Global -Value @{
2026 _ansible_check_mode = $true
2027 option_key = "abc"
2028 }
2029
2030 $failed = $false
2031 try {
2032 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2033 } catch [System.Management.Automation.RuntimeException] {
2034 $failed = $true
2035 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
2036 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2037 }
2038 $failed | Assert-Equals -Expected $true
2039
2040 $expected_msg = "remote module (undefined win module) does not support check mode"
2041
2042 $actual.Keys.Count | Assert-Equals -Expected 4
2043 $actual.changed | Assert-Equals -Expected $false
2044 $actual.skipped | Assert-Equals -Expected $true
2045 $actual.msg | Assert-Equals -Expected $expected_msg
2046 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = @{option_key = "abc"}}
2047 }
2048
2049 "Check mode with suboption without supports_check_mode" = {
2050 $spec = @{
2051 options = @{
2052 sub_options = @{
2053 # This tests the situation where a sub key doesn't set supports_check_mode, the logic in
2054 # Ansible.Basic automatically sets that to $false and we want it to ignore it for a nested check
2055 type = "dict"
2056 options = @{
2057 sub_option = @{ type = "str"; default = "value" }
2058 }
2059 }
2060 }
2061 supports_check_mode = $true
2062 }
2063 Set-Variable -Name complex_args -Scope Global -Value @{
2064 _ansible_check_mode = $true
2065 }
2066
2067 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2068 $m.CheckMode | Assert-Equals -Expected $true
2069 }
2070
2071 "Type conversion error" = {
2072 $spec = @{
2073 options = @{
2074 option_key = @{
2075 type = "int"
2076 }
2077 }
2078 }
2079 Set-Variable -Name complex_args -Scope Global -Value @{
2080 option_key = "a"
2081 }
2082
2083 $failed = $false
2084 try {
2085 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2086 } catch [System.Management.Automation.RuntimeException] {
2087 $failed = $true
2088 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2089 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2090 }
2091 $failed | Assert-Equals -Expected $true
2092
2093 $expected_msg = "argument for option_key is of type System.String and we were unable to convert to int: "
2094 $expected_msg += "Input string was not in a correct format."
2095
2096 $actual.Keys.Count | Assert-Equals -Expected 4
2097 $actual.changed | Assert-Equals -Expected $false
2098 $actual.failed | Assert-Equals -Expected $true
2099 $actual.msg | Assert-Equals -Expected $expected_msg
2100 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2101 }
2102
2103 "Type conversion error - delegate" = {
2104 $spec = @{
2105 options = @{
2106 option_key = @{
2107 type = "dict"
2108 options = @{
2109 sub_option_key = @{
2110 type = [Func[[Object], [UInt64]]]{ [System.UInt64]::Parse($args[0]) }
2111 }
2112 }
2113 }
2114 }
2115 }
2116 Set-Variable -Name complex_args -Scope Global -Value @{
2117 option_key = @{
2118 sub_option_key = "a"
2119 }
2120 }
2121
2122 $failed = $false
2123 try {
2124 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2125 } catch [System.Management.Automation.RuntimeException] {
2126 $failed = $true
2127 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2128 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2129 }
2130 $failed | Assert-Equals -Expected $true
2131
2132 $expected_msg = "argument for sub_option_key is of type System.String and we were unable to convert to delegate: "
2133 $expected_msg += "Exception calling `"Parse`" with `"1`" argument(s): `"Input string was not in a correct format.`" "
2134 $expected_msg += "found in option_key"
2135
2136 $actual.Keys.Count | Assert-Equals -Expected 4
2137 $actual.changed | Assert-Equals -Expected $false
2138 $actual.failed | Assert-Equals -Expected $true
2139 $actual.msg | Assert-Equals -Expected $expected_msg
2140 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2141 }
2142
2143 "Numeric choices" = {
2144 $spec = @{
2145 options = @{
2146 option_key = @{
2147 choices = 1, 2, 3
2148 type = "int"
2149 }
2150 }
2151 }
2152 Set-Variable -Name complex_args -Scope Global -Value @{
2153 option_key = "2"
2154 }
2155
2156 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2157 try {
2158 $m.ExitJson()
2159 } catch [System.Management.Automation.RuntimeException] {
2160 $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2161 }
2162 $output.Keys.Count | Assert-Equals -Expected 2
2163 $output.changed | Assert-Equals -Expected $false
2164 $output.invocation | Assert-DictionaryEquals -Expected @{module_args = @{option_key = 2}}
2165 }
2166
2167 "Case insensitive choice" = {
2168 $spec = @{
2169 options = @{
2170 option_key = @{
2171 choices = "abc", "def"
2172 }
2173 }
2174 }
2175 Set-Variable -Name complex_args -Scope Global -Value @{
2176 option_key = "ABC"
2177 }
2178
2179 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2180 try {
2181 $m.ExitJson()
2182 } catch [System.Management.Automation.RuntimeException] {
2183 $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2184 }
2185 $expected_warning = "value of option_key was a case insensitive match of one of: abc, def. "
2186 $expected_warning += "Checking of choices will be case sensitive in a future Ansible release. "
2187 $expected_warning += "Case insensitive matches were: ABC"
2188
2189 $output.invocation | Assert-DictionaryEquals -Expected @{module_args = @{option_key = "ABC"}}
2190 # We have disabled the warnings for now
2191 #$output.warnings.Count | Assert-Equals -Expected 1
2192 #$output.warnings[0] | Assert-Equals -Expected $expected_warning
2193 }
2194
2195 "Case insensitive choice no_log" = {
2196 $spec = @{
2197 options = @{
2198 option_key = @{
2199 choices = "abc", "def"
2200 no_log = $true
2201 }
2202 }
2203 }
2204 Set-Variable -Name complex_args -Scope Global -Value @{
2205 option_key = "ABC"
2206 }
2207
2208 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2209 try {
2210 $m.ExitJson()
2211 } catch [System.Management.Automation.RuntimeException] {
2212 $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2213 }
2214 $expected_warning = "value of option_key was a case insensitive match of one of: abc, def. "
2215 $expected_warning += "Checking of choices will be case sensitive in a future Ansible release. "
2216 $expected_warning += "Case insensitive matches were: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
2217
2218 $output.invocation | Assert-DictionaryEquals -Expected @{module_args = @{option_key = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"}}
2219 # We have disabled the warnings for now
2220 #$output.warnings.Count | Assert-Equals -Expected 1
2221 #$output.warnings[0] | Assert-Equals -Expected $expected_warning
2222 }
2223
2224 "Case insentitive choice as list" = {
2225 $spec = @{
2226 options = @{
2227 option_key = @{
2228 choices = "abc", "def", "ghi", "JKL"
2229 type = "list"
2230 elements = "str"
2231 }
2232 }
2233 }
2234 Set-Variable -Name complex_args -Scope Global -Value @{
2235 option_key = "AbC", "ghi", "jkl"
2236 }
2237
2238 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2239 try {
2240 $m.ExitJson()
2241 } catch [System.Management.Automation.RuntimeException] {
2242 $output = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2243 }
2244 $expected_warning = "value of option_key was a case insensitive match of one or more of: abc, def, ghi, JKL. "
2245 $expected_warning += "Checking of choices will be case sensitive in a future Ansible release. "
2246 $expected_warning += "Case insensitive matches were: AbC, jkl"
2247
2248 $output.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2249 # We have disabled the warnings for now
2250 #$output.warnings.Count | Assert-Equals -Expected 1
2251 #$output.warnings[0] | Assert-Equals -Expected $expected_warning
2252 }
2253
2254 "Invalid choice" = {
2255 $spec = @{
2256 options = @{
2257 option_key = @{
2258 choices = "a", "b"
2259 }
2260 }
2261 }
2262 Set-Variable -Name complex_args -Scope Global -Value @{
2263 option_key = "c"
2264 }
2265
2266 $failed = $false
2267 try {
2268 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2269 } catch [System.Management.Automation.RuntimeException] {
2270 $failed = $true
2271 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2272 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2273 }
2274 $failed | Assert-Equals -Expected $true
2275
2276 $expected_msg = "value of option_key must be one of: a, b. Got no match for: c"
2277
2278 $actual.Keys.Count | Assert-Equals -Expected 4
2279 $actual.changed | Assert-Equals -Expected $false
2280 $actual.failed | Assert-Equals -Expected $true
2281 $actual.msg | Assert-Equals -Expected $expected_msg
2282 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2283 }
2284
2285 "Invalid choice with no_log" = {
2286 $spec = @{
2287 options = @{
2288 option_key = @{
2289 choices = "a", "b"
2290 no_log = $true
2291 }
2292 }
2293 }
2294 Set-Variable -Name complex_args -Scope Global -Value @{
2295 option_key = "abc"
2296 }
2297
2298 $failed = $false
2299 try {
2300 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2301 } catch [System.Management.Automation.RuntimeException] {
2302 $failed = $true
2303 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2304 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2305 }
2306 $failed | Assert-Equals -Expected $true
2307
2308 $expected_msg = "value of option_key must be one of: a, b. Got no match for: ********"
2309
2310 $actual.Keys.Count | Assert-Equals -Expected 4
2311 $actual.changed | Assert-Equals -Expected $false
2312 $actual.failed | Assert-Equals -Expected $true
2313 $actual.msg | Assert-Equals -Expected $expected_msg
2314 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = @{option_key = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"}}
2315 }
2316
2317 "Invalid choice in list" = {
2318 $spec = @{
2319 options = @{
2320 option_key = @{
2321 choices = "a", "b"
2322 type = "list"
2323 }
2324 }
2325 }
2326 Set-Variable -Name complex_args -Scope Global -Value @{
2327 option_key = "a", "c"
2328 }
2329
2330 $failed = $false
2331 try {
2332 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2333 } catch [System.Management.Automation.RuntimeException] {
2334 $failed = $true
2335 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2336 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2337 }
2338 $failed | Assert-Equals -Expected $true
2339
2340 $expected_msg = "value of option_key must be one or more of: a, b. Got no match for: c"
2341
2342 $actual.Keys.Count | Assert-Equals -Expected 4
2343 $actual.changed | Assert-Equals -Expected $false
2344 $actual.failed | Assert-Equals -Expected $true
2345 $actual.msg | Assert-Equals -Expected $expected_msg
2346 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2347 }
2348
2349 "Mutually exclusive options" = {
2350 $spec = @{
2351 options = @{
2352 option1 = @{}
2353 option2 = @{}
2354 }
2355 mutually_exclusive = @(,@("option1", "option2"))
2356 }
2357 Set-Variable -Name complex_args -Scope Global -Value @{
2358 option1 = "a"
2359 option2 = "b"
2360 }
2361
2362 $failed = $false
2363 try {
2364 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2365 } catch [System.Management.Automation.RuntimeException] {
2366 $failed = $true
2367 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2368 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2369 }
2370 $failed | Assert-Equals -Expected $true
2371
2372 $expected_msg = "parameters are mutually exclusive: option1, option2"
2373
2374 $actual.Keys.Count | Assert-Equals -Expected 4
2375 $actual.changed | Assert-Equals -Expected $false
2376 $actual.failed | Assert-Equals -Expected $true
2377 $actual.msg | Assert-Equals -Expected $expected_msg
2378 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2379 }
2380
2381 "Missing required argument" = {
2382 $spec = @{
2383 options = @{
2384 option1 = @{}
2385 option2 = @{required = $true}
2386 }
2387 }
2388 Set-Variable -Name complex_args -Scope Global -Value @{
2389 option1 = "a"
2390 }
2391
2392 $failed = $false
2393 try {
2394 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2395 } catch [System.Management.Automation.RuntimeException] {
2396 $failed = $true
2397 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2398 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2399 }
2400 $failed | Assert-Equals -Expected $true
2401
2402 $expected_msg = "missing required arguments: option2"
2403
2404 $actual.Keys.Count | Assert-Equals -Expected 4
2405 $actual.changed | Assert-Equals -Expected $false
2406 $actual.failed | Assert-Equals -Expected $true
2407 $actual.msg | Assert-Equals -Expected $expected_msg
2408 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2409 }
2410
2411 "Missing required argument subspec - no value defined" = {
2412 $spec = @{
2413 options = @{
2414 option_key = @{
2415 type = "dict"
2416 options = @{
2417 sub_option_key = @{
2418 required = $true
2419 }
2420 }
2421 }
2422 }
2423 }
2424
2425 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2426 $failed = $false
2427 try {
2428 $m.ExitJson()
2429 } catch [System.Management.Automation.RuntimeException] {
2430 $failed = $true
2431 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
2432 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2433 }
2434 $failed | Assert-Equals -Expected $true
2435
2436 $actual.Keys.Count | Assert-Equals -Expected 2
2437 $actual.changed | Assert-Equals -Expected $false
2438 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2439 }
2440
2441 "Missing required argument subspec" = {
2442 $spec = @{
2443 options = @{
2444 option_key = @{
2445 type = "dict"
2446 options = @{
2447 sub_option_key = @{
2448 required = $true
2449 }
2450 another_key = @{}
2451 }
2452 }
2453 }
2454 }
2455 Set-Variable -Name complex_args -Scope Global -Value @{
2456 option_key = @{
2457 another_key = "abc"
2458 }
2459 }
2460
2461 $failed = $false
2462 try {
2463 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2464 } catch [System.Management.Automation.RuntimeException] {
2465 $failed = $true
2466 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2467 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2468 }
2469 $failed | Assert-Equals -Expected $true
2470
2471 $expected_msg = "missing required arguments: sub_option_key found in option_key"
2472
2473 $actual.Keys.Count | Assert-Equals -Expected 4
2474 $actual.changed | Assert-Equals -Expected $false
2475 $actual.failed | Assert-Equals -Expected $true
2476 $actual.msg | Assert-Equals -Expected $expected_msg
2477 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2478 }
2479
2480 "Required together not set" = {
2481 $spec = @{
2482 options = @{
2483 option1 = @{}
2484 option2 = @{}
2485 }
2486 required_together = @(,@("option1", "option2"))
2487 }
2488 Set-Variable -Name complex_args -Scope Global -Value @{
2489 option1 = "abc"
2490 }
2491
2492 $failed = $false
2493 try {
2494 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2495 } catch [System.Management.Automation.RuntimeException] {
2496 $failed = $true
2497 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2498 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2499 }
2500 $failed | Assert-Equals -Expected $true
2501
2502 $expected_msg = "parameters are required together: option1, option2"
2503
2504 $actual.Keys.Count | Assert-Equals -Expected 4
2505 $actual.changed | Assert-Equals -Expected $false
2506 $actual.failed | Assert-Equals -Expected $true
2507 $actual.msg | Assert-Equals -Expected $expected_msg
2508 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2509 }
2510
2511 "Required together not set - subspec" = {
2512 $spec = @{
2513 options = @{
2514 option_key = @{
2515 type = "dict"
2516 options = @{
2517 option1 = @{}
2518 option2 = @{}
2519 }
2520 required_together = @(,@("option1", "option2"))
2521 }
2522 another_option = @{}
2523 }
2524 required_together = @(,@("option_key", "another_option"))
2525 }
2526 Set-Variable -Name complex_args -Scope Global -Value @{
2527 option_key = @{
2528 option1 = "abc"
2529 }
2530 another_option = "def"
2531 }
2532
2533 $failed = $false
2534 try {
2535 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2536 } catch [System.Management.Automation.RuntimeException] {
2537 $failed = $true
2538 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2539 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2540 }
2541 $failed | Assert-Equals -Expected $true
2542
2543 $expected_msg = "parameters are required together: option1, option2 found in option_key"
2544
2545 $actual.Keys.Count | Assert-Equals -Expected 4
2546 $actual.changed | Assert-Equals -Expected $false
2547 $actual.failed | Assert-Equals -Expected $true
2548 $actual.msg | Assert-Equals -Expected $expected_msg
2549 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2550 }
2551
2552 "Required one of not set" = {
2553 $spec = @{
2554 options = @{
2555 option1 = @{}
2556 option2 = @{}
2557 option3 = @{}
2558 }
2559 required_one_of = @(@("option1", "option2"), @("option2", "option3"))
2560 }
2561 Set-Variable -Name complex_args -Scope Global -Value @{
2562 option1 = "abc"
2563 }
2564
2565 $failed = $false
2566 try {
2567 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2568 } catch [System.Management.Automation.RuntimeException] {
2569 $failed = $true
2570 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2571 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2572 }
2573 $failed | Assert-Equals -Expected $true
2574
2575 $expected_msg = "one of the following is required: option2, option3"
2576
2577 $actual.Keys.Count | Assert-Equals -Expected 4
2578 $actual.changed | Assert-Equals -Expected $false
2579 $actual.failed | Assert-Equals -Expected $true
2580 $actual.msg | Assert-Equals -Expected $expected_msg
2581 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2582 }
2583
2584 "Required if invalid entries" = {
2585 $spec = @{
2586 options = @{
2587 state = @{choices = "absent", "present"; default = "present"}
2588 path = @{type = "path"}
2589 }
2590 required_if = @(,@("state", "absent"))
2591 }
2592
2593 $failed = $false
2594 try {
2595 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2596 } catch [System.Management.Automation.RuntimeException] {
2597 $failed = $true
2598 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2599 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2600 }
2601 $failed | Assert-Equals -Expected $true
2602
2603 $expected_msg = "internal error: invalid required_if value count of 2, expecting 3 or 4 entries"
2604
2605 $actual.Keys.Count | Assert-Equals -Expected 4
2606 $actual.changed | Assert-Equals -Expected $false
2607 $actual.failed | Assert-Equals -Expected $true
2608 $actual.msg | Assert-Equals -Expected $expected_msg
2609 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2610 }
2611
2612 "Required if no missing option" = {
2613 $spec = @{
2614 options = @{
2615 state = @{choices = "absent", "present"; default = "present"}
2616 name = @{}
2617 path = @{type = "path"}
2618 }
2619 required_if = @(,@("state", "absent", @("name", "path")))
2620 }
2621 Set-Variable -Name complex_args -Scope Global -Value @{
2622 name = "abc"
2623 }
2624 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2625
2626 $failed = $false
2627 try {
2628 $m.ExitJson()
2629 } catch [System.Management.Automation.RuntimeException] {
2630 $failed = $true
2631 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
2632 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2633 }
2634 $failed | Assert-Equals -Expected $true
2635
2636 $actual.Keys.Count | Assert-Equals -Expected 2
2637 $actual.changed | Assert-Equals -Expected $false
2638 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2639 }
2640
2641 "Required if missing option" = {
2642 $spec = @{
2643 options = @{
2644 state = @{choices = "absent", "present"; default = "present"}
2645 name = @{}
2646 path = @{type = "path"}
2647 }
2648 required_if = @(,@("state", "absent", @("name", "path")))
2649 }
2650 Set-Variable -Name complex_args -Scope Global -Value @{
2651 state = "absent"
2652 name = "abc"
2653 }
2654
2655 $failed = $false
2656 try {
2657 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2658 } catch [System.Management.Automation.RuntimeException] {
2659 $failed = $true
2660 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2661 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2662 }
2663 $failed | Assert-Equals -Expected $true
2664
2665 $expected_msg = "state is absent but all of the following are missing: path"
2666
2667 $actual.Keys.Count | Assert-Equals -Expected 4
2668 $actual.changed | Assert-Equals -Expected $false
2669 $actual.failed | Assert-Equals -Expected $true
2670 $actual.msg | Assert-Equals -Expected $expected_msg
2671 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2672 }
2673
2674 "Required if missing option and required one is set" = {
2675 $spec = @{
2676 options = @{
2677 state = @{choices = "absent", "present"; default = "present"}
2678 name = @{}
2679 path = @{type = "path"}
2680 }
2681 required_if = @(,@("state", "absent", @("name", "path"), $true))
2682 }
2683 Set-Variable -Name complex_args -Scope Global -Value @{
2684 state = "absent"
2685 }
2686
2687 $failed = $false
2688 try {
2689 $null = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2690 } catch [System.Management.Automation.RuntimeException] {
2691 $failed = $true
2692 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2693 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2694 }
2695 $failed | Assert-Equals -Expected $true
2696
2697 $expected_msg = "state is absent but any of the following are missing: name, path"
2698
2699 $actual.Keys.Count | Assert-Equals -Expected 4
2700 $actual.changed | Assert-Equals -Expected $false
2701 $actual.failed | Assert-Equals -Expected $true
2702 $actual.msg | Assert-Equals -Expected $expected_msg
2703 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2704 }
2705
2706 "Required if missing option but one required set" = {
2707 $spec = @{
2708 options = @{
2709 state = @{choices = "absent", "present"; default = "present"}
2710 name = @{}
2711 path = @{type = "path"}
2712 }
2713 required_if = @(,@("state", "absent", @("name", "path"), $true))
2714 }
2715 Set-Variable -Name complex_args -Scope Global -Value @{
2716 state = "absent"
2717 name = "abc"
2718 }
2719 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
2720
2721 $failed = $false
2722 try {
2723 $m.ExitJson()
2724 } catch [System.Management.Automation.RuntimeException] {
2725 $failed = $true
2726 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
2727 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2728 }
2729 $failed | Assert-Equals -Expected $true
2730
2731 $actual.Keys.Count | Assert-Equals -Expected 2
2732 $actual.changed | Assert-Equals -Expected $false
2733 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2734 }
2735
2736 "PS Object in return result" = {
2737 $m = [Ansible.Basic.AnsibleModule]::Create(@(), @{})
2738
2739 # JavaScriptSerializer struggles with PS Object like PSCustomObject due to circular references, this test makes
2740 # sure we can handle these types of objects without bombing
2741 $m.Result.output = [PSCustomObject]@{a = "a"; b = "b"}
2742 $failed = $true
2743 try {
2744 $m.ExitJson()
2745 } catch [System.Management.Automation.RuntimeException] {
2746 $failed = $true
2747 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
2748 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2749 }
2750 $failed | Assert-Equals -Expected $true
2751
2752 $actual.Keys.Count | Assert-Equals -Expected 3
2753 $actual.changed | Assert-Equals -Expected $false
2754 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = @{}}
2755 $actual.output | Assert-DictionaryEquals -Expected @{a = "a"; b = "b"}
2756 }
2757
2758 "String json array to object" = {
2759 $input_json = '["abc", "def"]'
2760 $actual = [Ansible.Basic.AnsibleModule]::FromJson($input_json)
2761 $actual -is [Array] | Assert-Equals -Expected $true
2762 $actual.Length | Assert-Equals -Expected 2
2763 $actual[0] | Assert-Equals -Expected "abc"
2764 $actual[1] | Assert-Equals -Expected "def"
2765 }
2766
2767 "String json array of dictionaries to object" = {
2768 $input_json = '[{"abc":"def"}]'
2769 $actual = [Ansible.Basic.AnsibleModule]::FromJson($input_json)
2770 $actual -is [Array] | Assert-Equals -Expected $true
2771 $actual.Length | Assert-Equals -Expected 1
2772 $actual[0] | Assert-DictionaryEquals -Expected @{"abc" = "def"}
2773 }
2774
2775 "Spec with fragments" = {
2776 $spec = @{
2777 options = @{
2778 option1 = @{ type = "str" }
2779 }
2780 }
2781 $fragment1 = @{
2782 options = @{
2783 option2 = @{ type = "str" }
2784 }
2785 }
2786
2787 Set-Variable -Name complex_args -Scope Global -Value @{
2788 option1 = "option1"
2789 option2 = "option2"
2790 }
2791 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment1))
2792
2793 $failed = $false
2794 try {
2795 $m.ExitJson()
2796 } catch [System.Management.Automation.RuntimeException] {
2797 $failed = $true
2798 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
2799 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2800 }
2801 $failed | Assert-Equals -Expected $true
2802
2803 $actual.changed | Assert-Equals -Expected $false
2804 $actual.invocation | Assert-DictionaryEquals -Expected @{module_args = $complex_args}
2805 }
2806
2807 "Fragment spec that with a deprecated alias" = {
2808 $spec = @{
2809 options = @{
2810 option1 = @{
2811 aliases = @("alias1_spec")
2812 type = "str"
2813 deprecated_aliases = @(
2814 @{name = "alias1_spec"; version = "2.0"}
2815 )
2816 }
2817 option2 = @{
2818 aliases = @("alias2_spec")
2819 deprecated_aliases = @(
2820 @{name = "alias2_spec"; version = "2.0"; collection_name = "ansible.builtin"}
2821 )
2822 }
2823 }
2824 }
2825 $fragment1 = @{
2826 options = @{
2827 option1 = @{
2828 aliases = @("alias1")
2829 deprecated_aliases = @() # Makes sure it doesn't overwrite the spec, just adds to it.
2830 }
2831 option2 = @{
2832 aliases = @("alias2")
2833 deprecated_aliases = @(
2834 @{name = "alias2"; version = "2.0"; collection_name = "foo.bar"}
2835 )
2836 type = "str"
2837 }
2838 }
2839 }
2840
2841 Set-Variable -Name complex_args -Scope Global -Value @{
2842 alias1_spec = "option1"
2843 alias2 = "option2"
2844 }
2845 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment1))
2846
2847 $failed = $false
2848 try {
2849 $m.ExitJson()
2850 } catch [System.Management.Automation.RuntimeException] {
2851 $failed = $true
2852 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
2853 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2854 }
2855 $failed | Assert-Equals -Expected $true
2856
2857 $actual.deprecations.Count | Assert-Equals -Expected 2
2858 $actual.deprecations[0] | Assert-DictionaryEquals -Expected @{
2859 msg = "Alias 'alias1_spec' is deprecated. See the module docs for more information"; version = "2.0"; collection_name = $null
2860 }
2861 $actual.deprecations[1] | Assert-DictionaryEquals -Expected @{
2862 msg = "Alias 'alias2' is deprecated. See the module docs for more information"; version = "2.0"; collection_name = "foo.bar"
2863 }
2864 $actual.changed | Assert-Equals -Expected $false
2865 $actual.invocation | Assert-DictionaryEquals -Expected @{
2866 module_args = @{
2867 option1 = "option1"
2868 alias1_spec = "option1"
2869 option2 = "option2"
2870 alias2 = "option2"
2871 }
2872 }
2873 }
2874
2875 "Fragment spec with mutual args" = {
2876 $spec = @{
2877 options = @{
2878 option1 = @{ type = "str" }
2879 option2 = @{ type = "str" }
2880 }
2881 mutually_exclusive = @(
2882 ,@('option1', 'option2')
2883 )
2884 }
2885 $fragment1 = @{
2886 options = @{
2887 fragment1_1 = @{ type = "str" }
2888 fragment1_2 = @{ type = "str" }
2889 }
2890 mutually_exclusive = @(
2891 ,@('fragment1_1', 'fragment1_2')
2892 )
2893 }
2894 $fragment2 = @{
2895 options = @{
2896 fragment2 = @{ type = "str" }
2897 }
2898 }
2899
2900 Set-Variable -Name complex_args -Scope Global -Value @{
2901 option1 = "option1"
2902 fragment1_1 = "fragment1_1"
2903 fragment1_2 = "fragment1_2"
2904 fragment2 = "fragment2"
2905 }
2906
2907 $failed = $false
2908 try {
2909 [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment1, $fragment2))
2910 } catch [System.Management.Automation.RuntimeException] {
2911 $failed = $true
2912 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2913 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2914 }
2915 $failed | Assert-Equals -Expected $true
2916
2917 $actual.changed | Assert-Equals -Expected $false
2918 $actual.failed | Assert-Equals -Expected $true
2919 $actual.msg | Assert-Equals -Expected "parameters are mutually exclusive: fragment1_1, fragment1_2"
2920 $actual.invocation | Assert-DictionaryEquals -Expected @{ module_args = $complex_args }
2921 }
2922
2923 "Fragment spec with no_log" = {
2924 $spec = @{
2925 options = @{
2926 option1 = @{
2927 aliases = @("alias")
2928 }
2929 }
2930 }
2931 $fragment1 = @{
2932 options = @{
2933 option1 = @{
2934 no_log = $true # Makes sure that a value set in the fragment but not in the spec is respected.
2935 type = "str"
2936 }
2937 }
2938 }
2939
2940 Set-Variable -Name complex_args -Scope Global -Value @{
2941 alias = "option1"
2942 }
2943 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment1))
2944
2945 $failed = $false
2946 try {
2947 $m.ExitJson()
2948 } catch [System.Management.Automation.RuntimeException] {
2949 $failed = $true
2950 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
2951 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2952 }
2953 $failed | Assert-Equals -Expected $true
2954
2955 $actual.changed | Assert-Equals -Expected $false
2956 $actual.invocation | Assert-DictionaryEquals -Expected @{
2957 module_args = @{
2958 option1 = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
2959 alias = "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
2960 }
2961 }
2962 }
2963
2964 "Catch invalid fragment spec format" = {
2965 $spec = @{
2966 options = @{
2967 option1 = @{ type = "str" }
2968 }
2969 }
2970 $fragment = @{
2971 options = @{}
2972 invalid = "will fail"
2973 }
2974
2975 Set-Variable -Name complex_args -Scope Global -Value @{
2976 option1 = "option1"
2977 }
2978
2979 $failed = $false
2980 try {
2981 [Ansible.Basic.AnsibleModule]::Create(@(), $spec, @($fragment))
2982 } catch [System.Management.Automation.RuntimeException] {
2983 $failed = $true
2984 $_.Exception.Message | Assert-Equals -Expected "exit: 1"
2985 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
2986 }
2987 $failed | Assert-Equals -Expected $true
2988
2989 $actual.failed | Assert-Equals -Expected $true
2990 $actual.msg.StartsWith("internal error: argument spec entry contains an invalid key 'invalid', valid keys: ") | Assert-Equals -Expected $true
2991 }
2992
2993 "Spec with different list types" = {
2994 $spec = @{
2995 options = @{
2996 # Single element of the same list type not in a list
2997 option1 = @{
2998 aliases = "alias1"
2999 deprecated_aliases = @{name="alias1";version="2.0";collection_name="foo.bar"}
3000 }
3001
3002 # Arrays
3003 option2 = @{
3004 aliases = ,"alias2"
3005 deprecated_aliases = ,@{name="alias2";version="2.0";collection_name="foo.bar"}
3006 }
3007
3008 # ArrayList
3009 option3 = @{
3010 aliases = [System.Collections.ArrayList]@("alias3")
3011 deprecated_aliases = [System.Collections.ArrayList]@(@{name="alias3";version="2.0";collection_name="foo.bar"})
3012 }
3013
3014 # Generic.List[Object]
3015 option4 = @{
3016 aliases = [System.Collections.Generic.List[Object]]@("alias4")
3017 deprecated_aliases = [System.Collections.Generic.List[Object]]@(@{name="alias4";version="2.0";collection_name="foo.bar"})
3018 }
3019
3020 # Generic.List[T]
3021 option5 = @{
3022 aliases = [System.Collections.Generic.List[String]]@("alias5")
3023 deprecated_aliases = [System.Collections.Generic.List[Hashtable]]@()
3024 }
3025 }
3026 }
3027 $spec.options.option5.deprecated_aliases.Add(@{name="alias5";version="2.0";collection_name="foo.bar"})
3028
3029 Set-Variable -Name complex_args -Scope Global -Value @{
3030 alias1 = "option1"
3031 alias2 = "option2"
3032 alias3 = "option3"
3033 alias4 = "option4"
3034 alias5 = "option5"
3035 }
3036 $m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
3037
3038 $failed = $false
3039 try {
3040 $m.ExitJson()
3041 } catch [System.Management.Automation.RuntimeException] {
3042 $failed = $true
3043 $_.Exception.Message | Assert-Equals -Expected "exit: 0"
3044 $actual = [Ansible.Basic.AnsibleModule]::FromJson($_.Exception.InnerException.Output)
3045 }
3046 $failed | Assert-Equals -Expected $true
3047
3048 $actual.changed | Assert-Equals -Expected $false
3049 $actual.deprecations.Count | Assert-Equals -Expected 5
3050 foreach ($dep in $actual.deprecations) {
3051 $dep.msg -like "Alias 'alias?' is deprecated. See the module docs for more information" | Assert-Equals -Expected $true
3052 $dep.version | Assert-Equals -Expected '2.0'
3053 $dep.collection_name | Assert-Equals -Expected 'foo.bar'
3054 }
3055 $actual.invocation | Assert-DictionaryEquals -Expected @{
3056 module_args = @{
3057 alias1 = "option1"
3058 option1 = "option1"
3059 alias2 = "option2"
3060 option2 = "option2"
3061 alias3 = "option3"
3062 option3 = "option3"
3063 alias4 = "option4"
3064 option4 = "option4"
3065 alias5 = "option5"
3066 option5 = "option5"
3067 }
3068 }
3069 }
3070 }
3071
3072 try {
3073 foreach ($test_impl in $tests.GetEnumerator()) {
3074 # Reset the variables before each test
3075 Set-Variable -Name complex_args -Value @{} -Scope Global
3076
3077 $test = $test_impl.Key
3078 &$test_impl.Value
3079 }
3080 $module.Result.data = "success"
3081 } catch [System.Management.Automation.RuntimeException] {
3082 $module.Result.failed = $true
3083 $module.Result.test = $test
3084 $module.Result.line = $_.InvocationInfo.ScriptLineNumber
3085 $module.Result.method = $_.InvocationInfo.Line.Trim()
3086
3087 if ($_.Exception.Message.StartSwith("exit: ")) {
3088 # The exception was caused by an unexpected Exit call, log that on the output
3089 $module.Result.output = (ConvertFrom-Json -InputObject $_.Exception.InnerException.Output)
3090 $module.Result.msg = "Uncaught AnsibleModule exit in tests, see output"
3091 } else {
3092 # Unrelated exception
3093 $module.Result.exception = $_.Exception.ToString()
3094 $module.Result.msg = "Uncaught exception: $(($_ | Out-String).ToString())"
3095 }
3096 }
3097
3098 Exit-Module
3099