From 6fc0e451da500fe1fcf7dc740a3f41cb0a347566 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Wed, 4 Sep 2024 14:36:38 -0700 Subject: [PATCH 01/11] generated specs --- .../http_client_interface_hal_spec.rb | 150 ++++++++++++++++++ .../http_server_interface_hal_spec.rb | 132 +++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 openc3/spec/interfaces/http_client_interface_hal_spec.rb create mode 100644 openc3/spec/interfaces/http_server_interface_hal_spec.rb diff --git a/openc3/spec/interfaces/http_client_interface_hal_spec.rb b/openc3/spec/interfaces/http_client_interface_hal_spec.rb new file mode 100644 index 0000000000..01a80f18c2 --- /dev/null +++ b/openc3/spec/interfaces/http_client_interface_hal_spec.rb @@ -0,0 +1,150 @@ +require 'spec_helper' +require 'openc3/interfaces/http_client_interface' + +module OpenC3 + describe HttpClientInterface do + before(:each) do + @interface = HttpClientInterface.new('example.com', 8080, 'https', 10, 15, 5, true) + end + + describe "#initialize" do + 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 + it "returns the correct URL" do + expect(@interface.connection_string).to eq('https://example.com:8080') + end + end + + 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 + + it "returns nil when queue is empty" do + expect(@interface.read_interface).to be_nil + end + end + + describe "#write_interface" do + before(:each) do + @mock_http = double('faraday') + @interface.instance_variable_set(:@http, @mock_http) + 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 + expect(@mock_http).to receive(:post).and_return(double('response', headers: {}, status: 201, body: 'created')) + data, extra = @interface.write_interface('post_data', { 'HTTP_METHOD' => 'post', 'HTTP_URI' => '/create' }) + expect(@interface.instance_variable_get(:@response_queue).pop).to eq(['created', { 'HTTP_REQUEST' => ['post_data', { 'HTTP_METHOD' => 'post', 'HTTP_URI' => '/create' }], 'HTTP_STATUS' => 201 }]) + 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 "extracts data and extra information from the packet" do + packet = Packet.new('TARGET', 'COMMAND') + packet.append_item('HTTP_PATH', 8, :STRING) + packet.write('HTTP_PATH', '/api/v1/command') + packet.buffer = 'command_data' + + data, extra = @interface.convert_packet_to_data(packet) + expect(data).to eq('command_data') + expect(extra['HTTP_URI']).to eq('https://example.com:8080/api/v1/command') + expect(extra['HTTP_REQUEST_TARGET_NAME']).to eq('TARGET') + expect(extra['HTTP_REQUEST_PACKET_NAME']).to eq('COMMAND') + end + end + end +end +``` + +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) +8. Error handling and special case responses + +To run these tests, make sure you have the necessary dependencies installed and the HttpClientInterface class is properly required. You may need to adjust the `require` statements at the top of the test file to match your project structure. + diff --git a/openc3/spec/interfaces/http_server_interface_hal_spec.rb b/openc3/spec/interfaces/http_server_interface_hal_spec.rb new file mode 100644 index 0000000000..7ec613e58b --- /dev/null +++ b/openc3/spec/interfaces/http_server_interface_hal_spec.rb @@ -0,0 +1,132 @@ +require 'spec_helper' +require 'openc3/interfaces/http_server_interface' +require 'openc3/packets/packet' +require 'openc3/system/system' + +module OpenC3 + describe HttpServerInterface do + before(:each) do + @interface = HttpServerInterface.new(8080) + allow(System).to receive(:commands).and_return(double("commands")) + allow(System.commands).to receive(:packets).and_return({}) + allow(Logger).to receive(:error) + end + + describe "#initialize" do + it "initializes with default values" do + expect(@interface.instance_variable_get(:@listen_address)).to eq('0.0.0.0') + expect(@interface.instance_variable_get(:@port)).to eq(8080) + end + end + + describe "#set_option" do + it "sets the listen address" do + @interface.set_option("LISTEN_ADDRESS", ["127.0.0.1"]) + expect(@interface.instance_variable_get(:@listen_address)).to eq('127.0.0.1') + end + end + + describe "#connection_string" do + it "returns the correct connection string" do + expect(@interface.connection_string).to eq("listening on 0.0.0.0:8080") + end + end + + describe "#connect" do + it "creates a WEBrick server and mounts routes" do + allow(WEBrick::HTTPServer).to receive(:new).and_return(double("server").as_null_object) + allow(@interface).to receive(:super) + allow(Thread).to receive(:new) + + target_name = "TARGET" + packet_name = "PACKET" + packet = double("packet") + allow(packet).to receive(:restore_defaults) + allow(packet).to receive(:read).with('HTTP_PATH').and_return("/test") + allow(packet).to receive(:read).with('HTTP_STATUS').and_return(200) + allow(packet).to receive(:extra).and_return({'HTTP_HEADERS' => {'Content-Type' => 'application/json'}}) + allow(packet).to receive(:buffer).and_return("test body") + allow(packet).to receive(:read).with('HTTP_PACKET').and_return("TEST_PACKET") + + allow(System.commands).to receive(:packets).and_return({target_name => {packet_name => packet}}) + allow(@interface).to receive(:target_names).and_return([target_name]) + + expect_any_instance_of(WEBrick::HTTPServer).to receive(:mount_proc).with("/test") + + @interface.connect + end + end + + describe "#connected?" do + it "returns true when server is present" do + @interface.instance_variable_set(:@server, double("server")) + expect(@interface.connected?).to be true + end + + it "returns false when server is not present" do + expect(@interface.connected?).to be false + end + end + + describe "#disconnect" do + it "shuts down the server and clears the request queue" do + server = double("server") + expect(server).to receive(:shutdown) + @interface.instance_variable_set(:@server, server) + @interface.instance_variable_set(:@request_queue, Queue.new) + @interface.request_queue.push("test") + allow(@interface).to receive(:super) + + @interface.disconnect + + expect(@interface.instance_variable_get(:@server)).to be_nil + expect(@interface.request_queue.size).to eq(1) + expect(@interface.request_queue.pop).to be_nil + end + end + + describe "#read_interface" do + it "returns data and extra from the request queue" do + @interface.request_queue.push(["test_data", {"extra" => "info"}]) + allow(@interface).to receive(:read_interface_base) + + data, extra = @interface.read_interface + + expect(data).to eq("test_data") + expect(extra).to eq({"extra" => "info"}) + end + end + + describe "#write_interface" do + it "raises an error" do + expect { @interface.write_interface({}) }.to raise_error(RuntimeError, "Commands cannot be sent to HttpServerInterface") + end + end + + describe "#convert_data_to_packet" do + it "creates a packet with HttpAccessor" do + data = "test_data" + extra = { + "HTTP_REQUEST_TARGET_NAME" => "TARGET", + "HTTP_REQUEST_PACKET_NAME" => "PACKET", + "EXTRA_INFO" => "value" + } + + packet = @interface.convert_data_to_packet(data, extra) + + expect(packet).to be_a(Packet) + expect(packet.target_name).to eq("TARGET") + expect(packet.packet_name).to eq("PACKET") + expect(packet.accessor).to be_a(HttpAccessor) + expect(packet.extra).to eq({"EXTRA_INFO" => "value"}) + end + end + + describe "#convert_packet_to_data" do + it "raises an error" do + expect { @interface.convert_packet_to_data(double("packet")) }.to raise_error(RuntimeError, "Commands cannot be sent to HttpServerInterface") + end + end + end +end + From 07eb7badd3e5b31bc37a2e5aa25247ac8c8ca2aa Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Wed, 4 Sep 2024 15:54:21 -0600 Subject: [PATCH 02/11] draft specs from generator --- .../http_client_interface_hal_spec.rb | 35 ++++++++++--------- .../http_server_interface_hal_spec.rb | 6 ++-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/openc3/spec/interfaces/http_client_interface_hal_spec.rb b/openc3/spec/interfaces/http_client_interface_hal_spec.rb index 01a80f18c2..89338c1bae 100644 --- a/openc3/spec/interfaces/http_client_interface_hal_spec.rb +++ b/openc3/spec/interfaces/http_client_interface_hal_spec.rb @@ -1,3 +1,19 @@ + +=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) +8. Error handling and special case responses + +=end + require 'spec_helper' require 'openc3/interfaces/http_client_interface' @@ -72,7 +88,7 @@ module OpenC3 expect(extra).to eq({ 'extra' => 'info' }) end - it "returns nil when queue is empty" do + xit "returns nil when queue is empty" do expect(@interface.read_interface).to be_nil end end @@ -118,7 +134,7 @@ module OpenC3 end describe "#convert_packet_to_data" do - it "extracts data and extra information from the packet" do + xit "extracts data and extra information from the packet" do packet = Packet.new('TARGET', 'COMMAND') packet.append_item('HTTP_PATH', 8, :STRING) packet.write('HTTP_PATH', '/api/v1/command') @@ -133,18 +149,3 @@ module OpenC3 end end end -``` - -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) -8. Error handling and special case responses - -To run these tests, make sure you have the necessary dependencies installed and the HttpClientInterface class is properly required. You may need to adjust the `require` statements at the top of the test file to match your project structure. - diff --git a/openc3/spec/interfaces/http_server_interface_hal_spec.rb b/openc3/spec/interfaces/http_server_interface_hal_spec.rb index 7ec613e58b..c729f7bd8c 100644 --- a/openc3/spec/interfaces/http_server_interface_hal_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_hal_spec.rb @@ -33,7 +33,7 @@ module OpenC3 end describe "#connect" do - it "creates a WEBrick server and mounts routes" do + xit "creates a WEBrick server and mounts routes" do allow(WEBrick::HTTPServer).to receive(:new).and_return(double("server").as_null_object) allow(@interface).to receive(:super) allow(Thread).to receive(:new) @@ -69,7 +69,7 @@ module OpenC3 end describe "#disconnect" do - it "shuts down the server and clears the request queue" do + xit "shuts down the server and clears the request queue" do server = double("server") expect(server).to receive(:shutdown) @interface.instance_variable_set(:@server, server) @@ -86,7 +86,7 @@ module OpenC3 end describe "#read_interface" do - it "returns data and extra from the request queue" do + xit "returns data and extra from the request queue" do @interface.request_queue.push(["test_data", {"extra" => "info"}]) allow(@interface).to receive(:read_interface_base) From ab164c41ffea363da79f08eb20b050ce729be70c Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Tue, 10 Sep 2024 20:20:20 -0600 Subject: [PATCH 03/11] clean tests, with more work needed on the server connect() method --- .../http_client_interface_hal_spec.rb | 151 ----------- .../interfaces/http_client_interface_spec.rb | 236 +++++++++++++++++- .../http_server_interface_hal_spec.rb | 132 ---------- .../interfaces/http_server_interface_spec.rb | 139 +++++++++-- 4 files changed, 352 insertions(+), 306 deletions(-) delete mode 100644 openc3/spec/interfaces/http_client_interface_hal_spec.rb delete mode 100644 openc3/spec/interfaces/http_server_interface_hal_spec.rb diff --git a/openc3/spec/interfaces/http_client_interface_hal_spec.rb b/openc3/spec/interfaces/http_client_interface_hal_spec.rb deleted file mode 100644 index 89338c1bae..0000000000 --- a/openc3/spec/interfaces/http_client_interface_hal_spec.rb +++ /dev/null @@ -1,151 +0,0 @@ - -=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) -8. Error handling and special case responses - -=end - -require 'spec_helper' -require 'openc3/interfaces/http_client_interface' - -module OpenC3 - describe HttpClientInterface do - before(:each) do - @interface = HttpClientInterface.new('example.com', 8080, 'https', 10, 15, 5, true) - end - - describe "#initialize" do - 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 - it "returns the correct URL" do - expect(@interface.connection_string).to eq('https://example.com:8080') - end - end - - 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 - - xit "returns nil when queue is empty" do - expect(@interface.read_interface).to be_nil - end - end - - describe "#write_interface" do - before(:each) do - @mock_http = double('faraday') - @interface.instance_variable_set(:@http, @mock_http) - 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 - expect(@mock_http).to receive(:post).and_return(double('response', headers: {}, status: 201, body: 'created')) - data, extra = @interface.write_interface('post_data', { 'HTTP_METHOD' => 'post', 'HTTP_URI' => '/create' }) - expect(@interface.instance_variable_get(:@response_queue).pop).to eq(['created', { 'HTTP_REQUEST' => ['post_data', { 'HTTP_METHOD' => 'post', 'HTTP_URI' => '/create' }], 'HTTP_STATUS' => 201 }]) - 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 - xit "extracts data and extra information from the packet" do - packet = Packet.new('TARGET', 'COMMAND') - packet.append_item('HTTP_PATH', 8, :STRING) - packet.write('HTTP_PATH', '/api/v1/command') - packet.buffer = 'command_data' - - data, extra = @interface.convert_packet_to_data(packet) - expect(data).to eq('command_data') - expect(extra['HTTP_URI']).to eq('https://example.com:8080/api/v1/command') - expect(extra['HTTP_REQUEST_TARGET_NAME']).to eq('TARGET') - expect(extra['HTTP_REQUEST_PACKET_NAME']).to eq('COMMAND') - end - end - end -end diff --git a/openc3/spec/interfaces/http_client_interface_spec.rb b/openc3/spec/interfaces/http_client_interface_spec.rb index 20f515430c..7d811f591f 100644 --- a/openc3/spec/interfaces/http_client_interface_spec.rb +++ b/openc3/spec/interfaces/http_client_interface_spec.rb @@ -16,11 +16,27 @@ # 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 +=begin describe "initialize" do it "sets all the instance variables" do i = HttpClientInterface.new('localhost', '8080', 'https', '10', '11', '12') @@ -42,7 +58,225 @@ module OpenC3 expect(i.connection_string).to eql "http://127.0.0.1:8080" end end +=end + + before(:each) do + @interface = HttpClientInterface.new('example.com', 8080, 'https', 10, 15, 5, true) + end + + describe "#initialize" do + 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 + it "returns the correct URL" do + expect(@interface.connection_string).to eq('https://example.com:8080') + end + end + + 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 - # TODO: This needs more testing + 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 diff --git a/openc3/spec/interfaces/http_server_interface_hal_spec.rb b/openc3/spec/interfaces/http_server_interface_hal_spec.rb deleted file mode 100644 index c729f7bd8c..0000000000 --- a/openc3/spec/interfaces/http_server_interface_hal_spec.rb +++ /dev/null @@ -1,132 +0,0 @@ -require 'spec_helper' -require 'openc3/interfaces/http_server_interface' -require 'openc3/packets/packet' -require 'openc3/system/system' - -module OpenC3 - describe HttpServerInterface do - before(:each) do - @interface = HttpServerInterface.new(8080) - allow(System).to receive(:commands).and_return(double("commands")) - allow(System.commands).to receive(:packets).and_return({}) - allow(Logger).to receive(:error) - end - - describe "#initialize" do - it "initializes with default values" do - expect(@interface.instance_variable_get(:@listen_address)).to eq('0.0.0.0') - expect(@interface.instance_variable_get(:@port)).to eq(8080) - end - end - - describe "#set_option" do - it "sets the listen address" do - @interface.set_option("LISTEN_ADDRESS", ["127.0.0.1"]) - expect(@interface.instance_variable_get(:@listen_address)).to eq('127.0.0.1') - end - end - - describe "#connection_string" do - it "returns the correct connection string" do - expect(@interface.connection_string).to eq("listening on 0.0.0.0:8080") - end - end - - describe "#connect" do - xit "creates a WEBrick server and mounts routes" do - allow(WEBrick::HTTPServer).to receive(:new).and_return(double("server").as_null_object) - allow(@interface).to receive(:super) - allow(Thread).to receive(:new) - - target_name = "TARGET" - packet_name = "PACKET" - packet = double("packet") - allow(packet).to receive(:restore_defaults) - allow(packet).to receive(:read).with('HTTP_PATH').and_return("/test") - allow(packet).to receive(:read).with('HTTP_STATUS').and_return(200) - allow(packet).to receive(:extra).and_return({'HTTP_HEADERS' => {'Content-Type' => 'application/json'}}) - allow(packet).to receive(:buffer).and_return("test body") - allow(packet).to receive(:read).with('HTTP_PACKET').and_return("TEST_PACKET") - - allow(System.commands).to receive(:packets).and_return({target_name => {packet_name => packet}}) - allow(@interface).to receive(:target_names).and_return([target_name]) - - expect_any_instance_of(WEBrick::HTTPServer).to receive(:mount_proc).with("/test") - - @interface.connect - end - end - - describe "#connected?" do - it "returns true when server is present" do - @interface.instance_variable_set(:@server, double("server")) - expect(@interface.connected?).to be true - end - - it "returns false when server is not present" do - expect(@interface.connected?).to be false - end - end - - describe "#disconnect" do - xit "shuts down the server and clears the request queue" do - server = double("server") - expect(server).to receive(:shutdown) - @interface.instance_variable_set(:@server, server) - @interface.instance_variable_set(:@request_queue, Queue.new) - @interface.request_queue.push("test") - allow(@interface).to receive(:super) - - @interface.disconnect - - expect(@interface.instance_variable_get(:@server)).to be_nil - expect(@interface.request_queue.size).to eq(1) - expect(@interface.request_queue.pop).to be_nil - end - end - - describe "#read_interface" do - xit "returns data and extra from the request queue" do - @interface.request_queue.push(["test_data", {"extra" => "info"}]) - allow(@interface).to receive(:read_interface_base) - - data, extra = @interface.read_interface - - expect(data).to eq("test_data") - expect(extra).to eq({"extra" => "info"}) - end - end - - describe "#write_interface" do - it "raises an error" do - expect { @interface.write_interface({}) }.to raise_error(RuntimeError, "Commands cannot be sent to HttpServerInterface") - end - end - - describe "#convert_data_to_packet" do - it "creates a packet with HttpAccessor" do - data = "test_data" - extra = { - "HTTP_REQUEST_TARGET_NAME" => "TARGET", - "HTTP_REQUEST_PACKET_NAME" => "PACKET", - "EXTRA_INFO" => "value" - } - - packet = @interface.convert_data_to_packet(data, extra) - - expect(packet).to be_a(Packet) - expect(packet.target_name).to eq("TARGET") - expect(packet.packet_name).to eq("PACKET") - expect(packet.accessor).to be_a(HttpAccessor) - expect(packet.extra).to eq({"EXTRA_INFO" => "value"}) - end - end - - describe "#convert_packet_to_data" do - it "raises an error" do - expect { @interface.convert_packet_to_data(double("packet")) }.to raise_error(RuntimeError, "Commands cannot be sent to HttpServerInterface") - end - end - end -end - diff --git a/openc3/spec/interfaces/http_server_interface_spec.rb b/openc3/spec/interfaces/http_server_interface_spec.rb index d77cd7d5a8..3ebc5091b6 100644 --- a/openc3/spec/interfaces/http_server_interface_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_spec.rb @@ -16,44 +16,139 @@ # This file may also be used under the terms of a commercial license # if purchased from OpenC3, Inc. +=begin +Here's a complete runnable RSpec test program for the HttpServerInterface class: + +This RSpec test program covers all the methods in the HttpServerInterface class and aims to maximize coverage. It includes tests for: + +1. Initialization with default and custom ports +2. Setting options +3. Connection string generation +4. Connecting and creating the WEBrick server +5. Checking connection status +6. Disconnecting and cleaning up +7. Reading from the interface +8. Writing to the interface (which raises an error) +9. Converting data to packets +10. Converting packets to data (which raises an error) + +Note that some methods like `connect` are more challenging to test thoroughly due to their complexity and dependencies. In a real-world scenario, you might want to consider using more advanced mocking techniques or integration tests to cover these areas more comprehensively. +=end + require 'spec_helper' require 'openc3/interfaces/http_server_interface' +require 'openc3/packets/packet' +require 'openc3/system/system' module OpenC3 describe HttpServerInterface do - describe "initialize" do - it "uses a default port of 80" do - i = HttpServerInterface.new() - expect(i.name).to eql "HttpServerInterface" - expect(i.instance_variable_get(:@port)).to eql 80 + before(:each) do + @interface = HttpServerInterface.new + allow(System).to receive_message_chain(:commands, :packets).and_return({ + 'TARGET' => { + 'PACKET' => Packet.new('TARGET', 'PACKET') + } + }) + end + + describe "#initialize" do + it "initializes with default port" do + expect(@interface.instance_variable_get(:@port)).to eq(80) + end + + it "initializes with custom port" do + interface = HttpServerInterface.new(8080) + expect(interface.instance_variable_get(:@port)).to eq(8080) end + end - it "sets the listen port" do - i = HttpServerInterface.new('8080') - expect(i.name).to eql "HttpServerInterface" - expect(i.instance_variable_get(:@port)).to eql 8080 + describe "#set_option" do + it "sets the listen address" do + @interface.set_option("LISTEN_ADDRESS", ["127.0.0.1"]) + expect(@interface.instance_variable_get(:@listen_address)).to eq("127.0.0.1") end end - describe "connection_string" do - it "builds a human readable connection string" do - i = HttpServerInterface.new() - expect(i.connection_string).to eql "listening on 0.0.0.0:80" + describe "#connection_string" do + it "returns the correct connection string" do + expect(@interface.connection_string).to eq("listening on 0.0.0.0:80") + end + end - i = HttpServerInterface.new('8080') - expect(i.connection_string).to eql "listening on 0.0.0.0:8080" + describe "#connect" do + it "creates a WEBrick server and mounts routes" do + allow(WEBrick::HTTPServer).to receive(:new).and_return(double('server').as_null_object) + allow_any_instance_of(Packet).to receive(:read).with('HTTP_PATH').and_return('/test') + #expect(@interface).to receive(:super) + @interface.connect + expect(@interface.instance_variable_get(:@server)).to_not be_nil + kill_leftover_threads() end end - describe "set_option" do - it "sets the listen address for the tcpip_server" do - i = HttpServerInterface.new('8888') - i.set_option('LISTEN_ADDRESS', ['127.0.0.1']) - expect(i.instance_variable_get(:@listen_address)).to eq '127.0.0.1' - expect(i.connection_string).to eql "listening on 127.0.0.1:8888" + describe "#connected?" do + it "returns true when server is present" do + @interface.instance_variable_set(:@server, double('server')) + expect(@interface.connected?).to be true + end + + it "returns false when server is not present" do + expect(@interface.connected?).to be false end end - # TODO: This needs more testing + describe "#disconnect" do + it "shuts down the server and clears the queue" do + server = double('server') + expect(server).to receive(:shutdown) + @interface.instance_variable_set(:@server, server) + @interface.instance_variable_set(:@request_queue, Queue.new) + @interface.instance_variable_get(:@request_queue).push("test") + #expect(@interface).to receive(:super) + @interface.disconnect + expect(@interface.instance_variable_get(:@server)).to be_nil + expect(@interface.instance_variable_get(:@request_queue).size).to eq(1) + expect(@interface.instance_variable_get(:@request_queue).pop).to be_nil + end + end + + describe "#read_interface" do + it "reads from the request queue" do + @interface.instance_variable_set(:@request_queue, Queue.new) + @interface.instance_variable_get(:@request_queue).push(["data", {}]) + expect(@interface).to receive(:read_interface_base).with("data", {}) + data, extra = @interface.read_interface + expect(data).to eq("data") + expect(extra).to eq({}) + end + end + + describe "#write_interface" do + it "raises an error" do + expect { @interface.write_interface({}) }.to raise_error(RuntimeError, "Commands cannot be sent to HttpServerInterface") + end + end + + describe "#convert_data_to_packet" do + it "converts data to a packet" do + data = "test data" + extra = { + 'HTTP_REQUEST_TARGET_NAME' => 'TARGET', + 'HTTP_REQUEST_PACKET_NAME' => 'PACKET', + 'EXTRA_INFO' => 'value' + } + packet = @interface.convert_data_to_packet(data, extra) + expect(packet.target_name).to eq('TARGET') + expect(packet.packet_name).to eq('PACKET') + expect(packet.buffer).to eq(data) + expect(packet.extra).to eq({'EXTRA_INFO' => 'value'}) + end + end + + describe "#convert_packet_to_data" do + it "raises an error" do + expect { @interface.convert_packet_to_data(Packet.new('TGT', 'PKT')) }.to raise_error(RuntimeError, "Commands cannot be sent to HttpServerInterface") + end + end end end From 86c49ede2de2eb746a969a2b95e634e16a351834 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Thu, 12 Sep 2024 16:31:57 -0600 Subject: [PATCH 04/11] both client and server http interfaces over 95% coverage --- .../interfaces/http_server_interface.rb | 1 + .../config/targets/INST/cmd_tlm/inst_cmds.txt | 5 +++ .../interfaces/http_client_interface_spec.rb | 27 ++------------- .../interfaces/http_server_interface_spec.rb | 34 +++++++++++++------ 4 files changed, 32 insertions(+), 35 deletions(-) diff --git a/openc3/lib/openc3/interfaces/http_server_interface.rb b/openc3/lib/openc3/interfaces/http_server_interface.rb index 450a537147..6ee255cecd 100644 --- a/openc3/lib/openc3/interfaces/http_server_interface.rb +++ b/openc3/lib/openc3/interfaces/http_server_interface.rb @@ -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 diff --git a/openc3/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt b/openc3/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt index 3306498d5b..82a9e51cbb 100644 --- a/openc3/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt +++ b/openc3/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt @@ -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" \ No newline at end of file diff --git a/openc3/spec/interfaces/http_client_interface_spec.rb b/openc3/spec/interfaces/http_client_interface_spec.rb index 7d811f591f..130733ed96 100644 --- a/openc3/spec/interfaces/http_client_interface_spec.rb +++ b/openc3/spec/interfaces/http_client_interface_spec.rb @@ -18,7 +18,8 @@ =begin -This RSpec test program covers all the methods in the HttpClientInterface class and aims to maximize coverage. It includes tests for: +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 @@ -36,30 +37,6 @@ module OpenC3 describe HttpClientInterface do -=begin - 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 - end - - describe "connection_string" do - 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" - - i = HttpClientInterface.new('machine', '443', 'https', '10', '11', '12') - expect(i.connection_string).to eql "https://machine" - - i = HttpClientInterface.new('127.0.0.1', '8080', 'http', '10', '11', '12') - expect(i.connection_string).to eql "http://127.0.0.1:8080" - end - end -=end - before(:each) do @interface = HttpClientInterface.new('example.com', 8080, 'https', 10, 15, 5, true) end diff --git a/openc3/spec/interfaces/http_server_interface_spec.rb b/openc3/spec/interfaces/http_server_interface_spec.rb index 3ebc5091b6..a45ab8bb4d 100644 --- a/openc3/spec/interfaces/http_server_interface_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_spec.rb @@ -42,13 +42,16 @@ module OpenC3 describe HttpServerInterface do + before(:all) do + setup_system() + end + before(:each) do @interface = HttpServerInterface.new - allow(System).to receive_message_chain(:commands, :packets).and_return({ - 'TARGET' => { - 'PACKET' => Packet.new('TARGET', 'PACKET') - } - }) + end + + after (:each) do + kill_leftover_threads() end describe "#initialize" do @@ -76,13 +79,25 @@ module OpenC3 end describe "#connect" do - it "creates a WEBrick server and mounts routes" do - allow(WEBrick::HTTPServer).to receive(:new).and_return(double('server').as_null_object) + it "connects to a web server and mounts routes" do allow_any_instance_of(Packet).to receive(:read).with('HTTP_PATH').and_return('/test') - #expect(@interface).to receive(:super) + allow_any_instance_of(Packet).to receive(:read).with('HTTP_STATUS').and_return(200) @interface.connect expect(@interface.instance_variable_get(:@server)).to_not be_nil - kill_leftover_threads() + end + + it "creates a response hook for every command packet" do + @interface.target_names = ['INST'] + server_double = double + allow(WEBrick::HTTPServer).to receive(:new).and_return(server_double) + request = OpenStruct.new(header: {alpha: "bet"}, query: {what: "is"}, status: nil, body: nil) + response = OpenStruct.new(status: nil, body: nil) + allow(server_double).to receive(:mount_proc).with('/test').and_yield(request, response) + expect(server_double).to receive(:start) do + sleep(0.1) + end + @interface.connect + sleep 0.2 end end @@ -104,7 +119,6 @@ module OpenC3 @interface.instance_variable_set(:@server, server) @interface.instance_variable_set(:@request_queue, Queue.new) @interface.instance_variable_get(:@request_queue).push("test") - #expect(@interface).to receive(:super) @interface.disconnect expect(@interface.instance_variable_get(:@server)).to be_nil expect(@interface.instance_variable_get(:@request_queue).size).to eq(1) From 367ffb5a1bc315fac534d617eed6313258b76ff3 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Mon, 16 Sep 2024 10:19:12 -0600 Subject: [PATCH 05/11] merge with existing cases where they are more thorough --- .../interfaces/http_client_interface_spec.rb | 18 ++++++++++++++++++ openc3/spec/packets/packet_item_spec.rb | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/openc3/spec/interfaces/http_client_interface_spec.rb b/openc3/spec/interfaces/http_client_interface_spec.rb index 130733ed96..bd98c4533e 100644 --- a/openc3/spec/interfaces/http_client_interface_spec.rb +++ b/openc3/spec/interfaces/http_client_interface_spec.rb @@ -42,6 +42,13 @@ module OpenC3 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') @@ -64,6 +71,17 @@ module OpenC3 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" + + i = HttpClientInterface.new('machine', '443', 'https', '10', '11', '12') + expect(i.connection_string).to eql "https://machine" + + i = HttpClientInterface.new('127.0.0.1', '8080', 'http', '10', '11', '12') + expect(i.connection_string).to eql "http://127.0.0.1:8080" + end end describe "#connect" do diff --git a/openc3/spec/packets/packet_item_spec.rb b/openc3/spec/packets/packet_item_spec.rb index 17a3035e6e..339af2e174 100644 --- a/openc3/spec/packets/packet_item_spec.rb +++ b/openc3/spec/packets/packet_item_spec.rb @@ -14,7 +14,7 @@ # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. -# All changes Copyright 2022, OpenC3, Inc. +# All changes Copyright 2024, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license From d43402137e149fc9120f4deae466380f79656a05 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Mon, 16 Sep 2024 12:35:06 -0600 Subject: [PATCH 06/11] make server spec defer to its runtime environment --- .../interfaces/http_server_interface_spec.rb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/openc3/spec/interfaces/http_server_interface_spec.rb b/openc3/spec/interfaces/http_server_interface_spec.rb index a45ab8bb4d..eb4c0cdb77 100644 --- a/openc3/spec/interfaces/http_server_interface_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_spec.rb @@ -44,10 +44,19 @@ module OpenC3 describe HttpServerInterface do before(:all) do setup_system() + @socket_80 = 80 # make desktop spec not collide with dev/CI env where Cosmos is already running + begin + sock = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) + sock.bind(Socket.pack_sockaddr_in(80, '127.0.0.1')) #raise if listening + sock.close + @socket_80 = 81 + rescue Errno::EACCES => e; + Logger.info("Found listener on port 80; presumably in CI\n#{e.message}") + end end before(:each) do - @interface = HttpServerInterface.new + @interface = HttpServerInterface.new(@socket_80) end after (:each) do @@ -56,12 +65,12 @@ module OpenC3 describe "#initialize" do it "initializes with default port" do - expect(@interface.instance_variable_get(:@port)).to eq(80) + expect(@interface.instance_variable_get(:@port)).to eq(@socket_80) end it "initializes with custom port" do - interface = HttpServerInterface.new(8080) - expect(interface.instance_variable_get(:@port)).to eq(8080) + interface = HttpServerInterface.new("80#{@socket_80}".to_i) + expect(interface.instance_variable_get(:@port)).to eq("80#{@socket_80}".to_i) end end @@ -74,7 +83,7 @@ module OpenC3 describe "#connection_string" do it "returns the correct connection string" do - expect(@interface.connection_string).to eq("listening on 0.0.0.0:80") + expect(@interface.connection_string).to eq("listening on 0.0.0.0:#{@socket_80}") end end From 060d4ab7dcb6645afda7a6c1915a8d21a84fc48e Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Mon, 16 Sep 2024 13:13:50 -0600 Subject: [PATCH 07/11] still trying to convince spec to cover real interface code --- openc3/spec/interfaces/http_server_interface_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openc3/spec/interfaces/http_server_interface_spec.rb b/openc3/spec/interfaces/http_server_interface_spec.rb index eb4c0cdb77..3f956c7993 100644 --- a/openc3/spec/interfaces/http_server_interface_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_spec.rb @@ -91,6 +91,7 @@ module OpenC3 it "connects to a web server and mounts routes" do allow_any_instance_of(Packet).to receive(:read).with('HTTP_PATH').and_return('/test') allow_any_instance_of(Packet).to receive(:read).with('HTTP_STATUS').and_return(200) + @interface.instance_variable_set(:@port, @socket_80) @interface.connect expect(@interface.instance_variable_get(:@server)).to_not be_nil end @@ -105,6 +106,7 @@ module OpenC3 expect(server_double).to receive(:start) do sleep(0.1) end + @interface.instance_variable_set(:@port, @socket_80) @interface.connect sleep 0.2 end From 7410d2fb6dd101068a3b4b953d00c0c0d49b21f2 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Mon, 16 Sep 2024 13:32:56 -0600 Subject: [PATCH 08/11] move port shim to rescue --- openc3/spec/interfaces/http_server_interface_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openc3/spec/interfaces/http_server_interface_spec.rb b/openc3/spec/interfaces/http_server_interface_spec.rb index 3f956c7993..75c24f4152 100644 --- a/openc3/spec/interfaces/http_server_interface_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_spec.rb @@ -49,9 +49,9 @@ module OpenC3 sock = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) sock.bind(Socket.pack_sockaddr_in(80, '127.0.0.1')) #raise if listening sock.close - @socket_80 = 81 rescue Errno::EACCES => e; Logger.info("Found listener on port 80; presumably in CI\n#{e.message}") + @socket_80 = 81 end end From e73e57539d2a36dba8235004e94144c063f62bf5 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Mon, 16 Sep 2024 15:55:37 -0600 Subject: [PATCH 09/11] test interface ports above system range --- .../interfaces/http_server_interface_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openc3/spec/interfaces/http_server_interface_spec.rb b/openc3/spec/interfaces/http_server_interface_spec.rb index 75c24f4152..61657c7a7c 100644 --- a/openc3/spec/interfaces/http_server_interface_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_spec.rb @@ -56,7 +56,7 @@ module OpenC3 end before(:each) do - @interface = HttpServerInterface.new(@socket_80) + @interface = HttpServerInterface.new(8000+@socket_80) end after (:each) do @@ -64,13 +64,13 @@ module OpenC3 end describe "#initialize" do - it "initializes with default port" do - expect(@interface.instance_variable_get(:@port)).to eq(@socket_80) + it "initializes with default port" do # above the privileged port range + expect(@interface.instance_variable_get(:@port)).to eq(8000+@socket_80) end it "initializes with custom port" do - interface = HttpServerInterface.new("80#{@socket_80}".to_i) - expect(interface.instance_variable_get(:@port)).to eq("80#{@socket_80}".to_i) + interface = HttpServerInterface.new(8000+@socket_80) + expect(interface.instance_variable_get(:@port)).to eq(8000+@socket_80) end end @@ -83,7 +83,7 @@ module OpenC3 describe "#connection_string" do it "returns the correct connection string" do - expect(@interface.connection_string).to eq("listening on 0.0.0.0:#{@socket_80}") + expect(@interface.connection_string).to eq("listening on 0.0.0.0:#{8000+@socket_80}") end end @@ -91,7 +91,7 @@ module OpenC3 it "connects to a web server and mounts routes" do allow_any_instance_of(Packet).to receive(:read).with('HTTP_PATH').and_return('/test') allow_any_instance_of(Packet).to receive(:read).with('HTTP_STATUS').and_return(200) - @interface.instance_variable_set(:@port, @socket_80) + @interface.instance_variable_set(:@port, 8000+@socket_80) @interface.connect expect(@interface.instance_variable_get(:@server)).to_not be_nil end @@ -106,7 +106,7 @@ module OpenC3 expect(server_double).to receive(:start) do sleep(0.1) end - @interface.instance_variable_set(:@port, @socket_80) + @interface.instance_variable_set(:@port, 8000+@socket_80) @interface.connect sleep 0.2 end From ac3c1a4069381a225cb5eb92a170fbfa753576c4 Mon Sep 17 00:00:00 2001 From: Joseph Brothers Date: Mon, 16 Sep 2024 17:18:51 -0600 Subject: [PATCH 10/11] clear CQ issues --- .../interfaces/http_client_interface_spec.rb | 50 +++++++++++-------- .../interfaces/http_server_interface_spec.rb | 7 +-- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/openc3/spec/interfaces/http_client_interface_spec.rb b/openc3/spec/interfaces/http_client_interface_spec.rb index bd98c4533e..2ceb962951 100644 --- a/openc3/spec/interfaces/http_client_interface_spec.rb +++ b/openc3/spec/interfaces/http_client_interface_spec.rb @@ -37,8 +37,16 @@ class and aims to maximize coverage. It includes tests for: module OpenC3 describe HttpClientInterface 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) + @interface = HttpClientInterface.new(@example_com, 8080, 'https', 10, 15, 5, true) end describe "#initialize" do @@ -50,14 +58,14 @@ module OpenC3 end it "initializes with default parameters" do - interface = HttpClientInterface.new('example.com') - expect(interface.instance_variable_get(:@hostname)).to eq('example.com') + 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(:@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) @@ -146,7 +154,7 @@ module OpenC3 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' }) + .with("#{@api_resource}/1", { 'confirm' => 'true' }, { 'Authorization' => 'Bearer token' }) .and_return(mock_response) @interface.write_interface(data, extra) @@ -166,13 +174,13 @@ module OpenC3 data = '{"post": "data"}' extra = { 'HTTP_METHOD' => 'post', - 'HTTP_URI' => '/api/resource', + 'HTTP_URI' => @api_resource, 'HTTP_QUERIES' => { 'param' => 'value' }, - 'HTTP_HEADERS' => { 'Content-Type' => 'application/json' } + '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(: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}') @@ -183,7 +191,7 @@ module OpenC3 @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_HEADERS' => {@content_type => @application_json}, 'HTTP_STATUS' => 201 }]) end @@ -192,13 +200,13 @@ module OpenC3 data = '{"put": "data"}' extra = { 'HTTP_METHOD' => 'put', - 'HTTP_URI' => '/api/resource/1', + 'HTTP_URI' => "#{@api_resource}/1", 'HTTP_QUERIES' => { 'param' => 'value' }, - 'HTTP_HEADERS' => { 'Content-Type' => 'application/json' } + '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(: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}') @@ -209,7 +217,7 @@ module OpenC3 @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_HEADERS' => {@content_type => @application_json}, 'HTTP_STATUS' => 200 }]) end @@ -239,36 +247,36 @@ module OpenC3 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(: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(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') + 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(: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(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(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 diff --git a/openc3/spec/interfaces/http_server_interface_spec.rb b/openc3/spec/interfaces/http_server_interface_spec.rb index 61657c7a7c..7228381180 100644 --- a/openc3/spec/interfaces/http_server_interface_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_spec.rb @@ -44,10 +44,11 @@ module OpenC3 describe HttpServerInterface do before(:all) do setup_system() + @localhost = '127.0.0.1' @socket_80 = 80 # make desktop spec not collide with dev/CI env where Cosmos is already running begin sock = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0) - sock.bind(Socket.pack_sockaddr_in(80, '127.0.0.1')) #raise if listening + sock.bind(Socket.pack_sockaddr_in(80, @localhost )) #raise if listening sock.close rescue Errno::EACCES => e; Logger.info("Found listener on port 80; presumably in CI\n#{e.message}") @@ -76,8 +77,8 @@ module OpenC3 describe "#set_option" do it "sets the listen address" do - @interface.set_option("LISTEN_ADDRESS", ["127.0.0.1"]) - expect(@interface.instance_variable_get(:@listen_address)).to eq("127.0.0.1") + @interface.set_option("LISTEN_ADDRESS", [ @localhost ]) + expect(@interface.instance_variable_get(:@listen_address)).to eq( @localhost ) end end From c2d9104ca0499af03c3c12217f29f6b2856ac637 Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Tue, 17 Sep 2024 15:04:36 -0600 Subject: [PATCH 11/11] Remove generated comments --- .../interfaces/http_server_interface.rb | 2 +- .../interfaces/http_client_interface_spec.rb | 16 -------------- .../interfaces/http_server_interface_spec.rb | 21 +------------------ 3 files changed, 2 insertions(+), 37 deletions(-) diff --git a/openc3/lib/openc3/interfaces/http_server_interface.rb b/openc3/lib/openc3/interfaces/http_server_interface.rb index 6ee255cecd..c65e31f8db 100644 --- a/openc3/lib/openc3/interfaces/http_server_interface.rb +++ b/openc3/lib/openc3/interfaces/http_server_interface.rb @@ -75,7 +75,7 @@ def connect # No HTTP_STATUS - Leave at default end - # TODO: is packet.extra ever set? + # http_accessor stores all the pseudo-derived HTTP configuration in extra if packet.extra headers = packet.extra['HTTP_HEADERS'] if headers diff --git a/openc3/spec/interfaces/http_client_interface_spec.rb b/openc3/spec/interfaces/http_client_interface_spec.rb index 2ceb962951..f43eebe4d5 100644 --- a/openc3/spec/interfaces/http_client_interface_spec.rb +++ b/openc3/spec/interfaces/http_client_interface_spec.rb @@ -16,22 +16,6 @@ # 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' diff --git a/openc3/spec/interfaces/http_server_interface_spec.rb b/openc3/spec/interfaces/http_server_interface_spec.rb index 7228381180..324c1faab6 100644 --- a/openc3/spec/interfaces/http_server_interface_spec.rb +++ b/openc3/spec/interfaces/http_server_interface_spec.rb @@ -16,25 +16,6 @@ # This file may also be used under the terms of a commercial license # if purchased from OpenC3, Inc. -=begin -Here's a complete runnable RSpec test program for the HttpServerInterface class: - -This RSpec test program covers all the methods in the HttpServerInterface class and aims to maximize coverage. It includes tests for: - -1. Initialization with default and custom ports -2. Setting options -3. Connection string generation -4. Connecting and creating the WEBrick server -5. Checking connection status -6. Disconnecting and cleaning up -7. Reading from the interface -8. Writing to the interface (which raises an error) -9. Converting data to packets -10. Converting packets to data (which raises an error) - -Note that some methods like `connect` are more challenging to test thoroughly due to their complexity and dependencies. In a real-world scenario, you might want to consider using more advanced mocking techniques or integration tests to cover these areas more comprehensively. -=end - require 'spec_helper' require 'openc3/interfaces/http_server_interface' require 'openc3/packets/packet' @@ -60,7 +41,7 @@ module OpenC3 @interface = HttpServerInterface.new(8000+@socket_80) end - after (:each) do + after(:each) do kill_leftover_threads() end