diff --git a/lib/req_llm/application.ex b/lib/req_llm/application.ex index 71d75526..3ea64fa6 100644 --- a/lib/req_llm/application.ex +++ b/lib/req_llm/application.ex @@ -20,10 +20,15 @@ defmodule ReqLLM.Application do @impl true def start(_type, _args) do - if Application.get_env(:req_llm, :load_dotenv, true) do + req_llm_load_dotenv = Application.get_env(:req_llm, :load_dotenv, true) + + sync_llm_db_dotenv_config(req_llm_load_dotenv) + + if req_llm_load_dotenv do load_dotenv() end + ensure_llm_db_started() initialize_registry() initialize_schema_cache() @@ -127,6 +132,23 @@ defmodule ReqLLM.Application do ]) end + defp sync_llm_db_dotenv_config(req_llm_load_dotenv) do + case Application.fetch_env(:llm_db, :load_dotenv) do + :error -> + Application.put_env(:llm_db, :load_dotenv, req_llm_load_dotenv) + + {:ok, _value} -> + :ok + end + end + + defp ensure_llm_db_started do + case Application.ensure_all_started(:llm_db) do + {:ok, _} -> :ok + {:error, reason} -> raise "failed to start :llm_db: #{inspect(reason)}" + end + end + defp load_dotenv do env_file = Path.join(File.cwd!(), ".env") diff --git a/mix.exs b/mix.exs index 07e75009..84fd26b2 100644 --- a/mix.exs +++ b/mix.exs @@ -19,7 +19,7 @@ defmodule ReqLLM.MixProject do # Dialyzer configuration dialyzer: [ - plt_add_apps: [:mix], + plt_add_apps: [:mix, :llm_db], exclude_paths: ["test/support"] ], @@ -185,6 +185,7 @@ defmodule ReqLLM.MixProject do def application do [ extra_applications: [:logger, :xmerl], + included_applications: [:llm_db], mod: {ReqLLM.Application, []} ] end diff --git a/test/req_llm/application_test.exs b/test/req_llm/application_test.exs index be9b922e..e3001c33 100644 --- a/test/req_llm/application_test.exs +++ b/test/req_llm/application_test.exs @@ -1,6 +1,26 @@ defmodule ReqLLM.ApplicationTest do use ExUnit.Case, async: false + @dotenv_key "REQ_LLM_ISSUE_527_DOTENV_KEY" + + setup do + original_req_llm_load_dotenv = Application.get_env(:req_llm, :load_dotenv) + original_llm_db_load_dotenv = Application.get_env(:llm_db, :load_dotenv) + + on_exit(fn -> + stop_app(:req_llm) + stop_app(:llm_db) + restore_app_env(:req_llm, :load_dotenv, original_req_llm_load_dotenv) + restore_app_env(:llm_db, :load_dotenv, original_llm_db_load_dotenv) + System.delete_env(@dotenv_key) + Application.ensure_all_started(:llm_db) + LLMDB.load(custom: Application.get_env(:llm_db, :custom, %{})) + Application.ensure_all_started(:req_llm) + end) + + :ok + end + describe "load_dotenv configuration" do test "get_finch_config/0 returns default configuration" do config = ReqLLM.Application.get_finch_config() @@ -40,5 +60,73 @@ defmodule ReqLLM.ApplicationTest do end end end + + test "load_dotenv false prevents llm_db from loading .env during req_llm startup" do + with_apps_stopped(fn -> + with_temp_dir(fn -> + File.write!(".env", "#{@dotenv_key}=from_file\n") + System.delete_env(@dotenv_key) + Application.put_env(:req_llm, :load_dotenv, false) + Application.delete_env(:llm_db, :load_dotenv) + + assert {:ok, _} = Application.ensure_all_started(:req_llm) + assert System.get_env(@dotenv_key) == nil + end) + end) + end + + test "startup does not overwrite existing env vars from .env files" do + with_apps_stopped(fn -> + with_temp_dir(fn -> + File.write!(".env", "#{@dotenv_key}=from_file\n") + System.put_env(@dotenv_key, "from_env") + Application.delete_env(:req_llm, :load_dotenv) + Application.delete_env(:llm_db, :load_dotenv) + + assert {:ok, _} = Application.ensure_all_started(:req_llm) + assert System.get_env(@dotenv_key) == "from_env" + end) + end) + end + + test "explicit llm_db load_dotenv config overrides req_llm default" do + with_apps_stopped(fn -> + with_temp_dir(fn -> + File.write!(".env", "#{@dotenv_key}=from_file\n") + System.delete_env(@dotenv_key) + Application.put_env(:req_llm, :load_dotenv, false) + Application.put_env(:llm_db, :load_dotenv, true) + + assert {:ok, _} = Application.ensure_all_started(:req_llm) + assert System.get_env(@dotenv_key) == "from_file" + end) + end) + end + end + + defp with_apps_stopped(fun) do + stop_app(:req_llm) + stop_app(:llm_db) + fun.() + end + + defp with_temp_dir(fun) do + path = Path.join(System.tmp_dir!(), "req-llm-issue-527-#{System.unique_integer([:positive])}") + File.mkdir_p!(path) + + try do + File.cd!(path, fun) + after + File.rm_rf!(path) + end + end + + defp stop_app(app) do + if Keyword.has_key?(Application.started_applications(), app) do + Application.stop(app) + end end + + defp restore_app_env(app, key, nil), do: Application.delete_env(app, key) + defp restore_app_env(app, key, value), do: Application.put_env(app, key, value) end