Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Http interface tests #1513

Merged
merged 13 commits into from
Sep 17, 2024
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

# TODO: is packet.extra ever set?
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"
243 changes: 240 additions & 3 deletions openc3/spec/interfaces/http_client_interface_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,70 @@
# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.

=begin

This RSpec test program covers all the methods in the HttpClientInterface
class and aims to maximize coverage. It includes tests for:

1. Initialization with default and custom parameters
2. Connection string generation
3. Connecting and disconnecting
4. Checking connection status
5. Reading from and writing to the interface
6. Converting data to packets and packets to data
7. Handling different HTTP methods (GET, POST, PUT, DELETE)
8. Error handling and special case responses

=end

require 'spec_helper'
require 'openc3/interfaces/http_client_interface'

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 +92,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
Loading