Compare commits
95 commits
v0.6.1-rc.
...
main
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 | |||
5c524faef8 | |||
5f8e19ee07 | |||
56e6fb9a4c | |||
04f1393c7c | |||
bab42763d3 | |||
1ed4612aec | |||
9a5579bb50 | |||
f03b4487bf | |||
535acaa1a2 | |||
550178ed34 | |||
690a388a93 | |||
e99dec4808 | |||
0139a0e496 | |||
0bf111b22a | |||
50eb88866f | |||
d5acff239d | |||
198cb16a9c | |||
13395f343c | |||
f6d6529f88 | |||
35e0d80dbd | |||
51960bb6b6 | |||
29eb9d2a5b | |||
e2b88e262e | |||
7d294a5e25 | |||
b9039bd529 | |||
2c25a1eda6 | |||
edfe181d7d | |||
9ad6337ac3 | |||
dd68439047 | |||
25c67edccf | |||
492e90badf | |||
0e895100ea |
30 changed files with 948 additions and 182 deletions
82
.drone.yml
82
.drone.yml
|
@ -252,47 +252,47 @@ steps:
|
|||
- git log -1 --format=%s > .last_commit_message
|
||||
- asdf mix git_ops.check_message .last_commit_message
|
||||
|
||||
# - name: mix git_ops.release
|
||||
# image: harton.dev/james/asdf_container:latest
|
||||
# when:
|
||||
# branch:
|
||||
# - main
|
||||
# event:
|
||||
# exclude:
|
||||
# - pull_request
|
||||
# depends_on:
|
||||
# - mix test
|
||||
# - mix credo
|
||||
# - mix hex.audit
|
||||
# - mix format
|
||||
# - mix spark.formatter
|
||||
# - mix spark.cheat_sheets
|
||||
# - mix deps.unlock
|
||||
# - mix doctor
|
||||
# - mix git_ops.check_message
|
||||
# environment:
|
||||
# MIX_ENV: test
|
||||
# HEX_HOME: /drone/src/.hex
|
||||
# MIX_HOME: /drone/src/.mix
|
||||
# REBAR_BASE_DIR: /drone/src/.rebar3
|
||||
# ASDF_DATA_DIR: /drone/src/.asdf
|
||||
# ASDF_DIR: /root/.asdf
|
||||
# DRONE_TOKEN:
|
||||
# from_secret: DRONE_TOKEN
|
||||
# commands:
|
||||
# - git fetch --tags
|
||||
# - . $ASDF_DIR/asdf.sh
|
||||
# - mix git_ops.project_info --format=shell > before.env
|
||||
# - mix git_ops.release --yes --no-major || true
|
||||
# - mix git_ops.project_info --format=shell > after.env
|
||||
# - . ./before.env
|
||||
# - export OLD_APP_VERSION=$${APP_VERSION}
|
||||
# - . ./after.env
|
||||
# - export NEW_APP_VERSION=$${APP_VERSION}
|
||||
# - if [ "v$${OLD_APP_VERSION}" != "v$${NEW_APP_VERSION}" ]; then
|
||||
# - export GIT_URL=$(echo $DRONE_GIT_HTTP_URL | sed -e "s/:\\/\\//:\\/\\/$DRONE_REPO_OWNER:$DRONE_TOKEN@/")
|
||||
# - git push $${GIT_URL} "HEAD:${DRONE_COMMIT_REF}" "refs/tags/v$${NEW_APP_VERSION}"
|
||||
# - fi
|
||||
- name: mix git_ops.release
|
||||
image: harton.dev/james/asdf_container:latest
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
exclude:
|
||||
- pull_request
|
||||
depends_on:
|
||||
- mix test
|
||||
- mix credo
|
||||
- mix hex.audit
|
||||
- mix format
|
||||
- mix spark.formatter
|
||||
- mix spark.cheat_sheets
|
||||
- mix deps.unlock
|
||||
- mix doctor
|
||||
- mix git_ops.check_message
|
||||
environment:
|
||||
MIX_ENV: test
|
||||
HEX_HOME: /drone/src/.hex
|
||||
MIX_HOME: /drone/src/.mix
|
||||
REBAR_BASE_DIR: /drone/src/.rebar3
|
||||
ASDF_DATA_DIR: /drone/src/.asdf
|
||||
ASDF_DIR: /root/.asdf
|
||||
DRONE_TOKEN:
|
||||
from_secret: DRONE_TOKEN
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- . $ASDF_DIR/asdf.sh
|
||||
- mix git_ops.project_info --format=shell > before.env
|
||||
- mix git_ops.release --yes --no-major || true
|
||||
- mix git_ops.project_info --format=shell > after.env
|
||||
- . ./before.env
|
||||
- export OLD_APP_VERSION=$${APP_VERSION}
|
||||
- . ./after.env
|
||||
- export NEW_APP_VERSION=$${APP_VERSION}
|
||||
- if [ "v$${OLD_APP_VERSION}" != "v$${NEW_APP_VERSION}" ]; then
|
||||
- export GIT_URL=$(echo $DRONE_GIT_HTTP_URL | sed -e "s/:\\/\\//:\\/\\/$DRONE_REPO_OWNER:$DRONE_TOKEN@/")
|
||||
- git push $${GIT_URL} "HEAD:${DRONE_COMMIT_REF}" "refs/tags/v$${NEW_APP_VERSION}"
|
||||
- fi
|
||||
|
||||
- name: build artifacts
|
||||
image: harton.dev/james/asdf_container:latest
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
spark_locals_without_parens = [
|
||||
after_build: 1,
|
||||
after_build: 2,
|
||||
attribute: 2,
|
||||
attribute: 3,
|
||||
auto_build: 1,
|
||||
auto_load: 1,
|
||||
before_build: 1,
|
||||
before_build: 2,
|
||||
domain: 1,
|
||||
factory: 1,
|
||||
factory: 2,
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
elixir 1.16.2-otp-26
|
||||
erlang 26.2.3
|
||||
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"
|
||||
]
|
||||
}
|
52
CHANGELOG.md
52
CHANGELOG.md
|
@ -5,6 +5,58 @@ 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)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* Add `before_build` and `after_build` entities to factories.
|
||||
|
||||
## [v0.7.0](https://harton.dev/james/smokestack/compare/v0.6.2...v0.7.0) (2024-05-20)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* related: Newly build records can now be related to existing records.
|
||||
|
||||
## [v0.6.2](https://harton.dev/james/smokestack/compare/v0.6.1-rc.2...v0.6.2) (2024-05-11)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v0.6.1-rc.2](https://harton.dev/james/smokestack/compare/v0.6.1-rc.1...v0.6.1-rc.2) (2024-04-03)
|
||||
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ add it directly to your `mix.exs`:
|
|||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:smokestack, "~> 0.6.1-rc.2"},
|
||||
{:smokestack, "~> 0.9.1"},
|
||||
]
|
||||
end
|
||||
```
|
||||
|
|
|
@ -13,7 +13,9 @@ The DSL definition for the Smokestack DSL.
|
|||
|
||||
* smokestack
|
||||
* factory
|
||||
* after_build
|
||||
* attribute
|
||||
* before_build
|
||||
|
||||
### Docs
|
||||
|
||||
|
@ -22,7 +24,9 @@ The DSL definition for the Smokestack DSL.
|
|||
|
||||
|
||||
* [factory](#module-factory)
|
||||
* after_build
|
||||
* attribute
|
||||
* before_build
|
||||
|
||||
|
||||
|
||||
|
@ -38,7 +42,9 @@ The DSL definition for the Smokestack DSL.
|
|||
|
||||
Define factories for a resource
|
||||
|
||||
* after_build
|
||||
* attribute
|
||||
* before_build
|
||||
|
||||
|
||||
|
||||
|
@ -48,6 +54,30 @@ 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
|
||||
|
||||
Modify the record after building.
|
||||
|
||||
Allows you to provide a function which can modify the built record before returning.
|
||||
|
||||
These hooks are only applied when building records and not parameters.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
* `:hook` (mfa or function of arity 1) - Required. A function which returns an updated record
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##### attribute
|
||||
|
@ -67,6 +97,24 @@ Define factories for a resource
|
|||
|
||||
|
||||
|
||||
##### before_build
|
||||
|
||||
Modify the attributes before building.
|
||||
|
||||
Allows you to provide a function which can modify the the attributes before building.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
* `:hook` (mfa or function of arity 1) - Required. A function which returns an updated record
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -79,7 +127,9 @@ Define factories for a resource
|
|||
|
||||
### Nested DSLs
|
||||
* [factory](#smokestack-factory)
|
||||
* after_build
|
||||
* attribute
|
||||
* before_build
|
||||
|
||||
|
||||
|
||||
|
@ -102,7 +152,9 @@ factory resource, variant \\ :default
|
|||
Define factories for a resource
|
||||
|
||||
### Nested DSLs
|
||||
* [after_build](#smokestack-factory-after_build)
|
||||
* [attribute](#smokestack-factory-attribute)
|
||||
* [before_build](#smokestack-factory-before_build)
|
||||
|
||||
|
||||
|
||||
|
@ -118,8 +170,42 @@ 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
|
||||
```elixir
|
||||
after_build hook
|
||||
```
|
||||
|
||||
|
||||
Modify the record after building.
|
||||
|
||||
Allows you to provide a function which can modify the built record before returning.
|
||||
|
||||
These hooks are only applied when building records and not parameters.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Default | Docs |
|
||||
|------|------|---------|------|
|
||||
| [`hook`](#smokestack-factory-after_build-hook){: #smokestack-factory-after_build-hook .spark-required} | `(any -> any) \| mfa` | | A function which returns an updated record |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Introspection
|
||||
|
||||
Target: `Smokestack.Dsl.AfterBuild`
|
||||
|
||||
## smokestack.factory.attribute
|
||||
```elixir
|
||||
attribute name, generator
|
||||
|
@ -148,6 +234,36 @@ attribute name, generator
|
|||
|
||||
Target: `Smokestack.Dsl.Attribute`
|
||||
|
||||
## smokestack.factory.before_build
|
||||
```elixir
|
||||
before_build hook
|
||||
```
|
||||
|
||||
|
||||
Modify the attributes before building.
|
||||
|
||||
Allows you to provide a function which can modify the the attributes before building.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Default | Docs |
|
||||
|------|------|---------|------|
|
||||
| [`hook`](#smokestack-factory-before_build-hook){: #smokestack-factory-before_build-hook .spark-required} | `(any -> any) \| mfa` | | A function which returns an updated record |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Introspection
|
||||
|
||||
Target: `Smokestack.Dsl.BeforeBuild`
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ defmodule Smokestack do
|
|||
alias Spark.{Dsl, Dsl.Extension}
|
||||
|
||||
@moduledoc """
|
||||
Smokestack provides a way to define test factories for your
|
||||
[Ash Resources](https://ash-hq.org/docs/module/ash/latest/ash-resource)
|
||||
using a convenient DSL:
|
||||
Smokestack provides a way to define test factories for your [Ash
|
||||
Resources](https://ash-hq.org/docs/module/ash/latest/ash-resource) using a
|
||||
convenient DSL:
|
||||
|
||||
```
|
||||
defmodule MyApp.Factory do
|
||||
|
@ -36,8 +36,8 @@ defmodule Smokestack do
|
|||
|
||||
## Variants
|
||||
|
||||
Sometimes you need to make slightly different factories to build a resource
|
||||
in a specific state for your test scenario.
|
||||
Sometimes you need to make slightly different factories to build a resource in
|
||||
a specific state for your test scenario.
|
||||
|
||||
Here's an example defining an alternate `:trek` variant for the character
|
||||
factory defined above:
|
||||
|
@ -58,8 +58,8 @@ defmodule Smokestack do
|
|||
### Options
|
||||
|
||||
- `load`: an atom, list of atoms or keyword list of the same listing
|
||||
relationships, calculations and aggregates that should be loaded
|
||||
after the record is created.
|
||||
relationships, calculations and aggregates that should be loaded after the
|
||||
record is created.
|
||||
- `count`: rather than inserting just a single record, you can specify a
|
||||
number of records to be inserted. A list of records will be returned.
|
||||
- `build`: an atom, list of atoms or keyword list of the same describing
|
||||
|
@ -68,6 +68,8 @@ defmodule Smokestack do
|
|||
used, and if not the `:default` variant will be.
|
||||
- `attrs`: A map or keyword list of attributes you would like to set directly
|
||||
on the created record, rather than using the value provided by the factory.
|
||||
- `relate`: A keyword list of relationships to records (or lists of records)
|
||||
to which you wish to directly relate the created record.
|
||||
|
||||
## Building parameters
|
||||
|
||||
|
@ -76,20 +78,20 @@ defmodule Smokestack do
|
|||
|
||||
### Options
|
||||
|
||||
- `encode`: rather than returning a map or maps, provide an encoder module
|
||||
to serialise the parameters. Commonly you would use `Jason` or `Poison`.
|
||||
- `nest`: rather than returning a map or maps directly, wrap the result in
|
||||
an outer map using the provided key.
|
||||
- `key_case`: change the case of the keys into one of the many cases
|
||||
supported by [recase](https://hex.pm/packages/recase).
|
||||
- `encode`: rather than returning a map or maps, provide an encoder module to
|
||||
serialise the parameters. Commonly you would use `Jason` or `Poison`.
|
||||
- `nest`: rather than returning a map or maps directly, wrap the result in an
|
||||
outer map using the provided key.
|
||||
- `key_case`: change the case of the keys into one of the many cases supported
|
||||
by [recase](https://hex.pm/packages/recase).
|
||||
- `key_type`: specify whether the returned map or maps should use string or
|
||||
atom keys (ignored when using the `encode` option).
|
||||
- `count`: rather than returning just a single map, you can specify a
|
||||
number of results to be returned. A list of maps will be returned.
|
||||
- `count`: rather than returning just a single map, you can specify a number
|
||||
of results to be returned. A list of maps will be returned.
|
||||
- `build`: an atom, list of atoms or keyword list of the same describing
|
||||
relationships which you would like built within the result. If the
|
||||
related resource has a variant which matches the current one, it will be
|
||||
used, and if not the `:default` variant will be.
|
||||
relationships which you would like built within the result. If the related
|
||||
resource has a variant which matches the current one, it will be used, and
|
||||
if not the `:default` variant will be.
|
||||
- `attrs`: A map or keyword list of attributes you would like to set directly
|
||||
on the result, rather than using the value provided by the factory.
|
||||
|
||||
|
@ -110,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
|
||||
|
||||
|
@ -194,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 """
|
||||
|
@ -227,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
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Smokestack.FactoryBuilder do
|
|||
"""
|
||||
|
||||
alias Ash.Resource
|
||||
alias Smokestack.{Builder, Dsl.Attribute, Dsl.Factory, Template}
|
||||
alias Smokestack.{Builder, Dsl.Factory, Template}
|
||||
alias Spark.Options
|
||||
@behaviour Builder
|
||||
|
||||
|
@ -24,25 +24,31 @@ defmodule Smokestack.FactoryBuilder do
|
|||
@impl true
|
||||
@spec build(Factory.t(), [option]) :: {:ok, result} | {:error, error}
|
||||
def build(factory, options) do
|
||||
overrides = options[:attrs]
|
||||
overrides = Keyword.get(options, :attrs, %{})
|
||||
|
||||
factory
|
||||
|> Map.get(:attributes, [])
|
||||
|> Enum.filter(&is_struct(&1, Attribute))
|
||||
|> Enum.reduce({:ok, %{}}, fn
|
||||
attr, {:ok, attrs} when is_map_key(overrides, attr.name) ->
|
||||
{:ok, Map.put(attrs, attr.name, Map.get(overrides, attr.name))}
|
||||
with {:ok, overrides} <- validate_overrides(factory, overrides) do
|
||||
attrs =
|
||||
factory.attributes
|
||||
|> remove_overridden_attrs(overrides)
|
||||
|> Enum.reduce(overrides, fn attr, attrs ->
|
||||
generator = maybe_initialise_generator(attr)
|
||||
value = Template.generate(generator, attrs, options)
|
||||
Map.put(attrs, attr.name, value)
|
||||
end)
|
||||
|
||||
attr, {:ok, attrs} ->
|
||||
generator = maybe_initialise_generator(attr)
|
||||
value = Template.generate(generator, attrs, options)
|
||||
{:ok, Map.put(attrs, attr.name, value)}
|
||||
end)
|
||||
attrs =
|
||||
factory.before_build
|
||||
|> Enum.reduce(attrs, fn hook, attrs ->
|
||||
hook.hook.(attrs)
|
||||
end)
|
||||
|
||||
{:ok, attrs}
|
||||
end
|
||||
end
|
||||
|
||||
@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
|
||||
|
@ -74,7 +80,7 @@ defmodule Smokestack.FactoryBuilder do
|
|||
```
|
||||
"""
|
||||
]
|
||||
]}
|
||||
], "Options for building instances"}
|
||||
end
|
||||
|
||||
defp maybe_initialise_generator(attr) do
|
||||
|
@ -84,4 +90,26 @@ defmodule Smokestack.FactoryBuilder do
|
|||
generator
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_overrides(factory, overrides) do
|
||||
valid_attr_names =
|
||||
factory.resource
|
||||
|> Resource.Info.attributes()
|
||||
|> Enum.map(& &1.name)
|
||||
|
||||
Enum.reduce_while(overrides, {:ok, overrides}, fn {key, _}, {:ok, overrides} ->
|
||||
if key in valid_attr_names do
|
||||
{:cont, {:ok, overrides}}
|
||||
else
|
||||
{:halt,
|
||||
{:error,
|
||||
"No attribute named `#{inspect(key)}` available on resource `#{inspect(factory.resource)}`"}}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp remove_overridden_attrs(attrs, overrides) when map_size(overrides) == 0, do: attrs
|
||||
|
||||
defp remove_overridden_attrs(attrs, overrides),
|
||||
do: Enum.reject(attrs, &is_map_key(overrides, &1.name))
|
||||
end
|
||||
|
|
|
@ -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,18 +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
|
||||
maybe_load(record_list, factory, load)
|
||||
record_list
|
||||
|> maybe_hook(factory)
|
||||
|> 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
|
||||
maybe_load(record, factory, load)
|
||||
record
|
||||
|> maybe_hook(factory)
|
||||
|> maybe_load(factory, List.wrap(load))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -141,11 +145,30 @@ 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 ->
|
||||
Enum.reduce(factory.after_build, record, fn hook, record ->
|
||||
hook.(record)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp maybe_hook(record, factory) when is_map(record) do
|
||||
Enum.reduce(factory.after_build, record, fn hook, record ->
|
||||
hook.hook.(record)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,13 +9,18 @@ defmodule Smokestack.RelatedBuilder do
|
|||
alias Spark.Options
|
||||
@behaviour Builder
|
||||
|
||||
@type option :: build_option | FactoryBuilder.option()
|
||||
@type option :: build_option | relate_option | FactoryBuilder.option()
|
||||
|
||||
@typedoc """
|
||||
A nested keyword list of associations that should also be built.
|
||||
"""
|
||||
@type build_option :: {:build, Smokestack.recursive_atom_list()}
|
||||
|
||||
@typedoc """
|
||||
A nested keyword list of previously built records to explicitly associate to the new record.
|
||||
"""
|
||||
@type relate_option :: {:relate, [{atom, Resource.record()}]}
|
||||
|
||||
@type result :: %{optional(atom) => any}
|
||||
@type error :: FactoryBuilder.error() | Exception.t()
|
||||
|
||||
|
@ -25,16 +30,17 @@ 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.delete(options, :build)) do
|
||||
maybe_build_related(factory, attrs, options)
|
||||
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
|
||||
end
|
||||
|
||||
@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 =
|
||||
|
@ -55,6 +61,26 @@ defmodule Smokestack.RelatedBuilder do
|
|||
{:or, [{:wrap_list, :atom}, :keyword_list]}
|
||||
end
|
||||
|
||||
relate_type =
|
||||
if factory do
|
||||
factory.resource
|
||||
|> Resource.Info.relationships()
|
||||
|> Enum.map(fn
|
||||
relationship when relationship.cardinality == :one ->
|
||||
{relationship.name, [type: {:struct, relationship.destination}, required: false]}
|
||||
|
||||
relationship when relationship.cardinality == :many ->
|
||||
{relationship.name,
|
||||
[type: {:wrap_list, {:struct, relationship.destination}}, required: false]}
|
||||
end)
|
||||
|> case do
|
||||
[] -> :keyword_list
|
||||
keys -> {:or, [{:keyword_list, keys}]}
|
||||
end
|
||||
else
|
||||
:keyword_list
|
||||
end
|
||||
|
||||
schema =
|
||||
[
|
||||
build: [
|
||||
|
@ -83,21 +109,35 @@ defmodule Smokestack.RelatedBuilder do
|
|||
build one instance.
|
||||
|
||||
If these caveats are an issue, then you can build them yourself and
|
||||
pass them in using the `attrs` option.
|
||||
pass them in using the `relate` option.
|
||||
|
||||
For example:
|
||||
|
||||
```elixir
|
||||
posts = insert!(Post, count: 3)
|
||||
author = insert(Author, posts: posts)
|
||||
author = insert(Author, relate: [posts: posts])
|
||||
```
|
||||
"""
|
||||
],
|
||||
relate: [
|
||||
type: relate_type,
|
||||
required: false,
|
||||
default: [],
|
||||
doc: """
|
||||
A list of records to relate.
|
||||
|
||||
For example
|
||||
|
||||
```elixir
|
||||
author = insert!(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
|
||||
|
||||
|
@ -105,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,
|
||||
|
@ -122,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)
|
||||
|
@ -141,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)}
|
||||
|
@ -153,6 +206,53 @@ defmodule Smokestack.RelatedBuilder do
|
|||
end
|
||||
end
|
||||
|
||||
defp maybe_relate(factory, attrs, options) do
|
||||
options
|
||||
|> Keyword.get(:relate, [])
|
||||
|> List.wrap()
|
||||
|> Enum.map(fn {relationship, record} ->
|
||||
{relationship, record, Resource.Info.relationship(factory.resource, relationship)}
|
||||
end)
|
||||
|> Enum.reduce_while({:ok, attrs}, fn
|
||||
{relationship_name, _record, nil}, {:ok, _attrs} ->
|
||||
{:halt,
|
||||
{:error,
|
||||
"No relationship named `#{inspect(relationship_name)}` defined on resource `#{inspect(factory.resource)}`"}}
|
||||
|
||||
{_, record, relationship}, {:ok, attrs} ->
|
||||
case relate(attrs, relationship, record) do
|
||||
{:ok, attrs} -> {:cont, {:ok, attrs}}
|
||||
{:error, reason} -> {:halt, {:error, reason}}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp relate(attrs, relationship, record)
|
||||
when is_struct(record, relationship.destination) and relationship.cardinality == :one,
|
||||
do: {:ok, Map.put(attrs, relationship.name, record)}
|
||||
|
||||
defp relate(_attrs, relationship, record) when relationship.cardinality == :one,
|
||||
do:
|
||||
{:error,
|
||||
"Expected value to be a `#{inspect(relationship.destination)}` record, however it is #{inspect(record)}"}
|
||||
|
||||
defp relate(attrs, relationship, records) when relationship.cardinality == :many do
|
||||
records
|
||||
|> Enum.reduce_while({:ok, []}, fn
|
||||
record, {:ok, records} when is_struct(record, relationship.destination) ->
|
||||
{:cont, {:ok, [record | records]}}
|
||||
|
||||
record, _ ->
|
||||
{:halt,
|
||||
{:error,
|
||||
"Expected value to be a `#{inspect(relationship.destination)}` record, however it is #{inspect(record)}"}}
|
||||
end)
|
||||
|> case do
|
||||
{:ok, records} -> {:ok, Map.put(attrs, relationship.name, records)}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_related_factory(resource, factory) when factory.variant == :default,
|
||||
do: Info.factory(factory.module, resource, :default)
|
||||
|
||||
|
|
43
lib/smokestack/dsl/after_build.ex
Normal file
43
lib/smokestack/dsl/after_build.ex
Normal file
|
@ -0,0 +1,43 @@
|
|||
defmodule Smokestack.Dsl.AfterBuild do
|
||||
@moduledoc """
|
||||
The `after_build` DSL entity.
|
||||
|
||||
See `d:Smokestack.factory.after_build` for more information.
|
||||
"""
|
||||
|
||||
defstruct __identifier__: nil, hook: nil
|
||||
|
||||
alias Ash.Resource
|
||||
alias Spark.Dsl.Entity
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
__identifier__: any,
|
||||
hook: mfa | (Resource.record() -> Resource.record())
|
||||
}
|
||||
|
||||
@doc false
|
||||
@spec __entities__ :: [Entity.t()]
|
||||
def __entities__,
|
||||
do: [
|
||||
%Entity{
|
||||
name: :after_build,
|
||||
describe: """
|
||||
Modify the record after building.
|
||||
|
||||
Allows you to provide a function which can modify the built record before returning.
|
||||
|
||||
These hooks are only applied when building records and not parameters.
|
||||
""",
|
||||
target: __MODULE__,
|
||||
args: [:hook],
|
||||
identifier: {:auto, :unique_integer},
|
||||
schema: [
|
||||
hook: [
|
||||
type: {:mfa_or_fun, 1},
|
||||
required: true,
|
||||
doc: "A function which returns an updated record"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
|
@ -2,7 +2,7 @@ defmodule Smokestack.Dsl.Attribute do
|
|||
@moduledoc """
|
||||
The `attribute ` DSL entity.
|
||||
|
||||
See `d:Smokestack.factory.default.attribute` for more information.
|
||||
See `d:Smokestack.factory.attribute` for more information.
|
||||
"""
|
||||
|
||||
defstruct __identifier__: nil, generator: nil, name: nil
|
||||
|
@ -11,7 +11,7 @@ defmodule Smokestack.Dsl.Attribute do
|
|||
alias Spark.Dsl.Entity
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
__identifier__: nil,
|
||||
__identifier__: any,
|
||||
generator:
|
||||
mfa | (-> any) | (Resource.record() -> any) | (Resource.record(), keyword -> any),
|
||||
name: atom
|
||||
|
|
41
lib/smokestack/dsl/before_build.ex
Normal file
41
lib/smokestack/dsl/before_build.ex
Normal file
|
@ -0,0 +1,41 @@
|
|||
defmodule Smokestack.Dsl.BeforeBuild do
|
||||
@moduledoc """
|
||||
The `before_build` DSL entity.
|
||||
|
||||
See `d:Smokestack.factory.before_build` for more information.
|
||||
"""
|
||||
|
||||
defstruct __identifier__: nil, hook: nil
|
||||
|
||||
alias Spark.Dsl.Entity
|
||||
|
||||
@type attrs :: %{required(String.t() | atom) => any}
|
||||
@type t :: %__MODULE__{
|
||||
__identifier__: any,
|
||||
hook: mfa | (attrs -> attrs)
|
||||
}
|
||||
|
||||
@doc false
|
||||
@spec __entities__ :: [Entity.t()]
|
||||
def __entities__,
|
||||
do: [
|
||||
%Entity{
|
||||
name: :before_build,
|
||||
describe: """
|
||||
Modify the attributes before building.
|
||||
|
||||
Allows you to provide a function which can modify the the attributes before building.
|
||||
""",
|
||||
target: __MODULE__,
|
||||
args: [:hook],
|
||||
identifier: {:auto, :unique_integer},
|
||||
schema: [
|
||||
hook: [
|
||||
type: {:mfa_or_fun, 1},
|
||||
required: true,
|
||||
doc: "A function which returns an updated record"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
|
@ -6,19 +6,27 @@ defmodule Smokestack.Dsl.Factory do
|
|||
"""
|
||||
|
||||
defstruct __identifier__: nil,
|
||||
after_build: [],
|
||||
attributes: [],
|
||||
auto_load: [],
|
||||
auto_build: [],
|
||||
before_build: [],
|
||||
domain: nil,
|
||||
module: nil,
|
||||
resource: nil,
|
||||
variant: :default
|
||||
|
||||
alias Ash.Resource
|
||||
alias Smokestack.Dsl.{Attribute, Template}
|
||||
alias Smokestack.Dsl.{AfterBuild, Attribute, BeforeBuild, Template}
|
||||
alias Spark.Dsl.Entity
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
__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,
|
||||
resource: Resource.t(),
|
||||
|
@ -52,9 +60,25 @@ 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: [attributes: Attribute.__entities__()]
|
||||
entities: [
|
||||
after_build: AfterBuild.__entities__(),
|
||||
attributes: Attribute.__entities__(),
|
||||
before_build: BeforeBuild.__entities__()
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
|
|
@ -27,4 +27,24 @@ defmodule Smokestack.Dsl.Info do
|
|||
{:ok, factory}
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Raising version of `factory/3`"
|
||||
def factory!(factory, resource, variant) do
|
||||
case factory(factory, resource, variant) do
|
||||
{:ok, factory} -> factory
|
||||
{: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
|
||||
|
|
6
mix.exs
6
mix.exs
|
@ -1,7 +1,7 @@
|
|||
defmodule Smokestack.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
@version "0.6.1-rc.2"
|
||||
@version "0.9.1"
|
||||
|
||||
@moduledoc """
|
||||
Test factories for Ash resources.
|
||||
|
@ -93,7 +93,7 @@ defmodule Smokestack.MixProject do
|
|||
opts = [only: ~w[dev test]a, runtime: false]
|
||||
|
||||
[
|
||||
{:ash, "~> 3.0.0-rc.8"},
|
||||
{:ash, "~> 3.0"},
|
||||
{:credo, "~> 1.7", opts},
|
||||
{:dialyxir, "~> 1.3", opts},
|
||||
{:doctor, "~> 0.21", opts},
|
||||
|
@ -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
|
||||
|
|
49
mix.lock
49
mix.lock
|
@ -1,37 +1,44 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "3.0.0-rc.8", "775e14c3ca7f20a73cf92fdfd6f4244712658f06a860aaff915bc471b2c82881", [: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", [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.7 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.6", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f74451def7ae71f33e82fc3263312dd6521705c0db4176d1f6f02fec32b10dc0"},
|
||||
"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.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [: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", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"},
|
||||
"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.31.2", "8b06d0a5ac69e1a54df35519c951f1f44a7b7ca9a5bb7a260cd8a174d6322ece", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "317346c14febaba9ca40fd97b5b5919f7751fb85d399cc8e7e8872049f37e0af"},
|
||||
"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.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"},
|
||||
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
||||
"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"},
|
||||
"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.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
|
||||
"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, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"},
|
||||
"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"},
|
||||
"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.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.1", "1aec71d16083901277727c8162f6dd0f07e80f5ca98911b6ef4f2c95e6e62758", [: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", "ae3936d97a3e4a316744f70c77b85345b08b70da334024c26e6b5eb8ede1246b"},
|
||||
"recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"},
|
||||
"sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"},
|
||||
"spark": {:hex, :spark, "2.1.11", "8093149dfd583b5ce2c06e1fea1faaf4125b50e4703138b2cbefb78c8f4aa07f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "1877d92ab993b860e9d828bfd72d50367c0d3a53dd84f4de5d221baf66ae8723"},
|
||||
"splode": {:hex, :splode, "0.2.1", "020079ec06c9e00f8b6586852e781b5e07aee6ba588f3f45dd993831c87b0511", [:mix], [], "hexpm", "d232a933666061fe1f659d9906042fa94b9b393bb1129a4fde6fa680033b2611"},
|
||||
"stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||
"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.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
|
||||
|
|
|
@ -2,19 +2,26 @@ defmodule Smokestack.RelatedBuilderTest do
|
|||
@moduledoc false
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Smokestack.{Builder, Dsl.Info, RelatedBuilder}
|
||||
alias Support.{Factory, Post}
|
||||
alias Smokestack.{Builder, Dsl.Info, RecordBuilder, RelatedBuilder}
|
||||
alias Support.{Author, Factory, Post}
|
||||
|
||||
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} = 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
|
||||
|
@ -107,4 +120,101 @@ defmodule Smokestack.DslTest do
|
|||
assert %Post{title: title} = FactoryUser.test()
|
||||
assert title =~ ~r/[a-z]+/i
|
||||
end
|
||||
|
||||
test "before build hooks can be applied" do
|
||||
defmodule BeforeBuildFactory do
|
||||
@moduledoc false
|
||||
use Smokestack
|
||||
|
||||
factory Post do
|
||||
attribute :title, &Faker.Company.catch_phrase/0
|
||||
before_build &capitalise_title/1
|
||||
end
|
||||
|
||||
def capitalise_title(record) do
|
||||
%{record | title: String.upcase(record.title)}
|
||||
end
|
||||
end
|
||||
|
||||
title = Faker.Company.catch_phrase()
|
||||
upper_title = String.upcase(title)
|
||||
assert %Post{title: ^upper_title} = BeforeBuildFactory.insert!(Post, attrs: %{title: title})
|
||||
end
|
||||
|
||||
test "after build hooks can be applied" do
|
||||
defmodule AfterBuildFactory do
|
||||
@moduledoc false
|
||||
use Smokestack
|
||||
|
||||
factory Post do
|
||||
attribute :title, &Faker.Company.catch_phrase/0
|
||||
after_build &add_metadata/1
|
||||
end
|
||||
|
||||
def add_metadata(record) do
|
||||
Ash.Resource.put_metadata(record, :wat, true)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
|
83
test/smokestack/option_test.exs
Normal file
83
test/smokestack/option_test.exs
Normal file
|
@ -0,0 +1,83 @@
|
|||
defmodule Smokestack.OptionTest do
|
||||
@moduledoc false
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
use Support.Factory
|
||||
alias Support.{Author, Post}
|
||||
|
||||
describe "no options" do
|
||||
test "a record can be generated directly from the factory" do
|
||||
assert {:ok, author} = insert(Author)
|
||||
assert is_binary(author.id)
|
||||
assert is_binary(author.name)
|
||||
assert is_binary(to_string(author.email))
|
||||
end
|
||||
end
|
||||
|
||||
describe "attrs" do
|
||||
test "a record can be generated from the factory with some attributes overridden" do
|
||||
assert {:ok, author} = insert(Author, attrs: %{name: "J.M. Dillard"})
|
||||
assert author.name == "J.M. Dillard"
|
||||
end
|
||||
end
|
||||
|
||||
describe "count" do
|
||||
test "many records can be generated from the factory" do
|
||||
assert {:ok, authors} = insert(Author, count: 3)
|
||||
assert length(authors) == 3
|
||||
end
|
||||
end
|
||||
|
||||
describe "build" do
|
||||
test "it can build directly related records from the factory" do
|
||||
assert {:ok, author} = insert(Author, build: [:posts])
|
||||
assert [%Post{}] = author.posts
|
||||
end
|
||||
|
||||
test "it can build indirectly directly related records from the factory" do
|
||||
assert {:ok, post} = insert(Post, build: [author: :posts])
|
||||
assert %Author{} = post.author
|
||||
assert [other_post] = post.author.posts
|
||||
assert other_post.id != post.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "load" do
|
||||
test "it can load related records at build time" do
|
||||
assert {:ok, author} = insert(Author)
|
||||
assert {:ok, post} = insert(Post, attrs: %{author_id: author.id}, load: [:author])
|
||||
assert post.author.id == author.id
|
||||
end
|
||||
|
||||
test "it can load calculations at build time" do
|
||||
assert {:ok, post} = insert(Post, load: [:full_title])
|
||||
assert post.full_title == "#{post.title}: #{post.sub_title}"
|
||||
end
|
||||
|
||||
test "it can load aggregates at build time" do
|
||||
assert {:ok, post} = insert(Post, build: [:author], load: [author: :count_of_posts])
|
||||
assert post.author.count_of_posts == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "relate" do
|
||||
test "it can relate records at build time" do
|
||||
assert {:ok, author} = insert(Author)
|
||||
assert {:ok, post} = insert(Post, relate: [author: author])
|
||||
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
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ defmodule Support.Post do
|
|||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :author, Support.Author
|
||||
belongs_to :author, Support.Author, attribute_writable?: true
|
||||
end
|
||||
|
||||
actions do
|
||||
|
|
Loading…
Reference in a new issue