zf

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

commit b089135108143e995eef4f48454aacd0c1316bb7
parent be0e289f199d57096305643ac981f902354c0a18
Author: srfsh <dev@srf.sh>
Date:   Mon, 22 Aug 2022 21:23:33 +0300

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

Diffstat:
Msrc/zenflows/vf/process/domain.ex | 51++++++++++++++++++++++++++-------------------------
Msrc/zenflows/vf/process/resolv.ex | 22+++++++++++++---------
Msrc/zenflows/vf/process/type.ex | 59++++++++++++++++++++++++++++++-----------------------------
Mtest/vf/process/domain.test.exs | 91++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mtest/vf/process/type.test.exs | 184+++++++++++++++++++++++++++++++++----------------------------------------------
5 files changed, 193 insertions(+), 214 deletions(-)

diff --git a/src/zenflows/vf/process/domain.ex b/src/zenflows/vf/process/domain.ex @@ -20,35 +20,48 @@ defmodule Zenflows.VF.Process.Domain do alias Ecto.Multi alias Zenflows.DB.Repo +alias Zenflows.GQL.Paging alias Zenflows.VF.Process @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()) :: Process.t() | nil -def by_id(repo \\ Repo, id) do - repo.get(Process, id) +@spec one(repo(), id() | map() | Keyword.t()) + :: {:ok, Process.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(Process, clauses) do + nil -> {:error, "not found"} + found -> {:ok, found} + end +end + +@spec all(Paging.params()) :: Paging.result(Process.t()) +def all(params) do + Paging.page(Process, params) end @spec create(params()) :: {:ok, Process.t()} | {:error, chgset()} def create(params) do Multi.new() - |> Multi.insert(:proc, Process.chgset(params)) + |> Multi.insert(:insert, Process.chgset(params)) |> Repo.transaction() |> case do - {:ok, %{proc: p}} -> {:ok, p} + {:ok, %{insert: p}} -> {:ok, p} {:error, _, cset, _} -> {:error, cset} end end -@spec update(id(), params()) :: {:ok, Process.t()} | {:error, chgset()} +@spec update(id(), params()) + :: {:ok, Process.t()} | {:error, String.t() | chgset()} def update(id, params) do Multi.new() - |> Multi.run(:get, multi_get(id)) - |> Multi.update(:update, &Process.chgset(&1.get, params)) + |> Multi.put(:id, id) + |> Multi.run(:one, &one/2) + |> Multi.update(:update, &Process.chgset(&1.one, params)) |> Repo.transaction() |> case do {:ok, %{update: p}} -> {:ok, p} @@ -56,11 +69,12 @@ def update(id, params) do end end -@spec delete(id()) :: {:ok, Process.t()} | {:error, chgset()} +@spec delete(id()) :: {:ok, Process.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: p}} -> {:ok, p} @@ -81,17 +95,4 @@ end def preload(proc, :nested_in) do Repo.preload(proc, :nested_in) end - -# Returns a Process 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, Process.t()} | {:error, String.t()}) -defp multi_get(id) do - fn repo, _ -> - case by_id(repo, id) do - nil -> {:error, "not found"} - p -> {:ok, p} - end - end -end end diff --git a/src/zenflows/vf/process/resolv.ex b/src/zenflows/vf/process/resolv.ex @@ -20,41 +20,45 @@ defmodule Zenflows.VF.Process.Resolv do use Absinthe.Schema.Notation -alias Zenflows.VF.{Process, Process.Domain} +alias Zenflows.VF.Process.Domain -def process(%{id: id}, _info) do - {:ok, Domain.by_id(id)} +def process(params, _) do + Domain.one(params) end -def create_process(%{process: params}, _info) do +def processes(params, _) do + Domain.all(params) +end + +def create_process(%{process: params}, _) do with {:ok, process} <- Domain.create(params) do {:ok, %{process: process}} end end -def update_process(%{process: %{id: id} = params}, _info) do +def update_process(%{process: %{id: id} = params}, _) do with {:ok, proc} <- Domain.update(id, params) do {:ok, %{process: proc}} end end -def delete_process(%{id: id}, _info) do +def delete_process(%{id: id}, _) do with {:ok, _} <- Domain.delete(id) do {:ok, true} end end -def based_on(%Process{} = proc, _args, _info) do +def based_on(proc, _, _) do proc = Domain.preload(proc, :based_on) {:ok, proc.based_on} end -def planned_within(%Process{} = proc, _args, _info) do +def planned_within(proc, _, _) do proc = Domain.preload(proc, :planned_within) {:ok, proc.planned_within} end -def nested_in(%Process{} = proc, _args, _info) do +def nested_in(proc, _, _) do proc = Domain.preload(proc, :nested_in) {:ok, proc.nested_in} end diff --git a/src/zenflows/vf/process/type.ex b/src/zenflows/vf/process/type.ex @@ -41,12 +41,15 @@ References one or more concepts in a common taxonomy or other classification scheme for purposes of categorization or grouping. """ @based_on "The definition or specification for a process." +@based_on_id "(`ProcesssSpecification`) #{@based_on}" @planned_within """ The process with its inputs and outputs is part of the plan. """ +@planned_within_id "(`Plan`) #{@planned_within}" @nested_in """ The process with its inputs and outputs is part of the scenario. """ +@nested_in_id "(`Scenario`) #{@nested_in}" @desc """ A logical collection of processes that constitute a body of processned work @@ -88,10 +91,6 @@ object :process do field :nested_in, :scenario, resolve: &Resolv.nested_in/3 end -object :process_response do - field :process, non_null(:process) -end - input_object :process_create_params do @desc @name field :name, non_null(:string) @@ -111,22 +110,13 @@ input_object :process_create_params do @desc @classified_as field :classified_as, list_of(non_null(:uri)) - # TODO: When - # https://github.com/absinthe-graphql/absinthe/issues/1126 results, - # apply the correct changes if any. - @desc "(`ProcessSpecification`) " <> @based_on + @desc @based_on_id field :based_on_id, :id, name: "based_on" - # TODO: When - # https://github.com/absinthe-graphql/absinthe/issues/1126 results, - # apply the correct changes if any. - @desc "(`Plan`) " <> @planned_within + @desc @planned_within_id field :planned_within_id, :id, name: "planned_within" - # TODO: When - # https://github.com/absinthe-graphql/absinthe/issues/1126 results, - # apply the correct changes if any. - @desc "(`Scenario`) " <> @nested_in + @desc @nested_in_id field :nested_in_id, :id, name: "nested_in" end @@ -151,32 +141,43 @@ input_object :process_update_params do @desc @classified_as field :classified_as, list_of(non_null(:uri)) - # TODO: When - # https://github.com/absinthe-graphql/absinthe/issues/1126 results, - # apply the correct changes if any. - @desc "(`ProcessSpecification`) " <> @based_on + @desc :based_on_id field :based_on_id, :id, name: "based_on" - # TODO: When - # https://github.com/absinthe-graphql/absinthe/issues/1126 results, - # apply the correct changes if any. - @desc "(`Plan`) " <> @planned_within + @desc @planned_within_id field :planned_within_id, :id, name: "planned_within" - # TODO: When - # https://github.com/absinthe-graphql/absinthe/issues/1126 results, - # apply the correct changes if any. - @desc "(`Scenario`) " <> @nested_in + @desc @nested_in_id field :nested_in_id, :id, name: "nested_in" end +object :process_response do + field :process, non_null(:process) +end + +object :process_edge do + field :cursor, non_null(:id) + field :node, non_null(:process) +end + +object :process_connection do + field :page_info, non_null(:page_info) + field :edges, non_null(list_of(non_null(:process_edge))) +end + object :query_process do field :process, :process do arg :id, non_null(:id) resolve &Resolv.process/2 end - #processes(start: ID, limit: Int): [Process!] + field :processes, :process_connection do + arg :first, :integer + arg :after, :id + arg :last, :integer + arg :before, :id + resolve &Resolv.processes/2 + end end object :mutation_process do diff --git a/test/vf/process/domain.test.exs b/test/vf/process/domain.test.exs @@ -27,55 +27,55 @@ alias Zenflows.VF.{ Scenario, } -setup ctx do - params = %{ - name: Factory.uniq("name"), - note: Factory.uniq("note"), - has_beginning: DateTime.utc_now(), - has_end: DateTime.utc_now(), - finished: Factory.bool(), - classified_as: Factory.uniq_list("class"), - based_on_id: Factory.insert!(:process_specification).id, - planned_within_id: Factory.insert!(:plan).id, - nested_in_id: Factory.insert!(:scenario).id, - } +setup do + %{ + params: %{ + name: Factory.uniq("name"), + note: Factory.uniq("note"), + has_beginning: DateTime.utc_now(), + has_end: DateTime.utc_now(), + finished: Factory.bool(), + classified_as: Factory.uniq_list("class"), + based_on_id: Factory.insert!(:process_specification).id, + planned_within_id: Factory.insert!(:plan).id, + nested_in_id: Factory.insert!(:scenario).id, + }, + inserted: Factory.insert!(:process), + } +end - if ctx[:no_insert] do - %{params: params} - else - %{params: params, process: Factory.insert!(:process)} +describe "one/1" do + test "with good id: finds the Process", %{inserted: %{id: id}} do + assert {:ok, %Process{}} = Domain.one(id) end -end -test "by_id/1 returns a Process", %{process: proc} do - assert %Process{} = Domain.by_id(proc.id) + test "with bad id: doesn't find the Process" do + assert {:error, "not found"} = Domain.one(Factory.id()) + end end describe "create/1" do - @tag :no_insert - test "creates a Process", %{params: params} do - assert {:ok, %Process{} = proc} = Domain.create(params) - - assert proc.name == params.name - assert proc.note == params.note - assert proc.has_beginning == params.has_beginning - assert proc.has_end == params.has_end - assert proc.finished == params.finished - assert proc.classified_as == params.classified_as - assert proc.based_on_id == params.based_on_id - assert proc.planned_within_id == params.planned_within_id - assert proc.nested_in_id == params.nested_in_id + test "with good params: creates a Process", %{params: params} do + assert {:ok, %Process{} = new} = Domain.create(params) + assert new.name == params.name + assert new.note == params.note + assert new.has_beginning == params.has_beginning + assert new.has_end == params.has_end + assert new.finished == params.finished + assert new.classified_as == params.classified_as + assert new.based_on_id == params.based_on_id + assert new.planned_within_id == params.planned_within_id + assert new.nested_in_id == params.nested_in_id end - test "doesn't create a Process 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 Process with valid params", %{params: params, process: old} do + test "with good params: updates the Process", %{params: params, inserted: old} do assert {:ok, %Process{} = new} = Domain.update(old.id, params) - assert new.name == params.name assert new.note == params.note assert new.has_beginning == params.has_beginning @@ -87,9 +87,8 @@ describe "update/2" do assert new.nested_in_id == params.nested_in_id end - test "doesn't update a Process with invalid params", %{process: old} do + test "with bad params: doesn't update the Process", %{inserted: old} do assert {:ok, %Process{} = new} = Domain.update(old.id, %{}) - assert new.name == old.name assert new.note == old.note assert new.has_beginning == old.has_beginning @@ -102,25 +101,31 @@ describe "update/2" do end end -test "delete/1 deletes a Process", %{process: %{id: id}} do - assert {:ok, %Process{id: ^id}} = Domain.delete(id) - assert Domain.by_id(id) == nil +describe "delete/1" do + test "with good id: deletes the Process", %{inserted: %{id: id}} do + assert {:ok, %Process{id: ^id}} = Domain.delete(id) + assert {:error, "not found"} = Domain.one(id) + end + + test "with bad id: doesn't delete the Process" do + assert {:error, "not found"} = Domain.delete(Factory.id()) + end end describe "preload/2" do - test "preloads :based_on", %{process: proc} do + test "preloads :based_on", %{inserted: proc} do proc = Domain.preload(proc, :based_on) assert based_on = %ProcessSpecification{} = proc.based_on assert based_on.id == proc.based_on_id end - test "preloads :planned_within", %{process: proc} do + test "preloads :planned_within", %{inserted: proc} do proc = Domain.preload(proc, :planned_within) assert planed_within = %Plan{} = proc.planned_within assert planed_within.id == proc.planned_within_id end - test "preloads :nested_in", %{process: proc} do + test "preloads :nested_in", %{inserted: proc} do proc = Domain.preload(proc, :nested_in) assert nested_in = %Scenario{} = proc.nested_in assert nested_in.id == proc.nested_in_id diff --git a/test/vf/process/type.test.exs b/test/vf/process/type.test.exs @@ -21,143 +21,111 @@ use ZenflowsTest.Help.AbsinCase, async: true setup do %{ params: %{ - name: Factory.uniq("name"), - note: Factory.uniq("note"), - has_beginning: DateTime.utc_now(), - has_end: DateTime.utc_now(), - finished: Factory.bool(), - classified_as: Factory.uniq_list("class"), - based_on_id: Factory.insert!(:process_specification).id, - planned_within_id: Factory.insert!(:plan).id, - nested_in_id: Factory.insert!(:scenario).id, + "name" => Factory.uniq("name"), + "note" => Factory.uniq("note"), + "hasBeginning" => Factory.iso_now(), + "hasEnd" => Factory.iso_now(), + "finished" => Factory.bool(), + "classifiedAs" => Factory.uniq_list("class"), + "basedOn" => Factory.insert!(:process_specification).id, + "plannedWithin" => Factory.insert!(:plan).id, + "nestedIn" => Factory.insert!(:scenario).id, }, - process: Factory.insert!(:process), + inserted: Factory.insert!(:process), } end +@frag """ +fragment process on Process { + id + name + note + hasBeginning + hasEnd + finished + deletable + classifiedAs + basedOn {id} + plannedWithin {id} + nestedIn {id} +} +""" + describe "Query" do - test "process()", %{process: proc} do + test "process", %{inserted: new} do assert %{data: %{"process" => data}} = - query!(""" - process(id: "#{proc.id}") { - id - name - note - hasBeginning - hasEnd - finished - deletable - classifiedAs - basedOn {id} - plannedWithin {id} - nestedIn {id} + run!(""" + #{@frag} + query ($id: ID!) { + process(id: $id) {...process} } - """) + """, vars: %{"id" => new.id}) - assert data["id"] == proc.id - assert data["name"] == proc.name - assert data["hasBeginning"] == DateTime.to_iso8601(proc.has_beginning) - assert data["hasEnd"] == DateTime.to_iso8601(proc.has_end) - assert data["finished"] == proc.finished + assert data["id"] == new.id + assert data["name"] == new.name + assert data["hasBeginning"] == DateTime.to_iso8601(new.has_beginning) + assert data["hasEnd"] == DateTime.to_iso8601(new.has_end) + assert data["finished"] == new.finished assert data["deletable"] == false - assert data["classifiedAs"] == proc.classified_as - assert data["basedOn"]["id"] == proc.based_on_id - assert data["plannedWithin"]["id"] == proc.planned_within_id - assert data["nestedIn"]["id"] == proc.nested_in_id + assert data["classifiedAs"] == new.classified_as + assert data["basedOn"]["id"] == new.based_on_id + assert data["plannedWithin"]["id"] == new.planned_within_id + assert data["nestedIn"]["id"] == new.nested_in_id end end describe "Mutation" do - test "createProcess()", %{params: params} do + test "createProcess", %{params: params} do assert %{data: %{"createProcess" => %{"process" => data}}} = - mutation!(""" - createProcess(process: { - name: "#{params.name}" - note: "#{params.note}" - hasBeginning: "#{params.has_beginning}" - hasEnd: "#{params.has_end}" - finished: #{params.finished} - classifiedAs: #{inspect(params.classified_as)} - basedOn: "#{params.based_on_id}" - plannedWithin: "#{params.planned_within_id}" - nestedIn: "#{params.nested_in_id}" - }) { - process { - id - name - note - hasBeginning - hasEnd - finished - deletable - classifiedAs - basedOn {id} - plannedWithin {id} - nestedIn {id} + run!(""" + #{@frag} + mutation ($process: ProcessCreateParams!) { + createProcess(process: $process) { + process {...process} } } - """) + """, vars: %{"process" => params}) assert {:ok, _} = Zenflows.DB.ID.cast(data["id"]) - assert data["name"] == params.name - assert data["hasBeginning"] == DateTime.to_iso8601(params.has_beginning) - assert data["hasEnd"] == DateTime.to_iso8601(params.has_end) - assert data["finished"] == params.finished + + keys = ~w[name hasBeginning hasEnd finished classifiedAs] + assert Map.take(data, keys) == Map.take(params, keys) + assert data["deletable"] == false - assert data["classifiedAs"] == params.classified_as - assert data["basedOn"]["id"] == params.based_on_id - assert data["plannedWithin"]["id"] == params.planned_within_id - assert data["nestedIn"]["id"] == params.nested_in_id + assert data["basedOn"]["id"] == params["basedOn"] + assert data["plannedWithin"]["id"] == params["plannedWithin"] + assert data["nestedIn"]["id"] == params["nestedIn"] end - test "updateProcess()", %{params: params, process: proc} do + test "updateProcess", %{params: params, inserted: old} do assert %{data: %{"updateProcess" => %{"process" => data}}} = - mutation!(""" - updateProcess(process: { - id: "#{proc.id}" - name: "#{params.name}" - note: "#{params.note}" - hasBeginning: "#{params.has_beginning}" - hasEnd: "#{params.has_end}" - finished: #{params.finished} - classifiedAs: #{inspect(params.classified_as)} - basedOn: "#{params.based_on_id}" - plannedWithin: "#{params.planned_within_id}" - nestedIn: "#{params.nested_in_id}" - }) { - process { - id - name - note - hasBeginning - hasEnd - finished - deletable - classifiedAs - basedOn {id} - plannedWithin {id} - nestedIn {id} + run!(""" + #{@frag} + mutation ($process: ProcessUpdateParams!) { + updateProcess(process: $process) { + process {...process} } } - """) + """, vars: %{"process" => Map.put(params, "id", old.id)}) + + + assert data["id"] == old.id + keys = ~w[name hasBeginning hasEnd finished classifiedAs] + assert Map.take(data, keys) == Map.take(params, keys) - assert data["id"] == proc.id - assert data["name"] == params.name - assert data["hasBeginning"] == DateTime.to_iso8601(params.has_beginning) - assert data["hasEnd"] == DateTime.to_iso8601(params.has_end) - assert data["finished"] == params.finished assert data["deletable"] == false - assert data["classifiedAs"] == params.classified_as - assert data["basedOn"]["id"] == params.based_on_id - assert data["plannedWithin"]["id"] == params.planned_within_id - assert data["nestedIn"]["id"] == params.nested_in_id + assert data["basedOn"]["id"] == params["basedOn"] + assert data["plannedWithin"]["id"] == params["plannedWithin"] + assert data["nestedIn"]["id"] == params["nestedIn"] end - test "deleteProcess()", %{process: %{id: id}} do + test "deleteProcess", %{inserted: %{id: id}} do assert %{data: %{"deleteProcess" => true}} = - mutation!(""" - deleteProcess(id: "#{id}") - """) + run!(""" + mutation ($id: ID!) { + deleteProcess(id: $id) + } + """, vars: %{"id" => id}) end end end