
These tests were blocking because of the newly introduced EventLoop. The messages sent to the EventLoop are now mocked and the EventLoop isn't blocking anymore in the tests.
300 lines
10 KiB
Ruby
300 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
describe Runner::Strategy::Poseidon do
|
|
let(:runner_id) { FactoryBot.attributes_for(:runner)[:runner_id] }
|
|
let(:execution_environment) { FactoryBot.create :ruby }
|
|
let(:poseidon) { described_class.new(runner_id, execution_environment) }
|
|
let(:error_message) { 'test error message' }
|
|
let(:response_body) { nil }
|
|
|
|
# All requests handle a BadRequest (400) response the same way.
|
|
shared_examples 'BadRequest (400) error handling' do
|
|
context 'when Poseidon returns BadRequest (400)' do
|
|
let(:response_body) { {message: error_message}.to_json }
|
|
let(:response_status) { 400 }
|
|
|
|
it 'raises an error' do
|
|
allow(Runner).to receive(:destroy).with(runner_id)
|
|
expect { action.call }.to raise_error(Runner::Error::BadRequest, /#{error_message}/)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Only #copy_files and #execute_command destroy the runner locally in case
|
|
# of a BadRequest (400) response.
|
|
shared_examples 'BadRequest (400) destroys local runner' do
|
|
context 'when Poseidon returns BadRequest (400)' do
|
|
let(:response_body) { {message: error_message}.to_json }
|
|
let(:response_status) { 400 }
|
|
|
|
it 'destroys the runner locally' do
|
|
expect(Runner).to receive(:destroy).with(runner_id)
|
|
expect { action.call }.to raise_error(Runner::Error::BadRequest)
|
|
end
|
|
end
|
|
end
|
|
|
|
# All requests handle a Unauthorized (401) response the same way.
|
|
shared_examples 'Unauthorized (401) error handling' do
|
|
context 'when Poseidon returns Unauthorized (401)' do
|
|
let(:response_status) { 401 }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(Runner::Error::Unauthorized)
|
|
end
|
|
end
|
|
end
|
|
|
|
# All requests except creation handle a NotFound (404) response the same way.
|
|
shared_examples 'NotFound (404) error handling' do
|
|
context 'when Poseidon returns NotFound (404)' do
|
|
let(:response_status) { 404 }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(Runner::Error::RunnerNotFound)
|
|
end
|
|
end
|
|
end
|
|
|
|
# All requests handle an InternalServerError (500) response the same way.
|
|
shared_examples 'InternalServerError (500) error handling' do
|
|
context 'when Poseidon returns InternalServerError (500)' do
|
|
shared_examples 'InternalServerError (500) with error code' do |error_code, error_class|
|
|
let(:response_status) { 500 }
|
|
let(:response_body) { {message: error_message, errorCode: error_code}.to_json }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(error_class) do |error|
|
|
expect(error.message).to match(/#{error_message}/)
|
|
expect(error.message).to match(/#{error_code}/)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when error code is nomad overload' do
|
|
include_examples(
|
|
'InternalServerError (500) with error code',
|
|
described_class.error_nomad_overload, Runner::Error::NotAvailable
|
|
)
|
|
end
|
|
|
|
context 'when error code is not nomad overload' do
|
|
include_examples(
|
|
'InternalServerError (500) with error code',
|
|
described_class.error_unknown, Runner::Error::InternalServerError
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
# All requests handle an unknown response status the same way.
|
|
shared_examples 'unknown response status error handling' do
|
|
context 'when Poseidon returns an unknown response status' do
|
|
let(:response_status) { 1337 }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(Runner::Error::UnexpectedResponse, /#{response_status}/)
|
|
end
|
|
end
|
|
end
|
|
|
|
# All requests handle a Faraday error the same way.
|
|
shared_examples 'Faraday error handling' do
|
|
context 'when Faraday throws an error' do
|
|
# The response status is not needed in this context but the describes block this context is embedded
|
|
# into expect this variable to be set in order to properly stub requests to the runner management.
|
|
let(:response_status) { -1 }
|
|
|
|
it 'raises an error' do
|
|
%i[post patch delete].each {|message| allow(Faraday).to receive(message).and_raise(Faraday::TimeoutError) }
|
|
expect { action.call }.to raise_error(Runner::Error::FaradayError)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '::request_from_management' do
|
|
let(:action) { -> { described_class.request_from_management(execution_environment) } }
|
|
let!(:request_runner_stub) do
|
|
WebMock
|
|
.stub_request(:post, "#{Runner::BASE_URL}/runners")
|
|
.with(
|
|
body: {executionEnvironmentId: execution_environment.id, inactivityTimeout: Runner::UNUSED_EXPIRATION_TIME},
|
|
headers: {'Content-Type' => 'application/json'}
|
|
)
|
|
.to_return(body: response_body, status: response_status)
|
|
end
|
|
|
|
context 'when Poseidon returns Ok (200) with an id' do
|
|
let(:response_body) { {runnerId: runner_id}.to_json }
|
|
let(:response_status) { 200 }
|
|
|
|
it 'successfully requests Poseidon' do
|
|
action.call
|
|
expect(request_runner_stub).to have_been_requested.once
|
|
end
|
|
|
|
it 'returns the received runner id' do
|
|
id = action.call
|
|
expect(id).to eq(runner_id)
|
|
end
|
|
end
|
|
|
|
context 'when Poseidon returns Ok (200) without an id' do
|
|
let(:response_body) { {}.to_json }
|
|
let(:response_status) { 200 }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(Runner::Error::UnexpectedResponse)
|
|
end
|
|
end
|
|
|
|
context 'when Poseidon returns Ok (200) with invalid JSON' do
|
|
let(:response_body) { '{hello}' }
|
|
let(:response_status) { 200 }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(Runner::Error::UnexpectedResponse)
|
|
end
|
|
end
|
|
|
|
include_examples 'BadRequest (400) error handling'
|
|
include_examples 'Unauthorized (401) error handling'
|
|
|
|
context 'when Poseidon returns NotFound (404)' do
|
|
let(:response_status) { 404 }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(Runner::Error::EnvironmentNotFound)
|
|
end
|
|
end
|
|
|
|
include_examples 'InternalServerError (500) error handling'
|
|
include_examples 'unknown response status error handling'
|
|
include_examples 'Faraday error handling'
|
|
end
|
|
|
|
describe '#execute_command' do
|
|
let(:command) { 'ls' }
|
|
let(:action) { -> { poseidon.send(:execute_command, command) } }
|
|
let(:websocket_url) { 'ws://ws.example.com/path/to/websocket' }
|
|
let!(:execute_command_stub) do
|
|
WebMock
|
|
.stub_request(:post, "#{Runner::BASE_URL}/runners/#{runner_id}/execute")
|
|
.with(
|
|
body: {command: command, timeLimit: execution_environment.permitted_execution_time},
|
|
headers: {'Content-Type' => 'application/json'}
|
|
)
|
|
.to_return(body: response_body, status: response_status)
|
|
end
|
|
|
|
context 'when Poseidon returns Ok (200) with a websocket url' do
|
|
let(:response_status) { 200 }
|
|
let(:response_body) { {websocketUrl: websocket_url}.to_json }
|
|
|
|
it 'schedules an execution in Poseidon' do
|
|
action.call
|
|
expect(execute_command_stub).to have_been_requested.once
|
|
end
|
|
|
|
it 'returns the url' do
|
|
url = action.call
|
|
expect(url).to eq(websocket_url)
|
|
end
|
|
end
|
|
|
|
context 'when Poseidon returns Ok (200) without a websocket url' do
|
|
let(:response_body) { {}.to_json }
|
|
let(:response_status) { 200 }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(Runner::Error::UnexpectedResponse)
|
|
end
|
|
end
|
|
|
|
context 'when Poseidon returns Ok (200) with invalid JSON' do
|
|
let(:response_body) { '{hello}' }
|
|
let(:response_status) { 200 }
|
|
|
|
it 'raises an error' do
|
|
expect { action.call }.to raise_error(Runner::Error::UnexpectedResponse)
|
|
end
|
|
end
|
|
|
|
include_examples 'BadRequest (400) error handling'
|
|
include_examples 'BadRequest (400) destroys local runner'
|
|
include_examples 'Unauthorized (401) error handling'
|
|
include_examples 'NotFound (404) error handling'
|
|
include_examples 'InternalServerError (500) error handling'
|
|
include_examples 'unknown response status error handling'
|
|
include_examples 'Faraday error handling'
|
|
end
|
|
|
|
describe '#destroy_at_management' do
|
|
let(:action) { -> { poseidon.destroy_at_management } }
|
|
let!(:destroy_stub) do
|
|
WebMock
|
|
.stub_request(:delete, "#{Runner::BASE_URL}/runners/#{runner_id}")
|
|
.to_return(body: response_body, status: response_status)
|
|
end
|
|
|
|
context 'when Poseidon returns NoContent (204)' do
|
|
let(:response_status) { 204 }
|
|
|
|
it 'deletes the runner from Poseidon' do
|
|
action.call
|
|
expect(destroy_stub).to have_been_requested.once
|
|
end
|
|
end
|
|
|
|
include_examples 'Unauthorized (401) error handling'
|
|
include_examples 'NotFound (404) error handling'
|
|
include_examples 'InternalServerError (500) error handling'
|
|
include_examples 'unknown response status error handling'
|
|
include_examples 'Faraday error handling'
|
|
end
|
|
|
|
describe '#copy_files' do
|
|
let(:file_content) { 'print("Hello World!")' }
|
|
let(:file) { FactoryBot.build(:file, content: file_content) }
|
|
let(:action) { -> { poseidon.copy_files([file]) } }
|
|
let(:encoded_file_content) { Base64.strict_encode64(file.content) }
|
|
let!(:copy_files_stub) do
|
|
WebMock
|
|
.stub_request(:patch, "#{Runner::BASE_URL}/runners/#{runner_id}/files")
|
|
.with(
|
|
body: {copy: [{path: file.filepath, content: encoded_file_content}]},
|
|
headers: {'Content-Type' => 'application/json'}
|
|
)
|
|
.to_return(body: response_body, status: response_status)
|
|
end
|
|
|
|
context 'when Poseidon returns NoContent (204)' do
|
|
let(:response_status) { 204 }
|
|
|
|
it 'sends the files to Poseidon' do
|
|
action.call
|
|
expect(copy_files_stub).to have_been_requested.once
|
|
end
|
|
end
|
|
|
|
include_examples 'BadRequest (400) error handling'
|
|
include_examples 'BadRequest (400) destroys local runner'
|
|
include_examples 'Unauthorized (401) error handling'
|
|
include_examples 'NotFound (404) error handling'
|
|
include_examples 'InternalServerError (500) error handling'
|
|
include_examples 'unknown response status error handling'
|
|
include_examples 'Faraday error handling'
|
|
end
|
|
|
|
describe '#attach_to_execution' do
|
|
# TODO: add tests here
|
|
|
|
let(:command) { 'ls' }
|
|
let(:event_loop) { Runner::EventLoop.new }
|
|
let(:action) { -> { poseidon.attach_to_execution(command, event_loop) } }
|
|
let(:websocket_url) { 'ws://ws.example.com/path/to/websocket' }
|
|
end
|
|
end
|