zf

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

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:
Msrc/zenflows/vf/person/domain.ex | 74++++++++++++++++++++++++++++----------------------------------------------
Msrc/zenflows/vf/person/resolv.ex | 24++++++++++++++----------
Msrc/zenflows/vf/person/type.ex | 38+++++++++++++++++++++++++++++---------
Mtest/vf/person/domain.test.exs | 113++++++++++++++++++++++++++++++++++++-------------------------------------------
Mtest/vf/person/type.test.exs | 161+++++++++++++++++++++++++++++++++----------------------------------------------
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