mirror of
https://github.com/wanderer-industries/wanderer
synced 2026-01-20 13:50:25 +00:00
213 lines
5.9 KiB
Elixir
213 lines
5.9 KiB
Elixir
defmodule WandererApp.Test.DatabaseAccessManager do
|
|
@moduledoc """
|
|
Comprehensive database access management for integration tests.
|
|
|
|
This module provides utilities to ensure that all processes spawned during
|
|
integration tests have proper database sandbox access.
|
|
"""
|
|
|
|
@doc """
|
|
Grants database access to a process and monitors for child processes.
|
|
|
|
This function not only grants access to the given process but also
|
|
monitors it for child processes and grants access to them as well.
|
|
"""
|
|
def grant_database_access(pid, owner_pid \\ self()) do
|
|
# Grant access to the primary process (basic sandbox access)
|
|
try do
|
|
Ecto.Adapters.SQL.Sandbox.allow(WandererApp.Repo, owner_pid, pid)
|
|
rescue
|
|
# Ignore errors if already allowed
|
|
_ -> :ok
|
|
end
|
|
|
|
# Set up lightweight monitoring for child processes
|
|
setup_lightweight_monitoring(pid, owner_pid)
|
|
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Grants database access to a GenServer and all its potential child processes.
|
|
|
|
This includes monitoring for Task.async processes, linked processes,
|
|
and any other processes that might be spawned by the GenServer.
|
|
"""
|
|
def grant_genserver_database_access(genserver_pid, owner_pid \\ self()) do
|
|
# Grant access to the GenServer itself
|
|
grant_database_access(genserver_pid, owner_pid)
|
|
|
|
# Get all current linked processes and grant them access (once)
|
|
grant_access_to_linked_processes(genserver_pid, owner_pid)
|
|
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Grants database access to all processes in a supervision tree.
|
|
|
|
This recursively grants access to all processes under a supervisor.
|
|
"""
|
|
def grant_supervision_tree_access(supervisor_pid, owner_pid \\ self()) do
|
|
# Grant access to the supervisor
|
|
grant_database_access(supervisor_pid, owner_pid)
|
|
|
|
# Get all children and grant them access
|
|
children = get_supervisor_children(supervisor_pid)
|
|
|
|
Enum.each(children, fn child_pid ->
|
|
grant_database_access(child_pid, owner_pid)
|
|
|
|
# If the child is also a supervisor, recurse
|
|
if is_supervisor?(child_pid) do
|
|
grant_supervision_tree_access(child_pid, owner_pid)
|
|
end
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
|
|
@doc """
|
|
Monitors a process for database access issues and automatically grants access.
|
|
|
|
This sets up a monitoring process that watches for database access errors
|
|
and automatically grants access to processes that need it.
|
|
"""
|
|
def setup_automatic_access_granting(monitored_pid, owner_pid \\ self()) do
|
|
spawn_link(fn ->
|
|
Process.monitor(monitored_pid)
|
|
monitor_for_database_access_errors(monitored_pid, owner_pid)
|
|
end)
|
|
end
|
|
|
|
# Private helper functions
|
|
|
|
defp setup_lightweight_monitoring(parent_pid, owner_pid) do
|
|
# Simple one-time check for immediate child processes
|
|
spawn(fn ->
|
|
# Give process time to spawn children
|
|
:timer.sleep(100)
|
|
grant_access_to_linked_processes(parent_pid, owner_pid)
|
|
end)
|
|
end
|
|
|
|
defp grant_access_to_linked_processes(pid, owner_pid) do
|
|
case Process.info(pid, :links) do
|
|
{:links, links} ->
|
|
links
|
|
|> Enum.filter(&is_pid/1)
|
|
|> Enum.filter(&Process.alive?/1)
|
|
|> Enum.each(fn linked_pid ->
|
|
try do
|
|
Ecto.Adapters.SQL.Sandbox.allow(WandererApp.Repo, owner_pid, linked_pid)
|
|
rescue
|
|
# Ignore errors if already allowed
|
|
_ -> :ok
|
|
end
|
|
end)
|
|
|
|
nil ->
|
|
:ok
|
|
end
|
|
end
|
|
|
|
defp monitor_for_database_access_errors(monitored_pid, owner_pid) do
|
|
if Process.alive?(monitored_pid) do
|
|
# Monitor for error messages that indicate database access issues
|
|
receive do
|
|
{:DOWN, _ref, :process, ^monitored_pid, _reason} ->
|
|
:ok
|
|
after
|
|
100 ->
|
|
# Check for any processes that might need database access
|
|
check_and_grant_access_to_related_processes(monitored_pid, owner_pid)
|
|
monitor_for_database_access_errors(monitored_pid, owner_pid)
|
|
end
|
|
end
|
|
end
|
|
|
|
defp check_and_grant_access_to_related_processes(monitored_pid, owner_pid) do
|
|
# Get all processes related to the monitored process
|
|
related_processes = get_related_processes(monitored_pid)
|
|
|
|
Enum.each(related_processes, fn pid ->
|
|
grant_database_access(pid, owner_pid)
|
|
end)
|
|
end
|
|
|
|
defp get_related_processes(pid) do
|
|
# Get linked processes
|
|
linked =
|
|
case Process.info(pid, :links) do
|
|
{:links, links} -> Enum.filter(links, &is_pid/1)
|
|
nil -> []
|
|
end
|
|
|
|
# Get child processes
|
|
children = get_process_children(pid)
|
|
|
|
# Combine and filter for alive processes
|
|
(linked ++ children)
|
|
|> Enum.uniq()
|
|
|> Enum.filter(&Process.alive?/1)
|
|
end
|
|
|
|
defp get_process_children(pid) do
|
|
case Process.info(pid, :links) do
|
|
{:links, links} ->
|
|
links
|
|
|> Enum.filter(&is_pid/1)
|
|
|> Enum.filter(&Process.alive?/1)
|
|
|> Enum.filter(fn linked_pid ->
|
|
# Check if this is a child process (not just a linked process)
|
|
case Process.info(linked_pid, :parent) do
|
|
{:parent, ^pid} -> true
|
|
_ -> false
|
|
end
|
|
end)
|
|
|
|
nil ->
|
|
[]
|
|
end
|
|
end
|
|
|
|
defp get_supervisor_children(supervisor_pid) do
|
|
try do
|
|
case Supervisor.which_children(supervisor_pid) do
|
|
children when is_list(children) ->
|
|
children
|
|
|> Enum.map(fn {_id, pid, _type, _modules} -> pid end)
|
|
|> Enum.filter(&is_pid/1)
|
|
|> Enum.filter(&Process.alive?/1)
|
|
|
|
_ ->
|
|
[]
|
|
end
|
|
rescue
|
|
# Catch any exceptions
|
|
_ -> []
|
|
catch
|
|
# Catch exit signals when supervisor is shutting down
|
|
:exit, _ -> []
|
|
end
|
|
end
|
|
|
|
defp is_supervisor?(pid) do
|
|
try do
|
|
case Process.info(pid, :dictionary) do
|
|
{:dictionary, dict} ->
|
|
Keyword.get(dict, :"$initial_call") == {:supervisor, :init, 1}
|
|
|
|
_ ->
|
|
false
|
|
end
|
|
rescue
|
|
# Catch any exceptions
|
|
_ -> false
|
|
catch
|
|
# Catch exit signals when process is shutting down
|
|
:exit, _ -> false
|
|
end
|
|
end
|
|
end
|