diff --git a/lib/mobilizon/actors/actor.ex b/lib/mobilizon/actors/actor.ex index 347f4085f..e4aeb0c16 100644 --- a/lib/mobilizon/actors/actor.ex +++ b/lib/mobilizon/actors/actor.ex @@ -8,6 +8,12 @@ defenum(Mobilizon.Actors.ActorTypeEnum, :actor_type, [ :Service ]) +defenum(Mobilizon.Actors.ActorOpennesssEnum, :openness, [ + :invite_only, + :moderated, + :open +]) + defmodule Mobilizon.Actors.Actor do @moduledoc """ Represents an actor (local and remote actors) @@ -42,6 +48,7 @@ defmodule Mobilizon.Actors.Actor do field(:suspended, :boolean, default: false) field(:avatar_url, :string) field(:banner_url, :string) + # field(:openness, Mobilizon.Actors.ActorOpennesssEnum, default: :moderated) many_to_many(:followers, Actor, join_through: Follower) has_many(:organized_events, Event, foreign_key: :organizer_actor_id) many_to_many(:memberships, Actor, join_through: Member) diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index 02e4b5ff5..04bd83706 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -10,10 +10,12 @@ defmodule Mobilizon.Actors do alias Mobilizon.Service.ActivityPub + @doc false def data() do Dataloader.Ecto.new(Repo, query: &query/2) end + @doc false def query(queryable, _params) do queryable end @@ -312,14 +314,51 @@ defmodule Mobilizon.Actors do if preload, do: Repo.preload(actor, [:followers]), else: actor end - def get_actor_by_name(name) do - case String.split(name, "@") do - [name] -> - Repo.one(from(a in Actor, where: a.preferred_username == ^name and is_nil(a.domain))) + @doc """ + Get an actor by name - [name, domain] -> - Repo.get_by(Actor, preferred_username: name, domain: domain) - end + ## Examples + iex> get_actor_by_name("tcit") + %Mobilizon.Actors.Actor{preferred_username: "tcit", domain: nil} + + iex> get_actor_by_name("tcit@social.tcit.fr") + %Mobilizon.Actors.Actor{preferred_username: "tcit", domain: "social.tcit.fr"} + + iex> get_actor_by_name("tcit", :Group) + nil + + """ + @spec get_actor_by_name(String.t(), atom() | nil) :: Actor.t() + def get_actor_by_name(name, type \\ nil) do + # Base query + query = from(a in Actor) + + # If we have Person / Group information + query = + if type in [:Person, :Group] do + from(a in query, where: a.type == ^type) + else + query + end + + # If the name is a remote actor + query = + case String.split(name, "@") do + [name] -> do_get_actor_by_name(query, name) + [name, domain] -> do_get_actor_by_name(query, name, domain) + end + + Repo.one(query) + end + + # Get actor by username and domain is nil + defp do_get_actor_by_name(query, name) do + from(a in query, where: a.preferred_username == ^name and is_nil(a.domain)) + end + + # Get actor by username and domain + defp do_get_actor_by_name(query, name, domain) do + from(a in query, where: a.preferred_username == ^name and a.domain == ^domain) end def get_local_actor_by_name(name) do @@ -331,17 +370,11 @@ defmodule Mobilizon.Actors do Repo.preload(actor, :organized_events) end - def get_actor_by_name_with_everything(name) do - actor = - case String.split(name, "@") do - [name] -> - Repo.one(from(a in Actor, where: a.preferred_username == ^name and is_nil(a.domain))) - - [name, domain] -> - Repo.one(from(a in Actor, where: a.preferred_username == ^name and a.domain == ^domain)) - end - - Repo.preload(actor, :organized_events) + @spec get_actor_by_name_with_everything(String.t(), atom() | nil) :: Actor.t() + def get_actor_by_name_with_everything(name, type \\ nil) do + name + |> get_actor_by_name(type) + |> Repo.preload(:organized_events) end def get_or_fetch_by_url(url, preload \\ false) do @@ -394,6 +427,7 @@ defmodule Mobilizon.Actors do @doc """ Find actors by their name or displayed name """ + @spec find_actors_by_username_or_name(String.t(), integer(), integer()) :: list(Actor.t()) def find_actors_by_username_or_name(username, page \\ 1, limit \\ 10) def find_actors_by_username_or_name("", _page, _limit), do: [] @@ -418,6 +452,7 @@ defmodule Mobilizon.Actors do end @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ + @spec search(String.t()) :: {:ok, list(Actor.t())} | {:ok, []} | {:error, any()} def search(name) do # find already saved accounts case find_actors_by_username_or_name(name) do diff --git a/lib/mobilizon/events/tag.ex b/lib/mobilizon/events/tag.ex index d0453bb51..967015351 100644 --- a/lib/mobilizon/events/tag.ex +++ b/lib/mobilizon/events/tag.ex @@ -23,7 +23,7 @@ defmodule Mobilizon.Events.Tag.TitleSlug do nil -> slug - _story -> + _tag -> slug |> Mobilizon.Slug.increment_slug() |> build_unique_slug(changeset) @@ -51,8 +51,8 @@ defmodule Mobilizon.Events.Tag do def changeset(%Tag{} = tag, attrs) do tag |> cast(attrs, [:title]) - |> validate_required([:title]) |> TitleSlug.maybe_generate_slug() + |> validate_required([:title, :slug]) |> TitleSlug.unique_constraint() end end diff --git a/lib/mobilizon_web/resolvers/actor.ex b/lib/mobilizon_web/resolvers/actor.ex index 8cb1909c5..1dfd02630 100644 --- a/lib/mobilizon_web/resolvers/actor.ex +++ b/lib/mobilizon_web/resolvers/actor.ex @@ -12,14 +12,40 @@ defmodule MobilizonWeb.Resolvers.Actor do end end + @doc """ + Find a person + """ + def find_person(_parent, %{preferred_username: name}, _resolution) do + case ActivityPub.find_or_make_person_from_nickname(name) do + {:ok, actor} -> + {:ok, actor} + + _ -> + {:error, "Person with name #{name} not found"} + end + end + + @doc """ + Find a person + """ + def find_group(_parent, %{preferred_username: name}, _resolution) do + case ActivityPub.find_or_make_group_from_nickname(name) do + {:ok, actor} -> + {:ok, actor} + + _ -> + {:error, "Group with name #{name} not found"} + end + end + @doc """ Returns the current actor for the currently logged-in user """ - def get_current_actor(_parent, _args, %{context: %{current_user: user}}) do + def get_current_person(_parent, _args, %{context: %{current_user: user}}) do {:ok, Actors.get_actor_for_user(user)} end - def get_current_actor(_parent, _args, _resolution) do - {:error, "You need to be logged-in to view current actor"} + def get_current_person(_parent, _args, _resolution) do + {:error, "You need to be logged-in to view current person"} end end diff --git a/lib/mobilizon_web/resolvers/user.ex b/lib/mobilizon_web/resolvers/user.ex index df3f2c5f2..7a9db6399 100644 --- a/lib/mobilizon_web/resolvers/user.ex +++ b/lib/mobilizon_web/resolvers/user.ex @@ -60,7 +60,7 @@ defmodule MobilizonWeb.Resolvers.User do Mobilizon.Actors.Service.Activation.check_confirmation_token(token), %Actor{} = actor <- Actors.get_actor_for_user(user), {:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do - {:ok, %{token: token, user: user, actor: actor}} + {:ok, %{token: token, user: user, person: actor}} end end diff --git a/lib/mobilizon_web/schema.ex b/lib/mobilizon_web/schema.ex index be2a9c794..6a97f687b 100644 --- a/lib/mobilizon_web/schema.ex +++ b/lib/mobilizon_web/schema.ex @@ -3,8 +3,8 @@ defmodule MobilizonWeb.Schema do import Absinthe.Resolution.Helpers, only: [dataloader: 1] alias Mobilizon.{Actors, Events} - alias Mobilizon.Actors.Actor - alias Mobilizon.Events.Event + alias Mobilizon.Actors.{Actor, Follower, Member} + alias Mobilizon.Events.{Event, Comment, Participant} import_types(MobilizonWeb.Schema.Custom.UUID) import_types(Absinthe.Type.Custom) @@ -12,18 +12,20 @@ defmodule MobilizonWeb.Schema do alias MobilizonWeb.Resolvers - @desc "An ActivityPub actor" - object :actor do + @desc """ + Represents a person identity + """ + object :person do + interfaces([:actor]) + field(:user, :user, description: "The user this actor is associated to") + + field(:member_of, list_of(:member), description: "The list of groups this person is member of") + field(:url, :string, description: "The ActivityPub actor's URL") - # We probably don't need all of that - # field(:outbox_url, :string, description: "The ActivityPub actor outbox_url") - # field(:inbox_url, :string) - # field(:following_url, :string) - # field(:followers_url, :string) - # field(:shared_inbox_url, :string) field(:type, :actor_type, description: "The type of Actor (Person, Group,…)") field(:name, :string, description: "The actor's displayed name") field(:domain, :string, description: "The actor's domain if (null if it's this instance)") + field(:local, :boolean, description: "If the actor is from this instance") field(:summary, :string, description: "The actor's summary") field(:preferred_username, :string, description: "The actor's preferred username") field(:keys, :string, description: "The actors RSA Keys") @@ -35,14 +37,126 @@ defmodule MobilizonWeb.Schema do field(:suspended, :boolean, description: "If the actor is suspended") field(:avatar_url, :string, description: "The actor's avatar url") field(:banner_url, :string, description: "The actor's banner url") - # field(:followers, list_of(:follower)) + + # These one should have a privacy setting + field(:following, list_of(:follower), description: "List of followings") + field(:followers, list_of(:follower), description: "List of followers") + field(:followersCount, :integer, description: "Number of followers for this actor") + field(:followingCount, :integer, description: "Number of actors following this actor") + + # This one should have a privacy setting + field(:organized_events, list_of(:event), + resolve: dataloader(Events), + description: "A list of the events this actor has organized" + ) + end + + @desc """ + Represents a group of actors + """ + object :group do + interfaces([:actor]) + + field(:url, :string, description: "The ActivityPub actor's URL") + field(:type, :actor_type, description: "The type of Actor (Person, Group,…)") + field(:name, :string, description: "The actor's displayed name") + field(:domain, :string, description: "The actor's domain if (null if it's this instance)") + field(:local, :boolean, description: "If the actor is from this instance") + field(:summary, :string, description: "The actor's summary") + field(:preferred_username, :string, description: "The actor's preferred username") + field(:keys, :string, description: "The actors RSA Keys") + + field(:manually_approves_followers, :boolean, + description: "Whether the actors manually approves followers" + ) + + field(:suspended, :boolean, description: "If the actor is suspended") + field(:avatar_url, :string, description: "The actor's avatar url") + field(:banner_url, :string, description: "The actor's banner url") + + # These one should have a privacy setting + field(:following, list_of(:follower), description: "List of followings") + field(:followers, list_of(:follower), description: "List of followers") + field(:followersCount, :integer, description: "Number of followers for this actor") + field(:followingCount, :integer, description: "Number of actors following this actor") + + # This one should have a privacy setting field(:organized_events, list_of(:event), resolve: dataloader(Events), description: "A list of the events this actor has organized" ) + field(:types, :group_type, description: "The type of group : Group, Community,…") + + field(:openness, :openness, + description: "Whether the group is opened to all or has restricted access" + ) + + field(:members, non_null(list_of(:member)), description: "List of group members") + end + + @desc """ + Describes how an actor is opened to follows + """ + enum :openness do + value(:invite_only, description: "The actor can only be followed by invitation") + + value(:moderated, description: "The actor needs to accept the following before it's effective") + + value(:open, description: "The actor is open to followings") + end + + @desc """ + The types of Group that exist + """ + enum :group_type do + value(:group, description: "A private group of persons") + value(:community, description: "A public group of many actors") + end + + @desc "An ActivityPub actor" + interface :actor do + field(:url, :string, description: "The ActivityPub actor's URL") + field(:type, :actor_type, description: "The type of Actor (Person, Group,…)") + field(:name, :string, description: "The actor's displayed name") + field(:domain, :string, description: "The actor's domain if (null if it's this instance)") + field(:local, :boolean, description: "If the actor is from this instance") + field(:summary, :string, description: "The actor's summary") + field(:preferred_username, :string, description: "The actor's preferred username") + field(:keys, :string, description: "The actors RSA Keys") + + field(:manually_approves_followers, :boolean, + description: "Whether the actors manually approves followers" + ) + + field(:suspended, :boolean, description: "If the actor is suspended") + field(:avatar_url, :string, description: "The actor's avatar url") + field(:banner_url, :string, description: "The actor's banner url") + + # These one should have a privacy setting + field(:following, list_of(:follower), description: "List of followings") + field(:followers, list_of(:follower), description: "List of followers") + field(:followersCount, :integer, description: "Number of followers for this actor") + field(:followingCount, :integer, description: "Number of actors following this actor") + + # This one should have a privacy setting + field(:organized_events, list_of(:event), + resolve: dataloader(Events), + description: "A list of the events this actor has organized" + ) + + # This one is for the person itself **only** + # field(:feed, list_of(:event), description: "List of events the actor sees in his or her feed") + # field(:memberships, list_of(:member)) - field(:user, :user, description: "The user this actor is associated to") + + resolve_type(fn + %Actor{type: :Person}, _ -> + :person + + %Actor{type: :Group}, _ -> + :group + end) end @desc "The list of types an actor can be" @@ -58,11 +172,12 @@ defmodule MobilizonWeb.Schema do object :user do field(:id, non_null(:id), description: "The user's ID") field(:email, non_null(:string), description: "The user's email") - # , resolve: dataloader(:actors)) - field(:actors, non_null(list_of(:actor)), - description: "The user's list of actors (identities)" + + field(:profiles, non_null(list_of(:person)), + description: "The user's list of profiles (identities)" ) + # TODO: This shouldn't be an ID, but the actor itself field(:default_actor_id, non_null(:integer), description: "The user's default actor") field(:confirmed_at, :datetime, @@ -86,89 +201,177 @@ defmodule MobilizonWeb.Schema do @desc "A JWT and the associated user ID" object :login do - field(:token, non_null(:string)) - field(:user, non_null(:user)) - field(:actor, non_null(:actor)) + field(:token, non_null(:string), description: "A JWT Token for this session") + field(:user, non_null(:user), description: "The user associated to this session") + field(:person, non_null(:person), description: "The person associated to this session") end @desc "An event" object :event do - field(:uuid, :uuid) - field(:url, :string) - field(:local, :boolean) - field(:title, :string) - field(:description, :string) - field(:begins_on, :datetime) - field(:ends_on, :datetime) - field(:state, :integer) - field(:status, :integer) - field(:public, :boolean) - field(:thumbnail, :string) - field(:large_image, :string) - field(:publish_at, :datetime) - field(:address_type, :address_type) - field(:online_address, :string) - field(:phone, :string) + field(:uuid, :uuid, description: "The Event UUID") + field(:url, :string, description: "The ActivityPub Event URL") + field(:local, :boolean, description: "Whether the event is local or not") + field(:title, :string, description: "The event's title") + field(:description, :string, description: "The event's description") + field(:begins_on, :datetime, description: "Datetime for when the event begins") + field(:ends_on, :datetime, description: "Datetime for when the event ends") + field(:state, :integer, description: "State of the event") + field(:status, :integer, description: "Status of the event") + field(:public, :boolean, description: "Whether the event is public or not") + # TODO replace me with picture object + field(:thumbnail, :string, description: "A thumbnail picture for the event") + # TODO replace me with banner + field(:large_image, :string, description: "A large picture for the event") + field(:publish_at, :datetime, description: "When the event was published") + field(:address_type, :address_type, description: "The type of the event's address") + # TODO implement these properly with an interface + # field(:online_address, :string, description: "???") + # field(:phone, :string, description: "") - field :organizer_actor, :actor do - resolve(dataloader(Actors)) - end + field(:organizer_actor, :person, + resolve: dataloader(Actors), + description: "The event's organizer (as a person)" + ) - field(:attributed_to, :actor) + field(:attributed_to, :actor, description: "Who the event is attributed to (often a group)") # field(:tags, list_of(:tag)) - field(:category, :category) + field(:category, :category, description: "The event's category") field(:participants, list_of(:participant), - resolve: &Resolvers.Event.list_participants_for_event/3 + resolve: &Resolvers.Event.list_participants_for_event/3, + description: "The event's participants" ) # field(:tracks, list_of(:track)) # field(:sessions, list_of(:session)) # field(:physical_address, :address) - field(:updated_at, :datetime) - field(:created_at, :datetime) + field(:updated_at, :datetime, description: "When the event was last updated") + field(:created_at, :datetime, description: "When the event was created") + end + + @desc "A comment" + object :comment do + field(:uuid, :uuid) + field(:url, :string) + field(:local, :boolean) + field(:content, :string) + field(:primaryLanguage, :string) + field(:replies, list_of(:comment)) + field(:threadLanguages, non_null(list_of(:string))) end @desc "Represents a participant to an event" object :participant do - # field(:event, :event, resolve: dataloader(Events)) - # , resolve: dataloader(Actors) - field(:actor, :actor) - field(:role, :integer) + field(:event, :event, + resolve: dataloader(Events), + description: "The event which the actor participates in" + ) + + field(:actor, :actor, description: "The actor that participates to the event") + field(:role, :integer, description: "The role of this actor at this event") end @desc "The list of types an address can be" enum :address_type do - value(:physical) - value(:url) - value(:phone) - value(:other) + value(:physical, description: "The address is physical, like a postal address") + value(:url, description: "The address is on the Web, like an URL") + value(:phone, description: "The address is a phone number for a conference") + value(:other, description: "The address is something else") end @desc "A category" object :category do - field(:id, :id) - field(:description, :string) - field(:picture, :picture) - field(:title, :string) - field(:updated_at, :datetime) - field(:created_at, :datetime) + field(:id, :id, description: "The category's ID") + field(:description, :string, description: "The category's description") + field(:picture, :picture, description: "The category's picture") + field(:title, :string, description: "The category's title") end @desc "A picture" object :picture do - field(:url, :string) - field(:url_thumbnail, :string) + field(:url, :string, description: "The URL for this picture") + field(:url_thumbnail, :string, description: "The URL for this picture's thumbnail") + end + + @desc """ + Represents a notification for an user + """ + object :notification do + field(:id, :integer, description: "The notification ID") + field(:user, :user, description: "The user to transmit the notification to") + field(:actor, :actor, description: "The notification target profile") + + field(:activity_type, :integer, + description: + "Whether the notification is about a follow, group join, event change or comment" + ) + + field(:target_object, :object, description: "The object responsible for the notification") + field(:summary, :string, description: "Text inside the notification") + field(:seen, :boolean, description: "Whether or not the notification was seen by the user") + field(:published, :datetime, description: "Datetime when the notification was published") + end + + @desc """ + Represents a member of a group + """ + object :member do + field(:parent, :group, description: "Of which the profile is member") + field(:person, :person, description: "Which profile is member of") + field(:role, :integer, description: "The role of this membership") + field(:approved, :boolean, description: "Whether this membership has been approved") + end + + @desc """ + Represents an actor's follower + """ + object :follower do + field(:target_actor, :actor, description: "What or who the profile follows") + field(:actor, :actor, description: "Which profile follows") + + field(:approved, :boolean, + description: "Whether the follow has been approved by the target actor" + ) + end + + union :object do + types([:event, :person, :group, :comment, :follower, :member, :participant]) + + resolve_type(fn + %Actor{type: :Person}, _ -> + :person + + %Actor{type: :Group}, _ -> + :group + + %Event{}, _ -> + :event + + %Comment{}, _ -> + :comment + + %Follower{}, _ -> + :follower + + %Member{}, _ -> + :member + + %Participant{}, _ -> + :participant + end) end @desc "A search result" union :search_result do - types([:event, :actor]) + types([:event, :person, :group]) resolve_type(fn - %Actor{}, _ -> - :actor + %Actor{type: :Person}, _ -> + :person + + %Actor{type: :Group}, _ -> + :group %Event{}, _ -> :event @@ -188,13 +391,16 @@ defmodule MobilizonWeb.Schema do [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()] end + @desc """ + Root Query + """ query do @desc "Get all events" field :events, list_of(:event) do resolve(&Resolvers.Event.list_events/3) end - @desc "Search through events and actors" + @desc "Search through events, persons and groups" field :search, list_of(:search_result) do arg(:search, non_null(:string)) arg(:page, :integer, default_value: 1) @@ -226,22 +432,25 @@ defmodule MobilizonWeb.Schema do end @desc "Get the current actor for the logged-in user" - field :logged_actor, :actor do - resolve(&Resolvers.Actor.get_current_actor/3) + field :logged_person, :person do + resolve(&Resolvers.Actor.get_current_person/3) end - @desc "Get an actor" - field :actor, :actor do + @desc "Get a person" + field :person, :person do arg(:preferred_username, non_null(:string)) - resolve(&Resolvers.Actor.find_actor/3) + resolve(&Resolvers.Actor.find_person/3) end @desc "Get the list of categories" - field :categories, list_of(:category) do + field :categories, non_null(list_of(:category)) do resolve(&Resolvers.Category.list_categories/3) end end + @desc """ + Root Mutation + """ mutation do @desc "Create an event" field :create_event, type: :event do @@ -273,7 +482,7 @@ defmodule MobilizonWeb.Schema do end @desc "Create an user (returns an actor)" - field :create_user, type: :actor do + field :create_user, type: :person do arg(:email, non_null(:string)) arg(:password, non_null(:string)) arg(:username, non_null(:string)) diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex index 812af5934..4556e00e2 100644 --- a/lib/service/activity_pub/activity_pub.ex +++ b/lib/service/activity_pub/activity_pub.ex @@ -219,15 +219,21 @@ defmodule Mobilizon.Service.ActivityPub do @doc """ Find an actor in our local database or call Webfinger to find what's its AP ID is and then fetch it """ - @spec find_or_make_actor_from_nickname(String.t()) :: tuple() - def find_or_make_actor_from_nickname(nickname) do - with %Actor{} = actor <- Actors.get_actor_by_name(nickname) do + @spec find_or_make_actor_from_nickname(String.t(), atom() | nil) :: tuple() + def find_or_make_actor_from_nickname(nickname, type \\ nil) do + with %Actor{} = actor <- Actors.get_actor_by_name(nickname, type) do {:ok, actor} else nil -> make_actor_from_nickname(nickname) end end + @spec find_or_make_person_from_nickname(String.t()) :: tuple() + def find_or_make_person_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Person) + + @spec find_or_make_group_from_nickname(String.t()) :: tuple() + def find_or_make_group_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Group) + @doc """ Create an actor inside our database from username, using Webfinger to find out it's AP ID and then fetch it """ diff --git a/test/mobilizon/actors/actors_test.exs b/test/mobilizon/actors/actors_test.exs index 8ff37ab6e..b35a10fbc 100644 --- a/test/mobilizon/actors/actors_test.exs +++ b/test/mobilizon/actors/actors_test.exs @@ -306,7 +306,7 @@ defmodule Mobilizon.ActorsTest do test "list_users/0 returns all users" do user = insert(:user) users = Actors.list_users() - assert users == [user] + assert [user.id] == users |> Enum.map(& &1.id) end test "get_user!/1 returns the user with given id" do diff --git a/test/mobilizon/addresses/addresses_test.exs b/test/mobilizon/addresses/addresses_test.exs index fcb20420f..1da92902f 100644 --- a/test/mobilizon/addresses/addresses_test.exs +++ b/test/mobilizon/addresses/addresses_test.exs @@ -48,12 +48,12 @@ defmodule Mobilizon.AddressesTest do test "list_addresses/0 returns all addresses" do address = address_fixture() - assert Addresses.list_addresses() == [address] + assert [address.id] == Addresses.list_addresses() |> Enum.map(& &1.id) end test "get_address!/1 returns the address with given id" do address = address_fixture() - assert Addresses.get_address!(address.id) == address + assert Addresses.get_address!(address.id).id == address.id end test "create_address/1 with valid data creates a address" do @@ -69,8 +69,7 @@ defmodule Mobilizon.AddressesTest do test "update_address/2 with valid data updates the address" do address = address_fixture() - assert {:ok, address} = Addresses.update_address(address, @update_attrs) - assert %Address{} = address + assert {:ok, %Address{} = address} = Addresses.update_address(address, @update_attrs) assert address.addressCountry == "some updated addressCountry" assert address.addressLocality == "some updated addressLocality" assert address.addressRegion == "some updated addressRegion" diff --git a/test/mobilizon/events/events_test.exs b/test/mobilizon/events/events_test.exs index d0a3ea078..c6fd79e19 100644 --- a/test/mobilizon/events/events_test.exs +++ b/test/mobilizon/events/events_test.exs @@ -189,15 +189,15 @@ defmodule Mobilizon.EventsTest do @invalid_attrs %{description: nil, picture: nil, title: nil} test "list_categories/0 returns all categories", %{category: category} do - assert Events.list_categories() == [category] + assert [category.id] == Events.list_categories() |> Enum.map(& &1.id) end test "get_category!/1 returns the category with given id", %{category: category} do - assert Events.get_category!(category.id) == category + assert Events.get_category!(category.id).id == category.id end test "get_category_by_title/1 return the category with given title", %{category: category} do - assert Events.get_category_by_title(category.title) == category + assert Events.get_category_by_title(category.title).id == category.id end test "create_category/1 with valid data creates a category" do @@ -212,8 +212,7 @@ defmodule Mobilizon.EventsTest do end test "update_category/2 with valid data updates the category", %{category: category} do - assert {:ok, category} = Events.update_category(category, @update_attrs) - assert %Category{} = category + assert {:ok, %Category{} = category} = Events.update_category(category, @update_attrs) assert category.description == "some updated description" assert category.picture.file_name == @update_attrs.picture.filename assert category.title == "some updated title" @@ -221,7 +220,7 @@ defmodule Mobilizon.EventsTest do test "update_category/2 with invalid data returns error changeset", %{category: category} do assert {:error, %Ecto.Changeset{}} = Events.update_category(category, @invalid_attrs) - assert category == Events.get_category!(category.id) + assert category.description == Events.get_category!(category.id).description end test "delete_category/1 deletes the category", %{category: category} do @@ -241,28 +240,24 @@ defmodule Mobilizon.EventsTest do @update_attrs %{title: "some updated title"} @invalid_attrs %{title: nil} - def tag_fixture(attrs \\ %{}) do - {:ok, tag} = - attrs - |> Enum.into(@valid_attrs) - |> Events.create_tag() - - tag - end - test "list_tags/0 returns all tags" do - tag = tag_fixture() - assert Events.list_tags() == [tag] + tag = insert(:tag) + assert [tag.id] == Events.list_tags() |> Enum.map(& &1.id) end test "get_tag!/1 returns the tag with given id" do - tag = tag_fixture() - assert Events.get_tag!(tag.id) == tag + tag = insert(:tag) + assert Events.get_tag!(tag.id).id == tag.id end test "create_tag/1 with valid data creates a tag" do assert {:ok, %Tag{} = tag} = Events.create_tag(@valid_attrs) assert tag.title == "some title" + assert tag.slug == "some-title" + + assert {:ok, %Tag{} = tag2} = Events.create_tag(@valid_attrs) + assert tag2.title == "some title" + assert tag2.slug == "some-title-1" end test "create_tag/1 with invalid data returns error changeset" do @@ -270,26 +265,26 @@ defmodule Mobilizon.EventsTest do end test "update_tag/2 with valid data updates the tag" do - tag = tag_fixture() + tag = insert(:tag) assert {:ok, tag} = Events.update_tag(tag, @update_attrs) assert %Tag{} = tag assert tag.title == "some updated title" end test "update_tag/2 with invalid data returns error changeset" do - tag = tag_fixture() + tag = insert(:tag) assert {:error, %Ecto.Changeset{}} = Events.update_tag(tag, @invalid_attrs) - assert tag == Events.get_tag!(tag.id) + assert tag.id == Events.get_tag!(tag.id).id end test "delete_tag/1 deletes the tag" do - tag = tag_fixture() + tag = insert(:tag) assert {:ok, %Tag{}} = Events.delete_tag(tag) assert_raise Ecto.NoResultsError, fn -> Events.get_tag!(tag.id) end end test "change_tag/1 returns a tag changeset" do - tag = tag_fixture() + tag = insert(:tag) assert %Ecto.Changeset{} = Events.change_tag(tag) end end @@ -405,26 +400,14 @@ defmodule Mobilizon.EventsTest do videos_urls: nil } - def session_fixture(attrs \\ %{}) do - event = insert(:event) - valid_attrs = Map.put(@valid_attrs, :event_id, event.id) - - {:ok, session} = - attrs - |> Enum.into(valid_attrs) - |> Events.create_session() - - session - end - test "list_sessions/0 returns all sessions" do - session = session_fixture() - assert Events.list_sessions() == [session] + session = insert(:session) + assert [session.id] == Events.list_sessions() |> Enum.map(& &1.id) end test "get_session!/1 returns the session with given id" do - session = session_fixture() - assert Events.get_session!(session.id) == session + session = insert(:session) + assert Events.get_session!(session.id).id == session.id end test "create_session/1 with valid data creates a session" do @@ -446,9 +429,8 @@ defmodule Mobilizon.EventsTest do end test "update_session/2 with valid data updates the session" do - session = session_fixture() - assert {:ok, session} = Events.update_session(session, @update_attrs) - assert %Session{} = session + session = insert(:session) + assert {:ok, %Session{} = session} = Events.update_session(session, @update_attrs) assert session.audios_urls == "some updated audios_urls" assert session.language == "some updated language" assert session.long_abstract == "some updated long_abstract" @@ -460,19 +442,19 @@ defmodule Mobilizon.EventsTest do end test "update_session/2 with invalid data returns error changeset" do - session = session_fixture() + session = insert(:session) assert {:error, %Ecto.Changeset{}} = Events.update_session(session, @invalid_attrs) - assert session == Events.get_session!(session.id) + assert session.title == Events.get_session!(session.id).title end test "delete_session/1 deletes the session" do - session = session_fixture() + session = insert(:session) assert {:ok, %Session{}} = Events.delete_session(session) assert_raise Ecto.NoResultsError, fn -> Events.get_session!(session.id) end end test "change_session/1 returns a session changeset" do - session = session_fixture() + session = insert(:session) assert %Ecto.Changeset{} = Events.change_session(session) end end @@ -488,26 +470,14 @@ defmodule Mobilizon.EventsTest do } @invalid_attrs %{color: nil, description: nil, name: nil} - def track_fixture(attrs \\ %{}) do - event = insert(:event) - valid_attrs = Map.put(@valid_attrs, :event_id, event.id) - - {:ok, track} = - attrs - |> Enum.into(valid_attrs) - |> Events.create_track() - - track - end - test "list_tracks/0 returns all tracks" do - track = track_fixture() - assert Events.list_tracks() == [track] + track = insert(:track) + assert [track.id] == Events.list_tracks() |> Enum.map(& &1.id) end test "get_track!/1 returns the track with given id" do - track = track_fixture() - assert Events.get_track!(track.id) == track + track = insert(:track) + assert Events.get_track!(track.id).id == track.id end test "create_track/1 with valid data creates a track" do @@ -524,28 +494,27 @@ defmodule Mobilizon.EventsTest do end test "update_track/2 with valid data updates the track" do - track = track_fixture() - assert {:ok, track} = Events.update_track(track, @update_attrs) - assert %Track{} = track + track = insert(:track) + {:ok, %Track{} = track} = Events.update_track(track, @update_attrs) assert track.color == "some updated color" assert track.description == "some updated description" assert track.name == "some updated name" end test "update_track/2 with invalid data returns error changeset" do - track = track_fixture() + track = insert(:track) assert {:error, %Ecto.Changeset{}} = Events.update_track(track, @invalid_attrs) - assert track == Events.get_track!(track.id) + assert track.color == Events.get_track!(track.id).color end test "delete_track/1 deletes the track" do - track = track_fixture() + track = insert(:track) assert {:ok, %Track{}} = Events.delete_track(track) assert_raise Ecto.NoResultsError, fn -> Events.get_track!(track.id) end end test "change_track/1 returns a track changeset" do - track = track_fixture() + track = insert(:track) assert %Ecto.Changeset{} = Events.change_track(track) end end diff --git a/test/mobilizon_web/resolvers/actor_resolver_test.exs b/test/mobilizon_web/resolvers/person_resolver_test.exs similarity index 54% rename from test/mobilizon_web/resolvers/actor_resolver_test.exs rename to test/mobilizon_web/resolvers/person_resolver_test.exs index f2a798ab8..0b5ca25a8 100644 --- a/test/mobilizon_web/resolvers/actor_resolver_test.exs +++ b/test/mobilizon_web/resolvers/person_resolver_test.exs @@ -1,4 +1,4 @@ -defmodule MobilizonWeb.Resolvers.ActorResolverTest do +defmodule MobilizonWeb.Resolvers.PersonResolverTest do use MobilizonWeb.ConnCase alias Mobilizon.Actors alias MobilizonWeb.AbsintheHelpers @@ -6,13 +6,13 @@ defmodule MobilizonWeb.Resolvers.ActorResolverTest do @valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"} @non_existent_username "nonexistent" - describe "Actor Resolver" do - test "find_actor/3 returns an actor by it's username", context do + describe "Person Resolver" do + test "find_actor/3 returns a person by it's username", context do {:ok, actor} = Actors.register(@valid_actor_params) query = """ { - actor(preferredUsername: "#{actor.preferred_username}") { + person(preferredUsername: "#{actor.preferred_username}") { preferredUsername, } } @@ -20,14 +20,14 @@ defmodule MobilizonWeb.Resolvers.ActorResolverTest do res = context.conn - |> get("/api", AbsintheHelpers.query_skeleton(query, "actor")) + |> get("/api", AbsintheHelpers.query_skeleton(query, "person")) - assert json_response(res, 200)["data"]["actor"]["preferredUsername"] == + assert json_response(res, 200)["data"]["person"]["preferredUsername"] == actor.preferred_username query = """ { - actor(preferredUsername: "#{@non_existent_username}") { + person(preferredUsername: "#{@non_existent_username}") { preferredUsername, } } @@ -35,20 +35,20 @@ defmodule MobilizonWeb.Resolvers.ActorResolverTest do res = context.conn - |> get("/api", AbsintheHelpers.query_skeleton(query, "actor")) + |> get("/api", AbsintheHelpers.query_skeleton(query, "person")) - assert json_response(res, 200)["data"]["actor"] == nil + assert json_response(res, 200)["data"]["person"] == nil assert hd(json_response(res, 200)["errors"])["message"] == - "Actor with name #{@non_existent_username} not found" + "Person with name #{@non_existent_username} not found" end - test "get_current_actor/3 returns the current logged-in actor", context do + test "get_current_person/3 returns the current logged-in actor", context do {:ok, actor} = Actors.register(@valid_actor_params) query = """ { - loggedActor { + loggedPerson { avatarUrl, preferredUsername, } @@ -57,19 +57,19 @@ defmodule MobilizonWeb.Resolvers.ActorResolverTest do res = context.conn - |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_actor")) + |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_person")) - assert json_response(res, 200)["data"]["loggedActor"] == nil + assert json_response(res, 200)["data"]["loggedPerson"] == nil assert hd(json_response(res, 200)["errors"])["message"] == - "You need to be logged-in to view current actor" + "You need to be logged-in to view current person" res = context.conn |> auth_conn(actor.user) - |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_actor")) + |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_person")) - assert json_response(res, 200)["data"]["loggedActor"]["preferredUsername"] == + assert json_response(res, 200)["data"]["loggedPerson"]["preferredUsername"] == actor.preferred_username end end diff --git a/test/mobilizon_web/resolvers/user_resolver_test.exs b/test/mobilizon_web/resolvers/user_resolver_test.exs index 387284773..a9adfef3a 100644 --- a/test/mobilizon_web/resolvers/user_resolver_test.exs +++ b/test/mobilizon_web/resolvers/user_resolver_test.exs @@ -140,7 +140,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do user { id }, - actor { + person { preferredUsername } } @@ -151,7 +151,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do context.conn |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) - assert json_response(res, 200)["data"]["validateUser"]["actor"]["preferredUsername"] == + assert json_response(res, 200)["data"]["validateUser"]["person"]["preferredUsername"] == @valid_actor_params.username assert json_response(res, 200)["data"]["validateUser"]["user"]["id"] == @@ -170,7 +170,7 @@ defmodule MobilizonWeb.Resolvers.UserResolverTest do user { id }, - actor { + person { preferredUsername } } diff --git a/test/support/factory.ex b/test/support/factory.ex index ac81c0cd4..54b072e88 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -53,6 +53,13 @@ defmodule Mobilizon.Factory do } end + def tag_factory do + %Mobilizon.Events.Tag{ + title: "MyTag", + slug: sequence("MyTag") + } + end + def address_factory do %Mobilizon.Addresses.Address{ description: sequence("MyAddress"),