defmodule Uploadex.TestStorage do
@moduledoc """
Storage to be used for testing. It holds the files in memory (using a `Agent`).
## Configuring
To configure your app to use the `Uploadex.TestStorage`, just add this storage to your `Uploadex` module,
depending on the current environment:
defmodule MyApp.Uploader do
use Uploadex
@impl true
def get_fields(%User{}), do: :files
@impl true
def default_opts(Uploadex.FileStorage), do: [...]
def default_opts(Uploadex.TestStorage), do: []
@impl true
def storage(%User{}, _field) do
if test_environment?() do
{Uploadex.TestStorage, []}
else
{Uploadex.FileStorage, [...]}
end
end
@impl true
def accepted_extensions(%User{}, _field), do: ~w(.jpg .png)
defp test_environment?() do
# This env must be set in your config files depending on the environment.
Application.fetch_env!(:my_app, :environment) == :test
end
end
## Using
In your `ExUnit` tests, add a `setup` block starting the storage:
setup do
Uploadex.TestStorage.start_link()
:ok
end
Then, in the tests, you can use the `get_stored/1`, `get_deleted/1` and `get_opts`:
test "some test with files" do
assert ["document-1.pdf", "document-2.pdf"] == Uploadex.TestStorage.get_stored()
assert ["deleted-document.pdf"] == Uploadex.TestStorage.get_deleted()
assert [] == Uploadex.TestStorage.get_opts()
end
This module is just the base for testing, check `Uploadex.Testing` for a more convenient way to test uploads.
"""
use Agent
@behaviour Uploadex.Storage
def start_link(initial_state \\ %{}) when is_map(initial_state) do
agent_name = initial_state |> Map.get(:opts, []) |> get_agent_name()
default_state = %{stored: [], deleted: [], opts: [agent_name: agent_name]}
initial_state = Map.merge(default_state, Map.new(initial_state))
Agent.start_link(fn -> initial_state end, name: agent_name)
end
def get_stored(opts \\ []) do
agent_get(:stored, opts)
end
def get_deleted(opts \\ []) do
agent_get(:deleted, opts)
end
def get_opts(opts \\ []) do
agent_get(:opts, opts)
end
@impl true
def store(file, opts) do
update_fun = fn state ->
state
|> Map.update!(:stored, &(&1 ++ [file]))
|> Map.update!(:opts, &Keyword.merge(&1, opts))
end
agent_update(update_fun, opts)
end
@impl true
def delete(file, opts) do
update_fun = fn state ->
state
|> Map.update!(:deleted, &(&1 ++ [file]))
|> Map.update!(:opts, &Keyword.merge(&1, opts))
end
agent_update(update_fun, opts)
end
@impl true
def get_url(%{filename: filename}, _opts), do: {:ok, filename}
def get_url(filename, _opts) when is_binary(filename), do: {:ok, filename}
@impl true
def get_temporary_file(%{filename: filename}, _path, _opts), do: filename
def get_temporary_file(filename, _path, _opts) when is_binary(filename), do: filename
defp agent_get(state_field, opts) when is_atom(state_field) do
opts
|> get_agent_name()
|> Agent.get(fn state -> Map.fetch!(state, state_field) end)
end
defp agent_update(update_fun, opts) when is_function(update_fun) do
opts
|> get_agent_name()
|> Agent.update(update_fun)
end
defp get_agent_name(opts) do
current_pid_as_atom = self() |> inspect() |> String.to_atom()
opts[:agent_name] || current_pid_as_atom
end
end