defmodule AssertURL do @moduledoc ~S""" AssertURL is a module with semantic helpers to test URLs. The goal is to provide an explicit way to test the different parts of a URL without matching string == string. """ defmodule ErrorFormatter do @moduledoc ~S""" ANSI format error messages. """ def format(expected, given) do to_string(IO.ANSI.format([ :reset, "Expected ", :green, :bright, "#{expected}", :reset, ", got ", :red, :bright, "#{given}", :reset, "." ])) end def format(key: key, query: query) do to_string(IO.ANSI.format([ :reset, "Query key ", :red, :bright, "#{key}", :reset, " is missing from ", :yellow, :bright, query ])) end def format(key: key, expected_value: expected_value, value: value, query: query) do to_string(IO.ANSI.format([ :reset, "Expected ", :green, :bright, "#{key}=#{expected_value}", :reset, " but got ", :red, :bright, "#{key}=#{value}", :reset, " in ", :yellow, :bright, query ])) end end defmodule SchemeError do @moduledoc ~S""" Represents an error in the expected scheme. """ defexception [:message, :expected, :given] def exception(expected: expected, given: given) do message = ErrorFormatter.format(expected, given) %SchemeError{message: message} end end @doc ~S""" Verifies the scheme of a url. ## Use assert AssertURL.scheme_equal("http", "http://foo.com") """ def scheme_equal(expected, url) do scheme = URI.parse(url).scheme if expected != scheme do raise SchemeError, expected: expected, given: scheme else true end end defmodule HostError do @moduledoc ~S""" Represents an error in the expected host. """ defexception [:message, :expected, :given] def exception(expected: expected, given: given) do message = ErrorFormatter.format(expected, given) %HostError{message: message} end end @doc ~S""" Verifies the host of a url. ## Use assert AssertURL.host_equal("foo.com", "http://foo.com") """ def host_equal(expected, url) do host = URI.parse(url).host if expected != host do raise HostError, expected: expected, given: host else true end end defmodule PortError do @moduledoc ~S""" Represents an error in the expected host. """ defexception [:message, :expected, :given] def exception(expected: expected, given: given) do message = ErrorFormatter.format(expected, given) %PortError{message: message} end end @doc ~S""" Verifies the port of a url. ## Use assert AssertURL.port_equal(80, "http://foo.com") """ def port_equal(expected, url) do port = URI.parse(url).port if expected != port do raise PortError, expected: expected, given: port else true end end defmodule PathError do @moduledoc ~S""" Represents an error in the expected path. """ defexception [:message, :expected, :given] def exception(expected: expected, given: given) do message = ErrorFormatter.format(expected, given) %PathError{message: message} end end @doc ~S""" Verifies the path of a url. ## Use assert AssertURL.path_equal("/path", "http://foo.com/path") """ def path_equal(expected, url) do path = URI.parse(url).path if expected != path do raise PathError, expected: expected, given: path else true end end defmodule QueryError do @moduledoc ~S""" Represents an error in the expected query. """ defexception [:message, :expected, :given] def exception(expected: expected, given: given) do message = ErrorFormatter.format(expected, given) %QueryError{message: message} end end @doc ~S""" Verifies the query of a url. The order of the query items is relevant for this method. ## Use assert AssertURL.query_equal("foo=bar", "http://foo.com/path?foo=bar") """ def query_equal(expected, url) do query = URI.parse(url).query if expected != query do raise QueryError, expected: expected, given: query else true end end defmodule QueryKeyMissingError do @moduledoc ~S""" Raised when a key in the expected list is not present in the query: ## Example # Will raise QueryKeyMissingError AssertURL.query_include [sorry: "missing"], "http://foo.com/path?foo=bar" """ defexception [:message, :expected, :given] def exception(key: key, query: query) do message = ErrorFormatter.format(key: key, query: query) %QueryKeyMissingError{message: message} end end defmodule QueryValueError do @moduledoc ~S""" Raised when a key in the query doesn't have the expected value. ## Example # Will raise QueryValueError AssertURL.query_include [foo: "not_bar"], "http://foo.com/path?foo=bar" """ defexception [:message, :expected, :given] def exception(key: key, expected_value: expected_value, value: value, query: query) do message = ErrorFormatter.format(key: key, expected_value: expected_value, value: value, query: query) %QueryValueError{message: message} end end @doc ~S""" Verifies that the elements of the keyword list are part of the query. The order of the keyword is irrelevant for this method. ## Use assert AssertURL.query_equal [foo: "bar"], "http://foo.com/path?foo=bar&very=wow" assert AssertURL.query_equal [foo: "bar", very: "wow"], "http://foo.com/path?foo=bar&very=wow" assert AssertURL.query_equal [very: "wow", foo: "bar"], "http://foo.com/path?foo=bar&very=wow" assert AssertURL.query_equal [], "http://foo.com/path?foo=bar&very=wow" """ def query_include(expected_query_items, url) do query = URI.parse(url).query query_items = URI.decode_query(query) for {key, value} <- expected_query_items do key = to_string(key) cond do !Map.has_key?(query_items, key) -> raise QueryKeyMissingError, key: key, query: query query_items[key] != value -> raise QueryValueError, key: key, expected_value: value, value: query_items[key], query: query true -> true end end end defmodule FragmentError do @moduledoc ~S""" Represents an error in the expected fragment. """ defexception [:message, :expected, :given] def exception(expected: expected, given: given) do message = ErrorFormatter.format(expected, given) %FragmentError{message: message} end end @doc ~S""" Verifies the fragment of a url. ## Use assert AssertURL.fragment_equal("fragment", "http://foo.com/path#fragment" """ def fragment_equal(expected, url) do fragment = URI.parse(url).fragment if expected != fragment do raise FragmentError, expected: expected, given: fragment else true end end end