hussh 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab64923b5852ed124f71625eb035668ffac73fe7
4
- data.tar.gz: 4eb55836fcacc42470e7376a2ae6e9153e09575a
3
+ metadata.gz: a257554c5698eec071106e3d07d3ba27ac8e4d68
4
+ data.tar.gz: 9898be5599601f68c93b161e37ca5569752b9e69
5
5
  SHA512:
6
- metadata.gz: 4cbac79becbae698f895437051fb3ae239859ae67ac417913e5cce135235d0f1c535e307f1c8bca8f28b59ee16151680c8c944e5fdacb172a0956bbe03a04665
7
- data.tar.gz: 84e10d5ded1e5b56151677b6c58172ec2c1c3c0a09a729f39c485483979a7fec24922b9ba04c4f94b91fd480d48d8e9baf84c681bf1ee06aa1474aa4a270dc2c
6
+ metadata.gz: 500700b7c98b007ccfabecc971b2939a829621e616dd84fe8248c18b5f89ecfc2f2d8d3ff291458f4ef7453c5f2834a0a8c85fb445dab6c95faecc34ffa966bc
7
+ data.tar.gz: df7f6353a10afea92a5d643cf6403e648f9a94d91d3824c85a942bf4a3cbf9a6963861f70055b0aca1ef3e690fb481c0fc6faac8c75ed8cadfb2327b7c3784e1
@@ -3,6 +3,7 @@
3
3
  A mocking library for <tt>Net::SSH</tt> which allows testers to specify
4
4
  responses and record real-life responses for later use.
5
5
 
6
+ {<img src="https://badge.fury.io/rb/hussh.svg" alt="Gem Version" />}[http://badge.fury.io/rb/hussh]
6
7
  {<img src="https://travis-ci.org/moneyadviceservice/hussh.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/moneyadviceservice/hussh]
7
8
 
8
9
  == Installation
@@ -5,47 +5,90 @@ module Hussh
5
5
  @request_pty = false
6
6
  end
7
7
 
8
- def real_channel
9
- @real_channel ||= @session.real_session.open_channel
8
+ def have_real_channel?
9
+ !!@real_channel
10
10
  end
11
11
 
12
- def response_data
13
- @data
12
+ def open_real_channel
13
+ @real_channel ||= @session.real_session.open_channel
14
14
  end
15
15
 
16
16
  attr :command
17
17
  attr :exec_block
18
18
  def exec(command, &block)
19
19
  @command = command
20
- @exec_block = block
21
- end
22
-
23
- attr :on_data_block
24
- def on_data(&block)
25
- @on_data_block = block
26
- end
20
+ Hussh.commands_run << @command
21
+ if !@session.has_response?(@command)
22
+ open_real_channel
23
+ request_pty(&@request_pty_callback) if @request_pty
24
+ on_data(&@on_data_callback) if @on_data_callback
25
+ if @on_extended_data_callback
26
+ on_extended_data(&@on_extended_data_callback)
27
+ end
27
28
 
28
- attr :on_extended_data_block
29
- def on_extended_data(&block)
30
- @on_extended_data_block = block
29
+ @real_channel.exec(command) do |ch, success|
30
+ @exec_result = success
31
+ block.call(self, success) if block
32
+ end
33
+ elsif block_given?
34
+ yield(self, true)
35
+ end
31
36
  end
32
37
 
33
- attr :request_pty_block
34
38
  def request_pty(&block)
35
- @request_pty = true
36
- @request_pty_block = block
39
+ if have_real_channel?
40
+ @real_channel.request_pty do |ch, success|
41
+ block.call(ch, success) if block
42
+ end
43
+ else
44
+ @request_pty = true
45
+ @request_pty_callback = block
46
+ end
37
47
  end
38
48
 
39
49
  def requested_pty?
40
50
  @request_pty
41
51
  end
42
52
 
43
- # def on_close(&block)
44
- # @on_close = block
45
- # end
53
+ def on_data(&block)
54
+ if have_real_channel?
55
+ @real_channel.on_data do |ch, output|
56
+ @stdout ||= ''
57
+ @stdout << output
58
+ block.call(ch, output) if block
59
+ end
60
+ else
61
+ @on_data_callback = block
62
+ end
63
+ end
64
+
65
+ def on_extended_data(&block)
66
+ if have_real_channel?
67
+ @real_channel.on_extended_data do |ch, output|
68
+ @stderr ||= ''
69
+ @stderr << output
70
+ block.call(ch, output) if block
71
+ end
72
+ else
73
+ @on_extended_data_callback = block
74
+ end
75
+ end
46
76
 
47
- # def close
48
- # @real_channel.close if @real_channel
49
- # end
77
+ def wait
78
+ if @real_channel
79
+ @real_channel.wait
80
+ end
81
+ end
82
+
83
+ def close
84
+ if have_real_channel?
85
+ @real_channel.close
86
+ @session.update_recording(@command, @stdout) if @stdout
87
+ else
88
+ stdout = @session.response_for(@command)
89
+ @on_data_callback.call(self, stdout) if stdout && @on_data_callback
90
+ @on_extended_data_callback.call(self, @stderr) if @stderr && @on_extended_data_callback
91
+ end
92
+ end
50
93
  end
51
94
  end
@@ -20,9 +20,18 @@ module Hussh
20
20
 
21
21
  config.before(:each, hussh: lambda { |v| !!v }) do |example|
22
22
  options = example.metadata[:hussh]
23
- options = options.is_a?(Hash) ? options.dup : {}
24
- recording_name = options.delete(:recording_name) ||
25
- recording_name_for[example.metadata]
23
+ if options.is_a?(Hash)
24
+ options = options.dup
25
+ recording_name = options.delete(:recording_name) ||
26
+ recording_name_for[example.metadata]
27
+ elsif options.is_a?(String)
28
+ recording_name = options
29
+ options = {}
30
+ else
31
+ recording_name = recording_name_for[example.metadata]
32
+ options = {}
33
+ end
34
+
26
35
  Hussh.load_recording(recording_name)
27
36
  Hussh.clear_stubbed_responses
28
37
  end
@@ -5,12 +5,17 @@ module Hussh
5
5
  def initialize(host, user)
6
6
  @host = host
7
7
  @user = user
8
+ @channel_id_counter = 0
8
9
  end
9
10
 
10
11
  def real_session
11
12
  @real_session ||= Net::SSH.start_without_hussh(@host, @user)
12
13
  end
13
14
 
15
+ def have_real_session?
16
+ !!@real_session
17
+ end
18
+
14
19
  def has_response?(command)
15
20
  Hussh.stubbed_responses.fetch(@host, {}).fetch(@user, {})
16
21
  .has_key?(command) ||
@@ -43,54 +48,27 @@ module Hussh
43
48
  end
44
49
  end
45
50
 
46
- def open_channel(&block)
47
- @channel = Channel.new(self)
48
- block.call(@channel)
49
- Hussh.commands_run << @channel.command
50
- if self.has_response?(@channel.command)
51
- if @channel.exec_block.respond_to?(:call)
52
- @channel.exec_block.call(@channel, true)
53
- end
54
-
55
- if @channel.on_data_block.respond_to?(:call)
56
- @channel.on_data_block.call(@channel, self.response_for(@channel.command))
57
- end
58
- else
59
- self.real_session.open_channel do |ch|
60
-
61
- if @channel.requested_pty?
62
- ch.request_pty do |ch, success|
63
- if @channel.request_pty_block.respond_to?(:call)
64
- @channel.request_pty_block.call(ch, success)
65
- end
66
- end
67
- end
68
-
69
- ch.exec(@channel.command) do |ch, success|
70
- @channel.exec_block.call(@channel, success) if @channel.exec_block
71
-
72
- ch.on_data do |ch, output|
73
- if @channel.on_data_block.respond_to?(:call)
74
- @channel.on_data_block.call(@channel, output)
75
- @on_data = output
76
- self.update_recording(@channel.command, @on_data)
77
- end
78
- end
51
+ def channels
52
+ @channels ||= {}
53
+ end
79
54
 
80
- ch.on_extended_data do |ch, output|
81
- if @channel.on_extended_data_block.respond_to?(:call)
82
- @channel.on_extended_data_block.call(@channel, output)
83
- end
84
- end
55
+ def get_next_channel_id
56
+ @channel_id_counter += 1
57
+ end
85
58
 
86
- end
87
- end
88
- end
59
+ def open_channel(&block)
60
+ channel = Channel.new(self)
61
+ yield(channel) if block_given?
62
+ channels[get_next_channel_id] = channel
89
63
  end
90
64
 
91
65
  def close
92
- @real_session.close if @real_session
93
- @real_session = nil
66
+ channels.each do |id, channel|
67
+ channel.close
68
+ end
69
+ if have_real_session?
70
+ real_session.close
71
+ end
94
72
  end
95
73
  end
96
74
  end
@@ -1,4 +1,4 @@
1
1
  module Hussh
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.1'
3
3
  end
4
4
 
@@ -7,142 +7,156 @@ require 'hussh'
7
7
  RSpec.describe Hussh do
8
8
  include FakeFS::SpecHelpers
9
9
 
10
- describe Hussh::Channel do
11
- before do
12
- Hussh.commands_run.clear
13
- Hussh.clear_recorded_responses
14
- Hussh.clear_stubbed_responses
15
- end
10
+ let(:session) { spy(Hussh::Session) }
11
+ let(:channel) { Hussh::Channel.new(session) }
12
+ let(:real_channel) do
13
+ channel.instance_eval { @real_channel }
14
+ end
16
15
 
17
- let!(:channel) do
18
- channel = instance_spy('Net::SSH::Connection::Channel')
19
- # Setup a fake Channel object which will hopefully behave like a
20
- # Net::SSH channel, i.e. register the command and callbacks, and then
21
- # call them all with the appropriate data and values.
22
- allow(channel).to receive(:exec) do |cmd, &blk|
23
- @command = cmd
24
- @exec = blk
25
- end
26
- allow(channel).to receive(:request_pty) { |&block| @request_pty = block }
27
- allow(channel).to receive(:on_data) { |&block| @on_data = block }
28
- allow(channel).to receive(:on_extended_data) do |&block|
29
- @on_extended_data = block
30
- end
31
- # TODO: loop and wait should also end up calling the callbacks
32
- allow(channel).to receive(:close) do
33
- # Allow the test to specify a command that fails.
34
- @exec.call(channel, !@command.match(/fail/)) if @exec
35
- # Allow the test to specify a failed pty request.
36
- @request_pty.call(channel, !@command.match(/nopty/)) if @request_pty
37
- @on_data.call(channel, "#{@command} output") if @on_data
38
- if @on_extended_data
39
- @on_extended_data.call(channel, "#{@command} stderr output")
40
- end
41
- end
42
- allow(channel).to receive(:wait) do
43
- channel.close
44
- end
45
- channel
46
- end
16
+ let(:mock_block) do
17
+ block = Proc.new {}
18
+ allow(block).to receive(:call)
19
+ block
20
+ end
47
21
 
48
- let!(:session) do
49
- session = instance_spy('Net::SSH::Connection::Session')
50
- # The session here is just used to stub our channel in for the real one.
51
- allow(session).to receive(:open_channel) do |&blk|
52
- blk.call(channel)
53
- channel.close
54
- end
55
- allow(Net::SSH).to receive(:start_without_hussh).and_return(session)
56
- session
22
+ context 'real channel opened' do
23
+ before do
24
+ spy = instance_spy('Net::SSH::Connection::Channel')
25
+ channel.instance_eval { @real_channel = spy }
57
26
  end
58
27
 
59
- context 'when using an exec block' do
60
- before do
61
- FileUtils.mkdir_p 'fixtures/hussh'
62
- File.write('fixtures/hussh/saved_responses.yaml', saved_responses)
63
- Hussh.load_recording('saved_responses')
64
-
65
- # Simulate how we would use Hussh, which sits between the
66
- # application code (the code below) and the mocked-out Net::SSH
67
- # code.
68
- Net::SSH.start('host', 'user') do |session|
69
- session.open_channel do |ch|
70
- ch.request_pty
71
- ch.exec command do |ch, success|
72
- @exec_success = success
73
- ch.on_data { |c, data| @data = data }
74
- ch.on_extended_data { |c, data| @extended_data = data }
75
- end
28
+ describe :exec do
29
+ context 'where there is no saved response' do
30
+ before do
31
+ allow(real_channel).to receive(:exec) do |command, &block|
32
+ @command = command
33
+ @hussh_block = block
76
34
  end
77
35
  end
78
- end
79
36
 
80
- context 'with a command that has not been run before' do
81
- let(:saved_responses) { {}.to_yaml }
37
+ before do
38
+ allow(session).to receive(:has_response?).and_return(false)
39
+ end
82
40
 
83
- context 'and execution is succesful' do
84
- let(:command) { 'test' }
41
+ it 'records that the command was run' do
42
+ channel.exec('record-command')
43
+ expect(Hussh.commands_run.last).to eql('record-command')
44
+ end
85
45
 
86
- it 'runs the command via ssh' do
87
- expect(channel).to have_received(:exec).with('test')
88
- end
46
+ context 'when a pty has been requested' do
47
+ before { channel.instance_eval { @request_pty = true } }
89
48
 
90
- it 'records that the command was run' do
91
- expect(Hussh.commands_run).to include('test')
49
+ it 'requests a pty' do
50
+ channel.exec('request-pty')
51
+ expect(real_channel).to have_received(:request_pty)
92
52
  end
53
+ end
93
54
 
94
- it 'passes command status to exec' do
95
- expect(@exec_success).to eql(true)
96
- end
55
+ context 'when a pty has not been requested' do
56
+ before { channel.instance_eval { @request_pty = false } }
97
57
 
98
- it 'gives us the stdout' do
99
- expect(@data).to eq 'test output'
58
+ it 'does not request a pty' do
59
+ channel.exec('no-request-pty')
60
+ expect(real_channel).to_not have_received(:request_pty)
100
61
  end
62
+ end
101
63
 
102
- it 'gives us the stderr' do
103
- expect(@extended_data).to eq 'test stderr output'
104
- end
64
+ context 'when an on_data block has been previously defined' do
65
+ before { channel.instance_eval { @on_data_callback = Proc.new {} } }
105
66
 
106
- it 'allows us to request a pty' do
107
- expect(channel).to have_received(:request_pty)
67
+ it 'sets up an on_data callback' do
68
+ channel.exec('on-data')
69
+ expect(real_channel).to have_received(:on_data)
108
70
  end
71
+ end
109
72
 
110
- it 'saves the result of the command' do
111
- expect(Hussh.recorded_responses['host']['user']['test'])
112
- .to eq 'test output'
113
- end
73
+ context 'when an on_data block has not been previously defined' do
74
+ before { channel.instance_eval { @on_data_callback = nil } }
114
75
 
115
- it 'flags the recording as changed' do
116
- expect(Hussh.recording_changed?).to eql(true)
76
+ it 'does not setup an on_data callback' do
77
+ channel.exec('no-on-data')
78
+ expect(real_channel).to_not have_received(:on_data)
117
79
  end
118
80
  end
119
81
 
120
- context 'and has failed to execute' do
121
- let(:command) { 'test-fail' }
122
- subject { @exec_success }
123
- it { is_expected.to eq false }
82
+ it 'calls our block' do
83
+ channel.exec('block-command', &mock_block)
84
+ @hussh_block.call(channel, 'test-for-success')
85
+ expect(mock_block).to have_received(:call)
86
+ .with(channel, 'test-for-success')
124
87
  end
125
88
  end
126
89
 
127
- context 'with a command that has recorded results' do
128
- let(:saved_responses) do
129
- {
130
- 'host' => { 'user' => { 'test' => 'recorded test output' } }
131
- }.to_yaml
90
+ context 'where there is a saved response' do
91
+ before do
92
+ allow(session).to receive(:has_response?).and_return(true)
132
93
  end
133
94
 
134
- context 'and execution is succesful' do
135
- let(:command) { 'test' }
95
+ it 'calls our block' do
96
+ channel.exec('block-command') { @block_called = true }
97
+ expect(@block_called).to eql(true)
98
+ end
99
+ end
100
+ end
136
101
 
137
- it 'gives us the stdout' do
138
- expect(@data).to eq 'recorded test output'
139
- end
102
+ describe :request_pty do
103
+ before do
104
+ allow(real_channel).to receive(:request_pty) { |&b| @hussh_block = b }
105
+ end
140
106
 
141
- it 'gives us the success status' do
142
- expect(@exec_success).to eql(true)
143
- end
107
+ it 'calls request_pty on the real channel' do
108
+ channel.request_pty
109
+ expect(real_channel).to have_received(:request_pty)
110
+ end
111
+
112
+ it 'calls our block' do
113
+ channel.request_pty(&mock_block)
114
+ @hussh_block.call(channel, :status)
115
+ expect(mock_block).to have_received(:call).with(channel, :status)
116
+ end
117
+ end
118
+
119
+ describe :on_data do
120
+ before do
121
+ allow(real_channel).to receive(:on_data) { |&blk| @hussh_block = blk }
122
+ end
123
+
124
+ it 'calls on_data on the real channel' do
125
+ channel.on_data {}
126
+ expect(real_channel).to have_received(:on_data)
127
+ end
128
+
129
+ it 'calls our block' do
130
+ channel.on_data(&mock_block)
131
+ @hussh_block.call(channel, 'stdout')
132
+ expect(mock_block).to have_received(:call).with(channel, 'stdout')
133
+ end
134
+ end
135
+
136
+ describe :on_extended_data do
137
+ before do
138
+ allow(real_channel).to receive(:on_extended_data) do |&block|
139
+ @hussh_block = block
144
140
  end
145
141
  end
142
+
143
+ it 'calls on_extended_data on the real channel' do
144
+ channel.on_extended_data {}
145
+ expect(real_channel).to have_received(:on_extended_data)
146
+ end
147
+
148
+ it 'calls our block' do
149
+ channel.on_extended_data(&mock_block)
150
+ @hussh_block.call(channel, 'stderr')
151
+ expect(mock_block).to have_received(:call).with(channel, 'stderr')
152
+ end
153
+ end
154
+
155
+ describe :wait do
156
+ it 'calls wait on the real channel' do
157
+ channel.wait
158
+ expect(real_channel).to have_received(:wait)
159
+ end
146
160
  end
147
161
  end
148
162
  end
@@ -29,7 +29,80 @@ RSpec.describe Hussh do
29
29
  })
30
30
  end
31
31
 
32
- context 'after block' do
32
+ describe 'before block' do
33
+ let(:recorded_responses) do
34
+ { 'host' => { 'user' => { 'cmd' => 'output' } } }
35
+ end
36
+
37
+ context 'with no params' do
38
+ before do
39
+ allow(@example).to receive(:metadata).and_return(
40
+ {
41
+ hussh: true,
42
+ description: 'some spec',
43
+ example_group: {
44
+ description: 'example group',
45
+ parent_example_group: {
46
+ description: 'parent group'
47
+ }
48
+ }
49
+ }
50
+ )
51
+ FileUtils.mkdir_p('fixtures/hussh/parent group/example group')
52
+ File.write(
53
+ 'fixtures/hussh/parent group/example group/some spec.yaml',
54
+ recorded_responses.to_yaml
55
+ )
56
+ @before.call(@example)
57
+ end
58
+
59
+ it 'loads a recording with a generated name' do
60
+ expect(Hussh.recorded_responses).to eq(recorded_responses)
61
+ end
62
+ end
63
+
64
+ context 'with a string param' do
65
+ before do
66
+ allow(@example).to receive(:metadata).and_return(
67
+ { hussh: 'group/spec' }
68
+ )
69
+ FileUtils.mkdir_p('fixtures/hussh/group')
70
+ File.write(
71
+ 'fixtures/hussh/group/spec.yaml',
72
+ recorded_responses.to_yaml
73
+ )
74
+ @before.call(@example)
75
+ end
76
+
77
+ it 'uses the string as the recording name' do
78
+ expect(Hussh.recorded_responses).to eq(recorded_responses)
79
+ end
80
+ end
81
+
82
+ context 'with a hash param' do
83
+ before do
84
+ allow(@example).to receive(:metadata).and_return(
85
+ {
86
+ hussh: {
87
+ recording_name: 'parent/spec'
88
+ }
89
+ }
90
+ )
91
+ FileUtils.mkdir_p('fixtures/hussh/parent')
92
+ File.write(
93
+ 'fixtures/hussh/parent/spec.yaml',
94
+ recorded_responses.to_yaml
95
+ )
96
+ @before.call(@example)
97
+ end
98
+
99
+ it 'gets the recording_name from the hash' do
100
+ expect(Hussh.recorded_responses).to eq(recorded_responses)
101
+ end
102
+ end
103
+ end
104
+
105
+ describe 'after block' do
33
106
  before do
34
107
  Hussh.recorded_responses = {
35
108
  'host' => { 'user' => { 'cmd' => 'output' } }
@@ -0,0 +1,377 @@
1
+ require 'hussh'
2
+ require 'fakefs/spec_helpers'
3
+ require 'yaml'
4
+
5
+ RSpec.describe Hussh do
6
+ include FakeFS::SpecHelpers
7
+
8
+ # Setup a fake Channel object which will hopefully behave like a Net::SSH
9
+ # channel, i.e. register the command and callbacks, and then call them all
10
+ # with the appropriate data and in the right order.
11
+ let!(:real_channel) do
12
+ channel = instance_spy('Net::SSH::Connection::Channel')
13
+
14
+ allow(channel).to receive(:exec) do |cmd, &block|
15
+ @command = cmd
16
+ # Allow the test to specify a command that fails.
17
+ #
18
+ # So ... in Ruby 1.9.3, `@command.match(/fail/)` returns nil even when it
19
+ # matches, inside this block to RSpec::Mocks::ExampleMethods#receive, but
20
+ # not outside of it. There's some deep dark voodoo going on here, so flip
21
+ # the Regexp and the String for now to make tests pass.
22
+ #
23
+ # See https://github.com/rspec/rspec-expectations/issues/781
24
+ @success = !/fail/.match(@command)
25
+ block.call(channel, @success) if block
26
+ output = "#{cmd} output"
27
+ error_output = "#{cmd} error output"
28
+ if cmd.match(/pty/) && !@request_pty
29
+ output = nil
30
+ error_output = 'no pty'
31
+ end
32
+
33
+ if output
34
+ if @on_data
35
+ @on_data.call(channel, output)
36
+ else
37
+ @on_data_pending = output
38
+ end
39
+ end
40
+
41
+ if error_output
42
+ if @on_extended_data
43
+ @on_extended_data.call(channel, error_output)
44
+ else
45
+ @on_extended_data_pending = error_output
46
+ end
47
+ end
48
+ end
49
+
50
+ allow(channel).to receive(:request_pty) do |&block|
51
+ block.call(channel, true) if block
52
+ @request_pty = true
53
+ end
54
+
55
+ allow(channel).to receive(:on_data) do |&block|
56
+ @on_data = block
57
+ if @on_data_pending
58
+ block.call(channel, @on_data_pending)
59
+ @on_data_pending = nil
60
+ end
61
+ end
62
+
63
+ allow(channel).to receive(:on_extended_data) do |&block|
64
+ @on_extended_data = block
65
+ if @on_extended_data_pending
66
+ block.call(channel, @on_extended_data_pending)
67
+ @on_extended_data_pending = nil
68
+ end
69
+ end
70
+
71
+ channel
72
+ end
73
+
74
+ # Inject our "real_channel" above into code that uses Net::SSH
75
+ let!(:real_session) do
76
+ session = instance_spy('Net::SSH::Connection::Session')
77
+ allow(session).to receive(:exec!) { |c| "#{c} output" }
78
+ allow(session).to receive(:open_channel) do |&blk|
79
+
80
+ blk.call(real_channel) if blk
81
+ real_channel
82
+ end
83
+ allow(Net::SSH).to receive(:start_without_hussh).and_return(session)
84
+ session
85
+ end
86
+
87
+ let(:saved_responses) { {}.to_yaml }
88
+
89
+
90
+ before do
91
+ Hussh.commands_run.clear
92
+ Hussh.clear_stubbed_responses
93
+
94
+ FileUtils.mkdir_p 'fixtures/hussh'
95
+ File.write('fixtures/hussh/saved_responses.yaml', saved_responses)
96
+ Hussh.load_recording('saved_responses')
97
+ end
98
+
99
+ describe :exec! do
100
+ context 'with a command that has not been run before' do
101
+ before do
102
+ Net::SSH.start('host', 'user') do |s|
103
+ @output = s.exec!('id')
104
+ end
105
+ end
106
+
107
+ it 'runs the command via ssh' do
108
+ expect(real_session).to have_received(:exec!).with('id')
109
+ end
110
+
111
+ it 'records that the command was run' do
112
+ expect(Hussh.commands_run).to include('id')
113
+ end
114
+
115
+ it 'returns the result of the command' do
116
+ expect(@output).to eql("id output")
117
+ end
118
+
119
+ it 'saves the result of the command' do
120
+ expect(Hussh.recorded_responses['host']['user']['id'])
121
+ .to eql("id output")
122
+ end
123
+
124
+ it 'flags the recording as changed' do
125
+ expect(Hussh.recording_changed?).to eql(true)
126
+ end
127
+ end
128
+
129
+ context 'with a command that has been run before' do
130
+ before do
131
+ FileUtils.mkdir_p 'fixtures/hussh'
132
+ File.write(
133
+ 'fixtures/hussh/saved_responses.yaml',
134
+ {
135
+ 'host' => { 'user' => { 'hostname' => "subsix\n" } }
136
+ }.to_yaml
137
+ )
138
+ Hussh.load_recording('saved_responses')
139
+ Net::SSH.start('host', 'user') { |s| s.exec!('hostname') }
140
+ end
141
+
142
+ it "doesn't run the command via ssh" do
143
+ expect(Net::SSH).to_not have_received(:start_without_hussh)
144
+ end
145
+
146
+ it 'records that the command was run' do
147
+ expect(Hussh.commands_run).to include('hostname')
148
+ end
149
+
150
+ it "doesn't flags the recording as changed" do
151
+ expect(Hussh.recording_changed?).to eql(false)
152
+ end
153
+ end
154
+ end
155
+
156
+ describe 'using callbacks defined after exec and no pty' do
157
+ before do
158
+ # Simulate how we would use Hussh, which sits between the
159
+ # application code (the code below) and the mocked-out Net::SSH
160
+ # code.
161
+ Net::SSH.start('host', 'user') do |session|
162
+ session.open_channel do |ch|
163
+ ch.exec command
164
+ ch.on_data { |c, data| @data = data }
165
+ ch.on_extended_data { |c, data| @extended_data = data }
166
+ end
167
+ end
168
+ end
169
+
170
+ let(:command) { 'callbacks-after' }
171
+
172
+ it 'runs the command via ssh' do
173
+ expect(real_channel).to have_received(:exec).with(command)
174
+ end
175
+
176
+ it 'records the command was run' do
177
+ expect(Hussh.commands_run).to include(command)
178
+ end
179
+
180
+ it 'gives us the stdout' do
181
+ expect(@data).to eq "#{command} output"
182
+ end
183
+
184
+ it 'gives us the stderr' do
185
+ expect(@extended_data).to eq "#{command} error output"
186
+ end
187
+
188
+ context 'with a command that requires a pty' do
189
+ let(:command) { 'test-pty-fail' }
190
+
191
+ it 'has no stdout' do
192
+ expect(@data).to eq nil
193
+ end
194
+
195
+ it 'signals error on stderr' do
196
+ expect(@extended_data).to eq 'no pty'
197
+ end
198
+ end
199
+
200
+ context 'with saved responses' do
201
+ let(:saved_responses) do
202
+ {
203
+ 'host' => { 'user' => { command => "recorded #{command} output" } }
204
+ }.to_yaml
205
+ end
206
+
207
+ it 'gives us the recorded stdout' do
208
+ expect(@data).to eq "recorded #{command} output"
209
+ end
210
+ end
211
+ end
212
+
213
+ describe 'callbacks defined before exec and no pty' do
214
+ before do
215
+ # Callbacks defined before the exec should still be called.
216
+ Net::SSH.start('host', 'user') do |session|
217
+ session.open_channel do |ch|
218
+ ch.on_data { |c, data| @data = data }
219
+ ch.on_extended_data { |c, data| @extended_data = data }
220
+ ch.exec 'callbacks-before'
221
+ end
222
+ end
223
+ end
224
+
225
+ it 'runs the command via ssh' do
226
+ expect(real_channel).to have_received(:exec).with('callbacks-before')
227
+ end
228
+
229
+ it 'records the command was run' do
230
+ expect(Hussh.commands_run).to include('callbacks-before')
231
+ end
232
+
233
+ it 'gives us the stdout' do
234
+ expect(@data).to eq 'callbacks-before output'
235
+ end
236
+
237
+ it 'gives us the stderr' do
238
+ expect(@extended_data).to eq 'callbacks-before error output'
239
+ end
240
+ end
241
+
242
+ describe 'requesting a pty' do
243
+ before do
244
+ # Request a pty before we run exec.
245
+ Net::SSH.start('host', 'user') do |session|
246
+ session.open_channel do |ch|
247
+ ch.request_pty
248
+ ch.exec 'test-pty'
249
+ end
250
+ end
251
+ end
252
+
253
+ it 'requests a pty' do
254
+ expect(real_channel).to have_received(:request_pty)
255
+ end
256
+ end
257
+
258
+ describe 'using an exec block' do
259
+ before do
260
+ # Use Net::SSH and our callbacks defined in the exec block.
261
+ Net::SSH.start('host', 'user') do |session|
262
+ session.open_channel do |ch|
263
+ ch.exec command do |ch, success|
264
+ @exec_success = success
265
+ ch.on_data { |c, data| @data = data }
266
+ ch.on_extended_data { |c, data| @extended_data = data }
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ let(:command) { 'block-test' }
273
+
274
+ it 'runs the command via ssh' do
275
+ expect(real_channel).to have_received(:exec).with(command)
276
+ end
277
+
278
+ it 'records that the command was run' do
279
+ expect(Hussh.commands_run).to include(command)
280
+ end
281
+
282
+ it 'passes command status to exec' do
283
+ expect(@exec_success).to eql(true)
284
+ end
285
+
286
+ it 'gives us the stdout' do
287
+ expect(@data).to eq "#{command} output"
288
+ end
289
+
290
+ it 'gives us the stderr' do
291
+ expect(@extended_data).to eq "#{command} error output"
292
+ end
293
+
294
+ it 'saves the result of the command' do
295
+ expect(Hussh.recorded_responses['host']['user'][command])
296
+ .to eq "#{command} output"
297
+ end
298
+
299
+ it 'flags the recording as changed' do
300
+ expect(Hussh.recording_changed?).to eql(true)
301
+ end
302
+
303
+ context 'with a failed connection' do
304
+ let(:command) { 'test-fail' }
305
+ subject { @exec_success }
306
+ it { is_expected.to eq false }
307
+ end
308
+
309
+ context 'with a command that has recorded results' do
310
+ let(:command) { 'recorded-test' }
311
+ let(:saved_responses) do
312
+ {
313
+ 'host' => { 'user' => { 'test-recorded' => "#{command} output" } }
314
+ }.to_yaml
315
+ end
316
+
317
+ it 'gives us the stdout' do
318
+ expect(@data).to eq "#{command} output"
319
+ end
320
+
321
+ it 'gives us the success status' do
322
+ expect(@exec_success).to eql(true)
323
+ end
324
+ end
325
+ end
326
+
327
+ describe 'using exec wthout a block' do
328
+ before do
329
+ # Use Net::SSH and our callbacks defined in the exec block.
330
+ Net::SSH.start('host', 'user') do |session|
331
+ ch = session.open_channel
332
+ ch.on_data { |c, data| @data = data }
333
+ ch.on_extended_data { |c, data| @extended_data = data }
334
+ ch.exec command
335
+ end
336
+ end
337
+
338
+ let(:command) { 'exec-no-block' }
339
+
340
+ it 'runs the command via ssh' do
341
+ expect(real_channel).to have_received(:exec).with(command)
342
+ end
343
+
344
+ it 'records that the command was run' do
345
+ expect(Hussh.commands_run).to include(command)
346
+ end
347
+
348
+ it 'gives us the stdout' do
349
+ expect(@data).to eq "#{command} output"
350
+ end
351
+
352
+ it 'gives us the stderr' do
353
+ expect(@extended_data).to eq "#{command} error output"
354
+ end
355
+
356
+ it 'saves the result of the command' do
357
+ expect(Hussh.recorded_responses['host']['user'][command])
358
+ .to eq "#{command} output"
359
+ end
360
+
361
+ it 'flags the recording as changed' do
362
+ expect(Hussh.recording_changed?).to eql(true)
363
+ end
364
+
365
+ context 'with a command that has recorded results' do
366
+ let(:saved_responses) do
367
+ {
368
+ 'host' => { 'user' => { 'exec-no-block' => "#{command} output" } }
369
+ }.to_yaml
370
+ end
371
+
372
+ it 'gives us the stdout' do
373
+ expect(@data).to eq "#{command} output"
374
+ end
375
+ end
376
+ end
377
+ end
@@ -6,72 +6,35 @@ require 'hussh'
6
6
 
7
7
  RSpec.describe Hussh do
8
8
  include FakeFS::SpecHelpers
9
- describe Hussh::Session do
10
- describe :exec! do
11
- let(:real_session) do
12
- spy = instance_spy('Net::SSH::Connection::Session')
13
- allow(spy).to receive(:exec!) { |c| "#{c} output" }
14
- spy
15
- end
16
- before do
17
- Hussh.commands_run.clear
18
- Hussh.clear_recorded_responses
19
- Hussh.clear_stubbed_responses
20
- allow(Net::SSH).to receive(:start_without_hussh)
21
- .and_return(real_session)
22
- end
23
-
24
- context 'with a command that has not been run before' do
25
- before do
26
- Net::SSH.start('host', 'user') { |s| @output = s.exec!('hostname') }
27
- end
28
9
 
29
- it 'runs the command via ssh' do
30
- expect(real_session).to have_received(:exec!).with('hostname')
31
- end
32
-
33
- it 'records that the command was run' do
34
- expect(Hussh.commands_run).to include('hostname')
35
- end
10
+ describe Hussh::Session do
11
+ let(:real_session) do
12
+ spy = instance_spy('Net::SSH::Connection::Session')
13
+ allow(spy).to receive(:exec!) { |c| "#{c} output" }
14
+ spy
15
+ end
36
16
 
37
- it 'returns the result of the command' do
38
- expect(@output).to eql('hostname output')
39
- end
17
+ before do
18
+ Hussh.commands_run.clear
19
+ Hussh.clear_recorded_responses
20
+ Hussh.clear_stubbed_responses
21
+ allow(Net::SSH).to receive(:start_without_hussh)
22
+ .and_return(real_session)
23
+ end
40
24
 
41
- it 'saves the result of the command' do
42
- expect(Hussh.recorded_responses['host']['user']['hostname'])
43
- .to eql('hostname output')
44
- end
25
+ describe :open_channel do
26
+ let(:session) { Hussh::Session.new('host', 'user') }
45
27
 
46
- it 'flags the recording as changed' do
47
- expect(Hussh.recording_changed?).to eql(true)
48
- end
28
+ it 'returns a new channel' do
29
+ @channel = session.open_channel
30
+ expect(@channel).to be_a(Hussh::Channel)
49
31
  end
50
32
 
51
- context 'with a command that has been run before' do
52
- before do
53
- FileUtils.mkdir_p 'fixtures/hussh'
54
- File.write(
55
- 'fixtures/hussh/saved_responses.yaml',
56
- {
57
- 'host' => { 'user' => { 'hostname' => "subsix\n" } }
58
- }.to_yaml
59
- )
60
- Hussh.load_recording('saved_responses')
61
- Net::SSH.start('host', 'user') { |s| s.exec!('hostname') }
62
- end
63
-
64
- it "doesn't run the command via ssh" do
65
- expect(Net::SSH).to_not have_received(:start_without_hussh)
66
- end
67
-
68
- it 'records that the command was run' do
69
- expect(Hussh.commands_run).to include('hostname')
70
- end
71
-
72
- it "doesn't flags the recording as changed" do
73
- expect(Hussh.recording_changed?).to eql(false)
33
+ it 'runs the given block' do
34
+ session.open_channel do |ch|
35
+ @channel = ch
74
36
  end
37
+ expect(@channel).to be_a(Hussh::Channel)
75
38
  end
76
39
  end
77
40
  end
@@ -1,3 +1,6 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
1
4
  # This file was generated by the `rspec --init` command. Conventionally, all
2
5
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
6
  # The generated `.rspec` file contains `--require spec_helper` which will cause
@@ -42,7 +45,6 @@ RSpec.configure do |config|
42
45
 
43
46
  # The settings below are suggested to provide a good initial experience
44
47
  # with RSpec, but feel free to customize to your heart's content.
45
- =begin
46
48
  # These two settings work together to allow you to limit a spec run
47
49
  # to individual examples or groups you care about by tagging them with
48
50
  # `:focus` metadata. When nothing is tagged with `:focus`, all examples
@@ -50,6 +52,7 @@ RSpec.configure do |config|
50
52
  config.filter_run :focus
51
53
  config.run_all_when_everything_filtered = true
52
54
 
55
+ =begin
53
56
  # Limits the available syntax to the non-monkey patched syntax that is
54
57
  # recommended. For more details, see:
55
58
  # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hussh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Misha Gorodnitzky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-12 00:00:00.000000000 Z
11
+ date: 2015-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '2.9'
139
+ - !ruby/object:Gem::Dependency
140
+ name: codeclimate-test-reporter
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  description: Session-recording library for Net::SSH to make testing easy
140
154
  email: misaka@pobox.com
141
155
  executables: []
@@ -151,6 +165,7 @@ files:
151
165
  - lib/hussh/version.rb
152
166
  - spec/hussh_channel_spec.rb
153
167
  - spec/hussh_configuration_spec.rb
168
+ - spec/hussh_functional_spec.rb
154
169
  - spec/hussh_session_spec.rb
155
170
  - spec/spec_helper.rb
156
171
  homepage: http://github.com/moneyadviceservice/hussh
@@ -180,5 +195,6 @@ summary: Session-recording library for Net::SSH to make testing easy
180
195
  test_files:
181
196
  - spec/hussh_channel_spec.rb
182
197
  - spec/hussh_configuration_spec.rb
198
+ - spec/hussh_functional_spec.rb
183
199
  - spec/hussh_session_spec.rb
184
200
  - spec/spec_helper.rb