Skip to content

Commit ef3651d

Browse files
authored
fix tests according to #20 (#32)
1 parent 6140542 commit ef3651d

File tree

4 files changed

+141
-132
lines changed

4 files changed

+141
-132
lines changed

.tool-versions

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
elixir 1.12.3-otp-24
2-
erlang 24.1.7
1+
erlang 26.0.1
2+
elixir 1.15.2-otp-26

test/flagsmith_engine_test.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ defmodule Flagsmith.EngineTest do
350350
end
351351

352352
test "evaluate_identity_in_segment/3 uses composite key if django id not present", %{
353-
env: env,
354353
identity: identity
355354
} do
356355
segment = %Segments.Segment{
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
defmodule Flagsmith.Client.PollerErrors.Test do
2+
use ExUnit.Case, async: false
3+
4+
import Mox
5+
import Flagsmith.Test.Helpers, only: [assert_request: 2]
6+
7+
alias Flagsmith.Engine.Test
8+
alias Flagsmith.Schemas
9+
10+
@environment_header Flagsmith.Configuration.environment_header()
11+
@api_url Flagsmith.Configuration.default_url()
12+
@api_paths Flagsmith.Configuration.api_paths()
13+
14+
# usually we want to ensure through careful allowances that a given process is
15+
# the one calling the mocks, but in this case it makes it much more difficult to
16+
# follow through because it's a process started through a dynamic supervisor
17+
# that then spawns requests that they themselves are the ones calling the mocks
18+
# so we set it to global and we'll use stubs and do our own logic in the stub
19+
# resolution.
20+
# Stubs aren't verified but because we have the right assertions - initialization
21+
# error that we get an exit, and the refresh that we get all corresponding messages
22+
# plus that the final state of the flags is as expected we can be confident that
23+
# those 2 situations are tested and properly converge on the expected output
24+
setup :set_mox_global
25+
26+
# we start the supervisor as a supervised process so it's shut down on every test
27+
# and subsequently shutting down the individual dynamic supervisor for the poller(s)
28+
# which in turn will make the poller shutdown too
29+
setup do
30+
start_supervised!(Flagsmith.Supervisor)
31+
:ok
32+
end
33+
34+
setup do
35+
config =
36+
Flagsmith.Client.new(
37+
enable_local_evaluation: true,
38+
environment_key: "test_key"
39+
)
40+
41+
[config: config]
42+
end
43+
44+
test "initializing with api error", %{config: config} do
45+
stub(Tesla.Adapter.Mock, :call, fn tesla_env, _options ->
46+
assert_request(
47+
tesla_env,
48+
body: nil,
49+
query: [],
50+
headers: [{@environment_header, "test_key"}],
51+
url: Path.join([@api_url, @api_paths.environment]) <> "/",
52+
method: :get
53+
)
54+
55+
{:error, :noop}
56+
end)
57+
58+
# here we start it through the dynamic supervisor instead of directly
59+
# since we don't want the process exit to exit the test as well
60+
{:ok, _pid} = Flagsmith.Client.Poller.Supervisor.start_child(config)
61+
62+
# we assert that we'll have an exit from the function since the call will fail
63+
assert catch_exit(Flagsmith.Client.get_environment(config))
64+
end
65+
66+
test "refresh with errors retries in next cycle", %{config: config} do
67+
config = %{config | environment_refresh_interval_milliseconds: 3}
68+
env_response = Jason.decode!(Test.Generators.json_env())
69+
70+
test_process = self()
71+
# we use a counter to keep track of how many times the stub has been called
72+
# first (0) is normal response
73+
# second (1) is the refresh error
74+
# third and last (2) is the refresh ok with updated features states being an empty
75+
# list
76+
ref_counter = :counters.new(1, [:atomics])
77+
78+
# stub the call for first call
79+
stub(Tesla.Adapter.Mock, :call, fn tesla_env, _options ->
80+
assert_request(
81+
tesla_env,
82+
body: nil,
83+
query: [],
84+
headers: [{@environment_header, "test_key"}],
85+
url: Path.join([@api_url, @api_paths.environment]) <> "/",
86+
method: :get
87+
)
88+
89+
case :counters.get(ref_counter, 1) do
90+
0 ->
91+
:counters.add(ref_counter, 1, 1)
92+
send(test_process, :ok)
93+
{:ok, %Tesla.Env{status: 200, body: env_response}}
94+
95+
1 ->
96+
:counters.add(ref_counter, 1, 1)
97+
send(test_process, :ok)
98+
{:error, :noop}
99+
100+
2 ->
101+
poller_pid = Flagsmith.Client.Poller.whereis("test_key")
102+
:ok = :gen_statem.call(poller_pid, {:update_refresh_rate, 60_000})
103+
new_env_response = %{env_response | "feature_states" => []}
104+
send(test_process, :ok)
105+
{:ok, %Tesla.Env{status: 200, body: new_env_response}}
106+
end
107+
end)
108+
109+
{:ok, _pid} = Flagsmith.Client.Poller.Supervisor.start_child(config)
110+
111+
# we ensure that it has been called all 3 times by receiving 3 :ok messages that
112+
# are sent from the stub resolution
113+
Enum.each(1..3, fn n ->
114+
receive do
115+
:ok -> :ok
116+
after
117+
500 ->
118+
raise "didn't receive stub call number #{n}"
119+
end
120+
end)
121+
122+
# because we waited for 3 :ok messages we know the stub has to have been resolved
123+
# 3 times and as such the state of the poller should hold the last feature states
124+
# which to disambiguate from the normal mock response is in this case an empty list
125+
# and so if we have 0 flags it must mean that the poller held the last valid call
126+
# and also that it refreshed after the error
127+
assert Flagsmith.Test.Helpers.wait_until(
128+
fn ->
129+
{:ok, %Schemas.Flags{flags: flags}} =
130+
Flagsmith.Client.get_environment_flags(config)
131+
132+
flags == %{}
133+
end,
134+
200
135+
)
136+
end
137+
end

test/flagsmith_poller_test.exs

Lines changed: 2 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Flagsmith.Client.Poller.Test do
22
use ExUnit.Case, async: false
33

4-
import Mox, only: [verify_on_exit!: 1, expect: 3, allow: 3, stub_with: 2]
4+
import Mox
55
import Flagsmith.Test.Helpers, only: [assert_request: 2]
66

77
alias Flagsmith.Engine.Test
@@ -11,7 +11,7 @@ defmodule Flagsmith.Client.Poller.Test do
1111
@api_url Flagsmith.Configuration.default_url()
1212
@api_paths Flagsmith.Configuration.api_paths()
1313

14-
# setup Mox to verify any expectations
14+
# setup Mox to verify any expectations
1515
setup :verify_on_exit!
1616

1717
# we start the supervisor as a supervised process so it's shut down on every test
@@ -502,131 +502,4 @@ defmodule Flagsmith.Client.Poller.Test do
502502
}} = Flagsmith.Client.get_environment_flags(new_config)
503503
end
504504
end
505-
506-
describe "api call errors" do
507-
setup do
508-
config =
509-
Flagsmith.Client.new(
510-
enable_local_evaluation: true,
511-
environment_key: "test_key"
512-
)
513-
514-
[config: config]
515-
end
516-
517-
test "initializing with api error", %{config: config} do
518-
expect(Tesla.Adapter.Mock, :call, fn tesla_env, _options ->
519-
assert_request(
520-
tesla_env,
521-
body: nil,
522-
query: [],
523-
headers: [{@environment_header, "test_key"}],
524-
url: Path.join([@api_url, @api_paths.environment]) <> "/",
525-
method: :get
526-
)
527-
528-
{:error, :noop}
529-
end)
530-
531-
# here we start it through the dynamic supervisor instead of directly
532-
# since we don't want the process exit to exit the test as well
533-
{:ok, pid} = Flagsmith.Client.Poller.Supervisor.start_child(config)
534-
535-
allow(Tesla.Adapter.Mock, self(), pid)
536-
537-
# we assert that we'll have an exit from the function since the call will fail
538-
assert catch_exit(Flagsmith.Client.get_environment(config))
539-
end
540-
541-
test "refresh with errors retries in next cycle", %{config: config} do
542-
config = %{config | environment_refresh_interval_milliseconds: 3}
543-
env_response = Jason.decode!(Test.Generators.json_env())
544-
545-
# expectation for first call
546-
expect(Tesla.Adapter.Mock, :call, fn tesla_env, _options ->
547-
assert_request(
548-
tesla_env,
549-
body: nil,
550-
query: [],
551-
headers: [{@environment_header, "test_key"}],
552-
url: Path.join([@api_url, @api_paths.environment]) <> "/",
553-
method: :get
554-
)
555-
556-
{:ok, %Tesla.Env{status: 200, body: env_response}}
557-
end)
558-
559-
# expectation for refresh call 1
560-
expect(Tesla.Adapter.Mock, :call, fn tesla_env, _options ->
561-
assert_request(
562-
tesla_env,
563-
body: nil,
564-
query: [],
565-
headers: [{@environment_header, "test_key"}],
566-
url: Path.join([@api_url, @api_paths.environment]) <> "/",
567-
method: :get
568-
)
569-
570-
{:error, :noop}
571-
end)
572-
573-
# expectation for refresh call 2
574-
# similar to what we did previously to test the refreshes
575-
expect(Tesla.Adapter.Mock, :call, fn tesla_env, _options ->
576-
assert_request(
577-
tesla_env,
578-
body: nil,
579-
query: [],
580-
headers: [{@environment_header, "test_key"}],
581-
url: Path.join([@api_url, @api_paths.environment]) <> "/",
582-
method: :get
583-
)
584-
585-
poller_pid = Flagsmith.Client.Poller.whereis("test_key")
586-
:ok = :gen_statem.call(poller_pid, {:update_refresh_rate, 60_000})
587-
new_env_response = %{env_response | "feature_states" => []}
588-
589-
{:ok, %Tesla.Env{status: 200, body: new_env_response}}
590-
end)
591-
592-
{:ok, pid} = Flagsmith.Client.Poller.Supervisor.start_child(config)
593-
594-
allow(Tesla.Adapter.Mock, self(), pid)
595-
596-
:erlang.trace(pid, true, [:procs])
597-
598-
# we should now have 2 spawns calls the first fails, so another one afterwards
599-
assert Enum.reduce_while(1..100, false, fn _, acc ->
600-
receive do
601-
{:trace, ^pid, :spawn, spawned_pid,
602-
{Flagsmith.Client.Poller, :get_environment, [^pid, ^config]}} ->
603-
allow(Tesla.Adapter.Mock, self(), spawned_pid)
604-
605-
case acc do
606-
true ->
607-
{:halt, true}
608-
609-
false ->
610-
{:cont, true}
611-
end
612-
613-
_others ->
614-
{:cont, false}
615-
after
616-
200 ->
617-
{:halt, false}
618-
end
619-
end)
620-
621-
assert Flagsmith.Test.Helpers.wait_until(
622-
fn ->
623-
{:ok, %Schemas.Flags{flags: flags}} =
624-
Flagsmith.Client.get_environment_flags(config)
625-
626-
flags == %{}
627-
end,
628-
15
629-
)
630-
end
631-
end
632505
end

0 commit comments

Comments
 (0)