I'm working on a web application that frequently access simulation data on a remote server. I want to create test for errors handling that might happen during these request.
The problem I currently have is I cannot seems to mock a request with my ssh_with_stderr
method. The ssh
method works fine.
This the code I'm trying to test:
# app/jobs/zip_files_sync_job.rb
class ZipFilesSyncJob < ApplicationJob
queue_as :default
discard_on ActiveJob::DeserializationError
def perform(simulation)
simulation.zip_files.each do |f|
if f.path.nil? && f.created_at < 6.hours.ago
f.state = 'error'
f.save!
next
end
next if f.path.nil?
_, errors = simulation.server.ssh_with_stderr("ls #{f.path.shellescape}")
if errors.blank?
f.size = f.simulation.server.ssh("stat -c %s #{f.path.shellescape}")
f.state = 'ready' if f.size.to_i.positive?
elsif f.state == 'ready' && errors.present?
f.state = 'error'
elsif f.state == 'zipping' && errors.present? && f.created_at < 6.hours.ago
f.state = 'error'
end
f.save!
end
end
end
And this is what I want to test:
# spec/jobs/zip_files_sync_job_spec.rb
require 'rails_helper'
RSpec.describe ZipFilesSyncJob, type: :job do
let(:private_group) { Group::PRIVATE }
let(:user) { FactoryBot.create :user }
let(:server) { FactoryBot.create :server, user: user, external_storage: false }
let(:simulation) { FactoryBot.create :simulation, user: user, group: private_group, server: server }
let(:zip_file) { FactoryBot.create :zip_file, simulation: simulation, path: 'test/zip_file', state: 'pending', size: '100' }
let(:zip_file_no_path) { FactoryBot.create :zip_file, simulation: simulation, path: nil, created_at: 10.hours.ago, state: 'pending' }
let(:ssh_connection) { double('net-ssh') }
before do
zip_file_no_path
allow(Net::SSH).to receive(:start).and_yield(ssh_connection)
end
def perform_zip_file_sync(zip_file)
perform_enqueued_jobs do
ZipFilesSyncJob.perform_now(simulation)
end
zip_file.reload
yield
allow(Net::SSH).to receive(:start).and_call_original
end
describe '#perform' do
include ActiveJob::TestHelper
#################################
##### This test works fine #####
#################################
context 'with no errors' do
before do
zip_file
end
it 'it will change the state to ready' do
allow(Net::SSH).to receive(:start).and_return('144371201')
perform_zip_file_sync(zip_file) do
expect(zip_file.state).to eq 'ready'
end
end
end
#############################################################################
##### This test fails because it does not return on the ssh_with_stderr #####
#############################################################################
context 'with errors' do
it 'will change the state to error' do
allow(Net::SSH).to receive(:start).and_return("[' ', 'Error with connection']")
perform_enqueued_jobs do
ZipFilesSyncJob.perform_now(simulation)
end
zip_file.reload
expect(zip_file.state).to eq 'error'
end
end
end
end
This the the code for the server connection. It uses the net-ssh
gem
# app/models/server.rb
Class Server < ApplicationRecord
def ssh(command, storage = true, &block)
Net::SSH.start(hostname, username, port: port, keys: ["key"], non_interactive: true, timeout: 1) do |ssh|
ssh.exec! "cd #{folder.shellescape}; #{command}", &block
end
end
def ssh_with_stderr(command)
@output = ""
@errors = ""
begin
Net::SSH.start(hostname, username, port: port, keys: ["key"], non_interactive: true, timeout: 1) do |ssh|
ssh.exec! "cd #{folder.shellescape}; #{command}" do |_ch, stream, data|
if stream == :stderr
@errors += data
else
@output += data
end
end
end
rescue Net::SSH::Exception, Errno::ECONNREFUSED, Errno::EINVAL, Errno::EADDRNOTAVAIL => e
@output = nil
@errors = e.message
end
[@output, @errors]
end
With this mock
allow(Net::SSH).to receive(:start).and_return("[' ', 'Error with connection']")
the ssh_with_stderr
looks like
def ssh_with_stderr(command)
@output = ""
@errors = ""
begin
[' ', 'Error with connection']
rescue Net::SSH::Exception, Errno::ECONNREFUSED, Errno::EINVAL, Errno::EADDRNOTAVAIL => e
@output = nil
@errors = e.message
end
[@output, @errors]
end
So it always returns ["",""]
, and checking errors.blank?
always positive.
Try to mock Net::SSH
with and_raise
instead of and_return
, something like
allow(Net::SSH).to receive(:start).and_raise(Errno::ECONNREFUSED, "Error with connection")