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:
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