winrm 1.3.6 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -10
  3. data/.rspec +3 -3
  4. data/.rubocop.yml +9 -9
  5. data/.travis.yml +4 -4
  6. data/Gemfile +9 -9
  7. data/LICENSE +202 -202
  8. data/README.md +148 -148
  9. data/Rakefile +28 -28
  10. data/Vagrantfile +9 -9
  11. data/bin/rwinrm +97 -97
  12. data/changelog.md +49 -49
  13. data/lib/winrm.rb +42 -41
  14. data/lib/winrm/exceptions/exceptions.rb +57 -57
  15. data/lib/winrm/helpers/iso8601_duration.rb +58 -58
  16. data/lib/winrm/helpers/powershell_script.rb +37 -37
  17. data/lib/winrm/http/response_handler.rb +82 -82
  18. data/lib/winrm/http/transport.rb +294 -294
  19. data/lib/winrm/output.rb +43 -43
  20. data/lib/winrm/soap_provider.rb +40 -40
  21. data/lib/winrm/version.rb +7 -0
  22. data/lib/winrm/winrm_service.rb +490 -490
  23. data/preamble +17 -17
  24. data/spec/auth_timeout_spec.rb +16 -16
  25. data/spec/cmd_spec.rb +102 -102
  26. data/spec/config-example.yml +19 -19
  27. data/spec/exception_spec.rb +50 -50
  28. data/spec/issue_59_spec.rb +15 -15
  29. data/spec/matchers.rb +74 -74
  30. data/spec/output_spec.rb +110 -110
  31. data/spec/powershell_spec.rb +103 -103
  32. data/spec/response_handler_spec.rb +59 -59
  33. data/spec/spec_helper.rb +48 -48
  34. data/spec/stubs/responses/open_shell_v1.xml +19 -19
  35. data/spec/stubs/responses/open_shell_v2.xml +20 -20
  36. data/spec/stubs/responses/soap_fault_v1.xml +36 -36
  37. data/spec/stubs/responses/soap_fault_v2.xml +42 -42
  38. data/spec/stubs/responses/wmi_error_v2.xml +41 -41
  39. data/spec/winrm_options_spec.rb +76 -76
  40. data/spec/winrm_primitives_spec.rb +51 -51
  41. data/spec/wql_spec.rb +14 -14
  42. data/winrm.gemspec +40 -41
  43. metadata +4 -4
  44. data/VERSION +0 -1
data/preamble CHANGED
@@ -1,17 +1,17 @@
1
- =begin
2
- This file is part of WinRM; the Ruby library for Microsoft WinRM.
3
-
4
- Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
5
-
6
- Licensed under the Apache License, Version 2.0 (the "License");
7
- you may not use this file except in compliance with the License.
8
- You may obtain a copy of the License at
9
-
10
- http://www.apache.org/licenses/LICENSE-2.0
11
-
12
- Unless required by applicable law or agreed to in writing, software
13
- distributed under the License is distributed on an "AS IS" BASIS,
14
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- See the License for the specific language governing permissions and
16
- limitations under the License.
17
- =end
1
+ =begin
2
+ This file is part of WinRM; the Ruby library for Microsoft WinRM.
3
+
4
+ Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
5
+
6
+ Licensed under the Apache License, Version 2.0 (the "License");
7
+ you may not use this file except in compliance with the License.
8
+ You may obtain a copy of the License at
9
+
10
+ http://www.apache.org/licenses/LICENSE-2.0
11
+
12
+ Unless required by applicable law or agreed to in writing, software
13
+ distributed under the License is distributed on an "AS IS" BASIS,
14
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ See the License for the specific language governing permissions and
16
+ limitations under the License.
17
+ =end
@@ -1,16 +1,16 @@
1
- # encoding: UTF-8
2
- # This test may only be meaningful with kerberos auth
3
- # Against server 2012, a kerberos connection will require reauth (get a 401)
4
- # if there are no requests for >= 15 seconds
5
-
6
- describe 'Verify kerberos will reauth when necessary', kerberos: true do
7
- before(:all) do
8
- @winrm = winrm_connection
9
- end
10
-
11
- it 'work with a 18 second sleep' do
12
- ps_command = 'Start-Sleep -s 18'
13
- output = @winrm.run_powershell_script(ps_command)
14
- output[:exitcode].should == 0
15
- end
16
- end
1
+ # encoding: UTF-8
2
+ # This test may only be meaningful with kerberos auth
3
+ # Against server 2012, a kerberos connection will require reauth (get a 401)
4
+ # if there are no requests for >= 15 seconds
5
+
6
+ describe 'Verify kerberos will reauth when necessary', kerberos: true do
7
+ before(:all) do
8
+ @winrm = winrm_connection
9
+ end
10
+
11
+ it 'work with a 18 second sleep' do
12
+ ps_command = 'Start-Sleep -s 18'
13
+ output = @winrm.run_powershell_script(ps_command)
14
+ output[:exitcode].should == 0
15
+ end
16
+ end
@@ -1,102 +1,102 @@
1
- # encoding: UTF-8
2
- describe 'winrm client cmd', integration: true do
3
- before(:all) do
4
- @winrm = winrm_connection
5
- end
6
-
7
- describe 'empty string' do
8
- subject(:output) { @winrm.cmd('') }
9
- it { should have_exit_code 0 }
10
- it { should have_no_stdout }
11
- it { should have_no_stderr }
12
- end
13
-
14
- describe 'ipconfig' do
15
- subject(:output) { @winrm.cmd('ipconfig') }
16
- it { should have_exit_code 0 }
17
- it { should have_stdout_match(/Windows IP Configuration/) }
18
- it { should have_no_stderr }
19
- end
20
-
21
- describe 'echo \'hello world\' using apostrophes' do
22
- subject(:output) { @winrm.cmd("echo 'hello world'") }
23
- it { should have_exit_code 0 }
24
- it { should have_stdout_match(/'hello world'/) }
25
- it { should have_no_stderr }
26
- end
27
-
28
- describe 'echo "string with trailing \\" using double quotes' do
29
- # This is a regression test for #131. " is converted to &quot; when serializing
30
- # the command to SOAP/XML. Any naive substitution performed on such a serialized
31
- # string can result in any \& sequence being interpreted as a back-substitution.
32
- subject(:output) { @winrm.cmd('echo "string with trailing \\"') }
33
- it { should have_exit_code 0 }
34
- it { should have_stdout_match(/string with trailing \\/) }
35
- it { should have_no_stderr }
36
- end
37
-
38
- describe 'capturing output from stdout and stderr' do
39
- subject(:output) do
40
- # Note: Multiple lines doesn't work:
41
- # script = <<-eos
42
- # echo Hello
43
- # echo , world! 1>&2
44
- # eos
45
-
46
- script = 'echo Hello & echo , world! 1>&2'
47
-
48
- @captured_stdout = ''
49
- @captured_stderr = ''
50
- @winrm.cmd(script) do |stdout, stderr|
51
- @captured_stdout << stdout if stdout
52
- @captured_stderr << stderr if stderr
53
- end
54
- end
55
-
56
- it 'should have stdout' do
57
- expect(output.stdout).to eq("Hello \r\n")
58
- expect(output.stdout).to eq(@captured_stdout)
59
- end
60
-
61
- it 'should have stderr' do
62
- expect(output.stderr).to eq(", world! \r\n")
63
- expect(output.stderr).to eq(@captured_stderr)
64
- end
65
-
66
- it 'should have output' do
67
- expect(output.output).to eq("Hello \r\n, world! \r\n")
68
- end
69
- end
70
-
71
- describe 'ipconfig with /all argument' do
72
- subject(:output) { @winrm.cmd('ipconfig', %w(/all)) }
73
- it { should have_exit_code 0 }
74
- it { should have_stdout_match(/Windows IP Configuration/) }
75
- it { should have_no_stderr }
76
- end
77
-
78
- describe 'dir with incorrect argument /z' do
79
- subject(:output) { @winrm.cmd('dir /z') }
80
- it { should have_exit_code 1 }
81
- it { should have_no_stdout }
82
- it { should have_stderr_match(/Invalid switch/) }
83
- end
84
-
85
- describe 'ipconfig && echo error 1>&2' do
86
- subject(:output) { @winrm.cmd('ipconfig && echo error 1>&2') }
87
- it { should have_exit_code 0 }
88
- it { should have_stdout_match(/Windows IP Configuration/) }
89
- it { should have_stderr_match(/error/) }
90
- end
91
-
92
- describe 'ipconfig with a block' do
93
- subject(:stdout) do
94
- outvar = ''
95
- @winrm.cmd('ipconfig') do |stdout, _stderr|
96
- outvar << stdout
97
- end
98
- outvar
99
- end
100
- it { should match(/Windows IP Configuration/) }
101
- end
102
- end
1
+ # encoding: UTF-8
2
+ describe 'winrm client cmd', integration: true do
3
+ before(:all) do
4
+ @winrm = winrm_connection
5
+ end
6
+
7
+ describe 'empty string' do
8
+ subject(:output) { @winrm.cmd('') }
9
+ it { should have_exit_code 0 }
10
+ it { should have_no_stdout }
11
+ it { should have_no_stderr }
12
+ end
13
+
14
+ describe 'ipconfig' do
15
+ subject(:output) { @winrm.cmd('ipconfig') }
16
+ it { should have_exit_code 0 }
17
+ it { should have_stdout_match(/Windows IP Configuration/) }
18
+ it { should have_no_stderr }
19
+ end
20
+
21
+ describe 'echo \'hello world\' using apostrophes' do
22
+ subject(:output) { @winrm.cmd("echo 'hello world'") }
23
+ it { should have_exit_code 0 }
24
+ it { should have_stdout_match(/'hello world'/) }
25
+ it { should have_no_stderr }
26
+ end
27
+
28
+ describe 'echo "string with trailing \\" using double quotes' do
29
+ # This is a regression test for #131. " is converted to &quot; when serializing
30
+ # the command to SOAP/XML. Any naive substitution performed on such a serialized
31
+ # string can result in any \& sequence being interpreted as a back-substitution.
32
+ subject(:output) { @winrm.cmd('echo "string with trailing \\"') }
33
+ it { should have_exit_code 0 }
34
+ it { should have_stdout_match(/string with trailing \\/) }
35
+ it { should have_no_stderr }
36
+ end
37
+
38
+ describe 'capturing output from stdout and stderr' do
39
+ subject(:output) do
40
+ # Note: Multiple lines doesn't work:
41
+ # script = <<-eos
42
+ # echo Hello
43
+ # echo , world! 1>&2
44
+ # eos
45
+
46
+ script = 'echo Hello & echo , world! 1>&2'
47
+
48
+ @captured_stdout = ''
49
+ @captured_stderr = ''
50
+ @winrm.cmd(script) do |stdout, stderr|
51
+ @captured_stdout << stdout if stdout
52
+ @captured_stderr << stderr if stderr
53
+ end
54
+ end
55
+
56
+ it 'should have stdout' do
57
+ expect(output.stdout).to eq("Hello \r\n")
58
+ expect(output.stdout).to eq(@captured_stdout)
59
+ end
60
+
61
+ it 'should have stderr' do
62
+ expect(output.stderr).to eq(", world! \r\n")
63
+ expect(output.stderr).to eq(@captured_stderr)
64
+ end
65
+
66
+ it 'should have output' do
67
+ expect(output.output).to eq("Hello \r\n, world! \r\n")
68
+ end
69
+ end
70
+
71
+ describe 'ipconfig with /all argument' do
72
+ subject(:output) { @winrm.cmd('ipconfig', %w(/all)) }
73
+ it { should have_exit_code 0 }
74
+ it { should have_stdout_match(/Windows IP Configuration/) }
75
+ it { should have_no_stderr }
76
+ end
77
+
78
+ describe 'dir with incorrect argument /z' do
79
+ subject(:output) { @winrm.cmd('dir /z') }
80
+ it { should have_exit_code 1 }
81
+ it { should have_no_stdout }
82
+ it { should have_stderr_match(/Invalid switch/) }
83
+ end
84
+
85
+ describe 'ipconfig && echo error 1>&2' do
86
+ subject(:output) { @winrm.cmd('ipconfig && echo error 1>&2') }
87
+ it { should have_exit_code 0 }
88
+ it { should have_stdout_match(/Windows IP Configuration/) }
89
+ it { should have_stderr_match(/error/) }
90
+ end
91
+
92
+ describe 'ipconfig with a block' do
93
+ subject(:stdout) do
94
+ outvar = ''
95
+ @winrm.cmd('ipconfig') do |stdout, _stderr|
96
+ outvar << stdout
97
+ end
98
+ outvar
99
+ end
100
+ it { should match(/Windows IP Configuration/) }
101
+ end
102
+ end
@@ -1,19 +1,19 @@
1
- # Copy this file to config.yml and edit the settings below.
2
- # This should work out of the box for vagrant provisioned boxes.
3
-
4
- ## Kerberos
5
- #auth_type: kerberos
6
- #endpoint: "http://<yourserver>:5985/wsman"
7
- #options:
8
- # realm: "your.realm"
9
-
10
- ## Plain Text
11
- auth_type: plaintext
12
- # If you are running this in a vagrant provisioned box using NAT,
13
- # this will be the forwarded WinRM HTTP port to your VM.
14
- # If you are running this on the VM, the default HTTP port is 5985.
15
- # See README.md#Troubleshooting.
16
- endpoint: "http://localhost:55985/wsman"
17
- options:
18
- user: vagrant
19
- pass: vagrant
1
+ # Copy this file to config.yml and edit the settings below.
2
+ # This should work out of the box for vagrant provisioned boxes.
3
+
4
+ ## Kerberos
5
+ #auth_type: kerberos
6
+ #endpoint: "http://<yourserver>:5985/wsman"
7
+ #options:
8
+ # realm: "your.realm"
9
+
10
+ ## Plain Text
11
+ auth_type: plaintext
12
+ # If you are running this in a vagrant provisioned box using NAT,
13
+ # this will be the forwarded WinRM HTTP port to your VM.
14
+ # If you are running this on the VM, the default HTTP port is 5985.
15
+ # See README.md#Troubleshooting.
16
+ endpoint: "http://localhost:55985/wsman"
17
+ options:
18
+ user: vagrant
19
+ pass: vagrant
@@ -1,50 +1,50 @@
1
- # encoding: UTF-8
2
- describe 'Exceptions', unit: true do
3
- describe WinRM::WinRMAuthorizationError do
4
- let(:error) { WinRM::WinRMHTTPTransportError.new('Foo happened', 500) }
5
-
6
- it 'adds the response code to the message' do
7
- expect(error.message).to eq('Foo happened (500).')
8
- end
9
-
10
- it 'exposes the response code as an attribute' do
11
- expect(error.status_code).to eq 500
12
- end
13
-
14
- it 'is a winrm error' do
15
- expect(error).to be_kind_of(WinRM::WinRMError)
16
- end
17
- end
18
-
19
- describe WinRM::WinRMWSManFault do
20
- let(:error) { WinRM::WinRMWSManFault.new('fault text', 42) }
21
-
22
- it 'exposes the fault text as an attribute' do
23
- expect(error.fault_description).to eq('fault text')
24
- end
25
-
26
- it 'exposes the fault code as an attribute' do
27
- expect(error.fault_code).to eq 42
28
- end
29
-
30
- it 'is a winrm error' do
31
- expect(error).to be_kind_of(WinRM::WinRMError)
32
- end
33
- end
34
-
35
- describe WinRM::WinRMWMIError do
36
- let(:error) { WinRM::WinRMWMIError.new('message text', 77_777) }
37
-
38
- it 'exposes the error text as an attribute' do
39
- expect(error.error).to eq('message text')
40
- end
41
-
42
- it 'exposes the error code as an attribute' do
43
- expect(error.error_code).to eq 77_777
44
- end
45
-
46
- it 'is a winrm error' do
47
- expect(error).to be_kind_of(WinRM::WinRMError)
48
- end
49
- end
50
- end
1
+ # encoding: UTF-8
2
+ describe 'Exceptions', unit: true do
3
+ describe WinRM::WinRMAuthorizationError do
4
+ let(:error) { WinRM::WinRMHTTPTransportError.new('Foo happened', 500) }
5
+
6
+ it 'adds the response code to the message' do
7
+ expect(error.message).to eq('Foo happened (500).')
8
+ end
9
+
10
+ it 'exposes the response code as an attribute' do
11
+ expect(error.status_code).to eq 500
12
+ end
13
+
14
+ it 'is a winrm error' do
15
+ expect(error).to be_kind_of(WinRM::WinRMError)
16
+ end
17
+ end
18
+
19
+ describe WinRM::WinRMWSManFault do
20
+ let(:error) { WinRM::WinRMWSManFault.new('fault text', 42) }
21
+
22
+ it 'exposes the fault text as an attribute' do
23
+ expect(error.fault_description).to eq('fault text')
24
+ end
25
+
26
+ it 'exposes the fault code as an attribute' do
27
+ expect(error.fault_code).to eq 42
28
+ end
29
+
30
+ it 'is a winrm error' do
31
+ expect(error).to be_kind_of(WinRM::WinRMError)
32
+ end
33
+ end
34
+
35
+ describe WinRM::WinRMWMIError do
36
+ let(:error) { WinRM::WinRMWMIError.new('message text', 77_777) }
37
+
38
+ it 'exposes the error text as an attribute' do
39
+ expect(error.error).to eq('message text')
40
+ end
41
+
42
+ it 'exposes the error code as an attribute' do
43
+ expect(error.error_code).to eq 77_777
44
+ end
45
+
46
+ it 'is a winrm error' do
47
+ expect(error).to be_kind_of(WinRM::WinRMError)
48
+ end
49
+ end
50
+ end
@@ -1,15 +1,15 @@
1
- # encoding: UTF-8
2
- describe 'issue 59', integration: true do
3
- before(:all) do
4
- @winrm = winrm_connection
5
- end
6
-
7
- describe 'long running script without output' do
8
- it 'should not error' do
9
- output = @winrm.powershell('sleep 60; Write-Host "Hello"')
10
- expect(output).to have_exit_code 0
11
- expect(output).to have_stdout_match(/Hello/)
12
- expect(output).to have_no_stderr
13
- end
14
- end
15
- end
1
+ # encoding: UTF-8
2
+ describe 'issue 59', integration: true do
3
+ before(:all) do
4
+ @winrm = winrm_connection
5
+ end
6
+
7
+ describe 'long running script without output' do
8
+ it 'should not error' do
9
+ output = @winrm.powershell('sleep 60; Write-Host "Hello"')
10
+ expect(output).to have_exit_code 0
11
+ expect(output).to have_stdout_match(/Hello/)
12
+ expect(output).to have_no_stderr
13
+ end
14
+ end
15
+ end
@@ -1,74 +1,74 @@
1
- # encoding: UTF-8
2
- require 'rspec/expectations'
3
-
4
- # rspec matchers
5
- module WinRMSpecs
6
- def self.stdout(output)
7
- output[:data].collect do |i|
8
- i[:stdout]
9
- end.join('\r\n').gsub(/(\\r\\n)+$/, '')
10
- end
11
-
12
- def self.stderr(output)
13
- output[:data].collect do |i|
14
- i[:stderr]
15
- end.join('\r\n').gsub(/(\\r\\n)+$/, '')
16
- end
17
- end
18
-
19
- RSpec::Matchers.define :have_stdout_match do |expected_stdout|
20
- match do |actual_output|
21
- !expected_stdout.match(WinRMSpecs.stdout(actual_output)).nil?
22
- end
23
- failure_message do |actual_output|
24
- "expected that '#{WinRMSpecs.stdout(actual_output)}' would match #{expected_stdout}"
25
- end
26
- end
27
-
28
- RSpec::Matchers.define :have_stderr_match do |expected_stderr|
29
- match do |actual_output|
30
- !expected_stderr.match(WinRMSpecs.stderr(actual_output)).nil?
31
- end
32
- failure_message do |actual_output|
33
- "expected that '#{WinRMSpecs.stderr(actual_output)}' would match #{expected_stderr}"
34
- end
35
- end
36
-
37
- RSpec::Matchers.define :have_no_stdout do
38
- match do |actual_output|
39
- stdout = WinRMSpecs.stdout(actual_output)
40
- stdout == '\r\n' || stdout == ''
41
- end
42
- failure_message do |actual_output|
43
- "expected that '#{WinRMSpecs.stdout(actual_output)}' would have no stdout"
44
- end
45
- end
46
-
47
- RSpec::Matchers.define :have_no_stderr do
48
- match do |actual_output|
49
- stderr = WinRMSpecs.stderr(actual_output)
50
- stderr == '\r\n' || stderr == ''
51
- end
52
- failure_message do |actual_output|
53
- "expected that '#{WinRMSpecs.stderr(actual_output)}' would have no stderr"
54
- end
55
- end
56
-
57
- RSpec::Matchers.define :have_exit_code do |expected_exit_code|
58
- match do |actual_output|
59
- expected_exit_code == actual_output[:exitcode]
60
- end
61
- failure_message do |actual_output|
62
- "expected exit code #{expected_exit_code}, but got #{actual_output[:exitcode]}"
63
- end
64
- end
65
-
66
- RSpec::Matchers.define :be_a_uid do
67
- match do |actual|
68
- # WinRM1.1 returns uuid's prefixed with 'uuid:' where as later versions do not
69
- actual && actual.to_s.match(/^(uuid:)*\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/)
70
- end
71
- failure_message do |actual|
72
- "expected a uid, but got '#{actual}'"
73
- end
74
- end
1
+ # encoding: UTF-8
2
+ require 'rspec/expectations'
3
+
4
+ # rspec matchers
5
+ module WinRMSpecs
6
+ def self.stdout(output)
7
+ output[:data].collect do |i|
8
+ i[:stdout]
9
+ end.join('\r\n').gsub(/(\\r\\n)+$/, '')
10
+ end
11
+
12
+ def self.stderr(output)
13
+ output[:data].collect do |i|
14
+ i[:stderr]
15
+ end.join('\r\n').gsub(/(\\r\\n)+$/, '')
16
+ end
17
+ end
18
+
19
+ RSpec::Matchers.define :have_stdout_match do |expected_stdout|
20
+ match do |actual_output|
21
+ !expected_stdout.match(WinRMSpecs.stdout(actual_output)).nil?
22
+ end
23
+ failure_message do |actual_output|
24
+ "expected that '#{WinRMSpecs.stdout(actual_output)}' would match #{expected_stdout}"
25
+ end
26
+ end
27
+
28
+ RSpec::Matchers.define :have_stderr_match do |expected_stderr|
29
+ match do |actual_output|
30
+ !expected_stderr.match(WinRMSpecs.stderr(actual_output)).nil?
31
+ end
32
+ failure_message do |actual_output|
33
+ "expected that '#{WinRMSpecs.stderr(actual_output)}' would match #{expected_stderr}"
34
+ end
35
+ end
36
+
37
+ RSpec::Matchers.define :have_no_stdout do
38
+ match do |actual_output|
39
+ stdout = WinRMSpecs.stdout(actual_output)
40
+ stdout == '\r\n' || stdout == ''
41
+ end
42
+ failure_message do |actual_output|
43
+ "expected that '#{WinRMSpecs.stdout(actual_output)}' would have no stdout"
44
+ end
45
+ end
46
+
47
+ RSpec::Matchers.define :have_no_stderr do
48
+ match do |actual_output|
49
+ stderr = WinRMSpecs.stderr(actual_output)
50
+ stderr == '\r\n' || stderr == ''
51
+ end
52
+ failure_message do |actual_output|
53
+ "expected that '#{WinRMSpecs.stderr(actual_output)}' would have no stderr"
54
+ end
55
+ end
56
+
57
+ RSpec::Matchers.define :have_exit_code do |expected_exit_code|
58
+ match do |actual_output|
59
+ expected_exit_code == actual_output[:exitcode]
60
+ end
61
+ failure_message do |actual_output|
62
+ "expected exit code #{expected_exit_code}, but got #{actual_output[:exitcode]}"
63
+ end
64
+ end
65
+
66
+ RSpec::Matchers.define :be_a_uid do
67
+ match do |actual|
68
+ # WinRM1.1 returns uuid's prefixed with 'uuid:' where as later versions do not
69
+ actual && actual.to_s.match(/^(uuid:)*\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/)
70
+ end
71
+ failure_message do |actual|
72
+ "expected a uid, but got '#{actual}'"
73
+ end
74
+ end