# 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 end describe '#register_module' do 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)) r = Intranet::TestResponder.new('/index.html' => [200, 'text/html', '']) thread = Thread.new do @intranet.start end while @intranet.instance_variable_get(:@server).status != :Running end expect { @intranet.register_module(r, ['path'], '') }.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)) 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 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 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 do @intranet.start end while @intranet.instance_variable_get(:@server).status != :Running end end after(:each) do Thread.kill(@thread) @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/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/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 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'], 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 /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)}' ) 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, ['r'], 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 /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 # Returned HTML document: includes the partial content and the title expect(html).to match(%r{
.*PARTIAL_CONTENT.*
}m) expect(html).to match(%r{.*.*MyTitle.*.*}m) # Returned HTML document: includes the hostname in title, h1-title and footer hostname = Socket.gethostname expect(html).to match(%r{.*.*#{hostname.capitalize}.*.*}m) expect(html).to match(%r{.*

.*#{hostname.capitalize}.*

.*}m) expect(html).to match(%r{}m) # Returned HTML document: includes all CSS dependencies, relative or absolute path expect(html).to match(%r{}) expect(html).to match(%r{}) # Returned HTML document: includes Intranet Core name, version and URL expect(html).to match( %r{}m # rubocop:disable Layout/LineLength ) # Returned HTML document: includes all registered modules version name, version and URL expect(html).to match( %r{}m ) ensure socket.close Thread.kill(thread) thread.join end end it 'should be called to update the main navigation menu' 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' }] ) other_responder = Intranet::TestResponder.new({}, [], [], true) @intranet.register_module(responder, %w[r], responder.resources_dir) @intranet.register_module(responder, %w[dep_th1], responder.resources_dir) @intranet.register_module(responder, %w[depth2 res_p1], responder.resources_dir) @intranet.register_module(responder, %w[depth2 resp2], responder.resources_dir) @intranet.register_module(other_responder, %w[depth2 resp], other_responder.resources_dir) @intranet.register_module(other_responder, %w[other1], other_responder.resources_dir) @intranet.register_module(other_responder, %w[other2 res1], other_responder.resources_dir) @intranet.register_module(other_responder, %w[other2 res2], other_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 /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) break if line.empty? end html = socket.readpartial(4096) # read rest of data # Returned HTML document main menu expect(html).to match(%r{.*Dep Th1.*}) expect(html).not_to match(%r{}) expect(html).to match( %r{.*Depth2.*.*}m ) expect(html).to match( %r{.*Depth2.*.*}m ) expect(html).not_to match(%r{}) expect(html).not_to match(%r{.*Other2.*}m) expect(html).not_to match(%r{}) expect(html).not_to match(%r{}) ensure socket.close Thread.kill(thread) thread.join end end end end describe '#home_url=' do context 'given a relative URL' do it 'should fail' do @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL)) expect { @intranet.home_url = 'foo/index.html' }.to raise_error ArgumentError end end context 'given an absolute URL' do it 'should set up a redirection from /index.html to the provided URL' do begin @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL)) @intranet.home_url = '/responder/index.html' 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") expect(socket.gets).to include('HTTP/1.1 307 Temporary Redirect') while (line = socket.gets.chomp) # search the HTTP response for the 'Location' header break if line.start_with?('Location:') end expect(line).to include("http://localhost:#{@intranet.port}/responder/index.html") ensure socket.close Thread.kill(thread) thread.join end end end end describe '#stop' do it 'should stop the web server and finalize all registered responders' do begin @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL)) responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', 'CONTENT']) @intranet.register_module(responder, %w[r], 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 /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n") expect(socket.gets).to include('HTTP/1.1 200 OK') expect(responder.finalized).to be false socket.close @intranet.stop while @intranet.instance_variable_get(:@server).status != :Stop end expect { TCPSocket.new('localhost', @intranet.port) }.to raise_error(Errno::ECONNREFUSED) expect(responder.finalized).to be true ensure socket.close Thread.kill(thread) thread.join end end end end