# frozen_string_literal: true
require 'intranet/core'
require 'intranet/logger'
# Warning! We use buffered methods for reading/writing data to the socket. Mixing unbuffered
# methods (send, recv) and buffered methods (puts, gets, and other IO methods) does not work.
require 'socket'
require_relative '../test_responder/responder'
RSpec.describe Intranet::Core do
it 'should define its version' do
expect { described_class::VERSION }.not_to raise_error
end
describe '#start' do
it 'should start an HTTP server on an available port' do
# make sure port 80 & 8080 are not available
@intranet8080 = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread8080 = Thread.new { @intranet8080.start }
while @intranet8080.instance_variable_get(:@server).status != :Running
end
expect(@intranet8080.port).to be >= 8080
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
expect(@intranet.port).to be > 8080
@intranet.stop
thread&.join
@intranet8080.stop
thread8080&.join
end
it 'should start searching for an available port at the given +preferred_port+' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL), 9090)
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
expect(@intranet.port).to be >= 9090
@intranet.stop
thread&.join
end
it 'should serve the /design directory' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /design/favicon.ico HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
expect(socket.gets).to include('HTTP/1.1 200 OK')
socket.close
@intranet.stop
thread&.join
end
it 'should not advertise server version in HTTP response headers' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /design/favicon.ico HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
server_identification = ''
expect(socket.gets).to include('HTTP/1.1 200 OK') # pre-requisite
while (line = socket.gets.chomp) # look for Server response header
break if line.empty?
if line.start_with?('Server: ')
server_identification = line[8..-1]
break
end
end
expect(server_identification).to be_empty
socket.close
@intranet.stop
thread&.join
end
context 'when no module is registered' do
it 'should return HTTP error 404 when requested for /index.html' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /index.html HTTP/1.1\r\n" \
"Host: localhost:#{@intranet.port}\r\n\r\n")
expect(socket.gets).to include('HTTP/1.1 404 Not Found')
socket.close
@intranet.stop
thread&.join
end
end
end
describe '#register_module' do
context 'registering a module when the server is running' do
it 'should fail' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
r = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
expect { @intranet.register_module(r, ['path'], '') }.to raise_error Errno::EALREADY
@intranet.stop
thread&.join
end
end
context 'registering a module with an invalid path' do
it 'should fail' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
r = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
expect { @intranet.register_module(r, [], '') }.to raise_error ArgumentError
expect { @intranet.register_module(r, %w[1 2 3], '') }.to raise_error ArgumentError
expect { @intranet.register_module(r, ['', 'valid'], '') }.to raise_error ArgumentError
expect { @intranet.register_module(r, ['Invalid'], '') }.to raise_error ArgumentError
expect { @intranet.register_module(r, 'fo', '') }.to raise_error ArgumentError
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
@intranet.stop
thread&.join
end
end
context 'registering an invalid module' do
it 'should fail' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
expect { @intranet.register_module(nil, ['path'], '') }.to raise_error ArgumentError
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
@intranet.stop
thread&.join
end
end
context 'when a valid module is registered' do
before(:each) do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
# Third argument of register_module() is optional, so we test both cases.
@intranet.register_module(responder, %w[responder], responder.resources_dir)
@intranet.register_module(responder, %w[resp onder])
@thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
end
after(:each) do
@intranet.stop
@thread&.join
end
it 'should be used to serve URI relative to the module root' do
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /responder/ HTTP/1.1\r\n" \
"Host: localhost:#{@intranet.port}\r\n\r\n")
expect(socket.gets).to include('HTTP/1.1 200 OK')
socket.close
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /resp/onder/index.html HTTP/1.1\r\n" \
"Host: localhost:#{@intranet.port}\r\n\r\n")
expect(socket.gets).to include('HTTP/1.1 200 OK')
socket.close
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /resp/onder/index2.html HTTP/1.1\r\n" \
"Host: localhost:#{@intranet.port}\r\n\r\n")
expect(socket.gets).to include('HTTP/1.1 404 Not Found')
socket.close
end
it 'should have its www/ directory available under the subfolder design/' do
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /responder/design/style.css HTTP/1.1\r\n" \
"Host: localhost:#{@intranet.port}\r\n\r\n")
expect(socket.gets).to include('HTTP/1.1 200 OK')
socket.close
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /resp/onder/design/style.css HTTP/1.1\r\n" \
"Host: localhost:#{@intranet.port}\r\n\r\n")
expect(socket.gets).to include('HTTP/1.1 200 OK')
socket.close
end
end
context 'given a valid and registered module' do
it 'should be called with the decoded URL path and query in UTF-8 encoding' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
@intranet.register_module(responder, ['responder'], responder.resources_dir)
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /responder/query%20t?var1=value1&var2=value2 HTTP/1.1\r\n" \
"Host: localhost:#{@intranet.port}\r\n\r\n")
expect(socket.gets).to include('HTTP/1.1 200 OK')
while (line = socket.gets.chomp) # consume HTTP response headers
break if line.empty?
end
line = socket.gets.chomp
expect(line).to eql(
'PATH=/query t (UTF-8), ' \
'QUERY={var1 (UTF-8) => value1 (UTF-8),var2 (UTF-8) => value2 (UTF-8)}'
)
socket.close
@intranet.stop
thread&.join
end
end
context 'given a module returning partial HTML content' do
it 'should be called to retrieve the body of the page' do
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
responder = Intranet::TestResponder.new(
{
'/index.html' => [206, 'text/html', { content: 'PARTIAL_CONTENT', title: 'MyTitle' }]
},
['/responder.css', 'nav.css'],
['module.js', '/js/interactive.js']
)
@intranet.register_module(responder, ['r'], responder.resources_dir)
thread = Thread.new { @intranet.start }
while @intranet.instance_variable_get(:@server).status != :Running
end
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
# Return code: HTTP error 200
expect(socket.gets).to include('HTTP/1.1 200 OK')
while (line = socket.gets.chomp) # consume HTTP response headers
break if line.empty?
end
html = socket.readpartial(4096) # read rest of data
socket.close
# Returned HTML document: includes the partial content and the title
expect(html).to match(%r{