Skip to content

Commit

Permalink
Merge pull request #1513 from OpenC3/http_interface_tests
Browse files Browse the repository at this point in the history
Http interface tests
  • Loading branch information
jmthomas authored Sep 17, 2024
2 parents 6e3bd76 + c2d9104 commit a467d70
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 26 deletions.
1 change: 1 addition & 0 deletions openc3/lib/openc3/interfaces/http_server_interface.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def connect
# No HTTP_STATUS - Leave at default
end

# http_accessor stores all the pseudo-derived HTTP configuration in extra
if packet.extra
headers = packet.extra['HTTP_HEADERS']
if headers
Expand Down
5 changes: 5 additions & 0 deletions openc3/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,8 @@ COMMAND INST DISABLED BIG_ENDIAN "Disabled command"
PARAMETER CCSDSSEQCNT 18 14 UINT 0 16383 0 "CCSDS primary header sequence count"
PARAMETER CCSDSLENGTH 32 16 UINT 0 65535 0 "CCSDS primary header packet length"
ID_PARAMETER PKTID 48 16 UINT 0 65535 11 "Packet id"

COMMAND INST HTTP_SERVER BIG_ENDIAN "OPTIONAL"
APPEND_PARAMETER HTTP_STATUS 256 STRING "ok"
APPEND_PARAMETER HTTP_PATH 256 STRING "/test"
APPEND_PARAMETER HTTP_PACKET 256 STRING "packet_name"
227 changes: 224 additions & 3 deletions openc3/spec/interfaces/http_client_interface_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,49 @@

module OpenC3
describe HttpClientInterface do
describe "initialize" do
before(:all) do
@api_resource = '/api/resource'
@application_json = 'application/json'
@content_type = 'Content-Type'
@example_com = 'example.com'
@packet_data = 'packet data'
end

before(:each) do
@interface = HttpClientInterface.new(@example_com, 8080, 'https', 10, 15, 5, true)
end

describe "#initialize" do
it "sets all the instance variables" do
i = HttpClientInterface.new('localhost', '8080', 'https', '10', '11', '12')
expect(i.name).to eql "HttpClientInterface"
expect(i.instance_variable_get(:@hostname)).to eql 'localhost'
expect(i.instance_variable_get(:@port)).to eql 8080
end

it "initializes with default parameters" do
interface = HttpClientInterface.new(@example_com)
expect(interface.instance_variable_get(:@hostname)).to eq(@example_com)
expect(interface.instance_variable_get(:@port)).to eq(80)
expect(interface.instance_variable_get(:@protocol)).to eq('http')
end

it "initializes with custom parameters" do
expect(@interface.instance_variable_get(:@hostname)).to eq(@example_com)
expect(@interface.instance_variable_get(:@port)).to eq(8080)
expect(@interface.instance_variable_get(:@protocol)).to eq('https')
expect(@interface.instance_variable_get(:@write_timeout)).to eq(10.0)
expect(@interface.instance_variable_get(:@read_timeout)).to eq(15.0)
expect(@interface.instance_variable_get(:@connect_timeout)).to eq(5.0)
expect(@interface.instance_variable_get(:@include_request_in_response)).to be true
end
end

describe "connection_string" do
describe "#connection_string" do
it "returns the correct URL" do
expect(@interface.connection_string).to eq('https://example.com:8080')
end

it "builds a human readable connection string" do
i = HttpClientInterface.new('localhost', '80', 'http', '10', '11', '12')
expect(i.connection_string).to eql "http://localhost"
Expand All @@ -43,6 +76,194 @@ module OpenC3
end
end

# TODO: This needs more testing
describe "#connect" do
it "creates a Faraday connection" do
allow(Faraday).to receive(:new).and_return(double('faraday'))
@interface.connect
expect(@interface.instance_variable_get(:@http)).to_not be_nil
end
end

describe "#connected?" do
it "returns true when connected" do
@interface.instance_variable_set(:@http, double('faraday'))
expect(@interface.connected?).to be true
end

it "returns false when not connected" do
expect(@interface.connected?).to be false
end
end

describe "#disconnect" do
it "closes the connection and clears the queue" do
mock_http = double('faraday')
expect(mock_http).to receive(:close)
@interface.instance_variable_set(:@http, mock_http)
@interface.instance_variable_get(:@response_queue).push(['data', {}])
@interface.disconnect
expect(@interface.instance_variable_get(:@http)).to be_nil
expect(@interface.instance_variable_get(:@response_queue).empty?).to be false
expect(@interface.instance_variable_get(:@response_queue).pop).to be_nil
end
end

describe "#read_interface" do
it "returns data from the queue" do
@interface.instance_variable_get(:@response_queue).push(['test_data', { 'extra' => 'info' }])
data, extra = @interface.read_interface
expect(data).to eq('test_data')
expect(extra).to eq({ 'extra' => 'info' })
end
end

describe "#write_interface" do
before(:each) do
@mock_http = double('faraday')
@interface.instance_variable_set(:@http, @mock_http)
end

it "handles DELETE requests" do
data = ""
extra = {
'HTTP_METHOD' => 'delete',
'HTTP_URI' => '/api/resource/1',
'HTTP_QUERIES' => { 'confirm' => 'true' },
'HTTP_HEADERS' => { 'Authorization' => 'Bearer token' }
}

mock_response = double('response')
allow(mock_response).to receive(:headers).and_return({})
allow(mock_response).to receive(:status).and_return(204)
allow(mock_response).to receive(:body).and_return('')

expect(@interface.instance_variable_get(:@http)).to receive(:delete)
.with("#{@api_resource}/1", { 'confirm' => 'true' }, { 'Authorization' => 'Bearer token' })
.and_return(mock_response)

@interface.write_interface(data, extra)
expect(@interface.instance_variable_get(:@response_queue).pop).to eq(['', {
'HTTP_REQUEST' => [data, extra],
'HTTP_STATUS' => 204
}])
end

it "handles GET requests" do
expect(@mock_http).to receive(:get).and_return(double('response', headers: {}, status: 200, body: 'response'))
data, extra = @interface.write_interface('', { 'HTTP_METHOD' => 'get', 'HTTP_URI' => '/test' })
expect(@interface.instance_variable_get(:@response_queue).pop).to eq(['response', { 'HTTP_REQUEST' => ['', { 'HTTP_METHOD' => 'get', 'HTTP_URI' => '/test' }], 'HTTP_STATUS' => 200 }])
end

it "handles POST requests" do
data = '{"post": "data"}'
extra = {
'HTTP_METHOD' => 'post',
'HTTP_URI' => @api_resource,
'HTTP_QUERIES' => { 'param' => 'value' },
'HTTP_HEADERS' => { @content_type => 'application/json' }
}

mock_response = double('response')
allow(mock_response).to receive(:headers).and_return({@content_type => @application_json})
allow(mock_response).to receive(:status).and_return(201)
allow(mock_response).to receive(:body).and_return('{"id": 1}')

expect(@interface.instance_variable_get(:@http)).to receive(:post)
.and_yield(double('request').as_null_object)
.and_return(mock_response)

@interface.write_interface(data, extra)
expect(@interface.instance_variable_get(:@response_queue).pop).to eq(['{"id": 1}', {
'HTTP_REQUEST' => [data, extra],
'HTTP_HEADERS' => {@content_type => @application_json},
'HTTP_STATUS' => 201
}])
end

it "handles PUT requests" do
data = '{"put": "data"}'
extra = {
'HTTP_METHOD' => 'put',
'HTTP_URI' => "#{@api_resource}/1",
'HTTP_QUERIES' => { 'param' => 'value' },
'HTTP_HEADERS' => { @content_type => @application_json }
}

mock_response = double('response')
allow(mock_response).to receive(:headers).and_return({@content_type => @application_json})
allow(mock_response).to receive(:status).and_return(200)
allow(mock_response).to receive(:body).and_return('{"updated": true}')

expect(@interface.instance_variable_get(:@http)).to receive(:put)
.and_yield(double('request').as_null_object)
.and_return(mock_response)

@interface.write_interface(data, extra)
expect(@interface.instance_variable_get(:@response_queue).pop).to eq(['{"updated": true}', {
'HTTP_REQUEST' => [data, extra],
'HTTP_HEADERS' => {@content_type => @application_json},
'HTTP_STATUS' => 200
}])
end
end

describe "#convert_data_to_packet" do
it "creates a packet with HttpAccessor" do
packet = @interface.convert_data_to_packet('test_data', { 'HTTP_STATUS' => 200 })
expect(packet).to be_a(Packet)
expect(packet.accessor).to be_a(HttpAccessor)
expect(packet.buffer).to eq('test_data')
end

it "sets target and packet names for successful responses" do
packet = @interface.convert_data_to_packet('success', { 'HTTP_STATUS' => 200, 'HTTP_REQUEST_TARGET_NAME' => 'TARGET', 'HTTP_PACKET' => 'SUCCESS' })
expect(packet.target_name).to eq('TARGET')
expect(packet.packet_name).to eq('SUCCESS')
end

it "sets error packet name for error responses" do
packet = @interface.convert_data_to_packet('error', { 'HTTP_STATUS' => 404, 'HTTP_REQUEST_TARGET_NAME' => 'TARGET', 'HTTP_ERROR_PACKET' => 'ERROR' })
expect(packet.target_name).to eq('TARGET')
expect(packet.packet_name).to eq('ERROR')
end
end

describe "#convert_packet_to_data" do
it "converts a packet to data and extra hash" do
packet = double('packet')
allow(packet).to receive(:buffer).and_return( @packet_data )
allow(packet).to receive(:read).with('HTTP_PATH').and_return(@api_resource)
allow(packet).to receive(:target_name).and_return('TARGET')
allow(packet).to receive(:packet_name).and_return('PACKET')
allow(packet).to receive(:extra).and_return(nil)

data, extra = @interface.convert_packet_to_data(packet)

expect(data).to eq( @packet_data )
expect(extra['HTTP_REQUEST_TARGET_NAME']).to eq('TARGET')
expect(extra['HTTP_REQUEST_PACKET_NAME']).to eq('PACKET')
uri_str = extra['HTTP_URI'].encode('ASCII-8BIT', 'UTF-8')
expect(uri_str).to eq("https://example.com:8080#{@api_resource}")
end

it "preserves existing extra data" do
packet = double('packet')
allow(packet).to receive(:buffer).and_return( @packet_data )
allow(packet).to receive(:read).with('HTTP_PATH').and_return(@api_resource)
allow(packet).to receive(:target_name).and_return('TARGET')
allow(packet).to receive(:packet_name).and_return('PACKET')
allow(packet).to receive(:extra).and_return({'EXISTING' => 'DATA'})

data, extra = @interface.convert_packet_to_data(packet)

expect(data).to eq( @packet_data )
expect(extra['EXISTING']).to eq('DATA')
uri_str = extra['HTTP_URI'].encode('ASCII-8BIT', 'UTF-8')

expect(uri_str).to eq("https://example.com:8080#{@api_resource}")
expect(extra['HTTP_REQUEST_TARGET_NAME']).to eq('TARGET')
expect(extra['HTTP_REQUEST_PACKET_NAME']).to eq('PACKET')
end
end
end
end
Loading

0 comments on commit a467d70

Please sign in to comment.