# 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
begin
# make sure port 80 & 8080 are not available
@intranet8080 = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new do
@intranet8080.start
end
# Wait for the server on port 8080 to be running
while @intranet8080.instance_variable_get(:@server).status != :Running
end
intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
expect(intranet.port).to eql(
intranet.instance_variable_get(:@server).instance_variable_get(:@config)[:Port]
)
expect(intranet.port).not_to eql(8080)
ensure
intranet.stop if intranet.respond_to?(:stop)
Thread.kill(thread)
thread.join
end
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)
expect(intranet.port).to be >= 9090
end
it 'should serve the /design directory' do
begin
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new do
@intranet.start
end
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')
ensure
socket.close
Thread.kill(thread)
thread.join
end
end
context 'when no module is registered' do
it 'should return HTTP error 404 when requested for /index.html' do
begin
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new do
@intranet.start
end
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')
ensure
socket.close
Thread.kill(thread)
thread.join
end
end
end
context 'registering a module when the server is running' do
it 'should fail' do
begin
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
thread = Thread.new do
@intranet.start
end
while @intranet.instance_variable_get(:@server).status != :Running
end
expect { @intranet.register_module(nil, [], '') }.to raise_error Errno::EALREADY
ensure
Thread.kill(thread)
thread.join
end
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))
expect { @intranet.register_module(nil, ['Invalid'], '') }.to raise_error ArgumentError
end
end
context 'registering an invalid module' do
it 'should succeed but accesses to the module URL should return HTTP error 404' do
begin
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
@intranet.register_module(nil, [], '')
thread = Thread.new do
@intranet.start
end
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
ensure
socket.close
Thread.kill(thread)
thread.join
end
end
end
context 'when a valid module is registered with no path (home module)' do
it 'should be used to serve all URI' do
begin
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''],
'/folder/index.html' => [200, 'text/html', ''])
@intranet.register_module(responder, [], responder.resources_dir)
thread = Thread.new do
@intranet.start
end
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 200 OK')
socket.close
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /folder/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 /folder/subdir/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
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /design/home/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')
ensure
socket.close
Thread.kill(thread)
thread.join
end
end
end
context 'when a valid module is registered with a path' do
it 'should be used to serve URI relative to the module root' do
begin
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
@intranet.register_module(responder, [], responder.resources_dir)
@intranet.register_module(responder, %w[responder], responder.resources_dir)
@intranet.register_module(responder, %w[resp onder], responder.resources_dir)
thread = Thread.new do
@intranet.start
end
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 200 OK')
socket.close
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /design/home/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 /responder/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 /design/responder/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/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 /design/resp/onder/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')
ensure
socket.close
Thread.kill(thread)
thread.join
end
end
end
context 'given a valid and registered module' do
it 'should be called with the URL path and query' do
begin
@intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
@intranet.register_module(responder, [], responder.resources_dir)
thread = Thread.new do
@intranet.start
end
while @intranet.instance_variable_get(:@server).status != :Running
end
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /query?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({ 'var1' => 'value1', 'var2' => 'value2' }.to_s)
socket.close
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /query?foo=bar&baz=boz 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({ 'foo' => 'bar', 'baz' => 'boz' }.to_s)
ensure
socket.close
Thread.kill(thread)
thread.join
end
end
end
context 'given a module returning partial HTML content' do
it 'should be called to retrieve the body of the page' do
begin
@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, [], responder.resources_dir)
thread = Thread.new do
@intranet.start
end
while @intranet.instance_variable_get(:@server).status != :Running
end
socket = TCPSocket.new('localhost', @intranet.port)
socket.puts("GET /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
# Returned HTML document: includes the partial content and the title
expect(html).to match(%r{