#!/usr/bin/env rspec -cfd

require_relative '../../spec_helper'

require 'arborist/node/service'


describe Arborist::Node::Service do

	let( :host ) do
		Arborist::Node.create( 'host', 'testhost' ) do
			address '192.168.118.3'
		end
	end


	it "can be created without reasonable defaults based on its identifier" do
		result = described_class.new( 'ssh', host )
		expect( result.port ).to eq( 22 )
		expect( result.protocol ).to eq( 'tcp' )
	end


	it "can be created with an explicit port" do
		result = described_class.new( 'ssh', host, port: 2222 )
		expect( result.port ).to eq( 2222 )
		expect( result.protocol ).to eq( 'tcp' )
	end


	it "can be created with an explicit port" do
		result = described_class.new( 'rsspk', host, port: 1801, protocol: 'udp' )
		expect( result.port ).to eq( 1801 )
		expect( result.protocol ).to eq( 'udp' )
	end


	it "uses the identifier as the application protocol if none is specified" do
		result = described_class.new( 'rsspk', host, port: 1801 )
		expect( result.port ).to eq( 1801 )
		expect( result.app_protocol ).to eq( 'rsspk' )
	end


	it "can specify an explicit application protocol" do
		result = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'dns' )
		expect( result.app_protocol ).to eq( 'dns' )
	end


	it "raises a sensible error when created without a host" do
		expect {
			described_class.new( 'dnsd', nil )
		}.to raise_error( Arborist::NodeError, /no host/i )
	end


	it "includes its service attributes when turned into a Hash" do
		service = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'dns' )

		expect( service.to_h ).to include( :port, :protocol, :app_protocol )
		expect( service.to_h[:port] ).to eq( service.port )
		expect( service.to_h[:protocol] ).to eq( service.protocol )
		expect( service.to_h[:app_protocol] ).to eq( service.app_protocol )
	end


	it "keeps its service attributes when marshalled" do
		service = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'dns' )

		expect( service.to_h ).to include( :port, :protocol, :app_protocol )
		expect( service.to_h[:port] ).to eq( service.port )
		expect( service.to_h[:protocol] ).to eq( service.protocol )
		expect( service.to_h[:app_protocol] ).to eq( service.app_protocol )
	end


	it "is equal to another service node with the same metadata and service attributes" do
		service1 = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'dns' )
		service2 = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'dns' )

		expect( service1 ).to eq( service2 )
	end


	it "is not equal to another service node with the same metadata and different service attributes" do
		service1 = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'dns' )
		service2 = described_class.new( 'dnsd', host, port: 53, protocol: 'tcp', app_protocol: 'dns' )

		expect( service1 ).to_not eq( service2 )
	end


	it "is not equal to another service node with the same metadata and different port" do
		service1 = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'dns' )
		service2 = described_class.new( 'dnsd', host, port: 80, protocol: 'udp', app_protocol: 'dns' )

		expect( service1 ).to_not eq( service2 )
	end


	it "is not equal to another service node with the same metadata and different app protocol" do
		service1 = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'dns' )
		service2 = described_class.new( 'dnsd', host, port: 53, protocol: 'udp', app_protocol: 'smtp' )

		expect( service1 ).to_not eq( service2 )
	end



	describe "matching" do

		let( :host ) do
			Arborist::Node.create( 'host', 'testhost' ) do
				address '192.168.66.12'
				address '10.1.33.8'
			end
		end

		let( :node ) do
			described_class.new( 'ssh', host )
		end


		it "inherits its host's addresses" do
			expect( node ).to match_criteria( address: '192.168.66.12' )
			expect( node ).to_not match_criteria( address: '127.0.0.1' )
		end


		it "can be limited to a subset of its host's addresses" do
			node.address( host.addresses.first )
			expect( node ).to match_criteria( address: '192.168.66.12' )
			expect( node ).to_not match_criteria( address: '10.1.33.8' )
			expect( node ).to_not match_criteria( address: '127.0.0.1' )
		end


		it "errors if it specifies an address other than one of its host's addresses" do
			expect {
				node.address( '127.0.0.1' )
			}.to raise_error( Arborist::ConfigError, /127.0.0.1 is not one of testhost's addresses/i )
		end


		it "can be matched with a netblock that includes one of its host's addresses" do
			expect( node ).to match_criteria( address: '192.168.66.0/24' )
			expect( node ).to match_criteria( address: '10.0.0.0/8' )
			expect( node ).to_not match_criteria( address: '192.168.66.64/27' )
			expect( node ).to_not match_criteria( address: '127.0.0.0/8' )
		end


		it "can be matched with a port" do
			expect( node ).to match_criteria( port: 22 )
			expect( node ).to match_criteria( port: 'ssh' )
			expect( node ).to_not match_criteria( port: 80 )
			expect( node ).to_not match_criteria( port: 'www' )
			expect( node ).to_not match_criteria( port: 'chungwatch' )
		end


		it "can be matched with a protocol" do
			expect( node ).to match_criteria( protocol: 'tcp' )
			expect( node ).to_not match_criteria( protocol: 'udp' )
		end


		it "can be matched with an application protocol" do
			expect( node ).to match_criteria( app_protocol: 'ssh' )
			expect( node ).to match_criteria( app: 'ssh' )
			expect( node ).to_not match_criteria( app_protocol: 'http' )
			expect( node ).to_not match_criteria( app: 'http' )
		end

	end

end