zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

commit e5b306f9cb2a3a0ea211a3a251a18107b2688527
parent 761bdaabe3cbd6495289bf10054b892647a5c65a
Author: srfsh <dev@srf.sh>
Date:   Tue, 23 Aug 2022 11:03:10 +0300

Zenflows{Test,}.VF.RecipeResource: add paging support and small improvements

Diffstat:
Msrc/zenflows/vf/recipe_resource/domain.ex | 51+++++++++++++++++++++++++++------------------------
Msrc/zenflows/vf/recipe_resource/resolv.ex | 25+++++++++++++------------
Msrc/zenflows/vf/recipe_resource/type.ex | 44++++++++++++++++++++++++++++++--------------
Mtest/vf/recipe_resource/domain.test.exs | 57+++++++++++++++++++++++++++++++++------------------------
Mtest/vf/recipe_resource/type.test.exs | 87++++++++++++++++++++++++++++++++++---------------------------------------------
5 files changed, 140 insertions(+), 124 deletions(-)

diff --git a/src/zenflows/vf/recipe_resource/domain.ex b/src/zenflows/vf/recipe_resource/domain.ex @@ -20,35 +20,48 @@ defmodule Zenflows.VF.RecipeResource.Domain do alias Ecto.Multi alias Zenflows.DB.Repo +alias Zenflows.GQL.Paging alias Zenflows.VF.RecipeResource @typep repo() :: Ecto.Repo.t() @typep chgset() :: Ecto.Changeset.t() -@typep changes() :: Ecto.Multi.changes() @typep id() :: Zenflows.DB.Schema.id() @typep params() :: Zenflows.DB.Schema.params() -@spec by_id(repo(), id()) :: RecipeResource.t() | nil -def by_id(repo \\ Repo, id) do - repo.get(RecipeResource, id) +@spec one(repo(), id() | map() | Keyword.t()) + :: {:ok, RecipeResource.t()} | {:error, String.t()} +def one(repo \\ Repo, _) +def one(repo, id) when is_binary(id), do: one(repo, id: id) +def one(repo, clauses) do + case repo.get_by(RecipeResource, clauses) do + nil -> {:error, "not found"} + found -> {:ok, found} + end +end + +@spec all(Paging.params()) :: Paging.result(RecipeResource.t()) +def all(params) do + Paging.page(RecipeResource, params) end @spec create(params()) :: {:ok, RecipeResource.t()} | {:error, chgset()} def create(params) do Multi.new() - |> Multi.insert(:rec_res, RecipeResource.chgset(params)) + |> Multi.insert(:insert, RecipeResource.chgset(params)) |> Repo.transaction() |> case do - {:ok, %{rec_res: rr}} -> {:ok, rr} + {:ok, %{insert: rr}} -> {:ok, rr} {:error, _, cset, _} -> {:error, cset} end end -@spec update(id(), params()) :: {:ok, RecipeResource.t()} | {:error, chgset()} +@spec update(id(), params()) + :: {:ok, RecipeResource.t()} | {:error, String.t() | chgset()} def update(id, params) do Multi.new() - |> Multi.run(:get, multi_get(id)) - |> Multi.update(:update, &RecipeResource.chgset(&1.get, params)) + |> Multi.put(:id, id) + |> Multi.run(:one, &one/2) + |> Multi.update(:update, &RecipeResource.chgset(&1.one, params)) |> Repo.transaction() |> case do {:ok, %{update: rr}} -> {:ok, rr} @@ -56,11 +69,13 @@ def update(id, params) do end end -@spec delete(id()) :: {:ok, RecipeResource.t()} | {:error, chgset()} +@spec delete(id()) + :: {:ok, RecipeResource.t()} | {:error, String.t() | chgset()} def delete(id) do Multi.new() - |> Multi.run(:get, multi_get(id)) - |> Multi.delete(:delete, &(&1.get)) + |> Multi.put(:id, id) + |> Multi.run(:one, &one/2) + |> Multi.delete(:delete, & &1.one) |> Repo.transaction() |> case do {:ok, %{delete: rr}} -> {:ok, rr} @@ -81,16 +96,4 @@ end def preload(rec_res, :resource_conforms_to) do Repo.preload(rec_res, :resource_conforms_to) end - -# Returns a RecipeResource in ok-err tuple from given ID. Used inside -# Ecto.Multi.run/5 to get a record in transaction. -@spec multi_get(id()) :: (repo(), changes() -> {:ok, RecipeResource.t()} | {:error, String.t()}) -defp multi_get(id) do - fn repo, _ -> - case by_id(repo, id) do - nil -> {:error, "not found"} - rr -> {:ok, rr} - end - end -end end diff --git a/src/zenflows/vf/recipe_resource/resolv.ex b/src/zenflows/vf/recipe_resource/resolv.ex @@ -18,44 +18,45 @@ defmodule Zenflows.VF.RecipeResource.Resolv do @moduledoc "Resolvers of RecipeResources." -alias Zenflows.VF.{ - RecipeResource, - RecipeResource.Domain, -} +alias Zenflows.VF.RecipeResource.Domain -def recipe_resource(%{id: id}, _info) do - {:ok, Domain.by_id(id)} +def recipe_resource(params, _) do + Domain.one(params) end -def create_recipe_resource(%{recipe_resource: params}, _info) do +def recipe_resources(params, _) do + Domain.all(params) +end + +def create_recipe_resource(%{recipe_resource: params}, _) do with {:ok, proc_spec} <- Domain.create(params) do {:ok, %{recipe_resource: proc_spec}} end end -def update_recipe_resource(%{recipe_resource: %{id: id} = params}, _info) do +def update_recipe_resource(%{recipe_resource: %{id: id} = params}, _) do with {:ok, proc_spec} <- Domain.update(id, params) do {:ok, %{recipe_resource: proc_spec}} end end -def delete_recipe_resource(%{id: id}, _info) do +def delete_recipe_resource(%{id: id}, _) do with {:ok, _} <- Domain.delete(id) do {:ok, true} end end -def unit_of_resource(%RecipeResource{} = rec_res, _args, _info) do +def unit_of_resource(rec_res, _, _) do rec_res = Domain.preload(rec_res, :unit_of_resource) {:ok, rec_res.unit_of_resource} end -def unit_of_effort(%RecipeResource{} = rec_res, _args, _info) do +def unit_of_effort(rec_res, _, _) do rec_res = Domain.preload(rec_res, :unit_of_effort) {:ok, rec_res.unit_of_effort} end -def resource_conforms_to(%RecipeResource{} = rec_res, _args, _info) do +def resource_conforms_to(rec_res, _, _) do rec_res = Domain.preload(rec_res, :resource_conforms_to) {:ok, rec_res.resource_conforms_to} end diff --git a/src/zenflows/vf/recipe_resource/type.ex b/src/zenflows/vf/recipe_resource/type.ex @@ -32,16 +32,19 @@ The base64-encoded image binary relevant to the entity, such as a photo, diagram @unit_of_resource """ The unit of inventory used for this resource in the recipe. """ +@unit_of_resource_id "(`Unit`) #{@unit_of_resource}" @unit_of_effort """ The unit used for use action on this resource or work action in the recipe. """ +@unit_of_effort_id "(`Unit`) #{@unit_of_resource}" @note "A textual description or comment." @resource_conforms_to """ The primary resource specification or definition of an existing or potential economic resource. A resource will have only one, as this specifies exactly what the resource is. """ +@resource_conforms_to_id "(`ResourceSpecification`) #{@resource_conforms_to}" @resource_classified_as """ References a concept in a common taxonomy or other classification scheme for purposes of categorization or grouping. @@ -86,21 +89,14 @@ object :recipe_resource do field :substitutable, non_null(:boolean) end -object :recipe_resource_response do - field :recipe_resource, non_null(:recipe_resource) -end - input_object :recipe_resource_create_params do @desc @name field :name, non_null(:string) - # TODO: When - # https://github.com/absinthe-graphql/absinthe/issues/1126 results, - # apply the correct changes if any. - @desc "(`Unit`) " <> @unit_of_resource + @desc @unit_of_resource_id field :unit_of_resource_id, :id, name: "unit_of_resource" - @desc "(`Unit`) " <> @unit_of_effort + @desc @unit_of_effort_id field :unit_of_effort_id, :id, name: "unit_of_effort" @desc @image @@ -109,7 +105,7 @@ input_object :recipe_resource_create_params do @desc @note field :note, :string - @desc "(`ResourceSpecification`) " <> @resource_conforms_to + @desc @resource_conforms_to_id field :resource_conforms_to_id, :id, name: "resource_conforms_to" @desc @resource_classified_as @@ -125,10 +121,10 @@ input_object :recipe_resource_update_params do @desc @name field :name, :string - @desc "(`Unit`) " <> @unit_of_resource + @desc @unit_of_resource_id field :unit_of_resource_id, :id, name: "unit_of_resource" - @desc "(`Unit`) " <> @unit_of_effort + @desc @unit_of_effort_id field :unit_of_effort_id, :id, name: "unit_of_effort" @desc @image @@ -137,7 +133,7 @@ input_object :recipe_resource_update_params do @desc @note field :note, :string - @desc "(`ResourceSpecification`) " <> @resource_conforms_to + @desc @resource_conforms_to_id field :resource_conforms_to_id, :id, name: "resource_conforms_to" @desc @resource_classified_as @@ -147,13 +143,33 @@ input_object :recipe_resource_update_params do field :substitutable, :boolean end +object :recipe_resource_response do + field :recipe_resource, non_null(:recipe_resource) +end + +object :recipe_resource_edge do + field :cursor, non_null(:id) + field :node, non_null(:recipe_resource) +end + +object :recipe_resource_connection do + field :page_info, non_null(:page_info) + field :edges, non_null(list_of(non_null(:recipe_resource_edge))) +end + object :query_recipe_resource do field :recipe_resource, :recipe_resource do arg :id, non_null(:id) resolve &Resolv.recipe_resource/2 end - #recipeResources(start: ID, limit: Int): [RecipeResource!] + field :recipe_resources, :recipe_resource_connection do + arg :first, :integer + arg :after, :id + arg :last, :integer + arg :before, :id + resolve &Resolv.recipe_resources/2 + end end object :mutation_recipe_resource do diff --git a/test/vf/recipe_resource/domain.test.exs b/test/vf/recipe_resource/domain.test.exs @@ -37,37 +37,41 @@ setup do note: Factory.uniq("note"), image: Factory.img(), }, - recipe_resource: Factory.insert!(:recipe_resource), + inserted: Factory.insert!(:recipe_resource), } end -test "by_id/1 returns a RecipeResource", %{recipe_resource: rec_res} do - assert %RecipeResource{} = Domain.by_id(rec_res.id) +describe "one/1" do + test "with good id: finds the RecipeResource", %{inserted: %{id: id}} do + assert {:ok, %RecipeResource{}} = Domain.one(id) + end + + test "with bad id: doesn't find the RecipeResource" do + assert {:error, "not found"} = Domain.one(Factory.id()) + end end describe "create/1" do - test "creates a RecipeResource with valid params", %{params: params} do - assert {:ok, %RecipeResource{} = rec_res} = Domain.create(params) - - assert rec_res.name == params.name - assert rec_res.resource_classified_as == params.resource_classified_as - assert rec_res.unit_of_resource_id == params.unit_of_resource_id - assert rec_res.unit_of_effort_id == params.unit_of_effort_id - assert rec_res.resource_conforms_to_id == params.resource_conforms_to_id - assert rec_res.substitutable == params.substitutable - assert rec_res.note == params.note - assert rec_res.image == params.image + test "with good params: creates a RecipeResource", %{params: params} do + assert {:ok, %RecipeResource{} = new} = Domain.create(params) + assert new.name == params.name + assert new.resource_classified_as == params.resource_classified_as + assert new.unit_of_resource_id == params.unit_of_resource_id + assert new.unit_of_effort_id == params.unit_of_effort_id + assert new.resource_conforms_to_id == params.resource_conforms_to_id + assert new.substitutable == params.substitutable + assert new.note == params.note + assert new.image == params.image end - test "doesn't create a RecipeResource with invalid params" do + test "with bad params: doesn't create a Process" do assert {:error, %Changeset{}} = Domain.create(%{}) end end describe "update/2" do - test "updates a RecipeResource with valid params", %{params: params, recipe_resource: old} do + test "with good params: updates the RecipeResource", %{params: params, inserted: old} do assert {:ok, %RecipeResource{} = new} = Domain.update(old.id, params) - assert new.name == params.name assert new.resource_classified_as == params.resource_classified_as assert new.unit_of_resource_id == params.unit_of_resource_id @@ -78,9 +82,8 @@ describe "update/2" do assert new.image == params.image end - test "doesn't update a RecipeResource", %{recipe_resource: old} do + test "with bad params: doesn't update the RecipeResource", %{inserted: old} do assert {:ok, %RecipeResource{} = new} = Domain.update(old.id, %{}) - assert new.name == old.name assert new.resource_classified_as == old.resource_classified_as assert new.unit_of_resource_id == old.unit_of_resource_id @@ -92,19 +95,25 @@ describe "update/2" do end end -test "delete/1 deletes a RecipeResource", %{recipe_resource: %{id: id}} do - assert {:ok, %RecipeResource{id: ^id}} = Domain.delete(id) - assert Domain.by_id(id) == nil +describe "delete/1" do + test "with good id: deletes the RecipeResource", %{inserted: %{id: id}} do + assert {:ok, %RecipeResource{id: ^id}} = Domain.delete(id) + assert {:error, "not found"} = Domain.one(id) + end + + test "with bad id: doesn't delete the RecipeResource" do + assert {:error, "not found"} = Domain.delete(Factory.id()) + end end describe "preload/2" do - test "preloads :unit_of_resource", %{recipe_resource: rec_res} do + test "preloads :unit_of_resource", %{inserted: rec_res} do rec_res = Domain.preload(rec_res, :unit_of_resource) assert unit_res = %Unit{} = rec_res.unit_of_resource assert unit_res.id == rec_res.unit_of_resource_id end - test "preloads :unit_of_effort", %{recipe_resource: rec_res} do + test "preloads :unit_of_effort", %{inserted: rec_res} do rec_res = Domain.preload(rec_res, :unit_of_effort) assert unit_eff = %Unit{} = rec_res.unit_of_effort assert unit_eff.id == rec_res.unit_of_effort_id diff --git a/test/vf/recipe_resource/type.test.exs b/test/vf/recipe_resource/type.test.exs @@ -30,58 +30,54 @@ setup do "note" => Factory.uniq("note"), "image" => Factory.img(), }, - recipe_resource: Factory.insert!(:recipe_resource), + inserted: Factory.insert!(:recipe_resource), } end +@frag """ +fragment recipeResource on RecipeResource { + id + name + resourceClassifiedAs + unitOfResource {id} + unitOfEffort {id} + resourceConformsTo {id} + substitutable + image + note +} +""" + describe "Query" do - test "recipeResource()", %{recipe_resource: rec_res} do + test "recipeResource", %{inserted: new} do assert %{data: %{"recipeResource" => data}} = run!(""" + #{@frag} query ($id: ID!) { - recipeResource(id: $id) { - id - name - resourceClassifiedAs - unitOfResource { id } - unitOfEffort { id } - resourceConformsTo { id } - substitutable - image - note - } + recipeResource(id: $id) {...recipeResource} } - """, vars: %{"id" => rec_res.id}) + """, vars: %{"id" => new.id}) - assert data["id"] == rec_res.id - assert data["name"] == rec_res.name - assert data["resourceClassifiedAs"] == rec_res.resource_classified_as - assert data["unitOfResource"]["id"] == rec_res.unit_of_resource_id - assert data["unitOfEffort"]["id"] == rec_res.unit_of_effort_id - assert data["resourceConformsTo"]["id"] == rec_res.resource_conforms_to_id - assert data["note"] == rec_res.note - assert data["substitutable"] == rec_res.substitutable - assert data["image"] == rec_res.image + assert data["id"] == new.id + assert data["name"] == new.name + assert data["resourceClassifiedAs"] == new.resource_classified_as + assert data["unitOfResource"]["id"] == new.unit_of_resource_id + assert data["unitOfEffort"]["id"] == new.unit_of_effort_id + assert data["resourceConformsTo"]["id"] == new.resource_conforms_to_id + assert data["note"] == new.note + assert data["substitutable"] == new.substitutable + assert data["image"] == new.image end end describe "Mutation" do - test "createRecipeResource()", %{params: params} do + test "createRecipeResource", %{params: params} do assert %{data: %{"createRecipeResource" => %{"recipeResource" => data}}} = run!(""" + #{@frag} mutation ($recipeResource: RecipeResourceCreateParams!) { createRecipeResource(recipeResource: $recipeResource) { - recipeResource { - id - name - image - resourceClassifiedAs - unitOfResource { id } - unitOfEffort { id } - resourceConformsTo { id } - substitutable - note - } + recipeResource {...recipeResource} } } """, vars: %{"recipeResource" => params}) @@ -94,27 +90,18 @@ describe "Mutation" do assert data["resourceConformsTo"]["id"] == params["resourceConformsTo"] end - test "updateRecipeResource()", %{params: params, recipe_resource: rec_res} do + test "updateRecipeResource()", %{params: params, inserted: old} do assert %{data: %{"updateRecipeResource" => %{"recipeResource" => data}}} = run!(""" + #{@frag} mutation ($recipeResource: RecipeResourceUpdateParams!) { updateRecipeResource(recipeResource: $recipeResource) { - recipeResource { - id - name - resourceClassifiedAs - unitOfResource { id } - unitOfEffort { id } - resourceConformsTo { id } - substitutable - note - image - } + recipeResource {...recipeResource} } } - """, vars: %{"recipeResource" => Map.put(params, "id", rec_res.id)}) + """, vars: %{"recipeResource" => Map.put(params, "id", old.id)}) - assert data["id"] == rec_res.id + assert data["id"] == old.id keys = ~w[name note image resourceClassifiedAs substitutable] assert Map.take(data, keys) == Map.take(params, keys) assert data["unitOfResource"]["id"] == params["unitOfResource"] @@ -122,7 +109,7 @@ describe "Mutation" do assert data["resourceConformsTo"]["id"] == params["resourceConformsTo"] end - test "deleteRecipeResource()", %{recipe_resource: %{id: id}} do + test "deleteRecipeResource()", %{inserted: %{id: id}} do assert %{data: %{"deleteRecipeResource" => true}} = run!(""" mutation ($id: ID!) {