From 76226bc1ff45baf8fb04b934a50943d765047487 Mon Sep 17 00:00:00 2001 From: Hez Ronningen Date: Fri, 17 Oct 2025 14:39:09 -0700 Subject: [PATCH] add test data support - add some helpers to run ldclient in tests - new variant module to handle types/etc - refactored App.start to make tag and options optional --- lib/ex_launch_darkly/app.ex | 30 +++++++++++++------- lib/ex_launch_darkly/test_data.ex | 46 +++++++++++++++++++++++++++++++ lib/ex_launch_darkly/user.ex | 9 ++++-- lib/ex_launch_darkly/variation.ex | 11 ++++++++ 4 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 lib/ex_launch_darkly/test_data.ex create mode 100644 lib/ex_launch_darkly/variation.ex diff --git a/lib/ex_launch_darkly/app.ex b/lib/ex_launch_darkly/app.ex index 75be2f3..e066cc4 100644 --- a/lib/ex_launch_darkly/app.ex +++ b/lib/ex_launch_darkly/app.ex @@ -1,20 +1,30 @@ defmodule ExLaunchDarkly.App do + @default_instance_name :default + @test_options %{ + datasource: :testdata, + send_events: false, + feature_store: :ldclient_storage_map + } + @spec start(String.t(), atom(), map()) :: :ok | {:error, any(), any()} - def start(key, tag \\ :default, options \\ %{}) - when is_binary(key) and is_atom(tag) and is_map(options) do + @spec start(String.t(), map()) :: :ok | {:error, any(), any()} + @spec start(String.t(), atom()) :: :ok | {:error, any(), any()} + def start(key, tag, %{} = options) when is_binary(key) and is_atom(tag) do options = - Map.merge( - %{ - :http_options => %{ - :tls_options => :ldclient_config.tls_basic_options() - } - }, - options - ) + Map.merge(%{http_options: %{tls_options: :ldclient_config.tls_basic_options()}}, options) key |> String.to_charlist() |> :ldclient.start_instance(tag, options) end + def start(key, options \\ %{}) + + def start(key, %{} = options) when is_binary(key), + do: start(key, @default_instance_name, options) + + def start(key, tag) when is_binary(key) and is_atom(tag), do: start(key, tag, %{}) + @spec stop_all() :: :ok def stop_all, do: :ldclient.stop_all_instances() + + def test_options, do: @test_options end diff --git a/lib/ex_launch_darkly/test_data.ex b/lib/ex_launch_darkly/test_data.ex new file mode 100644 index 0000000..fbeb78a --- /dev/null +++ b/lib/ex_launch_darkly/test_data.ex @@ -0,0 +1,46 @@ +defmodule ExLaunchDarkly.TestData do + @moduledoc """ + This provides some wrappers around the LaunchDarkly Erlang :ldclient_flagbuilder and :ldclient_testdata modules. + + ## To Use + + Add the following to your exunit test helper: + ```elixir + ExLaunchDarkly.App.start("test", :sometag, ExLaunchDarkly.App.test_options()) + or + ExLaunchDarkly.App.start("test", ExLaunchDarkly.App.test_options()) + ``` + + Add some flag set calls to your test file. + + For an individual user: + ```elixir + ExLaunchDarkly.TestData.set("feature-foo-bar", true, "someUser") + ``` + + For all users: + ```elixir + ExLaunchDarkly.TestData.set("feature-fizz-buzz", true, "someUser") + ``` + """ + import ExLaunchDarkly.Variation, only: [is_variation_value: 1] + alias ExLaunchDarkly.Variation + + @spec set(String.t(), Variation.value(), String.t()) :: :ok + def set(flag, value, user) + when is_binary(flag) and is_binary(user) and is_variation_value(value) do + {:ok, ld_flag} = :ldclient_testdata.flag(flag) + user_variation = :ldclient_flagbuilder.variation_for_context(value, "user", user, ld_flag) + ld_flag = :ldclient_flagbuilder.fallthrough_variation(false, user_variation) + :ldclient_testdata.update(ld_flag) + end + + @spec set_all(String.t(), Variation.value()) :: :ok + def set_all(flag, value) when is_binary(flag) and is_variation_value(value) do + {:ok, ld_flag} = :ldclient_testdata.flag(flag) + + value + |> :ldclient_flagbuilder.variation_for_all(ld_flag) + |> :ldclient_testdata.update() + end +end diff --git a/lib/ex_launch_darkly/user.ex b/lib/ex_launch_darkly/user.ex index 8ecbeb7..62eb153 100644 --- a/lib/ex_launch_darkly/user.ex +++ b/lib/ex_launch_darkly/user.ex @@ -4,7 +4,7 @@ defmodule ExLaunchDarkly.User do Usage - ``` + ```elixir user = User.new("a-slug") # => %{key: "a-slug"} @@ -13,10 +13,13 @@ defmodule ExLaunchDarkly.User do ``` """ @type t() :: :ldclient_user.user() + @type key() :: :ldclient_user.key() @type attribute() :: :ldclient_user.attribute() - @spec new(String.t()) :: t() - def new(key) when is_binary(key), do: :ldclient_user.new(key) + defguard is_user_key(key) when is_binary(key) or key == :null + + @spec new(key()) :: t() + def new(key) when is_user_key(key), do: :ldclient_user.new(key) @spec new_from_map(map()) :: t() def new_from_map(%{} = user_attributes), do: :ldclient_user.new_from_map(user_attributes) diff --git a/lib/ex_launch_darkly/variation.ex b/lib/ex_launch_darkly/variation.ex new file mode 100644 index 0000000..635f298 --- /dev/null +++ b/lib/ex_launch_darkly/variation.ex @@ -0,0 +1,11 @@ +defmodule ExLaunchDarkly.Variation do + @type value() :: :ldclient_flag.variation_value() + @type key() :: :ldclient_flag.key() + + defguard is_variation_key(key) when is_binary(key) + + defguard is_variation_value(value) + when is_boolean(value) or is_integer(value) or + is_float(value) or is_binary(value) or + is_list(value) or is_map(value) +end