hussh 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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