Skip to content

Commit a6c1199

Browse files
committed
Abstract away access to application env global state to avoid test flakiness
1 parent 37cacb6 commit a6c1199

File tree

9 files changed

+119
-51
lines changed

9 files changed

+119
-51
lines changed

lib/trento/ai.ex

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@ defmodule Trento.AI do
33
The `Trento.AI` module provides functions to interact with the AI features of the Trento application.
44
"""
55

6+
alias Trento.AI.ApplicationConfigLoader
7+
68
alias Trento.AI.Configurations
79

810
@doc """
911
Checks if the AI features are enabled.
1012
"""
1113
@spec enabled?() :: boolean()
1214
def enabled?,
13-
do:
14-
:trento
15-
|> Application.get_env(:ai, [])
16-
|> Keyword.get(:enabled, false)
15+
do: Keyword.get(ApplicationConfigLoader.load(), :enabled, false)
1716

1817
@doc """
1918
Creates a user configuration for AI.
@@ -41,9 +40,6 @@ defmodule Trento.AI do
4140
end
4241
end
4342

44-
defp configurations do
45-
:trento
46-
|> Application.get_env(:ai)
47-
|> Keyword.get(:configurations, Configurations)
48-
end
43+
defp configurations,
44+
do: Keyword.get(ApplicationConfigLoader.load(), :configurations, Configurations)
4945
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defmodule Trento.AI.ApplicationConfigLoader do
2+
@moduledoc """
3+
This module is responsible for loading the AI application configuration and providing access to it.
4+
"""
5+
6+
@behaviour Trento.AI.ApplicationConfigLoader
7+
8+
@callback load_config :: keyword()
9+
10+
@impl true
11+
def load_config, do: Application.get_env(:trento, :ai, [])
12+
13+
@spec load :: keyword()
14+
def load, do: impl().load_config()
15+
16+
defp impl,
17+
do:
18+
:trento
19+
|> Application.get_env(:ai, [])
20+
|> Keyword.get(:application_config_loader, __MODULE__)
21+
end

lib/trento/ai/llm_registry.ex

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ defmodule Trento.AI.LLMRegistry do
33
This module is responsible for managing the registry of available LLM providers and their models.
44
"""
55

6+
alias Trento.AI.ApplicationConfigLoader
7+
68
@doc """
79
Returns the list of configured LLM providers.
810
"""
@@ -47,9 +49,5 @@ defmodule Trento.AI.LLMRegistry do
4749
@spec model_supported?(bitstring()) :: boolean()
4850
def model_supported?(model), do: model in get_provider_models(:all)
4951

50-
defp get_ai_providers_config do
51-
:trento
52-
|> Application.get_env(:ai, [])
53-
|> Keyword.get(:providers, [])
54-
end
52+
defp get_ai_providers_config, do: Keyword.get(ApplicationConfigLoader.load(), :providers, [])
5553
end

test/support/ai/ai_case.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule Trento.AI.AICase do
2+
@moduledoc false
3+
4+
use ExUnit.CaseTemplate
5+
6+
setup _ do
7+
stub_config_loader()
8+
9+
:ok
10+
end
11+
12+
def stub_config_loader do
13+
Mox.stub_with(
14+
Trento.AI.ApplicationConfigLoader.Mock,
15+
Trento.AI.ApplicationConfigLoader
16+
)
17+
end
18+
end

test/support/factory.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,8 @@ defmodule Trento.Factory do
14621462
end
14631463

14641464
def random_ai_model do
1465+
Trento.AI.AICase.stub_config_loader()
1466+
14651467
:all
14661468
|> LLMRegistry.get_provider_models()
14671469
|> Enum.random()

test/test_helper.exs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ Mox.defmock(Trento.Support.DateService.Mock, for: Trento.Support.DateService)
5353
Mox.defmock(Joken.CurrentTime.Mock, for: Joken.CurrentTime)
5454
Application.put_env(:joken, :current_time_adapter, Joken.CurrentTime.Mock)
5555

56+
Mox.defmock(Trento.AI.ApplicationConfigLoader.Mock,
57+
for: Trento.AI.ApplicationConfigLoader
58+
)
59+
60+
test_ai_config =
61+
Keyword.put(
62+
Application.get_env(:trento, :ai),
63+
:application_config_loader,
64+
Trento.AI.ApplicationConfigLoader.Mock
65+
)
66+
67+
Application.put_env(:trento, :ai, test_ai_config)
68+
5669
Application.ensure_all_started(:ex_machina, :faker)
5770

5871
if Application.get_env(:trento, :flaky_tests_detection)[:enabled?] == true do

test/trento/ai/configurations_test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule Trento.Ai.ConfigurationsTest do
22
use Trento.DataCase, async: true
3+
use Trento.AI.AICase
34

45
alias Trento.Users.User
56

test/trento/ai/llm_registry_test.exs

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,50 @@ defmodule Trento.AI.LLMRegistryTest do
33

44
alias Trento.AI.LLMRegistry
55

6-
setup do
7-
original_config = Application.get_env(:trento, :ai)
8-
9-
Application.put_env(:trento, :ai,
10-
enabled: true,
11-
providers: [
12-
provider1: [
13-
models: [
14-
"model1",
15-
"model2"
16-
]
17-
],
18-
provider2: [
19-
models: [
20-
"model3",
21-
"model4"
22-
]
23-
],
24-
provider3: [
25-
models: [
26-
"model5",
27-
"model6"
6+
import Mox
7+
8+
setup :verify_on_exit!
9+
10+
defp expect_config_loader_to_be_called_times(times) do
11+
expect(Trento.AI.ApplicationConfigLoader.Mock, :load_config, times, fn ->
12+
[
13+
enabled: true,
14+
providers: [
15+
provider1: [
16+
models: [
17+
"model1",
18+
"model2"
19+
]
20+
],
21+
provider2: [
22+
models: [
23+
"model3",
24+
"model4"
25+
]
26+
],
27+
provider3: [
28+
models: [
29+
"model5",
30+
"model6"
31+
]
2832
]
2933
]
3034
]
31-
)
32-
33-
on_exit(fn -> Application.put_env(:trento, :ai, original_config) end)
35+
end)
3436
end
3537

3638
describe "providers/0" do
3739
test "returns the list of configured providers" do
40+
expect_config_loader_to_be_called_times(1)
41+
3842
assert LLMRegistry.providers() == [:provider1, :provider2, :provider3]
3943
end
4044
end
4145

4246
describe "get_provider_models/1" do
4347
test "returns the list of models for a given provider" do
48+
expect_config_loader_to_be_called_times(3)
49+
4450
assert LLMRegistry.get_provider_models(:provider1) == [
4551
"model1",
4652
"model2"
@@ -58,11 +64,15 @@ defmodule Trento.AI.LLMRegistryTest do
5864
end
5965

6066
test "returns an empty list for an unknown provider" do
67+
expect_config_loader_to_be_called_times(1)
68+
6169
assert LLMRegistry.get_provider_models(:unknown) == []
6270
assert LLMRegistry.get_provider_models("foo") == []
6371
end
6472

6573
test "returns all available models" do
74+
expect_config_loader_to_be_called_times(1)
75+
6676
assert LLMRegistry.get_provider_models(:all) == [
6777
"model1",
6878
"model2",
@@ -76,22 +86,30 @@ defmodule Trento.AI.LLMRegistryTest do
7686

7787
describe "get_model_provider/1" do
7888
test "returns the provider for a given model" do
89+
expect_config_loader_to_be_called_times(2)
90+
7991
assert LLMRegistry.get_model_provider("model6") == :provider3
8092
assert LLMRegistry.get_model_provider("model1") == :provider1
8193
end
8294

8395
test "returns nil for an unknown model" do
96+
expect_config_loader_to_be_called_times(1)
97+
8498
assert LLMRegistry.get_model_provider("unknown-model") == nil
8599
end
86100
end
87101

88102
describe "model_supported?/1" do
89103
test "returns true for a supported model" do
104+
expect_config_loader_to_be_called_times(2)
105+
90106
assert LLMRegistry.model_supported?("model1") == true
91107
assert LLMRegistry.model_supported?("model6") == true
92108
end
93109

94110
test "returns false for an unsupported model" do
111+
expect_config_loader_to_be_called_times(1)
112+
95113
assert LLMRegistry.model_supported?("unknown-model") == false
96114
end
97115
end

test/trento/ai_test.exs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,31 @@ defmodule Trento.AITest do
55

66
import Trento.Factory
77

8-
setup do
9-
original_config = Application.get_env(:trento, :ai)
8+
import Mox
109

11-
on_exit(fn -> Application.put_env(:trento, :ai, original_config) end)
12-
end
10+
setup :verify_on_exit!
1311

1412
describe "enabled?/0" do
1513
test "returns true when AI features are enabled" do
16-
Application.put_env(:trento, :ai, enabled: true)
14+
expect(Trento.AI.ApplicationConfigLoader.Mock, :load_config, fn -> [enabled: true] end)
15+
1716
assert AI.enabled?() == true
1817
end
1918

2019
test "returns false when AI features are disabled" do
21-
Application.put_env(:trento, :ai, enabled: false)
20+
expect(Trento.AI.ApplicationConfigLoader.Mock, :load_config, fn -> [enabled: false] end)
2221
assert AI.enabled?() == false
2322
end
2423

2524
test "returns false when AI features are not configured" do
26-
Application.delete_env(:trento, :ai)
25+
expect(Trento.AI.ApplicationConfigLoader.Mock, :load_config, fn -> [] end)
2726
assert AI.enabled?() == false
2827
end
2928
end
3029

3130
describe "enabling/disabling features" do
3231
test "features are disabled" do
33-
Application.put_env(:trento, :ai, enabled: false)
32+
expect(Trento.AI.ApplicationConfigLoader.Mock, :load_config, 2, fn -> [enabled: false] end)
3433
user = build(:user)
3534

3635
attrs = %{
@@ -43,10 +42,12 @@ defmodule Trento.AITest do
4342
end
4443

4544
test "features are enabled" do
46-
Application.put_env(:trento, :ai,
47-
enabled: true,
48-
configurations: DummyConfigurations
49-
)
45+
expect(Trento.AI.ApplicationConfigLoader.Mock, :load_config, 4, fn ->
46+
[
47+
enabled: true,
48+
configurations: DummyConfigurations
49+
]
50+
end)
5051

5152
user = build(:user)
5253

0 commit comments

Comments
 (0)