Skip to content

Commit

Permalink
add updates
Browse files Browse the repository at this point in the history
  • Loading branch information
JoE11-y committed Dec 6, 2024
1 parent f5dc825 commit 50859b4
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 87 deletions.
16 changes: 10 additions & 6 deletions openfeature/providers/elixir-provider/lib/provider/provider.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule ElixirProvider.Provider do
@behaviour OpenFeature.Provider

require Logger
alias ElixirProvider.CacheController
alias ElixirProvider.ContextTransformer
alias ElixirProvider.DataCollectorHook
Expand All @@ -19,30 +20,32 @@ defmodule ElixirProvider.Provider do
defstruct [
:options,
:http_client,
:data_collector_hook,
:hooks,
:ws,
:domain
:domain,
name: "ElixirProvider"
]

@type t :: %__MODULE__{
name: String.t(),
options: GoFeatureFlagOptions.t(),
http_client: HttpClient.t(),
data_collector_hook: DataCollectorHook.t() | nil,
hooks: DataCollectorHook.t() | nil,
ws: GoFWebSocketClient.t(),
domain: String.t()
}

@impl true
def initialize(%__MODULE__{} = provider, domain, _context) do
{:ok, http_client} = HttpClient.start_http_connection(provider.options)
{:ok, data_collector_hook} = DataCollectorHook.start(provider.options, http_client)
{:ok, hooks} = DataCollectorHook.start(provider.options, http_client)
{:ok, ws} = GoFWebSocketClient.connect(provider.options.endpoint)

updated_provider = %__MODULE__{
provider
| domain: domain,
http_client: http_client,
data_collector_hook: data_collector_hook,
hooks: hooks,
ws: ws
}

Expand All @@ -56,7 +59,7 @@ defmodule ElixirProvider.Provider do
if(GenServer.whereis(GoFWebSocketClient), do: GoFWebSocketClient.stop())

if(GenServer.whereis(DataCollectorHook),
do: DataCollectorHook.stop(provider.data_collector_hook)
do: DataCollectorHook.stop(provider.hooks)
)

:ok
Expand Down Expand Up @@ -104,6 +107,7 @@ defmodule ElixirProvider.Provider do
end

defp handle_response(flag_key, eval_context_hash, response) do
Logger.debug("Unexpected frame received: #{inspect("here")}")
# Build the flag evaluation struct directly from the response map
flag_eval = ResponseFlagEvaluation.decode(response)

Expand Down
34 changes: 20 additions & 14 deletions openfeature/providers/elixir-provider/lib/provider/web_socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule ElixirProvider.GoFWebSocketClient do
closing?: boolean()
}

@websocket_uri "/ws/v1/flag/change"
@websocket_uri "ws/v1/flag/change"

def connect(url) do
with {:ok, socket} <- GenServer.start_link(__MODULE__, [], name: __MODULE__),
Expand All @@ -45,36 +45,42 @@ defmodule ElixirProvider.GoFWebSocketClient do
def handle_call({:connect, url}, from, state) do
uri = URI.parse(url)

http_scheme =
{http_scheme, ws_scheme} =
case uri.scheme do
"ws" -> :http
"wss" -> :https
"ws" -> {:http, :ws}
"wss" -> {:https, :wss}
"http" -> {:http, :ws}
"https" -> {:https, :wss}
_ -> {:reply, {:error, :invalid_scheme}, state}
end

ws_scheme =
case uri.scheme do
"ws" -> :ws
"wss" -> :wss
end

# Construct the WebSocket path
path = uri.path <> @websocket_uri
# Ensure the path is not nil
path = (uri.path || "/") <> @websocket_uri

with {:ok, conn} <- Mint.HTTP.connect(http_scheme, uri.host, uri.port),
with {:ok, conn} <-
Mint.HTTP.connect(http_scheme, uri.host, uri.port || default_port(http_scheme)),
{:ok, conn, ref} <- Mint.WebSocket.upgrade(ws_scheme, conn, path, []) do
state = %{state | conn: conn, request_ref: ref, caller: from}

{:noreply, state}
else
{:error, reason} ->
Logger.info("Parsed URI path: #{inspect("hi")}")
{:reply, {:error, reason}, state}

{:error, conn, reason} ->
Logger.info("Parsed URI path: #{inspect(reason)}")
{:reply, {:error, reason}, put_in(state.conn, conn)}
end
end

defp default_port(:http), do: 80
defp default_port(:https), do: 443

@impl GenServer
def handle_info(message, state) do
Logger.info("Received message: #{inspect(message)}")

case Mint.WebSocket.stream(state.conn, message) do
{:ok, conn, responses} ->
state = put_in(state.conn, conn) |> handle_responses(responses)
Expand Down Expand Up @@ -165,7 +171,7 @@ defmodule ElixirProvider.GoFWebSocketClient do

defp do_close(state) do
Mint.HTTP.close(state.conn)
Logger.info("Comfy websocket closed")
Logger.info("Websocket closed")
{:stop, :normal, state}
end

Expand Down
4 changes: 3 additions & 1 deletion openfeature/providers/elixir-provider/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ defmodule ElixirProvider.MixProject do
{:jason, "~> 1.4"},
{:mint, "~> 1.6"},
{:mint_web_socket, "~> 1.0"},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:bypass, "~> 2.1", only: :test},
{:plug, "~> 1.16", only: :test}
]
end
end
10 changes: 10 additions & 0 deletions openfeature/providers/elixir-provider/mix.lock
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
%{
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"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"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
"credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [: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", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"},
"elixir_sdk": {:git, "https://github.com/open-feature/elixir-sdk.git", "8e08041085aedec5d661b9a9a942cdf9f2606422", []},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [: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", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"mint_web_socket": {:hex, :mint_web_socket, "1.0.4", "0b539116dbb3d3f861cdf5e15e269a933cb501c113a14db7001a3157d96ffafd", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "027d4c5529c45a4ba0ce27a01c0f35f284a5468519c045ca15f43decb360a991"},
"open_feature": {:git, "https://github.com/open-feature/elixir-sdk.git", "8e08041085aedec5d661b9a9a942cdf9f2606422", []},
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"websocket_client": {:hex, :websocket_client, "1.5.0", "e825f23c51a867681a222148ed5200cc4a12e4fb5ff0b0b35963e916e2b5766b", [:rebar3], [], "hexpm", "2b9b201cc5c82b9d4e6966ad8e605832eab8f4ddb39f57ac62f34cb208b68de9"},
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
}
195 changes: 129 additions & 66 deletions openfeature/providers/elixir-provider/test/elixir_provider_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,82 +4,145 @@ defmodule ElixirProviderTest do
"""
use ExUnit.Case
doctest ElixirProvider
alias OpenFeature
alias OpenFeature.Client

## TEST CONTEXT TRANSFORMER
@endpoint "http://localhost:1031"

test "should use the targetingKey as user key" do
got =
ElixirProvider.ContextTransformer.transform_context(%{
targetingKey: "user-key"
})
@default_evaluation_ctx %{
targeting_key: "d45e303a-38c2-11ed-a261-0242ac120002",
email: "[email protected]",
firstname: "john",
lastname: "doe",
anonymous: false,
professional: true,
rate: 3.14,
age: 30,
company_info: %{name: "my_company", size: 120},
labels: ["pro", "beta"]
}

want =
{:ok,
%ElixirProvider.GofEvaluationContext{
key: "user-key",
custom: %{}
}}
setup do
provider = %ElixirProvider.Provider{
options: %ElixirProvider.GoFeatureFlagOptions{
endpoint: @endpoint,
data_flush_interval: 100,
disable_cache_invalidation: true
}
}

assert got == want
bypass = Bypass.open()
OpenFeature.set_provider(provider)
client = OpenFeature.get_client()
{:ok, bypass: bypass, client: client}
end

test "should specify the anonymous field base on the attributes" do
got =
ElixirProvider.ContextTransformer.transform_context(%{
targetingKey: "user-key",
anonymous: true
})

want =
{:ok,
%ElixirProvider.GofEvaluationContext{
key: "user-key",
custom: %{
anonymous: true
}
}}

assert got == want
end
## TEST CONTEXT TRANSFORMER

test "should fail if no targeting field is provided" do
got =
ElixirProvider.ContextTransformer.transform_context(%{
anonymous: true,
firstname: "John",
lastname: "Doe",
email: "[email protected]"
})
# test "should use the targetingKey as user key" do
# got =
# ElixirProvider.ContextTransformer.transform_context(%{
# targetingKey: "user-key"
# })

want = {:error, "targeting key not found"}
# want =/
# {:ok,
# %ElixirProvider.GofEvaluationContext{
# key: "user-key",
# custom: %{}
# }}

assert got == want
end
# assert got == want
# end

test "should fill custom fields if extra fields are present" do
got =
ElixirProvider.ContextTransformer.transform_context(%{
targetingKey: "user-key",
anonymous: true,
firstname: "John",
lastname: "Doe",
email: "[email protected]"
})

want =
{:ok,
%ElixirProvider.GofEvaluationContext{
key: "user-key",
custom: %{
firstname: "John",
lastname: "Doe",
email: "[email protected]",
anonymous: true
}
}}

assert got == want
end
# test "should specify the anonymous field base on the attributes" do
# got =
# ElixirProvider.ContextTransformer.transform_context(%{
# targetingKey: "user-key",
# anonymous: true
# })

# want =
# {:ok,
# %ElixirProvider.GofEvaluationContext{
# key: "user-key",
# custom: %{
# anonymous: true
# }
# }}

# assert got == want
# end

# test "should fail if no targeting field is provided" do
# got =
# ElixirProvider.ContextTransformer.transform_context(%{
# anonymous: true,
# firstname: "John",
# lastname: "Doe",
# email: "[email protected]"
# })

# want = {:error, "targeting key not found"}

# assert got == want
# end

# test "should fill custom fields if extra fields are present" do
# got =
# ElixirProvider.ContextTransformer.transform_context(%{
# targetingKey: "user-key",
# anonymous: true,
# firstname: "John",
# lastname: "Doe",
# email: "[email protected]"
# })

# want =
# {:ok,
# %ElixirProvider.GofEvaluationContext{
# key: "user-key",
# custom: %{
# firstname: "John",
# lastname: "Doe",
# email: "[email protected]",
# anonymous: true
# }
# }}

# assert got == want
# end

### PROVIDER TESTS

test "should provide an error if flag does not exist", %{bypass: bypass, client: client} do
flag_key = "flag_not_found"
default = false
ctx = @default_evaluation_ctx

# Corrected path (only the path, not the full URL)
path = "/v1/feature/#{flag_key}/eval"

# Set up Bypass to handle the POST request
Bypass.expect_once(bypass, "POST", path, fn conn ->
Plug.Conn.resp(conn, 404, ~s<{"errors": [{"code": 88, "message": "Rate limit exceeded"}]}>)
end)

# Make the client call
response = Client.get_boolean_details(client, flag_key, default, context: ctx)

# Define the expected response structure
expected_response = %{
error_code: :provider_not_ready,
error_message:
"impossible to call go-feature-flag relay proxy on #{@endpoint}#{path}: Error: Request failed with status code 404",
key: flag_key,
reason: :error,
value: false,
flag_metadata: %{}
}

# Assert the response matches the expected structure
assert response == expected_response
end
end

0 comments on commit 50859b4

Please sign in to comment.