local TEST_TIMEOUT = 2
describe("http.socks module", function()
	local http_socks = require "http.socks"
	local cqueues = require "cqueues"
	local ca = require "cqueues.auxlib"
	local ce = require "cqueues.errno"
	local cs = require "cqueues.socket"
	it("works with connect constructor", function()
		assert(http_socks.connect("socks5://127.0.0.1"))
		assert(http_socks.connect("socks5h://username:password@127.0.0.1"))
	end)
	it("fails on unknown protocols", function()
		assert.has.errors(function()
			http_socks.connect("socks3://host")
		end)
	end)
	it("fails when userinfo is missing password", function()
		assert.has.errors(function()
			http_socks.connect("socks5h://user@host")
		end)
	end)
	it("has a working :clone", function()
		local socks = http_socks.connect("socks5://127.0.0.1")
		assert.same(socks, socks:clone())
	end)
	it("has a working :clone when userinfo present", function()
		local socks = http_socks.connect("socks5://user:pass@127.0.0.1")
		assert.same(socks, socks:clone())
	end)
	it("can negotiate a IPv4 connection with no auth", function()
		local s, c = ca.assert(cs.pair())
		local cq = cqueues.new()
		cq:wrap(function()
			assert(http_socks.fdopen(c):negotiate("127.0.0.1", 123))
		end)
		cq:wrap(function()
			assert.same("\5", s:read(1))
			local n = assert(s:read(1)):byte()
			local available_auth = assert(s:read(n))
			assert.same("\0", available_auth)
			assert(s:xwrite("\5\0", "n"))
			assert.same("\5\1\0\1\127\0\0\1\0\123", s:read(10))
			assert(s:xwrite("\5\0\0\1\127\0\0\1\12\34", "n"))
		end)
		assert_loop(cq, TEST_TIMEOUT)
		assert.truthy(cq:empty())
		s:close()
		c:close()
	end)
	it("can negotiate a IPv6 connection with username+password auth", function()
		local s, c = ca.assert(cs.pair())
		local cq = cqueues.new()
		cq:wrap(function()
			c = http_socks.fdopen(c)
			assert(c:add_username_password_auth("open", "sesame"))
			assert(c:negotiate("::1", 123))
			c:close()
		end)
		cq:wrap(function()
			assert.same("\5", s:read(1))
			local n = assert(s:read(1)):byte()
			local available_auth = assert(s:read(n))
			assert.same("\0\2", available_auth)
			assert(s:xwrite("\5\2", "n"))
			assert.same("\1\4open\6sesame", s:read(13))
			assert(s:xwrite("\1\0", "n"))
			assert.same("\5\1\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\123", s:read(22))
			assert(s:xwrite("\5\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\12\34", "n"))
			s:close()
		end)
		assert_loop(cq, TEST_TIMEOUT)
		assert.truthy(cq:empty())
	end)
	it("can negotiate a connection where peername is a domain", function()
		local s, c = ca.assert(cs.pair())
		local cq = cqueues.new()
		cq:wrap(function()
			c = http_socks.fdopen(c)
			assert(c:negotiate("127.0.0.1", 123))
			assert.same(cs.AF_UNSPEC, c.dst_family)
			assert.same("test", c.dst_host)
			assert.same(1234, c.dst_port)
			c:close()
		end)
		cq:wrap(function()
			assert.same("\5", s:read(1))
			local n = assert(s:read(1)):byte()
			local available_auth = assert(s:read(n))
			assert.same("\0", available_auth)
			assert(s:xwrite("\5\0", "n"))
			assert.same("\5\1\0\1\127\0\0\1\0\123", s:read(10))
			assert(s:xwrite("\5\0\0\3\4test\4\210", "n"))
			s:close()
		end)
		assert_loop(cq, TEST_TIMEOUT)
		assert.truthy(cq:empty())
	end)
	it("fails incorrect username+password with EACCES", function()
		local s, c = ca.assert(cs.pair())
		local cq = cqueues.new()
		cq:wrap(function()
			c = http_socks.fdopen(c)
			assert(c:add_username_password_auth("open", "sesame"))
			assert.same(ce.EACCES, select(3, c:negotiate("unused", 123)))
			c:close()
		end)
		cq:wrap(function()
			assert.same("\5", s:read(1))
			local n = assert(s:read(1)):byte()
			local available_auth = assert(s:read(n))
			assert.same("\0\2", available_auth)
			assert(s:xwrite("\5\2", "n"))
			assert.same("\1\4open\6sesame", s:read(13))
			assert(s:xwrite("\1\1", "n"))
			s:close()
		end)
		assert_loop(cq, TEST_TIMEOUT)
		assert.truthy(cq:empty())
	end)
	it("fails with correct error messages", function()
		for i, correct_errno in ipairs({
			false;
			ce.EACCES;
			ce.ENETUNREACH;
			ce.EHOSTUNREACH;
			ce.ECONNREFUSED;
			ce.ETIMEDOUT;
			ce.EOPNOTSUPP;
			ce.EAFNOSUPPORT;
		}) do
			local c, s = ca.assert(cs.pair())
			local cq = cqueues.new()
			cq:wrap(function()
				c = http_socks.fdopen(c)
				local ok, _, errno = c:negotiate("127.0.0.1", 123)
				assert.falsy(ok)
				if correct_errno then
					assert.same(correct_errno, errno)
				end
				c:close()
			end)
			cq:wrap(function()
				assert.same("\5", s:read(1))
				local n = assert(s:read(1)):byte()
				local available_auth = assert(s:read(n))
				assert.same("\0", available_auth)
				assert(s:xwrite("\5\0", "n"))
				assert.same("\5\1\0\1\127\0\0\1\0\123", s:read(10))
				assert(s:xwrite("\5" .. string.char(i), "n"))
				s:close()
			end)
			assert_loop(cq, TEST_TIMEOUT)
			assert.truthy(cq:empty())
		end
	end)
	it("fails with EAFNOSUPPORT on unknown address type", function()
		local s, c = ca.assert(cs.pair())
		local cq = cqueues.new()
		cq:wrap(function()
			c = http_socks.fdopen(c)
			local ok, _, errno = c:negotiate("127.0.0.1", 123)
			assert.falsy(ok)
			assert.same(ce.EAFNOSUPPORT, errno)
			c:close()
		end)
		cq:wrap(function()
			assert.same("\5", s:read(1))
			local n = assert(s:read(1)):byte()
			local available_auth = assert(s:read(n))
			assert.same("\0", available_auth)
			assert(s:xwrite("\5\0", "n"))
			assert.same("\5\1\0\1\127\0\0\1\0\123", s:read(10))
			assert(s:xwrite("\5\0\0\5", "n"))
			s:close()
		end)
		assert_loop(cq, TEST_TIMEOUT)
		assert.truthy(cq:empty())
	end)
	it("has a working :take_socket", function()
		local s, c = ca.assert(cs.pair())
		local socks = http_socks.fdopen(c)
		assert.same(c, socks:take_socket())
		assert.same(nil, socks:take_socket())
		s:close()
		c:close()
	end)
end)
