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.",
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."
],
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 """
@ -122,6 +128,9 @@ defmodule Ash.Api do
]
)
@doc false
def parallel_side_load_schema(), do: @parallel_side_load_schema
@doc """
By default, side loading data happens synchronously. In order to
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
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} ->
@side_load_type :parallel
@side_load_config [
supervisor: config[:supervisor],
max_concurrency: config[:max_concurrency],
supervisor: opts[:supervisor],
max_concurrency: opts[:max_concurrency],
timeout: opts[:timeout],
shutdown: opts[:shutdown]
]
@ -157,18 +166,24 @@ defmodule Ash.Api do
end
defmacro __before_compile__(env) do
quote do
quote generated: true do
def default_page_size(), do: @default_page_size
def max_page_size(), do: @max_page_size
def mix_ins(), do: @mix_ins
def resources(), do: @resources
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
use Ash.Api.Interface
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)))
Module.eval_quoted(__MODULE__, code)
end)

View file

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

View file

@ -2,7 +2,7 @@ defmodule Ash.Error.ApiDslError do
defexception [:message, :path, :option, :using]
def message(%{message: message, path: nil, option: option, using: using}) do
"`use #{inspect(using)}, ...` #{option} #{message} "
"`use #{inspect(using)}, ...` #{option} #{message}"
end
def message(%{message: message, path: nil, option: option}) do

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