diff --git a/elixir/apps/web/mix.exs b/elixir/apps/web/mix.exs index 7b2f7d291..528bfd25f 100644 --- a/elixir/apps/web/mix.exs +++ b/elixir/apps/web/mix.exs @@ -78,7 +78,6 @@ defmodule Web.MixProject do # Test deps {:floki, "~> 0.37.0", only: :test}, {:bypass, "~> 2.1", only: :test}, - {:bureaucrat, "~> 0.2.9", only: :test}, {:wallaby, "~> 0.30.0", only: :test}, {:credo, "~> 1.5", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, diff --git a/elixir/apps/web/test/support/documentation/docs_generator.ex b/elixir/apps/web/test/support/documentation/docs_generator.ex deleted file mode 100644 index e8e07def4..000000000 --- a/elixir/apps/web/test/support/documentation/docs_generator.ex +++ /dev/null @@ -1,381 +0,0 @@ -defmodule Web.Documentation.Generator do - alias Domain.Config.Definition - - @keep_req_headers ["authorization"] - @keep_resp_headers ["content-type", "location"] - - def write(conns, path) do - write_config_doc!(Domain.Config.Definitions, "../../www/docs/reference/env-vars.mdx") - File.mkdir_p!(path) - write_api_doc!(conns, path) - end - - def write_config_doc!(module, file_path) do - file = File.open!(file_path, [:write, :utf8]) - - w!(file, "---") - - w!( - file, - docusaurus_header( - title: "Environment Variables", - sidebar_position: 1 - ) - ) - - w!(file, "---") - w!(file, "") - - with {:ok, doc} <- Definition.fetch_doc(module) do - w!(file, doc) - end - - w!(file, "## Environment Variable Listing") - w!(file, "We recommend setting these in your Docker ENV file (`$HOME/.firezone/.env` by") - w!(file, "default). Required fields in **bold**.") - - keys = - Enum.flat_map(module.doc_sections(), fn - {header, description, keys} -> - w_env_vars!(file, module, header, description, keys) - keys - - {header, keys} -> - w_env_vars!(file, module, header, nil, keys) - keys - end) - - all_keys = module.configs() |> Enum.map(&elem(&1, 1)) - w_env_vars!(file, module, "Other", nil, all_keys -- keys) - end - - defp w_env_vars!(_file, _module, _header, _description, []), do: :ok - - defp w_env_vars!(file, module, header, description, keys) do - w!(file, "") - w!(file, "### #{header}") - if description, do: w!(file, description) - - w!(file, "") - w!(file, "| Env Key | Description | Format | Default |") - w!(file, "| ------ | --------------- | ------ | ------- |") - - for key <- keys do - with {:ok, doc} <- Definition.fetch_doc(module, key) do - {type, {resolve_opts, _validate_opts, _dump_opts, _debug_opts}} = - Definition.fetch_spec_and_opts!(module, key) - - default = Keyword.get(resolve_opts, :default) - required? = if Keyword.has_key?(resolve_opts, :default), do: false, else: true - - key = Domain.Config.Resolver.env_key(key) - key = if required?, do: "**#{key}**", else: key - - doc = doc_env(doc) - - {type, default} = type_and_default(type, default) - - w!(file, "| #{key} | #{doc} | #{type} | #{default} |") - end - end - end - - defp doc_env(doc) do - doc - |> String.trim() - |> String.replace("\n * `", "
- `") - |> String.replace("```json", "```") - |> String.replace("\n\n", "

") - |> String.replace("\n", " ") - end - - defp type_and_default(type, default) when is_function(default), - do: type_and_default(type, "generated") - - defp type_and_default(type, nil), - do: type_and_default(type, "") - - defp type_and_default(type, []), - do: type_and_default(type, "[]") - - defp type_and_default({:parameterized, {Ecto.Enum, opts}}, default) do - values = - opts.mappings - |> Keyword.keys() - |> Enum.map(&to_string/1) - |> Enum.map_join(", ", &"`#{&1}`") - - default = - default - |> Atom.to_string() - - {"One of #{values}", "`#{default}`"} - end - - defp type_and_default(Domain.Types.CIDR, default), - do: {"CIDR", default} - - defp type_and_default(Domain.Types.IP, default), - do: {"IP", default} - - defp type_and_default(Domain.Types.IPPort, default), - do: {"IP with port", default} - - defp type_and_default(:integer, default), - do: {"integer", default} - - defp type_and_default(:string, default), - do: {"string", default} - - defp type_and_default(:boolean, default), - do: {"boolean", default} - - defp type_and_default(:map, default), - do: {"JSON-encoded map", "`" <> Jason.encode!(default) <> "`"} - - defp type_and_default(:embed, default), - do: {"JSON-encoded map", "`" <> Jason.encode!(default) <> "`"} - - defp type_and_default({:one_of, types}, default) do - types = - types - |> Enum.map(&type_and_default(&1, default)) - |> Enum.map(&elem(&1, 0)) - |> Enum.map(&to_string/1) - |> Enum.map_join(", ", &"`#{&1}`") - - {"one of #{types}", default} - end - - defp type_and_default({:json_array, _}, default), - do: {"JSON-encoded list", "`" <> Jason.encode!(default) <> "`"} - - defp type_and_default({:array, separator, type}, default) do - {type, default} = type_and_default(type, default) - {"a list of #{type} separated by `#{separator}`", default} - end - - defp type_and_default(type, default) when not is_binary(default), - do: type_and_default(type, inspect(default)) - - defp type_and_default(type, default), - do: {inspect(type), "`" <> default <> "`"} - - defp write_api_doc!(conns, path) do - routes = Phoenix.Router.routes(List.first(conns).private.phoenix_router) - - conns - |> Enum.group_by(& &1.private.phoenix_controller) - |> Enum.map(fn {controller, conns} -> - {module_doc, module_assigns, function_docs} = fetch_module_docs!(controller) - - title = - Keyword.get_lazy(module_assigns, :title, fn -> - controller - |> to_string() - |> String.split(".") - |> List.last() - |> String.replace_trailing("Controller", "") - end) - - path = Path.join(path, "#{String.downcase(title)}.mdx") - file = File.open!(path, [:write, :utf8]) - - w!(file, "---") - w!(file, docusaurus_header(module_assigns)) - w!(file, "---") - w!(file, "\n") - w!(file, module_doc) - w!(file, "## API Documentation") - - conns - |> Enum.group_by(& &1.private.phoenix_action) - # We order actions nicely - |> Enum.sort_by(fn - {:index, _} -> 1 - {:show, _} -> 3 - {:create, _} -> 2 - {:update, _} -> 4 - {:delete, _} -> 5 - {_other, _} -> 1000 - end) - |> Enum.map(fn {action, conns} -> - {path, verb} = fetch_route!(routes, controller, action) - {function_doc, function_assigns} = get_function_docs(function_docs, action) - - title = maybe_wrap(function_assigns[:action], "#{verb} #{path}") - - w!(file, "### #{title}") - w!(file, "\n") - w!(file, function_doc) - - uri_params = build_uri_params(path) - - write_examples(file, conns, path, uri_params) - end) - end) - end - - defp maybe_wrap(nil, title), do: title - defp maybe_wrap(action_assign, title), do: "#{action_assign} [`#{title}`]" - - defp docusaurus_header(assigns) do - assigns - |> Enum.map_join("\n", fn {key, value} -> - "#{key}: #{value}" - end) - end - - defp fetch_route!(routes, controller, controller_action) do - %{path: path, verb: verb} = - Enum.find(routes, fn - %{plug: ^controller, plug_opts: ^controller_action} -> true - _other -> false - end) - - path = String.replace(path, ~r|:([^/]*)|, "{\\1}") - verb = verb |> to_string() |> String.upcase() - - {path, verb} - end - - defp fetch_module_docs!(controller) do - case Code.fetch_docs(controller) do - {:docs_v1, _, _, _, module_doc, %{api_doc: module_assigns}, function_docs} -> - {get_doc(module_doc), module_assigns, function_docs} - - {:error, :module_not_found} -> - raise "No module #{controller}" - end - end - - defp get_doc(md) when is_map(md), do: Map.get(md, "en") - defp get_doc(_md), do: nil - - defp get_function_docs(function_docs, function) do - function_docs - |> Enum.find(fn - {{:function, ^function, _}, _, _, _, _} -> true - {{:function, _function, _}, _, _, _, _} -> false - end) - |> case do - {_, _, _, :none, %{api_doc: function_assigns}} -> - {nil, function_assigns} - - {_, _, _, doc, %{api_doc: function_assigns}} -> - {get_doc(doc), function_assigns} - - {_, _, _, doc, _chunks} -> - {get_doc(doc), %{}} - - _other -> - {nil, %{}} - end - end - - defp build_uri_params(path) do - Regex.scan(~r/{([^}]*)}/, path) - |> Enum.map(fn [_, param] -> - param - end) - end - - defp write_examples(file, conns, path, uri_params) do - conns - |> Enum.sort_by(& &1.status) - |> Enum.each(fn conn -> - example_description = conn.assigns.bureaucrat_opts[:example_description] || "Example" - w!(file, "#### #{example_description}") - - w_req_uri_params!(file, conn, uri_params) - - w!( - file, - """ - ```bash - $ curl -i \\ - -X #{conn.method} "https://{firezone_host}#{path}" \\ - -H 'Content-Type: application/json' \\ - """ - |> String.trim_trailing() - ) - - maybe_w!(file, b_req_headers(conn)) - maybe_w!(file, b_req_body(conn.body_params)) - - w!(file, "") - - w!(file, "HTTP/1.1 #{conn.status}") - maybe_w!(file, b_resp_headers(conn)) - maybe_w!(file, b_resp_body(conn.resp_body)) - w!(file, "```") - end) - end - - defp w_req_uri_params!(_file, _conn, []), do: :ok - - defp w_req_uri_params!(file, conn, params) do - w!(file, "**URI Parameters:**\n") - - Enum.each(params, fn param -> - w!(file, i(1, "- `#{param}`: `#{conn.params[param]}`")) - end) - end - - defp b_req_headers(conn) do - for {key, value} <- conn.req_headers, key in @keep_req_headers do - case {key, value} do - {"authorization", "bearer " <> _} -> - i(1, "-H 'Authorization: Bearer {api_token}' \\") - - {key, value} -> - i(1, "-H '#{camelize_header_key(key)}: #{value}' \\") - end - end - |> Enum.join("\n") - end - - defp b_req_body(params) when params == %{}, do: "" - - defp b_req_body(params) do - i(1, "--data-binary @- << EOF\n#{Jason.encode!(params, pretty: true)}\nEOF") - end - - defp b_resp_headers(conn) do - for {key, value} <- conn.resp_headers, key in @keep_resp_headers do - "#{camelize_header_key(key)}: #{value}" - end - |> Enum.join("\n") - end - - defp b_resp_body(resp_body) do - case Jason.decode(resp_body) do - {:ok, map} -> - "\n" <> Jason.encode!(map, pretty: true) - - _error -> - resp_body - end - end - - defp camelize_header_key(key) do - key - |> String.split("-") - |> Enum.map_join("-", fn - <> -> String.upcase(<>) <> rest - other -> other - end) - end - - defp i(level, text) do - String.duplicate(" ", level) <> text - end - - defp maybe_w!(_file, ""), do: :ok - defp maybe_w!(_file, nil), do: :ok - defp maybe_w!(file, text), do: w!(file, text) - - defp w!(file, content) do - IO.puts(file, content) - end -end diff --git a/elixir/apps/web/test/test_helper.exs b/elixir/apps/web/test/test_helper.exs index 866707d21..578427f52 100644 --- a/elixir/apps/web/test/test_helper.exs +++ b/elixir/apps/web/test/test_helper.exs @@ -1,12 +1,7 @@ # Delete screenshots from previous acceptance test executions Path.join(File.cwd!(), "screenshots") |> File.rm_rf!() -Bureaucrat.start( - writer: Web.Documentation.Generator, - default_path: "../../www/docs/reference/rest-api" -) - Finch.start_link(name: TestPool) Ecto.Adapters.SQL.Sandbox.mode(Domain.Repo, :manual) -ExUnit.start(formatters: [ExUnit.CLIFormatter, JUnitFormatter, Bureaucrat.Formatter]) +ExUnit.start(formatters: [ExUnit.CLIFormatter, JUnitFormatter]) diff --git a/elixir/config/test.exs b/elixir/config/test.exs index 3d29ce99b..ac7ec17f9 100644 --- a/elixir/config/test.exs +++ b/elixir/config/test.exs @@ -101,8 +101,6 @@ config :logger, level: :warning config :argon2_elixir, t_cost: 1, m_cost: 8 -config :bureaucrat, :json_library, Jason - config :wallaby, driver: Wallaby.Chrome, screenshot_on_failure: true, diff --git a/elixir/mix.lock b/elixir/mix.lock index 14f58bd18..1bd080b22 100644 --- a/elixir/mix.lock +++ b/elixir/mix.lock @@ -3,7 +3,6 @@ "argon2_elixir": {:hex, :argon2_elixir, "4.1.3", "4f28318286f89453364d7fbb53e03d4563fd7ed2438a60237eba5e426e97785f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7c295b8d8e0eaf6f43641698f962526cdf87c6feb7d14bd21e599271b510608c"}, "bandit": {:hex, :bandit, "1.7.0", "d1564f30553c97d3e25f9623144bb8df11f3787a26733f00b21699a128105c0c", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3e2f7a98c7a11f48d9d8c037f7177cd39778e74d55c7af06fe6227c742a8168a"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "bureaucrat": {:hex, :bureaucrat, "0.2.10", "b0de157dad540e40007b663b683f716ced21f85ff0591093aadb209ad0d967e1", [:mix], [{:inflex, ">= 1.10.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.0.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "bc7e5162b911c29c8ebefee87a2c16fbf13821a58f448a8fd024eb6c17fae15c"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"}, "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, @@ -45,7 +44,6 @@ "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, "junit_formatter": {:hex, :junit_formatter, "3.4.0", "d0e8db6c34dab6d3c4154c3b46b21540db1109ae709d6cf99ba7e7a2ce4b1ac2", [:mix], [], "hexpm", "bb36e2ae83f1ced6ab931c4ce51dd3dbef1ef61bb4932412e173b0cfa259dacd"},