test the api dsl

This commit is contained in:
Zach Daniel 2019-12-07 03:38:35 -05:00
parent eea6fc1003
commit 83d1761dd8
No known key found for this signature in database
GPG key ID: A57053A671EE649E
4 changed files with 223 additions and 9 deletions

View file

@ -17,6 +17,12 @@ defmodule Ash.Api do
"The maximum page size for any read action. Any request for a higher page size will simply use this number. Uses the smaller of the Api's or Resource's value.", "The maximum page size for any read action. Any request for a higher page size will simply use this number. Uses the smaller of the Api's or Resource's value.",
default_page_size: default_page_size:
"The default page size for any read action. If no page size is specified, this value is used. Uses the smaller of the Api's or Resource's value." "The default page size for any read action. If no page size is specified, this value is used. Uses the smaller of the Api's or Resource's value."
],
constraints: [
max_page_size:
{&Ash.Constraints.greater_than_zero?/1, "must be greater than zero"},
default_page_size:
{&Ash.Constraints.greater_than_zero?/1, "must be greater than zero"}
] ]
) )
@moduledoc """ @moduledoc """
@ -122,6 +128,9 @@ defmodule Ash.Api do
] ]
) )
@doc false
def parallel_side_load_schema(), do: @parallel_side_load_schema
@doc """ @doc """
By default, side loading data happens synchronously. In order to By default, side loading data happens synchronously. In order to
side load in parallel, you must start a task supervisor in your application side load in parallel, you must start a task supervisor in your application
@ -136,13 +145,13 @@ defmodule Ash.Api do
""" """
defmacro parallel_side_load(opts \\ []) do defmacro parallel_side_load(opts \\ []) do
quote bind_quoted: [opts: opts] do quote bind_quoted: [opts: opts] do
case Ashton.validate(opts, @parallel_side_load_schema) do case Ashton.validate(opts, Ash.Api.parallel_side_load_schema()) do
{:ok, opts} -> {:ok, opts} ->
@side_load_type :parallel @side_load_type :parallel
@side_load_config [ @side_load_config [
supervisor: config[:supervisor], supervisor: opts[:supervisor],
max_concurrency: config[:max_concurrency], max_concurrency: opts[:max_concurrency],
timeout: opts[:timeout], timeout: opts[:timeout],
shutdown: opts[:shutdown] shutdown: opts[:shutdown]
] ]
@ -157,18 +166,24 @@ defmodule Ash.Api do
end end
defmacro __before_compile__(env) do defmacro __before_compile__(env) do
quote do quote generated: true do
def default_page_size(), do: @default_page_size def default_page_size(), do: @default_page_size
def max_page_size(), do: @max_page_size def max_page_size(), do: @max_page_size
def mix_ins(), do: @mix_ins def mix_ins(), do: @mix_ins
def resources(), do: @resources def resources(), do: @resources
def side_load_config(), do: {@side_load_type, @side_load_config} def side_load_config(), do: {@side_load_type, @side_load_config}
def get_resource(mod) when mod in @resources, do: {:ok, mod}
def get_resource(name) do
Keyword.fetch(@named_resources, name)
end
if @interface? do if @interface? do
use Ash.Api.Interface use Ash.Api.Interface
end end
Enum.map(@mix_ins || [], fn hook_module -> Enum.map(@mix_ins, fn hook_module ->
code = hook_module.before_compile_hook(unquote(Macro.escape(env))) code = hook_module.before_compile_hook(unquote(Macro.escape(env)))
Module.eval_quoted(__MODULE__, code) Module.eval_quoted(__MODULE__, code)
end) end)

View file

@ -43,7 +43,6 @@ defmodule Ash.Api.Interface do
end end
def get(api, resource, id, params \\ %{}) do def get(api, resource, id, params \\ %{}) do
# TODO: Figure out this interface
params_with_filter = params_with_filter =
params params
|> Map.put_new(:filter, %{}) |> Map.put_new(:filter, %{})
@ -79,7 +78,13 @@ defmodule Ash.Api.Interface do
{:error, "no action provided, and no primary action found"} {:error, "no action provided, and no primary action found"}
action -> action ->
case api.get_resource(resource) do
{:ok, resource} ->
Ash.Actions.run_read_action(resource, action, api, params) Ash.Actions.run_read_action(resource, action, api, params)
:error ->
{:error, "no such resource"}
end
end end
end end
@ -95,7 +100,13 @@ defmodule Ash.Api.Interface do
{:error, "no action provided, and no primary action found"} {:error, "no action provided, and no primary action found"}
action -> action ->
case api.get_resource(resource) do
{:ok, resource} ->
Ash.Actions.run_create_action(resource, action, api, params) Ash.Actions.run_create_action(resource, action, api, params)
:error ->
{:error, "no such resource"}
end
end end
end end

188
test/api/api_test.exs Normal file
View file

@ -0,0 +1,188 @@
defmodule Ash.Test.Resource.ApiTest do
use ExUnit.Case, async: true
defmacrop defposts(do: body) do
quote do
defmodule Post do
use Ash.Resource, name: "posts", type: "post", primary_key: false
unquote(body)
end
end
end
defmacrop defapi(opts \\ [], do: body) do
quote do
defmodule Api do
use Ash.Api, unquote(opts)
unquote(body)
end
end
end
describe "representation" do
test "if `interface?: false` is set, interface functions are not defined" do
defapi(interface?: false) do
end
interface_funcs = [
get!: 3,
get!: 3,
read!: 2,
read: 2,
create!: 2,
create: 2,
update: 3,
update!: 3,
destroy: 3,
destroy: 3
]
for {func, arity} <- interface_funcs do
refute :erlang.function_exported(Api, func, arity)
end
end
end
describe "validation" do
test "it fails if `interface?` is not a boolean" do
assert_raise(
Ash.Error.ApiDslError,
"`use Ash.Test.Resource.ApiTest.Api, ...` interface? must be of type :boolean",
fn ->
defapi(interface?: 10) do
end
end
)
end
test "it fails if `max_page_size` is not an integer" do
assert_raise(
Ash.Error.ApiDslError,
"`use Ash.Test.Resource.ApiTest.Api, ...` max_page_size must be of type :integer",
fn ->
defapi(max_page_size: "ten") do
end
end
)
end
test "it fails if `max_page_size` is zero" do
assert_raise(
Ash.Error.ApiDslError,
"`use Ash.Test.Resource.ApiTest.Api, ...` max_page_size failed constraint: must be greater than zero",
fn ->
defapi(max_page_size: 0) do
end
end
)
end
test "it fails if `default_page_size` is not an integer" do
assert_raise(
Ash.Error.ApiDslError,
"`use Ash.Test.Resource.ApiTest.Api, ...` default_page_size must be of type :integer",
fn ->
defapi(default_page_size: "ten") do
end
end
)
end
test "it fails if `default_page_size` is zero" do
assert_raise(
Ash.Error.ApiDslError,
"`use Ash.Test.Resource.ApiTest.Api, ...` default_page_size failed constraint: must be greater than zero",
fn ->
defapi(default_page_size: 0) do
end
end
)
end
test "it fails if the parallel_side_load supervisor is not an atom" do
assert_raise(
Ash.Error.ApiDslError,
"option supervisor at parallel_side_load must be of type :atom",
fn ->
defapi do
parallel_side_load(supervisor: "foo")
end
end
)
end
test "it fails if the max_concurrency is not an integer" do
assert_raise(
Ash.Error.ApiDslError,
"option max_concurrency at parallel_side_load must be of type :integer",
fn ->
defapi do
parallel_side_load(supervisor: :foo, max_concurrency: "foo")
end
end
)
end
test "it fails if max_concurrency is zero" do
assert_raise(
Ash.Error.ApiDslError,
"option max_concurrency at parallel_side_load failed constraint: must be greater than zero",
fn ->
defapi do
parallel_side_load(supervisor: :foo, max_concurrency: 0)
end
end
)
end
test "it fails if the timeout is not an integer" do
assert_raise(
Ash.Error.ApiDslError,
"option timeout at parallel_side_load must be of type :integer",
fn ->
defapi do
parallel_side_load(supervisor: :foo, timeout: "foo")
end
end
)
end
test "it fails if timeout is negative" do
assert_raise(
Ash.Error.ApiDslError,
"option timeout at parallel_side_load failed constraint: must be positive",
fn ->
defapi do
parallel_side_load(supervisor: :foo, timeout: -1)
end
end
)
end
test "it fails if the shutdown is not an integer" do
assert_raise(
Ash.Error.ApiDslError,
"option shutdown at parallel_side_load must be of type :integer",
fn ->
defapi do
parallel_side_load(supervisor: :foo, shutdown: "foo")
end
end
)
end
test "it fails if shutdown is negative" do
assert_raise(
Ash.Error.ApiDslError,
"option shutdown at parallel_side_load failed constraint: must be positive",
fn ->
defapi do
parallel_side_load(supervisor: :foo, shutdown: -1)
end
end
)
end
end
end