diff --git a/js/src/graphql/admin.ts b/js/src/graphql/admin.ts index f970d7879..0d47b0fa7 100644 --- a/js/src/graphql/admin.ts +++ b/js/src/graphql/admin.ts @@ -74,6 +74,7 @@ export const INSTANCE_FRAGMENT = gql` fragment InstanceFragment on Instance { domain hasRelay + relayAddress followerStatus followedStatus eventCount diff --git a/js/src/types/instance.model.ts b/js/src/types/instance.model.ts index 5936f6ba6..165c4bb56 100644 --- a/js/src/types/instance.model.ts +++ b/js/src/types/instance.model.ts @@ -3,6 +3,7 @@ import { InstanceFollowStatus } from "./enums"; export interface IInstance { domain: string; hasRelay: boolean; + relayAddress: string | null; followerStatus: InstanceFollowStatus; followedStatus: InstanceFollowStatus; personCount: number; diff --git a/js/src/views/Admin/Instance.vue b/js/src/views/Admin/Instance.vue index fbdc68458..f20ef224b 100644 --- a/js/src/views/Admin/Instance.vue +++ b/js/src/views/Admin/Instance.vue @@ -66,8 +66,11 @@ <span class="text-sm block">{{ $t("Uploaded media size") }}</span> </div> </div> - <div class="mt-3 grid xl:grid-cols-2 gap-4" v-if="instance.hasRelay"> - <div class="border bg-white p-6 shadow-md rounded-md"> + <div class="mt-3 grid xl:grid-cols-2 gap-4"> + <div + class="border bg-white p-6 shadow-md rounded-md" + v-if="instance.hasRelay" + > <button @click="removeInstanceFollow" v-if="instance.followedStatus == InstanceFollowStatus.APPROVED" @@ -90,6 +93,9 @@ {{ $t("Follow instance") }} </button> </div> + <div v-else class="md:h-48 py-16 text-center opacity-50"> + {{ $t("Only Mobilizon instances can be followed") }} + </div> <div class="border bg-white p-6 shadow-md rounded-md flex flex-col gap-2"> <button @click="acceptInstance" @@ -110,9 +116,6 @@ </p> </div> </div> - <div v-else class="md:h-48 py-16 text-center opacity-50"> - {{ $t("Only Mobilizon instances can be followed") }} - </div> </div> </template> <script lang="ts"> @@ -159,7 +162,7 @@ export default class Instance extends Vue { await this.$apollo.mutate({ mutation: ACCEPT_RELAY, variables: { - address: `relay@${this.domain}`, + address: this.instance.relayAddress, }, update(cache: ApolloCache<any>) { cache.writeFragment({ @@ -191,7 +194,7 @@ export default class Instance extends Vue { await this.$apollo.mutate({ mutation: REJECT_RELAY, variables: { - address: `relay@${this.domain}`, + address: this.instance.relayAddress, }, update(cache: ApolloCache<any>) { cache.writeFragment({ @@ -239,7 +242,7 @@ export default class Instance extends Vue { await this.$apollo.mutate({ mutation: REMOVE_RELAY, variables: { - address: `relay@${this.domain}`, + address: this.instance.relayAddress, }, update(cache: ApolloCache<any>) { cache.writeFragment({ diff --git a/lib/federation/activity_pub/publisher.ex b/lib/federation/activity_pub/publisher.ex index f276cb741..19d645d43 100644 --- a/lib/federation/activity_pub/publisher.ex +++ b/lib/federation/activity_pub/publisher.ex @@ -110,13 +110,17 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do @spec convert_followers_in_recipients(list(String.t())) :: {list(String.t()), list(String.t())} defp convert_followers_in_recipients(recipients) do Enum.reduce(recipients, {recipients, []}, fn recipient, {recipients, follower_actors} = acc -> - case Actors.get_actor_by_followers_url(recipient) do - %Actor{} = group -> - {Enum.filter(recipients, fn recipient -> recipient != group.followers_url end), - follower_actors ++ Actors.list_external_followers_for_actor(group)} + if is_nil(recipient) do + acc + else + case Actors.get_actor_by_followers_url(recipient) do + %Actor{} = group -> + {Enum.filter(recipients, fn recipient -> recipient != group.followers_url end), + follower_actors ++ Actors.list_external_followers_for_actor(group)} - nil -> - acc + nil -> + acc + end end end) end @@ -128,19 +132,23 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do @spec convert_members_in_recipients(list(String.t())) :: {list(String.t()), list(Actor.t())} defp convert_members_in_recipients(recipients) do Enum.reduce(recipients, {recipients, []}, fn recipient, {recipients, member_actors} = acc -> - case Actors.get_group_by_members_url(recipient) do - # If the group is local just add external members - %Actor{domain: domain} = group when is_nil(domain) -> - {Enum.filter(recipients, fn recipient -> recipient != group.members_url end), - member_actors ++ Actors.list_external_actors_members_for_group(group)} + if is_nil(recipient) do + acc + else + case Actors.get_group_by_members_url(recipient) do + # If the group is local just add external members + %Actor{domain: domain} = group when is_nil(domain) -> + {Enum.filter(recipients, fn recipient -> recipient != group.members_url end), + member_actors ++ Actors.list_external_actors_members_for_group(group)} - # If it's remote add the remote group actor as well - %Actor{} = group -> - {Enum.filter(recipients, fn recipient -> recipient != group.members_url end), - member_actors ++ Actors.list_external_actors_members_for_group(group) ++ [group]} + # If it's remote add the remote group actor as well + %Actor{} = group -> + {Enum.filter(recipients, fn recipient -> recipient != group.members_url end), + member_actors ++ Actors.list_external_actors_members_for_group(group) ++ [group]} - _ -> - acc + _ -> + acc + end end end) end diff --git a/lib/graphql/resolvers/admin.ex b/lib/graphql/resolvers/admin.ex index 43b6a1540..0bbe46c82 100644 --- a/lib/graphql/resolvers/admin.ex +++ b/lib/graphql/resolvers/admin.ex @@ -468,12 +468,16 @@ defmodule Mobilizon.GraphQL.Resolvers.Admin do context: %{current_user: %User{role: role}} }) when is_admin(role) do - has_relay = Actors.has_relay?(domain) - remote_relay = Actors.get_actor_by_name("relay@#{domain}") + remote_relay = Actors.get_relay(domain) local_relay = Relay.get_actor() result = %{ - has_relay: has_relay, + has_relay: !is_nil(remote_relay), + relay_address: + if(is_nil(remote_relay), + do: nil, + else: "#{remote_relay.preferred_username}@#{remote_relay.domain}" + ), follower_status: follow_status(remote_relay, local_relay), followed_status: follow_status(local_relay, remote_relay) } diff --git a/lib/graphql/schema/admin.ex b/lib/graphql/schema/admin.ex index 5bb65d032..ddd287e97 100644 --- a/lib/graphql/schema/admin.ex +++ b/lib/graphql/schema/admin.ex @@ -216,6 +216,10 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do description: "Whether this instance has a relay, meaning that it's a Mobilizon instance that we can follow" ) + + field(:relay_address, :string, + description: "If this instance has a relay, it's federated username" + ) end @desc """ diff --git a/lib/mobilizon/actors/actor.ex b/lib/mobilizon/actors/actor.ex index 1efa5aacc..21ee6eaec 100644 --- a/lib/mobilizon/actors/actor.ex +++ b/lib/mobilizon/actors/actor.ex @@ -19,6 +19,7 @@ defmodule Mobilizon.Actors.Actor do alias Mobilizon.Web.Endpoint alias Mobilizon.Web.Router.Helpers, as: Routes import Mobilizon.Web.Gettext, only: [dgettext: 2] + import Mobilizon.Service.Guards, only: [is_valid_string: 1] require Logger @@ -224,10 +225,23 @@ defmodule Mobilizon.Actors.Actor do preferred_username_and_domain(actor) end - def display_name_and_username(%__MODULE__{name: name} = actor) do + def display_name_and_username(%__MODULE__{ + type: :Application, + name: name, + preferred_username: "relay", + domain: domain + }) + when domain not in [nil, ""] and name not in [nil, ""] do + "#{name} (#{domain})" + end + + def display_name_and_username(%__MODULE__{name: name, preferred_username: username} = actor) + when username not in [nil, ""] do "#{name} (@#{preferred_username_and_domain(actor)})" end + def display_name_and_username(_), do: nil + @doc """ Returns the preferred username with the eventual @domain suffix if it's a distant actor. @@ -235,8 +249,18 @@ defmodule Mobilizon.Actors.Actor do @spec preferred_username_and_domain(t) :: String.t() def preferred_username_and_domain(%__MODULE__{ preferred_username: preferred_username, - domain: nil - }) do + domain: domain + }) + when domain in [nil, ""] do + preferred_username + end + + def preferred_username_and_domain(%__MODULE__{ + type: :Application, + preferred_username: preferred_username, + domain: domain + }) + when not is_nil(domain) and preferred_username == domain do preferred_username end @@ -290,6 +314,7 @@ defmodule Mobilizon.Actors.Actor do |> build_urls() |> common_changeset(attrs) |> unique_username_validator() + |> username_validator() |> validate_required(@registration_required_attrs) end @@ -333,6 +358,7 @@ defmodule Mobilizon.Actors.Actor do |> put_change(:keys, Crypto.generate_rsa_2048_private_key()) |> put_change(:type, :Group) |> unique_username_validator() + |> username_validator() |> validate_required(@group_creation_required_attrs) |> validate_length(:summary, max: 5000) |> validate_length(:preferred_username, max: 100) @@ -358,6 +384,23 @@ defmodule Mobilizon.Actors.Actor do # When we don't even have any preferred_username, don't even try validating preferred_username defp unique_username_validator(changeset), do: changeset + defp username_validator(%Ecto.Changeset{} = changeset) do + username = Ecto.Changeset.fetch_field!(changeset, :preferred_username) + + if is_valid_string(username) and Regex.match?(~r/^[a-z0-9_]+$/, username) do + changeset + else + add_error( + changeset, + :preferred_username, + dgettext( + "errors", + "Username must only contain alphanumeric lowercased characters and underscores." + ) + ) + end + end + @spec build_urls(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t() defp build_urls(changeset, type \\ :Person) diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index 14d6cb372..2a6c84047 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -1297,14 +1297,12 @@ defmodule Mobilizon.Actors do :ok end - @spec has_relay?(String.t()) :: boolean() - def has_relay?(domain) do - Actor - |> where( - [a], - a.preferred_username == "relay" and a.domain == ^domain and a.type == :Application - ) - |> Repo.exists?() + @doc """ + Returns a relay actor, either `relay@domain` (Mobilizon) or `domain@domain` (Mastodon) + """ + @spec get_relay(String.t()) :: Actor.t() | nil + def get_relay(domain) do + get_actor_by_name("relay@#{domain}") || get_actor_by_name("#{domain}@#{domain}") end @spec delete_files_if_media_changed(Ecto.Changeset.t()) :: Ecto.Changeset.t() diff --git a/lib/web/email/follow.ex b/lib/web/email/follow.ex index 5da1c80f7..fab391b9f 100644 --- a/lib/web/email/follow.ex +++ b/lib/web/email/follow.ex @@ -44,11 +44,19 @@ defmodule Mobilizon.Web.Email.Follow do subject = if follower_type == :Application do - gettext( - "Instance %{name} (%{domain}) requests to follow your instance", - name: follower.name, - domain: follower.domain - ) + # Mastodon instance actor has no name and an username equal to the domain + if is_nil(follower.name) and follower.preferred_username == follower.domain do + gettext( + "Instance %{domain} requests to follow your instance", + domain: follower.domain + ) + else + gettext( + "Instance %{name} (%{domain}) requests to follow your instance", + name: follower.name || follower.preferred_username, + domain: follower.domain + ) + end else gettext( "%{name} requests to follow your instance", diff --git a/lib/web/templates/email/instance_follow.html.heex b/lib/web/templates/email/instance_follow.html.heex index 87f4ca93a..f2e551f71 100644 --- a/lib/web/templates/email/instance_follow.html.heex +++ b/lib/web/templates/email/instance_follow.html.heex @@ -44,18 +44,10 @@ style="padding: 20px 30px 0px 30px; color: #474467; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" > <p style="margin: 0;"> - <%= if @follower.type == :Application do %> - <%= gettext("<b>%{name} (%{domain})</b> just requested to follow your instance.", - name: @follower.name, - domain: @follower.domain - ) - |> raw %> - <% else %> - <%= gettext("<b>%{name}</b> just requested to follow your instance.", - name: Mobilizon.Actors.Actor.display_name_and_username(@follower) - ) - |> raw %> - <% end %> + <%= gettext("<b>%{name}</b> just requested to follow your instance.", + name: Mobilizon.Actors.Actor.display_name_and_username(@follower) + ) + |> raw %> <br /> <%= if @follower.type == :Application do %> <%= gettext("If you accept, this instance will receive all of your public events.") %> @@ -74,9 +66,8 @@ > <p style="margin: 0;"> <%= gettext( - "Note: %{name} (%{domain}) following you doesn't necessarily imply that you follow this instance, but you can ask to follow them too.", - name: @follower.name, - domain: @follower.domain + "Note: %{name} following you doesn't necessarily imply that you follow this instance, but you can ask to follow them too.", + name: Mobilizon.Actors.Actor.display_name_and_username(@follower) ) %> </p> </td> diff --git a/lib/web/templates/email/instance_follow.text.eex b/lib/web/templates/email/instance_follow.text.eex index 642dd32bf..930a7ed2d 100644 --- a/lib/web/templates/email/instance_follow.text.eex +++ b/lib/web/templates/email/instance_follow.text.eex @@ -2,9 +2,9 @@ == -<%= if @follower.type == :Application do %><%= gettext "%{name} (%{domain}) just requested to follow your instance.", name: @follower.name, domain: @follower.domain %><% else %><%= gettext "%{name} just requested to follow your instance.", name: Mobilizon.Actors.Actor.display_name_and_username(@follower) %><% end %> +<%= if @follower.type == :Application do %><%= gettext "%{name} just requested to follow your instance.", name: Mobilizon.Actors.Actor.display_name_and_username(@follower) %><% end %> <%= if @follower.type == :Application do %><%= gettext "If you accept, this instance will receive all of your public events." %><% else %><%= gettext "If you accept, this profile will receive all of your public events." %><% end %> -<%= if @follower.type == :Application do %><%= gettext "Note: %{name} (%{domain}) following you doesn't necessarily imply that you follow this instance, but you can ask to follow them too.", name: @follower.name, domain: @follower.domain %><% end %> +<%= if @follower.type == :Application do %><%= gettext "Note: %{name} following you doesn't necessarily imply that you follow this instance, but you can ask to follow them too.", name: Mobilizon.Actors.Actor.display_name_and_username(@follower) %><% end %> <%= if @follower.type == :Application do %><%= gettext "To accept this invitation, head over to the instance's admin settings." %><% else %><%= gettext "To accept this invitation, head over to the profile's admin page." %><% end %> <%= if @follower.type == :Application do %><%= "#{Mobilizon.Web.Endpoint.url()}/settings/admin/relays/followers" %><% else %><%= "#{Mobilizon.Web.Endpoint.url()}/settings/admin/profiles/#{@follower.id}" %><% end %> diff --git a/test/graphql/resolvers/group_test.exs b/test/graphql/resolvers/group_test.exs index 6fa9944ba..225b8f842 100644 --- a/test/graphql/resolvers/group_test.exs +++ b/test/graphql/resolvers/group_test.exs @@ -8,7 +8,7 @@ defmodule Mobilizon.Web.Resolvers.GroupTest do alias Mobilizon.GraphQL.AbsintheHelpers @non_existent_username "nonexistent" - @new_group_params %{groupname: "new group"} + @new_group_params %{name: "new group", preferredUsername: "new_group"} setup %{conn: conn} do user = insert(:user) @@ -17,49 +17,75 @@ defmodule Mobilizon.Web.Resolvers.GroupTest do {:ok, conn: conn, actor: actor, user: user} end - describe "create a group" do - test "create_group/3 creates a group and check a group with this name does not already exist", + describe "create_group/3" do + @create_group_mutation """ + mutation CreateGroup( + $preferredUsername: String! + $name: String! + $summary: String + $avatar: MediaInput + $banner: MediaInput + ) { + createGroup( + preferredUsername: $preferredUsername + name: $name + summary: $summary + banner: $banner + avatar: $avatar + ) { + preferredUsername + type + banner { + id + url + } + } + } + """ + + test "creates a group and check a group with this name does not already exist", %{conn: conn, user: user} do - mutation = """ - mutation { - createGroup( - preferred_username: "#{@new_group_params.groupname}" - ) { - preferred_username, - type - } - } - """ + res = + conn + |> auth_conn(user) + |> AbsintheHelpers.graphql_query( + query: @create_group_mutation, + variables: @new_group_params + ) + + assert res["errors"] == nil + + assert res["data"]["createGroup"]["preferredUsername"] == + @new_group_params.preferredUsername + + assert res["data"]["createGroup"]["type"] == "GROUP" res = conn |> auth_conn(user) - |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + |> AbsintheHelpers.graphql_query( + query: @create_group_mutation, + variables: @new_group_params + ) - assert json_response(res, 200)["data"]["createGroup"]["preferred_username"] == - @new_group_params.groupname - - assert json_response(res, 200)["data"]["createGroup"]["type"] == "GROUP" - - mutation = """ - mutation { - createGroup( - preferred_username: "#{@new_group_params.groupname}" - ) { - preferred_username, - type - } - } - """ - - res = - conn - |> auth_conn(user) - |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) - - assert hd(json_response(res, 200)["errors"])["message"] == + assert hd(res["errors"])["message"] == "A profile or group with that name already exists" end + + test "doesn't creates a group if the username doesn't match the requirements", + %{conn: conn, user: user} do + res = + conn + |> auth_conn(user) + |> AbsintheHelpers.graphql_query( + query: @create_group_mutation, + variables: Map.put(@new_group_params, :preferredUsername, "no@way") + ) + + assert hd(res["errors"])["message"] == [ + "Username must only contain alphanumeric lowercased characters and underscores." + ] + end end describe "list groups" do diff --git a/test/graphql/resolvers/person_test.exs b/test/graphql/resolvers/person_test.exs index 173e45b1a..dabdf3dfb 100644 --- a/test/graphql/resolvers/person_test.exs +++ b/test/graphql/resolvers/person_test.exs @@ -13,23 +13,34 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do @non_existent_username "nonexistent" - describe "Person Resolver" do - @get_person_query """ - query Person($id: ID!) { - person(id: $id) { - preferredUsername, - } + @get_person_query """ + query Person($id: ID!) { + person(id: $id) { + preferredUsername, } - """ + } + """ - @fetch_person_query """ - query FetchPerson($preferredUsername: String!) { - fetchPerson(preferredUsername: $preferredUsername) { - preferredUsername, - } + @fetch_person_query """ + query FetchPerson($preferredUsername: String!) { + fetchPerson(preferredUsername: $preferredUsername) { + preferredUsername, } - """ + } + """ + @fetch_identities_query """ + { + identities { + avatar { + url + }, + preferredUsername, + } + } + """ + + describe "Getting a person" do test "get_person/3 returns a person by its username", %{conn: conn} do user = insert(:user) actor = insert(:actor, user: user) @@ -128,107 +139,114 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do assert json_response(res, 200)["data"]["loggedPerson"]["avatar"]["url"] =~ Endpoint.url() end + end - test "create_person/3 creates a new identity", context do + describe "create_person/3" do + @create_person_mutation """ + mutation CreatePerson( + $preferredUsername: String! + $name: String! + $summary: String + $avatar: MediaInput + $banner: MediaInput + ) { + createPerson( + preferredUsername: $preferredUsername + name: $name + summary: $summary + avatar: $avatar + banner: $banner + ) { + id + preferredUsername + avatar { + id, + url + }, + banner { + id, + name, + url + } + } + } + """ + + test "creates a new identity", %{conn: conn} do user = insert(:user) actor = insert(:actor, user: user) - mutation = """ - mutation { - createPerson( - preferredUsername: "new_identity", - name: "secret person", - summary: "no-one will know who I am" - ) { - id, - preferredUsername - } - } - """ - res = - context.conn - |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + conn + |> AbsintheHelpers.graphql_query( + query: @create_person_mutation, + variables: %{ + preferredUsername: "new_identity", + name: "secret person", + summary: "no-one will know who I am" + } + ) - assert json_response(res, 200)["data"]["createPerson"] == nil + assert res["data"]["createPerson"] == nil - assert hd(json_response(res, 200)["errors"])["message"] == + assert hd(res["errors"])["message"] == "You need to be logged in" res = - context.conn + conn |> auth_conn(user) - |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + |> AbsintheHelpers.graphql_query( + query: @create_person_mutation, + variables: %{ + preferredUsername: "new_identity", + name: "secret person", + summary: "no-one will know who I am" + } + ) - assert json_response(res, 200)["data"]["createPerson"]["preferredUsername"] == + assert res["data"]["createPerson"]["preferredUsername"] == "new_identity" - query = """ - { - identities { - avatar { - url - }, - preferredUsername, - } - } - """ - res = - context.conn - |> get("/api", AbsintheHelpers.query_skeleton(query, "identities")) + conn + |> AbsintheHelpers.graphql_query(query: @fetch_identities_query) - assert json_response(res, 200)["data"]["identities"] == nil + assert res["data"]["identities"] == nil - assert hd(json_response(res, 200)["errors"])["message"] == + assert hd(res["errors"])["message"] == "You need to be logged in" res = - context.conn + conn |> auth_conn(user) - |> get("/api", AbsintheHelpers.query_skeleton(query, "identities")) + |> AbsintheHelpers.graphql_query(query: @fetch_identities_query) - assert json_response(res, 200)["data"]["identities"] + assert res["data"]["identities"] |> Enum.map(fn identity -> Map.get(identity, "preferredUsername") end) |> MapSet.new() == MapSet.new([actor.preferred_username, "new_identity"]) end - test "create_person/3 with an avatar and an banner creates a new identity", context do + test "with an avatar and an banner creates a new identity", %{conn: conn} do user = insert(:user) insert(:actor, user: user) - mutation = """ - mutation { - createPerson( - preferredUsername: "new_identity", - name: "secret person", - summary: "no-one will know who I am", - banner: { - media: { - file: "landscape.jpg", - name: "irish landscape", - alt: "The beautiful atlantic way" - } - } - ) { - id, - preferredUsername - avatar { - id, - url - }, - banner { - id, - name, - url - } - } + variables = %{ + preferredUsername: "new_identity", + name: "secret person", + summary: "no-one will know who I am", + banner: %{ + media: %{ + file: "landscape.jpg", + name: "irish landscape", + alt: "The beautiful atlantic way" } - """ + } + } map = %{ - "query" => mutation, + "query" => @create_person_mutation, + "variables" => variables, "landscape.jpg" => %Plug.Upload{ path: "test/fixtures/picture.png", filename: "landscape.jpg" @@ -236,34 +254,63 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do } res = - context.conn + conn |> put_req_header("content-type", "multipart/form-data") - |> post("/api", map) + |> post( + "/api", + map + ) + |> json_response(200) - assert json_response(res, 200)["data"]["createPerson"] == nil + assert res["data"]["createPerson"] == nil - assert hd(json_response(res, 200)["errors"])["message"] == + assert hd(res["errors"])["message"] == "You need to be logged in" res = - context.conn + conn |> auth_conn(user) |> put_req_header("content-type", "multipart/form-data") - |> post("/api", map) + |> post( + "/api", + map + ) + |> json_response(200) - assert json_response(res, 200)["data"]["createPerson"]["preferredUsername"] == + assert res["data"]["createPerson"]["preferredUsername"] == "new_identity" - assert json_response(res, 200)["data"]["createPerson"]["banner"]["id"] + assert res["data"]["createPerson"]["banner"]["id"] - assert json_response(res, 200)["data"]["createPerson"]["banner"]["name"] == + assert res["data"]["createPerson"]["banner"]["name"] == "The beautiful atlantic way" - assert json_response(res, 200)["data"]["createPerson"]["banner"]["url"] =~ + assert res["data"]["createPerson"]["banner"]["url"] =~ Endpoint.url() <> "/media/" end - test "update_person/3 updates an existing identity", context do + test "with an username that is not acceptable", %{conn: conn} do + user = insert(:user) + _actor = insert(:actor, user: user) + + res = + conn + |> auth_conn(user) + |> AbsintheHelpers.graphql_query( + query: @create_person_mutation, + variables: %{ + preferredUsername: "me@no", + name: "wrong person" + } + ) + + assert hd(res["errors"])["message"] == + ["Username must only contain alphanumeric lowercased characters and underscores."] + end + end + + describe "update_person/3" do + test "updates an existing identity", context do user = insert(:user) %Actor{id: person_id} = insert(:actor, user: user, preferred_username: "riri") @@ -333,7 +380,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do assert res_person["banner"]["url"] =~ Endpoint.url() <> "/media/" end - test "update_person/3 should fail to update a not owned identity", context do + test "should fail to update a not owned identity", context do user1 = insert(:user) user2 = insert(:user) %Actor{id: person_id} = insert(:actor, user: user2, preferred_username: "riri") @@ -360,7 +407,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do "Profile is not owned by authenticated user" end - test "update_person/3 should fail to update a not existing identity", context do + test "should fail to update a not existing identity", context do user = insert(:user) insert(:actor, user: user, preferred_username: "riri") @@ -385,8 +432,10 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do assert hd(json_response(res, 200)["errors"])["message"] == "Profile not found" end + end - test "delete_person/3 should fail to update a not owned identity", context do + describe "delete_person/3" do + test "should fail to update a not owned identity", context do user1 = insert(:user) user2 = insert(:user) %Actor{id: person_id} = insert(:actor, user: user2, preferred_username: "riri") @@ -410,7 +459,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do "Profile is not owned by authenticated user" end - test "delete_person/3 should fail to delete a not existing identity", context do + test "should fail to delete a not existing identity", context do user = insert(:user) insert(:actor, user: user, preferred_username: "riri") @@ -433,7 +482,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do "Profile not found" end - test "delete_person/3 should fail to delete the last user identity", context do + test "should fail to delete the last user identity", context do user = insert(:user) %Actor{id: person_id} = insert(:actor, user: user, preferred_username: "riri") @@ -456,7 +505,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do "Cannot remove the last identity of a user" end - test "delete_person/3 should fail to delete an identity that is the last admin of a group", + test "should fail to delete an identity that is the last admin of a group", context do group = insert(:group) classic_user = insert(:user) @@ -488,7 +537,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do "Cannot remove the last administrator of a group" end - test "delete_person/3 should delete an actor identity", context do + test "should delete an actor identity", context do user = insert(:user) %Actor{id: person_id} = insert(:actor, user: user, preferred_username: "riri") insert(:actor, user: user, preferred_username: "fifi") diff --git a/test/mobilizon/actors/actor_test.exs b/test/mobilizon/actors/actor_test.exs new file mode 100644 index 000000000..f48c5971e --- /dev/null +++ b/test/mobilizon/actors/actor_test.exs @@ -0,0 +1,70 @@ +defmodule Mobilizon.ActorTest do + use Mobilizon.DataCase + alias Mobilizon.Actors.Actor + + describe "display_name_and_username/1" do + test "returns correctly if everything is given" do + assert "hello (@someone@remote.tld)" == + Actor.display_name_and_username(%Actor{ + name: "hello", + domain: "remote.tld", + preferred_username: "someone" + }) + end + + test "returns for a local actor" do + assert "hello (@someone)" == + Actor.display_name_and_username(%Actor{ + name: "hello", + domain: nil, + preferred_username: "someone" + }) + + assert "hello (@someone)" == + Actor.display_name_and_username(%Actor{ + name: "hello", + domain: "", + preferred_username: "someone" + }) + end + + test "returns nil if the name is all that's given" do + assert nil == Actor.display_name_and_username(%Actor{name: "hello"}) + end + + test "returns with just the username if that's all that's given" do + assert "someone" == + Actor.display_name_and_username(%Actor{preferred_username: "someone"}) + end + + test "returns an appropriate name for a Mobilizon instance actor" do + assert "My Mobilizon Instance (remote.tld)" == + Actor.display_name_and_username(%Actor{ + name: "My Mobilizon Instance", + domain: "remote.tld", + preferred_username: "relay", + type: :Application + }) + end + + test "returns an appropriate name for a Mastodon instance actor" do + assert "remote.tld" == + Actor.display_name_and_username(%Actor{ + name: nil, + domain: "remote.tld", + preferred_username: "remote.tld", + type: :Application + }) + end + end + + describe "display_name/1" do + test "with name" do + assert "hello" == Actor.display_name(%Actor{preferred_username: "someone", name: "hello"}) + end + + test "without name" do + assert "someone" == Actor.display_name(%Actor{preferred_username: "someone"}) + end + end +end diff --git a/test/mobilizon/actors/actors_test.exs b/test/mobilizon/actors/actors_test.exs index 423b5c758..4ddfc333a 100644 --- a/test/mobilizon/actors/actors_test.exs +++ b/test/mobilizon/actors/actors_test.exs @@ -25,7 +25,7 @@ defmodule Mobilizon.ActorsTest do suspended: true, uri: "some uri", url: "some url", - preferred_username: "some username" + preferred_username: "some_username" } @update_attrs %{ summary: "some updated description", @@ -35,7 +35,7 @@ defmodule Mobilizon.ActorsTest do suspended: false, uri: "some updated uri", url: "some updated url", - preferred_username: "some updated username" + preferred_username: "some_updated_username" } @invalid_attrs %{ summary: nil, @@ -234,7 +234,7 @@ defmodule Mobilizon.ActorsTest do assert actor.domain == "some domain" assert actor.keys == "some keypair" assert actor.suspended - assert actor.preferred_username == "some username" + assert actor.preferred_username == "some_username" end test "create_actor/1 with empty data returns error changeset" do @@ -381,13 +381,13 @@ defmodule Mobilizon.ActorsTest do @valid_attrs %{ summary: "some description", suspended: true, - preferred_username: "some-title", + preferred_username: "some_title", name: "Some Title" } @update_attrs %{ summary: "some updated description", suspended: false, - preferred_username: "some-updated-title", + preferred_username: "some_updated_title", name: "Some Updated Title" } @invalid_attrs %{summary: nil, suspended: nil, preferred_username: nil, name: nil} @@ -400,7 +400,7 @@ defmodule Mobilizon.ActorsTest do assert group.summary == "some description" refute group.suspended - assert group.preferred_username == "some-title" + assert group.preferred_username == "some_title" end test "create_group/1 with an existing profile username fails" do