diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs index e9f5981..fe51488 100644 --- a/.dialyzer_ignore.exs +++ b/.dialyzer_ignore.exs @@ -1,9 +1 @@ -[ - # The following four ignore rules are due to an upstream type problem in - # HTTPoison 3.0. Once https://github.com/edgurgel/httpoison/pull/511 has been - # merged and released, we should be able to delete these rules. - {"deps/httpoison/lib/httpoison/base.ex", :callback_arg_type_mismatch}, - {"deps/httpoison/lib/httpoison/base.ex", :callback_type_mismatch}, - {"deps/httpoison/lib/httpoison/base.ex", :pattern_match_cov}, - {"lib/config_cat/api.ex", :invalid_contract} -] +[] diff --git a/lib/config_cat/api.ex b/lib/config_cat/api.ex index 452ab85..47af091 100644 --- a/lib/config_cat/api.ex +++ b/lib/config_cat/api.ex @@ -1,10 +1,66 @@ defmodule ConfigCat.API do @moduledoc false + @type option :: + {:connect_timeout_milliseconds, non_neg_integer()} + | {:http_proxy, String.t() | nil} + | {:read_timeout_milliseconds, non_neg_integer()} - use HTTPoison.Base + @callback get(binary(), [{binary(), binary()}], [option]) :: {:ok, Req.Request.t()} | {:error, Exception.t()} +end + +defmodule ConfigCat.API.ReqAPI do + @moduledoc false + @behaviour ConfigCat.API + + alias Req.Request + + @impl ConfigCat.API + def get(url, headers, options) do + req = build_request(url, headers, options) + Req.get(req) + end + + @doc false + @spec build_request(binary(), [{binary(), binary()}], Keyword.t()) :: Req.Request.t() + def build_request(url, headers, options) do + options = Keyword.validate!(options, [:connect_timeout_milliseconds, :http_proxy, :read_timeout_milliseconds]) + req = Req.new(decode_body: false, headers: headers, url: url) + + Enum.reduce(options, req, &apply_option/2) + end + + defp apply_option({:connect_timeout_milliseconds, timeout}, req) do + merge_connect_option(req, :timeout, timeout) + end + + defp apply_option({:http_proxy, nil}, req), do: req + + defp apply_option({:http_proxy, url}, req) do + uri = URI.parse(url) + proxy = {String.to_existing_atom(uri.scheme), uri.host, uri.port, []} + + req + |> merge_connect_option(:proxy, proxy) + |> add_userinfo(uri.userinfo) + end + + defp apply_option({:read_timeout_milliseconds, timeout}, req) do + Request.put_option(req, :receive_timeout, timeout) + end + + defp add_userinfo(req, nil), do: req + + defp add_userinfo(req, userinfo) do + proxy_headers = {"proxy-authorization", "Basic " <> Base.encode64(userinfo)} + merge_connect_option(req, :proxy_headers, proxy_headers) + end + + defp merge_connect_option(req, key, value) do + new_connect_options = + req + |> Request.get_option(:connect_options, []) + |> Keyword.put(key, value) - @impl HTTPoison.Base - def process_request_headers(headers) do - [{"Accept", "application/json"} | headers] + Request.put_option(req, :connect_options, new_connect_options) end end diff --git a/lib/config_cat/config_fetcher.ex b/lib/config_cat/config_fetcher.ex index c47bd6c..83fcb2a 100644 --- a/lib/config_cat/config_fetcher.ex +++ b/lib/config_cat/config_fetcher.ex @@ -2,7 +2,6 @@ defmodule ConfigCat.ConfigFetcher do @moduledoc false alias ConfigCat.ConfigEntry - alias HTTPoison.Response defmodule FetchError do @moduledoc false @@ -43,7 +42,7 @@ defmodule ConfigCat.CacheControlConfigFetcher do alias ConfigCat.ConfigEntry alias ConfigCat.ConfigFetcher alias ConfigCat.ConfigFetcher.FetchError - alias HTTPoison.Response + alias Req.Response require ConfigCat.ConfigCatLogger, as: ConfigCatLogger require ConfigCat.Constants, as: Constants @@ -54,7 +53,7 @@ defmodule ConfigCat.CacheControlConfigFetcher do use TypedStruct typedstruct enforce: true do - field :api, module(), default: ConfigCat.API + field :api, module(), default: ConfigCat.API.ReqAPI field :base_url, String.t() field :callers, [GenServer.from()], default: [] field :connect_timeout_milliseconds, non_neg_integer(), default: 8_000 @@ -209,26 +208,21 @@ defmodule ConfigCat.CacheControlConfigFetcher do end defp http_options(%State{} = state) do - options = - Map.take(state, [:http_proxy, :connect_timeout_milliseconds, :read_timeout_milliseconds]) - - Enum.map(options, fn - {:http_proxy, value} -> {:proxy, value} - {:connect_timeout_milliseconds, value} -> {:timeout, value} - {:read_timeout_milliseconds, value} -> {:recv_timeout, value} - end) + state + |> Map.take([:connect_timeout_milliseconds, :http_proxy, :read_timeout_milliseconds]) + |> Keyword.new() end # This function is slightly complex, but still reasonably understandable. # Breaking it up doesn't seem like it will help much. # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity - defp handle_response(%Response{status_code: code, body: raw_config, headers: headers}, %State{} = state, etag) + defp handle_response(%Response{status: code, body: raw_config} = response, %State{} = state, etag) when code >= 200 and code < 300 do - ConfigCatLogger.debug("ConfigCat configuration json fetch response code: #{code} Cached: #{extract_etag(headers)}") + ConfigCatLogger.debug("ConfigCat configuration json fetch response code: #{code} Cached: #{extract_etag(response)}") with {:ok, decoded_config} <- Jason.decode(raw_config), config = Config.inline_salt_and_segments(decoded_config), - new_etag = extract_etag(headers), + new_etag = extract_etag(response), %{base_url: new_base_url, custom_endpoint?: custom_endpoint?, redirects: redirects} <- state do preferences = Config.preferences(config) @@ -282,11 +276,11 @@ defmodule ConfigCat.CacheControlConfigFetcher do end end - defp handle_response(%Response{status_code: 304}, %State{} = state, _etag) do + defp handle_response(%Response{status: 304}, %State{} = state, _etag) do {:ok, :unchanged, state} end - defp handle_response(%Response{status_code: status} = response, %State{} = state, _etag) when status in [403, 404] do + defp handle_response(%Response{status: status} = response, %State{} = state, _etag) when status in [403, 404] do ConfigCatLogger.error( "Your SDK Key seems to be wrong. You can find the valid SDKKey at https://app.configcat.com/sdkkey. Received unexpected response: #{inspect(response)}", event_id: 1100 @@ -308,7 +302,7 @@ defmodule ConfigCat.CacheControlConfigFetcher do {:error, error, state} end - defp handle_error({:error, %HTTPoison.Error{reason: :checkout_timeout} = error}, %State{} = state) do + defp handle_error({:error, %Req.TransportError{reason: :timeout} = error}, %State{} = state) do ConfigCatLogger.error( "Request timed out while trying to fetch config JSON. Timeout values: [connect: #{state.connect_timeout_milliseconds}ms, read: #{state.read_timeout_milliseconds}ms]", event_id: 1102 @@ -329,10 +323,10 @@ defmodule ConfigCat.CacheControlConfigFetcher do FetchError.exception(reason: error, transient?: true) end - defp extract_etag(headers) do - case Enum.find(headers, fn {key, _value} -> String.downcase(key) == "etag" end) do - nil -> nil - {_key, value} -> value + defp extract_etag(%Response{} = response) do + case Response.get_header(response, "etag") do + [] -> nil + [etag] -> etag end end end diff --git a/mix.exs b/mix.exs index 696617f..80e5ba5 100644 --- a/mix.exs +++ b/mix.exs @@ -82,10 +82,10 @@ defmodule ConfigCat.MixProject do {:elixir_uuid, "~> 1.2"}, {:ex_doc, "~> 0.31.0", only: :dev, runtime: false}, {:excoveralls, "~> 0.18.0", only: :test}, - {:httpoison, "~> 2.0 or ~> 3.0"}, {:jason, "~> 1.2"}, {:mix_test_interactive, "~> 5.1", only: :dev, runtime: false}, {:mox, "~> 1.1", only: :test}, + {:req, "~> 0.6.2"}, {:styler, "~> 0.11", only: [:dev, :test], runtime: false}, {:typed_struct, "~> 0.3.0"}, {:tz, "~> 0.26.5", only: :test} diff --git a/mix.lock b/mix.lock index 3d06a53..cc990c4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,5 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "certifi": {:hex, :certifi, "2.17.0", "835748414307e15e05b17d0e518190228ce648b08d569a5cc93a85a40f3e5c9b", [:rebar3], [], "hexpm", "8122798a17f0293c80daada25d0f81c7f4d708c73fef782c7c9b1950e26e4d21"}, "credo": {:hex, :credo, "1.7.19", "cc52129665fc7c15143d47838fda0f9cd6dac9ceced7bf4da6f85fcbfe64b12a", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2d8bc95d5a7bb99dd2613621d4f08c6a3575c3fd4b62e6a2b48a100352a557b8"}, "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, @@ -9,26 +8,23 @@ "ex_doc": {:hex, :ex_doc, "0.31.2", "8b06d0a5ac69e1a54df35519c951f1f44a7b7ca9a5bb7a260cd8a174d6322ece", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "317346c14febaba9ca40fd97b5b5919f7751fb85d399cc8e7e8872049f37e0af"}, "excoveralls": {:hex, :excoveralls, "0.18.2", "86efd87a0676a3198ff50b8c77620ea2f445e7d414afa9ec6c4ba84c9f8bdcc2", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "230262c418f0de64077626a498bd4fdf1126d5c2559bb0e6b43deac3005225a4"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, - "h2": {:hex, :h2, "0.10.2", "ea0146b9c8b5f3b5de16045765f5684db38ef1e66f1c60444890948cb1003e47", [:rebar3], [], "hexpm", "497a899f338b42e6a0b292524e635b0ce6f9379fa39395c8e38d06351cd9b9cf"}, - "hackney": {:hex, :hackney, "4.4.3", "644c79b2eac605724e55fca651259c0560c4eea44b4c60b64baf5a2258b99376", [:rebar3], [{:certifi, "~> 2.17.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:h2, "~> 0.10.1", [hex: :h2, repo: "hexpm", optional: false]}, {:idna, "~> 7.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.2", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:quic, "~> 1.6.5", [hex: :quic, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:webtransport, "~> 0.4.1", [hex: :webtransport, repo: "hexpm", optional: false]}], "hexpm", "db6bbd96ff8562e6398c0f468ae63fc516857a0a12ecf232b3cb8e34fd167018"}, - "httpoison": {:hex, :httpoison, "3.0.0", "8566a933bb9175236d1ec335978445b67cd1f5b5d3ead6ca4b80be469d41f5d9", [:mix], [{:hackney, "~> 4.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "9130197b7658901c493d6fcfb842fb9676300fa8a6c8ed058c8889cf1a77f3c2"}, - "idna": {:hex, :idna, "7.1.0", "1067a13043538129602d2f2ce6899d8713125c7d19734aa557ce2e3ea55bd4f1", [:rebar3], [], "hexpm", "6ae959a025bf36df61a8cab8508d9654891b5426a84c44d82deaffd6ddf8c71f"}, + "finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"}, + "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mimerl": {:hex, :mimerl, "1.5.0", "f35aca6f23242339b3666e0ac0702379e362b469d0aea167f6cc713547e777ed", [:rebar3], [], "hexpm", "db648ce065bae14ea84ca8b5dd123f42f49417cef693541110bf6f9e9be9ecc4"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.9.2", "6e89e698d69cc29be001afd02c2cf4bae2d0994efe69a2ced7aab440d71584f9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d8e952b432fdac2321f570d29a68b9eb2664dc80a7a2ef9464531a5425abf222"}, "mix_test_interactive": {:hex, :mix_test_interactive, "5.1.0", "a2edd66806f06f6a0dbb92f023126a7e4ecdf02f7d34e3bb16f76886b79bed43", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:process_tree, ">= 0.1.3", [hex: :process_tree, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "0df8ab74c176040acd78939e92a5328775bf9f414a0f0472372cce852ee548cf"}, "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "parse_trans": {:hex, :parse_trans, "3.4.2", "c352ddc1a0d5e54f9b1654d45f9c432eef76f9cea371c55ddff769ef688fdb74", [:rebar3], [], "hexpm", "4c25347de3b7c35732d32e69ab43d1ceee0beae3f3b3ade1b59cbd3dd224d9ca"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "process_tree": {:hex, :process_tree, "0.3.0", "0eb58ec68f3d22a4f36040b0374469464c95fae5465c011b55bea01649b0bd70", [:mix], [], "hexpm", "6cb3b7be9c7d74b28a9f6e0f03115d5953e6bbda44be2b6fd9e667020870eb86"}, - "quic": {:hex, :quic, "1.6.5", "28b7d49c1732b2de3861701bb03ae7798537240552370ac49f0d139d2ea8f621", [:rebar3], [], "hexpm", "de1a88972c33201a50d1a17c8c4a14528bd1d8f25ef705897d680ae312d0aa78"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "req": {:hex, :req, "0.6.2", "b9b2024f35bcf60a92cc8cad2eaaf9d4e7aace463ff74be1afe5986830184413", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cc9cd30a2ddd04989929b887178e1610c940456d962c6c3a52df6146d2eef9bf"}, "styler": {:hex, :styler, "0.11.9", "2595393b94e660cd6e8b582876337cc50ff047d184ccbed42fdad2bfd5d78af5", [:mix], [], "hexpm", "8b7806ba1fdc94d0a75127c56875f91db89b75117fcc67572661010c13e1f259"}, + "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, "tz": {:hex, :tz, "0.26.6", "4d46178dd5bc4d2c1e78c9affcc3fd46764e29cd2a148c06666edb83cb18629f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "9ca97ea48b412f2404740867f6c321ee8ce112602035bb79b0b90c9c03174652"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, - "webtransport": {:hex, :webtransport, "0.4.1", "60ef6cd99282b5964c8172d674519239e31e357d934bde028bec70da34636fe5", [:rebar3], [{:h2, "~> 0.10.1", [hex: :h2, repo: "hexpm", optional: false]}, {:quic, "~> 1.6.5", [hex: :quic, repo: "hexpm", optional: false]}], "hexpm", "006e4e52a8f03b69201d4637c85b424a4ddccc1909f32c5742fed8d495a75174"}, } diff --git a/samples/multi/mix.lock b/samples/multi/mix.lock index 5c64cf5..dd24a82 100644 --- a/samples/multi/mix.lock +++ b/samples/multi/mix.lock @@ -1,14 +1,13 @@ %{ - "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, - "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, - "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, - "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"}, + "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.9.2", "6e89e698d69cc29be001afd02c2cf4bae2d0994efe69a2ced7aab440d71584f9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d8e952b432fdac2321f570d29a68b9eb2664dc80a7a2ef9464531a5425abf222"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "req": {:hex, :req, "0.6.2", "b9b2024f35bcf60a92cc8cad2eaaf9d4e7aace463ff74be1afe5986830184413", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cc9cd30a2ddd04989929b887178e1610c940456d962c6c3a52df6146d2eef9bf"}, + "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/samples/simple/mix.lock b/samples/simple/mix.lock index 5c64cf5..dd24a82 100644 --- a/samples/simple/mix.lock +++ b/samples/simple/mix.lock @@ -1,14 +1,13 @@ %{ - "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, - "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, - "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, - "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"}, + "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.9.2", "6e89e698d69cc29be001afd02c2cf4bae2d0994efe69a2ced7aab440d71584f9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d8e952b432fdac2321f570d29a68b9eb2664dc80a7a2ef9464531a5425abf222"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "req": {:hex, :req, "0.6.2", "b9b2024f35bcf60a92cc8cad2eaaf9d4e7aace463ff74be1afe5986830184413", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cc9cd30a2ddd04989929b887178e1610c940456d962c6c3a52df6146d2eef9bf"}, + "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/config_cat/api_test.exs b/test/config_cat/api_test.exs new file mode 100644 index 0000000..5fa9acd --- /dev/null +++ b/test/config_cat/api_test.exs @@ -0,0 +1,90 @@ +defmodule ConfigCat.API.ReqAPITest do + use ExUnit.Case, async: false + + alias ConfigCat.API.ReqAPI + alias Req.Request + + @url "https://some.example.com" + + describe "request building" do + test "includes URL" do + request = ReqAPI.build_request(@url, [], []) + + assert URI.to_string(request.url) == @url + end + + test "does not automatically decode the response body" do + request = ReqAPI.build_request(@url, [], []) + assert Request.get_option(request, :decode_body) == false + end + + test "lowercases and includes provided headers" do + user_agent = "Some-User-Agent" + headers = [{"User-Agent", user_agent}] + request = ReqAPI.build_request(@url, headers, []) + + assert Request.get_header(request, "user-agent") == [user_agent] + end + + test "handles read timeout option" do + timeout = 10 + request = ReqAPI.build_request(@url, [], read_timeout_milliseconds: timeout) + + assert Request.get_option(request, :receive_timeout) == timeout + end + + test "handles connection timeout option" do + timeout = 15 + request = ReqAPI.build_request(@url, [], connect_timeout_milliseconds: timeout) + + connect_options = Request.get_option(request, :connect_options, []) + assert connect_options[:timeout] == timeout + end + + test "handles simple proxy" do + proxy_url = "https://example.com" + request = ReqAPI.build_request(@url, [], http_proxy: proxy_url) + + connect_options = Request.get_option(request, :connect_options, []) + assert connect_options[:proxy] == {:https, "example.com", 443, []} + end + + test "handles proxy with custom port" do + proxy_url = "https://example.com:1234" + request = ReqAPI.build_request(@url, [], http_proxy: proxy_url) + + connect_options = Request.get_option(request, :connect_options, []) + assert connect_options[:proxy] == {:https, "example.com", 1234, []} + end + + test "handles proxy with username/password" do + proxy_url = "https://user:pass@example.com" + request = ReqAPI.build_request(@url, [], http_proxy: proxy_url) + + connect_options = Request.get_option(request, :connect_options, []) + assert connect_options[:proxy] == {:https, "example.com", 443, []} + assert {"proxy-authorization", "Basic " <> encoded_userinfo} = connect_options[:proxy_headers] + assert Base.decode64!(encoded_userinfo) == "user:pass" + end + + test "properly merges options" do + receive_timeout = 8 + timeout = 12 + proxy_url = "https://user:pass@example.com:1234" + + request = + ReqAPI.build_request(@url, [], + connect_timeout_milliseconds: timeout, + http_proxy: proxy_url, + read_timeout_milliseconds: receive_timeout + ) + + connect_options = Request.get_option(request, :connect_options, []) + assert Request.get_option(request, :receive_timeout) == receive_timeout + assert connect_options[:timeout] == timeout + assert connect_options[:proxy] == {:https, "example.com", 1234, []} + assert {"proxy-authorization", "Basic " <> encoded_userinfo} = connect_options[:proxy_headers] + assert Base.decode64!(encoded_userinfo) == "user:pass" + end + end +end diff --git a/test/config_cat/config_fetcher_test.exs b/test/config_cat/config_fetcher_test.exs index f33ba03..c56f5e7 100644 --- a/test/config_cat/config_fetcher_test.exs +++ b/test/config_cat/config_fetcher_test.exs @@ -9,14 +9,14 @@ defmodule ConfigCat.ConfigFetcherTest do alias ConfigCat.FetchTime alias ConfigCat.Hooks alias ConfigCat.MockAPI - alias HTTPoison.Response + alias Req.Response require ConfigCat.Constants, as: Constants setup :verify_on_exit! @config %{"key" => "value"} - @etag "ETAG" + @etag "etag" @mode "m" @raw_config Jason.encode!(@config) @sdk_key "SDK_KEY" @@ -42,7 +42,12 @@ defmodule ConfigCat.ConfigFetcherTest do url = global_config_url() stub(MockAPI, :get, fn ^url, _headers, _options -> - {:ok, %Response{status_code: 200, body: @raw_config, headers: [{"ETag", @etag}]}} + response = + [body: @raw_config, status: 200] + |> Response.new() + |> Response.put_header("etag", @etag) + + {:ok, response} end) before = FetchTime.now_ms() @@ -65,7 +70,13 @@ defmodule ConfigCat.ConfigFetcherTest do expect(MockAPI, :get, 1, fn ^url, _headers, _options -> Process.sleep(50) - {:ok, %Response{status_code: 200, body: @raw_config, headers: [{"ETag", @etag}]}} + + response = + [body: @raw_config, status: 200] + |> Response.new() + |> Response.put_header("etag", @etag) + + {:ok, response} end) results = @@ -86,7 +97,7 @@ defmodule ConfigCat.ConfigFetcherTest do test "user agent header that includes the fetch mode" do {:ok, fetcher} = start_fetcher(@fetcher_options) - response = %Response{status_code: 200, body: @raw_config} + response = Response.new(body: @raw_config, status: 200) stub(MockAPI, :get, fn _url, headers, _options -> assert_user_agent_matches(headers, ~r"^ConfigCat-Elixir/#{@mode}-") @@ -100,23 +111,22 @@ defmodule ConfigCat.ConfigFetcherTest do test "sends proper cache control header on later requests" do {:ok, fetcher} = start_fetcher(@fetcher_options) - initial_response = %Response{ - status_code: 200, - body: @raw_config, - headers: [{"ETag", @etag}] - } + initial_response = + [body: @raw_config, status: 200] + |> Response.new() + |> Response.put_header("etag", @etag) stub(MockAPI, :get, fn _url, headers, _options -> - assert List.keyfind(headers, "ETag", 0) == nil + assert List.keyfind(headers, "etag", 0) == nil {:ok, initial_response} end) {:ok, _} = ConfigFetcher.fetch(fetcher, nil) - not_modified_response = %Response{ - status_code: 304, - headers: [{"ETag", @etag}] - } + not_modified_response = + [status: 304] + |> Response.new() + |> Response.put_header("etag", @etag) expect(MockAPI, :get, fn _url, headers, _options -> assert {"If-None-Match", @etag} = List.keyfind(headers, "If-None-Match", 0) @@ -129,10 +139,10 @@ defmodule ConfigCat.ConfigFetcherTest do test "returns unchanged response when server responds that the config hasn't changed" do {:ok, fetcher} = start_fetcher(@fetcher_options) - response = %Response{ - status_code: 304, - headers: [{"ETag", @etag}] - } + response = + [status: 304] + |> Response.new() + |> Response.put_header("etag", @etag) stub(MockAPI, :get, fn _url, _headers, _options -> {:ok, response} end) assert {:ok, :unchanged} = ConfigFetcher.fetch(fetcher, @etag) @@ -141,10 +151,10 @@ defmodule ConfigCat.ConfigFetcherTest do test "returns unchanged response (with lowercase 'etag') when server responds that the config hasn't changed" do {:ok, fetcher} = start_fetcher(@fetcher_options) - response = %Response{ - status_code: 304, - headers: [{"etag", @etag}] - } + response = + [status: 304] + |> Response.new() + |> Response.put_header("etag", @etag) stub(MockAPI, :get, fn _url, _headers, _options -> {:ok, response} end) assert {:ok, :unchanged} = ConfigFetcher.fetch(fetcher, @etag) @@ -154,7 +164,7 @@ defmodule ConfigCat.ConfigFetcherTest do test "returns error for non-200 response from ConfigCat" do {:ok, fetcher} = start_fetcher(@fetcher_options) - response = %Response{status_code: 503} + response = Response.new(status: 503) stub(MockAPI, :get, fn _url, _headers, _options -> {:ok, response} end) assert {:error, %FetchError{reason: ^response}} = ConfigFetcher.fetch(fetcher, nil) @@ -164,7 +174,7 @@ defmodule ConfigCat.ConfigFetcherTest do test "returns error for error response from ConfigCat" do {:ok, fetcher} = start_fetcher(@fetcher_options) - error = %HTTPoison.Error{reason: "failed"} + error = %Req.TransportError{reason: "failed"} stub(MockAPI, :get, fn _url, _headers, _options -> {:error, error} end) assert {:error, %FetchError{reason: ^error}} = ConfigFetcher.fetch(fetcher, nil) @@ -177,18 +187,18 @@ defmodule ConfigCat.ConfigFetcherTest do url = config_url(base_url, @sdk_key) - expect(MockAPI, :get, fn ^url, _headers, _options -> {:ok, %Response{status_code: 200, body: @raw_config}} end) + expect(MockAPI, :get, fn ^url, _headers, _options -> {:ok, Response.new(body: @raw_config, status: 200)} end) {:ok, _} = ConfigFetcher.fetch(fetcher, nil) end test "uses default timeouts if none provided" do {:ok, fetcher} = start_fetcher(@fetcher_options) - response = %Response{status_code: 200, body: @raw_config} + response = Response.new(body: @raw_config, status: 200) expect(MockAPI, :get, fn _url, _headers, options -> - assert Keyword.get(options, :recv_timeout) == 5000 - assert Keyword.get(options, :timeout) == 8000 + assert Keyword.get(options, :connect_timeout_milliseconds) == 8000 + assert Keyword.get(options, :read_timeout_milliseconds) == 5000 {:ok, response} end) @@ -205,11 +215,11 @@ defmodule ConfigCat.ConfigFetcherTest do read_timeout_milliseconds: read_timeout ) - response = %Response{status_code: 200, body: @raw_config} + response = Response.new(body: @raw_config, status: 200) expect(MockAPI, :get, fn _url, _headers, options -> - assert Keyword.get(options, :recv_timeout) == read_timeout - assert Keyword.get(options, :timeout) == connect_timeout + assert Keyword.get(options, :connect_timeout_milliseconds) == connect_timeout + assert Keyword.get(options, :read_timeout_milliseconds) == read_timeout {:ok, response} end) @@ -220,10 +230,10 @@ defmodule ConfigCat.ConfigFetcherTest do proxy = "https://PROXY" {:ok, fetcher} = start_fetcher(@fetcher_options, http_proxy: proxy) - response = %Response{status_code: 200, body: @raw_config} + response = Response.new(body: @raw_config, status: 200) expect(MockAPI, :get, fn _url, _headers, options -> - assert Keyword.get(options, :proxy) == proxy + assert Keyword.get(options, :http_proxy) == proxy {:ok, response} end) diff --git a/test/config_cat/data_governance_test.exs b/test/config_cat/data_governance_test.exs index 8e7372d..4545739 100644 --- a/test/config_cat/data_governance_test.exs +++ b/test/config_cat/data_governance_test.exs @@ -9,7 +9,7 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do alias ConfigCat.ConfigEntry alias ConfigCat.Hooks alias ConfigCat.MockAPI - alias HTTPoison.Response + alias Req.Response require ConfigCat.Constants, as: Constants require ConfigCat.RedirectMode, as: RedirectMode @@ -50,13 +50,13 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 2, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: raw_config}} + {:ok, Response.new(body: raw_config, status: 200)} end) |> expect(:get, 0, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: raw_config}} + {:ok, Response.new(body: raw_config, status: 200)} end) |> expect(:get, 0, fn ^redirect_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: raw_config}} + {:ok, Response.new(body: raw_config, status: 200)} end) assert {:ok, %ConfigEntry{config: ^config}} = ConfigFetcher.fetch(fetcher, nil) @@ -75,13 +75,13 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 0, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: raw_config}} + {:ok, Response.new(body: raw_config, status: 200)} end) |> expect(:get, 2, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: raw_config}} + {:ok, Response.new(body: raw_config, status: 200)} end) |> expect(:get, 0, fn ^redirect_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: raw_config}} + {:ok, Response.new(body: raw_config, status: 200)} end) assert {:ok, %ConfigEntry{config: ^config}} = ConfigFetcher.fetch(fetcher, nil) @@ -99,10 +99,10 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 1, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_eu}} + {:ok, Response.new(body: config_to_eu, status: 200)} end) |> expect(:get, 2, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_eu}} + {:ok, Response.new(body: config_eu, status: 200)} end) assert {:ok, _} = ConfigFetcher.fetch(fetcher, nil) @@ -120,10 +120,10 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 0, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_eu}} + {:ok, Response.new(body: config_to_eu, status: 200)} end) |> expect(:get, 2, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_eu}} + {:ok, Response.new(body: config_eu, status: 200)} end) assert {:ok, _} = ConfigFetcher.fetch(fetcher, nil) @@ -145,13 +145,13 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 2, fn ^custom_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_global}} + {:ok, Response.new(body: config_to_global, status: 200)} end) |> expect(:get, 0, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: %{}}} + {:ok, Response.new(body: %{}, status: 200)} end) |> expect(:get, 0, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: %{}}} + {:ok, Response.new(body: %{}, status: 200)} end) assert {:ok, _} = ConfigFetcher.fetch(fetcher, nil) @@ -173,13 +173,13 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 2, fn ^custom_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_eu}} + {:ok, Response.new(body: config_to_eu, status: 200)} end) |> expect(:get, 0, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: %{}}} + {:ok, Response.new(body: %{}, status: 200)} end) |> expect(:get, 0, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: %{}}} + {:ok, Response.new(body: %{}, status: 200)} end) assert {:ok, _} = ConfigFetcher.fetch(fetcher, nil) @@ -197,10 +197,10 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 1, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_forced}} + {:ok, Response.new(body: config_to_forced, status: 200)} end) |> expect(:get, 2, fn ^forced_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: "{}"}} + {:ok, Response.new(body: "{}", status: 200)} end) |> expect(:get, 0, fn ^eu_url, _headers, _options -> :not_called @@ -226,16 +226,16 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 0, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: %{}}} + {:ok, Response.new(body: %{}, status: 200)} end) |> expect(:get, 0, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: %{}}} + {:ok, Response.new(body: %{}, status: 200)} end) |> expect(:get, 1, fn ^custom_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_forced}} + {:ok, Response.new(body: config_to_forced, status: 200)} end) |> expect(:get, 3, fn ^forced_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_forced}} + {:ok, Response.new(body: config_to_forced, status: 200)} end) assert {:ok, _} = ConfigFetcher.fetch(fetcher, nil) @@ -253,20 +253,20 @@ defmodule ConfigCat.ConfigFetcher.DataGovernanceTest do MockAPI |> expect(:get, 1, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_eu}} + {:ok, Response.new(body: config_to_eu, status: 200)} end) |> expect(:get, 1, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_global}} + {:ok, Response.new(body: config_to_global, status: 200)} end) assert {:ok, _} = ConfigFetcher.fetch(fetcher, nil) MockAPI |> expect(:get, 1, fn ^eu_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_global}} + {:ok, Response.new(body: config_to_global, status: 200)} end) |> expect(:get, 1, fn ^global_url, _headers, _options -> - {:ok, %Response{status_code: 200, body: config_to_eu}} + {:ok, Response.new(body: config_to_eu, status: 200)} end) assert {:ok, _} = ConfigFetcher.fetch(fetcher, nil) diff --git a/test/integration_test.exs b/test/integration_test.exs index a7124fd..465ef55 100644 --- a/test/integration_test.exs +++ b/test/integration_test.exs @@ -165,6 +165,36 @@ defmodule ConfigCat.IntegrationTest do "default value" end + @tag capture_log: true + @tag skip: + unless( + System.get_env("CONFIGCAT_RUN_PROXY_TEST") in ["1", "true", "TRUE"], + do: "set CONFIGCAT_RUN_PROXY_TEST=true to enable proxy integration test" + ) + test "fetches config through proxy" do + # Run a local Squid proxy for this test: + # docker run -d --name squid-container -e TZ=UTC -p 3128:3128 ubuntu/squid:5.2-22.04_beta + # + # Verify the proxy: + # curl --proxy localhost:3128 https://cdn-global.configcat.com/configuration-files/PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A/config_v6.json + # + # Run the test: + # CONFIGCAT_RUN_PROXY_TEST=true mix test --no-deps-check test/integration_test.exs + proxy_sdk_key = "PKDVCLf-Hq-h-kCzMp-L7Q/psuH7BGHoUmdONrzzUOY7A" + proxy_url = System.get_env("CONFIGCAT_PROXY_URL") || "http://localhost:3128" + + {:ok, client} = + start( + proxy_sdk_key, + cache_policy: CachePolicy.manual(), + http_proxy: proxy_url + ) + + :ok = ConfigCat.force_refresh(client: client) + + assert ConfigCat.get_value("stringDefaultCat", "", client: client) == "Cat" + end + defp start(sdk_key, options \\ []) do sdk_key |> Cache.generate_key() diff --git a/test/support/cache_policy_case.ex b/test/support/cache_policy_case.ex index b2345f4..e971d8e 100644 --- a/test/support/cache_policy_case.ex +++ b/test/support/cache_policy_case.ex @@ -14,7 +14,7 @@ defmodule ConfigCat.CachePolicyCase do alias ConfigCat.Hooks alias ConfigCat.InMemoryCache alias ConfigCat.MockFetcher - alias HTTPoison.Response + alias Req.Response using do quote do @@ -112,7 +112,7 @@ defmodule ConfigCat.CachePolicyCase do @spec assert_returns_error(function()) :: true def assert_returns_error(force_refresh_fn) do - response = %Response{status_code: 503} + response = Response.new(status: 503) error = FetchError.exception(reason: response, transient?: true) stub(MockFetcher, :fetch, fn _id, _etag -> {:error, error} end) diff --git a/test/support/mocks.ex b/test/support/mocks.ex index 6b64119..89b3391 100644 --- a/test/support/mocks.ex +++ b/test/support/mocks.ex @@ -1,4 +1,4 @@ -Mox.defmock(ConfigCat.MockAPI, for: HTTPoison.Base) +Mox.defmock(ConfigCat.MockAPI, for: ConfigCat.API) Mox.defmock(ConfigCat.MockCachePolicy, for: ConfigCat.CachePolicy.Behaviour) Mox.defmock(ConfigCat.MockConfigCache, for: ConfigCat.ConfigCache) Mox.defmock(ConfigCat.MockFetcher, for: ConfigCat.ConfigFetcher)