# 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{
.*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 expect(html).to match(%r{}) expect(html).to match(%r{}) # Returned HTML document: includes Intranet Core Version expect(html).to match(%r{}m) 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' }] ) @intranet.register_module(responder, [], 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) 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) 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).to match( %r{.*Depth2.*.*}m ) expect(html).to match( %r{.*Depth2.*.*}m ) 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, [], 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") 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