commit f16b3c6d12a644a855d2d62b2538269612b09a26
parent 2cf3a130c8bb1c6e270cac33e0cdb1072ac2da5b
Author: srfsh <dev@srf.sh>
Date: Mon, 22 Aug 2022 18:30:00 +0300
Zenflows{Test,}.VF.Person: add paging support and small improvements
Diffstat:
5 files changed, 189 insertions(+), 221 deletions(-)
diff --git a/src/zenflows/vf/person/domain.ex b/src/zenflows/vf/person/domain.ex
@@ -22,62 +22,55 @@ import Ecto.Query
alias Ecto.Multi
alias Zenflows.DB.Repo
+alias Zenflows.GQL.Paging
alias Zenflows.VF.Person
@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(repo(), Keyword.t() | map()) :: Person.t() | nil
-def by(repo \\ Repo, clauses)
-def by(repo, clauses) when is_map(clauses) do
- repo.get_by(Person, Map.put(clauses, :type, :per))
-end
-def by(repo, clauses) when is_list(clauses) do
- repo.get_by(Person, Keyword.put(clauses, :type, :per))
-end
-
-@spec by_id(repo(), id()) :: Person.t() | nil
-def by_id(repo \\ Repo, id) do
- by(repo, id: id)
-end
-
-@spec by_user(repo(), String.t()) :: Person.t() | nil
-def by_user(repo \\ Repo, user) do
- by(repo, user: user)
+@spec one(repo(), id() | map() | Keyword.t()) :: {:ok, Person.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
+ clauses = if(is_map(clauses),
+ do: Map.put(clauses, :type, :per),
+ else: Keyword.put(clauses, :type, :per))
+ case repo.get_by(Person, clauses) do
+ nil -> {:error, "not found"}
+ found -> {:ok, found}
+ end
end
-@spec exists_email(String.t()) :: boolean()
-def exists_email(email) do
- where(Person, email: ^email) |> Repo.exists?()
+@spec all(Paging.params()) :: Paging.result(Person.t())
+def all(params) do
+ Paging.page(where(Person, type: :per), params)
end
-@spec all() :: [Person.t()]
-def all() do
- Person
- |> from()
- |> where(type: :per)
- |> Repo.all()
+@spec exists?(Keyword.t()) :: boolean()
+def exists?(conds) do
+ where(Person, ^conds) |> Repo.exists?()
end
@spec create(params()) :: {:ok, Person.t()} | {:error, chgset()}
def create(params) do
Multi.new()
- |> Multi.insert(:per, Person.chgset(params))
+ |> Multi.insert(:insert, Person.chgset(params))
|> Repo.transaction()
|> case do
- {:ok, %{per: p}} -> {:ok, p}
+ {:ok, %{insert: p}} -> {:ok, p}
{:error, _, cset, _} -> {:error, cset}
end
end
-@spec update(id(), params()) :: {:ok, Person.t()} | {:error, String.t() | chgset()}
+@spec update(id(), params())
+ :: {:ok, Person.t()} | {:error, String.t() | chgset()}
def update(id, params) do
Multi.new()
- |> Multi.run(:get, multi_get(id))
- |> Multi.update(:update, &Person.chgset(&1.get, params))
+ |> Multi.put(:id, id)
+ |> Multi.run(:one, &one/2)
+ |> Multi.update(:update, &Person.chgset(&1.one, params))
|> Repo.transaction()
|> case do
{:ok, %{update: p}} -> {:ok, p}
@@ -88,8 +81,9 @@ end
@spec delete(id()) :: {:ok, Person.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}
@@ -101,16 +95,4 @@ end
def preload(per, :primary_location) do
Repo.preload(per, :primary_location)
end
-
-# Returns a Person 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, Person.t()} | {:error, String.t()})
-defp multi_get(id) do
- fn repo, _ ->
- case by_id(repo, id) do
- nil -> {:error, "not found"}
- per -> {:ok, per}
- end
- end
-end
end
diff --git a/src/zenflows/vf/person/resolv.ex b/src/zenflows/vf/person/resolv.ex
@@ -20,40 +20,44 @@ defmodule Zenflows.VF.Person.Resolv do
alias Zenflows.VF.{Agent, Person, Person.Domain}
-def person(%{id: id}, _info) do
- {:ok, Domain.by_id(id)}
+def person(params, _) do
+ Domain.one(params)
end
-def person_exists(params, _info) do
- {:ok, Domain.by(params)}
+def people(params, _) do
+ Domain.all(params)
end
-def create_person(%{person: params}, _info) do
+def person_exists(params, _) do
+ Domain.one(params)
+end
+
+def create_person(%{person: params}, _) do
with {:ok, per} <- Domain.create(params) do
{:ok, %{agent: per}}
end
end
-def update_person(%{person: %{id: id} = params}, _info) do
+def update_person(%{person: %{id: id} = params}, _) do
with {:ok, per} <- Domain.update(id, params) do
{:ok, %{agent: per}}
end
end
-def delete_person(%{id: id}, _info) do
+def delete_person(%{id: id}, _) do
with {:ok, _} <- Domain.delete(id) do
{:ok, true}
end
end
-def primary_location(%Person{} = per, _args, _info) do
+def primary_location(%Person{} = per, _, _) do
per = Domain.preload(per, :primary_location)
{:ok, per.primary_location}
end
# For some reason, Absinthe calls this one instead of the one on
# Zenflows.VF.Agent.Type for queries to Agent itself.
-def primary_location(%Agent{} = agent, args, info) do
- Agent.Resolv.primary_location(agent, args, info)
+def primary_location(%Agent{} = agent, params, info) do
+ Agent.Resolv.primary_location(agent, params, info)
end
end
diff --git a/src/zenflows/vf/person/type.ex b/src/zenflows/vf/person/type.ex
@@ -34,6 +34,7 @@ occur and mail can be sent. This is usually a mappable geographic
location. It also could be a website address, as in the case of agents
who have no physical location.
"""
+@primary_location_id "(`SpatialThing`) #{@primary_location}"
@user "Username of the agent. Implies uniqueness."
@email "Email address of the agent. Implies uniqueness."
@ecdh_public_key "ecdh public key, encoded by zenroom"
@@ -83,10 +84,6 @@ object :person do
field :schnorr_public_key, :string
end
-object :person_response do
- field :agent, non_null(:person)
-end
-
input_object :person_create_params do
@desc @name
field :name, non_null(:string)
@@ -100,7 +97,7 @@ input_object :person_create_params do
# TODO: When
# https://github.com/absinthe-graphql/absinthe/issues/1126 results,
# apply the correct changes if any.
- @desc "(`SpatialThing`) " <> @primary_location
+ @desc @primary_location_id
field :primary_location_id, :id, name: "primary_location"
@desc @user
@@ -137,13 +134,27 @@ input_object :person_update_params do
@desc @note
field :note, :string
- @desc "(`SpatialThing`) " <> @primary_location
+ @desc @primary_location_id
field :primary_location_id, :id, name: "primary_location"
@desc @user
field :user, :string
end
+object :person_response do
+ field :agent, non_null(:person)
+end
+
+object :person_edge do
+ field :cursor, non_null(:id)
+ field :node, non_null(:person)
+end
+
+object :person_connection do
+ field :page_info, non_null(:page_info)
+ field :edges, non_null(list_of(non_null(:person_edge)))
+end
+
object :query_person do
@desc "Find a person by their ID."
field :person, :person do
@@ -151,6 +162,18 @@ object :query_person do
resolve &Resolv.person/2
end
+ @desc """
+ Loads all people who have publicly registered with this collaboration
+ space.
+ """
+ field :people, :person_connection do
+ arg :first, :integer
+ arg :after, :id
+ arg :last, :integer
+ arg :before, :id
+ resolve &Resolv.people/2
+ end
+
@desc "Find if a person exists by email and eddsa-public-key."
field :person_exists, :person do
meta only_guest?: true
@@ -158,9 +181,6 @@ object :query_person do
arg :eddsa_public_key, non_null(:string)
resolve &Resolv.person_exists/2
end
-
- #"Loads all people who have publicly registered with this collaboration space."
- #people(start: ID, limit: Int): [Person!]
end
object :mutation_person do
diff --git a/test/vf/person/domain.test.exs b/test/vf/person/domain.test.exs
@@ -21,80 +21,65 @@ use ZenflowsTest.Help.EctoCase, async: true
alias Ecto.Changeset
alias Zenflows.VF.{Person, Person.Domain}
-setup ctx do
- params = %{
- name: Factory.uniq("name"),
- image: Factory.img(),
- note: Factory.uniq("note"),
- primary_location_id: Factory.insert!(:spatial_thing).id,
- user: Factory.uniq("user"),
- email: "#{Factory.uniq("user")}@example.com",
- ecdh_public_key: Base.encode64("ecdh_public_key"),
- eddsa_public_key: Base.encode64("eddsa_public_key"),
- ethereum_address: Base.encode64("ethereum_address"),
- reflow_public_key: Base.encode64("reflow_public_key"),
- schnorr_public_key: Base.encode64("schnorr_public_key"),
+setup do
+ %{
+ params: %{
+ name: Factory.uniq("name"),
+ image: Factory.img(),
+ note: Factory.uniq("note"),
+ primary_location_id: Factory.insert!(:spatial_thing).id,
+ user: Factory.uniq("user"),
+ email: "#{Factory.uniq("user")}@example.com",
+ ecdh_public_key: Base.encode64("ecdh_public_key"),
+ eddsa_public_key: Base.encode64("eddsa_public_key"),
+ ethereum_address: Base.encode64("ethereum_address"),
+ reflow_public_key: Base.encode64("reflow_public_key"),
+ schnorr_public_key: Base.encode64("schnorr_public_key"),
+ },
+ inserted: Factory.insert!(:person),
}
-
- if ctx[:no_insert] do
- %{params: params}
- else
- %{params: params, per: Factory.insert!(:person)}
- end
end
-describe "by_id/1" do
- test "returns a Person", %{per: per} do
- assert %Person{type: :per} = Domain.by_id(per.id)
+describe "one/1" do
+ test "with good id: finds the Person", %{inserted: %{id: id}} do
+ assert {:ok, %Person{}} = Domain.one(id)
end
- test "doesn't return an Organization" do
+ test "with org's id: doesn't return an Organization" do
org = Factory.insert!(:organization)
-
- assert Domain.by_id(org.id) == nil
+ assert {:error, "not found"} = Domain.one(org.id)
end
-end
-
-@tag :no_insert
-test "all/0 returns all Persons" do
- want_ids =
- Enum.map(1..10, fn _ -> Factory.insert!(:person).id end)
- |> Enum.sort()
- have_ids =
- Domain.all()
- |> Enum.map(& &1.id)
- |> Enum.sort()
- assert have_ids == want_ids
+ test "with bad id: doesn't find the Person" do
+ assert {:error, "not found"} = Domain.one(Factory.id())
+ end
end
describe "create/1" do
- test "creates a Person with valid params", %{params: params} do
- assert {:ok, %Person{} = per} = Domain.create(params)
-
- assert per.type == :per
- assert per.name == params.name
- assert per.note == params.note
- assert per.image == params.image
- assert per.primary_location_id == params.primary_location_id
- assert per.user == params.user
- assert per.email == params.email
- assert per.ecdh_public_key == params.ecdh_public_key
- assert per.eddsa_public_key == params.eddsa_public_key
- assert per.ethereum_address == params.ethereum_address
- assert per.reflow_public_key == params.reflow_public_key
- assert per.schnorr_public_key == params.schnorr_public_key
+ test "with good params: creates a Person", %{params: params} do
+ assert {:ok, %Person{} = new} = Domain.create(params)
+ assert new.type == :per
+ assert new.name == params.name
+ assert new.note == params.note
+ assert new.image == params.image
+ assert new.primary_location_id == params.primary_location_id
+ assert new.user == params.user
+ assert new.email == params.email
+ assert new.ecdh_public_key == params.ecdh_public_key
+ assert new.eddsa_public_key == params.eddsa_public_key
+ assert new.ethereum_address == params.ethereum_address
+ assert new.reflow_public_key == params.reflow_public_key
+ assert new.schnorr_public_key == params.schnorr_public_key
end
- test "doesn't create a Person with invalid params" do
+ test "with bad params: doesn't create a Person" do
assert {:error, %Changeset{}} = Domain.create(%{})
end
end
describe "update/2" do
- test "updates a Person with valid params", %{params: params, per: old} do
+ test "with good params: updates the Person", %{params: params, inserted: old} do
assert {:ok, %Person{} = new} = Domain.update(old.id, params)
-
assert new.name == params.name
assert new.note == params.note
assert new.image == params.image
@@ -108,10 +93,8 @@ describe "update/2" do
assert new.schnorr_public_key == old.schnorr_public_key
end
- test "doesn't update a Person with invalid params", %{per: old} do
- assert {:ok, %Person{} = new} =
- Domain.update(old.id, %{email: "can't change that yet"})
-
+ test "with bad params: doesn't update the Person", %{inserted: old} do
+ assert {:ok, %Person{} = new} = Domain.update(old.id, %{email: "can't change that yet"})
assert new.name == old.name
assert new.note == old.note
assert new.image == old.image
@@ -126,8 +109,14 @@ describe "update/2" do
end
end
-test "delete/1 deletes a Person", %{per: %{id: id}} do
- assert {:ok, %Person{id: ^id}} = Domain.delete(id)
- assert Domain.by_id(id) == nil
+describe "delete/1" do
+ test "with good id: deletes the Person", %{inserted: %{id: id}} do
+ assert {:ok, %Person{id: ^id}} = Domain.delete(id)
+ assert {:error, "not found"} = Domain.one(id)
+ end
+
+ test "with bad id: doesn't delete the Person" do
+ assert {:error, "not found"} = Domain.delete(Factory.id())
+ end
end
end
diff --git a/test/vf/person/type.test.exs b/test/vf/person/type.test.exs
@@ -33,94 +33,79 @@ setup do
"reflowPublicKey" => Base.encode64("reflow_public_key"),
"schnorrPublicKey" => Base.encode64("schnorr_public_key"),
},
- per: Factory.insert!(:person),
+ inserted: Factory.insert!(:person),
}
end
+@frag """
+fragment person on Person {
+ id
+ name
+ note
+ image
+ primaryLocation { id }
+ user
+ email
+ ecdhPublicKey
+ eddsaPublicKey
+ ethereumAddress
+ reflowPublicKey
+ schnorrPublicKey
+}
+"""
+
describe "Query" do
- test "person()", %{per: per} do
+ test "person", %{inserted: new} do
assert %{data: %{"person" => data}} =
run!("""
+ #{@frag}
query ($id: ID!) {
- person(id: $id) {
- id
- name
- note
- image
- primaryLocation { id }
- user
- email
- ecdhPublicKey
- eddsaPublicKey
- ethereumAddress
- reflowPublicKey
- schnorrPublicKey
- }
+ person(id: $id) {...person}
}
- """, variables: %{"id" => per.id})
-
- assert data["id"] == per.id
- assert data["name"] == per.name
- assert data["note"] == per.note
- assert data["image"] == per.image
- assert data["primaryLocation"]["id"] == per.primary_location_id
-
- assert data["user"] == per.user
- assert data["email"] == per.email
- assert data["ecdhPublicKey"] == per.ecdh_public_key
- assert data["eddsaPublicKey"] == per.eddsa_public_key
- assert data["ethereumAddress"] == per.ethereum_address
- assert data["reflowPublicKey"] == per.reflow_public_key
- assert data["schnorrPublicKey"] == per.schnorr_public_key
+ """, variables: %{"id" => new.id})
+
+ assert data["id"] == new.id
+ assert data["name"] == new.name
+ assert data["note"] == new.note
+ assert data["image"] == new.image
+ assert data["primaryLocation"]["id"] == new.primary_location_id
+
+ assert data["user"] == new.user
+ assert data["email"] == new.email
+ assert data["ecdhPublicKey"] == new.ecdh_public_key
+ assert data["eddsaPublicKey"] == new.eddsa_public_key
+ assert data["ethereumAddress"] == new.ethereum_address
+ assert data["reflowPublicKey"] == new.reflow_public_key
+ assert data["schnorrPublicKey"] == new.schnorr_public_key
end
end
describe "Mutation" do
- test "createPerson() doesn't create a person without the admin key", %{params: params} do
+ test "createPerson: doesn't create a person without the admin key", %{params: params} do
assert %{data: nil, errors: [%{message: "you are not an admin", path: ["createPerson"]}]} =
run!("""
+ #{@frag}
mutation ($person: PersonCreateParams!) {
createPerson(person: $person) {
- agent {
- id
- name
- note
- primaryLocation { id }
- user
- email
- ecdhPublicKey
- eddsaPublicKey
- ethereumAddress
- reflowPublicKey
- schnorrPublicKey
- }
+ agent {...person}
}
}
""", auth?: true, vars: %{"person" => params})
end
- test "createPerson() creates a person with the admin key", %{params: params} do
+ test "createPerson: creates a person with the admin key", %{params: params} do
assert %{data: %{"createPerson" => %{"agent" => data}}} =
run!("""
+ #{@frag}
mutation ($person: PersonCreateParams!) {
createPerson(person: $person) {
- agent {
- id
- name
- note
- primaryLocation { id }
- user
- email
- image
- ecdhPublicKey
- eddsaPublicKey
- ethereumAddress
- reflowPublicKey
- schnorrPublicKey
- }
+ agent {...person}
}
}
- """, vars: %{"person" => params})
+ """,
+ auth?: true,
+ ctx: %{gql_admin: admin_key()},
+ vars: %{"person" => params})
assert {:ok, _} = Zenflows.DB.ID.cast(data["id"])
data = Map.delete(data, "id")
@@ -132,60 +117,44 @@ describe "Mutation" do
assert data == params
end
- test "updatePerson()", %{params: params, per: per} do
+ test "updatePerson", %{params: params, inserted: old} do
assert %{data: %{"updatePerson" => %{"agent" => data}}} =
run!("""
+ #{@frag}
mutation ($person: PersonUpdateParams!) {
updatePerson(person: $person) {
- agent {
- id
- name
- note
- primaryLocation { id }
- user
- email
- image
- ecdhPublicKey
- eddsaPublicKey
- ethereumAddress
- reflowPublicKey
- schnorrPublicKey
- }
+ agent {...person}
}
}
""", vars: %{"person" =>
params
|> Map.take(~w[user name image note primaryLocation])
- |> Map.put("id", per.id)
+ |> Map.put("id", old.id)
})
keys = ~w[user name image note]
assert Map.take(data, keys) == Map.take(params, keys)
assert data["primaryLocation"]["id"] == params["primaryLocation"]
- assert data["id"] == per.id
- assert data["email"] == per.email
- assert data["ecdhPublicKey"] == per.ecdh_public_key
- assert data["eddsaPublicKey"] == per.eddsa_public_key
- assert data["ethereumAddress"] == per.ethereum_address
- assert data["reflowPublicKey"] == per.reflow_public_key
- assert data["schnorrPublicKey"] == per.schnorr_public_key
+ assert data["id"] == old.id
+ assert data["email"] == old.email
+ assert data["ecdhPublicKey"] == old.ecdh_public_key
+ assert data["eddsaPublicKey"] == old.eddsa_public_key
+ assert data["ethereumAddress"] == old.ethereum_address
+ assert data["reflowPublicKey"] == old.reflow_public_key
+ assert data["schnorrPublicKey"] == old.schnorr_public_key
end
- test "deletePerson() doesn't delete the person without the admin key", %{per: per} do
+ test "deletePerson: doesn't delete the person without the admin key", %{inserted: new} do
assert %{data: nil, errors: [%{message: "you are not an admin", path: ["deletePerson"]}]} =
run!("""
mutation ($id: ID!) {
deletePerson(id: $id)
}
- """, auth?: true, vars: %{"id" => per.id})
+ """, auth?: true, vars: %{"id" => new.id})
end
- test "deletePerson() deletes the person with the admin key", %{per: per} do
- key =
- Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key]
- |> Base.encode16(case: :lower)
-
+ test "deletePerson: deletes the person with the admin key", %{inserted: new} do
assert %{data: %{"deletePerson" => true}} =
run!(
"""
@@ -194,9 +163,13 @@ describe "Mutation" do
}
""",
auth?: true,
- vars: %{"id" => per.id},
- ctx: %{gql_admin: key}
- )
+ ctx: %{gql_admin: admin_key()},
+ vars: %{"id" => new.id})
+ end
+
+ defp admin_key() do
+ Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key]
+ |> Base.encode16(case: :lower)
end
end
end