commit 308fcdf722d3ae8bfacfedf0e4573927da975672
parent 3047717966d639b19549a0a901078c5a9bd7473b
Author: srfsh <dev@srf.sh>
Date: Tue, 23 Aug 2022 11:32:06 +0300
Zenflows{Test,}.VF.Scenario: add paging support and small improvements
Diffstat:
5 files changed, 162 insertions(+), 166 deletions(-)
diff --git a/src/zenflows/vf/scenario/domain.ex b/src/zenflows/vf/scenario/domain.ex
@@ -20,17 +20,28 @@ defmodule Zenflows.VF.Scenario.Domain do
alias Ecto.Multi
alias Zenflows.DB.Repo
+alias Zenflows.GQL.Paging
alias Zenflows.VF.Scenario
@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()) :: Scenario.t() | nil
-def by_id(repo \\ Repo, id) do
- repo.get(Scenario, id)
+@spec one(repo(), id() | map() | Keyword.t())
+ :: {:ok, Scenario.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(Scenario, clauses) do
+ nil -> {:error, "not found"}
+ found -> {:ok, found}
+ end
+end
+
+@spec all(Paging.params()) :: Paging.result(Scenario.t())
+def all(params) do
+ Paging.page(Scenario, params)
end
@spec create(params()) :: {:ok, Scenario.t()} | {:error, chgset()}
@@ -44,11 +55,13 @@ def create(params) do
end
end
-@spec update(id(), params()) :: {:ok, Scenario.t()} | {:error, chgset()}
+@spec update(id(), params())
+ :: {:ok, Scenario.t()} | {:error, String.t() | chgset()}
def update(id, params) do
Multi.new()
- |> Multi.run(:get, multi_get(id))
- |> Multi.update(:update, &Scenario.chgset(&1.get, params))
+ |> Multi.put(:id, id)
+ |> Multi.run(:one, &one/2)
+ |> Multi.update(:update, &Scenario.chgset(&1.one, params))
|> Repo.transaction()
|> case do
{:ok, %{update: s}} -> {:ok, s}
@@ -56,11 +69,12 @@ def update(id, params) do
end
end
-@spec delete(id()) :: {:ok, Scenario.t()} | {:error, chgset()}
+@spec delete(id()) :: {:ok, Scenario.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: s}} -> {:ok, s}
@@ -76,17 +90,4 @@ end
def preload(scen, :refinement_of) do
Repo.preload(scen, :refinement_of)
end
-
-# Returns a Scenario 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, Scenario.t()} | {:error, String.t()})
-defp multi_get(id) do
- fn repo, _ ->
- case by_id(repo, id) do
- nil -> {:error, "not found"}
- s -> {:ok, s}
- end
- end
-end
end
diff --git a/src/zenflows/vf/scenario/resolv.ex b/src/zenflows/vf/scenario/resolv.ex
@@ -20,36 +20,40 @@ defmodule Zenflows.VF.Scenario.Resolv do
use Absinthe.Schema.Notation
-alias Zenflows.VF.{Scenario, Scenario.Domain}
+alias Zenflows.VF.Scenario.Domain
-def scenario(%{id: id}, _info) do
- {:ok, Domain.by_id(id)}
+def scenario(params, _) do
+ Domain.one(params)
end
-def create_scenario(%{scenario: params}, _info) do
+def scenarios(params, _) do
+ Domain.all(params)
+end
+
+def create_scenario(%{scenario: params}, _) do
with {:ok, scen} <- Domain.create(params) do
{:ok, %{scenario: scen}}
end
end
-def update_scenario(%{scenario: %{id: id} = params}, _info) do
+def update_scenario(%{scenario: %{id: id} = params}, _) do
with {:ok, scen} <- Domain.update(id, params) do
{:ok, %{scenario: scen}}
end
end
-def delete_scenario(%{id: id}, _info) do
+def delete_scenario(%{id: id}, _) do
with {:ok, _} <- Domain.delete(id) do
{:ok, true}
end
end
-def defined_as(%Scenario{} = scen, _args, _info) do
+def defined_as(scen, _, _) do
scenario = Domain.preload(scen, :defined_as)
{:ok, scenario.defined_as}
end
-def refinement_of(%Scenario{} = scen, _args, _info) do
+def refinement_of(scen, _, _) do
scenario = Domain.preload(scen, :refinement_of)
{:ok, scenario.refinement_of}
end
diff --git a/src/zenflows/vf/scenario/type.ex b/src/zenflows/vf/scenario/type.ex
@@ -38,10 +38,12 @@ period.
@defined_as """
The scenario definition for this scenario, for example yearly budget.
"""
+@defined_as_id "(`ScenarioDefinition`) #{@defined_as}"
@refinement_of """
This scenario refines another scenario, often as time moves closer or
for more detail.
"""
+@refinement_of_id "(`Scenario`) #{@refinement_of}"
@desc """
An estimated or analytical logical collection of higher level processes
@@ -70,10 +72,6 @@ object :scenario do
field :refinement_of, :scenario, resolve: &Resolv.refinement_of/3
end
-object :scenario_response do
- field :scenario, non_null(:scenario)
-end
-
input_object :scenario_create_params do
@desc @name
field :name, non_null(:string)
@@ -87,16 +85,10 @@ input_object :scenario_create_params do
@desc @has_end
field :has_end, :datetime
- # TODO: When
- # https://github.com/absinthe-graphql/absinthe/issues/1126 results,
- # apply the correct changes if any.
- @desc "(`ScenarioDefinition`) " <> @defined_as
+ @desc @defined_as_id
field :defined_as_id, :id, name: "defined_as"
- # TODO: When
- # https://github.com/absinthe-graphql/absinthe/issues/1126 results,
- # apply the correct changes if any.
- @desc "(`Scenario`) " <> @refinement_of
+ @desc @refinement_of_id
field :refinement_of_id, :id, name: "refinement_of"
end
@@ -115,26 +107,40 @@ input_object :scenario_update_params do
@desc @has_end
field :has_end, :datetime
- # TODO: When
- # https://github.com/absinthe-graphql/absinthe/issues/1126 results,
- # apply the correct changes if any.
- @desc "(`ScenarioDefinition`) " <> @defined_as
+ @desc @defined_as_id
field :defined_as_id, :id, name: "defined_as"
- # TODO: When
- # https://github.com/absinthe-graphql/absinthe/issues/1126 results,
- # apply the correct changes if any.
- @desc "(`Scenario`) " <> @refinement_of
+ @desc @refinement_of_id
field :refinement_of_id, :id, name: "refinement_of"
end
+object :scenario_response do
+ field :scenario, non_null(:scenario)
+end
+
+object :scenario_edge do
+ field :cursor, non_null(:id)
+ field :node, non_null(:scenario)
+end
+
+object :scenario_connection do
+ field :page_info, non_null(:page_info)
+ field :edges, non_null(list_of(non_null(:scenario_edge)))
+end
+
object :query_scenario do
field :scenario, :scenario do
arg :id, non_null(:id)
resolve &Resolv.scenario/2
end
- #scenarioDefinitions(start: ID, limit: Int): [Scenario!]
+ field :scenarios, :scenario_connection do
+ arg :first, :integer
+ arg :after, :id
+ arg :last, :integer
+ arg :before, :id
+ resolve &Resolv.scenarios/2
+ end
end
object :mutation_scenario do
diff --git a/test/vf/scenario/domain.test.exs b/test/vf/scenario/domain.test.exs
@@ -25,49 +25,49 @@ alias Zenflows.VF.{
ScenarioDefinition,
}
-setup ctx do
- params = %{
- name: Factory.uniq("name"),
- note: Factory.uniq("note"),
- has_beginning: DateTime.utc_now(),
- has_end: DateTime.utc_now(),
- defined_as_id: Factory.insert!(:scenario_definition).id,
- refinement_of_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(),
+ defined_as_id: Factory.insert!(:scenario_definition).id,
+ refinement_of_id: Factory.insert!(:scenario).id,
+ },
+ inserted: Factory.insert!(:scenario),
+ }
+end
- if ctx[:no_insert] do
- %{params: params}
- else
- %{params: params, inserted: Factory.insert!(:scenario)}
+describe "one/1" do
+ test "with good id: finds the Scenario", %{inserted: %{id: id}} do
+ assert {:ok, %Scenario{}} = Domain.one(id)
end
-end
-test "by_id/1 returns a Scenario", %{inserted: scen} do
- assert %Scenario{} = Domain.by_id(scen.id)
+ test "with bad id: doesn't find the Scenario" do
+ assert {:error, "not found"} = Domain.one(Factory.id())
+ end
end
describe "create/1" do
- @tag :no_insert
- test "creates a Scenario with valid params", %{params: params} do
- assert {:ok, %Scenario{} = scen} = Domain.create(params)
-
- assert scen.name == params.name
- assert scen.note == params.note
- assert scen.has_beginning == params.has_beginning
- assert scen.has_end == params.has_end
- assert scen.defined_as_id == params.defined_as_id
- assert scen.refinement_of_id == params.refinement_of_id
+ test "with good params: creates a Scenario", %{params: params} do
+ assert {:ok, %Scenario{} = 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.defined_as_id == params.defined_as_id
+ assert new.refinement_of_id == params.refinement_of_id
end
- test "doesn't create a Scenario with invalid params" do
+ test "with bad params: doesn't create a Scenario" do
assert {:error, %Changeset{}} = Domain.create(%{})
end
end
describe "update/2" do
- test "updates a Scenario with valid params", %{params: params, inserted: old} do
+ test "with good params: updates the Scenario", %{params: params, inserted: old} do
assert {:ok, %Scenario{} = new} = Domain.update(old.id, params)
-
assert new.name == params.name
assert new.note == params.note
assert new.has_beginning == params.has_beginning
@@ -76,9 +76,8 @@ describe "update/2" do
assert new.refinement_of_id == params.refinement_of_id
end
- test "doesn't update a Scenario", %{inserted: old} do
+ test "with bad params: doesn't update the Scenario", %{inserted: old} do
assert {:ok, %Scenario{} = new} = Domain.update(old.id, %{})
-
assert new.name == old.name
assert new.note == old.note
assert new.has_beginning == old.has_beginning
@@ -88,9 +87,15 @@ describe "update/2" do
end
end
-test "delete/1 deletes a Scenario", %{inserted: %{id: id}} do
- assert {:ok, %Scenario{id: ^id}} = Domain.delete(id)
- assert Domain.by_id(id) == nil
+describe "delete/1" do
+ test "with good id: deletes the Scenario", %{inserted: %{id: id}} do
+ assert {:ok, %Scenario{id: ^id}} = Domain.delete(id)
+ assert {:error, "not found"} = Domain.one(id)
+ end
+
+ test "with bad id: doesn't delete the Scenario" do
+ assert {:error, "not found"} = Domain.delete(Factory.id())
+ end
end
describe "preload/2" do
diff --git a/test/vf/scenario/type.test.exs b/test/vf/scenario/type.test.exs
@@ -21,113 +21,93 @@ 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(),
- defined_as_id: Factory.insert!(:scenario_definition).id,
- refinement_of_id: Factory.insert!(:scenario).id,
+ "name" => Factory.str("name"),
+ "note" => Factory.str("note"),
+ "hasBeginning" => Factory.iso_now(),
+ "hasEnd" => Factory.iso_now(),
+ "definedAs" => Factory.insert!(:scenario_definition).id,
+ "refinementOf" => Factory.insert!(:scenario).id,
},
inserted: Factory.insert!(:scenario),
}
end
+@frag """
+fragment scenario on Scenario {
+ id
+ name
+ note
+ hasBeginning
+ hasEnd
+ definedAs {id}
+ refinementOf {id}
+}
+"""
+
describe "Query" do
- test "scenario()", %{inserted: scen} do
+ test "scenario", %{inserted: new} do
assert %{data: %{"scenario" => data}} =
- query!("""
- scenario(id: "#{scen.id}") {
- id
- name
- note
- hasBeginning
- hasEnd
- definedAs {id}
- refinementOf {id}
+ run!("""
+ #{@frag}
+ query ($id: ID!) {
+ scenario(id: $id) {...scenario}
}
- """)
+ """, vars: %{"id" => new.id})
- assert data["id"] == scen.id
- assert data["name"] == scen.name
- assert data["note"] == scen.note
- assert data["hasBeginning"] == DateTime.to_iso8601(scen.has_beginning)
- assert data["hasEnd"] == DateTime.to_iso8601(scen.has_end)
- assert data["definedAs"]["id"] == scen.defined_as_id
- assert data["refinementOf"]["id"] == scen.refinement_of_id
+ assert data["id"] == new.id
+ assert data["name"] == new.name
+ assert data["note"] == new.note
+ assert data["hasBeginning"] == DateTime.to_iso8601(new.has_beginning)
+ assert data["hasEnd"] == DateTime.to_iso8601(new.has_end)
+ assert data["definedAs"]["id"] == new.defined_as_id
+ assert data["refinementOf"]["id"] == new.refinement_of_id
end
end
describe "Mutation" do
- test "createScenario()", %{params: params} do
+ test "createScenario", %{params: params} do
assert %{data: %{"createScenario" => %{"scenario" => data}}} =
- mutation!("""
- createScenario(scenario: {
- name: "#{params.name}"
- note: "#{params.note}"
- hasBeginning: "#{params.has_beginning}"
- hasEnd: "#{params.has_end}"
- definedAs: "#{params.defined_as_id}"
- refinementOf: "#{params.refinement_of_id}"
- }) {
- scenario {
- id
- name
- note
- hasBeginning
- hasEnd
- definedAs {id}
- refinementOf {id}
+ run!("""
+ #{@frag}
+ mutation ($scenario: ScenarioCreateParams!) {
+ createScenario(scenario: $scenario) {
+ scenario {...scenario}
}
}
- """)
+ """, vars: %{"scenario" => params})
assert {:ok, _} = Zenflows.DB.ID.cast(data["id"])
- assert data["name"] == params.name
- assert data["note"] == params.note
- assert data["hasBeginning"] == DateTime.to_iso8601(params.has_beginning)
- assert data["hasEnd"] == DateTime.to_iso8601(params.has_end)
- assert data["definedAs"]["id"] == params.defined_as_id
- assert data["refinementOf"]["id"] == params.refinement_of_id
+ keys = ~w[name note hasBeginning hasEnd]
+ assert Map.take(data, keys) == Map.take(params, keys)
+ assert data["definedAs"]["id"] == params["definedAs"]
+ assert data["refinementOf"]["id"] == params["refinementOf"]
end
- test "updateScenario()", %{params: params, inserted: scen} do
+ test "updateScenario", %{params: params, inserted: old} do
assert %{data: %{"updateScenario" => %{"scenario" => data}}} =
- mutation!("""
- updateScenario(scenario: {
- id: "#{scen.id}"
- name: "#{params.name}"
- note: "#{params.note}"
- hasBeginning: "#{params.has_beginning}"
- hasEnd: "#{params.has_end}"
- definedAs: "#{params.defined_as_id}"
- refinementOf: "#{params.refinement_of_id}"
- }) {
- scenario {
- id
- name
- note
- hasBeginning
- hasEnd
- definedAs {id}
- refinementOf {id}
+ run!("""
+ #{@frag}
+ mutation ($scenario: ScenarioUpdateParams!) {
+ updateScenario(scenario: $scenario) {
+ scenario {...scenario}
}
}
- """)
+ """, vars: %{"scenario" => Map.put(params, "id", old.id)})
- assert data["id"] == scen.id
- assert data["name"] == params.name
- assert data["note"] == params.note
- assert data["hasBeginning"] == DateTime.to_iso8601(params.has_beginning)
- assert data["hasEnd"] == DateTime.to_iso8601(params.has_end)
- assert data["definedAs"]["id"] == params.defined_as_id
- assert data["refinementOf"]["id"] == params.refinement_of_id
+ assert data["id"] == old.id
+ keys = ~w[name note hasBeginning hasEnd]
+ assert Map.take(data, keys) == Map.take(params, keys)
+ assert data["definedAs"]["id"] == params["definedAs"]
+ assert data["refinementOf"]["id"] == params["refinementOf"]
end
- test "deleteScenario()", %{inserted: %{id: id}} do
+ test "deleteScenario", %{inserted: %{id: id}} do
assert %{data: %{"deleteScenario" => true}} =
- mutation!("""
- deleteScenario(id: "#{id}")
- """)
+ run!("""
+ mutation ($id: ID!) {
+ deleteScenario(id: $id)
+ }
+ """, vars: %{"id" => id})
end
end
end