Compare commits
63 commits
Author | SHA1 | Date | |
---|---|---|---|
a750c4a7ff | |||
e77bd9de2a | |||
39bd04b4af | |||
13c5619376 | |||
585766dd43 | |||
3e18763805 | |||
5e3aa634ec | |||
90aad764f5 | |||
b862125133 | |||
275110ba13 | |||
ffc5407b2c | |||
64d17a44c5 | |||
3f17cd9e99 | |||
3a0ddd207e | |||
324e15ca46 | |||
43f5360130 | |||
43535e1d28 | |||
3bbc2d62a3 | |||
975f7335e5 | |||
e11660011e | |||
01962eb4c9 | |||
a217a0fe11 | |||
60be63d5d2 | |||
8c4e71614d | |||
e6df6910c7 | |||
f767b723e0 | |||
df1f1309e4 | |||
7b041d3055 | |||
334363db70 | |||
5d5edad3e5 | |||
e60facc798 | |||
8cc0de70c4 | |||
3eb46cb485 | |||
452f827067 | |||
e590e8d9e8 | |||
e074b261d2 | |||
fbabaa6c93 | |||
59ea39c8ab | |||
e18319f525 | |||
957e2845c7 | |||
00cdf8106d | |||
d756b1ac12 | |||
e8fb1bb305 | |||
8dd043f13a | |||
9a26e7a74b | |||
ccbe9a124f | |||
5d767a2e91 | |||
13bd3c81b7 | |||
a874167d10 | |||
81b312b172 | |||
f9dc1112f0 | |||
c5adaf448b | |||
85aa3e2f6e | |||
f8480ec8e7 | |||
|
d5be51556d | ||
ebc07800b4 | |||
5368a030dd | |||
ef5d6462b9 | |||
f46d9bb6b9 | |||
4786ee97e6 | |||
0d42238d6f | |||
cb2d0376b5 | |||
aa65f4912b |
25 changed files with 377 additions and 99 deletions
|
@ -3,6 +3,8 @@ spark_locals_without_parens = [
|
|||
after_build: 2,
|
||||
attribute: 2,
|
||||
attribute: 3,
|
||||
auto_build: 1,
|
||||
auto_load: 1,
|
||||
before_build: 1,
|
||||
before_build: 2,
|
||||
domain: 1,
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
elixir 1.16.3
|
||||
erlang 27.0
|
||||
elixir 1.17.2
|
||||
erlang 27.0.1
|
||||
|
|
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"arities",
|
||||
"Cardassia",
|
||||
"recase",
|
||||
"Slickback"
|
||||
]
|
||||
}
|
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -5,6 +5,35 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
|
|||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v0.9.1](https://harton.dev/james/smokestack/compare/v0.9.0...v0.9.1) (2024-05-30)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* allow auto builds to be replaced by explicit relate commands.
|
||||
|
||||
## [v0.9.0](https://harton.dev/james/smokestack/compare/v0.8.1...v0.9.0) (2024-05-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Auto build/load factory options. (#83)
|
||||
|
||||
## [v0.8.1](https://harton.dev/james/smokestack/compare/v0.8.0...v0.8.1) (2024-05-28)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Include `:variant` in option schema.
|
||||
|
||||
* bug with generator arguments.
|
||||
|
||||
## [v0.8.0](https://harton.dev/james/smokestack/compare/v0.7.0...v0.8.0) (2024-05-26)
|
||||
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ add it directly to your `mix.exs`:
|
|||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:smokestack, "~> 0.8.0"},
|
||||
{:smokestack, "~> 0.9.1"},
|
||||
]
|
||||
end
|
||||
```
|
||||
|
|
|
@ -54,6 +54,10 @@ Define factories for a resource
|
|||
|
||||
* `:variant` (`t:atom/0`) - The name of a factory variant The default value is `:default`.
|
||||
|
||||
* `:auto_build` (one or a list of `t:atom/0`) - A list of relationships that should always be built when building this factory The default value is `[]`.
|
||||
|
||||
* `:auto_load` - An Ash "load statement" to always apply when building this factory The default value is `[]`.
|
||||
|
||||
|
||||
|
||||
##### after_build
|
||||
|
@ -166,6 +170,8 @@ Define factories for a resource
|
|||
| Name | Type | Default | Docs |
|
||||
|------|------|---------|------|
|
||||
| [`domain`](#smokestack-factory-domain){: #smokestack-factory-domain } | `module` | | The Ash Domain to use when evaluating loads |
|
||||
| [`auto_build`](#smokestack-factory-auto_build){: #smokestack-factory-auto_build } | `atom \| list(atom)` | `[]` | A list of relationships that should always be built when building this factory |
|
||||
| [`auto_load`](#smokestack-factory-auto_load){: #smokestack-factory-auto_load } | `atom \| keyword \| list(atom \| keyword)` | `[]` | An Ash "load statement" to always apply when building this factory |
|
||||
|
||||
|
||||
## smokestack.factory.after_build
|
||||
|
|
|
@ -112,7 +112,7 @@ defmodule Smokestack do
|
|||
|
||||
use Dsl, default_extensions: [extensions: [Smokestack.Dsl]]
|
||||
alias Ash.Resource
|
||||
alias Smokestack.{Builder, Dsl.Info, ParamBuilder, RecordBuilder}
|
||||
alias Smokestack.{Builder, ParamBuilder, RecordBuilder}
|
||||
|
||||
@type t :: module
|
||||
|
||||
|
@ -196,11 +196,7 @@ defmodule Smokestack do
|
|||
@spec params(Resource.t(), [Smokestack.param_option()]) ::
|
||||
{:ok, ParamBuilder.result()} | {:error, any}
|
||||
def params(resource, options \\ []) do
|
||||
{variant, options} = Keyword.pop(options, :variant, :default)
|
||||
|
||||
with {:ok, factory} <- Info.factory(__MODULE__, resource, variant) do
|
||||
Builder.build(ParamBuilder, factory, options)
|
||||
end
|
||||
Builder.build(__MODULE__, resource, ParamBuilder, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -229,11 +225,7 @@ defmodule Smokestack do
|
|||
@spec insert(Resource.t(), [Smokestack.insert_option()]) ::
|
||||
{:ok, RecordBuilder.result()} | {:error, any}
|
||||
def insert(resource, options \\ []) do
|
||||
{variant, options} = Keyword.pop(options, :variant, :default)
|
||||
|
||||
with {:ok, factory} <- Info.factory(__MODULE__, resource, variant) do
|
||||
Builder.build(RecordBuilder, factory, options)
|
||||
end
|
||||
Builder.build(__MODULE__, resource, RecordBuilder, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -3,7 +3,8 @@ defmodule Smokestack.Builder do
|
|||
A generic behaviour for "building things".
|
||||
"""
|
||||
|
||||
alias Smokestack.Dsl.Factory
|
||||
alias Ash.Resource
|
||||
alias Smokestack.Dsl.{Factory, Info}
|
||||
alias Spark.Options
|
||||
|
||||
@type result :: any
|
||||
|
@ -18,14 +19,18 @@ defmodule Smokestack.Builder do
|
|||
@doc """
|
||||
Provide a schema for validating options.
|
||||
"""
|
||||
@callback option_schema(nil | Factory.t()) :: {:ok, Options.schema()} | {:error, any}
|
||||
@callback option_schema(nil | Factory.t()) ::
|
||||
{:ok, Options.schema(), String.t()} | {:error, any}
|
||||
|
||||
@doc """
|
||||
Given a builder and a factory, validate it's options and call the builder.
|
||||
Find the appropriate factory, validate options and run the builder.
|
||||
"""
|
||||
@spec build(t, Factory.t(), Keyword.t()) :: {:ok, result} | {:error, error}
|
||||
def build(builder, factory, options) do
|
||||
with {:ok, schema} <- builder.option_schema(factory),
|
||||
@spec build(Smokestack.t(), Resource.t(), t, Keyword.t()) :: {:ok, result} | {:error, error}
|
||||
def build(factory_module, resource, builder, options) do
|
||||
with {:ok, our_schema} <- variant_schema(factory_module, resource),
|
||||
{:ok, factory} <- Info.factory(factory_module, resource, options[:variant] || :default),
|
||||
{:ok, builder_schema, section} <- builder.option_schema(factory),
|
||||
schema <- Options.merge(our_schema, builder_schema, section),
|
||||
{:ok, options} <- Options.validate(options, schema) do
|
||||
builder.build(factory, options)
|
||||
end
|
||||
|
@ -36,7 +41,29 @@ defmodule Smokestack.Builder do
|
|||
"""
|
||||
@spec docs(t, nil | Factory.t()) :: String.t()
|
||||
def docs(builder, factory) do
|
||||
{:ok, schema} = builder.option_schema(factory)
|
||||
{:ok, schema, _} = builder.option_schema(factory)
|
||||
Options.docs(schema)
|
||||
end
|
||||
|
||||
defp variant_schema(factory_module, resource) do
|
||||
case Info.variants(factory_module, resource) do
|
||||
[] ->
|
||||
{:error,
|
||||
"There are no factories defined for the resource `#{inspect(resource)}` in the `#{inspect(factory_module)}` module."}
|
||||
|
||||
variants ->
|
||||
our_schema = [
|
||||
variant: [
|
||||
type: {:in, variants},
|
||||
required: false,
|
||||
default: :default,
|
||||
doc: """
|
||||
The name of the factory variant to use.
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
{:ok, our_schema}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ defmodule Smokestack.FactoryBuilder do
|
|||
|> remove_overridden_attrs(overrides)
|
||||
|> Enum.reduce(overrides, fn attr, attrs ->
|
||||
generator = maybe_initialise_generator(attr)
|
||||
value = Template.generate(generator, attr, options)
|
||||
value = Template.generate(generator, attrs, options)
|
||||
Map.put(attrs, attr.name, value)
|
||||
end)
|
||||
|
||||
|
@ -48,7 +48,7 @@ defmodule Smokestack.FactoryBuilder do
|
|||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema()} | {:error, error}
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema(), String.t()} | {:error, error}
|
||||
def option_schema(factory) do
|
||||
attr_keys =
|
||||
if factory do
|
||||
|
@ -80,7 +80,7 @@ defmodule Smokestack.FactoryBuilder do
|
|||
```
|
||||
"""
|
||||
]
|
||||
]}
|
||||
], "Options for building instances"}
|
||||
end
|
||||
|
||||
defp maybe_initialise_generator(attr) do
|
||||
|
|
|
@ -30,9 +30,9 @@ defmodule Smokestack.ManyBuilder do
|
|||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema()} | {:error, error}
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema(), String.t()} | {:error, error}
|
||||
def option_schema(factory) do
|
||||
with {:ok, related_schema} <- RelatedBuilder.option_schema(factory) do
|
||||
with {:ok, related_schema, related_section} <- RelatedBuilder.option_schema(factory) do
|
||||
schema =
|
||||
[
|
||||
count: [
|
||||
|
@ -53,16 +53,16 @@ defmodule Smokestack.ManyBuilder do
|
|||
"""
|
||||
]
|
||||
]
|
||||
|> Options.merge(related_schema, "Options for building relationships")
|
||||
|> Options.merge(related_schema, related_section)
|
||||
|
||||
{:ok, schema}
|
||||
{:ok, schema, "Options for building multiple instances"}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_build(factory, how_many, options) when how_many > 0 and is_integer(how_many) do
|
||||
1..how_many
|
||||
|> Enum.reduce_while({:ok, []}, fn _, {:ok, results} ->
|
||||
case Builder.build(RelatedBuilder, factory, options) do
|
||||
case RelatedBuilder.build(factory, options) do
|
||||
{:ok, attrs} -> {:cont, {:ok, [attrs | results]}}
|
||||
{:error, reason} -> {:halt, {:error, reason}}
|
||||
end
|
||||
|
|
|
@ -69,10 +69,10 @@ defmodule Smokestack.ParamBuilder do
|
|||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema()} | {:error, error}
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema(), String.t()} | {:error, error}
|
||||
def option_schema(factory) do
|
||||
with {:ok, related_schema} <- RelatedBuilder.option_schema(factory),
|
||||
{:ok, many_schema} <- ManyBuilder.option_schema(factory) do
|
||||
with {:ok, related_schema, related_section} <- RelatedBuilder.option_schema(factory),
|
||||
{:ok, many_schema, many_section} <- ManyBuilder.option_schema(factory) do
|
||||
many_schema =
|
||||
Keyword.update!(many_schema, :count, fn current ->
|
||||
current
|
||||
|
@ -178,10 +178,10 @@ defmodule Smokestack.ParamBuilder do
|
|||
|
||||
schema =
|
||||
our_schema
|
||||
|> Options.merge(many_schema, "Options for building multiple instances")
|
||||
|> Options.merge(related_schema, "Options for building relationships")
|
||||
|> Options.merge(many_schema, many_section)
|
||||
|> Options.merge(related_schema, related_section)
|
||||
|
||||
{:ok, schema}
|
||||
{:ok, schema, "Options for building parameters"}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -189,7 +189,7 @@ defmodule Smokestack.ParamBuilder do
|
|||
{my_opts, their_opts} = split_options(options)
|
||||
their_opts = Keyword.put(their_opts, :count, count)
|
||||
|
||||
with {:ok, attr_list} <- Builder.build(ManyBuilder, factory, their_opts) do
|
||||
with {:ok, attr_list} <- ManyBuilder.build(factory, their_opts) do
|
||||
attr_list
|
||||
|> convert_keys(my_opts)
|
||||
|> maybe_nest_result(my_opts[:nest])
|
||||
|
@ -200,7 +200,7 @@ defmodule Smokestack.ParamBuilder do
|
|||
defp do_build(factory, options, _) do
|
||||
{my_opts, their_opts} = split_options(options)
|
||||
|
||||
with {:ok, attrs} <- Builder.build(RelatedBuilder, factory, their_opts) do
|
||||
with {:ok, attrs} <- RelatedBuilder.build(factory, their_opts) do
|
||||
attrs
|
||||
|> convert_keys(my_opts)
|
||||
|> maybe_nest_result(my_opts[:nest])
|
||||
|
|
|
@ -31,10 +31,10 @@ defmodule Smokestack.RecordBuilder do
|
|||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema()} | {:error, error}
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema(), String.t()} | {:error, error}
|
||||
def option_schema(factory) do
|
||||
with {:ok, related_schema} <- RelatedBuilder.option_schema(factory),
|
||||
{:ok, many_schema} <- ManyBuilder.option_schema(factory) do
|
||||
with {:ok, related_schema, related_section} <- RelatedBuilder.option_schema(factory),
|
||||
{:ok, many_schema, many_section} <- ManyBuilder.option_schema(factory) do
|
||||
load_type =
|
||||
if factory do
|
||||
loadable_names =
|
||||
|
@ -87,10 +87,10 @@ defmodule Smokestack.RecordBuilder do
|
|||
"""
|
||||
]
|
||||
]
|
||||
|> Options.merge(many_schema, "Options for building multiple instances")
|
||||
|> Options.merge(related_schema, "Options for building relationships")
|
||||
|> Options.merge(many_schema, many_section)
|
||||
|> Options.merge(related_schema, related_section)
|
||||
|
||||
{:ok, schema}
|
||||
{:ok, schema, "Options for building records"}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -98,22 +98,22 @@ defmodule Smokestack.RecordBuilder do
|
|||
{load, options} = Keyword.pop(options, :load, [])
|
||||
options = Keyword.put(options, :count, count)
|
||||
|
||||
with {:ok, attr_list} <- Builder.build(ManyBuilder, factory, options),
|
||||
with {:ok, attr_list} <- ManyBuilder.build(factory, options),
|
||||
{:ok, record_list} <- seed(attr_list, factory) do
|
||||
record_list
|
||||
|> maybe_hook(factory)
|
||||
|> maybe_load(factory, load)
|
||||
|> maybe_load(factory, List.wrap(load))
|
||||
end
|
||||
end
|
||||
|
||||
defp do_build(factory, options, _count) do
|
||||
{load, options} = Keyword.pop(options, :load, [])
|
||||
|
||||
with {:ok, attrs} <- Builder.build(RelatedBuilder, factory, options),
|
||||
with {:ok, attrs} <- RelatedBuilder.build(factory, options),
|
||||
{:ok, record} <- seed(attrs, factory) do
|
||||
record
|
||||
|> maybe_hook(factory)
|
||||
|> maybe_load(factory, load)
|
||||
|> maybe_load(factory, List.wrap(load))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -145,13 +145,18 @@ defmodule Smokestack.RecordBuilder do
|
|||
|> Resource.put_metadata(:variant, factory.variant)
|
||||
end
|
||||
|
||||
defp maybe_load(record_or_records, _factory, []), do: {:ok, record_or_records}
|
||||
defp maybe_load(record_or_records, %{auto_load: []}, []), do: {:ok, record_or_records}
|
||||
|
||||
defp maybe_load(_record_or_records, factory, _load) when is_nil(factory.domain),
|
||||
do: {:error, "Unable to perform `load` operation without an Domain."}
|
||||
|
||||
defp maybe_load(record_or_records, factory, load),
|
||||
do: Ash.load(record_or_records, load, domain: factory.domain)
|
||||
defp maybe_load(record_or_records, factory, load) do
|
||||
load =
|
||||
factory.auto_load
|
||||
|> Enum.concat(load)
|
||||
|
||||
Ash.load(record_or_records, load, domain: factory.domain)
|
||||
end
|
||||
|
||||
defp maybe_hook(records, factory) when is_list(records) do
|
||||
Enum.map(records, fn record ->
|
||||
|
|
|
@ -30,8 +30,7 @@ defmodule Smokestack.RelatedBuilder do
|
|||
@impl true
|
||||
@spec build(Factory.t(), [option]) :: {:ok, result} | {:error, error}
|
||||
def build(factory, options) do
|
||||
with {:ok, attrs} <-
|
||||
Builder.build(FactoryBuilder, factory, Keyword.drop(options, [:build, :relate])),
|
||||
with {:ok, attrs} <- FactoryBuilder.build(factory, Keyword.drop(options, [:build, :relate])),
|
||||
{:ok, attrs} <- maybe_build_related(factory, attrs, options) do
|
||||
maybe_relate(factory, attrs, options)
|
||||
end
|
||||
|
@ -39,9 +38,9 @@ defmodule Smokestack.RelatedBuilder do
|
|||
|
||||
@doc false
|
||||
@impl true
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema()} | {:error, error}
|
||||
@spec option_schema(nil | Factory.t()) :: {:ok, Options.schema(), String.t()} | {:error, error}
|
||||
def option_schema(factory) do
|
||||
with {:ok, factory_schema} <- FactoryBuilder.option_schema(factory) do
|
||||
with {:ok, factory_schema, factory_section} <- FactoryBuilder.option_schema(factory) do
|
||||
build_type =
|
||||
if factory do
|
||||
relationship_names =
|
||||
|
@ -131,14 +130,14 @@ defmodule Smokestack.RelatedBuilder do
|
|||
|
||||
```elixir
|
||||
author = insert!(Author)
|
||||
post = insert!(Post, relate: [author: Author])
|
||||
post = insert!(Post, relate: [author: author])
|
||||
```
|
||||
"""
|
||||
]
|
||||
]
|
||||
|> Options.merge(factory_schema, "Options for building instances")
|
||||
|> Options.merge(factory_schema, factory_section)
|
||||
|
||||
{:ok, schema}
|
||||
{:ok, schema, "Options for building relationships"}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -146,10 +145,12 @@ defmodule Smokestack.RelatedBuilder do
|
|||
options
|
||||
|> Keyword.get(:build, [])
|
||||
|> List.wrap()
|
||||
|> Enum.concat(factory.auto_build)
|
||||
|> Enum.map(fn
|
||||
{key, value} -> {key, value}
|
||||
key when is_atom(key) -> {key, []}
|
||||
end)
|
||||
|> remove_explicit_relates(options)
|
||||
|> Enum.reduce_while({:ok, attrs}, fn {relationship, nested_builds}, {:ok, attrs} ->
|
||||
case build_related(
|
||||
attrs,
|
||||
|
@ -163,6 +164,18 @@ defmodule Smokestack.RelatedBuilder do
|
|||
end)
|
||||
end
|
||||
|
||||
defp remove_explicit_relates(builds, options) do
|
||||
relates =
|
||||
options[:relate]
|
||||
|> List.wrap()
|
||||
|> Map.new()
|
||||
|
||||
builds
|
||||
|> Enum.reject(fn {key, _value} ->
|
||||
is_map_key(relates, key)
|
||||
end)
|
||||
end
|
||||
|
||||
defp build_related(attrs, relationship, factory, options) do
|
||||
ash_relationship = Resource.Info.relationship(factory.resource, relationship)
|
||||
build_related(attrs, relationship, factory, options, ash_relationship)
|
||||
|
@ -182,8 +195,7 @@ defmodule Smokestack.RelatedBuilder do
|
|||
|> Keyword.put(:attrs, %{})
|
||||
|
||||
with {:ok, related_factory} <- find_related_factory(relationship.destination, factory),
|
||||
{:ok, related_attrs} <-
|
||||
Builder.build(__MODULE__, related_factory, related_options) do
|
||||
{:ok, related_attrs} <- __MODULE__.build(related_factory, related_options) do
|
||||
case relationship.cardinality do
|
||||
:one ->
|
||||
{:ok, Map.put(attrs, relationship.name, related_attrs)}
|
||||
|
|
|
@ -8,6 +8,8 @@ defmodule Smokestack.Dsl.Factory do
|
|||
defstruct __identifier__: nil,
|
||||
after_build: [],
|
||||
attributes: [],
|
||||
auto_load: [],
|
||||
auto_build: [],
|
||||
before_build: [],
|
||||
domain: nil,
|
||||
module: nil,
|
||||
|
@ -22,6 +24,8 @@ defmodule Smokestack.Dsl.Factory do
|
|||
__identifier__: any,
|
||||
after_build: [AfterBuild.t()],
|
||||
attributes: [Attribute.t()],
|
||||
auto_load: [atom] | Keyword.t(),
|
||||
auto_build: [atom],
|
||||
before_build: [BeforeBuild.t()],
|
||||
domain: nil,
|
||||
module: module,
|
||||
|
@ -56,6 +60,18 @@ defmodule Smokestack.Dsl.Factory do
|
|||
required: false,
|
||||
doc: "The name of a factory variant",
|
||||
default: :default
|
||||
],
|
||||
auto_build: [
|
||||
type: {:wrap_list, :atom},
|
||||
required: false,
|
||||
doc: "A list of relationships that should always be built when building this factory",
|
||||
default: []
|
||||
],
|
||||
auto_load: [
|
||||
type: {:wrap_list, {:or, [:atom, :keyword_list]}},
|
||||
required: false,
|
||||
doc: "An Ash \"load statement\" to always apply when building this factory",
|
||||
default: []
|
||||
]
|
||||
],
|
||||
entities: [
|
||||
|
|
|
@ -35,4 +35,16 @@ defmodule Smokestack.Dsl.Info do
|
|||
{:error, reason} -> raise reason
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
List all variants available for a resource.
|
||||
"""
|
||||
@spec variants(Smokestack.t(), Resource.t()) :: [atom]
|
||||
def variants(factory, resource) do
|
||||
factory
|
||||
|> Extension.get_entities([:smokestack])
|
||||
|> Enum.filter(&(is_struct(&1, Factory) && &1.resource == resource))
|
||||
|> Enum.map(& &1.variant)
|
||||
|> Enum.uniq()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,11 @@ defmodule Smokestack.Dsl.Verifier do
|
|||
@impl true
|
||||
@spec verify(Dsl.t()) :: :ok | {:error, DslError.t()}
|
||||
def verify(dsl_state) do
|
||||
error_info = %{module: Verifier.get_persisted(dsl_state, :module), path: [:smokestack]}
|
||||
error_info = %{
|
||||
module: Verifier.get_persisted(dsl_state, :module),
|
||||
path: [:smokestack],
|
||||
dsl_state: dsl_state
|
||||
}
|
||||
|
||||
factories =
|
||||
dsl_state
|
||||
|
@ -68,7 +72,9 @@ defmodule Smokestack.Dsl.Verifier do
|
|||
error_info =
|
||||
Map.merge(error_info, %{resource: factory.resource, path: [:factory | error_info.path]})
|
||||
|
||||
with :ok <- verify_unique_attributes(factory, error_info) do
|
||||
with :ok <- verify_unique_attributes(factory, error_info),
|
||||
:ok <- verify_auto_build(factory, error_info),
|
||||
:ok <- verify_auto_load(factory, error_info) do
|
||||
factory
|
||||
|> Map.get(:attributes, [])
|
||||
|> Enum.filter(&is_struct(&1, Attribute))
|
||||
|
@ -186,4 +192,76 @@ defmodule Smokestack.Dsl.Verifier do
|
|||
)}
|
||||
end
|
||||
end
|
||||
|
||||
defp verify_auto_build(factory, error_info) do
|
||||
error_info = %{error_info | path: [:auto_build | error_info.path]}
|
||||
|
||||
Enum.reduce_while(factory.auto_build, :ok, fn relationship, :ok ->
|
||||
error_info = %{error_info | path: [relationship | error_info.path]}
|
||||
|
||||
with {:ok, relationship} <- verify_relationship(factory.resource, relationship, error_info),
|
||||
:ok <- verify_factory_exists(relationship.destination, error_info) do
|
||||
{:cont, :ok}
|
||||
else
|
||||
{:error, error} -> {:halt, {:error, error}}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp verify_relationship(resource, relationship, error_info) do
|
||||
case Info.relationship(resource, relationship) do
|
||||
nil ->
|
||||
{:error,
|
||||
DslError.exception(
|
||||
module: error_info.module,
|
||||
path: Enum.reverse(error_info.path),
|
||||
message:
|
||||
"The resource `#{inspect(resource)}` has no relationship named `#{inspect(relationship)}`."
|
||||
)}
|
||||
|
||||
relationship ->
|
||||
{:ok, relationship}
|
||||
end
|
||||
end
|
||||
|
||||
defp verify_factory_exists(resource, error_info) do
|
||||
factory_exists? =
|
||||
error_info.dsl_state
|
||||
|> Verifier.get_entities([:smokestack])
|
||||
|> Enum.any?(&(is_struct(&1, Factory) && &1.resource == resource))
|
||||
|
||||
if factory_exists? do
|
||||
:ok
|
||||
else
|
||||
{:error,
|
||||
DslError.exception(
|
||||
module: error_info.module,
|
||||
path: Enum.reverse(error_info.path),
|
||||
message: "No factories defined for resource `#{inspect(resource)}`."
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
||||
defp verify_auto_load(factory, error_info) do
|
||||
error_info = %{error_info | path: [:auto_load | error_info.path]}
|
||||
|
||||
Enum.reduce_while(factory.auto_load, :ok, fn load, :ok ->
|
||||
error_info = %{error_info | path: [load | error_info.path]}
|
||||
|
||||
with nil <- Info.calculation(factory.resource, load),
|
||||
nil <- Info.aggregate(factory.resource, load),
|
||||
nil <- Info.relationship(factory.resource, load) do
|
||||
{:halt,
|
||||
{:error,
|
||||
DslError.exception(
|
||||
module: error_info.module,
|
||||
path: Enum.reverse(error_info.path),
|
||||
message:
|
||||
"Expected an aggregate, calculation or relationship named `#{inspect(load)}` on resource `#{inspect(factory.resource)}`"
|
||||
)}}
|
||||
else
|
||||
_ -> {:cont, :ok}
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
4
mix.exs
4
mix.exs
|
@ -1,7 +1,7 @@
|
|||
defmodule Smokestack.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@version "0.8.0"
|
||||
@version "0.9.1"
|
||||
|
||||
@moduledoc """
|
||||
Test factories for Ash resources.
|
||||
|
@ -103,7 +103,7 @@ defmodule Smokestack.MixProject do
|
|||
{:faker, "~> 0.18", opts},
|
||||
{:git_ops, "~> 2.6", opts},
|
||||
{:mix_audit, "~> 2.1", opts},
|
||||
{:recase, "~> 0.7"},
|
||||
{:recase, "~> 0.8"},
|
||||
{:spark, "~> 2.1"}
|
||||
]
|
||||
end
|
||||
|
|
41
mix.lock
41
mix.lock
|
@ -1,37 +1,44 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "3.0.7", "6c37e092f53b1b21eb89596f600a652b2a601f84378f44fd5dd1cdec72eb1cc2", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, ">= 0.8.1 and < 1.0.0-0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.1.18 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9288ddb50fe727096c6f63fd82c631de2505dcd29bdfa50b5dc13c865f0bf434"},
|
||||
"ash": {:hex, :ash, "3.4.6", "3210de7f0dff06d30f334fb05da13929779d70d861f95443c88ac4edab5a4500", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.11 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.9", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.22 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4502b9cc192ac1e68cbbf7e7dd3de4180715190e320bc0a5d477af68d079f006"},
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
||||
"credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"},
|
||||
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
|
||||
"doctor": {:hex, :doctor, "0.21.0", "20ef89355c67778e206225fe74913e96141c4d001cb04efdeba1a2a9704f1ab5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "a227831daa79784eb24cdeedfa403c46a4cb7d0eab0e31232ec654314447e4e0"},
|
||||
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
|
||||
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
|
||||
"earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.40", "f3534689f6b58f48aa3a9ac850d4f05832654fe257bf0549c08cc290035f70d5", [:mix], [], "hexpm", "cdb34f35892a45325bad21735fadb88033bcb7c4c296a999bde769783f53e46a"},
|
||||
"ecto": {:hex, :ecto, "3.12.2", "bae2094f038e9664ce5f089e5f3b6132a535d8b018bd280a485c2f33df5c0ce1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e67c70f3a71c6afe80d946d3ced52ecc57c53c9829791bfff1830ff5a1f0c"},
|
||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
||||
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
|
||||
"ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.33.0", "690562b153153c7e4d455dc21dab86e445f66ceba718defe64b0ef6f0bd83ba0", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "3f69adc28274cb51be37d09b03e4565232862a4b10288a3894587b0131412124"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
|
||||
"faker": {:hex, :faker, "0.18.0", "943e479319a22ea4e8e39e8e076b81c02827d9302f3d32726c5bf82f430e6e14", [:mix], [], "hexpm", "bfbdd83958d78e2788e99ec9317c4816e651ad05e24cfd1196ce5db5b3e81797"},
|
||||
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
||||
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
|
||||
"git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"},
|
||||
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
||||
"glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"},
|
||||
"igniter": {:hex, :igniter, "0.3.24", "791a91650ffab9d66b9a3011c66491f767577ad55c363f820cc188554207ee6f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}, {:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: false]}], "hexpm", "2e1d336534c6129bae0db043fae650303b96974c0488c290191d6d4c61ec9a9f"},
|
||||
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
|
||||
"iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"},
|
||||
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
|
||||
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
|
||||
"mix_audit": {:hex, :mix_audit, "2.1.3", "c70983d5cab5dca923f9a6efe559abfb4ec3f8e87762f02bab00fa4106d17eda", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "8c3987100b23099aea2f2df0af4d296701efd031affb08d0746b2be9e35988ec"},
|
||||
"mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
||||
"reactor": {:hex, :reactor, "0.8.2", "b2be82b1c3402537d06a8f85bb1849f72cb6b4be140495cb8956de7aec2fdebd", [:mix], [{:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c35eb23b77cc77ba922af108722ac93257899e35cfdd18882f0e659ad2cac9f3"},
|
||||
"recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"},
|
||||
"sourceror": {:hex, :sourceror, "1.2.1", "b415255ad8bd05f0e859bb3d7ea617f6c2a4a405f2a534a231f229bd99b89f8b", [:mix], [], "hexpm", "e4d97087e67584a7585b5fe3d5a71bf8e7332f795dd1a44983d750003d5e750c"},
|
||||
"spark": {:hex, :spark, "2.1.22", "a36400eede64c51af578de5fdb5a5aaa3e0811da44bcbe7545fce059bd2a990b", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f764611d0b15ac132e72b2326539acc11fc4e63baa3e429f541bca292b5f7064"},
|
||||
"owl": {:hex, :owl, "0.11.0", "2cd46185d330aa2400f1c8c3cddf8d2ff6320baeff23321d1810e58127082cae", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "73f5783f0e963cc04a061be717a0dbb3e49ae0c4bfd55fb4b78ece8d33a65efe"},
|
||||
"reactor": {:hex, :reactor, "0.9.1", "082f8e9b1fd7586c0a016c2fb533835fec7eaef5ffb0263abb4473106c20b1ca", [:mix], [{:igniter, "~> 0.2", [hex: :igniter, repo: "hexpm", optional: false]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7191ddf95fdd2b65770a57a2e38dd502a94909e51ac8daf497330e67fc032dc3"},
|
||||
"recase": {:hex, :recase, "0.8.1", "ab98cd35857a86fa5ca99036f575241d71d77d9c2ab0c39aacf1c9b61f6f7d1d", [:mix], [], "hexpm", "9fd8d63e7e43bd9ea385b12364e305778b2bbd92537e95c4b2e26fc507d5e4c2"},
|
||||
"rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"},
|
||||
"sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"},
|
||||
"spark": {:hex, :spark, "2.2.23", "78f0a1b0b713a91ad556fe9dc19ec92d977aaa0803cce2e255d90e58b9045c2a", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "a354b5cd7c3f021e3cd1da5a033b7643fe7b3c71c96b96d9f500a742f40c94db"},
|
||||
"spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"},
|
||||
"splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"},
|
||||
"stream_data": {:hex, :stream_data, "1.0.0", "c1380747a4650902732696861d5cb66ad3cb1cc93f31c2c8498bf87cddbabe2d", [:mix], [], "hexpm", "acd53e27c66c617d466f42ec77a7f59e5751f6051583c621ccdb055b9690435d"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||
"stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"},
|
||||
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
|
||||
"ucwidth": {:hex, :ucwidth, "0.2.0", "1f0a440f541d895dff142275b96355f7e91e15bca525d4a0cc788ea51f0e3441", [:mix], [], "hexpm", "c1efd1798b8eeb11fb2bec3cafa3dd9c0c3647bee020543f0340b996177355bf"},
|
||||
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"},
|
||||
"yaml_elixir": {:hex, :yaml_elixir, "2.11.0", "9e9ccd134e861c66b84825a3542a1c22ba33f338d82c07282f4f1f52d847bd50", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "53cc28357ee7eb952344995787f4bb8cc3cecbf189652236e9b163e8ce1bc242"},
|
||||
}
|
||||
|
|
|
@ -7,14 +7,13 @@ defmodule Smokestack.FactoryBuilderTest do
|
|||
|
||||
test "it can build attributes from a factory" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, attrs} = Builder.build(FactoryBuilder, factory, [])
|
||||
assert {:ok, attrs} = FactoryBuilder.build(factory, [])
|
||||
assert byte_size(attrs[:title]) > 0
|
||||
end
|
||||
|
||||
test "it allows attributes to be overridden" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
|
||||
assert {:ok, %{title: "wat"}} =
|
||||
Builder.build(FactoryBuilder, factory, attrs: %{title: "wat"})
|
||||
assert {:ok, %{title: "wat"}} = FactoryBuilder.build(factory, attrs: %{title: "wat"})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,14 +7,14 @@ defmodule Smokestack.ManyBuilderTest do
|
|||
|
||||
test "it can build a factory more than once" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, results} = Builder.build(ManyBuilder, factory, count: 2)
|
||||
assert {:ok, results} = ManyBuilder.build(factory, count: 2)
|
||||
assert length(results) == 2
|
||||
assert Enum.all?(results, &(byte_size(&1.title) > 0))
|
||||
end
|
||||
|
||||
test "it errors when asked to build less than one instance" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:error, reason} = Builder.build(ManyBuilder, factory, count: 0)
|
||||
assert Exception.message(reason) =~ ~r/expected positive integer/i
|
||||
assert {:error, reason} = ManyBuilder.build(factory, count: 0)
|
||||
assert Exception.message(reason) =~ ~r/positive integer/i
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,35 +7,35 @@ defmodule Smokestack.RecordBuilderTest do
|
|||
|
||||
test "it can build a single record" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, record} = Builder.build(RecordBuilder, factory, [])
|
||||
assert {:ok, record} = RecordBuilder.build(factory, [])
|
||||
assert is_struct(record, Post)
|
||||
assert record.__meta__.state == :loaded
|
||||
end
|
||||
|
||||
test "it can build multiple records" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, records} = Builder.build(RecordBuilder, factory, count: 2)
|
||||
assert {:ok, records} = RecordBuilder.build(factory, count: 2)
|
||||
assert length(records) == 2
|
||||
assert Enum.all?(records, &(is_struct(&1, Post) && &1.__meta__.state == :loaded))
|
||||
end
|
||||
|
||||
test "it can build directly related records" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, record} = Builder.build(RecordBuilder, factory, build: :author)
|
||||
assert {:ok, record} = RecordBuilder.build(factory, build: :author)
|
||||
assert is_struct(record.author, Author)
|
||||
assert record.author.__meta__.state == :loaded
|
||||
end
|
||||
|
||||
test "it can build indirectly related records" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, record} = Builder.build(RecordBuilder, factory, build: [author: :posts])
|
||||
assert {:ok, record} = RecordBuilder.build(factory, build: [author: :posts])
|
||||
assert [%Post{} = post] = record.author.posts
|
||||
assert post.__meta__.state == :loaded
|
||||
end
|
||||
|
||||
test "it can load calculations" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, record} = Builder.build(RecordBuilder, factory, load: :full_title)
|
||||
assert {:ok, record} = RecordBuilder.build(factory, load: :full_title)
|
||||
assert record.full_title == record.title <> ": " <> record.sub_title
|
||||
end
|
||||
|
||||
|
@ -43,7 +43,7 @@ defmodule Smokestack.RecordBuilderTest do
|
|||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
|
||||
assert {:ok, record} =
|
||||
Builder.build(RecordBuilder, factory,
|
||||
RecordBuilder.build(factory,
|
||||
load: [author: :count_of_posts],
|
||||
build: [author: :posts]
|
||||
)
|
||||
|
@ -55,7 +55,7 @@ defmodule Smokestack.RecordBuilderTest do
|
|||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
|
||||
assert {:ok, record} =
|
||||
Builder.build(RecordBuilder, factory, build: :author, load: [author: :posts])
|
||||
RecordBuilder.build(factory, build: :author, load: [author: :posts])
|
||||
|
||||
assert [post] = record.author.posts
|
||||
assert post.id == record.id
|
||||
|
|
|
@ -7,21 +7,21 @@ defmodule Smokestack.RelatedBuilderTest do
|
|||
|
||||
test "it can build attributes from directly related factories" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, attrs} = Builder.build(RelatedBuilder, factory, build: :author)
|
||||
assert {:ok, attrs} = RelatedBuilder.build(factory, build: :author)
|
||||
assert byte_size(attrs[:author][:name]) > 0
|
||||
end
|
||||
|
||||
test "it can build attributes from indirectly related factories" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
assert {:ok, attrs} = Builder.build(RelatedBuilder, factory, build: [author: :posts])
|
||||
assert {:ok, attrs} = RelatedBuilder.build(factory, build: [author: :posts])
|
||||
assert [post] = attrs[:author][:posts]
|
||||
assert byte_size(post[:title]) > 0
|
||||
end
|
||||
|
||||
test "it can attach directly related records" do
|
||||
{:ok, factory} = Info.factory(Factory, Post, :default)
|
||||
{:ok, author} = Builder.build(RecordBuilder, Info.factory!(Factory, Author, :default), [])
|
||||
{:ok, attrs} = Builder.build(RelatedBuilder, factory, relate: [author: author])
|
||||
{:ok, author} = RecordBuilder.build(Info.factory!(Factory, Author, :default), [])
|
||||
{:ok, attrs} = RelatedBuilder.build(factory, relate: [author: author])
|
||||
assert attrs[:author] == author
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Smokestack.DslTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias Spark.Error.DslError
|
||||
alias Support.Author
|
||||
|
||||
defmodule Post do
|
||||
@moduledoc false
|
||||
|
@ -18,6 +19,18 @@ defmodule Smokestack.DslTest do
|
|||
|
||||
attribute :title, :string
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :author, Author
|
||||
end
|
||||
|
||||
calculations do
|
||||
calculate :title_first_word, :string, expr(title |> string_split() |> at(0))
|
||||
end
|
||||
|
||||
actions do
|
||||
defaults [:read]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Factory do
|
||||
|
@ -145,4 +158,63 @@ defmodule Smokestack.DslTest do
|
|||
|
||||
assert %Post{__metadata__: %{wat: true}} = AfterBuildFactory.insert!(Post)
|
||||
end
|
||||
|
||||
test "auto builds can be specified in the factory" do
|
||||
defmodule AutoBuildFactory do
|
||||
@moduledoc false
|
||||
use Smokestack
|
||||
|
||||
factory Post do
|
||||
attribute :title, &Faker.Company.catch_phrase/0
|
||||
auto_build :author
|
||||
end
|
||||
|
||||
factory Author do
|
||||
attribute :name, &Faker.Internet.email/0
|
||||
attribute :email, &Faker.Person.name/0
|
||||
end
|
||||
end
|
||||
|
||||
assert %Post{author: %Author{}} = AutoBuildFactory.insert!(Post)
|
||||
end
|
||||
|
||||
test "auto builds can be overridden at runtime" do
|
||||
defmodule AutoBuildRelateFactory do
|
||||
@moduledoc false
|
||||
use Smokestack
|
||||
|
||||
factory Post do
|
||||
attribute :title, &Faker.Company.catch_phrase/0
|
||||
auto_build :author
|
||||
end
|
||||
|
||||
factory Author do
|
||||
attribute :name, &Faker.Internet.email/0
|
||||
attribute :email, &Faker.Person.name/0
|
||||
end
|
||||
end
|
||||
|
||||
author = AutoBuildRelateFactory.insert!(Author)
|
||||
post = AutoBuildRelateFactory.insert!(Post, relate: [author: author])
|
||||
|
||||
# The auto-build should not be used - the existing author should be used
|
||||
assert Ash.count!(Author, domain: Support.Domain) == 1
|
||||
assert post.author.id == author.id
|
||||
end
|
||||
|
||||
test "auto loads can be specified in the factory" do
|
||||
defmodule AutoLoadFactory do
|
||||
@moduledoc false
|
||||
use Smokestack
|
||||
|
||||
factory Post do
|
||||
attribute :title, &Faker.Company.catch_phrase/0
|
||||
auto_load :title_first_word
|
||||
domain Support.Domain
|
||||
end
|
||||
end
|
||||
|
||||
assert post = AutoLoadFactory.insert!(Post)
|
||||
assert post.title_first_word == post.title |> String.split(" ") |> List.first()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,4 +67,17 @@ defmodule Smokestack.OptionTest do
|
|||
assert post.author.id == author.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "variant" do
|
||||
test "it can select a variant at build time" do
|
||||
assert {:ok, author} = insert(Author, variant: :trek)
|
||||
assert to_string(author.email) =~ ~r/\.(starfleet|rebellion)$/
|
||||
end
|
||||
end
|
||||
|
||||
test "it validates all options" do
|
||||
assert {:error, error} = insert(Author, sss: 2)
|
||||
message = Exception.message(error)
|
||||
assert message =~ ~r/required :[a-z]+ option not found/
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,9 +13,9 @@ defmodule Support.Factory do
|
|||
|
||||
attribute :email, fn
|
||||
%{name: "JL"} -> "captain@entrepreneur.starfleet"
|
||||
%{name: "Doc Holoday"} -> "cheifmed@voyager.starfleet"
|
||||
%{name: "Dr Mark"} -> "cheifmed@voyager.starfleet"
|
||||
%{name: "BLT"} -> "cheifeng@voyager.starfleet"
|
||||
%{name: "Cal Hudson"} -> "cal@maquis.stfu"
|
||||
%{name: "Cal Hudson"} -> "cal@maquis.rebellion"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue